Skip to content

feat: sync V2 ramps orders with Backup & Sync - #33148

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

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

Conversation

@georgeweiler

@georgeweiler georgeweiler commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Description

Syncs V2 buy/sell order history through Backup & Sync so orders created on Mobile can appear on other Mobile devices, Extension, and Portfolio for the same SRP/Profile Sync profile. It adds the Buy & sell orders setting, runs order sync after unlock when all identity and privacy gates are enabled, and keeps Activity rows distinct when providers reuse placeholder transaction hashes.

Missing ramps-sync toggle values on upgraded wallets default to enabled, matching new installs.

Companion PRs:

Implementation notes:

  • Delegates the User Storage and authentication messenger actions required by ramps-controller order sync.
  • Calls RampsController.syncOrdersWithUserStorage() from useIdentityEffects after unlock.
  • Falls back to order ID when txHash is missing or DUMMY_TX_ID, and normalizes createdAt for Activity sorting.
  • Pins preview ramps/profile-sync controller builds until the companion Core changes are published.

Changelog

CHANGELOG entry: Added Backup & Sync support for buy and sell order history

Related issues

Refs: MetaMask/core#9474

Manual testing steps

Feature: Buy and sell order history sync

  Scenario: Sync a Mobile ramps order to another client
    Given two clients use the same signed-in SRP profile and environment
    And Backup & Sync and Buy & sell orders are enabled
    When a V2 buy or sell order is completed on Mobile
    And the other client is unlocked
    Then the order appears in the other client's order history

  Scenario: Disable ramps order syncing
    Given Backup & Sync is enabled
    When Buy & sell orders is disabled
    Then ramps orders are not pushed to or pulled from User Storage

Automated coverage includes order-sync gating, controller messenger wiring, Activity adapter fallback IDs, settings UI, and controller initialization.

Screenshots/Recordings

N/A — the change adds another row using the existing Backup & Sync settings-toggle component and does not introduce a new layout or interaction pattern.

Pre-merge author checklist

Performance checks (if applicable)

  • I've tested on Android
  • I've tested with a power user scenario
  • I've instrumented key operations with Sentry traces for production performance metrics

The performance items were assessed as not applicable to this profile-sync integration; CI performance checks remain non-blocking.

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Medium Risk
Touches Backup & Sync / User Storage for financial order history and expands RampsController messenger permissions, with preview controller package pins until Core publishes.

Overview
Enables Backup & Sync for V2 buy/sell order history so Mobile orders can appear on other signed-in clients sharing the same profile.

Adds a Buy & sell orders toggle in Backup & Sync settings, a useRampsOrderSyncing hook that calls RampsController.syncOrdersWithUserStorage() when identity/privacy gates pass, and wires that into useIdentityEffects. Missing isRampsSyncingEnabled values default to enabled on upgrade.

Delegates User Storage and auth messenger actions to RampsController, tracks order-sync error situations, and requires a non-empty chainId when registering precreated orders. Also treats placeholder hashes like DUMMY_TX_ID as invalid Activity keys and normalizes createdAt timestamps so synced orders stay distinct and sortable.

Pins preview @metamask/ramps-controller / @metamask/profile-sync-controller builds until the companion Core release lands.

Reviewed by Cursor Bugbot for commit 0609d30. 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:50
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@metamask-ci metamask-ci Bot added team-money-movement issues related to Money Movement features INVALID-PR-TEMPLATE PR's body doesn't match template labels Jul 10, 2026
@github-actions github-actions Bot added size-M risk:low AI analysis: low risk labels Jul 10, 2026
@georgeweiler
georgeweiler force-pushed the feat/ramps-order-syncing branch from b129eca to e8da86c Compare July 11, 2026 20:12
@github-actions github-actions Bot added risk:medium AI analysis: medium risk and removed risk:low AI analysis: low risk labels Jul 11, 2026
@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

🧪 Flaky unit test detection

Run history flaky detection

View recent run history

Historical failure rate is a hint, not proof — review each suggestion in context. See the flaky-test-detection skill for the full pattern reference and manual audit workflow.

Failures / runs sampled per window:

File 7d 15d 30d
app/core/Engine/controllers/identity/authentication-controller-init.test.ts 0/147 0/184 0/389
app/util/identity/hooks/useRampsOrderSyncing/useRampsOrderSyncing.test.tsx 0/147 0/184 0/389
tests/smoke-appium/wallet/incoming-transactions.spec.ts 0/147 0/184 0/389

AI-detected flaky patterns

app/core/Engine/controllers/identity/authentication-controller-init.test.ts

  • J3 — Missing jest.clearAllMocks() / jest.resetAllMocks() (high)
    • The outer describe block has no beforeEach(() => jest.clearAllMocks()). The AuthenticationController constructor mock accumulates call history across every test in the suite. The test 'passes the proper arguments to the controller' uses toHaveBeenCalledWith which checks whether the mock was ever called with those args — so it passes even with stale calls — but 'wires getAppVersion to react-native-device-info getVersion()' relies on mock.lastCall which is safe only if no subsequent test has already called the constructor before it runs. More critically, jest.mocked(getVersion) call-count assertions can bleed between tests when run in a different order (e.g. with --randomize). Adding a beforeEach with jest.clearAllMocks() eliminates all of these order-dependent risks.
    • Suggested fix in app/core/Engine/controllers/identity/authentication-controller-init.test.ts:35:
      -describe('AuthenticationControllerInit', () => {
      -  it('initializes the controller', () => {
      -    const { controller } = authenticationControllerInit(getInitRequestMock());
      -    expect(controller).toBeInstanceOf(AuthenticationController);
      -  });
      -
      -  it('passes the proper arguments to the controller', () => {
      -    authenticationControllerInit(getInitRequestMock());
      -
      -    const controllerMock = jest.mocked(AuthenticationController);
      -    expect(controllerMock).toHaveBeenCalledWith({
      +describe('AuthenticationControllerInit', () => {
      +  beforeEach(() => {
      +    jest.clearAllMocks();
      +  });
      +
      +  it('initializes the controller', () => {
      +    const { controller } = authenticationControllerInit(getInitRequestMock());
      +    expect(controller).toBeInstanceOf(AuthenticationController);
      +  });
      +
      +  it('passes the proper arguments to the controller', () => {
      +    authenticationControllerInit(getInitRequestMock());
      +
      +    const controllerMock = jest.mocked(AuthenticationController);
      +    expect(controllerMock).toHaveBeenCalledWith({

app/util/identity/hooks/useRampsOrderSyncing/useRampsOrderSyncing.test.tsx

  • J3 — Missing jest.clearAllMocks() / jest.resetAllMocks() (high)
    • jest.clearAllMocks() is only called inside the arrangeAndAct helper used by the useRampsOrderSyncing describe block. The useShouldDispatchRampsOrderSyncing describe block — which runs first — has no beforeEach that clears mocks. If mockSyncOrdersWithUserStorage or the Engine mock accumulates state from a prior test run (e.g. when the full suite runs with --randomize), the useShouldDispatchRampsOrderSyncing tests can observe stale mock state. A top-level beforeEach(() => jest.clearAllMocks()) covering both describe blocks would fix this.
    • Suggested fix in app/util/identity/hooks/useRampsOrderSyncing/useRampsOrderSyncing.test.tsx:62:
      -describe('useShouldDispatchRampsOrderSyncing()', () => {
      -  const testCases = (() => {
      -    // ...
      -  })();
      -
      -  it('returns true if all conditions are met', () => {
      -    const { state } = arrangeMockState(testCases.successTestCase.state);
      -    const hook = renderHookWithProvider(
      -      () => useShouldDispatchRampsOrderSyncing(),
      -      { state },
      -    );
      -    expect(hook.result.current).toBe(true);
      -  });
      -
      -  // @ts-ignore
      -  it.each(testCases.failureStateCases)(
      -    'returns false if not all conditions are met [%s = false]',
      +// Add at the top level of the file, before the first describe block:
      +beforeEach(() => {
      +  jest.clearAllMocks();
      +});
      +
      +describe('useShouldDispatchRampsOrderSyncing()', () => {

tests/smoke-appium/wallet/incoming-transactions.spec.ts

  • J7 — Non-deterministic data: Date.now(), Math.random(), unstubbed network (medium)
    • new Date().toISOString() is evaluated at module-load time, producing a different timestamp on every test run. While the current assertions only check displayed text (e.g. 'Received ETH') and not the timestamp value, any future assertion against the timestamp field — or a sort/filter in the production code that compares timestamps — would produce non-deterministic results. Pinning to a fixed ISO string eliminates this risk entirely and makes the mock data self-documenting.
    • Suggested fix in tests/smoke-appium/wallet/incoming-transactions.spec.ts:57:
      -const RESPONSE_STANDARD_MOCK = {
      -  hash: '0x123456',
      -  timestamp: new Date().toISOString(),
      -  chainId: 1,
      -  // ...
      -};
      -
      -const RESPONSE_STANDARD_2_MOCK = {
      -  ...RESPONSE_STANDARD_MOCK,
      -  timestamp: new Date().toISOString(),
      -  hash: '0x2',
      -  value: '2340000000000000000',
      -};
      +const FIXED_TIMESTAMP = '2024-01-15T12:00:00.000Z';
      +
      +const RESPONSE_STANDARD_MOCK = {
      +  hash: '0x123456',
      +  timestamp: FIXED_TIMESTAMP,
      +  chainId: 1,
      +  blockNumber: 1,
      +  blockHash: '0x2',
      +  gas: 1,
      +  gasUsed: 1,
      +  gasPrice: '1',
      +  effectiveGasPrice: '1',
      +  nonce: 1,
      +  cumulativeGasUsed: 1,
      +  methodId: null,
      +  value: '1230000000000000000',
      +  to: DEFAULT_FIXTURE_ACCOUNT,
      +  from: TRUSTED_INCOMING_SENDER_CHECKSUM,
      +  isError: false,
      +  valueTransfers: [],
      +};
      +
      +const RESPONSE_STANDARD_2_MOCK = {
      +  ...RESPONSE_STANDARD_MOCK,
      +  timestamp: FIXED_TIMESTAMP,
      +  hash: '0x2',
      +  value: '2340000000000000000',
      +};

This check is informational only and does not block merging.

@github-actions github-actions Bot added risk:high AI analysis: high risk and removed risk:medium AI analysis: medium risk labels Jul 27, 2026
Comment thread app/components/UI/Ramp/orderProcessor/unifiedOrderProcessor.test.ts Outdated
@socket-security

socket-security Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​metamask-previews/​ramps-controller@​20.0.0-preview-8c482076b881007998100

View full report

@socket-security

socket-security Bot commented Aug 11, 2026

Copy link
Copy Markdown

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

Ignoring alerts on:

  • npm/@metamask-previews/ramps-controller@20.0.0-preview-8c482076b

View full report

@georgeweiler
georgeweiler requested a review from a team as a code owner August 11, 2026 18:04
@github-actions github-actions Bot added size-L and removed size-M labels Aug 11, 2026
@metamask-ci metamask-ci Bot removed the INVALID-PR-TEMPLATE PR's body doesn't match template label Aug 11, 2026
@georgeweiler

Copy link
Copy Markdown
Contributor Author

@SocketSecurity ignore npm/@metamask-previews/ramps-controller@20.0.0-preview-8c482076b

Reviewed the preview package's order-sync feature: network access is expected because syncOrdersWithUserStorage reads and writes encrypted ramps orders through the MetaMask User Storage service. The package is the companion Core preview pinned for this PR.

@georgeweiler
georgeweiler force-pushed the feat/ramps-order-syncing branch from 4355153 to cc82603 Compare August 11, 2026 20:31

@cursor cursor Bot left a comment

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.

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 cc82603. Configure here.

Comment thread package.json
@georgeweiler
georgeweiler force-pushed the feat/ramps-order-syncing branch from 0609d30 to d83c18d Compare August 17, 2026 12:59
georgeweiler and others added 12 commits August 17, 2026 07:00
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>
Align selectIsRampsSyncingEnabled with UserStorageController default state
so the Backup & Sync toggle and background sync gates stay consistent for
upgraded profiles that omit isRampsSyncingEnabled.

Co-authored-by: Cursor <cursoragent@cursor.com>
Treat placeholder tx hashes as missing so Activity falls back to the
order id for row identity, and normalize string createdAt for sorting.

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

Order syncing resolves the entropy source before reading remote storage, so
the action has to be delegated alongside the other User Storage actions.

Co-authored-by: Cursor <cursoragent@cursor.com>
Switching between the DEV and PRD Profile Sync backends left incompatible
persisted auth in place, so ramps order sync could never mint a fresh
session. Clear it during AuthenticationController init instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
Drop the unused listEntropySources delegation and keep canonical
provider codes for order polling / deeplink redirects.

Co-authored-by: Cursor <cursoragent@cursor.com>
Pass host trace and an order-sync error callback into RampsController so
sync failures surface in analytics, and cover the Buy & sell orders toggle
in the Backup & Sync view test and Appium page object.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Require the chain ID expected by ramps-controller v20 and mock the new ramps order storage read used during E2E startup.

Co-authored-by: Cursor <cursoragent@cursor.com>
SmokeMoney buy flows push synced orders via PUT after completion; cover collection and entry writes like other Backup & Sync features.

Co-authored-by: Cursor <cursoragent@cursor.com>
Order syncing is not in published ramps-controller yet, so point
ramps-controller and profile-sync-controller at the core#9474 preview.
Revert to published ranges once that work is released.

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

Init passes partial persisted state, and UserProfile now carries
canonicalProfileId, so widen the sanitizer signature and update the fixture.

Co-authored-by: Cursor <cursoragent@cursor.com>
@georgeweiler
georgeweiler force-pushed the feat/ramps-order-syncing branch from d83c18d to 6794410 Compare August 17, 2026 13:01
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeAccounts, SmokeConfirmations, SmokeNetworkAbstractions, SmokeNetworkExpansion, SmokeSwap, SmokeStake, SmokeWalletPlatform, SmokeMoney, SmokePerps, SmokeMultiChainAPI, SmokePredictions, SmokeSeedlessOnboarding, SmokeBrowser, SmokeSnaps, SmokeMMConnect
  • Selected Performance tags: @PerformanceAccountList, @PerformanceOnboarding, @PerformanceLogin, @PerformanceSwaps, @PerformanceLaunch, @PerformanceAssetLoading, @PerformancePredict, @PerformancePreps, @PerformanceMoney
  • Risk Level: high
  • AI Confidence: 100%
click to see 🤖 AI reasoning details

E2E Test Selection:
Hard rule (controller-version-update): @MetaMask controller package version updated in package.json: @metamask/ramps-controller, @metamask/profile-sync-controller, @metamask/assets-controller@npm:^13.1.3, @metamask/ramps-controller@^20.0.0. Running all tests.

Performance Test Selection:
Fallback: AI analysis did not complete successfully. Running all performance tests.

View GitHub Actions results

@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Performance Test Results

ℹ️ Performance test results are currently non-blocking and will not block this PR.

2 tests failed · 22 tests · 1 device

📱 Devices tested (1)

Android: Google Pixel 8 Pro (v14.0)

❌ Failed Tests (2)

🔬 App profiling vs main is included under each failed scenario that has a prior baseline.

@metamask-onboarding-team

Fresh SRP wallet creation performance

Platform Device Reason Recording
Android Google Pixel 8 Pro (v14.0) Quality gates exceeded 📹 Watch

🔬 App profiling check · Current run 32032827450 · Baseline (last run on main (scenario also failing)) run 30897750395 @ 67486d2

⚠️ No green baseline on main — comparing against the latest usable profiling.

Summary: ⚠️ 4 metrics over +10%: CPU avg (+1.71 (+28.7%)), Memory avg (+113.54 (+19.4%)), Memory max (+104.54 (+14.5%)), Slow frames (+13.03 (+519.1%))

ℹ️ API calls unavailable: Network logs API error: Bad Request

Full metric table (+10% variance rules)

Disclaimer — allowed variance: a +10% margin over the baseline is permitted.

  • If Current <= Baseline + 10%, treated as acceptable noise.
  • If Current > Baseline + 10%, Current and variance % are highlighted with ⚠️.
Metric Baseline Current Δ
CPU avg 5.95% 7.66% +1.71 (+28.7%) ⚠️
CPU max 19.17% 20.98% +1.81 (+9.4%)
Memory avg 586.2 MB 699.74 MB +113.54 (+19.4%) ⚠️
Memory max 722.61 MB 827.15 MB +104.54 (+14.5%) ⚠️
Slow frames 2.51% 15.54% +13.03 (+519.1%) ⚠️
Frozen frames 0% 0% 0 (0%)
ANRs 0 0 0 (0%)
Issues 2 2 0 (0%)
Critical issues 1 1 0 (0%)
App size 328.67 MB 329.74 MB +1.07 (+0.3%)

@mm-perps-engineering-team

Perps open position and close it

Platform Device Reason Recording
Android Google Pixel 8 Pro (v14.0) no_performance_metrics 📹 Watch

🔬 App profiling check · Current run 32032827450 · Baseline (last run on main (scenario also failing)) run 30897750395 @ 67486d2

⚠️ No green baseline on main — comparing against the latest usable profiling.

Summary: ⚠️ 5 metrics over +10%: CPU max (+7.06 (+37.5%)), Memory avg (+280.71 (+50.5%)), Memory max (+426.8 (+70.1%)), Slow frames (+4.73 (+91.7%)), Critical issues (+1 (+100%))

ℹ️ API calls unavailable: Network logs API error: Bad Request

Full metric table (+10% variance rules)

Disclaimer — allowed variance: a +10% margin over the baseline is permitted.

  • If Current <= Baseline + 10%, treated as acceptable noise.
  • If Current > Baseline + 10%, Current and variance % are highlighted with ⚠️.
Metric Baseline Current Δ
CPU avg 10.44% 8.01% -2.43 (-23.3%)
CPU max 18.83% 25.89% +7.06 (+37.5%) ⚠️
Memory avg 555.64 MB 836.35 MB +280.71 (+50.5%) ⚠️
Memory max 609.03 MB 1035.83 MB +426.8 (+70.1%) ⚠️
Slow frames 5.16% 9.89% +4.73 (+91.7%) ⚠️
Frozen frames 0% 0% 0 (0%)
ANRs 0 0 0 (0%)
Issues 2 2 0 (0%)
Critical issues 1 2 +1 (+100%) ⚠️
App size 328.67 MB 329.74 MB +1.07 (+0.3%)
✅ Passed Tests (20)
Test Platform Device Duration Team Recording
Asset View, SRP 1 + SRP 2 + SRP 3 Android Google Pixel 8 Pro (v14.0) 3.17s @assets-dev-team 📹 Watch
Aggregated Balance Loading Time, SRP 1 + SRP 2 + SRP 3 Android Google Pixel 8 Pro (v14.0) 8.46s @assets-dev-team 📹 Watch
Swap flow - ETH to LINK, SRP 1 + SRP 2 + SRP 3 Android Google Pixel 8 Pro (v14.0) 1.12s @swap-bridge-dev-team 📹 Watch
Cross-chain swap flow - ETH to SOL - 50+ accounts, SRP 1 + SRP 2 + SRP 3 Android Google Pixel 8 Pro (v14.0) 5.38s @swap-bridge-dev-team 📹 Watch
Import SRP with +50 accounts, SRP 1, SRP 2, SRP 3 Android Google Pixel 8 Pro (v14.0) 4.00s @Accounts-team 📹 Watch
Cold Start: Measure ColdStart To Login Screen Android Google Pixel 8 Pro (v14.0) 4.04s @metamask-mobile-platform 📹 Watch
Measure Warm Start: Login To Wallet Screen Android Google Pixel 8 Pro (v14.0) 1.63s @metamask-mobile-platform 📹 Watch
Measure Warm Start: Warm Start to Login Screen Android Google Pixel 8 Pro (v14.0) 0.20s @metamask-mobile-platform 📹 Watch
Perps add funds Android Google Pixel 8 Pro (v14.0) 7.79s @mm-perps-engineering-team 📹 Watch
Predict Available Balance - Complete Flow Performance Android Google Pixel 8 Pro (v14.0) 0.87s @team-predict 📹 Watch
Predict Market Details - Complete Flow Performance Android Google Pixel 8 Pro (v14.0) 2.93s @team-predict 📹 Watch
Predict Deposit - Complete Flow Performance Android Google Pixel 8 Pro (v14.0) 10.26s @team-predict 📹 Watch
Measure Cold Start To Onboarding Screen Android Google Pixel 8 Pro (v14.0) 3.22s @metamask-mobile-platform 📹 Watch
Onboarding Import SRP with +50 accounts, SRP 3 Android Google Pixel 8 Pro (v14.0) 4.64s @metamask-onboarding-team 📹 Watch
Money Home after fresh wallet creation with empty balance Android Google Pixel 8 Pro (v14.0) 2.82s @mm-earn-team 📹 Watch
Money Home after importing SRP with funded balance Android Google Pixel 8 Pro (v14.0) 4.09s @mm-earn-team 📹 Watch
Account creation after fresh install Android Google Pixel 8 Pro (v14.0) 2.49s @metamask-onboarding-team 📹 Watch
Seedless Onboarding: Apple Login New User Android Google Pixel 8 Pro (v14.0) 5.07s @metamask-onboarding-team 📹 Watch
Seedless Onboarding: Google Login New User Android Google Pixel 8 Pro (v14.0) 4.58s @metamask-onboarding-team 📹 Watch
Seedless Onboarding: Telegram Login New User Android Google Pixel 8 Pro (v14.0) 5.23s @metamask-onboarding-team 📹 Watch

Branch: feat/ramps-order-syncing · Build: E2E · Commit: efd524d · View full run

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

Labels

risk:high AI analysis: high risk size-L team-money-movement issues related to Money Movement features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant