From 6dbca4d083ad3a08e5146a46631129c4bd5757a2 Mon Sep 17 00:00:00 2001 From: Amitabh Aggarwal Date: Thu, 13 Aug 2026 01:27:58 -0600 Subject: [PATCH 1/2] fix(ramps): unblock neobank-demo lint for #9853 Add #9853 changelog PR links, oxfmt formatting, and eslint fixes so the neobank-demo PR can pass lint and publish previews. Co-authored-by: Cursor --- packages/kyc-controller/CHANGELOG.md | 10 +-- packages/profile-sync-controller/CHANGELOG.md | 4 +- packages/ramps-controller/CHANGELOG.md | 18 ++--- .../src/NeoBankService.test.ts | 26 ++++--- .../ramps-controller/src/NeoBankService.ts | 23 ++++-- .../src/RampsController.test.ts | 4 +- .../ramps-controller/src/RampsController.ts | 77 ++++++++++--------- .../controller-integration.ts | 25 +++--- .../src/autoramp-syncing/format-utils.test.ts | 5 +- .../src/autoramp-syncing/format-utils.ts | 20 ++--- .../src/autoramp-syncing/sync-utils.test.ts | 4 +- .../src/autoramp-syncing/sync-utils.ts | 3 +- .../ramps-controller/src/autorampAccount.ts | 15 +++- packages/ramps-controller/src/index.ts | 1 - .../transaction-pay-controller/CHANGELOG.md | 14 ++-- .../src/utils/chomp.test.ts | 9 +-- 16 files changed, 134 insertions(+), 124 deletions(-) diff --git a/packages/kyc-controller/CHANGELOG.md b/packages/kyc-controller/CHANGELOG.md index c661ce10e1..0c857a9d1a 100644 --- a/packages/kyc-controller/CHANGELOG.md +++ b/packages/kyc-controller/CHANGELOG.md @@ -9,10 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `KycController.getCustomerIdentity()` method and the `KycController:getCustomerIdentity` messenger action (plus the exported `KycControllerGetCustomerIdentityAction` and `KycCustomerIdentity` types). Returns the vendor-scoped `{ vendor, id }` for the currently authenticated customer, or `null` before authentication and after `reset()`. Lets consumers (e.g. ramps autoramp creation) attach the vendor customer id to downstream calls without reading the full KYC state, which also holds session/access tokens. The id is session-scoped and never persisted. -- Add Iron (Money/VBA) KYC path to `@metamask/kyc-controller`: `vendor: 'iron'` skips MoonPay Check/Auth frames; `KycService` clients for `/vendors/iron/*`, `POST /consents`, and `GET /kyc/status`; `refreshKycStatus` + `statusChanged` for Money toast state ([#9852](https://github.com/MetaMask/core/pull/9852)) -- Initial release of the `@metamask/kyc-controller` package for managing KYC / identity verification state across MetaMask clients ([#9781](https://github.com/MetaMask/core/pull/9781)) -- Add `KycController` and `KycService` for managing KYC / identity verification state across MetaMask clients ([#9615](https://github.com/MetaMask/core/pull/9615)) +- Add `KycController.getCustomerIdentity()` method and the `KycController:getCustomerIdentity` messenger action (plus the exported `KycControllerGetCustomerIdentityAction` and `KycCustomerIdentity` types). Returns the vendor-scoped `{ vendor, id }` for the currently authenticated customer, or `null` before authentication and after `reset()`. Lets consumers (e.g. ramps autoramp creation) attach the vendor customer id to downstream calls without reading the full KYC state, which also holds session/access tokens. The id is session-scoped and never persisted. ([#9853](https://github.com/MetaMask/core/pull/9853)) +- Add Iron (Money/VBA) KYC path to `@metamask/kyc-controller`: `vendor: 'iron'` skips MoonPay Check/Auth frames; `KycService` clients for `/vendors/iron/*`, `POST /consents`, and `GET /kyc/status`; `refreshKycStatus` + `statusChanged` for Money toast state ([#9852](https://github.com/MetaMask/core/pull/9852), [#9853](https://github.com/MetaMask/core/pull/9853)) +- Initial release of the `@metamask/kyc-controller` package for managing KYC / identity verification state across MetaMask clients ([#9781](https://github.com/MetaMask/core/pull/9781), [#9853](https://github.com/MetaMask/core/pull/9853)) +- Add `KycController` and `KycService` for managing KYC / identity verification state across MetaMask clients ([#9615](https://github.com/MetaMask/core/pull/9615), [#9853](https://github.com/MetaMask/core/pull/9853)) - `KycController` (`BaseController`) owns the flow state machine, the Check/Auth frame message protocol, X25519 credential decryption, and SumSub orchestration via an injected `KycSumSubLauncher` adapter. - `KycService` extends `BaseDataService` and performs the Universal KYC (UKYC) HTTP calls via an injected `fetch`, sourcing the auth bearer token and geolocation through the messenger. - Exposes a vendor-neutral, per-product surface (`ramps`, `card`) plus reselect selectors. @@ -23,6 +23,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed -- Move Money Account wallet registration to `@metamask/ramps-controller`: removes `KycController.registerMoneyAccountWallet`, the `KycService` wallet-registration methods (`getMoonpayCustomerId`, `getWalletRegistrationStatus`, `registerSelfHostedWallet`), the `neobankBaseUrl` service option, and the wallet registration exports (`WalletRegistrationError`, `SelfHostedRegistration`, `MoneyAccountWalletRegistrationResult`, and related types). Wallet ownership signing is a Money Movement (neobank-proxy) concern, so it now lives on `RampsController` / `NeoBankService`. +- Move Money Account wallet registration to `@metamask/ramps-controller`: removes `KycController.registerMoneyAccountWallet`, the `KycService` wallet-registration methods (`getMoonpayCustomerId`, `getWalletRegistrationStatus`, `registerSelfHostedWallet`), the `neobankBaseUrl` service option, and the wallet registration exports (`WalletRegistrationError`, `SelfHostedRegistration`, `MoneyAccountWalletRegistrationResult`, and related types). Wallet ownership signing is a Money Movement (neobank-proxy) concern, so it now lives on `RampsController` / `NeoBankService`. ([#9853](https://github.com/MetaMask/core/pull/9853)) [Unreleased]: https://github.com/MetaMask/core/ diff --git a/packages/profile-sync-controller/CHANGELOG.md b/packages/profile-sync-controller/CHANGELOG.md index 9b27e13124..88d0c936d4 100644 --- a/packages/profile-sync-controller/CHANGELOG.md +++ b/packages/profile-sync-controller/CHANGELOG.md @@ -9,8 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Resolve HD entropy source IDs from `KeyringController` instead of the message-signing snap (`getBearerToken` primary ID, `performSignIn` SRP enumeration) ([#9794](https://github.com/MetaMask/core/pull/9794)) -- Bump `@metamask/keyring-controller` from `^27.1.0` to `^27.1.1` ([#9791](https://github.com/MetaMask/core/pull/9791)) +- Resolve HD entropy source IDs from `KeyringController` instead of the message-signing snap (`getBearerToken` primary ID, `performSignIn` SRP enumeration) ([#9794](https://github.com/MetaMask/core/pull/9794), [#9853](https://github.com/MetaMask/core/pull/9853)) +- Bump `@metamask/keyring-controller` from `^27.1.0` to `^27.1.1` ([#9791](https://github.com/MetaMask/core/pull/9791), [#9853](https://github.com/MetaMask/core/pull/9853)) ## [29.0.0] diff --git a/packages/ramps-controller/CHANGELOG.md b/packages/ramps-controller/CHANGELOG.md index 324e864aa1..f6fc06692c 100644 --- a/packages/ramps-controller/CHANGELOG.md +++ b/packages/ramps-controller/CHANGELOG.md @@ -9,18 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `RampsController.createAutoramp(request, options?)` method and the `RampsController:createAutoramp` messenger action (plus the exported `RampsControllerCreateAutorampAction` and `CreateAutorampRequest` types). It resolves the MoonPay `customer_id` from the KYC controller via the new `KycController:getCustomerIdentity` action, injects it into the request (overwriting any caller-supplied `customer_id`), forwards the body to `NeoBankService:createAutoramp`, and applies the returned snapshot to local state. Throws when no verified KYC identity is available. -- Add the exported `RAMPS_CONTROLLER_REQUIRED_CONTROLLER_ACTIONS` constant listing the other-controller actions (`KycController:getCustomerIdentity`) that hosts must delegate to the `RampsController` messenger to enable autoramp creation. -- Add NeoBankService Pix / autoramp quote client methods and messenger actions, targeting the neobank-proxy `/neobank` prefix on the Ramp API host: `registerPixAddress`, `getAutorampQuote`, `createAutoramp`, `getAutorampQuoteForAutoramp`, `attachAutorampQuote`, and `getCustomerByExternalId`. Pix/quote helpers return parsed proxy JSON; `createAutoramp` maps autoramp-shaped responses via `mapNeoBankAutorampToRemoteSnapshot` (same as `getAutoramp`). Optional `Idempotency-Key` is supported on mutating calls. -- Export `TERMINAL_ORDER_STATUSES` and `isTerminalOrderStatus()` so consuming clients can share the controller's terminal order status set instead of maintaining duplicate copies. ([#9679](https://github.com/MetaMask/core/pull/9679)) -- Add `RampsController.registerMoneyAccountWallet({ address })` method and the `RampsController:registerMoneyAccountWallet` messenger action (moved from `@metamask/kyc-controller`). Resolves the MoonPay Iron customer id (KYC session identity when available, otherwise the neobank-proxy external-id lookup), signs the Monad ownership message via `KeyringController:signPersonalMessage`, and registers the self-hosted wallet through the neobank-proxy — including `409` disambiguation, transient-failure reconciliation, and UTC date rollover re-signing ([#9850](https://github.com/MetaMask/core/pull/9850), [#9847](https://github.com/MetaMask/core/pull/9847)) -- Add `NeoBankService.getMoonpayCustomerId`, `NeoBankService.getWalletRegistrationStatus`, and `NeoBankService.registerSelfHostedWallet` methods and messenger actions, targeting the transparent neobank routes (`GET /neobank/customers/{external_id}/external`, `GET /neobank/addresses/crypto/{customer_id}`, `POST /neobank/addresses/crypto/selfhosted`) with client-side Monad filtering, `Idempotency-Key` support, and upstream error bodies mirrored 1:1. -- Export the wallet registration types (`SelfHostedRegistration`, `RegistrationStatus`, `RegistrationOutcome`, `WalletRegistrationError`, `WalletRegistrationErrorKind`, `MoneyAccountWalletRegistrationResult`) and `buildOwnershipMessage` (moved from `@metamask/kyc-controller`). +- Add `RampsController.createAutoramp(request, options?)` method and the `RampsController:createAutoramp` messenger action (plus the exported `RampsControllerCreateAutorampAction` and `CreateAutorampRequest` types). It resolves the MoonPay `customer_id` from the KYC controller via the new `KycController:getCustomerIdentity` action, injects it into the request (overwriting any caller-supplied `customer_id`), forwards the body to `NeoBankService:createAutoramp`, and applies the returned snapshot to local state. Throws when no verified KYC identity is available. ([#9853](https://github.com/MetaMask/core/pull/9853)) +- Add the exported `RAMPS_CONTROLLER_REQUIRED_CONTROLLER_ACTIONS` constant listing the other-controller actions (`KycController:getCustomerIdentity`) that hosts must delegate to the `RampsController` messenger to enable autoramp creation. ([#9853](https://github.com/MetaMask/core/pull/9853)) +- Add NeoBankService Pix / autoramp quote client methods and messenger actions, targeting the neobank-proxy `/neobank` prefix on the Ramp API host: `registerPixAddress`, `getAutorampQuote`, `createAutoramp`, `getAutorampQuoteForAutoramp`, `attachAutorampQuote`, and `getCustomerByExternalId`. Pix/quote helpers return parsed proxy JSON; `createAutoramp` maps autoramp-shaped responses via `mapNeoBankAutorampToRemoteSnapshot` (same as `getAutoramp`). Optional `Idempotency-Key` is supported on mutating calls. ([#9853](https://github.com/MetaMask/core/pull/9853)) +- Export `TERMINAL_ORDER_STATUSES` and `isTerminalOrderStatus()` so consuming clients can share the controller's terminal order status set instead of maintaining duplicate copies. ([#9679](https://github.com/MetaMask/core/pull/9679), [#9853](https://github.com/MetaMask/core/pull/9853)) +- Add `RampsController.registerMoneyAccountWallet({ address })` method and the `RampsController:registerMoneyAccountWallet` messenger action (moved from `@metamask/kyc-controller`). Resolves the MoonPay Iron customer id (KYC session identity when available, otherwise the neobank-proxy external-id lookup), signs the Monad ownership message via `KeyringController:signPersonalMessage`, and registers the self-hosted wallet through the neobank-proxy — including `409` disambiguation, transient-failure reconciliation, and UTC date rollover re-signing ([#9850](https://github.com/MetaMask/core/pull/9850), [#9847](https://github.com/MetaMask/core/pull/9847), [#9853](https://github.com/MetaMask/core/pull/9853)) +- Add `NeoBankService.getMoonpayCustomerId`, `NeoBankService.getWalletRegistrationStatus`, and `NeoBankService.registerSelfHostedWallet` methods and messenger actions, targeting the transparent neobank routes (`GET /neobank/customers/{external_id}/external`, `GET /neobank/addresses/crypto/{customer_id}`, `POST /neobank/addresses/crypto/selfhosted`) with client-side Monad filtering, `Idempotency-Key` support, and upstream error bodies mirrored 1:1. ([#9853](https://github.com/MetaMask/core/pull/9853)) +- Export the wallet registration types (`SelfHostedRegistration`, `RegistrationStatus`, `RegistrationOutcome`, `WalletRegistrationError`, `WalletRegistrationErrorKind`, `MoneyAccountWalletRegistrationResult`) and `buildOwnershipMessage` (moved from `@metamask/kyc-controller`). ([#9853](https://github.com/MetaMask/core/pull/9853)) ### Changed -- Resolve the vendor customer identity for autoramps via a locally declared `KycController:getCustomerIdentity` messenger action type instead of a package dependency on `@metamask/kyc-controller`, keeping the ramps↔kyc packages decoupled in the monorepo build. -- Point `NeoBankService.getAutoramp` at `GET /neobank/autoramps/{id}` (neobank-proxy global `/neobank` prefix) instead of `/api/v2/autoramps/{id}`, so Core matches the proxy that ships. +- Resolve the vendor customer identity for autoramps via a locally declared `KycController:getCustomerIdentity` messenger action type instead of a package dependency on `@metamask/kyc-controller`, keeping the ramps↔kyc packages decoupled in the monorepo build. ([#9853](https://github.com/MetaMask/core/pull/9853)) +- Point `NeoBankService.getAutoramp` at `GET /neobank/autoramps/{id}` (neobank-proxy global `/neobank` prefix) instead of `/api/v2/autoramps/{id}`, so Core matches the proxy that ships. ([#9853](https://github.com/MetaMask/core/pull/9853)) ## [20.0.0] diff --git a/packages/ramps-controller/src/NeoBankService.test.ts b/packages/ramps-controller/src/NeoBankService.test.ts index e30812e346..94cc82f2f0 100644 --- a/packages/ramps-controller/src/NeoBankService.test.ts +++ b/packages/ramps-controller/src/NeoBankService.test.ts @@ -1,4 +1,6 @@ -import nock from 'nock'; +import { Messenger, MOCK_ANY_NAMESPACE } from '@metamask/messenger'; +import type { MockAnyNamespace } from '@metamask/messenger'; +import nock, { cleanAll } from 'nock'; import { mapNeoBankAutorampToRemoteSnapshot, @@ -6,8 +8,6 @@ import { } from './NeoBankService.js'; import type { NeoBankServiceMessenger } from './NeoBankService.js'; import { RampsEnvironment } from './RampsService.js'; -import { Messenger, MOCK_ANY_NAMESPACE } from '@metamask/messenger'; -import type { MockAnyNamespace } from '@metamask/messenger'; const STAGING_BASE = 'https://on-ramp.uat-api.cx.metamask.io'; @@ -82,7 +82,7 @@ function createService(options?: { describe('NeoBankService', () => { afterEach(() => { - nock.cleanAll(); + cleanAll(); }); describe('mapNeoBankAutorampToRemoteSnapshot', () => { @@ -132,7 +132,7 @@ describe('NeoBankService', () => { }); describe('getAutoramp', () => { - it('GETs /neobank/autoramps/{id} with bearer auth', async () => { + it('gets /neobank/autoramps/{id} with bearer auth', async () => { const scope = nock(STAGING_BASE) .get(/\/neobank\/autoramps\/ar-1/u) .matchHeader('Authorization', 'Bearer test-token') @@ -156,7 +156,9 @@ describe('NeoBankService', () => { }); it('throws HttpError when the proxy returns a non-2xx status', async () => { - nock(STAGING_BASE).get(/\/neobank\/autoramps\/missing/u).reply(404); + nock(STAGING_BASE) + .get(/\/neobank\/autoramps\/missing/u) + .reply(404); const service = createService(); await expect(service.getAutoramp('missing')).rejects.toThrow( @@ -177,7 +179,7 @@ describe('NeoBankService', () => { }); describe('registerPixAddress', () => { - it('POSTs /neobank/addresses/pix with JSON body and bearer auth', async () => { + it('posts /neobank/addresses/pix with JSON body and bearer auth', async () => { const body = { type: 'Pix', pix_key: 'user@example.com', @@ -216,7 +218,7 @@ describe('NeoBankService', () => { }); describe('getAutorampQuote', () => { - it('GETs /neobank/autoramps/quote with query params', async () => { + it('gets /neobank/autoramps/quote with query params', async () => { const scope = nock(STAGING_BASE) .get('/neobank/autoramps/quote') .query((query) => { @@ -243,7 +245,7 @@ describe('NeoBankService', () => { }); describe('createAutoramp', () => { - it('POSTs /neobank/autoramps and maps the Autoramp response', async () => { + it('posts /neobank/autoramps and maps the Autoramp response', async () => { const body = { signed_quote: 'sig', customer_id: 'cust-1', @@ -309,7 +311,7 @@ describe('NeoBankService', () => { }); describe('getAutorampQuoteForAutoramp', () => { - it('GETs /neobank/autoramps/{id}/quote with query params', async () => { + it('gets /neobank/autoramps/{id}/quote with query params', async () => { const scope = nock(STAGING_BASE) .get('/neobank/autoramps/ar-1/quote') .query((query) => { @@ -329,7 +331,7 @@ describe('NeoBankService', () => { }); describe('attachAutorampQuote', () => { - it('POSTs /neobank/autoramps/{id}/quotes with JSON body', async () => { + it('posts /neobank/autoramps/{id}/quotes with JSON body', async () => { const body = { signed_quote: 'attach-sig' }; const scope = nock(STAGING_BASE) @@ -348,7 +350,7 @@ describe('NeoBankService', () => { }); describe('getCustomerByExternalId', () => { - it('GETs /neobank/customers/{external_id}/external', async () => { + it('gets /neobank/customers/{external_id}/external', async () => { const scope = nock(STAGING_BASE) .get('/neobank/customers/ext-1/external') .query(true) diff --git a/packages/ramps-controller/src/NeoBankService.ts b/packages/ramps-controller/src/NeoBankService.ts index 832a7ee74a..cbc358c03f 100644 --- a/packages/ramps-controller/src/NeoBankService.ts +++ b/packages/ramps-controller/src/NeoBankService.ts @@ -32,16 +32,20 @@ export const serviceName = 'NeoBankService'; */ export type NeoBankAutorampResponse = { id: string; + // eslint-disable-next-line @typescript-eslint/naming-convention -- MoonPay API field customer_id: string; status: string; /** * Destination wallet when present on the proxy response. * Field name may evolve with the Ramp API contract. */ + // eslint-disable-next-line @typescript-eslint/naming-convention -- MoonPay API field wallet_address?: string; + // eslint-disable-next-line @typescript-eslint/naming-convention -- MoonPay API field recipient_account?: { address?: string; }; + // eslint-disable-next-line @typescript-eslint/naming-convention -- MoonPay API field deposit_rails?: unknown[]; }; @@ -162,7 +166,8 @@ export function mapNeoBankAutorampToRemoteSnapshot( response: NeoBankAutorampResponse, ): AutorampRemoteSnapshot { const depositRails = response.deposit_rails; - const hasDepositRails = Array.isArray(depositRails) && depositRails.length > 0; + const hasDepositRails = + Array.isArray(depositRails) && depositRails.length > 0; const depositRailsSummary: AutorampDepositRailsSummary | undefined = hasDepositRails || response.status === 'Approved' ? { @@ -291,10 +296,10 @@ export class NeoBankService { return url; } - async #getJson( + async #getJson( path: string, query?: NeoBankQueryParams, - ): Promise { + ): Promise { const url = this.#buildUrl(path, query); return this.#policy.execute(async () => { const headers = await this.#getRequestHeaders(); @@ -305,15 +310,15 @@ export class NeoBankService { `Fetching '${url.toString()}' failed with status '${fetchResponse.status}'`, ); } - return fetchResponse.json() as Promise; + return fetchResponse.json() as Promise; }); } - async #postJson( + async #postJson( path: string, body: Record, options: NeoBankRequestOptions, - ): Promise { + ): Promise { const url = this.#buildUrl(path); return this.#policy.execute(async () => { const headers = await this.#getRequestHeaders(options); @@ -329,11 +334,13 @@ export class NeoBankService { `Fetching '${url.toString()}' failed with status '${fetchResponse.status}'`, ); } - return fetchResponse.json() as Promise; + return fetchResponse.json() as Promise; }); } - #mapAutorampResponse(response: NeoBankAutorampResponse): AutorampRemoteSnapshot { + #mapAutorampResponse( + response: NeoBankAutorampResponse, + ): AutorampRemoteSnapshot { if (!response || typeof response !== 'object' || !response.id) { throw new Error('Malformed response received from neo-bank autoramp API'); } diff --git a/packages/ramps-controller/src/RampsController.test.ts b/packages/ramps-controller/src/RampsController.test.ts index 9284382622..845ae492af 100644 --- a/packages/ramps-controller/src/RampsController.test.ts +++ b/packages/ramps-controller/src/RampsController.test.ts @@ -11,8 +11,8 @@ import type { Json } from '@metamask/utils'; import * as fs from 'fs'; import * as path from 'path'; -import { MONEY_HEADLESS_ALL_PROVIDERS_FLAG_KEY } from './featureFlags.js'; import { AutorampStatus } from './autorampAccount.js'; +import { MONEY_HEADLESS_ALL_PROVIDERS_FLAG_KEY } from './featureFlags.js'; import type { RampsControllerMessenger, RampsControllerState, @@ -50,7 +50,6 @@ import type { } from './RampsService.js'; import { RampsOrderStatus } from './RampsService.js'; import { RequestStatus } from './RequestCache.js'; -import { WalletRegistrationError } from './wallet-registration-service.js'; import type { TransakAccessToken, TransakUserDetails, @@ -67,6 +66,7 @@ import type { TransakOrderPaymentMethod, PatchUserRequestBody, } from './TransakService.js'; +import { WalletRegistrationError } from './wallet-registration-service.js'; /** * The default redirect ("fake callback") URL a staging `RampsService` returns. diff --git a/packages/ramps-controller/src/RampsController.ts b/packages/ramps-controller/src/RampsController.ts index 40e8d1b37d..036156cbb1 100644 --- a/packages/ramps-controller/src/RampsController.ts +++ b/packages/ramps-controller/src/RampsController.ts @@ -6,16 +6,22 @@ import type { import { BaseController } from '@metamask/base-controller'; import { BrokenCircuitError } from '@metamask/controller-utils'; import type { Messenger } from '@metamask/messenger'; +import type { AuthenticationController } from '@metamask/profile-sync-controller'; +import type { UserStorageController } from '@metamask/profile-sync-controller'; import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller'; import type { Json } from '@metamask/utils'; import type { Draft } from 'immer'; import { - getHeadlessProviderAllowlist, - isHeadlessAllProvidersEnabled, - normalizeHeadlessProviderId, -} from './featureFlags.js'; - + deleteAutorampInRemoteStorage, + syncAutorampsWithUserStorage as syncAutorampsWithUserStorageInternal, + updateAutorampInRemoteStorage, +} from './autoramp-syncing/index.js'; +import type { SyncAutorampsWithUserStorageConfig } from './autoramp-syncing/index.js'; +import type { + AutorampSyncingController, + AutorampSyncingOptions, +} from './autoramp-syncing/types.js'; import type { AutorampAccount, AutorampRemoteSnapshot, @@ -27,11 +33,10 @@ import { markAutorampNotified, } from './autorampAccount.js'; import { - deleteAutorampInRemoteStorage, - syncAutorampsWithUserStorage as syncAutorampsWithUserStorageInternal, - updateAutorampInRemoteStorage, -} from './autoramp-syncing/index.js'; -import type { SyncAutorampsWithUserStorageConfig } from './autoramp-syncing/index.js'; + getHeadlessProviderAllowlist, + isHeadlessAllProvidersEnabled, + normalizeHeadlessProviderId, +} from './featureFlags.js'; import type { NeoBankServiceCreateAutorampAction, NeoBankServiceGetAutorampAction, @@ -40,25 +45,11 @@ import type { NeoBankServiceRegisterSelfHostedWalletAction, } from './NeoBankService-method-action-types.js'; import type { NeoBankServiceActions } from './NeoBankService.js'; -import { buildOwnershipMessage } from './ownership-message.js'; -import { - createInitialState as createInitialWalletRegistrationState, - transition as transitionWalletRegistration, -} from './wallet-registration-machine.js'; -import { - createIdempotencyKey, - WalletRegistrationError, -} from './wallet-registration-service.js'; -import type { - RegistrationStatus, - SelfHostedRegistration, -} from './wallet-registration-service.js'; -import type { AuthenticationController } from '@metamask/profile-sync-controller'; -import type { UserStorageController } from '@metamask/profile-sync-controller'; import { PENDING_ORDER_STATUSES, TERMINAL_ORDER_STATUSES, } from './orderStatus.js'; +import { buildOwnershipMessage } from './ownership-message.js'; import { getProvidersServingAsset, providerServesAsset, @@ -156,6 +147,18 @@ import type { TransakOrder, } from './TransakService.js'; import type { TransakServiceActions } from './TransakService.js'; +import { + createInitialState as createInitialWalletRegistrationState, + transition as transitionWalletRegistration, +} from './wallet-registration-machine.js'; +import { + createIdempotencyKey, + WalletRegistrationError, +} from './wallet-registration-service.js'; +import type { + RegistrationStatus, + SelfHostedRegistration, +} from './wallet-registration-service.js'; // === GENERAL === @@ -2602,6 +2605,8 @@ export class RampsController extends BaseController< /** * Whether a full autoramp User Storage sync is currently running. + * + * @returns True when a full autoramp sync is in progress. */ get isAutorampSyncingInProgress(): boolean { return this.#isAutorampSyncingInProgress; @@ -2651,10 +2656,10 @@ export class RampsController extends BaseController< ); } - #getAutorampSyncingOptions() { + #getAutorampSyncingOptions(): AutorampSyncingOptions { return { - getRampsControllerInstance: () => this, - getMessenger: () => this.messenger, + getRampsControllerInstance: (): AutorampSyncingController => this, + getMessenger: (): RampsControllerMessenger => this.messenger, }; } @@ -2677,8 +2682,9 @@ export class RampsController extends BaseController< }, ): AutorampAccount { const account = - 'updatedAt' in accountOrInput && 'lastSeenStatus' in accountOrInput - ? (accountOrInput as AutorampAccount) + typeof (accountOrInput as AutorampAccount).updatedAt === 'number' && + (accountOrInput as AutorampAccount).lastSeenStatus !== undefined + ? accountOrInput : createAutorampAccount(accountOrInput); this.update((state) => { @@ -3034,9 +3040,7 @@ export class RampsController extends BaseController< * @param remote - Remote autoramp snapshot. * @returns The updated local account. */ - applyAutorampStatusFromPush( - remote: AutorampRemoteSnapshot, - ): AutorampAccount { + applyAutorampStatusFromPush(remote: AutorampRemoteSnapshot): AutorampAccount { return this.#applyAutorampRemoteSnapshot(remote); } @@ -3090,9 +3094,12 @@ export class RampsController extends BaseController< ); } - #applyAutorampRemoteSnapshot(remote: AutorampRemoteSnapshot): AutorampAccount { + #applyAutorampRemoteSnapshot( + remote: AutorampRemoteSnapshot, + ): AutorampAccount { const local = - this.state.autoramps.find((autoramp) => autoramp.id === remote.id) ?? null; + this.state.autoramps.find((autoramp) => autoramp.id === remote.id) ?? + null; const result = applyAutorampRemoteStatus(local, remote); this.update((state) => { diff --git a/packages/ramps-controller/src/autoramp-syncing/controller-integration.ts b/packages/ramps-controller/src/autoramp-syncing/controller-integration.ts index 3834b2c5cf..28e99d31a8 100644 --- a/packages/ramps-controller/src/autoramp-syncing/controller-integration.ts +++ b/packages/ramps-controller/src/autoramp-syncing/controller-integration.ts @@ -1,3 +1,4 @@ +import type { AutorampAccount } from '../autorampAccount.js'; import { USER_STORAGE_RAMPS_AUTORAMPS_FEATURE, USER_STORAGE_VERSION, @@ -157,7 +158,7 @@ async function saveAutorampsToUserStorage( const { getMessenger, trace } = options; const { onAutorampSyncErroneousSituation } = config; - const save = async () => { + const save = async (): Promise => { const storageEntries: [string, string][] = []; for (const account of accounts) { const key = createAutorampStorageKey(account); @@ -217,13 +218,15 @@ export async function syncAutorampsWithUserStorage( controller.setIsAutorampSyncingInProgress(true); try { - const validRemoteAccounts = (await getRemoteAutoramps(options, config)).filter( + const validRemoteAccounts = ( + await getRemoteAutoramps(options, config) + ).filter( (account: SyncAutorampAccount) => Boolean(account.deletedAt) || isSyncableAutoramp(account), ); - const performSync = async () => { - const getLocalAccounts = () => + const performSync = async (): Promise => { + const getLocalAccounts = (): AutorampAccount[] => controller.state.autoramps.filter(isSyncableAutoramp); const pendingDeleteKeysBeforeApply = new Set( @@ -332,9 +335,8 @@ export async function syncAutorampsWithUserStorage( { name: TraceName.AutorampSyncFull, data: { - localAutorampCount: controller.state.autoramps.filter( - isSyncableAutoramp, - ).length, + localAutorampCount: + controller.state.autoramps.filter(isSyncableAutoramp).length, remoteAutorampCount: validRemoteAccounts.length, }, }, @@ -368,11 +370,8 @@ export async function updateAutorampInRemoteStorage( ): Promise { const { trace } = options; - const update = async () => { - if ( - !canPerformAutorampSyncing(options) || - !isSyncableAutoramp(account) - ) { + const update = async (): Promise => { + if (!canPerformAutorampSyncing(options) || !isSyncableAutoramp(account)) { return; } await saveAutorampsToUserStorage( @@ -403,7 +402,7 @@ export async function deleteAutorampInRemoteStorage( ): Promise { const { trace } = options; - const remove = async () => { + const remove = async (): Promise => { if (!canPerformAutorampSyncing(options) || !account.id) { return; } diff --git a/packages/ramps-controller/src/autoramp-syncing/format-utils.test.ts b/packages/ramps-controller/src/autoramp-syncing/format-utils.test.ts index 4b00ece435..540cf7e88c 100644 --- a/packages/ramps-controller/src/autoramp-syncing/format-utils.test.ts +++ b/packages/ramps-controller/src/autoramp-syncing/format-utils.test.ts @@ -1,8 +1,5 @@ import { AutorampStatus, createAutorampAccount } from '../autorampAccount.js'; -import { - USER_STORAGE_VERSION, - USER_STORAGE_VERSION_KEY, -} from './constants.js'; +import { USER_STORAGE_VERSION, USER_STORAGE_VERSION_KEY } from './constants.js'; import { areAutorampsEqual, createAutorampStorageKey, diff --git a/packages/ramps-controller/src/autoramp-syncing/format-utils.ts b/packages/ramps-controller/src/autoramp-syncing/format-utils.ts index 81669481ac..13af41c924 100644 --- a/packages/ramps-controller/src/autoramp-syncing/format-utils.ts +++ b/packages/ramps-controller/src/autoramp-syncing/format-utils.ts @@ -1,13 +1,7 @@ import type { AutorampAccount } from '../autorampAccount.js'; import { normalizeAutorampStatus } from '../autorampAccount.js'; -import { - USER_STORAGE_VERSION, - USER_STORAGE_VERSION_KEY, -} from './constants.js'; -import type { - SyncAutorampAccount, - UserStorageAutorampEntry, -} from './types.js'; +import { USER_STORAGE_VERSION, USER_STORAGE_VERSION_KEY } from './constants.js'; +import type { SyncAutorampAccount, UserStorageAutorampEntry } from './types.js'; /** * Storage key for an autoramp entry (MoonPay autoramp id). @@ -32,11 +26,11 @@ export function isSyncableAutoramp( ): account is AutorampAccount { return Boolean( account && - typeof account.id === 'string' && - account.id.length > 0 && - typeof account.customerId === 'string' && - typeof account.walletAddress === 'string' && - account.status, + typeof account.id === 'string' && + account.id.length > 0 && + typeof account.customerId === 'string' && + typeof account.walletAddress === 'string' && + account.status, ); } diff --git a/packages/ramps-controller/src/autoramp-syncing/sync-utils.test.ts b/packages/ramps-controller/src/autoramp-syncing/sync-utils.test.ts index c619447ad2..1b7c83dc45 100644 --- a/packages/ramps-controller/src/autoramp-syncing/sync-utils.test.ts +++ b/packages/ramps-controller/src/autoramp-syncing/sync-utils.test.ts @@ -21,7 +21,7 @@ describe('autoramp-syncing/sync-utils', () => { setIsApplyingAutorampSyncChanges: jest.fn(), addAutoramp: jest.fn(), removeAutoramp: jest.fn(), - getPendingRemoteAutorampDeletes: () => [], + getPendingRemoteAutorampDeletes: (): [] => [], acknowledgePendingRemoteAutorampDeletes: jest.fn(), }), }; @@ -49,7 +49,7 @@ describe('autoramp-syncing/sync-utils', () => { setIsApplyingAutorampSyncChanges: jest.fn(), addAutoramp: jest.fn(), removeAutoramp: jest.fn(), - getPendingRemoteAutorampDeletes: () => [], + getPendingRemoteAutorampDeletes: (): [] => [], acknowledgePendingRemoteAutorampDeletes: jest.fn(), }), } as AutorampSyncingOptions; diff --git a/packages/ramps-controller/src/autoramp-syncing/sync-utils.ts b/packages/ramps-controller/src/autoramp-syncing/sync-utils.ts index 735bc6f65b..b95a51015f 100644 --- a/packages/ramps-controller/src/autoramp-syncing/sync-utils.ts +++ b/packages/ramps-controller/src/autoramp-syncing/sync-utils.ts @@ -25,7 +25,8 @@ export function canPerformAutorampSyncing( const isBackupAndSyncEnabled = Boolean( userStorageState.isBackupAndSyncEnabled, ); - const isRampsSyncingEnabled = userStorageState.isRampsSyncingEnabled ?? true; + const isRampsSyncingEnabled = + userStorageState.isRampsSyncingEnabled ?? true; const isAuthEnabled = getMessenger().call( 'AuthenticationController:isSignedIn', ); diff --git a/packages/ramps-controller/src/autorampAccount.ts b/packages/ramps-controller/src/autorampAccount.ts index b812499a40..03fe6e939a 100644 --- a/packages/ramps-controller/src/autorampAccount.ts +++ b/packages/ramps-controller/src/autorampAccount.ts @@ -5,6 +5,7 @@ /** * Autoramp lifecycle statuses from MoonPay Enterprise. + * * @see https://dev.enterprise.moonpay.com/autoramp-status */ export enum AutorampStatus { @@ -136,6 +137,12 @@ export function normalizeAutorampStatus( * Build a new local autoramp account from create/response fields. * * @param input - Required identity + status fields. + * @param input.id - MoonPay autoramp id. + * @param input.customerId - MoonPay customer id. + * @param input.walletAddress - Destination wallet address. + * @param input.status - Optional remote status (defaults to Authorized). + * @param input.depositRailsSummary - Optional non-PII deposit readiness cache. + * @param input.updatedAt - Optional epoch ms timestamp (defaults to now). * @returns A new {@link AutorampAccount}. */ export function createAutorampAccount(input: { @@ -200,8 +207,8 @@ export function applyAutorampRemoteStatus( const account: AutorampAccount = { ...local, id: remote.id, - customerId: remote.customerId || local.customerId, - walletAddress: remote.walletAddress || local.walletAddress, + customerId: remote.customerId ?? local.customerId, + walletAddress: remote.walletAddress ?? local.walletAddress, status: remoteStatus, lastSeenStatus: previousStatus, updatedAt: Date.now(), @@ -223,7 +230,9 @@ export function applyAutorampRemoteStatus( * @param account - Account to update. * @returns Account with `notifiedForStatus` set to current status. */ -export function markAutorampNotified(account: AutorampAccount): AutorampAccount { +export function markAutorampNotified( + account: AutorampAccount, +): AutorampAccount { return { ...account, notifiedForStatus: account.status, diff --git a/packages/ramps-controller/src/index.ts b/packages/ramps-controller/src/index.ts index b897a34331..27f8c1cf3b 100644 --- a/packages/ramps-controller/src/index.ts +++ b/packages/ramps-controller/src/index.ts @@ -234,7 +234,6 @@ export type { NeoBankServiceGetMoonpayCustomerIdAction, NeoBankServiceGetWalletRegistrationStatusAction, NeoBankServiceRegisterSelfHostedWalletAction, - NeoBankServiceMethodActions, } from './NeoBankService-method-action-types.js'; export { NeoBankService, diff --git a/packages/transaction-pay-controller/CHANGELOG.md b/packages/transaction-pay-controller/CHANGELOG.md index ce39a90775..d796a2b02f 100644 --- a/packages/transaction-pay-controller/CHANGELOG.md +++ b/packages/transaction-pay-controller/CHANGELOG.md @@ -9,19 +9,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `TransactionPayController:submitMoneyAccountVaultDeposit` action to vault a completed mUSD payout into the Money Account vault, resolving the deposit amount from the payout transaction hash ([#9849](https://github.com/MetaMask/core/pull/9849)) -- Add `TransactionPayController:submitMoneyAccountVaultWithdraw` action to redeem vmUSD and transfer the resulting mUSD to a given recipient in a single atomic, user-confirmed batch ([#9849](https://github.com/MetaMask/core/pull/9849)) +- Add `TransactionPayController:submitMoneyAccountVaultDeposit` action to vault a completed mUSD payout into the Money Account vault, resolving the deposit amount from the payout transaction hash ([#9849](https://github.com/MetaMask/core/pull/9849), [#9853](https://github.com/MetaMask/core/pull/9853)) +- Add `TransactionPayController:submitMoneyAccountVaultWithdraw` action to redeem vmUSD and transfer the resulting mUSD to a given recipient in a single atomic, user-confirmed batch ([#9849](https://github.com/MetaMask/core/pull/9849), [#9853](https://github.com/MetaMask/core/pull/9853)) ### Changed -- Slim `SubmitMoneyAccountVaultWithdrawRequest` to on-chain fields only (`amountInRaw`, `moneyAccountAddress`, `recipient`, `requestId`); quote / chain / token validation stays outside Core ([#9849](https://github.com/MetaMask/core/pull/9849)) -- Return `{ skipped: true }` from Money Account vault deposit helpers when vaulting is disabled instead of a fake `0x` transaction hash ([#9849](https://github.com/MetaMask/core/pull/9849)) -- Bump `@metamask/transaction-controller` from `^69.5.1` to `^69.5.2` ([#9823](https://github.com/MetaMask/core/pull/9823)) +- Slim `SubmitMoneyAccountVaultWithdrawRequest` to on-chain fields only (`amountInRaw`, `moneyAccountAddress`, `recipient`, `requestId`); quote / chain / token validation stays outside Core ([#9849](https://github.com/MetaMask/core/pull/9849), [#9853](https://github.com/MetaMask/core/pull/9853)) +- Return `{ skipped: true }` from Money Account vault deposit helpers when vaulting is disabled instead of a fake `0x` transaction hash ([#9849](https://github.com/MetaMask/core/pull/9849), [#9853](https://github.com/MetaMask/core/pull/9853)) +- Bump `@metamask/transaction-controller` from `^69.5.1` to `^69.5.2` ([#9823](https://github.com/MetaMask/core/pull/9823), [#9853](https://github.com/MetaMask/core/pull/9853)) ### Fixed -- Persist successful Money Account vault deposit and withdraw results for the controller lifetime so retries / webhook replays do not re-submit or open a second approval ([#9849](https://github.com/MetaMask/core/pull/9849)) -- Match CHOMP vault deposits only when mUSD is transferred to the boring vault with an exact source amount ([#9849](https://github.com/MetaMask/core/pull/9849)) +- Persist successful Money Account vault deposit and withdraw results for the controller lifetime so retries / webhook replays do not re-submit or open a second approval ([#9849](https://github.com/MetaMask/core/pull/9849), [#9853](https://github.com/MetaMask/core/pull/9853)) +- Match CHOMP vault deposits only when mUSD is transferred to the boring vault with an exact source amount ([#9849](https://github.com/MetaMask/core/pull/9849), [#9853](https://github.com/MetaMask/core/pull/9853)) ## [26.3.0] diff --git a/packages/transaction-pay-controller/src/utils/chomp.test.ts b/packages/transaction-pay-controller/src/utils/chomp.test.ts index f940befa94..af4639ce27 100644 --- a/packages/transaction-pay-controller/src/utils/chomp.test.ts +++ b/packages/transaction-pay-controller/src/utils/chomp.test.ts @@ -11,8 +11,7 @@ const MONEY_ACCOUNT_ADDRESS = '0x1111111111111111111111111111111111111111' as Hex; const BORING_VAULT_ADDRESS = '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' as Hex; -const OTHER_RECIPIENT = - '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as Hex; +const OTHER_RECIPIENT = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' as Hex; const CHOMP_TX_HASH = '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' as Hex; const FROM_BLOCK = '0x100' as Hex; @@ -54,11 +53,7 @@ function buildMusdTransferLog({ return { address: MUSD_MONAD_ADDRESS, data, - topics: [ - ERC20_TRANSFER_TOPIC, - MONEY_ACCOUNT_PADDED, - padAddress(to), - ], + topics: [ERC20_TRANSFER_TOPIC, MONEY_ACCOUNT_PADDED, padAddress(to)], transactionHash: txHash, }; } From b7be9b3267d4e0e82466ba6d46ee8b5592cba0e3 Mon Sep 17 00:00:00 2001 From: Amitabh Aggarwal Date: Thu, 13 Aug 2026 01:41:20 -0600 Subject: [PATCH 2/2] fix(transaction-pay): replace restricted jest resolves matchers Use expect(await promise) so eslint jest/no-restricted-matchers passes on Money Account vault deposit/withdraw retry tests. Co-authored-by: Cursor --- .../src/TransactionPayController.test.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/transaction-pay-controller/src/TransactionPayController.test.ts b/packages/transaction-pay-controller/src/TransactionPayController.test.ts index c708945641..e173155c55 100644 --- a/packages/transaction-pay-controller/src/TransactionPayController.test.ts +++ b/packages/transaction-pay-controller/src/TransactionPayController.test.ts @@ -196,9 +196,9 @@ describe('TransactionPayController', () => { controller.submitMoneyAccountVaultDeposit(request), ).rejects.toThrow('vault failed'); - await expect( - controller.submitMoneyAccountVaultDeposit(request), - ).resolves.toStrictEqual({ transactionHash }); + expect( + await controller.submitMoneyAccountVaultDeposit(request), + ).toStrictEqual({ transactionHash }); expect( submitMoneyAccountVaultDepositFromPayoutMock, ).toHaveBeenCalledTimes(2); @@ -211,13 +211,13 @@ describe('TransactionPayController', () => { const controller = createController(); const request = { moneyAccountAddress, transactionHash }; - await expect( - controller.submitMoneyAccountVaultDeposit(request), - ).resolves.toStrictEqual({ skipped: true }); + expect( + await controller.submitMoneyAccountVaultDeposit(request), + ).toStrictEqual({ skipped: true }); - await expect( - controller.submitMoneyAccountVaultDeposit(request), - ).resolves.toStrictEqual({ transactionHash }); + expect( + await controller.submitMoneyAccountVaultDeposit(request), + ).toStrictEqual({ transactionHash }); expect( submitMoneyAccountVaultDepositFromPayoutMock, ).toHaveBeenCalledTimes(2); @@ -308,9 +308,9 @@ describe('TransactionPayController', () => { controller.submitMoneyAccountVaultWithdraw(request), ).rejects.toThrow('batch failed'); - await expect( - controller.submitMoneyAccountVaultWithdraw(request), - ).resolves.toStrictEqual({ batchId: '0x123' }); + expect( + await controller.submitMoneyAccountVaultWithdraw(request), + ).toStrictEqual({ batchId: '0x123' }); expect(submitMoneyAccountVaultWithdrawUtilMock).toHaveBeenCalledTimes(2); }); });