feat(money): show Paid by MetaMask on sponsored mUSD conversions#30120
Conversation
…USD-794) - bridge-fee-row: add isPaidByMetaMask predicate gated to musdConversion + all-zero fees; render green check + "Paid by MetaMask" label inline and hide the tooltip icon - total-row: hide for musdConversion via HIDE_TYPES (mirrors bridge-time-row) - custom-amount-info: 8px gap between post-quote rows; CTA label uses new earn.musd_conversion.confirm i18n key - useMusdConversionNavbar: header info icon now uses IconColor.IconDefault - Money routes: register RedesignedConfirmations on MoneyModalStack and route Money Home / Potential Earnings entry points through the modal stack so the convert screen overlays the bottom tab bar - locales/en.json: add earn.musd_conversion.confirm
|
CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes. |
…ader (MUSD-794) - Add MoneyClaimableBonusInfoSheet and MoneyConvertInfoSheet that mirror the existing MoneyApyInfoSheet pattern (BottomSheet + BottomSheetHeader + body, no "Got it" button), per design - Register both on MoneyModalStack so they overlay the bottom tab bar - PercentageRow: render a plain Icon + TouchableOpacity in place of InfoRow's built-in inline Tooltip; opens the new sheet on press at 16x16 IconAlternative - useMusdConversionNavbar: header (i) navigates to the new sheet; analytics and terms-link handling moved into the sheets themselves - locales/en.json: add earn.musd_conversion.convert_tooltip_description
Matt561
left a comment
There was a problem hiding this comment.
The "Paid by MetaMask" message is testing well but I have some concerns with the implementation.
| @@ -0,0 +1,77 @@ | |||
| import React, { useCallback, useRef } from 'react'; | |||
There was a problem hiding this comment.
I feel the better approach here would be to update the Tooltip component that all info-rows use. This way all MM Pay tooltip are uniform. We want to avoid creating bespoke bottom sheets for MM Pay since it's meant to be feature agnostic.
| @@ -0,0 +1,82 @@ | |||
| import React, { useCallback, useRef } from 'react'; | |||
| @@ -97,7 +97,7 @@ const MoneyPotentialEarningsView = () => { | |||
| address: token.address as Hex, | |||
| chainId: token.chainId as Hex, | |||
| }, | |||
| navigationStack: Routes.MONEY.ROOT, | |||
| navigationStack: Routes.MONEY.MODALS.ROOT, | |||
There was a problem hiding this comment.
Are these changes required? The confirmation screen (Routes.FULL_SCREEN_CONFIRMATIONS.REDESIGNED_CONFIRMATIONS) should be in the EarnScreenStack.
| )} | ||
| {isResultReady && ( | ||
| <Box> | ||
| <Box gap={8}> |
There was a problem hiding this comment.
@matthewwalsh0 are you okay with this styling change? I want to double-check to ensure that Earn isn't suggesting design changes to shared components without reaching out to confirmations first.
| return new BigNumber(sourceNetworkUsd).plus(targetNetworkUsd); | ||
| } | ||
|
|
||
| function isPaidByMetaMask({ |
There was a problem hiding this comment.
This could be a good candidate for a new hook.
| if (!hasTransactionType(transactionMeta, [TransactionType.musdConversion])) | ||
| return false; |
There was a problem hiding this comment.
Should we allow this "Paid by MetaMask" message to display for any transaction type where the fees are zero?
| labelChildren={ | ||
| <TouchableOpacity | ||
| onPress={handleInfoPress} | ||
| testID="percentage-row-tooltip-open-btn" | ||
| style={styles.tooltipButton} | ||
| hitSlop={8} | ||
| > | ||
| <Icon | ||
| name={IconName.Info} | ||
| size={IconSize.Sm} | ||
| color={IconColor.IconAlternative} | ||
| /> | ||
| </TouchableOpacity> |
There was a problem hiding this comment.
Let's stick to using the tooltip prop and try to avoid one off implementations to ease maintenance of MM Pay tooltip styles.
| trackEvent( | ||
| createEventBuilder(MetaMetricsEvents.MUSD_BONUS_TERMS_OF_USE_PRESSED) | ||
| .addProperties({ | ||
| location: EVENT_LOCATIONS.PERCENTAGE_ROW, |
There was a problem hiding this comment.
This is using a hardcoded location of PERCENTAGE_ROW for a component that isn't aware of where it's used. This could lead to event pollution if this component is used outside the percentage row.
| chainId: token.chainId as Hex, | ||
| }, | ||
| navigationStack: Routes.MONEY.ROOT, | ||
| navigationStack: Routes.MONEY.MODALS.ROOT, |
There was a problem hiding this comment.
Is this required for the redirect to work? The confirmation screen is in the EarnScreenStack
| chainId: defaultToken.chainId as Hex, | ||
| }, | ||
| navigationStack: Routes.MONEY.ROOT, | ||
| navigationStack: Routes.MONEY.MODALS.ROOT, |
There was a problem hiding this comment.
Is this required for the redirect to work? The confirmation screen is in the EarnScreenStack
…(MUSD-794) Replace the bespoke MoneyClaimableBonusInfoSheet and MoneyConvertInfoSheet with the existing Tooltip/TooltipModal pattern already used across the confirmations UI. The bespoke sheets duplicated styling and registered themselves as navigation routes, which fragmented MM Pay tooltip styling and coupled shared confirmation components to Money-owned modal screens. - Delete MoneyClaimableBonusInfoSheet and MoneyConvertInfoSheet components - Drop CLAIMABLE_BONUS_INFO_SHEET and CONVERT_INFO_SHEET route constants - Remove the two ModalStack.Screen registrations - PercentageRow: pass tooltip + tooltipTitle to InfoRow so the standard auto-rendered Tooltip handles open/close; terms link content moves inline - useMusdConversionNavbar: owns local modal state via useState and returns a TooltipNode for the consumer to render alongside the page body - musd-conversion-info: render the returned TooltipNode in a Fragment
…tries (MUSD-794) Routing the conversion confirmation through MONEY.MODALS.ROOT was only needed when the bespoke info sheets lived under that modal stack. Now that the tooltips are self-contained inline, the default screen-stack route is correct again. Reverts MoneyHomeView and MoneyPotentialEarningsView to the pre-PR value of Routes.MONEY.ROOT.
The sponsored-conversion detection in BridgeFeeRow was an inline helper that read three transaction-pay selectors. Extract it into a hook so the predicate is colocated with its data dependencies and can be reused. Scope is intentionally kept to TransactionType.musdConversion - broadening to any zero-fee transaction is a product call left for follow-up.
…USD-794) The hook signature now types TooltipNode as a JSX Element. The mock was returning null, which CI tsc rejected with TS2322.
…moke (MUSD-794) mUSD conversions are sponsored, so TotalRow returns null for that transaction type and the bridge-fee row renders a "Paid by MetaMask" pill in place of the total. The smoke test was still asserting the old "total" element which no longer exists on the mUSD confirmation. - Tag the bridge-fee-row pill with the existing PAID_BY_METAMASK testID - Expose a paidByMetaMask getter on TransactionPayConfirmation - Update the two mUSD happy-path scenarios to assert the new surface
…(MUSD-794) Routes.FULL_SCREEN_CONFIRMATIONS.REDESIGNED_CONFIRMATIONS was registered in both MoneyScreenStack and MoneyModalStack with different header options, which made navigate() resolution to the Confirm screen ambiguous. After reverting the navigationStack overrides to MONEY.ROOT, the MoneyModalStack copy is unreachable - remove it so the screen has a single source of truth in MoneyScreenStack.
mUSD conversions are sponsored by MetaMask, so the "Paid by MetaMask" pill renders only when useIsPaidByMetaMask returns true - which requires all fee components on the resolved TransactionPayTotals to be zero. The mUSD-specific relay quote mock was stubbing a non-zero relayer fee, which made the smoke test fail to find the pill after the assertions were updated to match production design.
…ke (MUSD-794) The intermediate "is the confirmation screen ready" assertion relied on useIsPaidByMetaMask returning true, which depends on the controller normalizing the relayer fee to zero - that path isn't reachable from the current mock setup. The activity-view check at the end of each scenario already verifies the conversion completed successfully, and the tapConfirmButton call waits for the footer button before tapping.
- combine the two early-return guards into a single conditional - extract SUPPORTED_TYPES constant for transaction-type extensibility - extract PaidByLabel local component in bridge-fee-row
- revert percentage-row.tsx + test to main; main already uses the `tooltip` prop as Matt561 requested, so the in-PR refactor was a no-op - revert custom-amount-info.tsx 8px gap; the change touched a confirmations-owned shared component without justification - drop cosmetic renames and import reorders in useMusdConversionNavbar (test + impl); keep only the functional changes for the inline TooltipModal and IconDefault header (i) per design - remove the unused TransactionPayConfirmation.paidByMetaMask page-object getter; no spec references it after the intermediate smoke assertion was dropped
…D-794) Money Home and Potential Earnings entry points were forcing initiateCustomConversion to push REDESIGNED_CONFIRMATIONS onto MoneyScreenStack via navigationStack: Routes.MONEY.ROOT. That stack is mounted as a Tab.Screen inside the bottom-tab navigator, so the convert screen rendered with the tab bar still visible. useMusdConversion already defaults navigationStack to Routes.EARN.ROOT, which is mounted at the root Stack level with EarnScreenStack — fullscreen, overlapping the tab bar — and REDESIGNED_CONFIRMATIONS is already registered there. Dropping the override is enough; no new route registrations or modal-stack plumbing needed (also addresses Matt561's review point that the screen should live in EarnScreenStack).
There was a problem hiding this comment.
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.
Reviewed by Cursor Bugbot for commit 1b2af29. Configure here.
| const sourceNetwork = new BigNumber(totals.fees.sourceNetwork.estimate.usd); | ||
| const targetNetwork = new BigNumber(totals.fees.targetNetwork.usd); | ||
| const provider = new BigNumber(totals.fees.provider.usd); | ||
| const metaMask = new BigNumber(totals.fees.metaMask.usd ?? 0); |
There was a problem hiding this comment.
Missing optional chaining causes crash on undefined fee properties
High Severity
The hook accesses totals.fees.sourceNetwork.estimate.usd, totals.fees.targetNetwork.usd, totals.fees.provider.usd, and totals.fees.metaMask.usd without optional chaining after only guarding !totals?.fees. Throughout the codebase, every other consumer of these same fee properties uses optional chaining and ?? 0 fallbacks (e.g., receive-row.tsx, useInsufficientPerpsBalanceAlert.ts, PerpsOrderView.tsx), confirming sub-properties like sourceNetwork, targetNetwork, provider, and metaMask can be undefined even when fees is defined. This will throw a TypeError at runtime if any intermediate object is missing.
Reviewed by Cursor Bugbot for commit 1b2af29. Configure here.
…ack (MUSD-794) EarnScreenStack defaults all screens to `presentation: 'transparentModal'`, which gives the screen a transparent card background. For Confirm that means the screen underneath stays visible — when launched from inside the bottom-tab navigator (Money Home / Potential Earnings), the tab bar bled through the Confirm screen. Override the presentation on the Confirm registration to 'card' so it renders with the default opaque background and fully covers the tab bar. Other consumers (e.g. MusdConversionAssetOverviewCta) are unaffected because they don't depend on the screen being a transparent overlay.
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection: The PR introduces changes focused on the mUSD conversion flow (Earn/Money feature):
The changes are medium risk - they modify UI components in the confirmation flow and navigation parameters, but are scoped to the mUSD/Earn/Money feature area. The confirmation row components (bridge-fee-row, total-row) are used in broader transaction flows, warranting SmokeConfirmations coverage. Performance Test Selection: |
|
The committed fixture schema is out of date. To update, comment: |






Description
When MM Pay returns a fully sponsored mUSD conversion quote (network, provider, and MetaMask fees all zero), the confirmation screen now surfaces the sponsorship explicitly: the "Transaction fee" row renders a green check + "Paid by MetaMask" label instead of "$0", and the redundant fee-breakdown tooltip is hidden. Polishes the same screen to match design: 8px row gap, "Confirm" CTA, icon-primary header (i), the Total row is hidden for mUSD conversion, and the screen overlays the bottom tab bar when launched from Money Home / Potential Earnings.
Changelog
CHANGELOG entry: Added a "Paid by MetaMask" treatment on the mUSD conversion confirmation screen when MetaMask fully sponsors the network, provider, and gas fees.
Related issues
Fixes: MUSD-794
Manual testing steps
Screenshots/Recordings
After
Pre-merge author checklist
Performance checks (if applicable)
Pre-merge reviewer checklist
Note
Medium Risk
Updates the transaction confirmation fee/total presentation for
musdConversionand adjusts navigation/tooltip behavior, which could affect conversion confirmation UX and fee transparency if edge cases (missing fee fields/quotes) are mishandled.Overview
mUSD conversion confirmations now explicitly show sponsorship: when MM Pay returns quotes and all fee components are zero, the Transaction fee row renders a green check +
Paid by MetaMasklabel and suppresses the fee-breakdown tooltip via the newuseIsPaidByMetaMaskhook.The confirmation screen is further aligned to the sponsored design by hiding the
TotalRowformusdConversion, changing the CTA label toearn.musd_conversion.confirm, and replacing the olduseTooltipModalnavbar info flow with an inlineTooltipModal(TooltipNode) rendered byMusdConversionInfo(including updated tooltip copy and analytics for the terms link).Navigation into redesigned confirmations from the Earn stack is adjusted to present as a
card, and Money conversion initiation calls drop thenavigationStackparameter. Tests, smoke specs, and English strings are updated accordingly.Reviewed by Cursor Bugbot for commit 7c32351. Bugbot is set up for automated code reviews on this repo. Configure here.