Skip to content

fix(money): Money API balance freshness + minBlock RPC fallback - #10422

Closed
ffmcgee725 wants to merge 6 commits into
mainfrom
jc/MUSD-1425
Closed

ffmcgee725 wants to merge 6 commits into
mainfrom
jc/MUSD-1425

Conversation

@ffmcgee725

@ffmcgee725 ffmcgee725 commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Explanation

  • @metamask/money-account-api-data-service: optional fetchPositions(address, { fresh }) cancels in-flight reads, invalidates the client cache, and fetches with Cache-Control: no-cache without writing through TanStack — so a rejected freshness body cannot seed the next steady-state poll. Asks the Money API to skip its Nest response cache when the server honors the header. Additive optional musd_balance_updated_at on the positions balance summary.
  • @metamask/money-account-balance-service: fetchBalanceWithFallback surfaces API freshness (asOfBlock, asOfTimestamp, dataFreshness, indexerLagSeconds, musdBalanceUpdatedAt) and accepts additive { minBlock, fresh }. When the API indexer watermark is behind minBlock, throws MoneyAccountBalanceStaleError, invalidates the positions query, and falls back to pending-tag RPC without reporting a defect. minBlock implies a cache-bypassing positions read.
  • @metamask/base-data-service: protected cancelQueries for forced-refresh races.
  • Client messengers (mobile + extension): delegate MoneyAccountApiDataService:invalidateQueries to the balance service.

Test plan

  • money-account-api-data-service unit tests (fresh header + no steady-state cache seed)
  • money-account-balance-service unit tests (freshness fields, minBlock → RPC, invalidate on stale, api-only stale, musdBalanceUpdatedAt mapping)
  • Mobile follow-up (separate): pass { fresh: true, minBlock: receipt.blockNumber } from useRefreshMoneyBalanceOnTxConfirm

Note

Medium Risk
Changes canonical balance fetch paths, TanStack cache invalidation, and post-tx fallback behavior; mistakes could show stale balances or over-fetch, though options are additive and heavily unit-tested.

Overview
Adds post-transaction balance refresh plumbing so Money API reads can bypass client/server caches without poisoning steady-state TanStack entries, and so lagging indexer watermarks can fall back to RPC once.

@metamask/base-data-service exposes protected cancelQueries and executeWithPolicy so subclasses can abort in-flight cached reads and still get retries/circuit-breaker on one-off network calls that must not write through TanStack.

@metamask/money-account-api-data-service extends fetchPositions with optional { fresh: true }: cancel + invalidate the positions query, send Cache-Control: no-cache, fetch via policy without seeding the query cache, and accept additive musd_balance_updated_at on the balance summary.

@metamask/money-account-balance-service extends fetchBalanceWithFallback with FetchBalanceWithFallbackOptions (fresh, minBlock). API-sourced results now include freshness metadata (asOfBlock, asOfTimestamp, etc.). When as_of_block < minBlock, it throws MoneyAccountBalanceStaleError (not reported as a defect), invalidates the positions cache via messenger MoneyAccountApiDataService:invalidateQueries, and falls back to RPC when policy allows; minBlock implies a fresh positions read.

Reviewed by Cursor Bugbot for commit cf53ee5. Bugbot is set up for automated code reviews on this repo. Configure here.

@ffmcgee725

Copy link
Copy Markdown
Member Author

@metamaskbot publish-preview

@ffmcgee725
ffmcgee725 marked this pull request as ready for review September 24, 2026 10:37
@ffmcgee725
ffmcgee725 requested review from a team as code owners September 24, 2026 10:37
@github-actions

Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@10.0.1-preview-acac4ba67
@metamask-previews/accounts-controller@40.0.0-preview-acac4ba67
@metamask-previews/address-book-controller@8.0.0-preview-acac4ba67
@metamask-previews/ai-controllers@2.0.0-preview-acac4ba67
@metamask-previews/analytics-controller@3.2.0-preview-acac4ba67
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-acac4ba67
@metamask-previews/announcement-controller@9.0.0-preview-acac4ba67
@metamask-previews/app-metadata-controller@3.0.0-preview-acac4ba67
@metamask-previews/approval-controller@10.0.0-preview-acac4ba67
@metamask-previews/assets-controller@16.1.0-preview-acac4ba67
@metamask-previews/assets-controllers@112.0.2-preview-acac4ba67
@metamask-previews/authenticated-user-storage@4.1.0-preview-acac4ba67
@metamask-previews/base-controller@10.0.0-preview-acac4ba67
@metamask-previews/base-data-service@2.0.0-preview-acac4ba67
@metamask-previews/bitcoin-regtest-up@2.0.0-preview-acac4ba67
@metamask-previews/bridge-controller@81.3.0-preview-acac4ba67
@metamask-previews/bridge-status-controller@76.3.0-preview-acac4ba67
@metamask-previews/build-utils@4.0.0-preview-acac4ba67
@metamask-previews/chain-agnostic-permission@2.0.0-preview-acac4ba67
@metamask-previews/chomp-api-service@5.0.0-preview-acac4ba67
@metamask-previews/claims-controller@1.0.1-preview-acac4ba67
@metamask-previews/client-controller@2.0.0-preview-acac4ba67
@metamask-previews/client-utils@3.0.2-preview-acac4ba67
@metamask-previews/compliance-controller@3.0.0-preview-acac4ba67
@metamask-previews/composable-controller@13.0.0-preview-acac4ba67
@metamask-previews/config-registry-controller@4.0.0-preview-acac4ba67
@metamask-previews/connectivity-controller@1.0.0-preview-acac4ba67
@metamask-previews/controller-utils@13.0.0-preview-acac4ba67
@metamask-previews/core-backend@11.0.0-preview-acac4ba67
@metamask-previews/cryptography@0.0.0-preview-acac4ba67
@metamask-previews/delegation-controller@4.0.0-preview-acac4ba67
@metamask-previews/earn-controller@13.0.0-preview-acac4ba67
@metamask-previews/eip-5792-middleware@4.0.0-preview-acac4ba67
@metamask-previews/eip-7702-internal-rpc-middleware@1.0.0-preview-acac4ba67
@metamask-previews/eip1193-permission-middleware@3.0.0-preview-acac4ba67
@metamask-previews/eth-block-tracker@16.0.0-preview-acac4ba67
@metamask-previews/eth-json-rpc-middleware@25.0.0-preview-acac4ba67
@metamask-previews/eth-json-rpc-provider@7.0.0-preview-acac4ba67
@metamask-previews/foundryup@2.0.0-preview-acac4ba67
@metamask-previews/gas-fee-controller@27.0.0-preview-acac4ba67
@metamask-previews/gator-permissions-controller@6.0.0-preview-acac4ba67
@metamask-previews/geolocation-controller@2.0.0-preview-acac4ba67
@metamask-previews/java-tron-up@2.0.0-preview-acac4ba67
@metamask-previews/json-rpc-engine@11.0.0-preview-acac4ba67
@metamask-previews/json-rpc-middleware-stream@9.0.0-preview-acac4ba67
@metamask-previews/keyring-controller@28.1.0-preview-acac4ba67
@metamask-previews/kyc-controller@0.4.0-preview-acac4ba67
@metamask-previews/local-node-utils@2.0.0-preview-acac4ba67
@metamask-previews/logging-controller@10.0.0-preview-acac4ba67
@metamask-previews/message-manager@15.0.0-preview-acac4ba67
@metamask-previews/messenger@3.0.0-preview-acac4ba67
@metamask-previews/messenger-cli@1.0.0-preview-acac4ba67
@metamask-previews/money-account-api-data-service@1.0.0-preview-acac4ba67
@metamask-previews/money-account-balance-service@3.0.0-preview-acac4ba67
@metamask-previews/money-account-controller@2.0.0-preview-acac4ba67
@metamask-previews/money-account-upgrade-controller@5.0.0-preview-acac4ba67
@metamask-previews/money-account-utils@2.0.0-preview-acac4ba67
@metamask-previews/multichain-account-service@14.0.0-preview-acac4ba67
@metamask-previews/multichain-api-middleware@5.0.0-preview-acac4ba67
@metamask-previews/multichain-network-controller@4.0.0-preview-acac4ba67
@metamask-previews/multichain-transactions-controller@8.0.0-preview-acac4ba67
@metamask-previews/name-controller@10.0.0-preview-acac4ba67
@metamask-previews/network-connection-banner-controller@1.0.0-preview-acac4ba67
@metamask-previews/network-controller@37.0.0-preview-acac4ba67
@metamask-previews/network-enablement-controller@7.0.0-preview-acac4ba67
@metamask-previews/notification-services-controller@29.0.0-preview-acac4ba67
@metamask-previews/passkey-controller@4.1.0-preview-acac4ba67
@metamask-previews/permission-controller@14.0.0-preview-acac4ba67
@metamask-previews/permission-log-controller@6.0.0-preview-acac4ba67
@metamask-previews/perps-controller@18.0.0-preview-acac4ba67
@metamask-previews/phishing-controller@18.1.0-preview-acac4ba67
@metamask-previews/platform-api-docs@0.2.1-preview-acac4ba67
@metamask-previews/polling-controller@17.0.0-preview-acac4ba67
@metamask-previews/preferences-controller@24.0.0-preview-acac4ba67
@metamask-previews/profile-metrics-controller@5.1.0-preview-acac4ba67
@metamask-previews/profile-sync-controller@32.3.1-preview-acac4ba67
@metamask-previews/ramps-controller@25.1.0-preview-acac4ba67
@metamask-previews/rate-limit-controller@8.0.0-preview-acac4ba67
@metamask-previews/react-data-query@2.0.0-preview-acac4ba67
@metamask-previews/remote-feature-flag-controller@7.0.0-preview-acac4ba67
@metamask-previews/sample-controllers@6.0.0-preview-acac4ba67
@metamask-previews/seedless-onboarding-controller@11.0.0-preview-acac4ba67
@metamask-previews/selected-network-controller@27.0.0-preview-acac4ba67
@metamask-previews/sentinel-api-service@2.0.0-preview-acac4ba67
@metamask-previews/shield-controller@7.0.1-preview-acac4ba67
@metamask-previews/signature-controller@40.0.0-preview-acac4ba67
@metamask-previews/smart-transactions-controller@27.0.1-preview-acac4ba67
@metamask-previews/snap-account-service@3.0.0-preview-acac4ba67
@metamask-previews/social-controllers@3.1.0-preview-acac4ba67
@metamask-previews/solana-test-validator-up@2.0.0-preview-acac4ba67
@metamask-previews/stellar-quickstart-up@0.0.0-preview-acac4ba67
@metamask-previews/storage-service@2.0.0-preview-acac4ba67
@metamask-previews/subscription-controller@9.1.0-preview-acac4ba67
@metamask-previews/transaction-controller@71.0.0-preview-acac4ba67
@metamask-previews/transaction-pay-controller@29.1.1-preview-acac4ba67
@metamask-previews/user-operation-controller@42.0.0-preview-acac4ba67
@metamask-previews/utils@12.0.0-preview-acac4ba67
@metamask-previews/wallet@14.0.1-preview-acac4ba67
@metamask-previews/wallet-cli@0.0.0-preview-acac4ba67

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b228946. Configure here.

