Skip to content

feat: Implement max amount with gas station fallback#7927

Merged
OGPoyraz merged 27 commits intomainfrom
ogp/25466
Mar 6, 2026
Merged

feat: Implement max amount with gas station fallback#7927
OGPoyraz merged 27 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 max-amount Relay quote computation and gas-fee-token costing logic, which can affect user spend limits and fee selection; behavior is guarded with multiple validation/fallback paths and extensive new tests.

Overview
Relay max-amount quotes now use a dedicated two-phase fallback: request the normal max quote, estimate gas cost in the source token via a probe quote and gas-station (TransactionController:getGasFeeTokens), then request an adjusted max quote and only accept it if gas limits are compatible and affordability checks pass; otherwise it falls back to the original quote.

Gas-station handling was extracted and reused via new getGasStationEligibility/getGasStationCostInSourceTokenRaw, including normalization for multi-item gas limits and support for decimal simulation fields, and quotes are tagged with metamask.isMaxGasStation when the adjusted path is used (with submit behavior covered by tests).

Written by Cursor Bugbot for commit bb72ffa. 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
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?

Copy link
Member Author

Choose a reason for hiding this comment

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

For future ref, after discussion we changed this to fully reliant on the probe quote in this commit: 3efa3b0#diff-d11fa4fc2725e2c73d5d3c98eb76b3c31767c71109d9955199fcdd15b33b490d

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?

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.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

matthewwalsh0
matthewwalsh0 previously approved these changes Mar 5, 2026
@OGPoyraz OGPoyraz added this pull request to the merge queue Mar 6, 2026
Merged via the queue into main with commit 0076a5d Mar 6, 2026
322 checks passed
@OGPoyraz OGPoyraz deleted the ogp/25466 branch March 6, 2026 10:57
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