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 2 commits 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
| import { getRelayQuotes } from './relay-quotes'; | ||
| import { submitRelayQuotes } from './relay-submit'; | ||
|
|
||
| export class RelayProvider implements TokenPayProvider<RelayQuote> { |
There was a problem hiding this comment.
For the sake of modularity and risk, can we refactor Relay in a dedicated PR and leave as is here?
Would let us have a working control in the clients when testing also.
| /** | ||
| * Deposit funds for Across quote. | ||
| */ | ||
| acrossDeposit = 'acrossDeposit', |
There was a problem hiding this comment.
We actually have a pending task to make relayDeposit more granular, so perpsRelayDeposit and predictRelayDeposit, so should do the same for Across also.
| totalFiat?: string; | ||
|
|
||
| /** Total time spent executing the MetaMask Pay flow, in milliseconds. */ | ||
| executionLatencyMs?: number; |
|
|
||
| - `tokenPay.providerOrder` controls priority (default: `[primaryProvider, 'relay', 'across']`). | ||
| - Each provider can be enabled/disabled via `tokenPay.providers.<id>.enabled`. | ||
| - Providers may also implement capability gating in `supports(...)` (e.g., Across rejects same-chain swaps). |
There was a problem hiding this comment.
This sounds like a good mechanism, but can we abstract to PayStrategy in general so we can accommodate fiat in future also and any strategy?
That way, we could isolate this into it's own dedicated PR for risk and easier review?
| export enum TransactionPayStrategy { | ||
| Bridge = 'bridge', | ||
| Relay = 'relay', | ||
| TokenPay = 'tokenPay', |
There was a problem hiding this comment.
I was assuming this would just be an abstract internal class to remove duplication.
Ideally the client could specify multiple preferences in priority order, and we pick the first that is supported or throw?
Thereby combining the new fallback mechanism and getStrategy?
| continue; | ||
| } | ||
|
|
||
| if (provider.id === 'across' && !config.providers.across.enabled) { |
There was a problem hiding this comment.
Is this breaking the abstraction if it knows about the implementations?
| return { transactionHash }; | ||
| } | ||
|
|
||
| async function executeSingleQuote( |
There was a problem hiding this comment.
I understood the core benefit of the TokenPayStrategy was to remove duplication between the Relay and Across strategies.
But if we're just using it for the fallback logic, would it not be simpler to incorporate that into PayStrategy directly (as mentioned in another comment)?
Then we could create some common token strategy utils to add the origin transactions for example given the duplication?
| return Math.ceil(estimatedGas * gasBuffer); | ||
| } catch (error) { | ||
| log('Gas estimate failed, using fallback', { error }); | ||
| return 900000; |
There was a problem hiding this comment.
We have some feature flags for this too, so another benefit of a common util.
| }; | ||
| } | ||
|
|
||
| function buildDelegationAction(delegation: { |
There was a problem hiding this comment.
This is all done in the client via a constructor callback, so we can decouple delegations from this controller.
| } | ||
|
|
||
| const relayer = quote.fees?.relayerTotal?.amountUsd ?? '0'; | ||
| const app = quote.fees?.app?.amountUsd ?? '0'; |
There was a problem hiding this comment.
I'm worried this isn't the total price impact for the user, but just the Across specific fee, does this definitely include the total impact of the bridge provider itself?
Have we confirmed for example, that the quote results in the requested fee minus the this fee only?
There was a problem hiding this comment.
Good point, I adjusted the provider fee to be the impact just like in the case of relay.
1a4c355 to
f5f13b3
Compare
4ee01fe to
204fc2e
Compare
packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts
Outdated
Show resolved
Hide resolved
fe1cc6e to
cb2195a
Compare
…etrics - Introduce TokenPay strategy with provider adapter interface and registry - Add Across provider (quotes + submit) and Relay adapter wrapper - Implement Across quote normalization (fees, dust, durations, fiat) and actions API payloads for delegated calls - Add feature flags for tokenPay providers and Across API config - Add Across submit flow (approvals + swap tx), intent completion, and confirmation waits - Gate unsupported cases (same-chain, perps deposits) and block type‑4 authorizationList until Across supports it - Add Across quote latency metrics and execution latency recording in metamaskPay metadata - Add/extend unit tests for Across quotes/submit/supports and publish hook metrics
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


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 Across quoting/submission path and changes pay strategy selection/execution to use ordered fallback, which can affect which provider executes and how source transactions are submitted. While guarded by feature flags and tests, it touches transaction submission, metadata updates, and quote normalization logic.
Overview
Adds a new Across MetaMask Pay strategy (quotes + submit) and moves strategy selection to an ordered list with compatibility checks and fallback for both quote retrieval and publish-hook execution.
Introduces Across quote fetching/normalization (including optional “actions” POST for delegation/transfer, gas estimation with buffer/fallback, and strict gating for unsupported cases like same-chain swaps when disallowed, perps deposits, and EIP-7702 authorization lists). Across submission now batches approvals + deposit transactions and records required transaction IDs/intent completion.
Adds metrics and fee model updates:
TransactionPayFeesgainsimpact/impactRatio, Relay quotes now compute impact when missing, and execution submit latency is recorded once per flow and persisted totransactionMeta.metamaskPay.executionLatencyMs(preserved across metadata updates). Feature flags now includepayStrategiesconfig (Across API base/keys/fees; Relay enabled/quote URL) and default strategy order[Relay, Across]. Also adds new transaction typesperpsAcrossDepositandpredictAcrossDepositand threadsonSubmittedlatency callbacks through strategies/submission helpers.Written by Cursor Bugbot for commit b6c9ca4. This will update automatically on new commits. Configure here.