Skip to content

Conversation

@Kriys94
Copy link
Contributor

@Kriys94 Kriys94 commented Jan 21, 2026

Explanation

Current State

The current asset management in MetaMask is fragmented across multiple controllers with different patterns for EVM vs non-EVM chains. Each chain type (Ethereum, Solana, Bitcoin, Tron) has separate logic paths, making it difficult to maintain consistency and add new chain support.

Solution

This PR introduces a complete rewrite of the AssetsController with a middleware architecture for unified asset management across all blockchain networks (EVM and non-EVM). The new architecture provides:

  • Unified asset management for all chains using CAIP-19 identifiers
  • Pluggable data source architecture with automatic priority-based fallback:
    1. BackendWebsocketDataSource - Real-time push (highest priority)
    2. AccountsApiDataSource - HTTP polling fallback
    3. SnapDataSource - Solana/Bitcoin/Tron via Snaps
    4. RpcDataSource - Direct RPC queries (lowest priority)
  • Middleware pipeline for data enrichment: Detection → Token metadata → Price
  • Lifecycle management - Auto start/stop on app open/close and keyring lock/unlock
  • Custom assets support - Per-account custom token storage

Architecture

┌──────────────────────────────────────────────────────────────────────────────┐
│                              AssetsController                                 │
├──────────────────────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐          │
│  │   State     │  │Subscriptions│  │  Middleware │  │   Events    │          │
│  │  Manager    │  │   Manager   │  │    Chain    │  │  Publisher  │          │
│  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘          │
├──────────────────────────────────────────────────────────────────────────────┤
│                                                                               │
│        Fetch Stack                          Event Stack                       │
│  ┌───────────────────┐               ┌───────────────────┐                   │
│  │    AccountsAPI    │               │                   │                   │
│  ├───────────────────┤               │                   │                   │
│  │       Snap        │               │                   │                   │
│  ├───────────────────┤               │                   │                   │
│  │       RPC         │               │                   │                   │
│  ├───────────────────┤               ├───────────────────┤                   │
│  │     Detection     │               │     Detection     │                   │
│  ├───────────────────┤               ├───────────────────┤                   │
│  │       Token       │               │       Token       │                   │
│  ├───────────────────┤               ├───────────────────┤                   │
│  │       Price       │               │       Price       │                   │
│  └───────────────────┘               └───────────────────┘                   │
│                                                                               │
│  On-demand data fetch                Process incoming updates                 │
│  (forceUpdate: true)                 (enrichment only)                        │
│                                                                               │
│  Subscription Sources: BackendWebsocket, AccountsApi, Snap, RPC              │
│  Push updates via: AssetsController:assetsUpdate action                      │
└──────────────────────────────────────────────────────────────────────────────┘

New Files

File Purpose
src/AssetsController.ts Complete rewrite with middleware architecture
src/types.ts Comprehensive CAIP-19 type definitions
src/README.md Architecture documentation with diagrams
src/data-sources/AbstractDataSource.ts Base class for data sources
src/data-sources/BackendWebsocketDataSource.ts Real-time WebSocket updates
src/data-sources/AccountsApiDataSource.ts HTTP polling fallback
src/data-sources/SnapDataSource.ts Solana/Bitcoin/Tron snap handler
src/data-sources/RpcDataSource.ts Direct RPC fallback
src/data-sources/DetectionMiddleware.ts Identifies assets without metadata
src/data-sources/TokenDataSource.ts Token metadata enrichment
src/data-sources/PriceDataSource.ts USD price fetching
src/data-sources/initDataSources.ts Initialization helpers

State Structure

{
  assetsMetadata: {
    "eip155:1/slip44:60": { type: "native", symbol: "ETH", decimals: 18, ... },
    "eip155:1/erc20:0xA0b8...": { type: "erc20", symbol: "USDC", decimals: 6, ... },
  },
  assetsBalance: {
    "account-uuid-1": {
      "eip155:1/slip44:60": { amount: "1000000000000000000" },
    },
  },
  customAssets: {
    "account-uuid-1": ["eip155:1/erc20:0x..."],
  },
}

Published Events

AssetsController:balanceChanged - When asset balances change
AssetsController:priceChanged - When prices update
AssetsController:assetsDetected - When new assets without metadata are found

References

Checklist

[ ] I've updated the test suite for new or updated code as appropriate
[x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
[x] 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

Implements a unified, middleware-based AssetsController with pluggable data sources and lifecycle-aware subscriptions for multi-chain asset management.

  • Introduces src/AssetsController.ts with request/response middleware (fetch stack and event stack), Messenger-integrated actions/events, state (assetsMetadata, assetsBalance, customAssets), ID normalization, and app/keyring lifecycle handling
  • Adds data sources: BackendWebsocketDataSource (real-time), AccountsApiDataSource (HTTP), RpcDataSource (RPC), and PriceDataSource (spot price polling); chain assignment/prioritization and subscription management included
  • Exposes APIs: getAssets, getAssetsBalance, getAssetMetadata, getAssetsPrice, addCustomAsset/removeCustomAsset/getCustomAssets; publishes balanceChanged, priceChanged, assetsDetected
  • Adds comprehensive docs (src/README.md) and tests (AssetsController.test.ts); updates deps and lowers Jest coverage thresholds for initial iteration

Written by Cursor Bugbot for commit aae01b4. This will update automatically on new commits. Configure here.

@Kriys94 Kriys94 requested review from a team as code owners January 21, 2026 10:40
@Kriys94 Kriys94 force-pushed the feature/assets-controller branch from 71bafa0 to 1970164 Compare January 21, 2026 10:43
cursor[bot]

This comment was marked as outdated.

@Kriys94 Kriys94 force-pushed the feature/assets-controller branch 2 times, most recently from 0b59481 to b233e63 Compare January 21, 2026 12:35
cursor[bot]

This comment was marked as outdated.

@Kriys94 Kriys94 force-pushed the feature/assets-controller branch 2 times, most recently from f6b72f5 to bba023a Compare January 21, 2026 12:56
cursor[bot]

This comment was marked as outdated.

@Kriys94 Kriys94 force-pushed the feature/assets-controller branch from bba023a to c5056a4 Compare January 21, 2026 15:59
cursor[bot]

This comment was marked as outdated.

@Kriys94 Kriys94 force-pushed the feature/assets-controller branch from c5056a4 to 8622b38 Compare January 21, 2026 16:12
cursor[bot]

This comment was marked as outdated.

@Kriys94 Kriys94 force-pushed the feature/assets-controller branch from 8622b38 to e7c0346 Compare January 21, 2026 16:33
cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

- Add AssetsController with data source architecture
- Add AbstractDataSource base class for data sources
- Add AccountsApiDataSource, BackendWebsocketDataSource, PriceDataSource, RpcDataSource, SnapDataSource, TokenDataSource
- Add DetectionMiddleware for token detection
- Update token-selectors in assets-controllers
Copy link

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

displayName,

// An object that configures minimum threshold enforcement for coverage results
// TODO: Increase thresholds as more tests are added
Copy link
Contributor

Choose a reason for hiding this comment

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

let me create a task for this todo here


// ============================================================================
// ABSTRACT DATA SOURCE
// ============================================================================
Copy link
Contributor

Choose a reason for hiding this comment

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

no need this comments , usually we just add js doc , we ca clean later

@salimtb salimtb added this pull request to the merge queue Jan 22, 2026
Merged via the queue into main with commit 9dc34f6 Jan 22, 2026
298 checks passed
@salimtb salimtb deleted the feature/assets-controller branch January 22, 2026 08:36
@salimtb salimtb mentioned this pull request Jan 23, 2026
4 tasks
github-merge-queue bot pushed a commit that referenced this pull request Jan 23, 2026
## Explanation

**Current state:**
The `RpcDataSource` was using manual `setInterval` for polling balance
updates and token detection. This approach had several limitations:
- Polling logic was tightly coupled within `RpcDataSource`, making it
harder to test and maintain
- Balance fetching and token detection shared the same polling mechanism
despite having different optimal intervals (30s vs 3 minutes)
- The polling intervals were not configurable from the initialization
layer

**Solution:**
This PR refactors the polling architecture by delegating polling
responsibilities to the individual services:

1. **`BalanceFetcher` and `TokenDetector` now extend
`StaticIntervalPollingControllerOnly`** - Each service manages its own
polling lifecycle with independent intervals, enabling fine-grained
control over polling behavior.

2. **`RpcDataSource` simplified to an orchestrator** - Instead of
managing polling directly, `RpcDataSource` now:
- Starts/stops polling through `subscribe`/`unsubscribe` by calling the
services' `startPolling`/`stopPollingByPollingToken` methods
- Receives updates via callbacks (`setOnBalanceUpdate`,
`setOnDetectionUpdate`)
   - Manages polling tokens for proper cleanup

3. **Configurable polling intervals** - Added `RpcDataSourceConfig` type
to `initDataSources.ts` allowing consumers to configure:
   - `balanceInterval` (default: 30s)
   - `detectionInterval` (default: 180s / 3 min)
   - `tokenDetectionEnabled` (default: false)
   - `timeout` (default: 10s)

4. **Improved test coverage** - All modified files now have near 100%
test coverage using the `withController` pattern for consistent test
setup and teardown.

## References

- Related to #7683 (TokenDetector service)
- Related to #7684 (BalanceFetcher service)
- Builds on #7685 (AssetsController rewrite)

## Checklist

- [x] I've updated the test suite for new or updated code as appropriate
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've communicated my changes to consumers by [updating changelogs
for packages I've
changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [ ] I've introduced [breaking
changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md)
in this PR and have prepared draft pull requests for clients and
consumer packages to resolve them

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Modernizes asset polling and configuration across the assets
controller.
> 
> - **RpcDataSource**: Reworked as an orchestrator; delegates polling to
services, adds `caipChainIdToHex`, multicall provider getter/cache,
improved active-chain handling, and updates `assetsMiddleware` behavior
> - **Services**: `BalanceFetcher` and `TokenDetector` now extend
`StaticIntervalPollingControllerOnly`; expose start/stop polling,
interval getters/setters, and update callbacks to `AssetsController`
> - **Configuration**: Adds `RpcDataSourceConfig` in `initDataSources`
to set `balanceInterval`, `detectionInterval`, `tokenDetectionEnabled`,
and `timeout`; messengers wired for
`AssetsController`/`TokenList`/`NetworkEnablement` state access
> - **Tests**: Large new test suites for `RpcDataSource`,
`BalanceFetcher`, and `initDataSources` covering subscriptions,
callbacks, error paths, and provider caching
> - **Meta**: Updates changelog; adds dependencies
(`@metamask/polling-controller`, controller/network utils)
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
4186208. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
@salimtb salimtb mentioned this pull request Jan 23, 2026
4 tasks
github-merge-queue bot pushed a commit that referenced this pull request Jan 23, 2026
## Explanation

This PR adds comprehensive unit tests for the new
`@metamask/assets-controller` package to ensure code quality and
maintainability.

**Current state:** The assets-controller package was recently introduced
with a middleware-based architecture for unified asset management across
all blockchain networks. However, test coverage was limited, leaving
critical code paths untested.

**Changes in this PR:**
- Added unit tests for all data sources:
- `AccountsApiDataSource` (18 tests, includes timer cleanup fix using
`.unref()` to prevent Jest worker process hanging)
  - `BackendWebsocketDataSource` (19 tests)
  - `PriceDataSource` (29 tests)
  - `TokenDataSource` (20 tests)
  - `SnapDataSource` (47 tests)
- Added unit tests for `DetectionMiddleware` (13 tests, 100% coverage)
- Expanded unit tests for `AssetsController` (42 tests, coverage
increased from 33% to 81%)

**Test architecture patterns used:**
- `setupController` / `withController` helper functions for consistent
test setup
- Proper messenger mocking with `MOCK_ANY_NAMESPACE`
- Test cleanup to prevent resource leaks
- `it.each` for parameterized tests where applicable
- Flat test structure avoiding deeply nested describe blocks

## References

- Related to #7685 (AssetsController middleware architecture)
- Related to #7587 (Initial assets-controller release)

## Checklist

- [x] I've updated the test suite for new or updated code as appropriate
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've communicated my changes to consumers by [updating changelogs
for packages I've
changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [ ] I've introduced [breaking
changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md)
in this PR and have prepared draft pull requests for clients and
consumer packages to resolve them

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Significantly increases test coverage and validates core behaviors
across the new middleware-based assets stack.
> 
> - Add extensive unit tests for `AssetsController`,
`DetectionMiddleware`, and data sources: `AbstractDataSource`,
`AccountsApiDataSource`, `BackendWebsocketDataSource`,
`PriceDataSource`, `RpcDataSource`, and `SnapDataSource`
> - Cover subscriptions, middleware chaining, lifecycle/events
(app/keyring/network), WebSocket handling, pricing polls, and snap-based
balances
> - Update `RpcDataSource` to import `BalanceFetcher`,
`MulticallClient`, and `TokenDetector` from `evm-rpc-services` (was
`rpc-datasource`)
> - Update `CHANGELOG.md` to note test additions
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
9b08b0a. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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.

4 participants