Skip to content

fix(ramps-controller): expose circuit breaker error key#8596

Open
saustrie-consensys wants to merge 7 commits intomainfrom
saustrie-consensys/TRAM-3475-circuit-breaker-open-error
Open

fix(ramps-controller): expose circuit breaker error key#8596
saustrie-consensys wants to merge 7 commits intomainfrom
saustrie-consensys/TRAM-3475-circuit-breaker-open-error

Conversation

@saustrie-consensys
Copy link
Copy Markdown
Contributor

@saustrie-consensys saustrie-consensys commented Apr 27, 2026

Explanation

When repeated upstream failures trip the service-policy circuit breaker, RampsController receives a BrokenCircuitError from @metamask/controller-utils. Cockatiel's default message for that condition is Execution prevented because the circuit breaker is open, which is useful for logs/debugging but not something clients should localize by string-matching.

This PR keeps core out of copy ownership by:

  • exporting RAMPS_ERROR_CODES.CIRCUIT_BREAKER_OPEN
  • attaching errorKey metadata to classified request/resource failures
  • rethrowing classified errors with the same errorKey so clients can localize them
  • preserving the original technical message for logging/debugging rather than replacing it with English UI copy

Follow-up after review tightened the contract in two ways:

  • classify circuit-breaker failures by error instanceof BrokenCircuitError first, with the previous message match retained only as a compatibility fallback for wrapped/plain errors that have lost the prototype
  • normalize and rethrow the same errorKey across the broader set of policy-backed Transak wrappers, so native-flow callers receive consistent metadata instead of a mix of keyed and raw infrastructure errors

This keeps the boundary clean: core decides what kind of failure this is, and clients decide which localized fallback to show. Coverage now includes the shared executeRequest path, request/resource state, selector behavior, and the Transak methods used by native flows that previously rethrew raw breaker errors.

The companion mobile change now uses that boundary to show a dedicated circuit-breaker fallback instead of the previous generic quote error. The copy says about 30 minutes because the service-policy cooldown defaults to 30 minutes, but the current ramps error contract does not carry a live remaining-duration value.

Demo Videos

These two recordings were captured by wiring the local core checkout into the local mobile checkout and hard-throwing the same BrokenCircuitError inside the RampsController.getQuotes fetcher on both branches.

GitHub sanitizes iframe markup in PR bodies, so the demos are embedded with HTML <video> tags backed by GitHub-hosted MP4s instead.

Before (main)

After (this core PR + mobile PR branch)

References

Testing

  • yarn workspace @metamask/ramps-controller test
  • yarn workspace @metamask/ramps-controller messenger-action-types:check
  • yarn eslint packages/ramps-controller/src/RampsController.ts packages/ramps-controller/src/RampsController.test.ts packages/ramps-controller/src/RequestCache.ts packages/ramps-controller/src/selectors.ts packages/ramps-controller/src/selectors.test.ts packages/ramps-controller/src/index.ts packages/ramps-controller/src/rampsErrorCodes.ts
  • yarn tsc --noEmit -p packages/ramps-controller/tsconfig.json (still fails in this checkout due existing referenced-build / monorepo type issues, including TS6305 build-artifact errors and unchanged pre-existing TransakService.ts type errors)
  • Follow-up review pass: yarn workspace @metamask/ramps-controller run jest --no-coverage src/RampsController.test.ts
  • Follow-up review pass: yarn eslint packages/ramps-controller/src/RampsController.ts packages/ramps-controller/src/RampsController.test.ts

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
Touches shared error-handling and state-shaping paths (requests/resources/selectors) and changes what gets thrown/returned on failures, which could affect downstream consumers that assume previous error shapes.

Overview
RampsController now classifies circuit-breaker failures and propagates a stable errorKey (RAMPS_ERROR_CODES.CIRCUIT_BREAKER_OPEN) alongside the original error message, so clients can localize fallback copy without string-matching Cockatiel text.

This threads errorKey through cached request state (createErrorState/RequestState), resource state (ResourceState + reset/clear paths), and request selectors (optionally returning errorKey), and normalizes/rethrows errors from executeRequest and multiple Transak helper methods to include the same errorKey even when the thrown value isn’t an Error (string/object). Tests and changelog are updated accordingly, and an eslint suppression entry is removed for RampsController.ts.

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

@saustrie-consensys saustrie-consensys changed the title fix(ramps-controller): replace circuit breaker error with retry copy fix(ramps-controller): expose circuit breaker error key Apr 27, 2026
Comment thread packages/ramps-controller/src/RampsController.ts Fixed
Copy link
Copy Markdown
Contributor Author

Follow-up pushed in 4bd517387 after review.

The main change in thinking here is that the circuit breaker is an internal service-policy signal, not provider copy. core should classify that condition once and attach stable metadata, while clients decide which localized fallback to show.

This follow-up tightens that contract in two ways:

  • it detects the breaker by error instanceof BrokenCircuitError first, instead of relying only on Cockatiel's English message; the old message match stays as a compatibility fallback for wrapped/plain errors that have lost their prototype
  • it normalizes and rethrows CIRCUIT_BREAKER_OPEN across the broader set of policy-backed Transak wrappers, so native-flow callers no longer get a mix of keyed errors and raw infrastructure text depending on which method failed

That keeps the mobile side decoupled from @metamask/controller-utils and Cockatiel details, while still preserving the original technical message for logging/debugging.

@saustrie-consensys saustrie-consensys marked this pull request as ready for review April 29, 2026 18:28
@saustrie-consensys saustrie-consensys requested review from a team as code owners April 29, 2026 18:28
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 cbe9a86. Configure here.

Comment thread packages/ramps-controller/src/RampsController.ts Outdated
@amitabh94
Copy link
Copy Markdown
Contributor

Can you please publish a preview of this PR and use that to create a mobile PR to see if anything breaks?

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-previews

@github-actions
Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@7.1.0-preview-7cef09c77
@metamask-previews/accounts-controller@37.2.0-preview-7cef09c77
@metamask-previews/address-book-controller@7.1.1-preview-7cef09c77
@metamask-previews/ai-controllers@0.6.3-preview-7cef09c77
@metamask-previews/analytics-controller@1.0.1-preview-7cef09c77
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-7cef09c77
@metamask-previews/announcement-controller@8.1.0-preview-7cef09c77
@metamask-previews/app-metadata-controller@2.0.1-preview-7cef09c77
@metamask-previews/approval-controller@9.0.1-preview-7cef09c77
@metamask-previews/assets-controller@6.2.1-preview-7cef09c77
@metamask-previews/assets-controllers@105.0.0-preview-7cef09c77
@metamask-previews/authenticated-user-storage@1.0.0-preview-7cef09c77
@metamask-previews/base-controller@9.1.0-preview-7cef09c77
@metamask-previews/base-data-service@0.1.1-preview-7cef09c77
@metamask-previews/bridge-controller@71.0.0-preview-7cef09c77
@metamask-previews/bridge-status-controller@71.1.0-preview-7cef09c77
@metamask-previews/build-utils@3.0.4-preview-7cef09c77
@metamask-previews/chain-agnostic-permission@1.5.0-preview-7cef09c77
@metamask-previews/chomp-api-service@2.0.0-preview-7cef09c77
@metamask-previews/claims-controller@0.5.0-preview-7cef09c77
@metamask-previews/client-controller@1.0.1-preview-7cef09c77
@metamask-previews/compliance-controller@2.0.0-preview-7cef09c77
@metamask-previews/composable-controller@12.0.1-preview-7cef09c77
@metamask-previews/config-registry-controller@0.3.0-preview-7cef09c77
@metamask-previews/connectivity-controller@0.2.0-preview-7cef09c77
@metamask-previews/controller-utils@11.20.0-preview-7cef09c77
@metamask-previews/core-backend@6.2.1-preview-7cef09c77
@metamask-previews/delegation-controller@3.0.0-preview-7cef09c77
@metamask-previews/earn-controller@12.0.0-preview-7cef09c77
@metamask-previews/eip-5792-middleware@3.0.3-preview-7cef09c77
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.0-preview-7cef09c77
@metamask-previews/eip1193-permission-middleware@1.0.3-preview-7cef09c77
@metamask-previews/ens-controller@19.1.1-preview-7cef09c77
@metamask-previews/eth-block-tracker@15.0.1-preview-7cef09c77
@metamask-previews/eth-json-rpc-middleware@23.1.3-preview-7cef09c77
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-7cef09c77
@metamask-previews/foundryup@1.0.1-preview-7cef09c77
@metamask-previews/gas-fee-controller@26.1.1-preview-7cef09c77
@metamask-previews/gator-permissions-controller@4.0.0-preview-7cef09c77
@metamask-previews/geolocation-controller@0.1.2-preview-7cef09c77
@metamask-previews/json-rpc-engine@10.2.4-preview-7cef09c77
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-7cef09c77
@metamask-previews/keyring-controller@25.3.0-preview-7cef09c77
@metamask-previews/logging-controller@8.0.1-preview-7cef09c77
@metamask-previews/message-manager@14.1.1-preview-7cef09c77
@metamask-previews/messenger@1.2.0-preview-7cef09c77
@metamask-previews/messenger-cli@0.2.0-preview-7cef09c77
@metamask-previews/money-account-balance-service@0.2.0-preview-7cef09c77
@metamask-previews/money-account-controller@0.1.0-preview-7cef09c77
@metamask-previews/money-account-upgrade-controller@1.2.0-preview-7cef09c77
@metamask-previews/multichain-account-service@8.0.1-preview-7cef09c77
@metamask-previews/multichain-api-middleware@2.0.0-preview-7cef09c77
@metamask-previews/multichain-network-controller@3.0.6-preview-7cef09c77
@metamask-previews/multichain-transactions-controller@7.0.4-preview-7cef09c77
@metamask-previews/name-controller@9.1.1-preview-7cef09c77
@metamask-previews/network-controller@30.1.0-preview-7cef09c77
@metamask-previews/network-enablement-controller@5.0.2-preview-7cef09c77
@metamask-previews/notification-services-controller@23.1.0-preview-7cef09c77
@metamask-previews/passkey-controller@1.0.0-preview-7cef09c77
@metamask-previews/permission-controller@12.3.0-preview-7cef09c77
@metamask-previews/permission-log-controller@5.1.0-preview-7cef09c77
@metamask-previews/perps-controller@4.0.0-preview-7cef09c77
@metamask-previews/phishing-controller@17.1.1-preview-7cef09c77
@metamask-previews/polling-controller@16.0.4-preview-7cef09c77
@metamask-previews/preferences-controller@23.1.0-preview-7cef09c77
@metamask-previews/profile-metrics-controller@3.1.3-preview-7cef09c77
@metamask-previews/profile-sync-controller@28.0.2-preview-7cef09c77
@metamask-previews/ramps-controller@13.2.0-preview-7cef09c77
@metamask-previews/rate-limit-controller@7.0.1-preview-7cef09c77
@metamask-previews/react-data-query@0.2.0-preview-7cef09c77
@metamask-previews/remote-feature-flag-controller@4.2.0-preview-7cef09c77
@metamask-previews/sample-controllers@4.0.4-preview-7cef09c77
@metamask-previews/seedless-onboarding-controller@9.1.0-preview-7cef09c77
@metamask-previews/selected-network-controller@26.1.0-preview-7cef09c77
@metamask-previews/shield-controller@5.1.1-preview-7cef09c77
@metamask-previews/signature-controller@39.2.0-preview-7cef09c77
@metamask-previews/snap-account-service@0.0.0-preview-7cef09c77
@metamask-previews/social-controllers@2.2.0-preview-7cef09c77
@metamask-previews/storage-service@1.0.1-preview-7cef09c77
@metamask-previews/subscription-controller@6.1.2-preview-7cef09c77
@metamask-previews/transaction-controller@65.0.0-preview-7cef09c77
@metamask-previews/transaction-pay-controller@20.0.1-preview-7cef09c77
@metamask-previews/user-operation-controller@41.2.0-preview-7cef09c77

Copy link
Copy Markdown
Contributor Author

Published the preview and wired it into a dedicated draft mobile validation PR.

Preview build used:

  • @metamask-previews/ramps-controller@13.2.0-preview-7cef09c77

Mobile validation PR:

A useful thing this surfaced immediately: switching mobile to the published preview introduced a second @metamask/messenger version (1.1.1 alongside 1.2.0), and because Messenger has a private field, TypeScript treated those as nominally incompatible types. The validation PR includes the minimal consumer-side alignment (@metamask/messenger to ^1.2.0 in dependencies + resolutions) so the preview-backed mobile branch resolves a single messenger version.

Local validation on that branch:

  • yarn install
  • yarn lint:tsc
  • ramp-focused Jest suite for parseUserFacingError / provider-country-token-payment-method hooks

I kept this separate from the main mobile companion PR on purpose so the preview-consumer plumbing stays isolated and easy to drop once we’re done validating the packaged artifact.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants