Skip to content

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

Merged
saustrie-consensys merged 7 commits intomainfrom
saustrie-consensys/TRAM-3475-circuit-breaker-open-error
Apr 30, 2026
Merged

fix(ramps-controller): expose circuit breaker error key#8596
saustrie-consensys merged 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)

before-buy-broken-circuit-main.mp4

After (this core PR + mobile PR branch)

after-buy-broken-circuit-pr-branches-rerecord.mp4

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.

@saustrie-consensys saustrie-consensys added this pull request to the merge queue Apr 30, 2026
Merged via the queue into main with commit 7cf7768 Apr 30, 2026
366 checks passed
@saustrie-consensys saustrie-consensys deleted the saustrie-consensys/TRAM-3475-circuit-breaker-open-error branch April 30, 2026 22:01
This was referenced May 5, 2026
@mcmire mcmire mentioned this pull request May 5, 2026
pull Bot pushed a commit to dmrazzy/core that referenced this pull request May 5, 2026
## Summary

This release PR publishes `@metamask/ramps-controller@13.3.0`, which
includes the circuit-breaker error-key fix from
[MetaMask#8596](MetaMask#8596) and the other
unreleased ramps-controller changes accumulated since `13.2.0`.

## Why this is separate

This is the successor to
[MetaMask#8692](MetaMask#8692). That PR was
originally cut as `Release/958.0.0`, but
[MetaMask#8691](MetaMask#8691) merged first and
consumed `958.0.0`, so this re-cuts the same content as
`Release/959.0.0` from current `main`.

## What changed

- bump the monorepo release version to `959.0.0`
- publish `@metamask/ramps-controller@13.3.0`
- move the current ramps-controller unreleased entries into the `13.3.0`
changelog section
- update `@metamask/transaction-pay-controller` to depend on
`@metamask/ramps-controller@^13.3.0`

## Why the transaction-pay-controller range update is included

`transaction-pay-controller` is the in-repo consumer of
`@metamask/ramps-controller`. Once ramps-controller is versioned to
`13.3.0`, the monorepo dependency constraints require the internal
dependency range to move from `^13.2.0` to `^13.3.0` as well. This keeps
the repo internally consistent without broadening the published package
scope.

## Validation

- `yarn install` (regenerates `yarn.lock`)
- `yarn constraints`
- `yarn lint:dependencies`
- `yarn lint:misc:check`
- `yarn workspace @metamask/ramps-controller changelog:validate`
- `yarn workspace @metamask/transaction-pay-controller
changelog:validate`
- `yarn workspace @metamask/ramps-controller build`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk release bookkeeping: version bumps, changelog cut, and an
internal dependency range update with no functional code changes in this
diff.
> 
> **Overview**
> Bumps the monorepo version to `959.0.0` and publishes
`@metamask/ramps-controller@13.3.0` by cutting the existing *Unreleased*
changelog entries into a new `13.3.0` section.
> 
> Updates `@metamask/transaction-pay-controller` to depend on
`@metamask/ramps-controller@^13.3.0`, with corresponding `yarn.lock`
changes to keep workspace constraints consistent.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
90e73f1. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
pull Bot pushed a commit to dmrazzy/core that referenced this pull request May 5, 2026
> [!NOTE]
> This is a repeat of MetaMask#8698 because the release workflow was not
triggered correctly.

## Summary

This release PR publishes `@metamask/ramps-controller@13.3.0`, which
includes the circuit-breaker error-key fix from
[MetaMask#8596](MetaMask#8596) and the other
unreleased ramps-controller changes accumulated since `13.2.0`.

## Why this is separate

This is the successor to
[MetaMask#8692](MetaMask#8692). That PR was
originally cut as `Release/958.0.0`, but
[MetaMask#8691](MetaMask#8691) merged first and
consumed `958.0.0`, so this re-cuts the same content as
`Release/959.0.0` from current `main`.

## What changed

- bump the monorepo release version to `959.0.0`
- publish `@metamask/ramps-controller@13.3.0`
- move the current ramps-controller unreleased entries into the `13.3.0`
changelog section
- update `@metamask/transaction-pay-controller` to depend on
`@metamask/ramps-controller@^13.3.0`

## Why the transaction-pay-controller range update is included

`transaction-pay-controller` is the in-repo consumer of
`@metamask/ramps-controller`. Once ramps-controller is versioned to
`13.3.0`, the monorepo dependency constraints require the internal
dependency range to move from `^13.2.0` to `^13.3.0` as well. This keeps
the repo internally consistent without broadening the published package
scope.

## Validation

- `yarn install` (regenerates `yarn.lock`)
- `yarn constraints`
- `yarn lint:dependencies`
- `yarn lint:misc:check`
- `yarn workspace @metamask/ramps-controller changelog:validate`
- `yarn workspace @metamask/transaction-pay-controller
changelog:validate`
- `yarn workspace @metamask/ramps-controller build`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk release bookkeeping: version/changelog updates and an
internal dependency range bump, with no functional code changes in this
diff.
> 
> **Overview**
> Bumps the monorepo release version to `959.0.0` and publishes
`@metamask/ramps-controller@13.3.0` by moving the previously
*Unreleased* ramps entries into a new `13.3.0` changelog section and
updating compare links.
> 
> Updates the in-repo consumer `@metamask/transaction-pay-controller` to
depend on `@metamask/ramps-controller@^13.3.0`, with corresponding
`yarn.lock` adjustments.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
45b6091. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: saustrie-consensys <shane.austrie@consensys.net>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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