Skip to content

fix(transaction-pay-controller): refresh max balances before requotes - #10911

Open
OGPoyraz wants to merge 8 commits into
mainfrom
fix/perps-max-requote-hook-generic-pay-controller
Open

OGPoyraz wants to merge 8 commits into
mainfrom
fix/perps-max-requote-hook-generic-pay-controller

Conversation

@OGPoyraz

@OGPoyraz OGPoyraz commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Explanation

Max-amount transactions can stay open while their source balance changes. The stored quote keeps the amount derived from the earlier balance, so submitting it can request more than the source account holds (for example, a Perps withdrawal quoted for 10 after the balance drops to 9.9).

This PR refreshes the max-amount source balance as part of the existing QuoteRefresher loop, using a per-transaction-type interval from a new maxBalanceRefreshInterval field in confirmations_pay_extended:

{
  "maxBalanceRefreshInterval": {
    "perpsWithdraw": 5000
  }
}
  • PayStrategyGetRefreshIntervalRequest now includes transactionId.
  • RelayStrategy.getRefreshInterval returns the configured interval for max-amount transactions whose type matches the flag (including nested batch transactions). Otherwise it returns undefined, keeping the default 30-second refresh.
  • When refreshQuotes requotes a max-amount transaction, it first re-derives source amounts through the controller update path using the existing getBalance callback, then requotes from the latest state.
  • TransactionPayController passes getBalance to QuoteRefresher.

Because this runs inside the existing refresh loop, a refresh never overlaps an in-flight quote request for the same transaction. Transaction types with no configured interval are unchanged.

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Medium Risk
Changes the periodic requote path and max-amount sizing for pay/withdraw flows; incorrect balance or interval logic could mis-quote or refresh too often, though behavior is gated by feature flags and max-amount checks.

Overview
Fixes max-amount pay flows staying quoted against an outdated source balance while the confirmation stays open (e.g. a Perps withdrawal still sized for 10 after the balance drops).

The periodic QuoteRefresher path now passes the controller’s optional getBalance into refreshQuotes. When quotes are expired, refreshQuotes re-runs updateSourceAmounts (with that callback when set) before fetching new quotes, and requotes from the latest controller state.

Max-amount transactions can use a shorter requote cadence via a new confirmations_pay_extended.maxBalanceRefreshInterval map (per TransactionType, including nested batch types). RelayStrategy.getRefreshInterval returns that interval only when isMaxAmount is set; otherwise behavior stays on the default 30s refresh. PayStrategyGetRefreshIntervalRequest now includes transactionId so strategies can read transaction data when resolving the interval.

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

@OGPoyraz
OGPoyraz force-pushed the fix/perps-max-requote-hook-generic-pay-controller branch from 065da61 to aeb52ce Compare October 20, 2026 13:26
@OGPoyraz

Copy link
Copy Markdown
Member Author

@metamaskbot publish-preview

@OGPoyraz
OGPoyraz marked this pull request as ready for review October 20, 2026 13:41
@OGPoyraz
OGPoyraz requested review from a team as code owners October 20, 2026 13:41

@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.

@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@11.1.0-preview-aeb52ce4f
@metamask-previews/accounts-controller@40.1.0-preview-aeb52ce4f
@metamask-previews/address-book-controller@8.0.0-preview-aeb52ce4f
@metamask-previews/advanced-chart-core@1.0.0-preview-aeb52ce4f
@metamask-previews/ai-controllers@2.0.0-preview-aeb52ce4f
@metamask-previews/analytics-controller@4.0.0-preview-aeb52ce4f
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-aeb52ce4f
@metamask-previews/announcement-controller@9.0.0-preview-aeb52ce4f
@metamask-previews/app-metadata-controller@3.0.0-preview-aeb52ce4f
@metamask-previews/approval-controller@10.0.0-preview-aeb52ce4f
@metamask-previews/assets-controller@19.1.0-preview-aeb52ce4f
@metamask-previews/assets-controllers@112.1.2-preview-aeb52ce4f
@metamask-previews/authenticated-user-storage@4.1.0-preview-aeb52ce4f
@metamask-previews/base-controller@10.1.0-preview-aeb52ce4f
@metamask-previews/base-data-service@2.1.0-preview-aeb52ce4f
@metamask-previews/bitcoin-regtest-up@2.0.0-preview-aeb52ce4f
@metamask-previews/bridge-controller@82.0.3-preview-aeb52ce4f
@metamask-previews/bridge-status-controller@76.4.0-preview-aeb52ce4f
@metamask-previews/build-utils@4.0.0-preview-aeb52ce4f
@metamask-previews/chain-agnostic-permission@2.0.0-preview-aeb52ce4f
@metamask-previews/chomp-api-service@6.0.1-preview-aeb52ce4f
@metamask-previews/claims-controller@1.0.3-preview-aeb52ce4f
@metamask-previews/client-controller@2.0.0-preview-aeb52ce4f
@metamask-previews/client-utils@3.0.4-preview-aeb52ce4f
@metamask-previews/compliance-controller@3.0.0-preview-aeb52ce4f
@metamask-previews/composable-controller@13.0.0-preview-aeb52ce4f
@metamask-previews/config-registry-controller@5.0.0-preview-aeb52ce4f
@metamask-previews/connectivity-controller@1.0.0-preview-aeb52ce4f
@metamask-previews/controller-utils@13.0.0-preview-aeb52ce4f
@metamask-previews/core-backend@12.3.0-preview-aeb52ce4f
@metamask-previews/cryptography@1.1.2-preview-aeb52ce4f
@metamask-previews/delegation-controller@4.0.0-preview-aeb52ce4f
@metamask-previews/earn-controller@13.0.2-preview-aeb52ce4f
@metamask-previews/eip-5792-middleware@4.0.1-preview-aeb52ce4f
@metamask-previews/eip-7702-internal-rpc-middleware@1.0.0-preview-aeb52ce4f
@metamask-previews/eip1193-permission-middleware@3.0.0-preview-aeb52ce4f
@metamask-previews/eth-block-tracker@16.0.0-preview-aeb52ce4f
@metamask-previews/eth-json-rpc-middleware@25.0.0-preview-aeb52ce4f
@metamask-previews/eth-json-rpc-provider@7.0.0-preview-aeb52ce4f
@metamask-previews/foundryup@2.0.0-preview-aeb52ce4f
@metamask-previews/gas-fee-controller@27.0.0-preview-aeb52ce4f
@metamask-previews/gator-permissions-controller@6.0.1-preview-aeb52ce4f
@metamask-previews/geolocation-controller@2.0.0-preview-aeb52ce4f
@metamask-previews/java-tron-up@2.0.0-preview-aeb52ce4f
@metamask-previews/json-rpc-engine@11.0.0-preview-aeb52ce4f
@metamask-previews/json-rpc-middleware-stream@9.0.0-preview-aeb52ce4f
@metamask-previews/keyring-controller@28.2.0-preview-aeb52ce4f
@metamask-previews/kyc-controller@0.10.0-preview-aeb52ce4f
@metamask-previews/local-node-utils@2.0.0-preview-aeb52ce4f
@metamask-previews/logging-controller@10.0.0-preview-aeb52ce4f
@metamask-previews/marketing-consent-controller@1.0.0-preview-aeb52ce4f
@metamask-previews/message-manager@15.0.0-preview-aeb52ce4f
@metamask-previews/messenger@3.0.0-preview-aeb52ce4f
@metamask-previews/messenger-cli@1.0.0-preview-aeb52ce4f
@metamask-previews/mfa-recovery-controller@0.0.0-preview-aeb52ce4f
@metamask-previews/money-account-api-data-service@2.2.0-preview-aeb52ce4f
@metamask-previews/money-account-balance-service@3.1.2-preview-aeb52ce4f
@metamask-previews/money-account-controller@2.0.0-preview-aeb52ce4f
@metamask-previews/money-account-mfa-whitelist@0.1.0-preview-aeb52ce4f
@metamask-previews/money-account-upgrade-controller@5.1.0-preview-aeb52ce4f
@metamask-previews/money-account-utils@2.1.0-preview-aeb52ce4f
@metamask-previews/multichain-account-service@14.1.0-preview-aeb52ce4f
@metamask-previews/multichain-api-middleware@5.0.0-preview-aeb52ce4f
@metamask-previews/multichain-network-controller@4.0.0-preview-aeb52ce4f
@metamask-previews/multichain-transactions-controller@8.0.0-preview-aeb52ce4f
@metamask-previews/name-controller@10.0.0-preview-aeb52ce4f
@metamask-previews/network-connection-banner-controller@1.0.0-preview-aeb52ce4f
@metamask-previews/network-controller@37.1.0-preview-aeb52ce4f
@metamask-previews/network-enablement-controller@7.0.2-preview-aeb52ce4f
@metamask-previews/notification-services-controller@30.0.0-preview-aeb52ce4f
@metamask-previews/passkey-controller@4.1.0-preview-aeb52ce4f
@metamask-previews/permission-controller@14.0.0-preview-aeb52ce4f
@metamask-previews/permission-log-controller@6.0.0-preview-aeb52ce4f
@metamask-previews/perps-controller@20.1.0-preview-aeb52ce4f
@metamask-previews/phishing-controller@18.2.1-preview-aeb52ce4f
@metamask-previews/platform-api-docs@0.2.1-preview-aeb52ce4f
@metamask-previews/polling-controller@17.0.0-preview-aeb52ce4f
@metamask-previews/preferences-controller@24.0.0-preview-aeb52ce4f
@metamask-previews/profile-controller@1.1.0-preview-aeb52ce4f
@metamask-previews/profile-metrics-controller@5.1.3-preview-aeb52ce4f
@metamask-previews/profile-sync-controller@34.1.0-preview-aeb52ce4f
@metamask-previews/ramps-controller@27.0.1-preview-aeb52ce4f
@metamask-previews/rate-limit-controller@8.0.0-preview-aeb52ce4f
@metamask-previews/react-data-query@2.0.0-preview-aeb52ce4f
@metamask-previews/remote-feature-flag-controller@7.0.0-preview-aeb52ce4f
@metamask-previews/sample-controllers@6.0.0-preview-aeb52ce4f
@metamask-previews/seedless-onboarding-controller@12.0.0-preview-aeb52ce4f
@metamask-previews/selected-network-controller@27.0.0-preview-aeb52ce4f
@metamask-previews/sentinel-api-service@2.1.0-preview-aeb52ce4f
@metamask-previews/shield-controller@7.0.4-preview-aeb52ce4f
@metamask-previews/signature-controller@40.0.0-preview-aeb52ce4f
@metamask-previews/smart-transactions-controller@27.1.0-preview-aeb52ce4f
@metamask-previews/snap-account-service@4.0.0-preview-aeb52ce4f
@metamask-previews/social-controllers@3.8.0-preview-aeb52ce4f
@metamask-previews/solana-test-validator-up@2.0.0-preview-aeb52ce4f
@metamask-previews/stellar-quickstart-up@0.0.0-preview-aeb52ce4f
@metamask-previews/storage-service@2.0.0-preview-aeb52ce4f
@metamask-previews/subscription-controller@14.0.1-preview-aeb52ce4f
@metamask-previews/transaction-controller@72.2.1-preview-aeb52ce4f
@metamask-previews/transaction-pay-controller@30.1.0-preview-aeb52ce4f
@metamask-previews/transaction-pay-middleware@0.0.0-preview-aeb52ce4f
@metamask-previews/type-utils@0.0.0-preview-aeb52ce4f
@metamask-previews/user-operation-controller@42.0.1-preview-aeb52ce4f
@metamask-previews/utils@12.0.0-preview-aeb52ce4f
@metamask-previews/wallet@18.0.1-preview-aeb52ce4f
@metamask-previews/wallet-cli@0.0.0-preview-aeb52ce4f

this.#getStrategy = getStrategy;
this.#getStrategies = getStrategies;
this.#polymarket = polymarket;
this.#maxBalanceRefresher = new MaxBalanceRefresher({

@matthewwalsh0 matthewwalsh0 Oct 23, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instead of setting up an additional loop that may clash with the default QuoteRefresher, could we just update getRefreshInterval in the RelayStrategy to check the feature flag based on max mode and transaction type according to the feature flag?

We could add transactionId to PayStrategyGetRefreshIntervalRequest then it's nicely encapsulated and fits in the existing refresh loop?

And call updateSourceAmounts as standard inside refreshQuotes?

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.

Done 👍

pollingTimeout?: number;
};
};
maxBalanceRefreshInterval?: MaxBalanceRefreshIntervalConfig;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Minor, alphabetical?

return STABLECOINS;
}

export function getMaxBalanceRefreshInterval(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unit test?

@OGPoyraz
OGPoyraz force-pushed the fix/perps-max-requote-hook-generic-pay-controller branch from aeb52ce to d5be077 Compare October 27, 2026 06:46
@OGPoyraz

OGPoyraz commented Nov 3, 2026

Copy link
Copy Markdown
Member Author

@metamaskbot publish-preview

@github-actions

github-actions Bot commented Nov 3, 2026

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@11.2.0-preview-c0453679f
@metamask-previews/accounts-controller@40.1.0-preview-c0453679f
@metamask-previews/address-book-controller@8.0.0-preview-c0453679f
@metamask-previews/advanced-chart-core@1.0.0-preview-c0453679f
@metamask-previews/ai-controllers@2.0.0-preview-c0453679f
@metamask-previews/analytics-controller@4.1.0-preview-c0453679f
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-c0453679f
@metamask-previews/announcement-controller@9.0.0-preview-c0453679f
@metamask-previews/app-metadata-controller@3.0.0-preview-c0453679f
@metamask-previews/approval-controller@10.0.0-preview-c0453679f
@metamask-previews/assets-controller@20.1.0-preview-c0453679f
@metamask-previews/assets-controllers@113.0.1-preview-c0453679f
@metamask-previews/authenticated-user-storage@4.1.0-preview-c0453679f
@metamask-previews/base-controller@10.1.0-preview-c0453679f
@metamask-previews/base-data-service@2.1.0-preview-c0453679f
@metamask-previews/bitcoin-regtest-up@2.0.0-preview-c0453679f
@metamask-previews/bridge-controller@83.1.3-preview-c0453679f
@metamask-previews/bridge-status-controller@78.0.1-preview-c0453679f
@metamask-previews/build-utils@4.0.0-preview-c0453679f
@metamask-previews/byte-utils@0.1.0-preview-c0453679f
@metamask-previews/chain-agnostic-permission@2.1.0-preview-c0453679f
@metamask-previews/chomp-api-service@7.0.0-preview-c0453679f
@metamask-previews/claims-controller@1.1.0-preview-c0453679f
@metamask-previews/client-controller@2.0.0-preview-c0453679f
@metamask-previews/client-utils@3.1.0-preview-c0453679f
@metamask-previews/compliance-controller@3.0.0-preview-c0453679f
@metamask-previews/composable-controller@13.0.0-preview-c0453679f
@metamask-previews/config-registry-controller@5.0.0-preview-c0453679f
@metamask-previews/connectivity-controller@1.0.0-preview-c0453679f
@metamask-previews/controller-utils@13.0.0-preview-c0453679f
@metamask-previews/core-backend@13.0.0-preview-c0453679f
@metamask-previews/cryptography@1.1.2-preview-c0453679f
@metamask-previews/delegation-controller@4.0.0-preview-c0453679f
@metamask-previews/earn-controller@13.0.2-preview-c0453679f
@metamask-previews/eip-5792-middleware@4.1.0-preview-c0453679f
@metamask-previews/eip-7702-internal-rpc-middleware@1.0.0-preview-c0453679f
@metamask-previews/eip1193-permission-middleware@3.0.0-preview-c0453679f
@metamask-previews/error-utils@0.1.0-preview-c0453679f
@metamask-previews/eth-block-tracker@16.0.0-preview-c0453679f
@metamask-previews/eth-json-rpc-middleware@26.0.0-preview-c0453679f
@metamask-previews/eth-json-rpc-provider@7.0.0-preview-c0453679f
@metamask-previews/foundryup@2.0.0-preview-c0453679f
@metamask-previews/gas-fee-controller@27.0.0-preview-c0453679f
@metamask-previews/gator-permissions-controller@6.1.0-preview-c0453679f
@metamask-previews/geolocation-controller@2.0.0-preview-c0453679f
@metamask-previews/java-tron-up@2.0.0-preview-c0453679f
@metamask-previews/json-rpc-engine@11.0.0-preview-c0453679f
@metamask-previews/json-rpc-middleware-stream@9.0.0-preview-c0453679f
@metamask-previews/keyring-controller@28.2.0-preview-c0453679f
@metamask-previews/kyc-controller@0.14.0-preview-c0453679f
@metamask-previews/local-node-utils@2.0.0-preview-c0453679f
@metamask-previews/logging-controller@10.0.0-preview-c0453679f
@metamask-previews/marketing-consent-controller@1.1.0-preview-c0453679f
@metamask-previews/message-manager@15.0.0-preview-c0453679f
@metamask-previews/messenger@3.0.0-preview-c0453679f
@metamask-previews/messenger-cli@1.0.0-preview-c0453679f
@metamask-previews/mfa-recovery-controller@0.0.0-preview-c0453679f
@metamask-previews/money-account-api-data-service@2.3.1-preview-c0453679f
@metamask-previews/money-account-balance-service@3.1.2-preview-c0453679f
@metamask-previews/money-account-controller@3.0.0-preview-c0453679f
@metamask-previews/money-account-lifecycle-controller@0.0.0-preview-c0453679f
@metamask-previews/money-account-mfa-whitelist@0.1.0-preview-c0453679f
@metamask-previews/money-account-upgrade-controller@6.0.0-preview-c0453679f
@metamask-previews/money-account-utils@2.2.0-preview-c0453679f
@metamask-previews/multichain-account-service@14.1.0-preview-c0453679f
@metamask-previews/multichain-api-middleware@5.0.0-preview-c0453679f
@metamask-previews/multichain-network-controller@4.0.0-preview-c0453679f
@metamask-previews/multichain-transactions-controller@8.0.0-preview-c0453679f
@metamask-previews/name-controller@10.0.0-preview-c0453679f
@metamask-previews/network-connection-banner-controller@1.0.0-preview-c0453679f
@metamask-previews/network-controller@37.1.1-preview-c0453679f
@metamask-previews/network-enablement-controller@7.1.0-preview-c0453679f
@metamask-previews/node-utils@0.1.0-preview-c0453679f
@metamask-previews/notification-services-controller@30.1.0-preview-c0453679f
@metamask-previews/passkey-controller@4.1.0-preview-c0453679f
@metamask-previews/permission-controller@14.1.0-preview-c0453679f
@metamask-previews/permission-log-controller@6.0.0-preview-c0453679f
@metamask-previews/perps-controller@22.1.0-preview-c0453679f
@metamask-previews/phishing-controller@18.3.0-preview-c0453679f
@metamask-previews/platform-api-docs@0.2.1-preview-c0453679f
@metamask-previews/polling-controller@17.0.0-preview-c0453679f
@metamask-previews/preferences-controller@24.0.0-preview-c0453679f
@metamask-previews/profile-controller@1.2.1-preview-c0453679f
@metamask-previews/profile-metrics-controller@5.2.1-preview-c0453679f
@metamask-previews/profile-sync-controller@36.0.0-preview-c0453679f
@metamask-previews/ramps-controller@29.0.0-preview-c0453679f
@metamask-previews/rate-limit-controller@8.0.0-preview-c0453679f
@metamask-previews/react-data-query@2.0.0-preview-c0453679f
@metamask-previews/remote-feature-flag-controller@7.0.0-preview-c0453679f
@metamask-previews/sample-controllers@6.0.0-preview-c0453679f
@metamask-previews/seedless-onboarding-controller@12.0.0-preview-c0453679f
@metamask-previews/selected-network-controller@27.0.0-preview-c0453679f
@metamask-previews/sentinel-api-service@2.1.0-preview-c0453679f
@metamask-previews/shield-controller@7.1.1-preview-c0453679f
@metamask-previews/signature-controller@40.0.0-preview-c0453679f
@metamask-previews/smart-transactions-controller@27.2.1-preview-c0453679f
@metamask-previews/snap-account-service@4.0.0-preview-c0453679f
@metamask-previews/social-controllers@3.15.0-preview-c0453679f
@metamask-previews/solana-test-validator-up@2.0.0-preview-c0453679f
@metamask-previews/stellar-quickstart-up@0.0.0-preview-c0453679f
@metamask-previews/storage-service@2.0.0-preview-c0453679f
@metamask-previews/subscription-controller@15.1.2-preview-c0453679f
@metamask-previews/transaction-controller@73.0.0-preview-c0453679f
@metamask-previews/transaction-pay-controller@30.3.3-preview-c0453679f
@metamask-previews/transaction-pay-middleware@2.0.1-preview-c0453679f
@metamask-previews/type-utils@0.1.0-preview-c0453679f
@metamask-previews/user-operation-controller@42.1.0-preview-c0453679f
@metamask-previews/utils@12.0.1-preview-c0453679f
@metamask-previews/wallet@21.0.0-preview-c0453679f
@metamask-previews/watch-only-account-service@0.1.0-preview-c0453679f

@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 using default effort 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 a963107. Configure here.


const latestTransactionData = messenger.call(
'TransactionPayController:getState',
).transactionData[transactionId];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Refresh can replace quotes with no-op

Medium Severity

Unconditionally calling updateSourceAmounts before each expired requote now also rewrites non-max source amounts. That helper can persist an empty list when getTokenFiatRate is briefly unavailable, and updateQuotes then treats empty requests plus a payment token as a direct route. The stored conversion quotes become a no-op, and QuoteRefresher stops because only a None strategy remains.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a963107. Configure here.

…-hook-generic-pay-controller

# Conflicts:
#	packages/transaction-pay-controller/CHANGELOG.md
#	packages/transaction-pay-controller/src/TransactionPayController.test.ts

This branch was previously deployed

1 inactive (outdated) deployment
default-branch — aeb52ce4 Deployed Oct 20, 2026 by OGPoyraz via Determine whether this PR is a release PR #5709
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.

2 participants