// An in-flight read started before the caller's reference point would
// otherwise be shared with this one under the same query key.
await this.cancelQueries({ queryKey });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fresh RPC cancel fails sibling fetches

Medium Severity

A fresh getMoneyAccountBalance cancels any in-flight read on the same query key, and the rejected caller surfaces CancelledError. fetchBalanceWithFallback treats that as a real source failure, so it falls back to the Money API or throws MoneyAccountBalanceFetchError. Concurrent post-tx refreshes, or a refresh overlapping a steady-state poll, can therefore return a stale API balance or fail entirely even though another RPC read is succeeding.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b228946. Configure here.

@ffmcgee725

Copy link
Copy Markdown
Member Author

@metamaskbot publish-preview

@github-actions

Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@10.0.1-preview-48ac8a8c1
@metamask-previews/accounts-controller@40.0.0-preview-48ac8a8c1
@metamask-previews/address-book-controller@8.0.0-preview-48ac8a8c1
@metamask-previews/ai-controllers@2.0.0-preview-48ac8a8c1
@metamask-previews/analytics-controller@3.2.0-preview-48ac8a8c1
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-48ac8a8c1
@metamask-previews/announcement-controller@9.0.0-preview-48ac8a8c1
@metamask-previews/app-metadata-controller@3.0.0-preview-48ac8a8c1
@metamask-previews/approval-controller@10.0.0-preview-48ac8a8c1
@metamask-previews/assets-controller@16.1.0-preview-48ac8a8c1
@metamask-previews/assets-controllers@112.0.2-preview-48ac8a8c1
@metamask-previews/authenticated-user-storage@4.1.0-preview-48ac8a8c1
@metamask-previews/base-controller@10.0.0-preview-48ac8a8c1
@metamask-previews/base-data-service@2.0.0-preview-48ac8a8c1
@metamask-previews/bitcoin-regtest-up@2.0.0-preview-48ac8a8c1
@metamask-previews/bridge-controller@81.3.0-preview-48ac8a8c1
@metamask-previews/bridge-status-controller@76.3.0-preview-48ac8a8c1
@metamask-previews/build-utils@4.0.0-preview-48ac8a8c1
@metamask-previews/chain-agnostic-permission@2.0.0-preview-48ac8a8c1
@metamask-previews/chomp-api-service@5.0.0-preview-48ac8a8c1
@metamask-previews/claims-controller@1.0.1-preview-48ac8a8c1
@metamask-previews/client-controller@2.0.0-preview-48ac8a8c1
@metamask-previews/client-utils@3.0.2-preview-48ac8a8c1
@metamask-previews/compliance-controller@3.0.0-preview-48ac8a8c1
@metamask-previews/composable-controller@13.0.0-preview-48ac8a8c1
@metamask-previews/config-registry-controller@4.0.0-preview-48ac8a8c1
@metamask-previews/connectivity-controller@1.0.0-preview-48ac8a8c1
@metamask-previews/controller-utils@13.0.0-preview-48ac8a8c1
@metamask-previews/core-backend@11.0.0-preview-48ac8a8c1
@metamask-previews/cryptography@0.0.0-preview-48ac8a8c1
@metamask-previews/delegation-controller@4.0.0-preview-48ac8a8c1
@metamask-previews/earn-controller@13.0.0-preview-48ac8a8c1
@metamask-previews/eip-5792-middleware@4.0.0-preview-48ac8a8c1
@metamask-previews/eip-7702-internal-rpc-middleware@1.0.0-preview-48ac8a8c1
@metamask-previews/eip1193-permission-middleware@3.0.0-preview-48ac8a8c1
@metamask-previews/eth-block-tracker@16.0.0-preview-48ac8a8c1
@metamask-previews/eth-json-rpc-middleware@25.0.0-preview-48ac8a8c1
@metamask-previews/eth-json-rpc-provider@7.0.0-preview-48ac8a8c1
@metamask-previews/foundryup@2.0.0-preview-48ac8a8c1
@metamask-previews/gas-fee-controller@27.0.0-preview-48ac8a8c1
@metamask-previews/gator-permissions-controller@6.0.0-preview-48ac8a8c1
@metamask-previews/geolocation-controller@2.0.0-preview-48ac8a8c1
@metamask-previews/java-tron-up@2.0.0-preview-48ac8a8c1
@metamask-previews/json-rpc-engine@11.0.0-preview-48ac8a8c1
@metamask-previews/json-rpc-middleware-stream@9.0.0-preview-48ac8a8c1
@metamask-previews/keyring-controller@28.1.0-preview-48ac8a8c1
@metamask-previews/kyc-controller@0.4.0-preview-48ac8a8c1
@metamask-previews/local-node-utils@2.0.0-preview-48ac8a8c1
@metamask-previews/logging-controller@10.0.0-preview-48ac8a8c1
@metamask-previews/message-manager@15.0.0-preview-48ac8a8c1
@metamask-previews/messenger@3.0.0-preview-48ac8a8c1
@metamask-previews/messenger-cli@1.0.0-preview-48ac8a8c1
@metamask-previews/money-account-api-data-service@1.0.0-preview-48ac8a8c1
@metamask-previews/money-account-balance-service@3.0.0-preview-48ac8a8c1
@metamask-previews/money-account-controller@2.0.0-preview-48ac8a8c1
@metamask-previews/money-account-upgrade-controller@5.0.0-preview-48ac8a8c1
@metamask-previews/money-account-utils@2.0.0-preview-48ac8a8c1
@metamask-previews/multichain-account-service@14.0.0-preview-48ac8a8c1
@metamask-previews/multichain-api-middleware@5.0.0-preview-48ac8a8c1
@metamask-previews/multichain-network-controller@4.0.0-preview-48ac8a8c1
@metamask-previews/multichain-transactions-controller@8.0.0-preview-48ac8a8c1
@metamask-previews/name-controller@10.0.0-preview-48ac8a8c1
@metamask-previews/network-connection-banner-controller@1.0.0-preview-48ac8a8c1
@metamask-previews/network-controller@37.0.0-preview-48ac8a8c1
@metamask-previews/network-enablement-controller@7.0.0-preview-48ac8a8c1
@metamask-previews/notification-services-controller@29.0.0-preview-48ac8a8c1
@metamask-previews/passkey-controller@4.1.0-preview-48ac8a8c1
@metamask-previews/permission-controller@14.0.0-preview-48ac8a8c1
@metamask-previews/permission-log-controller@6.0.0-preview-48ac8a8c1
@metamask-previews/perps-controller@18.0.0-preview-48ac8a8c1
@metamask-previews/phishing-controller@18.1.0-preview-48ac8a8c1
@metamask-previews/platform-api-docs@0.2.1-preview-48ac8a8c1
@metamask-previews/polling-controller@17.0.0-preview-48ac8a8c1
@metamask-previews/preferences-controller@24.0.0-preview-48ac8a8c1
@metamask-previews/profile-metrics-controller@5.1.0-preview-48ac8a8c1
@metamask-previews/profile-sync-controller@32.3.1-preview-48ac8a8c1
@metamask-previews/ramps-controller@25.1.0-preview-48ac8a8c1
@metamask-previews/rate-limit-controller@8.0.0-preview-48ac8a8c1
@metamask-previews/react-data-query@2.0.0-preview-48ac8a8c1
@metamask-previews/remote-feature-flag-controller@7.0.0-preview-48ac8a8c1
@metamask-previews/sample-controllers@6.0.0-preview-48ac8a8c1
@metamask-previews/seedless-onboarding-controller@11.0.0-preview-48ac8a8c1
@metamask-previews/selected-network-controller@27.0.0-preview-48ac8a8c1
@metamask-previews/sentinel-api-service@2.0.0-preview-48ac8a8c1
@metamask-previews/shield-controller@7.0.1-preview-48ac8a8c1
@metamask-previews/signature-controller@40.0.0-preview-48ac8a8c1
@metamask-previews/smart-transactions-controller@27.0.1-preview-48ac8a8c1
@metamask-previews/snap-account-service@3.0.0-preview-48ac8a8c1
@metamask-previews/social-controllers@3.1.0-preview-48ac8a8c1
@metamask-previews/solana-test-validator-up@2.0.0-preview-48ac8a8c1
@metamask-previews/stellar-quickstart-up@0.0.0-preview-48ac8a8c1
@metamask-previews/storage-service@2.0.0-preview-48ac8a8c1
@metamask-previews/subscription-controller@9.1.0-preview-48ac8a8c1
@metamask-previews/transaction-controller@71.0.0-preview-48ac8a8c1
@metamask-previews/transaction-pay-controller@29.1.1-preview-48ac8a8c1
@metamask-previews/user-operation-controller@42.0.0-preview-48ac8a8c1
@metamask-previews/utils@12.0.0-preview-48ac8a8c1
@metamask-previews/wallet@14.0.1-preview-48ac8a8c1
@metamask-previews/wallet-cli@0.0.0-preview-48ac8a8c1

