Skip to content

feat: Implement FiatStrategy and getQuotes functionality#8121

Open
OGPoyraz wants to merge 6 commits intomainfrom
ogp/7069
Open

feat: Implement FiatStrategy and getQuotes functionality#8121
OGPoyraz wants to merge 6 commits intomainfrom
ogp/7069

Conversation

@OGPoyraz
Copy link
Member

@OGPoyraz OGPoyraz commented Mar 5, 2026

Explanation

This PR introduces FiatStrategy:

  • Adds TransactionPayStrategy.Fiat and registers FiatStrategy in strategy resolution.
  • Adds Fiat asset mapping by tx type in constants (MMPAY_FIAT_ASSET_ID_BY_TX_TYPE).
  • Implements Fiat quote flow in fiat-quotes.ts with relay-first estimation:
    • reads fiatPayment.amountFiat + selected payment method
    • estimates relay fees first
    • requests ramps quotes with adjusted fiat amount
    • returns one combined strategy: fiat quote
  • Splits fees in the combined quote:
    • fees.provider = relay provider/swap fee
    • fees.fiatProvider = ramps provider/network fee
    • fees.metaMask = MM fee (100 bps over amountFiat + adjustedAmountFiat)
  • Updates totals/metadata sync so fee aggregation stays consistent, including combined metamaskPay.bridgeFeeFiat when fiat provider fee exists.
  • Adds package wiring for ramps types/dependency and updates changelog.

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
Adds a new quoting path that combines relay fee estimation with fiat on-ramp quotes and introduces new fee breakdown fields, which could affect quote selection and totals calculations. Risk is moderated by extensive unit tests and the submit path being a placeholder.

Overview
Adds a new TransactionPayStrategy.Fiat and registers FiatStrategy, enabling MM Pay to return combined fiat+relay quotes by first estimating relay fees, then requesting RampsController:getQuotes with an adjusted fiat amount.

Extends the quote/totals data model to carry fiatPaymentMethod through quoting and to include an optional fees.providerFiat breakdown (fiat on-ramp provider/network fees), and updates totals aggregation and metadata sync logic accordingly.

Wires in @metamask/ramps-controller (deps + TS references), adds fiat-asset mappings by transaction type, introduces computeRawFromFiatAmount, and includes comprehensive tests for the new fiat quote flow and edge cases.

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

@OGPoyraz OGPoyraz requested review from a team as code owners March 5, 2026 14:56
@socket-security
Copy link

socket-security bot commented Mar 5, 2026

No dependency changes detected. Learn more about Socket for GitHub.

👍 No dependency changes detected in pull request

@OGPoyraz OGPoyraz changed the title feat: Implement FiatStrategy and getQuotes functionality [DONT MERGE] feat: Implement FiatStrategy and getQuotes functionality Mar 5, 2026
@OGPoyraz OGPoyraz changed the title [DONT MERGE] feat: Implement FiatStrategy and getQuotes functionality feat: Implement FiatStrategy and getQuotes functionality Mar 17, 2026
@OGPoyraz
Copy link
Member Author

@SocketSecurity ignore npm/@metamask/ramps-controller@12.0.0

### Added

- **BREAKING:** Add `AssetsControllerGetStateForTransactionPayAction` to the `AllowedActions` messenger type ([#8163](https://github.com/MetaMask/core/pull/8163))
- Add MMPay `FiatStrategy` quotes flow ([#8121](https://github.com/MetaMask/core/pull/8121))
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- Add MMPay `FiatStrategy` quotes flow ([#8121](https://github.com/MetaMask/core/pull/8121))
- Add `FiatStrategy` to retrieve quotes via `RampsController` ([#8121](https://github.com/MetaMask/core/pull/8121))

export const POLYGON_USDCE_ADDRESS =
'0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174' as Hex;

export type TransactionPayFiatAsset = {
Copy link
Member

@matthewwalsh0 matthewwalsh0 Mar 20, 2026

Choose a reason for hiding this comment

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

Should all of the be in strategy/fiat/[types|consants].ts?

Copy link
Member Author

Choose a reason for hiding this comment

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

Moved to strategy/fiat/constants.ts and strategy/fiat/utils.ts


import type { RelayQuote } from '../relay/types';

export type FiatOriginalQuote = {
Copy link
Member

Choose a reason for hiding this comment

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

Minor, in the context of this controller, could this just be FiatQuote?

Copy link
Member Author

Choose a reason for hiding this comment

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

Renamed to FiatQuote

import type { RelayQuote } from '../relay/types';

export type FiatOriginalQuote = {
fiatQuote: Quote;
Copy link
Member

Choose a reason for hiding this comment

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

Would it add clarity to call this rampsQuote as "Fiat" is our abstraction over Ramps and Relay together?

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, renamed to rampsQuote

provider: FiatValue;

/** Fee charged by fiat on-ramp provider. */
fiatProvider?: FiatValue;
Copy link
Member

Choose a reason for hiding this comment

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

providerFiat?

Or should we be more generic and call this providerSecondary so we can have a mechanism to have a more granular fee and the client can choose what to call it?

Copy link
Member Author

Choose a reason for hiding this comment

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

Went with providerFiat for now, we can generalize later if needed


function getRampsProviderFee(fiatQuote: RampsQuote): BigNumber {
return new BigNumber(fiatQuote.quote.providerFee ?? 0).plus(
fiatQuote.quote.networkFee ?? 0,
Copy link
Member

Choose a reason for hiding this comment

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

Nice, so they obviously handle any network fees, but include it separately?

Copy link
Member Author

Choose a reason for hiding this comment

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

Exactly, not sure how accurate that is but it's definitely in the quote as I saw from testing.

fiat: metaMaskFee,
usd: metaMaskFee,
},
provider: relayQuote.fees.provider,
Copy link
Member

Choose a reason for hiding this comment

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

For consistency, this needs to be the total provider fee for the quote, so ramps + relay.

Meaning we can calculate the Relay portion if needed in the client by subtracting the fiatProvider value.

Copy link
Member Author

Choose a reason for hiding this comment

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

fees.provider is now relay + ramps combined; providerFiat is the ramps-only breakdown.

}
}

return MMPAY_FIAT_ASSET_ID_BY_TX_TYPE[transactionType as TransactionType];
Copy link
Member

Choose a reason for hiding this comment

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

Minor, is MMPAY implied?

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, renamed to FIAT_ASSET_ID_BY_TX_TYPE

tx.metamaskPay = {
bridgeFeeFiat: totals.fees.provider.usd,
bridgeFeeFiat: totals.fees.fiatProvider
? new BigNumber(totals.fees.provider.usd)
Copy link
Member

Choose a reason for hiding this comment

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

As mentioned in other comment, this shouldn't be needed as provider always needs to be the total.

.toString(10);

const totalUsd = new BigNumber(providerFee.usd)
.plus(fiatProviderFee.usd)
Copy link
Member

Choose a reason for hiding this comment

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

Also here, this is an additional optional breakdown, but provider remains the total provider fee.

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

@OGPoyraz OGPoyraz requested a review from matthewwalsh0 March 22, 2026 20:43
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.

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.

// TODO: Implement provider selection logic; force Transak staging for now.
(quote) => quote.provider === '/providers/transak-native-staging',
);
}
Copy link

Choose a reason for hiding this comment

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

Hardcoded staging provider blocks production fiat quotes

Medium Severity

pickBestFiatQuote only matches the staging provider string '/providers/transak-native-staging'. In any non-staging environment the ramps response will use a different provider identifier, causing this function to always return undefined and the fiat strategy to produce no quotes.

Fix in Cursor Fix in Web

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.

2 participants