Skip to content

feat(transaction-pay-controller): gate server strategy per transaction type - #10312

Open
matthewwalsh0 wants to merge 4 commits into
mainfrom
feat/pay-server-strategy-flow-gating
Open

matthewwalsh0 wants to merge 4 commits into
mainfrom
feat/pay-server-strategy-flow-gating

Conversation

@matthewwalsh0

@matthewwalsh0 matthewwalsh0 commented Sep 21, 2026

Copy link
Copy Markdown
Member

Explanation

ServerStrategy.supports returned the payStrategies.server.enabled feature flag directly, so the flag was all-or-nothing: enabling it selected the server strategy for every MetaMask Pay flow at once, including flows the strategy does not yet implement. That makes the strategy difficult to validate, because there is no way to try one flow while leaving the rest on the relay strategy.

This PR adds a server-support module that narrows supports through two independent layers:

  • A transaction type allowlist, sourced from the new payStrategies.server.enabledTransactionTypes remote feature flag. It defaults to an empty list, so payStrategies.server.enabled on its own no longer selects the strategy for anything, and flows are opted in one at a time.
  • Capability guards for request shapes the strategy does not implement yet: non-atomic execution, maximum amounts, HyperLiquid activation fees, Polymarket deposit wallets, direct mUSD Money Account quotes, and transactions requiring exact-output pricing. These are deliberately enforced in code rather than via the flag, so a flow that is known to misbehave cannot be enabled by configuration alone. Each guard is removed as the corresponding capability lands.

Declines are reported as a named ServerUnsupportedReason and logged, so it is clear which layer rejected a request.

Both layers match against nested transactions using hasTransactionType, since a transaction can carry several types. The exact-output guard is evaluated after the allowlist so that a transaction combining an allowlisted type with an exact-output type is still declined.

Unrecognised values in the feature flag are discarded rather than trusted, so a malformed remote configuration cannot widen the allowlist.

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 which MetaMask Pay flows use the server strategy at quote time; incorrect flag config could leave flows on relay unexpectedly, but defaults are conservative and hard guards block known-bad request shapes.

Overview
Server pay strategy selection is no longer all-or-nothing when payStrategies.server.enabled is on. ServerStrategy.supports now requires the transaction (including nested types) to appear in the new remote flag payStrategies.server.enabledTransactionTypes, which defaults to an empty list so enabling the server strategy alone does not route any flows until types are opted in individually.

A new server-support layer evaluates requests after the global enable check: getServerUnsupportedReason returns named decline reasons for flows that are not allowlisted or that use request shapes the server strategy does not implement yet (e.g. max amount, non-atomic, exact-output deposit-and-order types, HyperLiquid activation fee, Polymarket deposit wallet, direct mUSD money account). Those capability checks cannot be overridden by the flag. Declines are logged with reason and transaction type.

Feature-flag parsing adds enabledTransactionTypes to PayStrategiesConfig, normalizing remote strings to known TransactionType values and discarding unknown or duplicate entries.

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

…n type

`ServerStrategy.supports` previously returned the `payStrategies.server.enabled`
feature flag directly, so enabling the strategy selected it for every Pay flow
at once, including flows it does not yet implement.

Add a `server-support` module with two independent gating layers:

- An allowlist of transaction types, sourced from the new
  `payStrategies.server.enabledTransactionTypes` remote feature flag, so flows
  can be enabled individually.
- Capability guards for request shapes the strategy does not implement, which
  the feature flag cannot override.

Both layers match nested transactions via `hasTransactionType`, since a
transaction can carry several types.
@matthewwalsh0
matthewwalsh0 marked this pull request as ready for review September 21, 2026 15:50
@matthewwalsh0
matthewwalsh0 requested review from a team as code owners September 21, 2026 15:50

@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 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 1c425f3. Configure here.


if (request.hyperliquidActivationFeeUsd !== undefined) {
return ServerUnsupportedReason.HyperliquidActivationFee;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Activation fee guard never triggers

Medium Severity

The HyperLiquid activation-fee guard checks hyperliquidActivationFeeUsd, but the quote orchestrator never sets that field before supports runs. Relay writes it only while fetching quotes, after a strategy is already chosen. Enabling an allowlisted HyperLiquid-source type therefore still selects the server strategy for unactivated accounts, and those quotes will not reserve the activation fee.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1c425f3. Configure here.

This branch was successfully deployed

1 active deployment
default-branch 1c425f30 Deployed Sep 21, 2026 by matthewwalsh0 via Determine whether this PR is a release PR #4323
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