Skip to content

feat(transaction-pay-controller): add server pay strategy#8894

Merged
matthewwalsh0 merged 42 commits into
mainfrom
feat/generic-intents-pay-strategy
May 29, 2026
Merged

feat(transaction-pay-controller): add server pay strategy#8894
matthewwalsh0 merged 42 commits into
mainfrom
feat/generic-intents-pay-strategy

Conversation

@matthewwalsh0
Copy link
Copy Markdown
Member

@matthewwalsh0 matthewwalsh0 commented May 26, 2026

Explanation

Adds a Server strategy to TransactionPayController that consumes the intents-API's provider-agnostic /quote, /submit, and /status endpoints.

The strategy supports both gasless flows (where the quote provider handles submission using a signed delegation, removing the need for the user to submit an on-chain transaction) and non-gasless flows (where the quote steps are submitted through TransactionController as normal transactions). Both paths converge on a /status polling loop to track completion and surface the target hash.

Server strategy flags live in confirmations_pay_extended rather than confirmations_pay, keeping the rollout gated independently from Relay and Across. The strategy is disabled by default.

Not yet supported in this initial implementation:

  • Post-quote
  • Perps withdrawals
  • Predict withdrawals
  • Account overrides
  • Payment overrides

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
Touches payment execution, delegation/EIP-7702 quoting, and on-chain submission paths; rollout is gated by remote flags but misconfiguration or API errors could affect user fund flows.

Overview
Adds a Server pay strategy to transaction-pay-controller that talks to the MetaMask intents API (/quote, /submit, /status) instead of provider-specific Relay/Across clients directly.

Routing & flags: TransactionPayStrategy.Server is registered in strategy resolution; default strategy order now prefers Server first. Enablement, base URL, and polling settings come from confirmations_pay_extended.payStrategies.server (off by default), separate from Relay/Across flags.

Quote path: Builds intents quote requests (exact input vs expected output, ERC-20 recipient decoding, EIP-7702 delegation calls/authorizationList, gasless eligibility) and normalizes API results into TransactionPayQuote, including gas-station / source-network fee handling for non-gasless quotes. Hyperliquid perps deposits are detected via bridge calldata and rewritten to a HyperCore sentinel target.

Submit path: Gasless quotes submit a signed delegation via /submit; non-gasless quotes push steps through TransactionController (addTransaction / addTransactionBatch), then both paths poll /status until confirmed (with timeouts, source/target hash updates, and isIntentComplete). getGasFee is exported for reuse in server quote gas math.

Tests & changelog: Broad unit coverage for API client, quotes, submit, perps normalization, strategy wiring, and feature-flag behavior.

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

@matthewwalsh0 matthewwalsh0 changed the title feat(transaction-pay-controller): add generic intents-api pay strategy feat(transaction-pay-controller): add server intents-api pay strategy May 27, 2026
@matthewwalsh0 matthewwalsh0 force-pushed the feat/generic-intents-pay-strategy branch from e5c9ffc to 0c91233 Compare May 27, 2026 10:53
@matthewwalsh0 matthewwalsh0 reopened this May 27, 2026
@matthewwalsh0 matthewwalsh0 force-pushed the feat/generic-intents-pay-strategy branch from e079367 to fbd3a1b Compare May 27, 2026 23:26
@matthewwalsh0 matthewwalsh0 changed the title feat(transaction-pay-controller): add server intents-api pay strategy feat(transaction-pay-controller): add server pay strategy May 29, 2026
@matthewwalsh0 matthewwalsh0 marked this pull request as ready for review May 29, 2026 10:27
@matthewwalsh0 matthewwalsh0 requested review from a team as code owners May 29, 2026 10:27
…for non-gasless generic quotes

The generic strategy now branches on the backend's gasless flag: gasless quotes still go through POST /submit; non-gasless quotes are submitted through TransactionController.addTransaction or addTransactionBatch, including EIP-7702 batching and gas-station support, mirroring the existing Relay strategy. Status is polled via /status on both paths until CONFIRMED. Source network fees are computed for non-gasless quotes; gasless quotes remain zeroed since the relayer covers gas.
…s in generic strategy

Detects Hyperliquid perps deposits by sniffing the parent transaction calldata
for the Hyperliquid bridge contract address (no transaction-type coupling) and
rewrites the destination to a provider-agnostic HyperCore sentinel
(`0x2100…`) with 8-decimal amount. Backend providers translate the sentinel
to their own native HyperCore identifiers.
…ic quote requests

Adds an optional `supportsGasless` field to `GenericQuoteRequest` and sets it
in `buildGenericQuoteRequest` when:

- the calling account supports EIP-7702,
- Relay execute is enabled via the remote feature flag, and
- the source chain is EIP-7702-capable.

This mirrors the existing Relay strategy condition and lets backend providers
opt into gasless execution paths (notably `originGasOverhead` on Relay)
without coupling the client to any one provider's wire format.
… generic supportsGasless

Removes the `isRelayExecuteEnabled` gate from `supportsGasless`. The flag is
not yet rolled out in the relevant environments, but generic gasless flow
should remain available whenever the account itself supports EIP-7702 on a
7702-capable chain. Backend providers remain the source of truth for whether
they actually return a gasless quote.
…ntent confirms

Mirrors the Relay and Across strategies: once the generic /status endpoint
reports CONFIRMED, mark the parent transaction's `isIntentComplete` so
downstream UI (activity list, confirmation cleanup) treats the pay flow as
done. Previously the generic strategy returned the target hash but left
`isIntentComplete` unset, causing the parent transaction to linger in an
incomplete state.
… generic strategy

- Set default intents API URL to prod (https://intents.api.cx.metamask.io)
- Remove unnecessary Boolean() wrapper in buildGenericQuoteRequest
- Use Array.at(-1) instead of slice(-1)[0]
- Simplify URLSearchParams optional hash param
- Rename datas to fragments in transactionDataReferencesBridge with clarifying comment
- Simplify GENERIC_DEFAULT_PROVIDER_PRIORITY cast
- Remove obfuscated string-concat mock accessors in generic-submit test
- Trim changelog entry to consumer-facing surface area
…t quote time

- Pass sourceHash from metamaskPay into getStatus call so M0 provider can match openTx
- Estimate gas (maxFeePerGas, maxPriorityFeePerGas) at quote time via GasFeeController
- Add gasLimits, is7702, maxFeePerGas, maxPriorityFeePerGas to ServerQuoteClient type
- stepToParams uses step.maxFeePerGas with client estimate as fallback
- calculateSourceNetworkCost uses gas estimate from quote for cost display
@matthewwalsh0 matthewwalsh0 force-pushed the feat/generic-intents-pay-strategy branch from 036f479 to a2e42ba Compare May 29, 2026 10:36
Copy link
Copy Markdown

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

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

Comment on lines +19 to +22
supports(request: PayStrategyGetQuotesRequest): boolean {
const config = getPayStrategiesConfig(request.messenger);
return config.server.enabled;
}
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.

Just want to confirm we won't enable this strategy until we fully support the listed use-cases in the PR description right?

As we only gate with FF here, when this flag is eventually turned on, and Server becomes first in DEFAULT_STRATEGY_ORDER and will be attempted for every transaction - including the not implemented flows listed in the PR description.

The fallback loop only skips to the next strategy if getQuotes throws or returns empty. If the intents API happens to return a valid-looking quote for one of these unsupported flows, it'll be accepted and executed without the special handling that Relay/Across provide for those cases.

Copy link
Copy Markdown
Member Author

@matthewwalsh0 matthewwalsh0 May 29, 2026

Choose a reason for hiding this comment

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

Absolutely, this is just the initial logic.

The endpoints don't even exist in the production API yet so it would throw and try the next strategy in the priority order.

@matthewwalsh0 matthewwalsh0 added this pull request to the merge queue May 29, 2026
Merged via the queue into main with commit 72e5cc1 May 29, 2026
370 checks passed
@matthewwalsh0 matthewwalsh0 deleted the feat/generic-intents-pay-strategy branch May 29, 2026 11:30
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