Skip to content

fix(transaction-pay-controller): guard fiat post-ramp second leg against locked keyring and CHOMP races#9267

Open
matthewwalsh0 wants to merge 10 commits into
mainfrom
fix/pay-wait-keyring-unlock
Open

fix(transaction-pay-controller): guard fiat post-ramp second leg against locked keyring and CHOMP races#9267
matthewwalsh0 wants to merge 10 commits into
mainfrom
fix/pay-wait-keyring-unlock

Conversation

@matthewwalsh0

@matthewwalsh0 matthewwalsh0 commented Jun 25, 2026

Copy link
Copy Markdown
Member

Explanation

Two failure modes for the direct mUSD Money Account vault deposit are addressed in this PR.

Locked keyring at submit time

When a fiat order completes and the post-ramp second leg is triggered (direct mUSD vault deposit, Relay nested-calldata, or simple relay), the wallet may be locked — for example if the user backgrounded the app during Apple Pay checkout and the auto-lock timer fired. In that state KeyringController.keyrings is empty, doesAccountSupportEIP7702 returns false for the Money Account address, and addTransactionBatch (called with disableHook and disableSequential) throws Account does not support EIP-7702, failing the deposit.

A waitForKeyringUnlock guard is added at the top of submitRelayAfterFiatCompletion in fiat-submit.ts. Before any second-leg logic runs the function checks KeyringController:getState().isUnlocked. If already unlocked it proceeds immediately; if locked it subscribes to KeyringController:unlock and waits indefinitely, resuming only after the user authenticates. KeyringControllerUnlockEvent is added to AllowedEvents in types.ts to allow the subscription.

CHOMP auto-vault race

CHOMP is a backend service that may auto-vault mUSD from the Money Account independently of the extension/mobile submit path. If CHOMP runs concurrently with the checkout flow the vault deposit that addTransactionBatch tries to submit may conflict with a deposit CHOMP already completed, causing a double-vault.

A CHOMP idempotency check is added in submitDirectMusdVaultDeposit using a single eth_getLogs call that scans for recent mUSD Transfer-out events from the Money Account. The baseline block is derived from the ramps settlement tx receipt (already fetched for the amount) so no additional network request is needed. The check runs before addTransactionBatch (skip the submit entirely) and again in the catch path (CHOMP may have won the race just before submit). Detection errors are swallowed so they never break the normal vault path.

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 fiat post-ramp and direct mUSD vault submission path (signing, batch submit, and on-chain idempotency) and adds a new messenger event permission for clients; incorrect CHOMP matching could skip or mis-attribute a vault hash.

Overview
Fixes two post–fiat-ramp failure modes for the direct mUSD Money Account vault path and other fiat second legs.

Locked keyring: After the on-ramp order completes, the controller now calls waitForKeyringUnlock before submitRelayAfterFiatCompletion (relay, nested calldata, and direct mUSD). If the wallet is locked it subscribes to KeyringController:unlock and resumes only after unlock, avoiding false EIP-7702 / addTransactionBatch failures when the user was away during checkout. KeyringControllerUnlockEvent is added to AllowedEvents so clients must grant that subscription.

CHOMP race: New findRecentChompVaultDeposit scans Monad eth_getLogs for mUSD Transfer events from the Money Account (newest first, amount ≥ settled raw). submitDirectMusdVaultDeposit runs this before addTransactionBatch (skip submit and return the CHOMP tx hash) and again on batch failure (treat CHOMP as success). Scan errors are swallowed so the normal vault path still runs. The log window starts at the ramps settlement receipt block: resolveSourceAmountRaw / getTransferredAmountFromTxHash now return { amountRaw, fromBlock } / { amountRaw, blockNumber } instead of a bare string, with no extra RPC when the receipt was already fetched for ERC-20 amounts.

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