| RemoteFeatureFlagControllerGetStateAction
| MoneyAccountApiDataServiceFetchPositionsAction;
| MoneyAccountApiDataServiceFetchPositionsAction
| MoneyAccountApiDataServiceInvalidateQueriesAction;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this technically make it a breaking change? Since the messenger might need to be altered?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point actually, let me address this

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (options.fresh) {
// An in-flight read started before the caller's reference point would
// otherwise be shared with this one under the same query key.
await this.cancelQueries({ queryKey });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cancelling queries in tanStack will cause other callers to get the previously cached values. So if we make two 'fresh' reads at the same time, the first one will end up getting the old value.

I'm not sure to what extent that's a problem - but it could lead to some confusing situations.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay with it here since the intended caller is a single post-tx refresh (overlapping dual-fresh isn’t something we expect), and it’s still better than sharing a pre-tx in-flight promise.
Happy to harden if you think it's worth currently (e.g. revert: false, or bypass fetchQuery on the RPC path like we do for positions), or later if it starts biting us.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'm happy to go ahead. As you say we can update this later.

await this.invalidateQueries({ queryKey: [...queryKey] });
// Bypass fetchQuery: a minBlock-rejected body must not become the
// steady-state cache entry with a fresh dataUpdatedAt.
return await this.#fetchPositionsFromNetwork(normalizedAddress, true);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like when we go down this path we don't cache the result - could we find a way to insert it into the cache?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Money Account API handles caching in this path 👍🏾

Jwhiles
Jwhiles previously approved these changes Sep 24, 2026
@ffmcgee725

Copy link
Copy Markdown
Member Author

@metamaskbot publish-preview

@github-actions

Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@10.0.1-preview-1efee3183
@metamask-previews/accounts-controller@40.0.0-preview-1efee3183
@metamask-previews/address-book-controller@8.0.0-preview-1efee3183
@metamask-previews/ai-controllers@2.0.0-preview-1efee3183
@metamask-previews/analytics-controller@3.2.0-preview-1efee3183
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-1efee3183
@metamask-previews/announcement-controller@9.0.0-preview-1efee3183
@metamask-previews/app-metadata-controller@3.0.0-preview-1efee3183
@metamask-previews/approval-controller@10.0.0-preview-1efee3183
@metamask-previews/assets-controller@16.1.0-preview-1efee3183
@metamask-previews/assets-controllers@112.0.2-preview-1efee3183
@metamask-previews/authenticated-user-storage@4.1.0-preview-1efee3183
@metamask-previews/base-controller@10.0.0-preview-1efee3183
@metamask-previews/base-data-service@2.0.0-preview-1efee3183
@metamask-previews/bitcoin-regtest-up@2.0.0-preview-1efee3183
@metamask-previews/bridge-controller@81.3.0-preview-1efee3183
@metamask-previews/bridge-status-controller@76.3.0-preview-1efee3183
@metamask-previews/build-utils@4.0.0-preview-1efee3183
@metamask-previews/chain-agnostic-permission@2.0.0-preview-1efee3183
@metamask-previews/chomp-api-service@5.0.0-preview-1efee3183
@metamask-previews/claims-controller@1.0.1-preview-1efee3183
@metamask-previews/client-controller@2.0.0-preview-1efee3183
@metamask-previews/client-utils@3.0.2-preview-1efee3183
@metamask-previews/compliance-controller@3.0.0-preview-1efee3183
@metamask-previews/composable-controller@13.0.0-preview-1efee3183
@metamask-previews/config-registry-controller@4.0.0-preview-1efee3183
@metamask-previews/connectivity-controller@1.0.0-preview-1efee3183
@metamask-previews/controller-utils@13.0.0-preview-1efee3183
@metamask-previews/core-backend@11.0.0-preview-1efee3183
@metamask-previews/cryptography@0.0.0-preview-1efee3183
@metamask-previews/delegation-controller@4.0.0-preview-1efee3183
@metamask-previews/earn-controller@13.0.0-preview-1efee3183
@metamask-previews/eip-5792-middleware@4.0.0-preview-1efee3183
@metamask-previews/eip-7702-internal-rpc-middleware@1.0.0-preview-1efee3183
@metamask-previews/eip1193-permission-middleware@3.0.0-preview-1efee3183
@metamask-previews/eth-block-tracker@16.0.0-preview-1efee3183
@metamask-previews/eth-json-rpc-middleware@25.0.0-preview-1efee3183
@metamask-previews/eth-json-rpc-provider@7.0.0-preview-1efee3183
@metamask-previews/foundryup@2.0.0-preview-1efee3183
@metamask-previews/gas-fee-controller@27.0.0-preview-1efee3183
@metamask-previews/gator-permissions-controller@6.0.0-preview-1efee3183
@metamask-previews/geolocation-controller@2.0.0-preview-1efee3183
@metamask-previews/java-tron-up@2.0.0-preview-1efee3183
@metamask-previews/json-rpc-engine@11.0.0-preview-1efee3183
@metamask-previews/json-rpc-middleware-stream@9.0.0-preview-1efee3183
@metamask-previews/keyring-controller@28.1.0-preview-1efee3183
@metamask-previews/kyc-controller@0.4.0-preview-1efee3183
@metamask-previews/local-node-utils@2.0.0-preview-1efee3183
@metamask-previews/logging-controller@10.0.0-preview-1efee3183
@metamask-previews/message-manager@15.0.0-preview-1efee3183
@metamask-previews/messenger@3.0.0-preview-1efee3183
@metamask-previews/messenger-cli@1.0.0-preview-1efee3183
@metamask-previews/money-account-api-data-service@1.0.0-preview-1efee3183
@metamask-previews/money-account-balance-service@3.0.0-preview-1efee3183
@metamask-previews/money-account-controller@2.0.0-preview-1efee3183
@metamask-previews/money-account-upgrade-controller@5.0.0-preview-1efee3183
@metamask-previews/money-account-utils@2.0.0-preview-1efee3183
@metamask-previews/multichain-account-service@14.0.0-preview-1efee3183
@metamask-previews/multichain-api-middleware@5.0.0-preview-1efee3183
@metamask-previews/multichain-network-controller@4.0.0-preview-1efee3183
@metamask-previews/multichain-transactions-controller@8.0.0-preview-1efee3183
@metamask-previews/name-controller@10.0.0-preview-1efee3183
@metamask-previews/network-connection-banner-controller@1.0.0-preview-1efee3183
@metamask-previews/network-controller@37.0.0-preview-1efee3183
@metamask-previews/network-enablement-controller@7.0.0-preview-1efee3183
@metamask-previews/notification-services-controller@29.0.0-preview-1efee3183
@metamask-previews/passkey-controller@4.1.0-preview-1efee3183
@metamask-previews/permission-controller@14.0.0-preview-1efee3183
@metamask-previews/permission-log-controller@6.0.0-preview-1efee3183
@metamask-previews/perps-controller@18.0.0-preview-1efee3183
@metamask-previews/phishing-controller@18.1.0-preview-1efee3183
@metamask-previews/platform-api-docs@0.2.1-preview-1efee3183
@metamask-previews/polling-controller@17.0.0-preview-1efee3183
@metamask-previews/preferences-controller@24.0.0-preview-1efee3183
@metamask-previews/profile-metrics-controller@5.1.0-preview-1efee3183
@metamask-previews/profile-sync-controller@32.3.1-preview-1efee3183
@metamask-previews/ramps-controller@25.1.0-preview-1efee3183
@metamask-previews/rate-limit-controller@8.0.0-preview-1efee3183
@metamask-previews/react-data-query@2.0.0-preview-1efee3183
@metamask-previews/remote-feature-flag-controller@7.0.0-preview-1efee3183
@metamask-previews/sample-controllers@6.0.0-preview-1efee3183
@metamask-previews/seedless-onboarding-controller@11.0.0-preview-1efee3183
@metamask-previews/selected-network-controller@27.0.0-preview-1efee3183
@metamask-previews/sentinel-api-service@2.0.0-preview-1efee3183
@metamask-previews/shield-controller@7.0.1-preview-1efee3183
@metamask-previews/signature-controller@40.0.0-preview-1efee3183
@metamask-previews/smart-transactions-controller@27.0.1-preview-1efee3183
@metamask-previews/snap-account-service@3.0.0-preview-1efee3183
@metamask-previews/social-controllers@3.1.0-preview-1efee3183
@metamask-previews/solana-test-validator-up@2.0.0-preview-1efee3183
@metamask-previews/stellar-quickstart-up@0.0.0-preview-1efee3183
@metamask-previews/storage-service@2.0.0-preview-1efee3183
@metamask-previews/subscription-controller@9.1.0-preview-1efee3183
@metamask-previews/transaction-controller@71.0.0-preview-1efee3183
@metamask-previews/transaction-pay-controller@29.1.1-preview-1efee3183
@metamask-previews/user-operation-controller@42.0.0-preview-1efee3183
@metamask-previews/utils@12.0.0-preview-1efee3183
@metamask-previews/wallet@14.0.1-preview-1efee3183
@metamask-previews/wallet-cli@0.0.0-preview-1efee3183

@Matt561 Matt561 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a couple of comments but don't want to delay merging if you disagree 👍

await this.invalidateQueries({ queryKey: [...queryKey] });
// Bypass fetchQuery: a minBlock-rejected body must not become the
// steady-state cache entry with a fresh dataUpdatedAt.
return await this.#fetchPositionsFromNetwork(normalizedAddress, true);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This path appears to bypass the retry and circuit breaker policy from the BaseDataService. Meaning, open breaker cannot stop requests, transient errors get no retries, and health events become inaccurate.

Is it feasible here to still use fetchQuery with pre-cache freshness validation? We could add a validateResponse callback to fetchQuery in the base class that is executed after #policy.execute, processQueryResponse but before returning the response to TanStack.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressing

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

);
async fetchPositions(
address: string,
options?: FetchPositionsOptions,

@Matt561 Matt561 Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Building on top of #10422 (comment) - Does it make sense to move minBlock into FetchPositionOptions so that this module that owns the cache also owns the cache-acceptance decision?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having the cache owner own acceptance would also let us drop the invalidateQueries messenger permission. Going to keep minBlock on the balance facade for this PR and follow up with moving it into FetchPositionsOptions + validateResponse since that involves more cross package tweaks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

@ffmcgee725

Copy link
Copy Markdown
Member Author

@metamaskbot publish-preview

* @param filters - Optional filter for selecting specific queries.
* @returns Nothing.
*/
protected async cancelQueries(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind adding making these changes in a separate PR so they're more visible? (but also see notes for MoneyAccountApiDataService below)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#10454 (isolated BaseDataService change)

#10422 (comment)

Comment on lines +258 to +261
options?: FetchPositionsOptions,
): Promise<PositionResponse> {
const normalizedAddress = address.toLowerCase();
const fresh = options?.fresh === true;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Better to destructure options above and set a default for fresh at the same time? e.g.

Suggested change
options?: FetchPositionsOptions,
): Promise<PositionResponse> {
const normalizedAddress = address.toLowerCase();
const fresh = options?.fresh === true;
{ fetch = true }: FetchPositionsOptions,
): Promise<PositionResponse> {
const normalizedAddress = address.toLowerCase();

Comment on lines +267 to 283
if (fresh) {
// Cancel any in-flight non-fresh fetch so it cannot win the shared
// queryKey and re-seed a response that omitted Cache-Control: no-cache.
await this.cancelQueries({ queryKey: [...queryKey] });
await this.invalidateQueries({ queryKey: [...queryKey] });
// Bypass fetchQuery: a minBlock-rejected body must not become the
// steady-state cache entry with a fresh dataUpdatedAt. Still run through
// the service policy so transient failures retry and an open breaker
// can stop the request.
return await this.executeWithPolicy(async () => {
return await this.#fetchPositionsFromNetwork(normalizedAddress, true);
});
}

return this.fetchQuery({
queryKey: [`${this.name}:fetchPositions`, address.toLowerCase()],
queryKey: [...queryKey],
staleTime: DEFAULT_STALE_TIME_MS,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there is another way to accomplish this? If you were to set staleTime to 0 when fresh is true would that work to naturally invalidate the query without explicitly needing to call invalidateQueries or even bypass the query client?

Suggested change
if (fresh) {
// Cancel any in-flight non-fresh fetch so it cannot win the shared
// queryKey and re-seed a response that omitted Cache-Control: no-cache.
await this.cancelQueries({ queryKey: [...queryKey] });
await this.invalidateQueries({ queryKey: [...queryKey] });
// Bypass fetchQuery: a minBlock-rejected body must not become the
// steady-state cache entry with a fresh dataUpdatedAt. Still run through
// the service policy so transient failures retry and an open breaker
// can stop the request.
return await this.executeWithPolicy(async () => {
return await this.#fetchPositionsFromNetwork(normalizedAddress, true);
});
}
return this.fetchQuery({
queryKey: [`${this.name}:fetchPositions`, address.toLowerCase()],
queryKey: [...queryKey],
staleTime: DEFAULT_STALE_TIME_MS,
if (fresh) {
// Cancel any in-flight non-fresh fetch so it cannot win the shared
// queryKey and re-seed a response that omitted Cache-Control: no-cache.
await this.cancelQueries({ queryKey: [...queryKey] });
}
return this.fetchQuery({
queryKey: [...queryKey],
staleTime: fresh ? 0 : DEFAULT_STALE_TIME_MS,

@ffmcgee725

Copy link
Copy Markdown
Member Author

Superseded by the stacked PRs #10454 (isolated BaseDataService change) and #10455 (Money account service changes). This split keeps Core Platform review separate from the consumer changes and incorporates the latest review feedback.

@ffmcgee725 ffmcgee725 closed this Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants