Skip to content

feat(authenticated-user-storage): add watchlist support#8836

Open
Prithpal-Sooriya wants to merge 1 commit into
mainfrom
feat/aus-add-watchlist
Open

feat(authenticated-user-storage): add watchlist support#8836
Prithpal-Sooriya wants to merge 1 commit into
mainfrom
feat/aus-add-watchlist

Conversation

@Prithpal-Sooriya
Copy link
Copy Markdown
Contributor

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

Explanation

https://consensyssoftware.atlassian.net/browse/ASSETS-3219

Add watchlist support to the AUS SDK.

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Medium Risk
Adds a new persisted user-data domain with new network requests, caching, and runtime validation; risk is mainly around API contract/validation mismatches and cache invalidation behavior.

Overview
Adds assets watchlist support to the authenticated-user-storage SDK via new getAssetsWatchlist (returns null on 404) and setAssetsWatchlist methods plus corresponding messenger actions.

Introduces an AssetsWatchlistBlob schema/type and exports ASSETS_WATCHLIST_MAX_ASSETS (100), enforcing the cap on writes via superstruct while keeping reads lenient. Updates tests/fixtures/mocks to cover headers, error handling, caching, and invalidation, and refreshes README/CHANGELOG to document the new domain.

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

@Prithpal-Sooriya Prithpal-Sooriya force-pushed the feat/aus-add-watchlist branch 4 times, most recently from e4e6ad4 to c4d9e42 Compare May 15, 2026 18:04
@Prithpal-Sooriya Prithpal-Sooriya force-pushed the feat/aus-add-watchlist branch from c4d9e42 to bd2b5db Compare May 15, 2026 18:20
@Prithpal-Sooriya Prithpal-Sooriya marked this pull request as ready for review May 15, 2026 18:22
@Prithpal-Sooriya Prithpal-Sooriya requested review from a team as code owners May 15, 2026 18:22
pull Bot pushed a commit to Dustin4444/metamask-mobile that referenced this pull request May 18, 2026
…SETS-3115] (MetaMask#30337)

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## **Description**

Adds the initial **Storage Integration** layer for the WatchList
feature, as scoped by
[ASSETS-3115](https://consensyssoftware.atlassian.net/browse/ASSETS-3115)
and the [WatchList Tech
Spec](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/401467637802/WatchList+Tech+Spec+-+Technical+Breakdown+Tasks#block-ca5ff63649b3).

The [AUS SDK changes](MetaMask/core#8836) 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.parse`s 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.stringify`s 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](https://consensyssoftware.atlassian.net/browse/ASSETS-3115)
Parent story: [ASSETS-2912 — \[Mobile\]\[WatchList\] Allow users to add,
remove, and view their watchlisted
tokens](https://consensyssoftware.atlassian.net/browse/ASSETS-2912)
Tech Spec: [WatchList Tech Spec — Technical Breakdown
Tasks](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/401467637802/WatchList+Tech+Spec+-+Technical+Breakdown+Tasks#block-ca5ff63649b3)
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:

```gherkin
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**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable (`storage.test.ts`, 17 cases,
all passing)
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

#### 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.

<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-0ed5d6b3-936f-4ff8-aa9f-c7ab0f588eea"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-0ed5d6b3-936f-4ff8-aa9f-c7ab0f588eea"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>



[ASSETS-3115]:
https://consensyssoftware.atlassian.net/browse/ASSETS-3115?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
[ASSETS-3115]:
https://consensyssoftware.atlassian.net/browse/ASSETS-3115?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
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.

1 participant