Skip to content

fix(ramp): surface provider error messages for out-of-bounds amounts in V2 BuildQuote#28174

Open
saustrie-consensys wants to merge 23 commits intomainfrom
saustrie-consensys/fix-quote-limits-ux
Open

fix(ramp): surface provider error messages for out-of-bounds amounts in V2 BuildQuote#28174
saustrie-consensys wants to merge 23 commits intomainfrom
saustrie-consensys/fix-quote-limits-ux

Conversation

@saustrie-consensys
Copy link
Copy Markdown
Contributor

@saustrie-consensys saustrie-consensys commented Mar 31, 2026

Description

Fix UX for out-of-bounds/limits quote errors in the UB2 (Unified Buy V2) flow. Two improvements:

  1. Client-side limit validation: When the provider's fiat buy limits are available (via the enriched /v2/regions/providers endpoint), validate the entered amount locally and show an inline error immediately — skipping the slow quotes API round-trip entirely.
  2. Inline provider error text: When the quotes API returns a provider error (e.g. amount below minimum), display the provider's specific error message as plain inline text instead of the generic "encountered an error" TruncatedError.

Changes:

  • Extract provider fiat buy limits from selectedProvider.limits.fiat and validate against the entered amount before enabling the quote fetch
  • New getProviderBuyLimit() utility for looking up min/max by fiat + payment method
  • Render limit violations as plain <Text> with TextColor.ErrorDefault (no modal, no info icon)
  • Fall back to QuoteError.error from the quotes response for server-side limit errors
  • Short-circuit quoteFetchEnabled when the debounced amount violates known limits

Changelog

CHANGELOG entry: Fixed a bug where out-of-bounds amounts on the V2 Buy screen showed a generic error instead of the provider's minimum/maximum amount message. Added client-side limit validation to skip unnecessary quote API calls.

Related issues

Fixes: TRAM-3338

Related PRs (deploy in order):

  1. API: https://github.com/consensys-vertical-apps/va-mmcx-onramp-api/pull/958 — exposes fiat buy limits on /v2/regions/providers endpoint
  2. Core: feat(ramps-controller): add optional fiat buy limits to Provider type core#8405 — adds limits to Provider type in @metamask/ramps-controller
  3. Mobile: this PR — consumes limits for client-side validation + inline error text

Manual testing steps

Prerequisites: Enable UB2 via MM_RAMPS_UNIFIED_BUY_V2_ENABLED=true in .js.env

```gherkin
Feature: V2 Buy flow amount limit error display

Scenario: User enters amount below provider minimum
Given user is on the V2 Buy amount input screen
When user enters an amount below the provider's minimum limit
Then the provider's error message is displayed inline
And the Continue button is disabled
And no quote API call is made

Scenario: User enters amount above provider maximum
Given user is on the V2 Buy amount input screen
When user enters an amount above the provider's maximum limit
Then the provider's error message is displayed inline
And the Continue button is disabled
And no quote API call is made

Scenario: Provider returns error without client-side limits
Given user is on the V2 Buy amount input screen
And the provider does not have limits data
When the quotes API returns a provider error
Then the provider's specific error message is displayed as inline text
And the change provider option is hidden

Scenario: Provider returns error without message
Given user is on the V2 Buy amount input screen
When the provider returns an error without a specific message
Then the generic no-quotes fallback is shown with change provider option
```

Screenshots/Recordings

Before

https://www.loom.com/share/7aa20837afa84a2b9cbfc05547016196

After

https://www.loom.com/share/a95ca6fcc346470596d2dd43e9b6f754

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Medium Risk
Changes quote-fetch gating and continue-button enablement in the Unified Buy V2 amount screen based on provider min/max limits and provider-returned quote errors, which could affect conversion/flow if edge cases are missed. Covered by added unit tests for limits lookup, hook behavior, and BuildQuote UI states.

Overview
Improves Unified Buy V2 BuildQuote UX for out-of-bounds amounts by validating provider fiat buy limits client-side (min/max) and disabling quote fetching and Continue when the entered amount is invalid.

When no quotes are returned, the screen now surfaces the provider’s specific error message inline as plain error text (no info icon/modal), while still falling back to a generic TruncatedError (with change-provider option) when errors lack provider messages.

Adds getProviderBuyLimit and useProviderLimits (with tests), new i18n strings for min/max purchase limits, bumps @metamask/ramps-controller to ^13.1.0, and includes small test-only refactors in SafeAreaViewWithHookTopInset.test.tsx.

Reviewed by Cursor Bugbot for commit 58363b6. Bugbot is set up for automated code reviews on this repo. Configure here.

@saustrie-consensys saustrie-consensys self-assigned this Mar 31, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2026

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.

@saustrie-consensys saustrie-consensys force-pushed the saustrie-consensys/fix-quote-limits-ux branch from 5a4fadb to 00a89a7 Compare March 31, 2026 14:54
…date limit wording

Disable the Get Quotes button when the entered amount is below minimum, above maximum,
exceeds balance, or exceeds balance minus gas. Update limit error messages from
"Minimum/Maximum deposit is" to "Minimum/Maximum purchase is" to match Figma designs.
Surface provider-specific error messages on the Quotes screen when all providers return
errors instead of showing only the generic fallback text. Fix pre-existing test failure
by adding a mock for @metamask/react-native-button (incompatible with RN 0.76).
…x-quote-limits-ux

# Conflicts:
#	app/components/UI/Ramp/Aggregator/Views/BuildQuote/__snapshots__/BuildQuote.test.tsx.snap
#	app/components/UI/Ramp/Aggregator/Views/Quotes/__snapshots__/Quotes.test.tsx.snap
@saustrie-consensys saustrie-consensys force-pushed the saustrie-consensys/fix-quote-limits-ux branch from 00a89a7 to 5ba027e Compare April 1, 2026 13:37
@saustrie-consensys
Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@saustrie-consensys saustrie-consensys added the team-money-movement issues related to Money Movement features label Apr 1, 2026
@saustrie-consensys saustrie-consensys marked this pull request as ready for review April 1, 2026 13:59
@amitabh94
Copy link
Copy Markdown
Contributor

amitabh94 commented Apr 1, 2026

We are touching the wrong files here. Make sure you check with cursor that we want to only improve the experience under the feature flag unifiedBuyV2.

…in V2 BuildQuote

Revert changes to legacy Aggregator views and apply the fix to the V2
unified buy flow (behind unifiedBuyV2 feature flag). When the quotes API
returns provider errors with no successful quotes, display the provider's
error message (e.g. minimum/maximum amount) instead of the generic
"no quotes" fallback.
@saustrie-consensys saustrie-consensys changed the title fix(ramp): disable Get Quotes button for out-of-bounds amounts and up… fix(ramp): surface provider error messages for out-of-bounds amounts in V2 BuildQuote Apr 1, 2026
@github-actions github-actions bot added the risk-medium Moderate testing recommended · Possible bug introduction risk label Apr 1, 2026
Reverts unintended changes to legacy Aggregator files (pre-UB2) and
adds comprehensive tests for provider error handling in the V2
BuildQuote flow.
@github-actions github-actions bot added risk-low Low testing needed · Low bug introduction risk and removed risk-medium Moderate testing recommended · Possible bug introduction risk labels Apr 1, 2026
@github-actions github-actions bot added risk-low Low testing needed · Low bug introduction risk and removed risk-low Low testing needed · Low bug introduction risk labels Apr 1, 2026
@github-actions github-actions bot added risk-low Low testing needed · Low bug introduction risk and removed risk-low Low testing needed · Low bug introduction risk labels Apr 1, 2026
@github-actions github-actions bot added risk-high Extensive testing required · High bug introduction risk and removed risk-high Extensive testing required · High bug introduction risk labels Apr 9, 2026
… ternary

- Narrow type assertion scope in getProviderBuyLimit (SonarCloud)
- Remove isQuoteAmountSettled from quoteFetchEnabled so the Continue
  button no longer flashes disabled on every keystroke during debounce
- Extract nested ternary in action section into an actionSectionMessage
  variable with explicit if/else logic (SonarCloud)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions bot added risk-high Extensive testing required · High bug introduction risk and removed risk-high Extensive testing required · High bug introduction risk labels Apr 9, 2026
…ranch)

The RAMPS_BASE_URL_OVERRIDE feature now lives on
saustrie-consensys/ramps-base-url-override.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions bot added risk-high Extensive testing required · High bug introduction risk and removed risk-high Extensive testing required · High bug introduction risk labels Apr 10, 2026
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.15%. Comparing base (0c26cf4) to head (13bd4dc).
⚠️ Report is 25 commits behind head on main.

Files with missing lines Patch % Lines
...components/UI/Ramp/Views/BuildQuote/BuildQuote.tsx 94.11% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #28174      +/-   ##
==========================================
+ Coverage   82.13%   82.15%   +0.02%     
==========================================
  Files        4942     4950       +8     
  Lines      129947   130101     +154     
  Branches    28977    29015      +38     
==========================================
+ Hits       106732   106885     +153     
- Misses      15917    15921       +4     
+ Partials     7298     7295       -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown
Contributor

E2E fixtures updated.

@github-actions github-actions bot requested a review from a team as a code owner April 10, 2026 12:20
const debouncedPollingAmount = useDebouncedValue(amountAsNumber, 500);
const hasAmount = amountAsNumber > 0;

const selectedProviderBuyLimit = useMemo(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please move selectedProviderBuyLimit and amountLimitError to a useProviderLimits hook similar to what we have on aggregator:

const {
limits,
isFetching: isFetchingLimits,
isAmountBelowMinimum,
isAmountAboveMaximum,
isAmountValid,
} = useLimits();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Quick update: I implemented it on Friday. By doing this, I'm now over the 1k lines limit: https://github.com/MetaMask/metamask-mobile/actions/runs/24293058001/job/70933469937?pr=28174

@wachunei
Copy link
Copy Markdown
Member

From the loom videos I see some spacing issues with the new errors. The new error must be displayed just like the old error (same spacing, no tooltip though)

image image

saustrie-consensys and others added 2 commits April 11, 2026 23:11
… quotes async, fix error placement

- Revert unnecessary async/await in quotes.ts queryFn (no try/catch needed)
- Extract selectedProviderBuyLimit and amountLimitError into useProviderLimits hook
- Move inline limit error into actionSectionMessage IIFE inside actionSection
- Remove custom limitErrorText style, use actionSection gap for spacing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@saustrie-consensys
Copy link
Copy Markdown
Contributor Author

From the loom videos I see some spacing issues with the new errors. The new error must be displayed just like the old error (same spacing, no tooltip though)

image image

Here's a new screenshot:
Screenshot 2026-04-12 at 1 21 55 AM

@github-actions github-actions bot added size-XL and removed size-L risk-high Extensive testing required · High bug introduction risk labels Apr 11, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeAccounts, SmokeConfirmations, SmokeIdentity, SmokeNetworkAbstractions, SmokeNetworkExpansion, SmokeTrade, SmokeWalletPlatform, SmokeCard, SmokePerps, SmokeRamps, SmokeMultiChainAPI, SmokePredictions, SmokeSeedlessOnboarding, FlaskBuildTests
  • Selected Performance tags: @PerformanceAccountList, @PerformanceOnboarding, @PerformanceLogin, @PerformanceSwaps, @PerformanceLaunch, @PerformanceAssetLoading, @PerformancePredict, @PerformancePreps
  • Risk Level: high
  • AI Confidence: 100%
click to see 🤖 AI reasoning details

E2E Test Selection:
Hard rule (controller-version-update): @MetaMask controller package version updated in package.json: @metamask/ramps-controller. Running all tests.

Performance Test Selection:
Hard rule (controller-version-update): @MetaMask controller package version updated in package.json: @metamask/ramps-controller. Running all tests.

View GitHub Actions results

@github-actions github-actions bot added the risk-high Extensive testing required · High bug introduction risk label Apr 11, 2026
Copy link
Copy Markdown

@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.

Reviewed by Cursor Bugbot for commit 58363b6. Configure here.

<Text
variant={TextVariant.BodySm}
color={TextColor.ErrorDefault}
style={{ textAlign: 'center' as const }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Inline style object recreated on every render

Low Severity

The actionSectionMessage renders a Text component with an inline style object { textAlign: 'center' as const }, which creates a new object reference on every render. The rest of the component consistently uses StyleSheet.create for styles (e.g., styles.poweredByText). This inline object breaks the pattern and causes unnecessary style reconciliation on each render cycle.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 58363b6. Configure here.

@sonarqubecloud
Copy link
Copy Markdown

@github-actions
Copy link
Copy Markdown
Contributor

E2E Fixture Validation — Schema is up to date
11 value mismatches detected (expected — fixture represents an existing user).
View details

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

Labels

risk-high Extensive testing required · High bug introduction risk size-XL team-money-movement issues related to Money Movement features team-ramps type-bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants