Skip to content

feat: Implement max amount with gas station fallback#7927

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

feat: Implement max amount with gas station fallback#7927
OGPoyraz wants to merge 16 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
Introduces new control flow for Relay max-amount quoting and changes how gas-fee-token costs are estimated/normalized, which can affect quote correctness and max-amount execution paths across supported chains.

Overview
Implements a dedicated max-amount “gas station” fallback for Relay quotes: when isMaxAmount is set, the strategy now may request an initial quote, estimate gas cost in the source token (via quote values, Gas Station estimation, or a probe quote), then re-request an adjusted quote and validate affordability before returning it.

Extracts Gas Station logic into a new gas-station helper (eligibility + source-token gas cost with multi-item normalization and support for decimal/hex simulation fields) and wires it into existing post-quote/source-network fee calculation to reuse the same normalization behavior.

Adds metamask.isMaxGasStation to Relay quote types and adjusts Relay submission behavior to keep max-gas-station executions on the existing gasFeeToken-only path (no gasFeeTokenAmount), with extensive new unit test coverage and changelog entry.

Written by Cursor Bugbot for commit 11d1a85. 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

const log = createModuleLogger(
projectLogger,
'max-amount-with-gas-station-fallback',
Copy link
Member

Choose a reason for hiding this comment

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

relay-max-gas-station? Maybe the file also?

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

};
}

function getGasStationEligibility(
Copy link
Member

Choose a reason for hiding this comment

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

Minor, some duplication with the existing relay-quotes, so probably worth some gas-station utils not specific to any pay strategy in future.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good call, addressed it by extracting shared Relay gas-station logic into gas-station-utils.ts and reusing it from both quote paths.

): Promise<GasCostEstimate | undefined> {
const gasCost = quote.fees.sourceNetwork.max;

if (quote.fees.isSourceGasFeeToken) {
Copy link
Member

Choose a reason for hiding this comment

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

To clarify, this shouldn't be possible with max amount since there would always be no balance left?

};
}

async function getGasStationCostInSourceTokenRaw(
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 a big duplication with the existing logic in relay-quotes, could we make some gas-station utils that are Relay specific for now within strategy/relay?

Maybe a getGasStationCost(quote) for example?

Copy link
Member Author

Choose a reason for hiding this comment

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

I extracted the duplicated Relay gas-station logic into gas-station.ts and now reuse it in both relay-quotes and relay-max-gas-station.

return phase1Quote;
}

function isAdjustedAmountPositive(adjustedSourceAmount: BigNumber): boolean {
Copy link
Member

Choose a reason for hiding this comment

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

For greater readability, should we avoid these tiny utils that are only used once?

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

if (
!isAdjustedAmountAffordable(
adjustedSourceAmount,
validationGasEstimate.amount,
Copy link
Member

Choose a reason for hiding this comment

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

We don't care about the new amount do we, as we calculated the old one to be exactly what is remaining of the balance, so do we want to use that to avoid dust?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think we shouldn't blindly trust the new amount, but we also avoid forcing the original estimate when quote internals shift slightly. This keeps max safety and minimizes dust risk without introducing a new submit API in this PR.

};
metamask: {
gasLimits: number[];
twoPhaseQuoteForMaxAmount?: boolean;
Copy link
Member

Choose a reason for hiding this comment

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

isMaxGasStation?

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


markQuoteAsTwoPhaseForMaxAmount(phase2Quote);

return phase2Quote;
Copy link
Member

Choose a reason for hiding this comment

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

Should we force isSourceGasFeeToken here and also set the fees and amount for sourceNetwork using our original value from the probe quote?


const slippage = featureFlags.slippage ?? DEFAULT_SLIPPAGE;

const maxGaslessEnabled =
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 needed since currently the user just gets an error so there is no risk to functionality?

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed it, as it fallbacks and shows error. It rarely happens but still shows error on re-quoting so I assume we are safe


markQuoteAsTwoPhaseForMaxAmount(phase2Quote);

return phase2Quote;
Copy link
Member

Choose a reason for hiding this comment

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

Potentially a separate PR, but are the gas fee tokens consistent in your testing?

Or should we also update relay-submit to specify a new gasFeeTokenAmount property to the TransactionController to avoid any dust?

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.

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