⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content

feat(transaction-pay): add TokenPay strategy with Across provider + metrics#7806

Open
pedronfigueiredo wants to merge 2 commits intomainfrom
pnf/cor-across-strategy
Open

feat(transaction-pay): add TokenPay strategy with Across provider + metrics#7806
pedronfigueiredo wants to merge 2 commits intomainfrom
pnf/cor-across-strategy

Conversation

@pedronfigueiredo
Copy link
Contributor

@pedronfigueiredo pedronfigueiredo commented Feb 2, 2026

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

  • New TokenPay strategy + provider adapter interface/registry.
  • Across provider:
    • Quote requests to /swap/approval (GET or POST with actions).
    • Quote normalization for provider fees, source/target network fees, dust, amounts, and duration.
    • Actions API support for delegated calls (token transfer + delegation).
    • Submit flow for approval + swap txs, requiredTransactionIds tracking, intent completion.
  • Relay adapter:
    • Wraps existing Relay quote + submit logic for TokenPay.
  • Feature flags:
    • TokenPay provider config (Across API base/key/app fees/slippage; Relay quote URL).
  • Gating and TODOs:
    • Reject same‑chain quotes (mUSD conversions) when Across can’t do same‑chain swaps.
    • Disable perps deposits (Hypercore) until Across supports USDC‑PERPs.
    • Block type‑4/EIP‑7702 authorizationList actions until supported (first‑time Polymarket deposits).
  • Metrics:
    • Across quote latency stored on quote original.metrics.latency.
    • Execution latency stored on transactionMeta.metamaskPay.executionLatencyMs.

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
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: TransactionPayFees gains impact/impactRatio, Relay quotes now compute impact when missing, and execution submit latency is recorded once per flow and persisted to transactionMeta.metamaskPay.executionLatencyMs (preserved across metadata updates). Feature flags now include payStrategies config (Across API base/keys/fees; Relay enabled/quote URL) and default strategy order [Relay, Across]. Also adds new transaction types perpsAcrossDeposit and predictAcrossDeposit and threads onSubmitted latency callbacks through strategies/submission helpers.

Written by Cursor Bugbot for commit b6c9ca4. This will update automatically on new commits. Configure here.

@pedronfigueiredo pedronfigueiredo force-pushed the pnf/cor-across-strategy branch 2 times, most recently from 6e9ba0c to 047509a Compare February 2, 2026 15:16
import { getRelayQuotes } from './relay-quotes';
import { submitRelayQuotes } from './relay-submit';

export class RelayProvider implements TokenPayProvider<RelayQuote> {
Copy link
Member

Choose a reason for hiding this comment

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

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',
Copy link
Member

Choose a reason for hiding this comment

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

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;
Copy link
Member

Choose a reason for hiding this comment

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

Minor, alphabetical.


- `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).
Copy link
Member

Choose a reason for hiding this comment

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

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',
Copy link
Member

Choose a reason for hiding this comment

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

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) {
Copy link
Member

Choose a reason for hiding this comment

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

Is this breaking the abstraction if it knows about the implementations?

return { transactionHash };
}

async function executeSingleQuote(
Copy link
Member

Choose a reason for hiding this comment

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

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;
Copy link
Member

Choose a reason for hiding this comment

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

We have some feature flags for this too, so another benefit of a common util.

};
}

function buildDelegationAction(delegation: {
Copy link
Member

Choose a reason for hiding this comment

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

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';
Copy link
Member

Choose a reason for hiding this comment

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

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I adjusted the provider fee to be the impact just like in the case of relay.

@pedronfigueiredo pedronfigueiredo force-pushed the pnf/cor-across-strategy branch 3 times, most recently from 4ee01fe to 204fc2e Compare February 5, 2026 13:26
@pedronfigueiredo pedronfigueiredo force-pushed the pnf/cor-across-strategy branch 2 times, most recently from fe1cc6e to cb2195a Compare February 5, 2026 16:59
…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
Copy link

@cursor cursor bot left a comment

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.

default:
return TransactionType.perpsAcrossDeposit;
}
}
Copy link

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

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.

2 participants