Skip to content

feat: Implement max amount with gas station fallback#7927

Open
OGPoyraz wants to merge 22 commits intomainfrom
ogp/25466
Open

feat: Implement max amount with gas station fallback#7927
OGPoyraz wants to merge 22 commits intomainfrom
ogp/25466

Conversation

@OGPoyraz
Copy link
Member

@OGPoyraz OGPoyraz commented Feb 13, 2026

Explanation

This PR introduces a dedicated max-amount gas-station fallback flow for Relay, extracted into a focused helper and wired into quote orchestration. The goal was fixing max-amount mUSD conversion failures caused by gas-fee-token estimation dead-ends but in fact it fixes for any kind of intent.

The helper (getMaxAmountQuoteWithGasStationFallback) now implements a clear two-phase flow with explicit fallback points:

  1. Phase-1 quote (full max amount)
  • Request quote using original max source amount.
  1. Early-return guards (return phase-1 immediately)
  • If source gas fee token is not used and native balance already covers gas.
  • If maxGaslessEnabled is false.
  • If gas station is disabled/unsupported for source chain.
  1. Gas-cost estimation strategy
  • First try direct estimation from quote/gas-station params.
  • If that fails, request a probe quote with smaller source amount (PROBE_AMOUNT_PERCENTAGE = 0.25) to discover gas fee token + amount.
  • Uses TransactionController:getGasFeeTokens and calculateGasFeeTokenCost to normalize source-token gas fee amount.
  1. Phase-2 quote (adjusted max)
  • Compute adjusted amount as: adjusted = sourceAmount - estimatedGasCost.
  • Request phase-2 quote with adjusted source amount.
  1. Validation before accepting phase-2
  • Re-estimate gas on phase-2 quote.
  • Require affordability: adjusted + validationGasCost <= originalSourceAmount.
  • If valid, mark twoPhaseQuoteForMaxAmount = true and return phase-2.
  • Otherwise fallback to phase-1.

References

gasless.max.mov

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 Relay quote selection and gas-fee-token estimation for isMaxAmount flows, which can affect how much users spend and whether quotes/transactions succeed. Risk is mitigated by conservative phase-1 fallback paths and extensive new unit tests, but it still touches core quoting/gas logic.

Overview
Implements a dedicated two-phase Relay max-amount quoting flow that falls back to gas-station pricing when native gas is insufficient, using getRelayMaxGasStationQuote to estimate gas in source-token units (directly or via a probe quote), request an adjusted max quote, and only accept it after gas-limit/affordability validation.

Refactors gas-station handling into a new gas-station helper (eligibility + normalized gas-fee-token costing) and wires it into Relay quote generation, including improved normalization for multi-item/post-quote gas limits. Successful adjusted quotes are tagged via original.metamask.isMaxGasStation, and Relay submit logic is verified to keep max-gas-station submissions on the existing gasFeeToken-only path.

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

@OGPoyraz OGPoyraz marked this pull request as ready for review February 16, 2026 11:04
@OGPoyraz OGPoyraz requested review from a team as code owners February 16, 2026 11:04
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.

return primaryEstimate;
}

const probeCost = await getProbeGasCostInSourceTokenRaw(context);
Copy link
Member

Choose a reason for hiding this comment

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

How can the above fail?

If no gas fee tokens are returned from the original quote, then should we not just fallback at that point?

Why would it help to try get another quote with a smaller amount?

Copy link
Member Author

Choose a reason for hiding this comment

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

From testing, at full max, fee-token estimation/simulation can return no usable gas-fee-token result (or no match) because the transaction is too tight at the balance boundary.

With a smaller probe amount, the same route often becomes estimable and returns either:
isSourceGasFeeToken directly, or a usable getGasFeeTokens result we can normalize.

So this is a recovery path, not a bypass. It lets us rescue cases that would otherwise unnecessarily fall back.

Copy link
Member

@matthewwalsh0 matthewwalsh0 Mar 1, 2026

Choose a reason for hiding this comment

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

You're absolutely right, I'm missing the obvious, we will never get gas fee tokens in the original request since it's max so there is no remaining balance post-transaction for gas.

In that case, can we save time and skip the original identical request and go straight to the probe?

Copy link
Member Author

Choose a reason for hiding this comment

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

Great question. We’re not doing an extra "original identical" quote request here. At this point, phase-1 is already fetched (and needed for fallback + phase-1/phase-2 validation), so we first try to derive gas cost from that existing quote (isSourceGasFeeToken or getGasFeeTokens via gas-station).

Probe is intentionally a recovery fallback when that estimate is unavailable. Going straight to probe would add an extra quote call for all max requests (typically 2 calls to 3), increasing latency/failure surface, while many flows already succeed without probing.

Should we add a short inline comment in getInitialGasCostEstimate to make this ordering explicit?

return primaryEstimate;
}

const probeCost = await getProbeGasCostInSourceTokenRaw(context);
Copy link
Member

@matthewwalsh0 matthewwalsh0 Mar 1, 2026

Choose a reason for hiding this comment

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

You're absolutely right, I'm missing the obvious, we will never get gas fee tokens in the original request since it's max so there is no remaining balance post-transaction for gas.

In that case, can we save time and skip the original identical request and go straight to the probe?

.multipliedBy(PROBE_AMOUNT_PERCENTAGE)
.integerValue(BigNumber.ROUND_FLOOR);

const targetProbeRaw = new BigNumber(1).shiftedBy(
Copy link
Member

Choose a reason for hiding this comment

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

Minor, could we call this minimumProbeRaw for readability?

With a comment such as 0.01 Tokens?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable gasless mUSD conversions for max amount transactions

2 participants