@matthewwalsh0

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@7.5.3-preview-0e62f8959
@metamask-previews/accounts-controller@39.0.3-preview-0e62f8959
@metamask-previews/address-book-controller@7.1.2-preview-0e62f8959
@metamask-previews/ai-controllers@0.7.0-preview-0e62f8959
@metamask-previews/analytics-controller@1.1.1-preview-0e62f8959
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-0e62f8959
@metamask-previews/announcement-controller@8.1.0-preview-0e62f8959
@metamask-previews/app-metadata-controller@2.0.1-preview-0e62f8959
@metamask-previews/approval-controller@9.0.2-preview-0e62f8959
@metamask-previews/assets-controller@9.1.0-preview-0e62f8959
@metamask-previews/assets-controllers@109.2.2-preview-0e62f8959
@metamask-previews/authenticated-user-storage@2.1.0-preview-0e62f8959
@metamask-previews/base-controller@9.1.0-preview-0e62f8959
@metamask-previews/base-data-service@0.1.3-preview-0e62f8959
@metamask-previews/bitcoin-regtest-up@0.0.0-preview-0e62f8959
@metamask-previews/bridge-controller@77.0.0-preview-0e62f8959
@metamask-previews/bridge-status-controller@73.0.0-preview-0e62f8959
@metamask-previews/build-utils@3.0.4-preview-0e62f8959
@metamask-previews/chain-agnostic-permission@1.6.2-preview-0e62f8959
@metamask-previews/chomp-api-service@3.1.0-preview-0e62f8959
@metamask-previews/claims-controller@0.5.3-preview-0e62f8959
@metamask-previews/client-controller@1.0.1-preview-0e62f8959
@metamask-previews/compliance-controller@2.1.0-preview-0e62f8959
@metamask-previews/composable-controller@12.0.1-preview-0e62f8959
@metamask-previews/config-registry-controller@0.4.1-preview-0e62f8959
@metamask-previews/connectivity-controller@0.2.0-preview-0e62f8959
@metamask-previews/controller-utils@12.3.0-preview-0e62f8959
@metamask-previews/core-backend@6.3.3-preview-0e62f8959
@metamask-previews/delegation-controller@3.0.2-preview-0e62f8959
@metamask-previews/earn-controller@12.2.1-preview-0e62f8959
@metamask-previews/eip-5792-middleware@3.0.4-preview-0e62f8959
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.1-preview-0e62f8959
@metamask-previews/eip1193-permission-middleware@2.0.1-preview-0e62f8959
@metamask-previews/ens-controller@19.1.4-preview-0e62f8959
@metamask-previews/eth-block-tracker@15.0.1-preview-0e62f8959
@metamask-previews/eth-json-rpc-middleware@23.1.3-preview-0e62f8959
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-0e62f8959
@metamask-previews/foundryup@1.0.1-preview-0e62f8959
@metamask-previews/gas-fee-controller@26.2.3-preview-0e62f8959
@metamask-previews/gator-permissions-controller@4.2.1-preview-0e62f8959
@metamask-previews/geolocation-controller@0.1.3-preview-0e62f8959
@metamask-previews/java-tron-up@0.0.0-preview-0e62f8959
@metamask-previews/json-rpc-engine@10.5.0-preview-0e62f8959
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-0e62f8959
@metamask-previews/keyring-controller@27.1.0-preview-0e62f8959
@metamask-previews/local-node-utils@0.0.0-preview-0e62f8959
@metamask-previews/logging-controller@8.0.2-preview-0e62f8959
@metamask-previews/message-manager@14.1.2-preview-0e62f8959
@metamask-previews/messenger@1.2.0-preview-0e62f8959
@metamask-previews/messenger-cli@0.2.0-preview-0e62f8959
@metamask-previews/money-account-balance-service@2.1.1-preview-0e62f8959
@metamask-previews/money-account-controller@0.3.3-preview-0e62f8959
@metamask-previews/money-account-upgrade-controller@2.1.0-preview-0e62f8959
@metamask-previews/multichain-account-service@11.1.0-preview-0e62f8959
@metamask-previews/multichain-api-middleware@3.1.5-preview-0e62f8959
@metamask-previews/multichain-network-controller@3.2.0-preview-0e62f8959
@metamask-previews/multichain-transactions-controller@7.1.1-preview-0e62f8959
@metamask-previews/name-controller@9.1.2-preview-0e62f8959
@metamask-previews/network-controller@33.0.0-preview-0e62f8959
@metamask-previews/network-enablement-controller@5.4.0-preview-0e62f8959
@metamask-previews/notification-services-controller@24.2.0-preview-0e62f8959
@metamask-previews/passkey-controller@2.0.1-preview-0e62f8959
@metamask-previews/permission-controller@13.1.1-preview-0e62f8959
@metamask-previews/permission-log-controller@5.1.0-preview-0e62f8959
@metamask-previews/perps-controller@9.0.0-preview-0e62f8959
@metamask-previews/phishing-controller@17.2.0-preview-0e62f8959
@metamask-previews/polling-controller@16.0.7-preview-0e62f8959
@metamask-previews/preferences-controller@23.1.0-preview-0e62f8959
@metamask-previews/profile-metrics-controller@4.0.0-preview-0e62f8959
@metamask-previews/profile-sync-controller@28.2.0-preview-0e62f8959
@metamask-previews/ramps-controller@15.0.0-preview-0e62f8959
@metamask-previews/rate-limit-controller@7.0.1-preview-0e62f8959
@metamask-previews/react-data-query@0.2.1-preview-0e62f8959
@metamask-previews/remote-feature-flag-controller@4.2.2-preview-0e62f8959
@metamask-previews/sample-controllers@5.0.2-preview-0e62f8959
@metamask-previews/seedless-onboarding-controller@10.0.2-preview-0e62f8959
@metamask-previews/selected-network-controller@26.1.4-preview-0e62f8959
@metamask-previews/shield-controller@5.1.2-preview-0e62f8959
@metamask-previews/signature-controller@39.2.6-preview-0e62f8959
@metamask-previews/smart-transactions-controller@24.2.3-preview-0e62f8959
@metamask-previews/snap-account-service@1.0.0-preview-0e62f8959
@metamask-previews/social-controllers@2.3.1-preview-0e62f8959
@metamask-previews/solana-test-validator-up@0.0.0-preview-0e62f8959
@metamask-previews/storage-service@1.0.2-preview-0e62f8959
@metamask-previews/subscription-controller@6.2.0-preview-0e62f8959
@metamask-previews/transaction-controller@68.2.0-preview-0e62f8959
@metamask-previews/transaction-pay-controller@23.16.1-preview-0e62f8959
@metamask-previews/user-operation-controller@41.2.5-preview-0e62f8959
@metamask-previews/wallet@5.0.0-preview-0e62f8959
@metamask-previews/wallet-cli@0.0.0-preview-0e62f8959

…SD vault deposit

Adds CHOMP (auto-vault) detection to submitDirectMusdVaultDeposit:

- Pre-check via eth_getLogs before addTransactionBatch; returns CHOMP hash
  without adding a child transaction when CHOMP has already vaulted the funds.
- Post-check in the catch path to handle the race where CHOMP wins between
  pre-check and submit (e.g. the EIP-7702 account-support error case).
- Errors in either CHOMP check are swallowed so they never break the normal
  vault submit path.

CHOMP baseline block is derived from the ramps settlement tx receipt
blockNumber already fetched in getTransferredAmountFromTxHash (ERC-20 path),
requiring no extra network request. resolveSourceAmountRaw now returns
{ amountRaw, chompFromBlock } and getTransferredAmountFromTxHash returns
{ amountRaw, blockNumber }.

Detection: single eth_getLogs call — mUSD Transfer(from=moneyAccount) with
amount >= sourceAmountRaw; newest log wins.
@matthewwalsh0 matthewwalsh0 changed the title fix(transaction-pay-controller): wait for keyring unlock before fiat post-ramp second leg fix(transaction-pay-controller): guard fiat post-ramp second leg against locked keyring and CHOMP races Jun 25, 2026
@matthewwalsh0

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@7.5.3-preview-1ec310ea7
@metamask-previews/accounts-controller@39.0.3-preview-1ec310ea7
@metamask-previews/address-book-controller@7.1.2-preview-1ec310ea7
@metamask-previews/ai-controllers@0.7.0-preview-1ec310ea7
@metamask-previews/analytics-controller@1.1.1-preview-1ec310ea7
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-1ec310ea7
@metamask-previews/announcement-controller@8.1.0-preview-1ec310ea7
@metamask-previews/app-metadata-controller@2.0.1-preview-1ec310ea7
@metamask-previews/approval-controller@9.0.2-preview-1ec310ea7
@metamask-previews/assets-controller@9.1.0-preview-1ec310ea7
@metamask-previews/assets-controllers@109.2.2-preview-1ec310ea7
@metamask-previews/authenticated-user-storage@2.1.0-preview-1ec310ea7
@metamask-previews/base-controller@9.1.0-preview-1ec310ea7
@metamask-previews/base-data-service@0.1.3-preview-1ec310ea7
@metamask-previews/bitcoin-regtest-up@0.0.0-preview-1ec310ea7
@metamask-previews/bridge-controller@77.0.0-preview-1ec310ea7
@metamask-previews/bridge-status-controller@73.0.0-preview-1ec310ea7
@metamask-previews/build-utils@3.0.4-preview-1ec310ea7
@metamask-previews/chain-agnostic-permission@1.6.2-preview-1ec310ea7
@metamask-previews/chomp-api-service@3.1.0-preview-1ec310ea7
@metamask-previews/claims-controller@0.5.3-preview-1ec310ea7
@metamask-previews/client-controller@1.0.1-preview-1ec310ea7
@metamask-previews/compliance-controller@2.1.0-preview-1ec310ea7
@metamask-previews/composable-controller@12.0.1-preview-1ec310ea7
@metamask-previews/config-registry-controller@0.4.1-preview-1ec310ea7
@metamask-previews/connectivity-controller@0.2.0-preview-1ec310ea7
@metamask-previews/controller-utils@12.3.0-preview-1ec310ea7
@metamask-previews/core-backend@6.3.3-preview-1ec310ea7
@metamask-previews/delegation-controller@3.0.2-preview-1ec310ea7
@metamask-previews/earn-controller@12.2.1-preview-1ec310ea7
@metamask-previews/eip-5792-middleware@3.0.4-preview-1ec310ea7
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.1-preview-1ec310ea7
@metamask-previews/eip1193-permission-middleware@2.0.1-preview-1ec310ea7
@metamask-previews/ens-controller@19.1.4-preview-1ec310ea7
@metamask-previews/eth-block-tracker@15.0.1-preview-1ec310ea7
@metamask-previews/eth-json-rpc-middleware@23.1.3-preview-1ec310ea7
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-1ec310ea7
@metamask-previews/foundryup@1.0.1-preview-1ec310ea7
@metamask-previews/gas-fee-controller@26.2.3-preview-1ec310ea7
@metamask-previews/gator-permissions-controller@4.2.1-preview-1ec310ea7
@metamask-previews/geolocation-controller@0.1.3-preview-1ec310ea7
@metamask-previews/java-tron-up@0.0.0-preview-1ec310ea7
@metamask-previews/json-rpc-engine@10.5.0-preview-1ec310ea7
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-1ec310ea7
@metamask-previews/keyring-controller@27.1.0-preview-1ec310ea7
@metamask-previews/local-node-utils@0.0.0-preview-1ec310ea7
@metamask-previews/logging-controller@8.0.2-preview-1ec310ea7
@metamask-previews/message-manager@14.1.2-preview-1ec310ea7
@metamask-previews/messenger@1.2.0-preview-1ec310ea7
@metamask-previews/messenger-cli@0.2.0-preview-1ec310ea7
@metamask-previews/money-account-balance-service@2.1.1-preview-1ec310ea7
@metamask-previews/money-account-controller@0.3.3-preview-1ec310ea7
@metamask-previews/money-account-upgrade-controller@2.1.0-preview-1ec310ea7
@metamask-previews/multichain-account-service@11.1.0-preview-1ec310ea7
@metamask-previews/multichain-api-middleware@3.1.5-preview-1ec310ea7
@metamask-previews/multichain-network-controller@3.2.0-preview-1ec310ea7
@metamask-previews/multichain-transactions-controller@7.1.1-preview-1ec310ea7
@metamask-previews/name-controller@9.1.2-preview-1ec310ea7
@metamask-previews/network-controller@33.0.0-preview-1ec310ea7
@metamask-previews/network-enablement-controller@5.4.0-preview-1ec310ea7
@metamask-previews/notification-services-controller@24.2.0-preview-1ec310ea7
@metamask-previews/passkey-controller@2.0.1-preview-1ec310ea7
@metamask-previews/permission-controller@13.1.1-preview-1ec310ea7
@metamask-previews/permission-log-controller@5.1.0-preview-1ec310ea7
@metamask-previews/perps-controller@9.0.0-preview-1ec310ea7
@metamask-previews/phishing-controller@17.2.0-preview-1ec310ea7
@metamask-previews/polling-controller@16.0.7-preview-1ec310ea7
@metamask-previews/preferences-controller@23.1.0-preview-1ec310ea7
@metamask-previews/profile-metrics-controller@4.0.0-preview-1ec310ea7
@metamask-previews/profile-sync-controller@28.2.0-preview-1ec310ea7
@metamask-previews/ramps-controller@15.0.0-preview-1ec310ea7
@metamask-previews/rate-limit-controller@7.0.1-preview-1ec310ea7
@metamask-previews/react-data-query@0.2.1-preview-1ec310ea7
@metamask-previews/remote-feature-flag-controller@4.2.2-preview-1ec310ea7
@metamask-previews/sample-controllers@5.0.2-preview-1ec310ea7
@metamask-previews/seedless-onboarding-controller@10.0.2-preview-1ec310ea7
@metamask-previews/selected-network-controller@26.1.4-preview-1ec310ea7
@metamask-previews/shield-controller@5.1.2-preview-1ec310ea7
@metamask-previews/signature-controller@39.2.6-preview-1ec310ea7
@metamask-previews/smart-transactions-controller@24.2.3-preview-1ec310ea7
@metamask-previews/snap-account-service@1.0.0-preview-1ec310ea7
@metamask-previews/social-controllers@2.3.1-preview-1ec310ea7
@metamask-previews/solana-test-validator-up@0.0.0-preview-1ec310ea7
@metamask-previews/storage-service@1.0.2-preview-1ec310ea7
@metamask-previews/subscription-controller@6.2.0-preview-1ec310ea7
@metamask-previews/transaction-controller@68.2.0-preview-1ec310ea7
@metamask-previews/transaction-pay-controller@23.16.1-preview-1ec310ea7
@metamask-previews/user-operation-controller@41.2.5-preview-1ec310ea7
@metamask-previews/wallet@5.0.0-preview-1ec310ea7
@metamask-previews/wallet-cli@0.0.0-preview-1ec310ea7

@matthewwalsh0 matthewwalsh0 marked this pull request as ready for review June 26, 2026 00:53
@matthewwalsh0 matthewwalsh0 requested review from a team as code owners June 26, 2026 00:53
@matthewwalsh0 matthewwalsh0 deployed to default-branch June 26, 2026 00:53 — with GitHub Actions Active

@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 2 potential issues.

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 f307c21. Configure here.

txHash: txLog.transactionHash,
});

return txLog.transactionHash;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CHOMP scan matches unrelated transfers

High Severity

CHOMP idempotency treats any recent mUSD Transfer from the Money Account with amount at least the settled raw total as success. That can match unrelated outbound sends or another deposit’s vault tx, so the direct vault batch is skipped while this order’s ramp credit may stay unvaulted.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f307c21. Configure here.

decimals: tokenInfo.decimals,
});

return { amountRaw, fromBlock: undefined };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Settlement block lost on fallback

Medium Severity

When resolveSourceAmountRaw reads a settlement receipt but cannot derive an on-chain inbound amount, it falls back to order.cryptoAmount and drops the receipt blockNumber, so fromBlock stays undefined and CHOMP pre/post checks are skipped even though the baseline block was already fetched.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f307c21. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant