All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Add the pure
getHeadlessProviderAllowlist(remoteFeatureFlagState, surface?)helper plus theHEADLESS_ALLOWLIST_SURFACESconstant andHeadlessAllowlistSurfacetype, resolving the provider-id allowlist carried by themoneyHeadlessAllProvidersflag's object payload (surfaces[surface]overrides the top-levelproviderIds; absent, empty, or malformed levels fall through to no restriction) (#9524) - Add an optional
surfaceoption toRampsController.getQuotes(canonical valuesmoney|perps|predictions) selecting the per-surface allowlist from the flag payload on the widened all-providers path; it does not affect fetching or caching (#9524)
- Widen the
moneyHeadlessAllProvidersflag value contract to accept an object payload{ enabled: true, providerIds?: string[], surfaces?: Record<string, string[]> }alongside the boolean form (#9524)isHeadlessAllProvidersEnablednow returnstruefor an object payload whoseenabledis the literaltrue; every other previously-false value (strings, numbers, arrays, disabled or malformed payloads) still resolves tofalse, and the boolean forms behave exactly as before, so boolean-only configurations see no behavior change- When the payload lists provider ids, the widened quote pick drops candidates whose provider is not listed (ids match in prefixed
/providers/xor bare form, case-insensitively); if nothing survives,getQuotesreturns an emptysuccess[]withsorted/error/customActionspreserved - Clients on earlier versions coerce the object payload to
false(native-only), so serving it cannot enable widening for a client that cannot parse it
- Add the
MONEY_HEADLESS_ALL_PROVIDERS_FLAG_KEYconstant (moneyHeadlessAllProviders) and the pureisHeadlessAllProvidersEnabled(remoteFeatureFlagState)helper (with aHeadlessFeatureFlagsLookuptype) that own the flag key lookup,localOverrides-over-remoteFeatureFlagsmerging, and boolean coercion (only the literaltrueenables), so UI consumers resolve the flag exactly like the controller does (#9409) - Add pure provider-availability helpers
providerServesAsset,getProvidersServingAsset,regionHasProviderForAsset, andisFiatDepositAvailableso headless-buy consumers can share the controller's case-insensitive CAIP-19 asset matching and flag-aware region/availability gating instead of re-deriving it, keeping the two from disagreeing;regionHasProviderForAssetandisFiatDepositAvailabletake anallProvidersEnabledboolean (#9409) - Add pure quote-classification helpers
isExternalBrowserQuote,isCustomActionQuote, andisInAppOnlyQuoteso consumers can share the in-app-vs-external browser-mode classification without owning host redirect/deeplink concerns (#9409) - Add pure error-normalization helpers
getErrorMessage,extractExplicitTypedError, andnormalizeToTypedError(with aTypedError<Code>type) so consumers can share error-shape extraction while keeping their own error-code taxonomy (#9409) - Add
@metamask/remote-feature-flag-controller^4.2.2as a runtime dependency (#9409)
- BREAKING: Replace the
getProviderScopeconstructor option and the exportedProviderScopetype ('off' | 'in-app' | 'all') with a controller-side read of themoneyHeadlessAllProvidersboolean remote feature flag (#9409)RampsController.getQuotesresolves the flag through theRemoteFeatureFlagController:getStatemessenger action on each auto-selection call, so a remote fetch or a local dev override takes effect at runtime; consumers should delegate that action to the controller's messenger (when it is missing, the flag read fails closed and quoting stays native-only)- When the flag is
true, the auto-selection path (autoSelectProvider/restrictToKnownOrNativeProviders) widens to every supporting provider class (native, in-app WebView aggregator, and external-browser / custom-action) and returns the best quote atsuccess[0], enforcing per-provider fiat limits; when the flag isfalse, missing, or any non-boolean value, the path stays native-only - The intermediate
in-appscope (which excluded external-browser and custom-action quotes from selection) is removed
RampsControllernow derives its internal region provider-asset matching from the sharedproviderAvailabilityhelpers, so the exposed helpers stay behaviourally identical to the controller's own selection (#9409)
- BREAKING: Provider IDs are no longer normalized by stripping a
/providers/prefix.RampsController.getQuotesnow matches provider IDs from the providers list, quotes, custom actions, and sort order as-is, and a precreated stub order'sprovider.idis the canonical provider code passed to the create-order call rather than a/providers/-prefixed value. Consumers must supply non-prefixed (canonical) provider IDs (#9448) - Update
LICENSEtext (#9472) - Bump
@metamask/profile-sync-controllerfrom^28.2.0to^28.3.0(#9463)
- BREAKING: Remove the
normalizeProviderCodeexport (#9448)
- Add an optional
getProviderScopecallback toRampsControllerOptionsand export theProviderScopetype ('off' | 'in-app' | 'all'); when it returns a non-offscope,RampsController.getQuoteswidens its native-only auto-selection (theautoSelectProvider/restrictToKnownOrNativeProviderspath) to every supporting provider and returns the best in-app quote atsuccess[0], excluding external-browser and custom-action quotes and enforcing per-provider fiat limits, while the default stays native-only and explicit-providerscallers are unaffected (#9353) - Add an optional
getDefaultRedirectUrlcallback toRampsControllerOptions; on the widened in-app auto-selection path, when the caller omitsredirectUrl,RampsController.getQuotesnow supplies this default so aggregator quotes carry thebuyURL/buyWidgetwidget URL the app needs, while an explicit callerredirectUrlalways wins and scopeoffnever injects a default (#9353)
- Refetch the countries catalog on every app startup via
init()so region preset amounts (e.g. default amounts) stay current (#9261) - Re-sync
userRegionpreset amounts from the countries catalog after eachgetCountries()call (#9261) - On startup,
init()now preserves an already-persisteduserRegionwhen the refreshed countries catalog is momentarily empty or no longer lists that region, instead of clearing it (#9261) - Bump
@metamask/messengerfrom^1.2.0to^2.0.0(#9392)
- BREAKING:
RampsController.getProviders,RampsService.getProviders,RampsController.getPaymentMethods, andRampsService.getPaymentMethodsno longer accept afiatquery filter; region local fiat filtering is applied server-side when omitted (#9245) - Bump
@metamask/controller-utilsfrom^12.2.0to^12.3.0(#9218)
- Export
getTransakApiMessageandisTransakPhoneRegisteredErrorfor consumers handlingTransakApiError, and centralize known Transak API error codes intransakErrorCodes.ts(#9135)
- Bump
@metamask/profile-sync-controllerfrom^28.1.1to^28.2.0(#9119)
- Compare internal order codes (from canonical order
id) instead of provider-nativeproviderOrderIdwhen merging orders inRampsController.addOrderandRampsController.getOrder(#9159)
RampsService.getQuotesnow sends anAuthorization: Bearer <token>header, sourcing the token fromAuthenticationController:getBearerToken(already a required messenger action since14.0.0); the call throws if no token is available (e.g. the wallet is locked or the user is signed out) (#8888)- Bump
@metamask/controller-utilsfrom^12.1.0to^12.2.0(#9058, #9083)
- Fix Transak Native deposits failing on staging/UAT with a 400
paymentMethod is required parametererror by mapping canonical payment method IDs supplied without the/payments/prefix (e.g.apple-pay) to their deposit-format equivalents, in addition to the prefixed forms (e.g./payments/apple-pay) (#8980)
- Add
autoSelectProvider,preferredProviderIds, andrestrictToKnownOrNativeProvidersoptions to thegetQuotesmethod (andRampsController:getQuotesmessenger action) (#8949)- When
autoSelectProvideristrueandprovidersis omitted,getQuotesresolves a provider supporting the requestedassetIdfor that request only, against the provider list for the requested region. It prefers the currently selected provider (when it supports the asset), then preferred providers — taken frompreferredProviderIdswhen supplied, otherwise derived from the user's completed-order history (most recent first) — then a native provider (identified by the API'stypefield, e.g. Transak Native), then the first supporting provider. The selected-provider state is never mutated. - When
restrictToKnownOrNativeProvidersistrue, auto-selection still honors a previously-used provider (selected, then completed-order history) but otherwise resolves only a native provider, introducing no other provider; an explicitly passedproviderslist is filtered to those supporting the region/asset. If nothing qualifies,getQuotesreturns an empty response instead of quoting other providers.
- When
- Add an optional
typefield ('native' | 'aggregator') to theProvidertype, mirroring the v2 providers API (#8949)
- Bump
@metamask/profile-sync-controllerfrom^28.1.0to^28.1.1(#8912)
- Authenticate ramps requests by sourcing a bearer token from
AuthenticationController:getBearerTokenand sending it as anAuthorization: Bearer <token>header forgetBuyWidgetUrl(#8843) - Add
@metamask/profile-sync-controller^28.1.0as a runtime dependency (#8843)
- BREAKING:
RampsServiceMessengernow requires theAuthenticationController:getBearerTokenaction to be delegated to it; consumers must register this action handler before callinggetBuyWidgetUrl, otherwise the call will throw (#8843) - Bump
@metamask/controller-utilsfrom^12.0.0to^12.1.0(#8774)
- Bump
@metamask/controller-utilsfrom^11.20.0to^12.0.0(#8755)
- Bump
@metamask/messengerfrom^1.1.1to^1.2.0(#8632) RampsServiceroutesRampsEnvironment.Developmentto dev-api base URLs; regions requests in development omit the-cachehostname segment used in staging and production (#8574)
- Tag circuit-breaker errors in
RampsControllerwith a stableCIRCUIT_BREAKER_OPENerror key so clients can localize the fallback copy without depending on internal Cockatiel text. (#8596)
- Bump
@metamask/base-controllerfrom^9.0.1to^9.1.0(#8457)
TransakService.verifyUserOtpno longer retries on failure, preventing single-use OTP attempts from being silently consumed when consumers configure a non-zeromaxRetriesinpolicyOptions(#8468)
- Add optional provider fiat/payment buy limits to
Providerso consumers can validate quote amounts before requesting quotes (#8405)
- Bump
@metamask/controller-utilsfrom^11.19.0to^11.20.0(#8344) - Bump
@metamask/messengerfrom^1.0.0to^1.1.1(#8364, #8373) - BREAKING: Removed controller-side data fetching (
fireAndForget) fromsetSelectedToken,setSelectedProvider, andsetUserRegion; ramp data fetching is now fully driven by the client (#8354)- Client migration: trigger
getTokens,getProviders, andgetPaymentMethodsfrom the client layer (for example, React Query hooks/effects) when region/provider/token changes. setSelectedProvider/setSelectedToken/setUserRegionnow focus on selection/state updates and no longer implicitly fetch dependent resources.
- Client migration: trigger
setSelectedProviderandsetSelectedPaymentMethodaccept a full object in addition to an ID string; no longer throw when data is not loaded (#8354)
initno longer overrides a persisteduserRegionwith the geolocation endpoint response (#8354)
- Add
providerAutoSelectedboolean toRampsControllerStateto track whether the selected provider was system-guessed (soft selection) or user-chosen (#8305) - Add optional
optionsparameter tosetSelectedProvideraccepting{ autoSelected?: boolean }to control theproviderAutoSelectedflag (#8305)
- Bump
@metamask/base-controllerfrom^9.0.0to^9.0.1(#8317) - Bump
@metamask/messengerfrom^0.3.0to^1.0.0(#8317) - Stop persisting
providersandtokensstate across sessions to prevent stale data when API availability changes (#8307) RampsService.getOrderandgetOrderFromCallbackaccept provider codes with or without a/providers/prefix; API paths use the short provider segment.RampsControllerforwards provider ids from order polling to the service without stripping the prefix. (#8278)
addPrecreatedOrdernormalizesproviderCode(stripping a leading/providers/when present) and sets stubprovider.idto/providers/{code}so precreated stubs match the resource id shape used elsewhere for polling. (#8289)
- Expose all public
RampsControllermethods through its messenger (#8221)- The following actions are now available:
RampsController:executeRequestRampsController:abortRequestRampsController:getRequestStateRampsController:setUserRegionRampsController:setSelectedProviderRampsController:initRampsController:getCountriesRampsController:getTokensRampsController:getProvidersRampsController:getPaymentMethodsRampsController:setSelectedPaymentMethodRampsController:addOrderRampsController:removeOrderRampsController:startOrderPollingRampsController:stopOrderPollingRampsController:getBuyWidgetDataRampsController:addPrecreatedOrderRampsController:getOrderFromCallbackRampsController:transakSetApiKeyRampsController:transakSetAccessTokenRampsController:transakClearAccessTokenRampsController:transakSetAuthenticatedRampsController:transakResetStateRampsController:transakSendUserOtpRampsController:transakVerifyUserOtpRampsController:transakLogoutRampsController:transakGetUserDetailsRampsController:transakGetBuyQuoteRampsController:transakGetKycRequirementRampsController:transakGetAdditionalRequirementsRampsController:transakCreateOrderRampsController:transakGetOrderRampsController:transakGetUserLimitsRampsController:transakRequestOttRampsController:transakGeneratePaymentWidgetUrlRampsController:transakSubmitPurposeOfUsageFormRampsController:transakPatchUserRampsController:transakSubmitSsnDetailsRampsController:transakConfirmPaymentRampsController:transakGetTranslationRampsController:transakGetIdProofStatusRampsController:transakCancelOrderRampsController:transakCancelAllActiveOrdersRampsController:transakGetActiveOrders
- Corresponding action types are now exported (e.g.
RampsControllerGetOrderAction)
- The following actions are now available:
- Fix
getOrderwallet handling so API requests and event payloads stay valid and consistent (#8251)RampsService.getOrderno longer sends an emptywalletquery parameter, avoiding invalid API responses (e.g. 400).RampsController.getOrderpersists and returns a healed order (walletAddressandproviderOrderId) so controller state matches the return value andRampsController:orderStatusChangedlisteners.
- BREAKING: Update state hydration to make
init()idempotent and removehydrateState()(#8157)
- Remove
hydrateState()— useinit()as the single entry point for controller hydration
- BREAKING: Replace
getWidgetUrlwithgetBuyWidgetData(returnsBuyWidget | null); addaddPrecreatedOrderfor custom-action ramp flows (e.g., PayPal, Robinhood, Coinbase) (#8100)
setSelectedProviderno longer fetches payment methods when the selected token is explicitly not supported by the new provider, preventing empty payment method state with no user feedback (#8103)
- Added
orders: RampsOrder[]to controller state with persistence, along with crud methods(#8045) - Added
apiMessageproperty toTransakApiErrorto surface human-readable error messages from the Transak API (e.g. OTP rate-limit cooldown) (#8072) - Added
RampsController:orderStatusChangedevent, published when a polled order's status transitions (#8045) - Add messenger actions for
RampsController:setSelectedToken,RampsController:getQuotes, andRampsController:getOrder, register their handlers inRampsController, and export the action types from the package index (#8081)
- BREAKING: Remove
state.quotesandstate.widgetUrlfrom RampsController state. Quote and widget URL data are now managed by consuming components (#8013) - BREAKING: Remove
fetchQuotesForSelection()andsetSelectedQuote(). Components callgetQuotes()directly and manage selection locally (#8013) - Simplify
getWidgetUrl()to a pure fetch-and-return API; it no longer reads or writes controller state (#8013) - Improve
TransakServiceerror handling (#8010) - BREAKING: Replace
startQuotePolling()/stopQuotePolling()withfetchQuotesForSelection()— quotes are now fetched once per call instead of polling on a 15-second interval (#7999)
- Remove
stopQuotePolling()method (no interval to stop) (#7999) - Remove internal polling restart logic (
#restartPollingIfActive) fromsetSelectedProvider,setSelectedToken, andsetSelectedPaymentMethod(#7999)
- Fix RampsController flaky test (#8018)
- Add
getOrderandgetOrderFromCallbackmethods toRampsServiceandRampsControllerfor V2 unified order polling, along with newRampsOrder,RampsOrderFiatCurrency,RampsOrderCryptoCurrency,RampsOrderPaymentMethod, andRampsOrderStatustypes (#7934)
- BREAKING: Use concrete types in
RampsOrderinstead ofstring | Objectunions forprovider,cryptoCurrency,fiatCurrency,paymentMethod, andnetworkfields (#8000) - Bump
@metamask/controller-utilsfrom^11.18.0to^11.19.0(#7995)
- Add
widgetUrlresource state that automatically fetches and stores the buy widget URL whenever the selected quote changes (#7920) - Add
TransakServicefor native Transak deposit flow with OTP auth, KYC, quoting, order lifecycle, and payment widget URL generation (#7922) - Add
nativeProviders.transakstate slice and controller convenience methods for driving the Transak native deposit flow (#7922)
- Refactor: Consolidate reset logic with a shared resetResource helper and fix abort handling for dependent resources (#7818)
- BREAKING: Quote filter param renamed from
providertoprovidersarray ingetQuotes()andRampsService.getQuotes()(#7892) - BREAKING: Make
getWidgetUrl()async to fetch the actual provider widget URL from thebuyURLendpoint (#7881)
- Fixes quote race condition bug with missing payment method (#7863)
- Update payment method delay type to match API response structure (#7845)
- Add automatic quote polling with
startQuotePolling(),stopQuotePolling(), andsetSelectedQuote()methods, with auto-selection when a single quote is returned (#7824)
- BREAKING: Require provider selection for quote polling and update quotes API endpoint to
/v2/quotes(#7846)
- BREAKING: Restructure
RampsControllerStateto use nestedResourceStateobjects for each resource withdata,selected,isLoading, anderror(#7779)
- Add quotes functionality to RampsController (#7747)
- Fix
getQuotes()to trimassetIdandwalletAddressparameters before use (#7793)
- Add
hydrateState()method to fetch providers and tokens for user region (#7707) - Add
countriesstate to RampsController with 24 hour TTL caching (#7707) - Add
SupportedActionstype for{ buy: boolean; sell: boolean }support info - Add
selectedTokenstate andsetSelectedToken()method to RampsController (#7734) - Add
RampsEnvironment.Localoption to RampsService for local development (#7734)
- Reorganize
init()to only fetch geolocation and countries; remove token and provider fetching (#7707) - BREAKING: Change
Country.supportedandState.supportedfrombooleantoSupportedActionsobject. The API now returns buy/sell support info in a single call. - BREAKING: Remove
actionparameter fromgetCountries(). Countries are no longer fetched separately for buy/sell actions. - BREAKING: Rename
preferredProvidertoselectedProviderandsetPreferredProvider()tosetSelectedProvider()in RampsController (#7734) - BREAKING: Change
getPaymentMethods(options)togetPaymentMethods(region, options)with region as first parameter (#7734)
- Add sync trigger methods to RampsController (#7662)
- Export
RampActiontype for'buy' | 'sell'ramp actions (#7663) - Add payment methods support with
getPaymentMethods()method,paymentMethodsandselectedPaymentMethodstate (#7665)
- Evict expired cache entries based on TTL in addition to size-based eviction (#7674)
- Update
getTokens()to use v2 API endpoint and support optional provider parameter (#7664)
- Add
preferredProviderstate andsetPreferredProvider()method to RampsController (#7617) - Export
UserRegiontype (#7646) - Add
defaultAmountandquickAmountsfields to theCountrytype (#7645) - Add
providersstate andgetProviders()method to RampsController. Providers are automatically fetched on init and when the region changes (#7652)
- BREAKING: Change
userRegionfromstring | nulltoUserRegion | null. Access region code viauserRegion.regionCode. (#7646) - Update
getCountries()endpoint to use v2 API (v2/regions/countries) (#7645) - Add
getApiPath()helper function for versioned API paths with v2 default (#7645)
- BREAKING: Remove eligibility concept from RampsController. The
eligibilitystate,updateEligibility()method, andgetEligibility()service method have been removed. TheEligibilitytype andRampsServiceGetEligibilityActionare no longer exported. (#7651)
- Add
getTokens()method to RampsController for fetching available tokens by region and action (#7607)
- BREAKING: Rename
geolocationtouserRegionandupdateGeolocation()toupdateUserRegion()in RampsController (#7563) - Bump
@metamask/controller-utilsfrom^11.17.0to^11.18.0(#7583)
- Add eligibility state (#7539)
- Add
createRequestSelectorutility function for creating memoized selectors for RampsController request states (#7554) - Add request caching infrastructure with TTL, deduplication, and abort support (#7536)
- Add
init()andsetUserRegion()methods to RampsController (#7563)
- Bump
@metamask/controller-utilsfrom^11.16.0to^11.17.0(#7534)
- BREAKING: Rename
OnRampServicetoRampsServiceandOnRampEnvironmenttoRampsEnvironment(#7502) - BREAKING: Rename action types from
OnRampService:*toRampsService:*(e.g.,OnRampService:getGeolocation→RampsService:getGeolocation) (#7502)
- Fix
RampsService#getGeolocationto read response text within the policy execution and return parsed text (#7502)
- Initial release (#7316)
- Add
RampsControllerfor managing on/off ramps state - Add
OnRampServicefor interacting with the OnRamp API - Add geolocation detection via IP address lookup
- Add