feat(transaction-pay): add TokenPay strategy with Across provider + metrics#7806
feat(transaction-pay): add TokenPay strategy with Across provider + metrics#7806pedronfigueiredo wants to merge 1 commit intomainfrom
Conversation
packages/transaction-pay-controller/src/strategy/token-pay/TokenPayStrategy.ts
Outdated
Show resolved
Hide resolved
packages/transaction-pay-controller/src/strategy/across/across-submit.ts
Outdated
Show resolved
Hide resolved
packages/transaction-pay-controller/src/strategy/across/across-submit.ts
Show resolved
Hide resolved
6e9ba0c to
047509a
Compare
packages/transaction-pay-controller/src/strategy/across/across-submit.ts
Show resolved
Hide resolved
packages/transaction-pay-controller/src/strategy/across/across-quotes.ts
Outdated
Show resolved
Hide resolved
packages/transaction-pay-controller/src/strategy/relay/RelayProvider.ts
Outdated
Show resolved
Hide resolved
packages/transaction-pay-controller/src/strategy/token-pay/TokenPayStrategy.ts
Outdated
Show resolved
Hide resolved
packages/transaction-pay-controller/src/strategy/across/across-submit.ts
Outdated
Show resolved
Hide resolved
packages/transaction-pay-controller/src/strategy/across/across-submit.ts
Show resolved
Hide resolved
packages/transaction-pay-controller/src/strategy/across/across-quotes.ts
Outdated
Show resolved
Hide resolved
packages/transaction-pay-controller/src/strategy/across/across-quotes.ts
Outdated
Show resolved
Hide resolved
1a4c355 to
f5f13b3
Compare
4ee01fe to
204fc2e
Compare
packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts
Outdated
Show resolved
Hide resolved
cb2195a to
d0e45a5
Compare
| default: | ||
| return TransactionType.perpsAcrossDeposit; | ||
| } | ||
| } |
There was a problem hiding this comment.
Default deposit type mislabels non-perps Across transactions
Medium Severity
getAcrossDepositType defaults to TransactionType.perpsAcrossDeposit for any transaction type other than perpsDeposit or predictDeposit. Since AcrossStrategy.supports() already blocks perpsDeposit, the only transactions reaching Across are predictDeposit and all other types (e.g., regular token transfers via fallback). Non-perps, non-predict transactions get incorrectly labeled as perpsAcrossDeposit, which misrepresents the transaction in analytics, tracking, and any downstream type-checking logic. The transaction-controller CHANGELOG even references a generic acrossDeposit type that was never defined, suggesting a missing general-purpose deposit type.
d0e45a5 to
b6c9ca4
Compare
There was a problem hiding this comment.
This is looking awesome Pedro!
To summarise a few comments, I think my core suggestion now is trying to break up this 5000 line PR into multiple PRs for each discrete change, such as:
- Fallback mechanism
- Across strategy
- Execution latency property
- Minimise duplication between strategy submit logic
- Gas station support in Across
| - Add support for `transactionHistoryLimit` feature flag to configure the maximum number of transactions stored in state ([#7648](https://github.com/MetaMask/core/pull/7648)) | ||
| - Defaults to 40 if not provided. | ||
| - Add optional `callTraceErrors` to `simulationData` ([#7641](https://github.com/MetaMask/core/pull/7641)) | ||
| - Add `acrossDeposit` transaction type and `MetamaskPayMetadata.executionLatencyMs` for MetaMask Pay tracking ([#7806](https://github.com/MetaMask/core/pull/7806)) |
| /** | ||
| * Deposit funds for Across quote via Perps. | ||
| */ | ||
| perpsAcrossDeposit = 'perpsAcrossDeposit', |
| return [this.#getStrategy(transaction)]; | ||
| } | ||
|
|
||
| return getStrategyOrder(this.messenger); |
There was a problem hiding this comment.
To clarify, we proritise the client decision, but fallback to a generic flow defined by feature flags?
Should we define this in a dedicated local function like #getStrategiesWithFallback?
| ]); | ||
| }); | ||
|
|
||
| it('returns callback list if provided', async () => { |
There was a problem hiding this comment.
Do we need tests for fallback to getStrategy and then fallback to the feature flags?
| } | ||
|
|
||
| try { | ||
| const quotes = await strategy.getQuotes(request); |
There was a problem hiding this comment.
I don't believe execution fallback is possible as we can't present the new quote with new fees to the user since the confirmation is gone.
I believe the fallback mechanism is only for quote retrieval time?
| messenger, | ||
| }); | ||
|
|
||
| return { |
There was a problem hiding this comment.
Does this mean we don't support gas station yet?
That's fine as a future PR to reduce complexity, we can re-use Relay logic which includes balance checks etc.
| const { swapTx } = quote; | ||
| const chainId = toHex(swapTx.chainId); | ||
|
|
||
| const gasResult = await estimateGasWithBufferOrFallback({ |
There was a problem hiding this comment.
As this isn't conditional, does that mean we always have to work out the gas?
If so we have extensive logic in Relay for 7702, batch, and simulation that we could make generic in a future PR.
Though maybe not needed here if we can guarantee only one transaction, since no swap support?
| requiredTransactionNote: | ||
| 'Add required transaction ID from Across submission', | ||
| intentCompleteNote: 'Intent complete after Across submission', | ||
| buildTransactions: async (quote) => { |
There was a problem hiding this comment.
Better readability if we define this outside of the function call?
| chainId, | ||
| from, | ||
| transactions, | ||
| submit: async (preparedTransactions): Promise<void> => { |
There was a problem hiding this comment.
Isn't the calls to the transaction controller exactly what we want to avoid duplicating?
Could we have each strategy just build a list of standard params, then pass them to a standard util that adds them, waits for completion, and returns hashes?
But given the size and complexity of this PR, simpler is better, so happy for you to duplicate all the submit for now if that's easier and we have a dedicated PR to address duplication later?
| let hasReportedLatency = false; | ||
|
|
||
| for (const quote of quotes) { | ||
| const prepared = await buildTransactions(quote); |
There was a problem hiding this comment.
Could the strategies do this themselves then just pass a list of params to this function?
So this function would add (via batch if needed), collect transaction IDs, then wait for confirmed?
As mentioned in a different comment, happy to duplicate for now given complexity and size of this PR.
…execution metrics - Add an Across pay strategy (quotes + submit) and Across-specific transaction types for MetaMask Pay flows. - Extend strategy selection to support ordered strategies and compatibility checks via supports, then fallback to the next strategy on quote/execution failures. - Refactor Relay and Across execution paths to use a shared source-transaction submission helper with confirmation waits and required transaction tracking. - Add pay-strategy feature flag config for Across/Relay enablement and Across API settings, plus safer feature-flag reads. - Record quote/submit latency in MetaMask Pay metadata (executionLatencyMs) and preserve execution metrics when quotes are refreshed. - Expand tests for Across quote/submit/support behavior, publish-hook fallback logic, feature-flag defaults, and shared strategy helpers.
b6c9ca4 to
4185673
Compare
…7868) ## Explanation As per preemptive validation on #7806 <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> - Adds ordered strategy retrieval via `TransactionPayController:getStrategies` while preserving existing `getStrategy` behavior. - Adds compatibility filtering (`supports(...)`) during strategy selection. - Implements quote fallback orchestration: - try next strategy when quote retrieval fails - try next strategy when quote list is empty - skip unsupported strategies - Implements publish-hook fallback orchestration: - execute primary strategy from existing quote strategy - on execution failure, rebuild quote requests and try next compatible strategy - throw original primary error if all fallback attempts fail ## References <!-- Are there any issues that this pull request is tied to? Are there other links that reviewers should consult to understand these changes better? Are there client or consumer pull requests to adopt any breaking changes? For example: * Fixes #12345 * Related to #67890 --> Addresses MetaMask/MetaMask-planning#6992 ## 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](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes core quote-selection and submission routing for pay transactions; incorrect strategy ordering, supports checks, or fallback behavior could alter fees/quotes returned or cause pay flows to fail in edge cases. > > **Overview** > **MetaMask Pay now supports ordered strategy orchestration with fallback.** `TransactionPayController` accepts an optional `getStrategies` callback (while preserving `getStrategy`) and falls back to a remotely-configured `strategyOrder` feature flag (defaulting to Relay), filtering invalid values. > > Quote updates/refresh now try strategies in priority order via `getStrategiesByName`, optionally skip incompatible strategies via a new `PayStrategy.supports(...)` hook, and fall through when a strategy errors, returns no quotes, or fails batch-transaction generation. Publish-time submission now selects the strategy from `quotes[0].strategy` (instead of re-resolving from controller) to ensure the submit path matches the quote origin. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 7908026. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->


Explanation
This PR introduces a TokenPay strategy that routes to provider adapters (Relay + Across), adds an Across provider end‑to‑end, and tracks quote/execution latency and costs. It also enforces current Across limitations (same‑chain swaps, perps deposits, and type‑4 authorization lists) with explicit gating and TODOs.
Key changes
References
Checklist
Note
Medium Risk
Adds a new pay provider (Across) and changes strategy selection/execution to support ordered fallback, which can alter transaction submission behavior and quote routing; also touches tx metadata recording but with guarded, non-fatal updates.
Overview
Adds a new Across pay strategy end-to-end, including quote fetching/normalization from the Across API and submission logic that can send approval + deposit transactions (batched when needed) using new
TransactionTypevalues (perpsAcrossDeposit,predictAcrossDeposit).Updates
TransactionPayControllerto support an orderedgetStrategiesaction (defaulting to[Relay, Across]) and changes quote retrieval and publish-hook execution to select the first compatible strategy and fallback to the next on quote/execution failures.Introduces MetaMask Pay metrics: records quote latency on Across quotes and stores execution submit latency in
tx.metamaskPay.executionLatencyMs(preserving existing metadata), plus addsimpact/impactRatiofee fields and refactors Relay/Bridge submission flows via a sharedsubmitSourceTransactionshelper and more robust feature-flag parsing.Written by Cursor Bugbot for commit 4185673. This will update automatically on new commits. Configure here.