Skip to content

feat(ramps): sync V2 ramps orders via User Storage - #9474

Open
georgeweiler wants to merge 39 commits into
mainfrom
feat/ramps-order-syncing
Open

feat(ramps): sync V2 ramps orders via User Storage#9474
georgeweiler wants to merge 39 commits into
mainfrom
feat/ramps-order-syncing

Conversation

@georgeweiler

@georgeweiler georgeweiler commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Today, buy/sell (ramps) order history lives only on the device where the purchase happened. If you buy crypto on mobile, that order does not show up on Extension (and vice versa).

This PR adds order history sync for V2 ramps orders through Profile Sync / Backup & Sync. Once enabled, orders created on one MetaMask client appear on the other after unlock.

How it fits together

Client Role
Mobile Creates orders during buy/sell; pushes them to User Storage; pulls updates on unlock
Extension Does not run the buy/sell UI today; still pulls synced orders on unlock so Activity / history can show them
Toggle New independent setting: ramps syncing can be on or off without changing account/contact Backup & Sync

Missing toggle values on upgraded wallets are treated as enabled (same default as new installs).

Consumer impact

  • Merge this core PR first (with the @metamask/profile-sync-controller pieces in the same release). Mobile and Extension PRs need the published packages from this work.
  • Breaking for hosts wiring RampsController: the messenger must also delegate User Storage batch get/set actions and AuthenticationController:isSignedIn.

Companion PRs: Mobile and Extension follow once these packages are published.

Implementation notes (for reviewers)

  • Sync logic lives in @metamask/ramps-controller under order-syncing/ (same pattern as account-tree / contact sync — not new UserStorageController feature methods).
  • Each order is stored as its own User Storage entry under the rampsOrders feature.
  • Hosts call RampsController.syncOrdersWithUserStorage() on unlock; addOrder / removeOrder push incrementally when Backup & Sync + ramps syncing are on and the user is signed in.
  • Conflicts use last-write-wins timestamps; deletes are soft (remote tombstones) so a delete on one device can win over a stale copy on another.
  • Sensitive paymentDetails stay local only and are not written to User Storage.
  • Preferred provider is still derived from order history (not synced as a separate field).
  • No order cap in this PR. Portfolio migration is out of scope.

Test plan

  • yarn workspace @metamask/ramps-controller run jest --coverage (100% thresholds)
  • yarn workspace @metamask/ramps-controller run messenger-action-types:check
  • Order-sync unit tests cover merge/tombstone/LWW, mid-sync deletes, coalesced overlapping syncs, corrupt remote entries, and incremental push paths
  • Confirm host messengers (mobile/extension) delegate the new User Storage + auth actions before integrating the published packages

Note

Medium Risk
Large new sync surface touches persisted user data and messenger wiring; conflict/tombstone logic is complex though heavily tested, and PII is explicitly stripped from remote writes.

Overview
Adds cross-device V2 ramps order history through Backup & Sync: orders sync under the User Storage rampsOrders feature with last-write-wins timestamps, soft-delete tombstones, incremental pushes on addOrder/removeOrder, and a host-facing RampsController:syncOrdersWithUserStorage entry point (with coalescing for overlapping syncs). paymentDetails stay local; remote payloads strip them, and unchanged poll/update payloads skip remote writes.

Profile Sync gains an independent rampsSyncing feature flag and persisted isRampsSyncingEnabled (defaults on; absent state treated as enabled). Disabling ramps sync does not turn off main Backup & Sync.

Host integration (breaking): UserStorageControllerState must include isRampsSyncingEnabled when constructing full state; RampsControllerMessenger must newly delegate UserStorageController:getState, performGetStorageAllFeatureEntries, performBatchSetStorage, and AuthenticationController:isSignedIn. Order state uses optional lastUpdatedAt for LWW; getInternalOrderCode / remove-order behavior is tightened for polling and mid-sync races.

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

@georgeweiler
georgeweiler requested review from a team as code owners July 10, 2026 20:43
georgeweiler pushed a commit that referenced this pull request Jul 10, 2026
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread packages/ramps-controller/src/order-syncing/controller-integration.ts Outdated
georgeweiler pushed a commit that referenced this pull request Jul 11, 2026
Co-authored-by: Cursor <cursoragent@cursor.com>
@georgeweiler
georgeweiler force-pushed the feat/ramps-order-syncing branch from 63d4658 to 1e2c65b Compare July 11, 2026 20:12
georgeweiler pushed a commit to MetaMask/metamask-mobile that referenced this pull request Jul 11, 2026
Wire RampsController order syncing into mobile: messenger delegation,
Backup & Sync sub-toggle, identity-effect pull on unlock, and fixtures
for isRampsSyncingEnabled. Depends on MetaMask/core#9474.

Co-authored-by: Cursor <cursoragent@cursor.com>

This comment was marked as resolved.

Comment thread packages/ramps-controller/src/order-syncing/controller-integration.ts Outdated
Comment thread packages/ramps-controller/src/RampsController.ts
Comment thread packages/ramps-controller/src/order-syncing/controller-integration.ts Outdated
Comment thread packages/ramps-controller/src/RampsController.ts Outdated
@georgeweiler

Copy link
Copy Markdown
Contributor Author

@cursoragent review this PR

This comment was marked as resolved.

@georgeweiler

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.5.5-preview-acc8294b8
@metamask-previews/accounts-controller@39.0.5-preview-acc8294b8
@metamask-previews/address-book-controller@7.1.2-preview-acc8294b8
@metamask-previews/ai-controllers@0.8.0-preview-acc8294b8
@metamask-previews/analytics-controller@1.2.1-preview-acc8294b8
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-acc8294b8
@metamask-previews/announcement-controller@8.1.0-preview-acc8294b8
@metamask-previews/app-metadata-controller@2.0.1-preview-acc8294b8
@metamask-previews/approval-controller@9.0.2-preview-acc8294b8
@metamask-previews/assets-controller@11.1.0-preview-acc8294b8
@metamask-previews/assets-controllers@109.4.1-preview-acc8294b8
@metamask-previews/authenticated-user-storage@3.0.1-preview-acc8294b8
@metamask-previews/base-controller@9.1.0-preview-acc8294b8
@metamask-previews/base-data-service@0.1.3-preview-acc8294b8
@metamask-previews/bitcoin-regtest-up@1.0.0-preview-acc8294b8
@metamask-previews/bridge-controller@77.6.0-preview-acc8294b8
@metamask-previews/bridge-status-controller@74.3.0-preview-acc8294b8
@metamask-previews/build-utils@3.0.4-preview-acc8294b8
@metamask-previews/chain-agnostic-permission@1.7.0-preview-acc8294b8
@metamask-previews/chomp-api-service@3.1.0-preview-acc8294b8
@metamask-previews/claims-controller@0.5.3-preview-acc8294b8
@metamask-previews/client-controller@1.0.1-preview-acc8294b8
@metamask-previews/client-utils@1.2.0-preview-acc8294b8
@metamask-previews/compliance-controller@2.1.0-preview-acc8294b8
@metamask-previews/composable-controller@12.0.1-preview-acc8294b8
@metamask-previews/config-registry-controller@0.4.1-preview-acc8294b8
@metamask-previews/connectivity-controller@0.3.0-preview-acc8294b8
@metamask-previews/controller-utils@12.3.0-preview-acc8294b8
@metamask-previews/core-backend@6.5.0-preview-acc8294b8
@metamask-previews/delegation-controller@3.0.2-preview-acc8294b8
@metamask-previews/earn-controller@12.2.2-preview-acc8294b8
@metamask-previews/eip-5792-middleware@3.0.5-preview-acc8294b8
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.1-preview-acc8294b8
@metamask-previews/eip1193-permission-middleware@2.0.1-preview-acc8294b8
@metamask-previews/ens-controller@19.1.5-preview-acc8294b8
@metamask-previews/eth-block-tracker@15.0.1-preview-acc8294b8
@metamask-previews/eth-json-rpc-middleware@23.1.3-preview-acc8294b8
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-acc8294b8
@metamask-previews/foundryup@1.0.1-preview-acc8294b8
@metamask-previews/gas-fee-controller@26.2.4-preview-acc8294b8
@metamask-previews/gator-permissions-controller@4.2.3-preview-acc8294b8
@metamask-previews/geolocation-controller@0.1.3-preview-acc8294b8
@metamask-previews/java-tron-up@1.0.0-preview-acc8294b8
@metamask-previews/json-rpc-engine@10.5.0-preview-acc8294b8
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-acc8294b8
@metamask-previews/keyring-controller@27.1.0-preview-acc8294b8
@metamask-previews/local-node-utils@1.0.0-preview-acc8294b8
@metamask-previews/logging-controller@8.0.2-preview-acc8294b8
@metamask-previews/message-manager@14.1.2-preview-acc8294b8
@metamask-previews/messenger@2.0.0-preview-acc8294b8
@metamask-previews/messenger-cli@0.2.0-preview-acc8294b8
@metamask-previews/money-account-api-data-service@0.2.0-preview-acc8294b8
@metamask-previews/money-account-balance-service@2.3.0-preview-acc8294b8
@metamask-previews/money-account-controller@0.3.3-preview-acc8294b8
@metamask-previews/money-account-upgrade-controller@2.2.1-preview-acc8294b8
@metamask-previews/multichain-account-service@13.0.0-preview-acc8294b8
@metamask-previews/multichain-api-middleware@4.0.1-preview-acc8294b8
@metamask-previews/multichain-network-controller@3.2.1-preview-acc8294b8
@metamask-previews/multichain-transactions-controller@7.1.1-preview-acc8294b8
@metamask-previews/name-controller@9.1.2-preview-acc8294b8
@metamask-previews/network-connection-banner-controller@0.1.0-preview-acc8294b8
@metamask-previews/network-controller@34.0.0-preview-acc8294b8
@metamask-previews/network-enablement-controller@5.6.0-preview-acc8294b8
@metamask-previews/notification-services-controller@26.0.0-preview-acc8294b8
@metamask-previews/passkey-controller@3.0.0-preview-acc8294b8
@metamask-previews/permission-controller@13.1.1-preview-acc8294b8
@metamask-previews/permission-log-controller@5.1.0-preview-acc8294b8
@metamask-previews/perps-controller@9.3.0-preview-acc8294b8
@metamask-previews/phishing-controller@17.3.0-preview-acc8294b8
@metamask-previews/platform-api-docs@0.0.0-preview-acc8294b8
@metamask-previews/polling-controller@16.0.8-preview-acc8294b8
@metamask-previews/preferences-controller@23.1.0-preview-acc8294b8
@metamask-previews/profile-metrics-controller@4.0.2-preview-acc8294b8
@metamask-previews/profile-sync-controller@28.3.0-preview-acc8294b8
@metamask-previews/ramps-controller@17.0.0-preview-acc8294b8
@metamask-previews/rate-limit-controller@7.0.1-preview-acc8294b8
@metamask-previews/react-data-query@0.2.2-preview-acc8294b8
@metamask-previews/remote-feature-flag-controller@4.2.2-preview-acc8294b8
@metamask-previews/sample-controllers@5.0.3-preview-acc8294b8
@metamask-previews/seedless-onboarding-controller@10.0.3-preview-acc8294b8
@metamask-previews/selected-network-controller@26.1.5-preview-acc8294b8
@metamask-previews/sentinel-api-service@1.0.0-preview-acc8294b8
@metamask-previews/shield-controller@5.1.3-preview-acc8294b8
@metamask-previews/signature-controller@39.2.7-preview-acc8294b8
@metamask-previews/smart-transactions-controller@25.0.1-preview-acc8294b8
@metamask-previews/snap-account-service@2.0.0-preview-acc8294b8
@metamask-previews/social-controllers@2.5.0-preview-acc8294b8
@metamask-previews/solana-test-validator-up@1.0.0-preview-acc8294b8
@metamask-previews/stellar-quickstart-up@0.0.0-preview-acc8294b8
@metamask-previews/storage-service@1.0.2-preview-acc8294b8
@metamask-previews/subscription-controller@6.2.1-preview-acc8294b8
@metamask-previews/transaction-controller@69.1.0-preview-acc8294b8
@metamask-previews/transaction-pay-controller@25.0.0-preview-acc8294b8
@metamask-previews/user-operation-controller@41.2.7-preview-acc8294b8
@metamask-previews/wallet@7.0.1-preview-acc8294b8
@metamask-previews/wallet-cli@0.0.0-preview-acc8294b8

@georgeweiler

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.5.5-preview-68ba7f84e
@metamask-previews/accounts-controller@39.0.5-preview-68ba7f84e
@metamask-previews/address-book-controller@7.1.2-preview-68ba7f84e
@metamask-previews/ai-controllers@0.8.0-preview-68ba7f84e
@metamask-previews/analytics-controller@1.2.1-preview-68ba7f84e
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-68ba7f84e
@metamask-previews/announcement-controller@8.1.0-preview-68ba7f84e
@metamask-previews/app-metadata-controller@2.0.1-preview-68ba7f84e
@metamask-previews/approval-controller@9.0.2-preview-68ba7f84e
@metamask-previews/assets-controller@11.1.0-preview-68ba7f84e
@metamask-previews/assets-controllers@109.4.1-preview-68ba7f84e
@metamask-previews/authenticated-user-storage@3.0.1-preview-68ba7f84e
@metamask-previews/base-controller@9.1.0-preview-68ba7f84e
@metamask-previews/base-data-service@0.1.3-preview-68ba7f84e
@metamask-previews/bitcoin-regtest-up@1.0.0-preview-68ba7f84e
@metamask-previews/bridge-controller@77.6.0-preview-68ba7f84e
@metamask-previews/bridge-status-controller@74.3.0-preview-68ba7f84e
@metamask-previews/build-utils@3.0.4-preview-68ba7f84e
@metamask-previews/chain-agnostic-permission@1.7.0-preview-68ba7f84e
@metamask-previews/chomp-api-service@3.1.0-preview-68ba7f84e
@metamask-previews/claims-controller@0.5.3-preview-68ba7f84e
@metamask-previews/client-controller@1.0.1-preview-68ba7f84e
@metamask-previews/client-utils@1.2.0-preview-68ba7f84e
@metamask-previews/compliance-controller@2.1.0-preview-68ba7f84e
@metamask-previews/composable-controller@12.0.1-preview-68ba7f84e
@metamask-previews/config-registry-controller@0.4.1-preview-68ba7f84e
@metamask-previews/connectivity-controller@0.3.0-preview-68ba7f84e
@metamask-previews/controller-utils@12.3.0-preview-68ba7f84e
@metamask-previews/core-backend@6.5.0-preview-68ba7f84e
@metamask-previews/delegation-controller@3.0.2-preview-68ba7f84e
@metamask-previews/earn-controller@12.2.2-preview-68ba7f84e
@metamask-previews/eip-5792-middleware@3.0.5-preview-68ba7f84e
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.1-preview-68ba7f84e
@metamask-previews/eip1193-permission-middleware@2.0.1-preview-68ba7f84e
@metamask-previews/ens-controller@19.1.5-preview-68ba7f84e
@metamask-previews/eth-block-tracker@15.0.1-preview-68ba7f84e
@metamask-previews/eth-json-rpc-middleware@23.1.3-preview-68ba7f84e
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-68ba7f84e
@metamask-previews/foundryup@1.0.1-preview-68ba7f84e
@metamask-previews/gas-fee-controller@26.2.4-preview-68ba7f84e
@metamask-previews/gator-permissions-controller@4.2.3-preview-68ba7f84e
@metamask-previews/geolocation-controller@0.1.3-preview-68ba7f84e
@metamask-previews/java-tron-up@1.0.0-preview-68ba7f84e
@metamask-previews/json-rpc-engine@10.5.0-preview-68ba7f84e
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-68ba7f84e
@metamask-previews/keyring-controller@27.1.0-preview-68ba7f84e
@metamask-previews/local-node-utils@1.0.0-preview-68ba7f84e
@metamask-previews/logging-controller@8.0.2-preview-68ba7f84e
@metamask-previews/message-manager@14.1.2-preview-68ba7f84e
@metamask-previews/messenger@2.0.0-preview-68ba7f84e
@metamask-previews/messenger-cli@0.2.0-preview-68ba7f84e
@metamask-previews/money-account-api-data-service@0.2.0-preview-68ba7f84e
@metamask-previews/money-account-balance-service@2.3.0-preview-68ba7f84e
@metamask-previews/money-account-controller@0.3.3-preview-68ba7f84e
@metamask-previews/money-account-upgrade-controller@2.2.1-preview-68ba7f84e
@metamask-previews/multichain-account-service@13.0.0-preview-68ba7f84e
@metamask-previews/multichain-api-middleware@4.0.1-preview-68ba7f84e
@metamask-previews/multichain-network-controller@3.2.1-preview-68ba7f84e
@metamask-previews/multichain-transactions-controller@7.1.1-preview-68ba7f84e
@metamask-previews/name-controller@9.1.2-preview-68ba7f84e
@metamask-previews/network-connection-banner-controller@0.1.0-preview-68ba7f84e
@metamask-previews/network-controller@34.0.0-preview-68ba7f84e
@metamask-previews/network-enablement-controller@5.6.0-preview-68ba7f84e
@metamask-previews/notification-services-controller@26.0.0-preview-68ba7f84e
@metamask-previews/passkey-controller@3.0.0-preview-68ba7f84e
@metamask-previews/permission-controller@13.1.1-preview-68ba7f84e
@metamask-previews/permission-log-controller@5.1.0-preview-68ba7f84e
@metamask-previews/perps-controller@9.3.0-preview-68ba7f84e
@metamask-previews/phishing-controller@17.3.0-preview-68ba7f84e
@metamask-previews/platform-api-docs@0.0.0-preview-68ba7f84e
@metamask-previews/polling-controller@16.0.8-preview-68ba7f84e
@metamask-previews/preferences-controller@23.1.0-preview-68ba7f84e
@metamask-previews/profile-metrics-controller@4.0.2-preview-68ba7f84e
@metamask-previews/profile-sync-controller@28.3.0-preview-68ba7f84e
@metamask-previews/ramps-controller@17.0.0-preview-68ba7f84e
@metamask-previews/rate-limit-controller@7.0.1-preview-68ba7f84e
@metamask-previews/react-data-query@0.2.2-preview-68ba7f84e
@metamask-previews/remote-feature-flag-controller@4.2.2-preview-68ba7f84e
@metamask-previews/sample-controllers@5.0.3-preview-68ba7f84e
@metamask-previews/seedless-onboarding-controller@10.0.3-preview-68ba7f84e
@metamask-previews/selected-network-controller@26.1.5-preview-68ba7f84e
@metamask-previews/sentinel-api-service@1.0.0-preview-68ba7f84e
@metamask-previews/shield-controller@5.1.3-preview-68ba7f84e
@metamask-previews/signature-controller@39.2.7-preview-68ba7f84e
@metamask-previews/smart-transactions-controller@25.0.1-preview-68ba7f84e
@metamask-previews/snap-account-service@2.0.0-preview-68ba7f84e
@metamask-previews/social-controllers@2.5.0-preview-68ba7f84e
@metamask-previews/solana-test-validator-up@1.0.0-preview-68ba7f84e
@metamask-previews/stellar-quickstart-up@0.0.0-preview-68ba7f84e
@metamask-previews/storage-service@1.0.2-preview-68ba7f84e
@metamask-previews/subscription-controller@6.2.1-preview-68ba7f84e
@metamask-previews/transaction-controller@69.1.0-preview-68ba7f84e
@metamask-previews/transaction-pay-controller@25.0.0-preview-68ba7f84e
@metamask-previews/user-operation-controller@41.2.7-preview-68ba7f84e
@metamask-previews/wallet@7.0.1-preview-68ba7f84e
@metamask-previews/wallet-cli@0.0.0-preview-68ba7f84e

Comment thread packages/ramps-controller/src/order-syncing/controller-integration.ts Outdated
…9474)

- Add order-syncing module storing RampsOrder objects (without paymentDetails) as per-order User Storage entries under rampsOrders feature
- RampsController.syncOrdersWithUserStorage() performs bidirectional sync with timestamp-based conflict resolution and soft deletes
- addOrder/removeOrder incrementally push when Backup & Sync and isRampsSyncingEnabled are on
- Optional onOrderSyncErroneousSituation and trace callbacks for host observability

BREAKING CHANGES:
- RampsControllerMessenger requires UserStorageController storage actions and AuthenticationController:isSignedIn

Fixes and improvements:
- Default isRampsSyncingEnabled to true when absent from state
- Route incremental writes through performBatchSetStorage for IDs with hyphens
- Re-read local orders before upload so mid-sync additions sync correctly
- Queue mid-sync deletes for tombstone writes at sync end
- Coalesce overlapping sync calls
- Preserve remote createdAt as lastUpdatedAt when importing
- Apply LWW rules to tombstones
- Prefer freshest copy across entropy profiles
- Trim providerOrderId in key derivation
- Clear polling metadata on removeOrder

Co-authored-by: George Weiler <georgejweiler@gmail.com>
georgeweiler and others added 4 commits September 3, 2026 09:18
Key lastUpdatedAt preservation on the sync-apply phase so genuine local edits during a sync (e.g. order polling) still bump to now and win the queued follow-up sync under last-write-wins.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…eat/ramps-order-syncing

# Conflicts:
#	packages/profile-sync-controller/CHANGELOG.md
#	packages/ramps-controller/CHANGELOG.md
#	packages/ramps-controller/package.json
#	packages/ramps-controller/src/RampsController-method-action-types.ts
#	packages/ramps-controller/src/RampsController.ts
#	packages/ramps-controller/src/order-syncing/utils.test.ts
#	packages/ramps-controller/src/order-syncing/utils.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread packages/ramps-controller/src/RampsController.ts
Co-authored-by: Cursor <cursoragent@cursor.com>
@georgeweiler

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-previews

@github-actions

github-actions Bot commented Sep 4, 2026

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@8.1.0-preview-2d576f0c9
@metamask-previews/accounts-controller@39.1.1-preview-2d576f0c9
@metamask-previews/address-book-controller@7.1.2-preview-2d576f0c9
@metamask-previews/ai-controllers@1.0.0-preview-2d576f0c9
@metamask-previews/analytics-controller@2.1.0-preview-2d576f0c9
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-2d576f0c9
@metamask-previews/announcement-controller@8.1.0-preview-2d576f0c9
@metamask-previews/app-metadata-controller@2.0.1-preview-2d576f0c9
@metamask-previews/approval-controller@9.0.2-preview-2d576f0c9
@metamask-previews/assets-controller@15.0.0-preview-2d576f0c9
@metamask-previews/assets-controllers@111.1.3-preview-2d576f0c9
@metamask-previews/authenticated-user-storage@3.0.2-preview-2d576f0c9
@metamask-previews/base-controller@9.1.0-preview-2d576f0c9
@metamask-previews/base-data-service@1.0.0-preview-2d576f0c9
@metamask-previews/bitcoin-regtest-up@1.0.0-preview-2d576f0c9
@metamask-previews/bridge-controller@80.2.0-preview-2d576f0c9
@metamask-previews/bridge-status-controller@75.5.0-preview-2d576f0c9
@metamask-previews/build-utils@3.0.4-preview-2d576f0c9
@metamask-previews/chain-agnostic-permission@1.7.0-preview-2d576f0c9
@metamask-previews/chomp-api-service@4.0.1-preview-2d576f0c9
@metamask-previews/claims-controller@0.6.1-preview-2d576f0c9
@metamask-previews/client-controller@1.0.1-preview-2d576f0c9
@metamask-previews/client-utils@2.1.1-preview-2d576f0c9
@metamask-previews/compliance-controller@2.1.0-preview-2d576f0c9
@metamask-previews/composable-controller@12.0.1-preview-2d576f0c9
@metamask-previews/config-registry-controller@3.1.0-preview-2d576f0c9
@metamask-previews/connectivity-controller@0.3.0-preview-2d576f0c9
@metamask-previews/controller-utils@12.3.0-preview-2d576f0c9
@metamask-previews/core-backend@9.0.0-preview-2d576f0c9
@metamask-previews/cryptography@0.0.0-preview-2d576f0c9
@metamask-previews/delegation-controller@3.0.2-preview-2d576f0c9
@metamask-previews/earn-controller@12.2.6-preview-2d576f0c9
@metamask-previews/eip-5792-middleware@3.0.5-preview-2d576f0c9
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.1-preview-2d576f0c9
@metamask-previews/eip1193-permission-middleware@2.0.1-preview-2d576f0c9
@metamask-previews/eth-block-tracker@15.0.1-preview-2d576f0c9
@metamask-previews/eth-json-rpc-middleware@24.0.2-preview-2d576f0c9
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-2d576f0c9
@metamask-previews/foundryup@1.0.1-preview-2d576f0c9
@metamask-previews/gas-fee-controller@26.3.2-preview-2d576f0c9
@metamask-previews/gator-permissions-controller@5.0.2-preview-2d576f0c9
@metamask-previews/geolocation-controller@1.0.0-preview-2d576f0c9
@metamask-previews/java-tron-up@1.0.0-preview-2d576f0c9
@metamask-previews/json-rpc-engine@10.5.0-preview-2d576f0c9
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-2d576f0c9
@metamask-previews/keyring-controller@27.1.1-preview-2d576f0c9
@metamask-previews/kyc-controller@0.0.0-preview-2d576f0c9
@metamask-previews/local-node-utils@1.0.0-preview-2d576f0c9
@metamask-previews/logging-controller@9.0.0-preview-2d576f0c9
@metamask-previews/message-manager@14.1.2-preview-2d576f0c9
@metamask-previews/messenger@2.0.0-preview-2d576f0c9
@metamask-previews/messenger-cli@0.2.0-preview-2d576f0c9
@metamask-previews/money-account-api-data-service@0.4.1-preview-2d576f0c9
@metamask-previews/money-account-balance-service@2.4.3-preview-2d576f0c9
@metamask-previews/money-account-controller@1.0.0-preview-2d576f0c9
@metamask-previews/money-account-upgrade-controller@3.0.2-preview-2d576f0c9
@metamask-previews/money-account-utils@1.1.0-preview-2d576f0c9
@metamask-previews/multichain-account-service@13.0.2-preview-2d576f0c9
@metamask-previews/multichain-api-middleware@4.0.3-preview-2d576f0c9
@metamask-previews/multichain-network-controller@3.2.4-preview-2d576f0c9
@metamask-previews/multichain-transactions-controller@7.1.2-preview-2d576f0c9
@metamask-previews/name-controller@9.1.2-preview-2d576f0c9
@metamask-previews/network-connection-banner-controller@0.2.1-preview-2d576f0c9
@metamask-previews/network-controller@36.0.0-preview-2d576f0c9
@metamask-previews/network-enablement-controller@6.0.5-preview-2d576f0c9
@metamask-previews/notification-services-controller@27.0.0-preview-2d576f0c9
@metamask-previews/passkey-controller@3.1.0-preview-2d576f0c9
@metamask-previews/permission-controller@13.1.1-preview-2d576f0c9
@metamask-previews/permission-log-controller@5.1.0-preview-2d576f0c9
@metamask-previews/perps-controller@16.0.0-preview-2d576f0c9
@metamask-previews/phishing-controller@17.4.1-preview-2d576f0c9
@metamask-previews/platform-api-docs@0.1.0-preview-2d576f0c9
@metamask-previews/polling-controller@16.0.9-preview-2d576f0c9
@metamask-previews/preferences-controller@23.1.0-preview-2d576f0c9
@metamask-previews/profile-metrics-controller@4.0.3-preview-2d576f0c9
@metamask-previews/profile-sync-controller@29.0.0-preview-2d576f0c9
@metamask-previews/ramps-controller@20.2.0-preview-2d576f0c9
@metamask-previews/rate-limit-controller@7.0.1-preview-2d576f0c9
@metamask-previews/react-data-query@1.0.0-preview-2d576f0c9
@metamask-previews/remote-feature-flag-controller@6.1.0-preview-2d576f0c9
@metamask-previews/sample-controllers@5.0.6-preview-2d576f0c9
@metamask-previews/seedless-onboarding-controller@10.1.1-preview-2d576f0c9
@metamask-previews/selected-network-controller@26.1.7-preview-2d576f0c9
@metamask-previews/sentinel-api-service@1.0.1-preview-2d576f0c9
@metamask-previews/shield-controller@6.0.1-preview-2d576f0c9
@metamask-previews/signature-controller@39.2.10-preview-2d576f0c9
@metamask-previews/smart-transactions-controller@26.0.0-preview-2d576f0c9
@metamask-previews/snap-account-service@2.1.2-preview-2d576f0c9
@metamask-previews/social-controllers@2.8.0-preview-2d576f0c9
@metamask-previews/solana-test-validator-up@1.0.0-preview-2d576f0c9
@metamask-previews/stellar-quickstart-up@0.0.0-preview-2d576f0c9
@metamask-previews/storage-service@1.0.2-preview-2d576f0c9
@metamask-previews/subscription-controller@8.0.1-preview-2d576f0c9
@metamask-previews/transaction-controller@69.8.0-preview-2d576f0c9
@metamask-previews/transaction-pay-controller@27.1.1-preview-2d576f0c9
@metamask-previews/user-operation-controller@41.2.9-preview-2d576f0c9
@metamask-previews/wallet@12.0.2-preview-2d576f0c9
@metamask-previews/wallet-cli@0.0.0-preview-2d576f0c9

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	packages/profile-sync-controller/CHANGELOG.md
#	packages/ramps-controller/package.json

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread packages/ramps-controller/src/RampsController.ts
Comment thread packages/ramps-controller/src/RampsController.ts Outdated
Comment thread packages/ramps-controller/src/RampsController.ts
Co-authored-by: Cursor <cursoragent@cursor.com>
amitabh94
amitabh94 previously approved these changes Sep 9, 2026
Comment thread packages/profile-sync-controller/CHANGELOG.md Outdated
Comment thread packages/ramps-controller/src/order-syncing/controller-integration.ts Outdated
Comment thread packages/ramps-controller/src/order-syncing/controller-integration.ts Outdated
// A polling request can finish after removeOrder. Do not let that stale
// response recreate the local order and overwrite its remote tombstone.
if (!hadOrderAtRequestStart || orderStillExists) {
// Use addOrder to bump lastUpdatedAt and trigger incremental sync.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This with LWW made me a bit uncomfy but I couldn't pinpoint what really was the problem so I asked Fable to have a look, WDYT? Fable said this:

Unconditional lastUpdatedAt bump + remote write on every addOrder causes steady-state write amplification on User Storage.

getOrder (L3905-3906) now routes every poll result through addOrder, and addOrder stamps lastUpdatedAt: Date.now() (L3026-3028) and calls updateOrderInRemoteStorage (L3054) with no change detection on either side (controller-integration.ts L451-462 also writes unconditionally). #pollPendingOrders fires every 30s (DEFAULT_POLLING_INTERVAL_MS) for every non-terminal order.

Net effect: every pending order, on every unlocked device with Backup & Sync on, produces an encrypt + PUT to user-storage.api.cx.metamask.io twice a minute for as long as the order is pending (bank transfers: hours to days), even when the payload is byte-identical. Two unlocked devices ping-pong the same key. Beyond the load, it makes an idle poller always win LWW over a real edit on another device.

Bugbot flagged this as High ("Polling triggers sync side effects") and the thread was resolved without a change or a reply.

Fix: short-circuit when the payload is unchanged. areOrdersEqual already exists and already excludes sync metadata and paymentDetails, so:

const existing = this.state.orders.find(
  (o) => getInternalOrderCode(o) === internalOrderCode,
);
if (
  existing &&
  !this.#isApplyingOrderSyncChanges &&
  areOrdersEqual(existing, order)
) {
  return; // no timestamp bump, no remote write, no #orderSyncQueued
}

placed after the internalOrderCode guard (L3012).

Tests to add:

  • poll returning an unchanged order does not call UserStorageController:performBatchSetStorage and does not bump lastUpdatedAt
  • poll returning a status change produces exactly one write

Related: updateOrderInRemoteStorage (controller-integration.ts L451-459) has no way to know whether the order changed, so the check belongs in addOrder. If that function stays the single write path, worth documenting that callers own change detection, otherwise the next caller reintroduces the amplification.

The part that matters to me as accounts / identity CO is the write volume to User Storage; the LWW side effect is a bonus fix

@georgeweiler georgeweiler Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nice job Fable 👀 I've added areOrdersEqual so unchanged polls don't change lastUpdatedAt

Short-circuit addOrder when the syncable payload is unchanged so polling cannot amplify writes or steal LWW. Also rename incremental helpers to *UserStorage and trim a redundant changelog bullet.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread packages/ramps-controller/src/RampsController.ts
georgeweiler and others added 2 commits September 9, 2026 13:19
Rename the shadowed findIndex param in addOrder and regenerate RampsController-method-action-types.ts for the updated addOrder doc comment.

Co-authored-by: Cursor <cursoragent@cursor.com>
Move the #9474 changelog entries into a fresh Unreleased section above the 21.0.0 / 31.0.0 releases cut on main, and keep fast-deep-equal alongside main's dependency bumps.

Co-authored-by: Cursor <cursoragent@cursor.com>
@georgeweiler
georgeweiler requested a balanced review from Copilot September 10, 2026 02:07
@georgeweiler

Copy link
Copy Markdown
Contributor Author

@cursoragent please do a final review

@cursor

cursor Bot commented Sep 10, 2026

Copy link
Copy Markdown

Final Review Summary

I've completed a comprehensive review of the ramps order syncing PR and identified and fixed a critical bug reported by Bugbot.

Issue Fixed

Bugbot Finding: Polls drop local payment details (Medium Severity)

The problem was in addOrder() at lines 3023-3028. When areOrdersEqual() returned true (syncable payload unchanged), the function would return early, even if paymentDetails had changed. Since areOrdersEqual() intentionally strips paymentDetails for comparison (it's local-only and never synced), any poll that only updated paymentDetails would be silently dropped.

Solution: Added explicit handling for the case where only paymentDetails changed:

  • Merges new paymentDetails into local state
  • Does NOT bump lastUpdatedAt (since syncable payload unchanged)
  • Does NOT write remotely (since paymentDetails never syncs)
  • Added two comprehensive test cases covering both direct addOrder and poll-via-getOrder scenarios

Test Results

All tests pass with excellent coverage:

  • 978 tests pass (0 failures)
  • 99.71% statement coverage
  • 97% branch coverage
  • 100% function coverage

Architecture Review

The implementation is well-structured and follows the existing account-tree/contact sync patterns:

✅ Strengths:

  • Clean separation of concerns with order-syncing/ module
  • Last-write-wins (LWW) conflict resolution with proper timestamp handling
  • Soft-delete tombstones to handle cross-device deletion
  • Coalesced overlapping syncs to prevent race conditions
  • paymentDetails correctly stripped from remote payloads (PII protection)
  • Breaking changes clearly documented in both CHANGELOGs
  • Required messenger actions explicitly exported and documented

✅ Edge Cases Handled:

  • Mid-sync mutations coalesce into follow-up sync
  • Incremental push blocked during full sync
  • Stale poll responses after removeOrder are not re-added
  • Corrupt remote entries logged but don't crash sync
  • Missing isRampsSyncingEnabled defaults to enabled (backward compatible)

Ready to Merge

The PR is ready to merge pending:

  • ✅ Fix committed and pushed (commit 6b4819f282)
  • ✅ All tests passing
  • ✅ No security concerns
  • ✅ Breaking changes documented
  • ✅ Host integration requirements clear

Next steps for hosts: Mobile and Extension PRs can proceed once these packages are published, ensuring their messengers delegate the required User Storage and auth actions.

Open in Web Open in Cursor 

When a poll returns an order where only paymentDetails changed (everything
else is identical), the previous implementation would return early due to
areOrdersEqual returning true (it strips paymentDetails for comparison).

This meant paymentDetails updates from polls were dropped.

Now addOrder explicitly checks if paymentDetails differ when the syncable
payload is unchanged, and merges them into local state without bumping
lastUpdatedAt or writing remotely (since paymentDetails is local-only).

Fixes Bugbot finding: polls drop local payment details

Co-authored-by: George Weiler <georgejweiler@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

It changes breaking messenger/state contracts and adds non-trivial bidirectional sync logic, and introduces a required state field that breaks a full-state construction in account-tree-controller, so it needs human review and cross-package fixes.

Review details
  • Files reviewed: 21/22 changed files
  • Comments generated: 2
  • Review effort level: Balanced

/**
* Condition used by UI to determine if ramps order syncing is enabled.
*/
isRampsSyncingEnabled: boolean;
Comment thread packages/ramps-controller/CHANGELOG.md Outdated

- Add `NeoBankService` for MetaMask Ramp API neo-bank-proxy endpoints under the `/neobank` prefix on the Ramp API host, including messenger actions for `getAutoramp`, `registerPixAddress`, `getAutorampQuote`, `createAutoramp`, `getAutorampQuoteForAutoramp`, `attachAutorampQuote`, `getCustomerByExternalId`, `getMoonpayCustomerId`, `getWalletRegistrationStatus`, and `registerSelfHostedWallet`. Mutating POSTs do not retry (to avoid duplicate Pix/autoramp creates without a stable `Idempotency-Key`); GETs still retry 429/5xx/network errors. Optional `Idempotency-Key` is forwarded when callers supply one. Also exports `mapNeoBankAutorampToRemoteSnapshot`, `AutorampRemoteSnapshot`, and wallet-registration HTTP types (`WalletRegistrationError`, `RegistrationStatus`, `RegistrationOutcome`). ([#10031](https://github.com/MetaMask/core/pull/10031))
- Add `RampsController` autoramp last-seen cursor and Money Account wallet registration: persisted `autoramps` state, `createAutoramp` / `refreshAutoramp(s)` / `applyAutorampStatusFromPush`, `registerMoneyAccountWallet`, and `RampsController:autorampStatusChanged`. MoonPay remains the source of truth; hosts should call `refreshAutoramps` on resume to catch webhooks missed while the app was closed. Hosts must delegate `RAMPS_CONTROLLER_REQUIRED_CONTROLLER_ACTIONS` (`AuthenticationController:getSessionProfile`, `KeyringController:signPersonalMessage`, `RemoteFeatureFlagController:getState`) plus the NeoBank actions listed in `RAMPS_CONTROLLER_REQUIRED_SERVICE_ACTIONS`. ([#10032](https://github.com/MetaMask/core/pull/10032))
- 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))

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 6b4819f. Configure here.

Keep UserStorageControllerState constructions complete, and drop a duplicated #9679 changelog entry that already lives under 20.1.0.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants