Skip to content

feat(watchlist): add local-storage backed watchlist storage utils [ASSETS-3115]#30337

Merged
Prithpal-Sooriya merged 5 commits into
mainfrom
cursor/assets-3115-watchlist-storage-8eea
May 18, 2026
Merged

feat(watchlist): add local-storage backed watchlist storage utils [ASSETS-3115]#30337
Prithpal-Sooriya merged 5 commits into
mainfrom
cursor/assets-3115-watchlist-storage-8eea

Conversation

@Prithpal-Sooriya
Copy link
Copy Markdown
Contributor

@Prithpal-Sooriya Prithpal-Sooriya commented May 18, 2026

Description

Adds the initial Storage Integration layer for the WatchList feature, as scoped by ASSETS-3115 and the WatchList Tech Spec.

The AUS SDK changes are not yet available, so this PR introduces a temporary local-device implementation backed by the existing MMKV-based StorageWrapper. The async API and schema-validation contract are designed to remain stable, so the body of the two exported functions can later be swapped to delegate to the AUS SDK without changes for callers.

New module: app/components/UI/assets/watchlist/storage.ts

  • WatchlistBlobSchema@metamask/superstruct schema describing the persisted blob:
    • assets: defaulted array(string())
    • version: defaulted literal(1)
  • WatchlistBlob — type inferred from the schema.
  • WATCHLIST_STORAGE_PATH = 'watchlistV1.tokens' and EMPTY_BLOB = { assets: [], version: 1 } constants (matching the tech-spec snippet).
  • readFromTokenWatchList(): Promise<WatchlistBlob> — reads from StorageWrapper, returns EMPTY_BLOB when no value is stored, otherwise JSON.parses and validates through WatchlistBlobSchema (applying schema defaults) before returning. Per ticket AC: "must parse through the defined schema before client can use it".
  • writeToTokenWatchList(blob): Promise<void> — validates the input through WatchlistBlobSchema first, then JSON.stringifys and writes via StorageWrapper. Per ticket AC: "must validate input before stringify and store".

Note on file path: the ticket asks for app/components/UI/assets/watchlist/storage.ts (lowercase assets/). There is also an existing app/components/UI/Assets/ (capital A). On case-insensitive filesystems (macOS APFS default, Windows NTFS default) these two folders will collide. Flagging for awareness — happy to rename to Assets/watchlist/ (or another path) if preferred.

Changelog

CHANGELOG entry: null

Related issues

Fixes: ASSETS-3115
Parent story: ASSETS-2912 — [Mobile][WatchList] Allow users to add, remove, and view their watchlisted tokens
Tech Spec: WatchList Tech Spec — Technical Breakdown Tasks
Related SDK PR (future swap-in): MetaMask/core#8836

Manual testing steps

This PR introduces internal utility functions with no UI surface yet, so there is nothing to test manually in-app. Verified via unit tests:

Feature: WatchList local-device storage utilities

  Scenario: read with empty storage
    Given the WATCHLIST_STORAGE_PATH key is unset
    When the caller invokes readFromTokenWatchList()
    Then the result is EMPTY_BLOB ({ assets: [], version: 1 })

  Scenario: read with valid stored blob
    Given a valid JSON-serialized WatchlistBlob is stored at WATCHLIST_STORAGE_PATH
    When the caller invokes readFromTokenWatchList()
    Then the parsed blob is returned with schema defaults applied where fields are missing

  Scenario: read with invalid stored data
    Given the stored value has the wrong shape (e.g. non-string asset entries, or version !== 1)
    When the caller invokes readFromTokenWatchList()
    Then the promise rejects (no silently-malformed data reaches callers)

  Scenario: write with a valid blob
    Given a valid WatchlistBlob
    When the caller invokes writeToTokenWatchList(blob)
    Then StorageWrapper.setItem is called with WATCHLIST_STORAGE_PATH and the JSON-stringified blob

  Scenario: write with an invalid blob
    Given a malformed blob (wrong asset type, wrong version, or wrong shape)
    When the caller invokes writeToTokenWatchList(blob)
    Then the promise rejects and StorageWrapper.setItem is NOT called

Screenshots/Recordings

Before

N/A — new utility module, no UI.

After

N/A — new utility module, no UI.

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

Not applicable — internal storage helper, no perf-critical UI path or new in-app surface.

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.
Open in Web Open in Cursor 

Adds initial storage integration for the WatchList feature (ASSETS-3115),
backed by the existing MMKV-based StorageWrapper. The AUS SDK is not yet
available, so this local-device implementation is intended to be swapped
out for the SDK once it ships, while preserving the same async API and
schema validation contract.

- Define WatchlistBlobSchema with @metamask/superstruct
  (array of asset strings + version literal 1, both defaulted).
- Export WatchlistBlob type inferred from the schema.
- Export WATCHLIST_STORAGE_PATH ('watchlistV1.tokens') and EMPTY_BLOB.
- readFromTokenWatchList: returns EMPTY_BLOB when storage is empty,
  otherwise parses JSON and validates through the schema before
  returning to the caller (applying defaults for missing fields).
- writeToTokenWatchList: validates the input blob through the schema
  before serializing and writing, so malformed data never reaches storage.

Tests cover empty storage, schema defaults, valid round-trips, and
invalid-input rejection for both read and write paths.
@github-actions
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.

@github-actions github-actions Bot added the pr-not-ready-for-e2e Skip E2E and block merging. Remove this label once the PR is ready to run the E2E tests. label May 18, 2026
@Prithpal-Sooriya Prithpal-Sooriya added the no-changelog no-changelog Indicates no external facing user changes, therefore no changelog documentation needed label May 18, 2026
Comment on lines +37 to +39
const raw = await StorageWrapper.getItem(WATCHLIST_STORAGE_PATH);
if (!raw) return EMPTY_BLOB;
return create(JSON.parse(raw), WatchlistBlobSchema);
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.

Satisfactory, as this will be replaced by SDK changes:
MetaMask/core#8836

Comment on lines +15 to +18
const WatchlistBlobSchema = object({
assets: defaulted(array(string()), () => []),
version: defaulted(literal(1), () => 1 as const),
});
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.

SDK will offer the schema that we can import:
MetaMask/core#8836

For now to keep unblocked, we can continue to use this.

Comment thread app/components/UI/assets/watchlist/storage.test.ts Outdated
Comment thread app/components/UI/assets/watchlist/storage.test.ts Outdated
Address review feedback on ASSETS-3115: convert the three
readFromTokenWatchList 'throws when ...' tests into a single it.each
table parameterized on the stored value. Test descriptions are
preserved verbatim so test output is unchanged.
Address review feedback on ASSETS-3115: convert the two
readFromTokenWatchList 'applies ... defaults ...' tests into a single
it.each table parameterized on the stored value and expected result.
Test descriptions are preserved verbatim so test output is unchanged.
Comment thread app/components/UI/assets/watchlist/storage.test.ts Outdated
Address review feedback on ASSETS-3115: convert the three
writeToTokenWatchList 'rejects and does not write ...' tests into a
single it.each table parameterized on the invalid input. Test
descriptions are preserved verbatim so test output is unchanged.
@Prithpal-Sooriya Prithpal-Sooriya marked this pull request as ready for review May 18, 2026 17:09
@Prithpal-Sooriya Prithpal-Sooriya requested a review from a team as a code owner May 18, 2026 17:09
@Prithpal-Sooriya Prithpal-Sooriya removed the pr-not-ready-for-e2e Skip E2E and block merging. Remove this label once the PR is ready to run the E2E tests. label May 18, 2026
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8212ace. Configure here.

Comment thread app/components/UI/assets/watchlist/storage.ts
@Prithpal-Sooriya Prithpal-Sooriya added no-changelog no-changelog Indicates no external facing user changes, therefore no changelog documentation needed pr-not-ready-for-e2e Skip E2E and block merging. Remove this label once the PR is ready to run the E2E tests. and removed no-changelog no-changelog Indicates no external facing user changes, therefore no changelog documentation needed pr-not-ready-for-e2e Skip E2E and block merging. Remove this label once the PR is ready to run the E2E tests. labels May 18, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: None (no tests recommended)
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: low
  • AI Confidence: 95%
click to see 🤖 AI reasoning details

E2E Test Selection:
The PR introduces two brand new files:

  1. app/components/UI/assets/watchlist/storage.ts - A new watchlist storage utility module providing readFromTokenWatchList() and writeToTokenWatchList() functions backed by StorageWrapper. It uses @metamask/superstruct for schema validation.

  2. app/components/UI/assets/watchlist/storage.test.ts - Unit tests for the above module.

Key findings from investigation:

  • These are entirely NEW files — no existing code imports or depends on them yet (grep confirmed zero references to readFromTokenWatchList, writeToTokenWatchList, or WATCHLIST_STORAGE_PATH outside the new files themselves)
  • The watchlist directory only contains these two new files
  • No existing E2E tests cover this new functionality
  • The changes do not modify any shared components (navigation, modals, confirmations, browser, controllers, Engine, etc.)
  • The code is explicitly noted as a temporary placeholder until Account Universal Storage (AUS) SDK is ready
  • No user-facing flows are changed — this is purely a new isolated storage utility layer

Since no existing functionality is modified and no existing E2E tests cover this new isolated utility, there are no E2E test tags that need to run. The unit tests in the PR itself provide adequate coverage for this new module.

Performance Test Selection:
The changes introduce a new isolated storage utility module for a token watchlist. No UI rendering, list components, data loading pipelines, or critical user flows are affected. The new functions are not yet consumed by any component, so there is no performance impact to measure.

View GitHub Actions results

@sonarqubecloud
Copy link
Copy Markdown

@Prithpal-Sooriya Prithpal-Sooriya added this pull request to the merge queue May 18, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 18, 2026
@Prithpal-Sooriya Prithpal-Sooriya added this pull request to the merge queue May 18, 2026
Merged via the queue into main with commit 804e125 May 18, 2026
151 of 158 checks passed
@Prithpal-Sooriya Prithpal-Sooriya deleted the cursor/assets-3115-watchlist-storage-8eea branch May 18, 2026 20:16
@github-actions github-actions Bot locked and limited conversation to collaborators May 18, 2026
@metamaskbotv2 metamaskbotv2 Bot added the release-7.79.0 Issue or pull request that will be included in release 7.79.0 label May 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

no-changelog no-changelog Indicates no external facing user changes, therefore no changelog documentation needed release-7.79.0 Issue or pull request that will be included in release 7.79.0 size-M team-assets

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants