@metamask-previews/assets-controllers 99.3.2-preview-1a74d12 → 99.3.2-preview-254d7df
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -8
- package/dist/MultichainAssetsController/MultichainAssetsController.cjs +4 -92
- package/dist/MultichainAssetsController/MultichainAssetsController.cjs.map +1 -1
- package/dist/MultichainAssetsController/MultichainAssetsController.d.cts +0 -9
- package/dist/MultichainAssetsController/MultichainAssetsController.d.cts.map +1 -1
- package/dist/MultichainAssetsController/MultichainAssetsController.d.mts +0 -9
- package/dist/MultichainAssetsController/MultichainAssetsController.d.mts.map +1 -1
- package/dist/MultichainAssetsController/MultichainAssetsController.mjs +3 -91
- package/dist/MultichainAssetsController/MultichainAssetsController.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,14 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
### Added
|
|
11
|
-
|
|
12
|
-
- Add Blockaid token security scanning to `MultichainAssetsController` to filter out spam, malicious, and warning tokens during automatic asset detection ([#TBD](https://github.com/MetaMask/core/pull/7923))
|
|
13
|
-
- Tokens with `assetNamespace` of "token" (e.g. SPL tokens) are scanned via the Blockaid bulk token scan API
|
|
14
|
-
- Only tokens with a `Benign` result are kept; native assets (e.g. `slip44`) are not scanned
|
|
15
|
-
- The filter fails open: if the API is unreachable or returns an error, all tokens are kept
|
|
16
|
-
- Filtering applies to account-added and asset-list-updated events; `addAssets` (curated list) is not filtered
|
|
17
|
-
|
|
18
10
|
## [99.3.2]
|
|
19
11
|
|
|
20
12
|
### Changed
|
|
@@ -10,11 +10,10 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
10
10
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
11
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
12
|
};
|
|
13
|
-
var _MultichainAssetsController_instances, _MultichainAssetsController_snaps, _MultichainAssetsController_controllerOperationMutex, _MultichainAssetsController_handleAccountAssetListUpdatedEvent, _MultichainAssetsController_handleOnAccountAddedEvent, _MultichainAssetsController_registerMessageHandlers, _MultichainAssetsController_isAssetIgnored, _MultichainAssetsController_handleAccountAssetListUpdated, _MultichainAssetsController_isNonEvmAccount, _MultichainAssetsController_handleOnAccountAdded, _MultichainAssetsController_handleOnAccountRemovedEvent, _MultichainAssetsController_refreshAssetsMetadata, _MultichainAssetsController_updateAssetsMetadata, _MultichainAssetsController_getAssetSnaps, _MultichainAssetsController_getAssetSnapFor, _MultichainAssetsController_getAllSnaps, _MultichainAssetsController_getSnapsPermissions, _MultichainAssetsController_getAssetsMetadataFrom,
|
|
13
|
+
var _MultichainAssetsController_instances, _MultichainAssetsController_snaps, _MultichainAssetsController_controllerOperationMutex, _MultichainAssetsController_handleAccountAssetListUpdatedEvent, _MultichainAssetsController_handleOnAccountAddedEvent, _MultichainAssetsController_registerMessageHandlers, _MultichainAssetsController_isAssetIgnored, _MultichainAssetsController_handleAccountAssetListUpdated, _MultichainAssetsController_isNonEvmAccount, _MultichainAssetsController_handleOnAccountAdded, _MultichainAssetsController_handleOnAccountRemovedEvent, _MultichainAssetsController_refreshAssetsMetadata, _MultichainAssetsController_updateAssetsMetadata, _MultichainAssetsController_getAssetSnaps, _MultichainAssetsController_getAssetSnapFor, _MultichainAssetsController_getAllSnaps, _MultichainAssetsController_getSnapsPermissions, _MultichainAssetsController_getAssetsMetadataFrom, _MultichainAssetsController_getAssetsList, _MultichainAssetsController_getClient, _MultichainAssetsController_assertControllerMutexIsLocked, _MultichainAssetsController_withControllerLock;
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.MultichainAssetsController = exports.getDefaultMultichainAssetsControllerState =
|
|
15
|
+
exports.MultichainAssetsController = exports.getDefaultMultichainAssetsControllerState = void 0;
|
|
16
16
|
const base_controller_1 = require("@metamask/base-controller");
|
|
17
|
-
const controller_utils_1 = require("@metamask/controller-utils");
|
|
18
17
|
const keyring_api_1 = require("@metamask/keyring-api");
|
|
19
18
|
const keyring_snap_client_1 = require("@metamask/keyring-snap-client");
|
|
20
19
|
const snaps_utils_1 = require("@metamask/snaps-utils");
|
|
@@ -22,19 +21,6 @@ const utils_1 = require("@metamask/utils");
|
|
|
22
21
|
const async_mutex_1 = require("async-mutex");
|
|
23
22
|
const utils_2 = require("./utils.cjs");
|
|
24
23
|
const controllerName = 'MultichainAssetsController';
|
|
25
|
-
// Blockaid token security scanning constants
|
|
26
|
-
const SECURITY_ALERTS_BASE_URL = 'https://security-alerts.api.cx.metamask.io';
|
|
27
|
-
const TOKEN_BULK_SCANNING_ENDPOINT = '/token/scan-bulk';
|
|
28
|
-
/**
|
|
29
|
-
* Result type from Blockaid token security scan.
|
|
30
|
-
*/
|
|
31
|
-
var BlockaidResultType;
|
|
32
|
-
(function (BlockaidResultType) {
|
|
33
|
-
BlockaidResultType["Benign"] = "Benign";
|
|
34
|
-
BlockaidResultType["Spam"] = "Spam";
|
|
35
|
-
BlockaidResultType["Warning"] = "Warning";
|
|
36
|
-
BlockaidResultType["Malicious"] = "Malicious";
|
|
37
|
-
})(BlockaidResultType || (exports.BlockaidResultType = BlockaidResultType = {}));
|
|
38
24
|
/**
|
|
39
25
|
* Constructs the default {@link MultichainAssetsController} state. This allows
|
|
40
26
|
* consumers to provide a partial state object when initializing the controller
|
|
@@ -207,11 +193,9 @@ async function _MultichainAssetsController_handleAccountAssetListUpdated(event)
|
|
|
207
193
|
const existing = this.state.accountsAssets[accountId] || [];
|
|
208
194
|
// In case accountsAndAssetsToUpdate event is fired with "added" assets that already exist, we don't want to add them again
|
|
209
195
|
// Also filter out ignored assets
|
|
210
|
-
const
|
|
196
|
+
const filteredToBeAddedAssets = added.filter((asset) => !existing.includes(asset) &&
|
|
211
197
|
(0, utils_1.isCaipAssetType)(asset) &&
|
|
212
198
|
!__classPrivateFieldGet(this, _MultichainAssetsController_instances, "m", _MultichainAssetsController_isAssetIgnored).call(this, asset, accountId));
|
|
213
|
-
// Filter out tokens flagged by Blockaid as non-benign
|
|
214
|
-
const filteredToBeAddedAssets = await __classPrivateFieldGet(this, _MultichainAssetsController_instances, "m", _MultichainAssetsController_filterBlockaidSpamTokens).call(this, preFilteredToBeAddedAssets);
|
|
215
199
|
// In case accountsAndAssetsToUpdate event is fired with "removed" assets that don't exist, we don't want to remove them
|
|
216
200
|
const filteredToBeRemovedAssets = removed.filter((asset) => existing.includes(asset) && (0, utils_1.isCaipAssetType)(asset));
|
|
217
201
|
if (filteredToBeAddedAssets.length > 0 ||
|
|
@@ -267,8 +251,7 @@ async function _MultichainAssetsController_handleOnAccountAdded(account) {
|
|
|
267
251
|
__classPrivateFieldGet(this, _MultichainAssetsController_instances, "m", _MultichainAssetsController_assertControllerMutexIsLocked).call(this);
|
|
268
252
|
// Get assets list
|
|
269
253
|
if (account.metadata.snap) {
|
|
270
|
-
const
|
|
271
|
-
const assets = await __classPrivateFieldGet(this, _MultichainAssetsController_instances, "m", _MultichainAssetsController_filterBlockaidSpamTokens).call(this, allAssets);
|
|
254
|
+
const assets = await __classPrivateFieldGet(this, _MultichainAssetsController_instances, "m", _MultichainAssetsController_getAssetsList).call(this, account.id, account.metadata.snap.id);
|
|
272
255
|
await __classPrivateFieldGet(this, _MultichainAssetsController_instances, "m", _MultichainAssetsController_refreshAssetsMetadata).call(this, assets);
|
|
273
256
|
this.update((state) => {
|
|
274
257
|
state.accountsAssets[account.id] = assets;
|
|
@@ -413,77 +396,6 @@ async function _MultichainAssetsController_getAssetsMetadataFrom(assets, snapId)
|
|
|
413
396
|
console.error(error);
|
|
414
397
|
return undefined;
|
|
415
398
|
}
|
|
416
|
-
}, _MultichainAssetsController_scanBlockaidTokens =
|
|
417
|
-
/**
|
|
418
|
-
* Calls the Blockaid token bulk scanning endpoint for a given chain and
|
|
419
|
-
* set of token addresses.
|
|
420
|
-
*
|
|
421
|
-
* @param chain - The chain name for the Blockaid API (e.g. "solana").
|
|
422
|
-
* @param tokens - The token addresses to scan.
|
|
423
|
-
* @returns The scan response, or null if the request fails or times out.
|
|
424
|
-
*/
|
|
425
|
-
async function _MultichainAssetsController_scanBlockaidTokens(chain, tokens) {
|
|
426
|
-
try {
|
|
427
|
-
return await (0, controller_utils_1.handleFetch)(`${SECURITY_ALERTS_BASE_URL}${TOKEN_BULK_SCANNING_ENDPOINT}`, {
|
|
428
|
-
method: 'POST',
|
|
429
|
-
headers: {
|
|
430
|
-
Accept: 'application/json',
|
|
431
|
-
'Content-Type': 'application/json',
|
|
432
|
-
},
|
|
433
|
-
body: JSON.stringify({ chain, tokens }),
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
catch (error) {
|
|
437
|
-
console.error('Blockaid token scan failed:', error);
|
|
438
|
-
return null;
|
|
439
|
-
}
|
|
440
|
-
}, _MultichainAssetsController_filterBlockaidSpamTokens =
|
|
441
|
-
/**
|
|
442
|
-
* Filters out tokens flagged as non-benign by Blockaid. Only tokens with
|
|
443
|
-
* an `assetNamespace` of "token" are scanned (native assets like slip44 are
|
|
444
|
-
* passed through unfiltered). If the API call fails, all tokens are kept.
|
|
445
|
-
*
|
|
446
|
-
* @param assets - The CAIP asset type list to filter.
|
|
447
|
-
* @returns The filtered list with malicious/spam/warning tokens removed.
|
|
448
|
-
*/
|
|
449
|
-
async function _MultichainAssetsController_filterBlockaidSpamTokens(assets) {
|
|
450
|
-
// Group scannable token assets by chain namespace
|
|
451
|
-
const tokensByChain = {};
|
|
452
|
-
for (const asset of assets) {
|
|
453
|
-
const { assetNamespace, assetReference, chain } = (0, utils_1.parseCaipAssetType)(asset);
|
|
454
|
-
// Only scan fungible token assets (e.g. SPL tokens), skip native (slip44)
|
|
455
|
-
if (assetNamespace === 'token') {
|
|
456
|
-
const chainName = chain.namespace;
|
|
457
|
-
if (!tokensByChain[chainName]) {
|
|
458
|
-
tokensByChain[chainName] = [];
|
|
459
|
-
}
|
|
460
|
-
tokensByChain[chainName].push({ asset, address: assetReference });
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
// If there are no token assets to scan, return as-is
|
|
464
|
-
if (Object.keys(tokensByChain).length === 0) {
|
|
465
|
-
return assets;
|
|
466
|
-
}
|
|
467
|
-
// Build a set of assets to reject (non-benign tokens)
|
|
468
|
-
const rejectedAssets = new Set();
|
|
469
|
-
for (const [chainName, tokenEntries] of Object.entries(tokensByChain)) {
|
|
470
|
-
const addresses = tokenEntries.map((entry) => entry.address);
|
|
471
|
-
const scanResponse = await __classPrivateFieldGet(this, _MultichainAssetsController_instances, "m", _MultichainAssetsController_scanBlockaidTokens).call(this, chainName, addresses);
|
|
472
|
-
if (!scanResponse?.results) {
|
|
473
|
-
// If the scan fails, keep all tokens (fail open)
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
for (const entry of tokenEntries) {
|
|
477
|
-
const result = scanResponse.results[entry.address];
|
|
478
|
-
// Reject the token only if we have a definitive non-benign result
|
|
479
|
-
if (result?.result_type &&
|
|
480
|
-
result.result_type !== BlockaidResultType.Benign) {
|
|
481
|
-
rejectedAssets.add(entry.asset);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
// Filter while preserving original order
|
|
486
|
-
return assets.filter((asset) => !rejectedAssets.has(asset));
|
|
487
399
|
}, _MultichainAssetsController_getAssetsList =
|
|
488
400
|
/**
|
|
489
401
|
* Get assets list for an account
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultichainAssetsController.cjs","sourceRoot":"","sources":["../../src/MultichainAssetsController/MultichainAssetsController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountsControllerAccountAddedEvent,\n AccountsControllerAccountAssetListUpdatedEvent,\n AccountsControllerAccountRemovedEvent,\n AccountsControllerListMultichainAccountsAction,\n} from '@metamask/accounts-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { handleFetch } from '@metamask/controller-utils';\nimport { isEvmAccountType } from '@metamask/keyring-api';\nimport type {\n AccountAssetListUpdatedEventPayload,\n CaipAssetType,\n CaipAssetTypeOrId,\n} from '@metamask/keyring-api';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport { KeyringClient } from '@metamask/keyring-snap-client';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n GetPermissions,\n PermissionConstraint,\n SubjectPermissions,\n} from '@metamask/permission-controller';\nimport type {\n GetAllSnaps,\n HandleSnapRequest,\n} from '@metamask/snaps-controllers';\nimport type { FungibleAssetMetadata, Snap, SnapId } from '@metamask/snaps-sdk';\nimport { HandlerType } from '@metamask/snaps-utils';\nimport { isCaipAssetType, parseCaipAssetType } from '@metamask/utils';\nimport type { CaipChainId } from '@metamask/utils';\nimport type { Json, JsonRpcRequest } from '@metamask/utils';\nimport type { MutexInterface } from 'async-mutex';\nimport { Mutex } from 'async-mutex';\n\nimport { getChainIdsCaveat } from './utils';\n\nconst controllerName = 'MultichainAssetsController';\n\n// Blockaid token security scanning constants\nconst SECURITY_ALERTS_BASE_URL = 'https://security-alerts.api.cx.metamask.io';\nconst TOKEN_BULK_SCANNING_ENDPOINT = '/token/scan-bulk';\n\n/**\n * Result type from Blockaid token security scan.\n */\nexport enum BlockaidResultType {\n Benign = 'Benign',\n Spam = 'Spam',\n Warning = 'Warning',\n Malicious = 'Malicious',\n}\n\n/**\n * Response shape from the Blockaid token bulk scanning endpoint.\n */\ntype BlockaidTokenScanResponse = {\n results: Record<\n string,\n {\n /* eslint-disable @typescript-eslint/naming-convention */\n result_type: BlockaidResultType;\n malicious_score: string;\n attack_types: Record<string, unknown>;\n /* eslint-enable @typescript-eslint/naming-convention */\n chain: string;\n address: string;\n }\n >;\n};\n\nexport type MultichainAssetsControllerState = {\n assetsMetadata: {\n [asset: CaipAssetType]: FungibleAssetMetadata;\n };\n accountsAssets: { [account: string]: CaipAssetType[] };\n allIgnoredAssets: { [account: string]: CaipAssetType[] };\n};\n\n// Represents the response of the asset snap's onAssetLookup handler\nexport type AssetMetadataResponse = {\n assets: {\n [asset: CaipAssetType]: FungibleAssetMetadata;\n };\n};\n\nexport type MultichainAssetsControllerAccountAssetListUpdatedEvent = {\n type: `${typeof controllerName}:accountAssetListUpdated`;\n payload: AccountsControllerAccountAssetListUpdatedEvent['payload'];\n};\n\n/**\n * Constructs the default {@link MultichainAssetsController} state. This allows\n * consumers to provide a partial state object when initializing the controller\n * and also helps in constructing complete state objects for this controller in\n * tests.\n *\n * @returns The default {@link MultichainAssetsController} state.\n */\nexport function getDefaultMultichainAssetsControllerState(): MultichainAssetsControllerState {\n return { accountsAssets: {}, assetsMetadata: {}, allIgnoredAssets: {} };\n}\n\nexport type MultichainAssetsControllerGetAssetMetadataAction = {\n type: `${typeof controllerName}:getAssetMetadata`;\n handler: MultichainAssetsController['getAssetMetadata'];\n};\n\nexport type MultichainAssetsControllerIgnoreAssetsAction = {\n type: `${typeof controllerName}:ignoreAssets`;\n handler: MultichainAssetsController['ignoreAssets'];\n};\n\nexport type MultichainAssetsControllerAddAssetsAction = {\n type: `${typeof controllerName}:addAssets`;\n handler: MultichainAssetsController['addAssets'];\n};\n\n/**\n * Returns the state of the {@link MultichainAssetsController}.\n */\nexport type MultichainAssetsControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n MultichainAssetsControllerState\n>;\n\n/**\n * Event emitted when the state of the {@link MultichainAssetsController} changes.\n */\nexport type MultichainAssetsControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n MultichainAssetsControllerState\n >;\n\n/**\n * Actions exposed by the {@link MultichainAssetsController}.\n */\nexport type MultichainAssetsControllerActions =\n | MultichainAssetsControllerGetStateAction\n | MultichainAssetsControllerGetAssetMetadataAction\n | MultichainAssetsControllerIgnoreAssetsAction\n | MultichainAssetsControllerAddAssetsAction;\n\n/**\n * Events emitted by {@link MultichainAssetsController}.\n */\nexport type MultichainAssetsControllerEvents =\n | MultichainAssetsControllerStateChangeEvent\n | MultichainAssetsControllerAccountAssetListUpdatedEvent;\n\n/**\n * A function executed within a mutually exclusive lock, with\n * a mutex releaser in its option bag.\n *\n * @param releaseLock - A function to release the lock.\n */\ntype MutuallyExclusiveCallback<Result> = ({\n releaseLock,\n}: {\n releaseLock: MutexInterface.Releaser;\n}) => Promise<Result>;\n\n/**\n * Actions that this controller is allowed to call.\n */\ntype AllowedActions =\n | HandleSnapRequest\n | GetAllSnaps\n | GetPermissions\n | AccountsControllerListMultichainAccountsAction;\n\n/**\n * Events that this controller is allowed to subscribe.\n */\ntype AllowedEvents =\n | AccountsControllerAccountAddedEvent\n | AccountsControllerAccountRemovedEvent\n | AccountsControllerAccountAssetListUpdatedEvent;\n\n/**\n * Messenger type for the MultichainAssetsController.\n */\nexport type MultichainAssetsControllerMessenger = Messenger<\n typeof controllerName,\n MultichainAssetsControllerActions | AllowedActions,\n MultichainAssetsControllerEvents | AllowedEvents\n>;\n\n/**\n * {@link MultichainAssetsController}'s metadata.\n *\n * This allows us to choose if fields of the state should be persisted or not\n * using the `persist` flag; and if they can be sent to Sentry or not, using\n * the `anonymous` flag.\n */\nconst assetsControllerMetadata: StateMetadata<MultichainAssetsControllerState> =\n {\n assetsMetadata: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n accountsAssets: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n allIgnoredAssets: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n };\n\n// TODO: make this controller extends StaticIntervalPollingController and update all assetsMetadata once a day.\n\nexport class MultichainAssetsController extends BaseController<\n typeof controllerName,\n MultichainAssetsControllerState,\n MultichainAssetsControllerMessenger\n> {\n // Mapping of CAIP-2 Chain ID to Asset Snaps.\n #snaps: Record<CaipChainId, Snap[]>;\n\n readonly #controllerOperationMutex = new Mutex();\n\n constructor({\n messenger,\n state = {},\n }: {\n messenger: MultichainAssetsControllerMessenger;\n state?: Partial<MultichainAssetsControllerState>;\n }) {\n super({\n messenger,\n name: controllerName,\n metadata: assetsControllerMetadata,\n state: {\n ...getDefaultMultichainAssetsControllerState(),\n ...state,\n },\n });\n\n this.#snaps = {};\n\n this.messenger.subscribe(\n 'AccountsController:accountAdded',\n async (account) => await this.#handleOnAccountAddedEvent(account),\n );\n this.messenger.subscribe(\n 'AccountsController:accountRemoved',\n async (account) => await this.#handleOnAccountRemovedEvent(account),\n );\n this.messenger.subscribe(\n 'AccountsController:accountAssetListUpdated',\n async (event) => await this.#handleAccountAssetListUpdatedEvent(event),\n );\n\n this.#registerMessageHandlers();\n }\n\n async #handleAccountAssetListUpdatedEvent(\n event: AccountAssetListUpdatedEventPayload,\n ) {\n return this.#withControllerLock(async () =>\n this.#handleAccountAssetListUpdated(event),\n );\n }\n\n async #handleOnAccountAddedEvent(account: InternalAccount) {\n return this.#withControllerLock(async () =>\n this.#handleOnAccountAdded(account),\n );\n }\n\n /**\n * Constructor helper for registering the controller's messaging system\n * actions.\n */\n #registerMessageHandlers() {\n this.messenger.registerActionHandler(\n 'MultichainAssetsController:getAssetMetadata',\n this.getAssetMetadata.bind(this),\n );\n\n this.messenger.registerActionHandler(\n 'MultichainAssetsController:ignoreAssets',\n this.ignoreAssets.bind(this),\n );\n\n this.messenger.registerActionHandler(\n 'MultichainAssetsController:addAssets',\n this.addAssets.bind(this),\n );\n }\n\n /**\n * Returns the metadata for the given asset\n *\n * @param asset - The asset to get metadata for\n * @returns The metadata for the asset or undefined if not found.\n */\n getAssetMetadata(asset: CaipAssetType): FungibleAssetMetadata | undefined {\n return this.state.assetsMetadata[asset];\n }\n\n /**\n * Ignores a batch of assets for a specific account.\n *\n * @param assetsToIgnore - Array of asset IDs to ignore.\n * @param accountId - The account ID to ignore assets for.\n */\n ignoreAssets(assetsToIgnore: CaipAssetType[], accountId: string): void {\n this.update((state) => {\n if (state.accountsAssets[accountId]) {\n state.accountsAssets[accountId] = state.accountsAssets[\n accountId\n ].filter((asset) => !assetsToIgnore.includes(asset));\n }\n\n if (!state.allIgnoredAssets[accountId]) {\n state.allIgnoredAssets[accountId] = [];\n }\n\n const newIgnoredAssets = assetsToIgnore.filter(\n (asset) => !state.allIgnoredAssets[accountId].includes(asset),\n );\n state.allIgnoredAssets[accountId].push(...newIgnoredAssets);\n });\n }\n\n /**\n * Adds multiple assets to the stored asset list for a specific account.\n * All assets must belong to the same chain.\n *\n * @param assetIds - Array of CAIP asset IDs to add (must be from same chain).\n * @param accountId - The account ID to add the assets to.\n * @returns The updated asset list for the account.\n * @throws Error if assets are from different chains.\n */\n async addAssets(\n assetIds: CaipAssetType[],\n accountId: string,\n ): Promise<CaipAssetType[]> {\n if (assetIds.length === 0) {\n return this.state.accountsAssets[accountId] || [];\n }\n\n // Validate that all assets are from the same chain\n const chainIds = new Set(\n assetIds.map((assetId) => parseCaipAssetType(assetId).chainId),\n );\n if (chainIds.size > 1) {\n throw new Error(\n `All assets must belong to the same chain. Found assets from chains: ${Array.from(chainIds).join(', ')}`,\n );\n }\n\n return this.#withControllerLock(async () => {\n // Refresh metadata for all assets\n await this.#refreshAssetsMetadata(assetIds);\n\n const addedAssets: CaipAssetType[] = [];\n\n this.update((state) => {\n // Initialize account assets if it doesn't exist\n if (!state.accountsAssets[accountId]) {\n state.accountsAssets[accountId] = [];\n }\n\n // Add assets if they don't already exist\n for (const assetId of assetIds) {\n if (!state.accountsAssets[accountId].includes(assetId)) {\n state.accountsAssets[accountId].push(assetId);\n addedAssets.push(assetId);\n }\n }\n\n // Remove from ignored list if they exist there (inline logic like EVM)\n if (state.allIgnoredAssets[accountId]) {\n state.allIgnoredAssets[accountId] = state.allIgnoredAssets[\n accountId\n ].filter((asset) => !assetIds.includes(asset));\n\n // Clean up empty arrays\n if (state.allIgnoredAssets[accountId].length === 0) {\n delete state.allIgnoredAssets[accountId];\n }\n }\n });\n\n // Publish event to notify other controllers (balances, rates) about the new assets\n if (addedAssets.length > 0) {\n this.messenger.publish(`${controllerName}:accountAssetListUpdated`, {\n assets: {\n [accountId]: {\n added: addedAssets,\n removed: [],\n },\n },\n });\n }\n\n return this.state.accountsAssets[accountId] || [];\n });\n }\n\n /**\n * Checks if an asset is ignored for a specific account.\n *\n * @param asset - The asset ID to check.\n * @param accountId - The account ID to check for.\n * @returns True if the asset is ignored, false otherwise.\n */\n #isAssetIgnored(asset: CaipAssetType, accountId: string): boolean {\n return this.state.allIgnoredAssets[accountId]?.includes(asset) ?? false;\n }\n\n /**\n * Function to update the assets list for an account\n *\n * @param event - The list of assets to update\n */\n async #handleAccountAssetListUpdated(\n event: AccountAssetListUpdatedEventPayload,\n ) {\n this.#assertControllerMutexIsLocked();\n\n const assetsForMetadataRefresh = new Set<CaipAssetType>([]);\n const accountsAndAssetsToUpdate: AccountAssetListUpdatedEventPayload['assets'] =\n {};\n for (const [accountId, { added, removed }] of Object.entries(\n event.assets,\n )) {\n if (added.length > 0 || removed.length > 0) {\n const existing = this.state.accountsAssets[accountId] || [];\n\n // In case accountsAndAssetsToUpdate event is fired with \"added\" assets that already exist, we don't want to add them again\n // Also filter out ignored assets\n const preFilteredToBeAddedAssets = added.filter(\n (asset) =>\n !existing.includes(asset) &&\n isCaipAssetType(asset) &&\n !this.#isAssetIgnored(asset, accountId),\n );\n\n // Filter out tokens flagged by Blockaid as non-benign\n const filteredToBeAddedAssets = await this.#filterBlockaidSpamTokens(\n preFilteredToBeAddedAssets,\n );\n\n // In case accountsAndAssetsToUpdate event is fired with \"removed\" assets that don't exist, we don't want to remove them\n const filteredToBeRemovedAssets = removed.filter(\n (asset) => existing.includes(asset) && isCaipAssetType(asset),\n );\n\n if (\n filteredToBeAddedAssets.length > 0 ||\n filteredToBeRemovedAssets.length > 0\n ) {\n accountsAndAssetsToUpdate[accountId] = {\n added: filteredToBeAddedAssets,\n removed: filteredToBeRemovedAssets,\n };\n }\n\n for (const asset of existing) {\n assetsForMetadataRefresh.add(asset);\n }\n for (const asset of filteredToBeAddedAssets) {\n assetsForMetadataRefresh.add(asset);\n }\n for (const asset of filteredToBeRemovedAssets) {\n assetsForMetadataRefresh.delete(asset);\n }\n }\n }\n\n this.update((state) => {\n for (const [accountId, { added, removed }] of Object.entries(\n accountsAndAssetsToUpdate,\n )) {\n const assets = new Set([\n ...(state.accountsAssets[accountId] || []),\n ...added,\n ]);\n for (const asset of removed) {\n assets.delete(asset);\n }\n\n state.accountsAssets[accountId] = Array.from(assets);\n }\n });\n\n // Trigger fetching metadata for new assets\n await this.#refreshAssetsMetadata(Array.from(assetsForMetadataRefresh));\n\n this.messenger.publish(`${controllerName}:accountAssetListUpdated`, {\n assets: accountsAndAssetsToUpdate,\n });\n }\n\n /**\n * Checks for non-EVM accounts.\n *\n * @param account - The new account to be checked.\n * @returns True if the account is a non-EVM account, false otherwise.\n */\n #isNonEvmAccount(account: InternalAccount): boolean {\n return (\n !isEvmAccountType(account.type) &&\n // Non-EVM accounts are backed by a Snap for now\n account.metadata.snap !== undefined\n );\n }\n\n /**\n * Handles changes when a new account has been added.\n *\n * @param account - The new account being added.\n */\n async #handleOnAccountAdded(account: InternalAccount): Promise<void> {\n if (!this.#isNonEvmAccount(account)) {\n // Nothing to do here for EVM accounts\n return;\n }\n this.#assertControllerMutexIsLocked();\n\n // Get assets list\n if (account.metadata.snap) {\n const allAssets = await this.#getAssetsList(\n account.id,\n account.metadata.snap.id,\n );\n const assets = await this.#filterBlockaidSpamTokens(allAssets);\n await this.#refreshAssetsMetadata(assets);\n this.update((state) => {\n state.accountsAssets[account.id] = assets;\n });\n this.messenger.publish(`${controllerName}:accountAssetListUpdated`, {\n assets: {\n [account.id]: {\n added: assets,\n removed: [],\n },\n },\n });\n }\n }\n\n /**\n * Handles changes when a new account has been removed.\n *\n * @param accountId - The new account id being removed.\n */\n async #handleOnAccountRemovedEvent(accountId: string): Promise<void> {\n this.update((state) => {\n if (state.accountsAssets[accountId]) {\n delete state.accountsAssets[accountId];\n }\n if (state.allIgnoredAssets[accountId]) {\n delete state.allIgnoredAssets[accountId];\n }\n // TODO: We are not deleting the assetsMetadata because we will soon make this controller extends StaticIntervalPollingController\n // and update all assetsMetadata once a day.\n });\n }\n\n /**\n * Refreshes the assets snaps and metadata for the given list of assets\n *\n * @param assets - The assets to refresh\n */\n async #refreshAssetsMetadata(assets: CaipAssetType[]) {\n this.#assertControllerMutexIsLocked();\n\n const assetsWithoutMetadata: CaipAssetType[] = assets.filter(\n (asset) => !this.state.assetsMetadata[asset],\n );\n\n // Call the snap to get the metadata\n if (assetsWithoutMetadata.length > 0) {\n // Check if for every asset in assetsWithoutMetadata there is a snap in snaps by chainId else call getAssetSnaps\n if (\n !assetsWithoutMetadata.every((asset: CaipAssetType) => {\n const { chainId } = parseCaipAssetType(asset);\n return Boolean(this.#getAssetSnapFor(chainId));\n })\n ) {\n this.#snaps = this.#getAssetSnaps();\n }\n await this.#updateAssetsMetadata(assetsWithoutMetadata);\n }\n }\n\n /**\n * Updates the assets metadata for the given list of assets\n *\n * @param assets - The assets to update\n */\n async #updateAssetsMetadata(assets: CaipAssetType[]) {\n // Creates a mapping of scope to their respective assets list.\n const assetsByScope: Record<CaipChainId, CaipAssetType[]> = {};\n for (const asset of assets) {\n const { chainId } = parseCaipAssetType(asset);\n if (!assetsByScope[chainId]) {\n assetsByScope[chainId] = [];\n }\n assetsByScope[chainId].push(asset);\n }\n\n let newMetadata: Record<CaipAssetType, FungibleAssetMetadata> = {};\n for (const chainId of Object.keys(assetsByScope) as CaipChainId[]) {\n const assetsForChain = assetsByScope[chainId];\n // Now fetch metadata from the associated asset Snaps:\n const snap = this.#getAssetSnapFor(chainId);\n if (snap) {\n const metadata = await this.#getAssetsMetadataFrom(\n assetsForChain,\n snap.id,\n );\n newMetadata = {\n ...newMetadata,\n ...(metadata?.assets ?? {}),\n };\n }\n }\n this.update((state) => {\n state.assetsMetadata = {\n ...this.state.assetsMetadata,\n ...newMetadata,\n };\n });\n }\n\n /**\n * Creates a mapping of CAIP-2 Chain ID to Asset Snaps.\n *\n * @returns A mapping of CAIP-2 Chain ID to Asset Snaps.\n */\n #getAssetSnaps(): Record<CaipChainId, Snap[]> {\n const snaps: Record<CaipChainId, Snap[]> = {};\n const allSnaps = this.#getAllSnaps();\n const allPermissions = allSnaps.map((snap) =>\n this.#getSnapsPermissions(snap.id),\n );\n\n for (const [index, permission] of allPermissions.entries()) {\n let scopes;\n for (const singlePermissionConstraint of Object.values(permission)) {\n scopes = getChainIdsCaveat(singlePermissionConstraint);\n if (!scopes) {\n continue;\n }\n for (const scope of scopes as CaipChainId[]) {\n if (!snaps[scope]) {\n snaps[scope] = [];\n }\n snaps[scope].push(allSnaps[index]);\n }\n }\n }\n return snaps;\n }\n\n /**\n * Returns the first asset snap for the given scope\n *\n * @param scope - The scope to get the asset snap for\n * @returns The asset snap for the given scope\n */\n #getAssetSnapFor(scope: CaipChainId): Snap | undefined {\n const allSnaps = this.#snaps[scope];\n // Pick only the first one, we ignore the other Snaps if there are multiple candidates for now.\n return allSnaps?.[0]; // Will be undefined if there's no Snaps candidate for this scope.\n }\n\n /**\n * Returns all the asset snaps\n *\n * @returns All the asset snaps\n */\n #getAllSnaps(): Snap[] {\n // TODO: Use dedicated SnapController's action once available for this:\n return this.messenger\n .call('SnapController:getAll')\n .filter((snap) => snap.enabled && !snap.blocked);\n }\n\n /**\n * Returns the permissions for the given origin\n *\n * @param origin - The origin to get the permissions for\n * @returns The permissions for the given origin\n */\n #getSnapsPermissions(\n origin: string,\n ): SubjectPermissions<PermissionConstraint> {\n return this.messenger.call(\n 'PermissionController:getPermissions',\n origin,\n ) as SubjectPermissions<PermissionConstraint>;\n }\n\n /**\n * Returns the metadata for the given assets\n *\n * @param assets - The assets to get metadata for\n * @param snapId - The snap ID to get metadata from\n * @returns The metadata for the assets\n */\n async #getAssetsMetadataFrom(\n assets: CaipAssetType[],\n snapId: string,\n ): Promise<AssetMetadataResponse | undefined> {\n try {\n return (await this.messenger.call('SnapController:handleRequest', {\n snapId: snapId as SnapId,\n origin: 'metamask',\n handler: HandlerType.OnAssetsLookup,\n request: {\n jsonrpc: '2.0',\n method: 'onAssetLookup',\n params: {\n assets,\n },\n },\n })) as Promise<AssetMetadataResponse>;\n } catch (error) {\n // Ignore\n console.error(error);\n return undefined;\n }\n }\n\n /**\n * Calls the Blockaid token bulk scanning endpoint for a given chain and\n * set of token addresses.\n *\n * @param chain - The chain name for the Blockaid API (e.g. \"solana\").\n * @param tokens - The token addresses to scan.\n * @returns The scan response, or null if the request fails or times out.\n */\n async #scanBlockaidTokens(\n chain: string,\n tokens: string[],\n ): Promise<BlockaidTokenScanResponse | null> {\n try {\n return await handleFetch(\n `${SECURITY_ALERTS_BASE_URL}${TOKEN_BULK_SCANNING_ENDPOINT}`,\n {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ chain, tokens }),\n },\n );\n } catch (error) {\n console.error('Blockaid token scan failed:', error);\n return null;\n }\n }\n\n /**\n * Filters out tokens flagged as non-benign by Blockaid. Only tokens with\n * an `assetNamespace` of \"token\" are scanned (native assets like slip44 are\n * passed through unfiltered). If the API call fails, all tokens are kept.\n *\n * @param assets - The CAIP asset type list to filter.\n * @returns The filtered list with malicious/spam/warning tokens removed.\n */\n async #filterBlockaidSpamTokens(\n assets: CaipAssetType[],\n ): Promise<CaipAssetType[]> {\n // Group scannable token assets by chain namespace\n const tokensByChain: Record<\n string,\n { asset: CaipAssetType; address: string }[]\n > = {};\n\n for (const asset of assets) {\n const { assetNamespace, assetReference, chain } =\n parseCaipAssetType(asset);\n\n // Only scan fungible token assets (e.g. SPL tokens), skip native (slip44)\n if (assetNamespace === 'token') {\n const chainName = chain.namespace;\n if (!tokensByChain[chainName]) {\n tokensByChain[chainName] = [];\n }\n tokensByChain[chainName].push({ asset, address: assetReference });\n }\n }\n\n // If there are no token assets to scan, return as-is\n if (Object.keys(tokensByChain).length === 0) {\n return assets;\n }\n\n // Build a set of assets to reject (non-benign tokens)\n const rejectedAssets = new Set<CaipAssetType>();\n\n for (const [chainName, tokenEntries] of Object.entries(tokensByChain)) {\n const addresses = tokenEntries.map((entry) => entry.address);\n const scanResponse = await this.#scanBlockaidTokens(chainName, addresses);\n\n if (!scanResponse?.results) {\n // If the scan fails, keep all tokens (fail open)\n continue;\n }\n\n for (const entry of tokenEntries) {\n const result = scanResponse.results[entry.address];\n // Reject the token only if we have a definitive non-benign result\n if (\n result?.result_type &&\n result.result_type !== BlockaidResultType.Benign\n ) {\n rejectedAssets.add(entry.asset);\n }\n }\n }\n\n // Filter while preserving original order\n return assets.filter((asset) => !rejectedAssets.has(asset));\n }\n\n /**\n * Get assets list for an account\n *\n * @param accountId - AccountId to get assets for\n * @param snapId - Snap ID for the account\n * @returns list of assets\n */\n async #getAssetsList(\n accountId: string,\n snapId: string,\n ): Promise<CaipAssetTypeOrId[]> {\n return await this.#getClient(snapId).listAccountAssets(accountId);\n }\n\n /**\n * Gets a `KeyringClient` for a Snap.\n *\n * @param snapId - ID of the Snap to get the client for.\n * @returns A `KeyringClient` for the Snap.\n */\n #getClient(snapId: string): KeyringClient {\n return new KeyringClient({\n send: async (request: JsonRpcRequest) =>\n (await this.messenger.call('SnapController:handleRequest', {\n snapId: snapId as SnapId,\n origin: 'metamask',\n handler: HandlerType.OnKeyringRequest,\n request,\n })) as Promise<Json>,\n });\n }\n\n /**\n * Assert that the controller mutex is locked.\n *\n * @throws If the controller mutex is not locked.\n */\n #assertControllerMutexIsLocked() {\n if (!this.#controllerOperationMutex.isLocked()) {\n throw new Error(\n 'MultichainAssetsControllerError - Attempt to update state',\n );\n }\n }\n\n /**\n * Lock the controller mutex before executing the given function,\n * and release it after the function is resolved or after an\n * error is thrown.\n *\n * This wrapper ensures that each mutable operation that interacts with the\n * controller and that changes its state is executed in a mutually exclusive way,\n * preventing unsafe concurrent access that could lead to unpredictable behavior.\n *\n * @param callback - The function to execute while the controller mutex is locked.\n * @returns The result of the function.\n */\n async #withControllerLock<Result>(\n callback: MutuallyExclusiveCallback<Result>,\n ): Promise<Result> {\n return withLock(this.#controllerOperationMutex, callback);\n }\n}\n\n/**\n * Lock the given mutex before executing the given function,\n * and release it after the function is resolved or after an\n * error is thrown.\n *\n * @param mutex - The mutex to lock.\n * @param callback - The function to execute while the mutex is locked.\n * @returns The result of the function.\n */\nasync function withLock<Result>(\n mutex: Mutex,\n callback: MutuallyExclusiveCallback<Result>,\n): Promise<Result> {\n const releaseLock = await mutex.acquire();\n\n try {\n return await callback({ releaseLock });\n } finally {\n releaseLock();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"MultichainAssetsController.cjs","sourceRoot":"","sources":["../../src/MultichainAssetsController/MultichainAssetsController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountsControllerAccountAddedEvent,\n AccountsControllerAccountAssetListUpdatedEvent,\n AccountsControllerAccountRemovedEvent,\n AccountsControllerListMultichainAccountsAction,\n} from '@metamask/accounts-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { isEvmAccountType } from '@metamask/keyring-api';\nimport type {\n AccountAssetListUpdatedEventPayload,\n CaipAssetType,\n CaipAssetTypeOrId,\n} from '@metamask/keyring-api';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport { KeyringClient } from '@metamask/keyring-snap-client';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n GetPermissions,\n PermissionConstraint,\n SubjectPermissions,\n} from '@metamask/permission-controller';\nimport type {\n GetAllSnaps,\n HandleSnapRequest,\n} from '@metamask/snaps-controllers';\nimport type { FungibleAssetMetadata, Snap, SnapId } from '@metamask/snaps-sdk';\nimport { HandlerType } from '@metamask/snaps-utils';\nimport { isCaipAssetType, parseCaipAssetType } from '@metamask/utils';\nimport type { CaipChainId } from '@metamask/utils';\nimport type { Json, JsonRpcRequest } from '@metamask/utils';\nimport type { MutexInterface } from 'async-mutex';\nimport { Mutex } from 'async-mutex';\n\nimport { getChainIdsCaveat } from './utils';\n\nconst controllerName = 'MultichainAssetsController';\n\nexport type MultichainAssetsControllerState = {\n assetsMetadata: {\n [asset: CaipAssetType]: FungibleAssetMetadata;\n };\n accountsAssets: { [account: string]: CaipAssetType[] };\n allIgnoredAssets: { [account: string]: CaipAssetType[] };\n};\n\n// Represents the response of the asset snap's onAssetLookup handler\nexport type AssetMetadataResponse = {\n assets: {\n [asset: CaipAssetType]: FungibleAssetMetadata;\n };\n};\n\nexport type MultichainAssetsControllerAccountAssetListUpdatedEvent = {\n type: `${typeof controllerName}:accountAssetListUpdated`;\n payload: AccountsControllerAccountAssetListUpdatedEvent['payload'];\n};\n\n/**\n * Constructs the default {@link MultichainAssetsController} state. This allows\n * consumers to provide a partial state object when initializing the controller\n * and also helps in constructing complete state objects for this controller in\n * tests.\n *\n * @returns The default {@link MultichainAssetsController} state.\n */\nexport function getDefaultMultichainAssetsControllerState(): MultichainAssetsControllerState {\n return { accountsAssets: {}, assetsMetadata: {}, allIgnoredAssets: {} };\n}\n\nexport type MultichainAssetsControllerGetAssetMetadataAction = {\n type: `${typeof controllerName}:getAssetMetadata`;\n handler: MultichainAssetsController['getAssetMetadata'];\n};\n\nexport type MultichainAssetsControllerIgnoreAssetsAction = {\n type: `${typeof controllerName}:ignoreAssets`;\n handler: MultichainAssetsController['ignoreAssets'];\n};\n\nexport type MultichainAssetsControllerAddAssetsAction = {\n type: `${typeof controllerName}:addAssets`;\n handler: MultichainAssetsController['addAssets'];\n};\n\n/**\n * Returns the state of the {@link MultichainAssetsController}.\n */\nexport type MultichainAssetsControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n MultichainAssetsControllerState\n>;\n\n/**\n * Event emitted when the state of the {@link MultichainAssetsController} changes.\n */\nexport type MultichainAssetsControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n MultichainAssetsControllerState\n >;\n\n/**\n * Actions exposed by the {@link MultichainAssetsController}.\n */\nexport type MultichainAssetsControllerActions =\n | MultichainAssetsControllerGetStateAction\n | MultichainAssetsControllerGetAssetMetadataAction\n | MultichainAssetsControllerIgnoreAssetsAction\n | MultichainAssetsControllerAddAssetsAction;\n\n/**\n * Events emitted by {@link MultichainAssetsController}.\n */\nexport type MultichainAssetsControllerEvents =\n | MultichainAssetsControllerStateChangeEvent\n | MultichainAssetsControllerAccountAssetListUpdatedEvent;\n\n/**\n * A function executed within a mutually exclusive lock, with\n * a mutex releaser in its option bag.\n *\n * @param releaseLock - A function to release the lock.\n */\ntype MutuallyExclusiveCallback<Result> = ({\n releaseLock,\n}: {\n releaseLock: MutexInterface.Releaser;\n}) => Promise<Result>;\n\n/**\n * Actions that this controller is allowed to call.\n */\ntype AllowedActions =\n | HandleSnapRequest\n | GetAllSnaps\n | GetPermissions\n | AccountsControllerListMultichainAccountsAction;\n\n/**\n * Events that this controller is allowed to subscribe.\n */\ntype AllowedEvents =\n | AccountsControllerAccountAddedEvent\n | AccountsControllerAccountRemovedEvent\n | AccountsControllerAccountAssetListUpdatedEvent;\n\n/**\n * Messenger type for the MultichainAssetsController.\n */\nexport type MultichainAssetsControllerMessenger = Messenger<\n typeof controllerName,\n MultichainAssetsControllerActions | AllowedActions,\n MultichainAssetsControllerEvents | AllowedEvents\n>;\n\n/**\n * {@link MultichainAssetsController}'s metadata.\n *\n * This allows us to choose if fields of the state should be persisted or not\n * using the `persist` flag; and if they can be sent to Sentry or not, using\n * the `anonymous` flag.\n */\nconst assetsControllerMetadata: StateMetadata<MultichainAssetsControllerState> =\n {\n assetsMetadata: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n accountsAssets: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n allIgnoredAssets: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n };\n\n// TODO: make this controller extends StaticIntervalPollingController and update all assetsMetadata once a day.\n\nexport class MultichainAssetsController extends BaseController<\n typeof controllerName,\n MultichainAssetsControllerState,\n MultichainAssetsControllerMessenger\n> {\n // Mapping of CAIP-2 Chain ID to Asset Snaps.\n #snaps: Record<CaipChainId, Snap[]>;\n\n readonly #controllerOperationMutex = new Mutex();\n\n constructor({\n messenger,\n state = {},\n }: {\n messenger: MultichainAssetsControllerMessenger;\n state?: Partial<MultichainAssetsControllerState>;\n }) {\n super({\n messenger,\n name: controllerName,\n metadata: assetsControllerMetadata,\n state: {\n ...getDefaultMultichainAssetsControllerState(),\n ...state,\n },\n });\n\n this.#snaps = {};\n\n this.messenger.subscribe(\n 'AccountsController:accountAdded',\n async (account) => await this.#handleOnAccountAddedEvent(account),\n );\n this.messenger.subscribe(\n 'AccountsController:accountRemoved',\n async (account) => await this.#handleOnAccountRemovedEvent(account),\n );\n this.messenger.subscribe(\n 'AccountsController:accountAssetListUpdated',\n async (event) => await this.#handleAccountAssetListUpdatedEvent(event),\n );\n\n this.#registerMessageHandlers();\n }\n\n async #handleAccountAssetListUpdatedEvent(\n event: AccountAssetListUpdatedEventPayload,\n ) {\n return this.#withControllerLock(async () =>\n this.#handleAccountAssetListUpdated(event),\n );\n }\n\n async #handleOnAccountAddedEvent(account: InternalAccount) {\n return this.#withControllerLock(async () =>\n this.#handleOnAccountAdded(account),\n );\n }\n\n /**\n * Constructor helper for registering the controller's messaging system\n * actions.\n */\n #registerMessageHandlers() {\n this.messenger.registerActionHandler(\n 'MultichainAssetsController:getAssetMetadata',\n this.getAssetMetadata.bind(this),\n );\n\n this.messenger.registerActionHandler(\n 'MultichainAssetsController:ignoreAssets',\n this.ignoreAssets.bind(this),\n );\n\n this.messenger.registerActionHandler(\n 'MultichainAssetsController:addAssets',\n this.addAssets.bind(this),\n );\n }\n\n /**\n * Returns the metadata for the given asset\n *\n * @param asset - The asset to get metadata for\n * @returns The metadata for the asset or undefined if not found.\n */\n getAssetMetadata(asset: CaipAssetType): FungibleAssetMetadata | undefined {\n return this.state.assetsMetadata[asset];\n }\n\n /**\n * Ignores a batch of assets for a specific account.\n *\n * @param assetsToIgnore - Array of asset IDs to ignore.\n * @param accountId - The account ID to ignore assets for.\n */\n ignoreAssets(assetsToIgnore: CaipAssetType[], accountId: string): void {\n this.update((state) => {\n if (state.accountsAssets[accountId]) {\n state.accountsAssets[accountId] = state.accountsAssets[\n accountId\n ].filter((asset) => !assetsToIgnore.includes(asset));\n }\n\n if (!state.allIgnoredAssets[accountId]) {\n state.allIgnoredAssets[accountId] = [];\n }\n\n const newIgnoredAssets = assetsToIgnore.filter(\n (asset) => !state.allIgnoredAssets[accountId].includes(asset),\n );\n state.allIgnoredAssets[accountId].push(...newIgnoredAssets);\n });\n }\n\n /**\n * Adds multiple assets to the stored asset list for a specific account.\n * All assets must belong to the same chain.\n *\n * @param assetIds - Array of CAIP asset IDs to add (must be from same chain).\n * @param accountId - The account ID to add the assets to.\n * @returns The updated asset list for the account.\n * @throws Error if assets are from different chains.\n */\n async addAssets(\n assetIds: CaipAssetType[],\n accountId: string,\n ): Promise<CaipAssetType[]> {\n if (assetIds.length === 0) {\n return this.state.accountsAssets[accountId] || [];\n }\n\n // Validate that all assets are from the same chain\n const chainIds = new Set(\n assetIds.map((assetId) => parseCaipAssetType(assetId).chainId),\n );\n if (chainIds.size > 1) {\n throw new Error(\n `All assets must belong to the same chain. Found assets from chains: ${Array.from(chainIds).join(', ')}`,\n );\n }\n\n return this.#withControllerLock(async () => {\n // Refresh metadata for all assets\n await this.#refreshAssetsMetadata(assetIds);\n\n const addedAssets: CaipAssetType[] = [];\n\n this.update((state) => {\n // Initialize account assets if it doesn't exist\n if (!state.accountsAssets[accountId]) {\n state.accountsAssets[accountId] = [];\n }\n\n // Add assets if they don't already exist\n for (const assetId of assetIds) {\n if (!state.accountsAssets[accountId].includes(assetId)) {\n state.accountsAssets[accountId].push(assetId);\n addedAssets.push(assetId);\n }\n }\n\n // Remove from ignored list if they exist there (inline logic like EVM)\n if (state.allIgnoredAssets[accountId]) {\n state.allIgnoredAssets[accountId] = state.allIgnoredAssets[\n accountId\n ].filter((asset) => !assetIds.includes(asset));\n\n // Clean up empty arrays\n if (state.allIgnoredAssets[accountId].length === 0) {\n delete state.allIgnoredAssets[accountId];\n }\n }\n });\n\n // Publish event to notify other controllers (balances, rates) about the new assets\n if (addedAssets.length > 0) {\n this.messenger.publish(`${controllerName}:accountAssetListUpdated`, {\n assets: {\n [accountId]: {\n added: addedAssets,\n removed: [],\n },\n },\n });\n }\n\n return this.state.accountsAssets[accountId] || [];\n });\n }\n\n /**\n * Checks if an asset is ignored for a specific account.\n *\n * @param asset - The asset ID to check.\n * @param accountId - The account ID to check for.\n * @returns True if the asset is ignored, false otherwise.\n */\n #isAssetIgnored(asset: CaipAssetType, accountId: string): boolean {\n return this.state.allIgnoredAssets[accountId]?.includes(asset) ?? false;\n }\n\n /**\n * Function to update the assets list for an account\n *\n * @param event - The list of assets to update\n */\n async #handleAccountAssetListUpdated(\n event: AccountAssetListUpdatedEventPayload,\n ) {\n this.#assertControllerMutexIsLocked();\n\n const assetsForMetadataRefresh = new Set<CaipAssetType>([]);\n const accountsAndAssetsToUpdate: AccountAssetListUpdatedEventPayload['assets'] =\n {};\n for (const [accountId, { added, removed }] of Object.entries(\n event.assets,\n )) {\n if (added.length > 0 || removed.length > 0) {\n const existing = this.state.accountsAssets[accountId] || [];\n\n // In case accountsAndAssetsToUpdate event is fired with \"added\" assets that already exist, we don't want to add them again\n // Also filter out ignored assets\n const filteredToBeAddedAssets = added.filter(\n (asset) =>\n !existing.includes(asset) &&\n isCaipAssetType(asset) &&\n !this.#isAssetIgnored(asset, accountId),\n );\n\n // In case accountsAndAssetsToUpdate event is fired with \"removed\" assets that don't exist, we don't want to remove them\n const filteredToBeRemovedAssets = removed.filter(\n (asset) => existing.includes(asset) && isCaipAssetType(asset),\n );\n\n if (\n filteredToBeAddedAssets.length > 0 ||\n filteredToBeRemovedAssets.length > 0\n ) {\n accountsAndAssetsToUpdate[accountId] = {\n added: filteredToBeAddedAssets,\n removed: filteredToBeRemovedAssets,\n };\n }\n\n for (const asset of existing) {\n assetsForMetadataRefresh.add(asset);\n }\n for (const asset of filteredToBeAddedAssets) {\n assetsForMetadataRefresh.add(asset);\n }\n for (const asset of filteredToBeRemovedAssets) {\n assetsForMetadataRefresh.delete(asset);\n }\n }\n }\n\n this.update((state) => {\n for (const [accountId, { added, removed }] of Object.entries(\n accountsAndAssetsToUpdate,\n )) {\n const assets = new Set([\n ...(state.accountsAssets[accountId] || []),\n ...added,\n ]);\n for (const asset of removed) {\n assets.delete(asset);\n }\n\n state.accountsAssets[accountId] = Array.from(assets);\n }\n });\n\n // Trigger fetching metadata for new assets\n await this.#refreshAssetsMetadata(Array.from(assetsForMetadataRefresh));\n\n this.messenger.publish(`${controllerName}:accountAssetListUpdated`, {\n assets: accountsAndAssetsToUpdate,\n });\n }\n\n /**\n * Checks for non-EVM accounts.\n *\n * @param account - The new account to be checked.\n * @returns True if the account is a non-EVM account, false otherwise.\n */\n #isNonEvmAccount(account: InternalAccount): boolean {\n return (\n !isEvmAccountType(account.type) &&\n // Non-EVM accounts are backed by a Snap for now\n account.metadata.snap !== undefined\n );\n }\n\n /**\n * Handles changes when a new account has been added.\n *\n * @param account - The new account being added.\n */\n async #handleOnAccountAdded(account: InternalAccount): Promise<void> {\n if (!this.#isNonEvmAccount(account)) {\n // Nothing to do here for EVM accounts\n return;\n }\n this.#assertControllerMutexIsLocked();\n\n // Get assets list\n if (account.metadata.snap) {\n const assets = await this.#getAssetsList(\n account.id,\n account.metadata.snap.id,\n );\n await this.#refreshAssetsMetadata(assets);\n this.update((state) => {\n state.accountsAssets[account.id] = assets;\n });\n this.messenger.publish(`${controllerName}:accountAssetListUpdated`, {\n assets: {\n [account.id]: {\n added: assets,\n removed: [],\n },\n },\n });\n }\n }\n\n /**\n * Handles changes when a new account has been removed.\n *\n * @param accountId - The new account id being removed.\n */\n async #handleOnAccountRemovedEvent(accountId: string): Promise<void> {\n this.update((state) => {\n if (state.accountsAssets[accountId]) {\n delete state.accountsAssets[accountId];\n }\n if (state.allIgnoredAssets[accountId]) {\n delete state.allIgnoredAssets[accountId];\n }\n // TODO: We are not deleting the assetsMetadata because we will soon make this controller extends StaticIntervalPollingController\n // and update all assetsMetadata once a day.\n });\n }\n\n /**\n * Refreshes the assets snaps and metadata for the given list of assets\n *\n * @param assets - The assets to refresh\n */\n async #refreshAssetsMetadata(assets: CaipAssetType[]) {\n this.#assertControllerMutexIsLocked();\n\n const assetsWithoutMetadata: CaipAssetType[] = assets.filter(\n (asset) => !this.state.assetsMetadata[asset],\n );\n\n // Call the snap to get the metadata\n if (assetsWithoutMetadata.length > 0) {\n // Check if for every asset in assetsWithoutMetadata there is a snap in snaps by chainId else call getAssetSnaps\n if (\n !assetsWithoutMetadata.every((asset: CaipAssetType) => {\n const { chainId } = parseCaipAssetType(asset);\n return Boolean(this.#getAssetSnapFor(chainId));\n })\n ) {\n this.#snaps = this.#getAssetSnaps();\n }\n await this.#updateAssetsMetadata(assetsWithoutMetadata);\n }\n }\n\n /**\n * Updates the assets metadata for the given list of assets\n *\n * @param assets - The assets to update\n */\n async #updateAssetsMetadata(assets: CaipAssetType[]) {\n // Creates a mapping of scope to their respective assets list.\n const assetsByScope: Record<CaipChainId, CaipAssetType[]> = {};\n for (const asset of assets) {\n const { chainId } = parseCaipAssetType(asset);\n if (!assetsByScope[chainId]) {\n assetsByScope[chainId] = [];\n }\n assetsByScope[chainId].push(asset);\n }\n\n let newMetadata: Record<CaipAssetType, FungibleAssetMetadata> = {};\n for (const chainId of Object.keys(assetsByScope) as CaipChainId[]) {\n const assetsForChain = assetsByScope[chainId];\n // Now fetch metadata from the associated asset Snaps:\n const snap = this.#getAssetSnapFor(chainId);\n if (snap) {\n const metadata = await this.#getAssetsMetadataFrom(\n assetsForChain,\n snap.id,\n );\n newMetadata = {\n ...newMetadata,\n ...(metadata?.assets ?? {}),\n };\n }\n }\n this.update((state) => {\n state.assetsMetadata = {\n ...this.state.assetsMetadata,\n ...newMetadata,\n };\n });\n }\n\n /**\n * Creates a mapping of CAIP-2 Chain ID to Asset Snaps.\n *\n * @returns A mapping of CAIP-2 Chain ID to Asset Snaps.\n */\n #getAssetSnaps(): Record<CaipChainId, Snap[]> {\n const snaps: Record<CaipChainId, Snap[]> = {};\n const allSnaps = this.#getAllSnaps();\n const allPermissions = allSnaps.map((snap) =>\n this.#getSnapsPermissions(snap.id),\n );\n\n for (const [index, permission] of allPermissions.entries()) {\n let scopes;\n for (const singlePermissionConstraint of Object.values(permission)) {\n scopes = getChainIdsCaveat(singlePermissionConstraint);\n if (!scopes) {\n continue;\n }\n for (const scope of scopes as CaipChainId[]) {\n if (!snaps[scope]) {\n snaps[scope] = [];\n }\n snaps[scope].push(allSnaps[index]);\n }\n }\n }\n return snaps;\n }\n\n /**\n * Returns the first asset snap for the given scope\n *\n * @param scope - The scope to get the asset snap for\n * @returns The asset snap for the given scope\n */\n #getAssetSnapFor(scope: CaipChainId): Snap | undefined {\n const allSnaps = this.#snaps[scope];\n // Pick only the first one, we ignore the other Snaps if there are multiple candidates for now.\n return allSnaps?.[0]; // Will be undefined if there's no Snaps candidate for this scope.\n }\n\n /**\n * Returns all the asset snaps\n *\n * @returns All the asset snaps\n */\n #getAllSnaps(): Snap[] {\n // TODO: Use dedicated SnapController's action once available for this:\n return this.messenger\n .call('SnapController:getAll')\n .filter((snap) => snap.enabled && !snap.blocked);\n }\n\n /**\n * Returns the permissions for the given origin\n *\n * @param origin - The origin to get the permissions for\n * @returns The permissions for the given origin\n */\n #getSnapsPermissions(\n origin: string,\n ): SubjectPermissions<PermissionConstraint> {\n return this.messenger.call(\n 'PermissionController:getPermissions',\n origin,\n ) as SubjectPermissions<PermissionConstraint>;\n }\n\n /**\n * Returns the metadata for the given assets\n *\n * @param assets - The assets to get metadata for\n * @param snapId - The snap ID to get metadata from\n * @returns The metadata for the assets\n */\n async #getAssetsMetadataFrom(\n assets: CaipAssetType[],\n snapId: string,\n ): Promise<AssetMetadataResponse | undefined> {\n try {\n return (await this.messenger.call('SnapController:handleRequest', {\n snapId: snapId as SnapId,\n origin: 'metamask',\n handler: HandlerType.OnAssetsLookup,\n request: {\n jsonrpc: '2.0',\n method: 'onAssetLookup',\n params: {\n assets,\n },\n },\n })) as Promise<AssetMetadataResponse>;\n } catch (error) {\n // Ignore\n console.error(error);\n return undefined;\n }\n }\n\n /**\n * Get assets list for an account\n *\n * @param accountId - AccountId to get assets for\n * @param snapId - Snap ID for the account\n * @returns list of assets\n */\n async #getAssetsList(\n accountId: string,\n snapId: string,\n ): Promise<CaipAssetTypeOrId[]> {\n return await this.#getClient(snapId).listAccountAssets(accountId);\n }\n\n /**\n * Gets a `KeyringClient` for a Snap.\n *\n * @param snapId - ID of the Snap to get the client for.\n * @returns A `KeyringClient` for the Snap.\n */\n #getClient(snapId: string): KeyringClient {\n return new KeyringClient({\n send: async (request: JsonRpcRequest) =>\n (await this.messenger.call('SnapController:handleRequest', {\n snapId: snapId as SnapId,\n origin: 'metamask',\n handler: HandlerType.OnKeyringRequest,\n request,\n })) as Promise<Json>,\n });\n }\n\n /**\n * Assert that the controller mutex is locked.\n *\n * @throws If the controller mutex is not locked.\n */\n #assertControllerMutexIsLocked() {\n if (!this.#controllerOperationMutex.isLocked()) {\n throw new Error(\n 'MultichainAssetsControllerError - Attempt to update state',\n );\n }\n }\n\n /**\n * Lock the controller mutex before executing the given function,\n * and release it after the function is resolved or after an\n * error is thrown.\n *\n * This wrapper ensures that each mutable operation that interacts with the\n * controller and that changes its state is executed in a mutually exclusive way,\n * preventing unsafe concurrent access that could lead to unpredictable behavior.\n *\n * @param callback - The function to execute while the controller mutex is locked.\n * @returns The result of the function.\n */\n async #withControllerLock<Result>(\n callback: MutuallyExclusiveCallback<Result>,\n ): Promise<Result> {\n return withLock(this.#controllerOperationMutex, callback);\n }\n}\n\n/**\n * Lock the given mutex before executing the given function,\n * and release it after the function is resolved or after an\n * error is thrown.\n *\n * @param mutex - The mutex to lock.\n * @param callback - The function to execute while the mutex is locked.\n * @returns The result of the function.\n */\nasync function withLock<Result>(\n mutex: Mutex,\n callback: MutuallyExclusiveCallback<Result>,\n): Promise<Result> {\n const releaseLock = await mutex.acquire();\n\n try {\n return await callback({ releaseLock });\n } finally {\n releaseLock();\n }\n}\n"]}
|
|
@@ -7,15 +7,6 @@ import type { GetPermissions } from "@metamask/permission-controller";
|
|
|
7
7
|
import type { GetAllSnaps, HandleSnapRequest } from "@metamask/snaps-controllers";
|
|
8
8
|
import type { FungibleAssetMetadata } from "@metamask/snaps-sdk";
|
|
9
9
|
declare const controllerName = "MultichainAssetsController";
|
|
10
|
-
/**
|
|
11
|
-
* Result type from Blockaid token security scan.
|
|
12
|
-
*/
|
|
13
|
-
export declare enum BlockaidResultType {
|
|
14
|
-
Benign = "Benign",
|
|
15
|
-
Spam = "Spam",
|
|
16
|
-
Warning = "Warning",
|
|
17
|
-
Malicious = "Malicious"
|
|
18
|
-
}
|
|
19
10
|
export type MultichainAssetsControllerState = {
|
|
20
11
|
assetsMetadata: {
|
|
21
12
|
[asset: CaipAssetType]: FungibleAssetMetadata;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultichainAssetsController.d.cts","sourceRoot":"","sources":["../../src/MultichainAssetsController/MultichainAssetsController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mCAAmC,EACnC,8CAA8C,EAC9C,qCAAqC,EACrC,8CAA8C,EAC/C,sCAAsC;AACvC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAC3D,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAE3B,kCAAkC;
|
|
1
|
+
{"version":3,"file":"MultichainAssetsController.d.cts","sourceRoot":"","sources":["../../src/MultichainAssetsController/MultichainAssetsController.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,15 +7,6 @@ import type { GetPermissions } from "@metamask/permission-controller";
|
|
|
7
7
|
import type { GetAllSnaps, HandleSnapRequest } from "@metamask/snaps-controllers";
|
|
8
8
|
import type { FungibleAssetMetadata } from "@metamask/snaps-sdk";
|
|
9
9
|
declare const controllerName = "MultichainAssetsController";
|
|
10
|
-
/**
|
|
11
|
-
* Result type from Blockaid token security scan.
|
|
12
|
-
*/
|
|
13
|
-
export declare enum BlockaidResultType {
|
|
14
|
-
Benign = "Benign",
|
|
15
|
-
Spam = "Spam",
|
|
16
|
-
Warning = "Warning",
|
|
17
|
-
Malicious = "Malicious"
|
|
18
|
-
}
|
|
19
10
|
export type MultichainAssetsControllerState = {
|
|
20
11
|
assetsMetadata: {
|
|
21
12
|
[asset: CaipAssetType]: FungibleAssetMetadata;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultichainAssetsController.d.mts","sourceRoot":"","sources":["../../src/MultichainAssetsController/MultichainAssetsController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mCAAmC,EACnC,8CAA8C,EAC9C,qCAAqC,EACrC,8CAA8C,EAC/C,sCAAsC;AACvC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAC3D,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAE3B,kCAAkC;
|
|
1
|
+
{"version":3,"file":"MultichainAssetsController.d.mts","sourceRoot":"","sources":["../../src/MultichainAssetsController/MultichainAssetsController.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,9 +9,8 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var _MultichainAssetsController_instances, _MultichainAssetsController_snaps, _MultichainAssetsController_controllerOperationMutex, _MultichainAssetsController_handleAccountAssetListUpdatedEvent, _MultichainAssetsController_handleOnAccountAddedEvent, _MultichainAssetsController_registerMessageHandlers, _MultichainAssetsController_isAssetIgnored, _MultichainAssetsController_handleAccountAssetListUpdated, _MultichainAssetsController_isNonEvmAccount, _MultichainAssetsController_handleOnAccountAdded, _MultichainAssetsController_handleOnAccountRemovedEvent, _MultichainAssetsController_refreshAssetsMetadata, _MultichainAssetsController_updateAssetsMetadata, _MultichainAssetsController_getAssetSnaps, _MultichainAssetsController_getAssetSnapFor, _MultichainAssetsController_getAllSnaps, _MultichainAssetsController_getSnapsPermissions, _MultichainAssetsController_getAssetsMetadataFrom,
|
|
12
|
+
var _MultichainAssetsController_instances, _MultichainAssetsController_snaps, _MultichainAssetsController_controllerOperationMutex, _MultichainAssetsController_handleAccountAssetListUpdatedEvent, _MultichainAssetsController_handleOnAccountAddedEvent, _MultichainAssetsController_registerMessageHandlers, _MultichainAssetsController_isAssetIgnored, _MultichainAssetsController_handleAccountAssetListUpdated, _MultichainAssetsController_isNonEvmAccount, _MultichainAssetsController_handleOnAccountAdded, _MultichainAssetsController_handleOnAccountRemovedEvent, _MultichainAssetsController_refreshAssetsMetadata, _MultichainAssetsController_updateAssetsMetadata, _MultichainAssetsController_getAssetSnaps, _MultichainAssetsController_getAssetSnapFor, _MultichainAssetsController_getAllSnaps, _MultichainAssetsController_getSnapsPermissions, _MultichainAssetsController_getAssetsMetadataFrom, _MultichainAssetsController_getAssetsList, _MultichainAssetsController_getClient, _MultichainAssetsController_assertControllerMutexIsLocked, _MultichainAssetsController_withControllerLock;
|
|
13
13
|
import { BaseController } from "@metamask/base-controller";
|
|
14
|
-
import { handleFetch } from "@metamask/controller-utils";
|
|
15
14
|
import { isEvmAccountType } from "@metamask/keyring-api";
|
|
16
15
|
import { KeyringClient } from "@metamask/keyring-snap-client";
|
|
17
16
|
import { HandlerType } from "@metamask/snaps-utils";
|
|
@@ -19,19 +18,6 @@ import { isCaipAssetType, parseCaipAssetType } from "@metamask/utils";
|
|
|
19
18
|
import { Mutex } from "async-mutex";
|
|
20
19
|
import { getChainIdsCaveat } from "./utils.mjs";
|
|
21
20
|
const controllerName = 'MultichainAssetsController';
|
|
22
|
-
// Blockaid token security scanning constants
|
|
23
|
-
const SECURITY_ALERTS_BASE_URL = 'https://security-alerts.api.cx.metamask.io';
|
|
24
|
-
const TOKEN_BULK_SCANNING_ENDPOINT = '/token/scan-bulk';
|
|
25
|
-
/**
|
|
26
|
-
* Result type from Blockaid token security scan.
|
|
27
|
-
*/
|
|
28
|
-
export var BlockaidResultType;
|
|
29
|
-
(function (BlockaidResultType) {
|
|
30
|
-
BlockaidResultType["Benign"] = "Benign";
|
|
31
|
-
BlockaidResultType["Spam"] = "Spam";
|
|
32
|
-
BlockaidResultType["Warning"] = "Warning";
|
|
33
|
-
BlockaidResultType["Malicious"] = "Malicious";
|
|
34
|
-
})(BlockaidResultType || (BlockaidResultType = {}));
|
|
35
21
|
/**
|
|
36
22
|
* Constructs the default {@link MultichainAssetsController} state. This allows
|
|
37
23
|
* consumers to provide a partial state object when initializing the controller
|
|
@@ -202,11 +188,9 @@ async function _MultichainAssetsController_handleAccountAssetListUpdated(event)
|
|
|
202
188
|
const existing = this.state.accountsAssets[accountId] || [];
|
|
203
189
|
// In case accountsAndAssetsToUpdate event is fired with "added" assets that already exist, we don't want to add them again
|
|
204
190
|
// Also filter out ignored assets
|
|
205
|
-
const
|
|
191
|
+
const filteredToBeAddedAssets = added.filter((asset) => !existing.includes(asset) &&
|
|
206
192
|
isCaipAssetType(asset) &&
|
|
207
193
|
!__classPrivateFieldGet(this, _MultichainAssetsController_instances, "m", _MultichainAssetsController_isAssetIgnored).call(this, asset, accountId));
|
|
208
|
-
// Filter out tokens flagged by Blockaid as non-benign
|
|
209
|
-
const filteredToBeAddedAssets = await __classPrivateFieldGet(this, _MultichainAssetsController_instances, "m", _MultichainAssetsController_filterBlockaidSpamTokens).call(this, preFilteredToBeAddedAssets);
|
|
210
194
|
// In case accountsAndAssetsToUpdate event is fired with "removed" assets that don't exist, we don't want to remove them
|
|
211
195
|
const filteredToBeRemovedAssets = removed.filter((asset) => existing.includes(asset) && isCaipAssetType(asset));
|
|
212
196
|
if (filteredToBeAddedAssets.length > 0 ||
|
|
@@ -262,8 +246,7 @@ async function _MultichainAssetsController_handleOnAccountAdded(account) {
|
|
|
262
246
|
__classPrivateFieldGet(this, _MultichainAssetsController_instances, "m", _MultichainAssetsController_assertControllerMutexIsLocked).call(this);
|
|
263
247
|
// Get assets list
|
|
264
248
|
if (account.metadata.snap) {
|
|
265
|
-
const
|
|
266
|
-
const assets = await __classPrivateFieldGet(this, _MultichainAssetsController_instances, "m", _MultichainAssetsController_filterBlockaidSpamTokens).call(this, allAssets);
|
|
249
|
+
const assets = await __classPrivateFieldGet(this, _MultichainAssetsController_instances, "m", _MultichainAssetsController_getAssetsList).call(this, account.id, account.metadata.snap.id);
|
|
267
250
|
await __classPrivateFieldGet(this, _MultichainAssetsController_instances, "m", _MultichainAssetsController_refreshAssetsMetadata).call(this, assets);
|
|
268
251
|
this.update((state) => {
|
|
269
252
|
state.accountsAssets[account.id] = assets;
|
|
@@ -408,77 +391,6 @@ async function _MultichainAssetsController_getAssetsMetadataFrom(assets, snapId)
|
|
|
408
391
|
console.error(error);
|
|
409
392
|
return undefined;
|
|
410
393
|
}
|
|
411
|
-
}, _MultichainAssetsController_scanBlockaidTokens =
|
|
412
|
-
/**
|
|
413
|
-
* Calls the Blockaid token bulk scanning endpoint for a given chain and
|
|
414
|
-
* set of token addresses.
|
|
415
|
-
*
|
|
416
|
-
* @param chain - The chain name for the Blockaid API (e.g. "solana").
|
|
417
|
-
* @param tokens - The token addresses to scan.
|
|
418
|
-
* @returns The scan response, or null if the request fails or times out.
|
|
419
|
-
*/
|
|
420
|
-
async function _MultichainAssetsController_scanBlockaidTokens(chain, tokens) {
|
|
421
|
-
try {
|
|
422
|
-
return await handleFetch(`${SECURITY_ALERTS_BASE_URL}${TOKEN_BULK_SCANNING_ENDPOINT}`, {
|
|
423
|
-
method: 'POST',
|
|
424
|
-
headers: {
|
|
425
|
-
Accept: 'application/json',
|
|
426
|
-
'Content-Type': 'application/json',
|
|
427
|
-
},
|
|
428
|
-
body: JSON.stringify({ chain, tokens }),
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
catch (error) {
|
|
432
|
-
console.error('Blockaid token scan failed:', error);
|
|
433
|
-
return null;
|
|
434
|
-
}
|
|
435
|
-
}, _MultichainAssetsController_filterBlockaidSpamTokens =
|
|
436
|
-
/**
|
|
437
|
-
* Filters out tokens flagged as non-benign by Blockaid. Only tokens with
|
|
438
|
-
* an `assetNamespace` of "token" are scanned (native assets like slip44 are
|
|
439
|
-
* passed through unfiltered). If the API call fails, all tokens are kept.
|
|
440
|
-
*
|
|
441
|
-
* @param assets - The CAIP asset type list to filter.
|
|
442
|
-
* @returns The filtered list with malicious/spam/warning tokens removed.
|
|
443
|
-
*/
|
|
444
|
-
async function _MultichainAssetsController_filterBlockaidSpamTokens(assets) {
|
|
445
|
-
// Group scannable token assets by chain namespace
|
|
446
|
-
const tokensByChain = {};
|
|
447
|
-
for (const asset of assets) {
|
|
448
|
-
const { assetNamespace, assetReference, chain } = parseCaipAssetType(asset);
|
|
449
|
-
// Only scan fungible token assets (e.g. SPL tokens), skip native (slip44)
|
|
450
|
-
if (assetNamespace === 'token') {
|
|
451
|
-
const chainName = chain.namespace;
|
|
452
|
-
if (!tokensByChain[chainName]) {
|
|
453
|
-
tokensByChain[chainName] = [];
|
|
454
|
-
}
|
|
455
|
-
tokensByChain[chainName].push({ asset, address: assetReference });
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
// If there are no token assets to scan, return as-is
|
|
459
|
-
if (Object.keys(tokensByChain).length === 0) {
|
|
460
|
-
return assets;
|
|
461
|
-
}
|
|
462
|
-
// Build a set of assets to reject (non-benign tokens)
|
|
463
|
-
const rejectedAssets = new Set();
|
|
464
|
-
for (const [chainName, tokenEntries] of Object.entries(tokensByChain)) {
|
|
465
|
-
const addresses = tokenEntries.map((entry) => entry.address);
|
|
466
|
-
const scanResponse = await __classPrivateFieldGet(this, _MultichainAssetsController_instances, "m", _MultichainAssetsController_scanBlockaidTokens).call(this, chainName, addresses);
|
|
467
|
-
if (!scanResponse?.results) {
|
|
468
|
-
// If the scan fails, keep all tokens (fail open)
|
|
469
|
-
continue;
|
|
470
|
-
}
|
|
471
|
-
for (const entry of tokenEntries) {
|
|
472
|
-
const result = scanResponse.results[entry.address];
|
|
473
|
-
// Reject the token only if we have a definitive non-benign result
|
|
474
|
-
if (result?.result_type &&
|
|
475
|
-
result.result_type !== BlockaidResultType.Benign) {
|
|
476
|
-
rejectedAssets.add(entry.asset);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
// Filter while preserving original order
|
|
481
|
-
return assets.filter((asset) => !rejectedAssets.has(asset));
|
|
482
394
|
}, _MultichainAssetsController_getAssetsList =
|
|
483
395
|
/**
|
|
484
396
|
* Get assets list for an account
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MultichainAssetsController.mjs","sourceRoot":"","sources":["../../src/MultichainAssetsController/MultichainAssetsController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountsControllerAccountAddedEvent,\n AccountsControllerAccountAssetListUpdatedEvent,\n AccountsControllerAccountRemovedEvent,\n AccountsControllerListMultichainAccountsAction,\n} from '@metamask/accounts-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { handleFetch } from '@metamask/controller-utils';\nimport { isEvmAccountType } from '@metamask/keyring-api';\nimport type {\n AccountAssetListUpdatedEventPayload,\n CaipAssetType,\n CaipAssetTypeOrId,\n} from '@metamask/keyring-api';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport { KeyringClient } from '@metamask/keyring-snap-client';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n GetPermissions,\n PermissionConstraint,\n SubjectPermissions,\n} from '@metamask/permission-controller';\nimport type {\n GetAllSnaps,\n HandleSnapRequest,\n} from '@metamask/snaps-controllers';\nimport type { FungibleAssetMetadata, Snap, SnapId } from '@metamask/snaps-sdk';\nimport { HandlerType } from '@metamask/snaps-utils';\nimport { isCaipAssetType, parseCaipAssetType } from '@metamask/utils';\nimport type { CaipChainId } from '@metamask/utils';\nimport type { Json, JsonRpcRequest } from '@metamask/utils';\nimport type { MutexInterface } from 'async-mutex';\nimport { Mutex } from 'async-mutex';\n\nimport { getChainIdsCaveat } from './utils';\n\nconst controllerName = 'MultichainAssetsController';\n\n// Blockaid token security scanning constants\nconst SECURITY_ALERTS_BASE_URL = 'https://security-alerts.api.cx.metamask.io';\nconst TOKEN_BULK_SCANNING_ENDPOINT = '/token/scan-bulk';\n\n/**\n * Result type from Blockaid token security scan.\n */\nexport enum BlockaidResultType {\n Benign = 'Benign',\n Spam = 'Spam',\n Warning = 'Warning',\n Malicious = 'Malicious',\n}\n\n/**\n * Response shape from the Blockaid token bulk scanning endpoint.\n */\ntype BlockaidTokenScanResponse = {\n results: Record<\n string,\n {\n /* eslint-disable @typescript-eslint/naming-convention */\n result_type: BlockaidResultType;\n malicious_score: string;\n attack_types: Record<string, unknown>;\n /* eslint-enable @typescript-eslint/naming-convention */\n chain: string;\n address: string;\n }\n >;\n};\n\nexport type MultichainAssetsControllerState = {\n assetsMetadata: {\n [asset: CaipAssetType]: FungibleAssetMetadata;\n };\n accountsAssets: { [account: string]: CaipAssetType[] };\n allIgnoredAssets: { [account: string]: CaipAssetType[] };\n};\n\n// Represents the response of the asset snap's onAssetLookup handler\nexport type AssetMetadataResponse = {\n assets: {\n [asset: CaipAssetType]: FungibleAssetMetadata;\n };\n};\n\nexport type MultichainAssetsControllerAccountAssetListUpdatedEvent = {\n type: `${typeof controllerName}:accountAssetListUpdated`;\n payload: AccountsControllerAccountAssetListUpdatedEvent['payload'];\n};\n\n/**\n * Constructs the default {@link MultichainAssetsController} state. This allows\n * consumers to provide a partial state object when initializing the controller\n * and also helps in constructing complete state objects for this controller in\n * tests.\n *\n * @returns The default {@link MultichainAssetsController} state.\n */\nexport function getDefaultMultichainAssetsControllerState(): MultichainAssetsControllerState {\n return { accountsAssets: {}, assetsMetadata: {}, allIgnoredAssets: {} };\n}\n\nexport type MultichainAssetsControllerGetAssetMetadataAction = {\n type: `${typeof controllerName}:getAssetMetadata`;\n handler: MultichainAssetsController['getAssetMetadata'];\n};\n\nexport type MultichainAssetsControllerIgnoreAssetsAction = {\n type: `${typeof controllerName}:ignoreAssets`;\n handler: MultichainAssetsController['ignoreAssets'];\n};\n\nexport type MultichainAssetsControllerAddAssetsAction = {\n type: `${typeof controllerName}:addAssets`;\n handler: MultichainAssetsController['addAssets'];\n};\n\n/**\n * Returns the state of the {@link MultichainAssetsController}.\n */\nexport type MultichainAssetsControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n MultichainAssetsControllerState\n>;\n\n/**\n * Event emitted when the state of the {@link MultichainAssetsController} changes.\n */\nexport type MultichainAssetsControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n MultichainAssetsControllerState\n >;\n\n/**\n * Actions exposed by the {@link MultichainAssetsController}.\n */\nexport type MultichainAssetsControllerActions =\n | MultichainAssetsControllerGetStateAction\n | MultichainAssetsControllerGetAssetMetadataAction\n | MultichainAssetsControllerIgnoreAssetsAction\n | MultichainAssetsControllerAddAssetsAction;\n\n/**\n * Events emitted by {@link MultichainAssetsController}.\n */\nexport type MultichainAssetsControllerEvents =\n | MultichainAssetsControllerStateChangeEvent\n | MultichainAssetsControllerAccountAssetListUpdatedEvent;\n\n/**\n * A function executed within a mutually exclusive lock, with\n * a mutex releaser in its option bag.\n *\n * @param releaseLock - A function to release the lock.\n */\ntype MutuallyExclusiveCallback<Result> = ({\n releaseLock,\n}: {\n releaseLock: MutexInterface.Releaser;\n}) => Promise<Result>;\n\n/**\n * Actions that this controller is allowed to call.\n */\ntype AllowedActions =\n | HandleSnapRequest\n | GetAllSnaps\n | GetPermissions\n | AccountsControllerListMultichainAccountsAction;\n\n/**\n * Events that this controller is allowed to subscribe.\n */\ntype AllowedEvents =\n | AccountsControllerAccountAddedEvent\n | AccountsControllerAccountRemovedEvent\n | AccountsControllerAccountAssetListUpdatedEvent;\n\n/**\n * Messenger type for the MultichainAssetsController.\n */\nexport type MultichainAssetsControllerMessenger = Messenger<\n typeof controllerName,\n MultichainAssetsControllerActions | AllowedActions,\n MultichainAssetsControllerEvents | AllowedEvents\n>;\n\n/**\n * {@link MultichainAssetsController}'s metadata.\n *\n * This allows us to choose if fields of the state should be persisted or not\n * using the `persist` flag; and if they can be sent to Sentry or not, using\n * the `anonymous` flag.\n */\nconst assetsControllerMetadata: StateMetadata<MultichainAssetsControllerState> =\n {\n assetsMetadata: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n accountsAssets: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n allIgnoredAssets: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n };\n\n// TODO: make this controller extends StaticIntervalPollingController and update all assetsMetadata once a day.\n\nexport class MultichainAssetsController extends BaseController<\n typeof controllerName,\n MultichainAssetsControllerState,\n MultichainAssetsControllerMessenger\n> {\n // Mapping of CAIP-2 Chain ID to Asset Snaps.\n #snaps: Record<CaipChainId, Snap[]>;\n\n readonly #controllerOperationMutex = new Mutex();\n\n constructor({\n messenger,\n state = {},\n }: {\n messenger: MultichainAssetsControllerMessenger;\n state?: Partial<MultichainAssetsControllerState>;\n }) {\n super({\n messenger,\n name: controllerName,\n metadata: assetsControllerMetadata,\n state: {\n ...getDefaultMultichainAssetsControllerState(),\n ...state,\n },\n });\n\n this.#snaps = {};\n\n this.messenger.subscribe(\n 'AccountsController:accountAdded',\n async (account) => await this.#handleOnAccountAddedEvent(account),\n );\n this.messenger.subscribe(\n 'AccountsController:accountRemoved',\n async (account) => await this.#handleOnAccountRemovedEvent(account),\n );\n this.messenger.subscribe(\n 'AccountsController:accountAssetListUpdated',\n async (event) => await this.#handleAccountAssetListUpdatedEvent(event),\n );\n\n this.#registerMessageHandlers();\n }\n\n async #handleAccountAssetListUpdatedEvent(\n event: AccountAssetListUpdatedEventPayload,\n ) {\n return this.#withControllerLock(async () =>\n this.#handleAccountAssetListUpdated(event),\n );\n }\n\n async #handleOnAccountAddedEvent(account: InternalAccount) {\n return this.#withControllerLock(async () =>\n this.#handleOnAccountAdded(account),\n );\n }\n\n /**\n * Constructor helper for registering the controller's messaging system\n * actions.\n */\n #registerMessageHandlers() {\n this.messenger.registerActionHandler(\n 'MultichainAssetsController:getAssetMetadata',\n this.getAssetMetadata.bind(this),\n );\n\n this.messenger.registerActionHandler(\n 'MultichainAssetsController:ignoreAssets',\n this.ignoreAssets.bind(this),\n );\n\n this.messenger.registerActionHandler(\n 'MultichainAssetsController:addAssets',\n this.addAssets.bind(this),\n );\n }\n\n /**\n * Returns the metadata for the given asset\n *\n * @param asset - The asset to get metadata for\n * @returns The metadata for the asset or undefined if not found.\n */\n getAssetMetadata(asset: CaipAssetType): FungibleAssetMetadata | undefined {\n return this.state.assetsMetadata[asset];\n }\n\n /**\n * Ignores a batch of assets for a specific account.\n *\n * @param assetsToIgnore - Array of asset IDs to ignore.\n * @param accountId - The account ID to ignore assets for.\n */\n ignoreAssets(assetsToIgnore: CaipAssetType[], accountId: string): void {\n this.update((state) => {\n if (state.accountsAssets[accountId]) {\n state.accountsAssets[accountId] = state.accountsAssets[\n accountId\n ].filter((asset) => !assetsToIgnore.includes(asset));\n }\n\n if (!state.allIgnoredAssets[accountId]) {\n state.allIgnoredAssets[accountId] = [];\n }\n\n const newIgnoredAssets = assetsToIgnore.filter(\n (asset) => !state.allIgnoredAssets[accountId].includes(asset),\n );\n state.allIgnoredAssets[accountId].push(...newIgnoredAssets);\n });\n }\n\n /**\n * Adds multiple assets to the stored asset list for a specific account.\n * All assets must belong to the same chain.\n *\n * @param assetIds - Array of CAIP asset IDs to add (must be from same chain).\n * @param accountId - The account ID to add the assets to.\n * @returns The updated asset list for the account.\n * @throws Error if assets are from different chains.\n */\n async addAssets(\n assetIds: CaipAssetType[],\n accountId: string,\n ): Promise<CaipAssetType[]> {\n if (assetIds.length === 0) {\n return this.state.accountsAssets[accountId] || [];\n }\n\n // Validate that all assets are from the same chain\n const chainIds = new Set(\n assetIds.map((assetId) => parseCaipAssetType(assetId).chainId),\n );\n if (chainIds.size > 1) {\n throw new Error(\n `All assets must belong to the same chain. Found assets from chains: ${Array.from(chainIds).join(', ')}`,\n );\n }\n\n return this.#withControllerLock(async () => {\n // Refresh metadata for all assets\n await this.#refreshAssetsMetadata(assetIds);\n\n const addedAssets: CaipAssetType[] = [];\n\n this.update((state) => {\n // Initialize account assets if it doesn't exist\n if (!state.accountsAssets[accountId]) {\n state.accountsAssets[accountId] = [];\n }\n\n // Add assets if they don't already exist\n for (const assetId of assetIds) {\n if (!state.accountsAssets[accountId].includes(assetId)) {\n state.accountsAssets[accountId].push(assetId);\n addedAssets.push(assetId);\n }\n }\n\n // Remove from ignored list if they exist there (inline logic like EVM)\n if (state.allIgnoredAssets[accountId]) {\n state.allIgnoredAssets[accountId] = state.allIgnoredAssets[\n accountId\n ].filter((asset) => !assetIds.includes(asset));\n\n // Clean up empty arrays\n if (state.allIgnoredAssets[accountId].length === 0) {\n delete state.allIgnoredAssets[accountId];\n }\n }\n });\n\n // Publish event to notify other controllers (balances, rates) about the new assets\n if (addedAssets.length > 0) {\n this.messenger.publish(`${controllerName}:accountAssetListUpdated`, {\n assets: {\n [accountId]: {\n added: addedAssets,\n removed: [],\n },\n },\n });\n }\n\n return this.state.accountsAssets[accountId] || [];\n });\n }\n\n /**\n * Checks if an asset is ignored for a specific account.\n *\n * @param asset - The asset ID to check.\n * @param accountId - The account ID to check for.\n * @returns True if the asset is ignored, false otherwise.\n */\n #isAssetIgnored(asset: CaipAssetType, accountId: string): boolean {\n return this.state.allIgnoredAssets[accountId]?.includes(asset) ?? false;\n }\n\n /**\n * Function to update the assets list for an account\n *\n * @param event - The list of assets to update\n */\n async #handleAccountAssetListUpdated(\n event: AccountAssetListUpdatedEventPayload,\n ) {\n this.#assertControllerMutexIsLocked();\n\n const assetsForMetadataRefresh = new Set<CaipAssetType>([]);\n const accountsAndAssetsToUpdate: AccountAssetListUpdatedEventPayload['assets'] =\n {};\n for (const [accountId, { added, removed }] of Object.entries(\n event.assets,\n )) {\n if (added.length > 0 || removed.length > 0) {\n const existing = this.state.accountsAssets[accountId] || [];\n\n // In case accountsAndAssetsToUpdate event is fired with \"added\" assets that already exist, we don't want to add them again\n // Also filter out ignored assets\n const preFilteredToBeAddedAssets = added.filter(\n (asset) =>\n !existing.includes(asset) &&\n isCaipAssetType(asset) &&\n !this.#isAssetIgnored(asset, accountId),\n );\n\n // Filter out tokens flagged by Blockaid as non-benign\n const filteredToBeAddedAssets = await this.#filterBlockaidSpamTokens(\n preFilteredToBeAddedAssets,\n );\n\n // In case accountsAndAssetsToUpdate event is fired with \"removed\" assets that don't exist, we don't want to remove them\n const filteredToBeRemovedAssets = removed.filter(\n (asset) => existing.includes(asset) && isCaipAssetType(asset),\n );\n\n if (\n filteredToBeAddedAssets.length > 0 ||\n filteredToBeRemovedAssets.length > 0\n ) {\n accountsAndAssetsToUpdate[accountId] = {\n added: filteredToBeAddedAssets,\n removed: filteredToBeRemovedAssets,\n };\n }\n\n for (const asset of existing) {\n assetsForMetadataRefresh.add(asset);\n }\n for (const asset of filteredToBeAddedAssets) {\n assetsForMetadataRefresh.add(asset);\n }\n for (const asset of filteredToBeRemovedAssets) {\n assetsForMetadataRefresh.delete(asset);\n }\n }\n }\n\n this.update((state) => {\n for (const [accountId, { added, removed }] of Object.entries(\n accountsAndAssetsToUpdate,\n )) {\n const assets = new Set([\n ...(state.accountsAssets[accountId] || []),\n ...added,\n ]);\n for (const asset of removed) {\n assets.delete(asset);\n }\n\n state.accountsAssets[accountId] = Array.from(assets);\n }\n });\n\n // Trigger fetching metadata for new assets\n await this.#refreshAssetsMetadata(Array.from(assetsForMetadataRefresh));\n\n this.messenger.publish(`${controllerName}:accountAssetListUpdated`, {\n assets: accountsAndAssetsToUpdate,\n });\n }\n\n /**\n * Checks for non-EVM accounts.\n *\n * @param account - The new account to be checked.\n * @returns True if the account is a non-EVM account, false otherwise.\n */\n #isNonEvmAccount(account: InternalAccount): boolean {\n return (\n !isEvmAccountType(account.type) &&\n // Non-EVM accounts are backed by a Snap for now\n account.metadata.snap !== undefined\n );\n }\n\n /**\n * Handles changes when a new account has been added.\n *\n * @param account - The new account being added.\n */\n async #handleOnAccountAdded(account: InternalAccount): Promise<void> {\n if (!this.#isNonEvmAccount(account)) {\n // Nothing to do here for EVM accounts\n return;\n }\n this.#assertControllerMutexIsLocked();\n\n // Get assets list\n if (account.metadata.snap) {\n const allAssets = await this.#getAssetsList(\n account.id,\n account.metadata.snap.id,\n );\n const assets = await this.#filterBlockaidSpamTokens(allAssets);\n await this.#refreshAssetsMetadata(assets);\n this.update((state) => {\n state.accountsAssets[account.id] = assets;\n });\n this.messenger.publish(`${controllerName}:accountAssetListUpdated`, {\n assets: {\n [account.id]: {\n added: assets,\n removed: [],\n },\n },\n });\n }\n }\n\n /**\n * Handles changes when a new account has been removed.\n *\n * @param accountId - The new account id being removed.\n */\n async #handleOnAccountRemovedEvent(accountId: string): Promise<void> {\n this.update((state) => {\n if (state.accountsAssets[accountId]) {\n delete state.accountsAssets[accountId];\n }\n if (state.allIgnoredAssets[accountId]) {\n delete state.allIgnoredAssets[accountId];\n }\n // TODO: We are not deleting the assetsMetadata because we will soon make this controller extends StaticIntervalPollingController\n // and update all assetsMetadata once a day.\n });\n }\n\n /**\n * Refreshes the assets snaps and metadata for the given list of assets\n *\n * @param assets - The assets to refresh\n */\n async #refreshAssetsMetadata(assets: CaipAssetType[]) {\n this.#assertControllerMutexIsLocked();\n\n const assetsWithoutMetadata: CaipAssetType[] = assets.filter(\n (asset) => !this.state.assetsMetadata[asset],\n );\n\n // Call the snap to get the metadata\n if (assetsWithoutMetadata.length > 0) {\n // Check if for every asset in assetsWithoutMetadata there is a snap in snaps by chainId else call getAssetSnaps\n if (\n !assetsWithoutMetadata.every((asset: CaipAssetType) => {\n const { chainId } = parseCaipAssetType(asset);\n return Boolean(this.#getAssetSnapFor(chainId));\n })\n ) {\n this.#snaps = this.#getAssetSnaps();\n }\n await this.#updateAssetsMetadata(assetsWithoutMetadata);\n }\n }\n\n /**\n * Updates the assets metadata for the given list of assets\n *\n * @param assets - The assets to update\n */\n async #updateAssetsMetadata(assets: CaipAssetType[]) {\n // Creates a mapping of scope to their respective assets list.\n const assetsByScope: Record<CaipChainId, CaipAssetType[]> = {};\n for (const asset of assets) {\n const { chainId } = parseCaipAssetType(asset);\n if (!assetsByScope[chainId]) {\n assetsByScope[chainId] = [];\n }\n assetsByScope[chainId].push(asset);\n }\n\n let newMetadata: Record<CaipAssetType, FungibleAssetMetadata> = {};\n for (const chainId of Object.keys(assetsByScope) as CaipChainId[]) {\n const assetsForChain = assetsByScope[chainId];\n // Now fetch metadata from the associated asset Snaps:\n const snap = this.#getAssetSnapFor(chainId);\n if (snap) {\n const metadata = await this.#getAssetsMetadataFrom(\n assetsForChain,\n snap.id,\n );\n newMetadata = {\n ...newMetadata,\n ...(metadata?.assets ?? {}),\n };\n }\n }\n this.update((state) => {\n state.assetsMetadata = {\n ...this.state.assetsMetadata,\n ...newMetadata,\n };\n });\n }\n\n /**\n * Creates a mapping of CAIP-2 Chain ID to Asset Snaps.\n *\n * @returns A mapping of CAIP-2 Chain ID to Asset Snaps.\n */\n #getAssetSnaps(): Record<CaipChainId, Snap[]> {\n const snaps: Record<CaipChainId, Snap[]> = {};\n const allSnaps = this.#getAllSnaps();\n const allPermissions = allSnaps.map((snap) =>\n this.#getSnapsPermissions(snap.id),\n );\n\n for (const [index, permission] of allPermissions.entries()) {\n let scopes;\n for (const singlePermissionConstraint of Object.values(permission)) {\n scopes = getChainIdsCaveat(singlePermissionConstraint);\n if (!scopes) {\n continue;\n }\n for (const scope of scopes as CaipChainId[]) {\n if (!snaps[scope]) {\n snaps[scope] = [];\n }\n snaps[scope].push(allSnaps[index]);\n }\n }\n }\n return snaps;\n }\n\n /**\n * Returns the first asset snap for the given scope\n *\n * @param scope - The scope to get the asset snap for\n * @returns The asset snap for the given scope\n */\n #getAssetSnapFor(scope: CaipChainId): Snap | undefined {\n const allSnaps = this.#snaps[scope];\n // Pick only the first one, we ignore the other Snaps if there are multiple candidates for now.\n return allSnaps?.[0]; // Will be undefined if there's no Snaps candidate for this scope.\n }\n\n /**\n * Returns all the asset snaps\n *\n * @returns All the asset snaps\n */\n #getAllSnaps(): Snap[] {\n // TODO: Use dedicated SnapController's action once available for this:\n return this.messenger\n .call('SnapController:getAll')\n .filter((snap) => snap.enabled && !snap.blocked);\n }\n\n /**\n * Returns the permissions for the given origin\n *\n * @param origin - The origin to get the permissions for\n * @returns The permissions for the given origin\n */\n #getSnapsPermissions(\n origin: string,\n ): SubjectPermissions<PermissionConstraint> {\n return this.messenger.call(\n 'PermissionController:getPermissions',\n origin,\n ) as SubjectPermissions<PermissionConstraint>;\n }\n\n /**\n * Returns the metadata for the given assets\n *\n * @param assets - The assets to get metadata for\n * @param snapId - The snap ID to get metadata from\n * @returns The metadata for the assets\n */\n async #getAssetsMetadataFrom(\n assets: CaipAssetType[],\n snapId: string,\n ): Promise<AssetMetadataResponse | undefined> {\n try {\n return (await this.messenger.call('SnapController:handleRequest', {\n snapId: snapId as SnapId,\n origin: 'metamask',\n handler: HandlerType.OnAssetsLookup,\n request: {\n jsonrpc: '2.0',\n method: 'onAssetLookup',\n params: {\n assets,\n },\n },\n })) as Promise<AssetMetadataResponse>;\n } catch (error) {\n // Ignore\n console.error(error);\n return undefined;\n }\n }\n\n /**\n * Calls the Blockaid token bulk scanning endpoint for a given chain and\n * set of token addresses.\n *\n * @param chain - The chain name for the Blockaid API (e.g. \"solana\").\n * @param tokens - The token addresses to scan.\n * @returns The scan response, or null if the request fails or times out.\n */\n async #scanBlockaidTokens(\n chain: string,\n tokens: string[],\n ): Promise<BlockaidTokenScanResponse | null> {\n try {\n return await handleFetch(\n `${SECURITY_ALERTS_BASE_URL}${TOKEN_BULK_SCANNING_ENDPOINT}`,\n {\n method: 'POST',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ chain, tokens }),\n },\n );\n } catch (error) {\n console.error('Blockaid token scan failed:', error);\n return null;\n }\n }\n\n /**\n * Filters out tokens flagged as non-benign by Blockaid. Only tokens with\n * an `assetNamespace` of \"token\" are scanned (native assets like slip44 are\n * passed through unfiltered). If the API call fails, all tokens are kept.\n *\n * @param assets - The CAIP asset type list to filter.\n * @returns The filtered list with malicious/spam/warning tokens removed.\n */\n async #filterBlockaidSpamTokens(\n assets: CaipAssetType[],\n ): Promise<CaipAssetType[]> {\n // Group scannable token assets by chain namespace\n const tokensByChain: Record<\n string,\n { asset: CaipAssetType; address: string }[]\n > = {};\n\n for (const asset of assets) {\n const { assetNamespace, assetReference, chain } =\n parseCaipAssetType(asset);\n\n // Only scan fungible token assets (e.g. SPL tokens), skip native (slip44)\n if (assetNamespace === 'token') {\n const chainName = chain.namespace;\n if (!tokensByChain[chainName]) {\n tokensByChain[chainName] = [];\n }\n tokensByChain[chainName].push({ asset, address: assetReference });\n }\n }\n\n // If there are no token assets to scan, return as-is\n if (Object.keys(tokensByChain).length === 0) {\n return assets;\n }\n\n // Build a set of assets to reject (non-benign tokens)\n const rejectedAssets = new Set<CaipAssetType>();\n\n for (const [chainName, tokenEntries] of Object.entries(tokensByChain)) {\n const addresses = tokenEntries.map((entry) => entry.address);\n const scanResponse = await this.#scanBlockaidTokens(chainName, addresses);\n\n if (!scanResponse?.results) {\n // If the scan fails, keep all tokens (fail open)\n continue;\n }\n\n for (const entry of tokenEntries) {\n const result = scanResponse.results[entry.address];\n // Reject the token only if we have a definitive non-benign result\n if (\n result?.result_type &&\n result.result_type !== BlockaidResultType.Benign\n ) {\n rejectedAssets.add(entry.asset);\n }\n }\n }\n\n // Filter while preserving original order\n return assets.filter((asset) => !rejectedAssets.has(asset));\n }\n\n /**\n * Get assets list for an account\n *\n * @param accountId - AccountId to get assets for\n * @param snapId - Snap ID for the account\n * @returns list of assets\n */\n async #getAssetsList(\n accountId: string,\n snapId: string,\n ): Promise<CaipAssetTypeOrId[]> {\n return await this.#getClient(snapId).listAccountAssets(accountId);\n }\n\n /**\n * Gets a `KeyringClient` for a Snap.\n *\n * @param snapId - ID of the Snap to get the client for.\n * @returns A `KeyringClient` for the Snap.\n */\n #getClient(snapId: string): KeyringClient {\n return new KeyringClient({\n send: async (request: JsonRpcRequest) =>\n (await this.messenger.call('SnapController:handleRequest', {\n snapId: snapId as SnapId,\n origin: 'metamask',\n handler: HandlerType.OnKeyringRequest,\n request,\n })) as Promise<Json>,\n });\n }\n\n /**\n * Assert that the controller mutex is locked.\n *\n * @throws If the controller mutex is not locked.\n */\n #assertControllerMutexIsLocked() {\n if (!this.#controllerOperationMutex.isLocked()) {\n throw new Error(\n 'MultichainAssetsControllerError - Attempt to update state',\n );\n }\n }\n\n /**\n * Lock the controller mutex before executing the given function,\n * and release it after the function is resolved or after an\n * error is thrown.\n *\n * This wrapper ensures that each mutable operation that interacts with the\n * controller and that changes its state is executed in a mutually exclusive way,\n * preventing unsafe concurrent access that could lead to unpredictable behavior.\n *\n * @param callback - The function to execute while the controller mutex is locked.\n * @returns The result of the function.\n */\n async #withControllerLock<Result>(\n callback: MutuallyExclusiveCallback<Result>,\n ): Promise<Result> {\n return withLock(this.#controllerOperationMutex, callback);\n }\n}\n\n/**\n * Lock the given mutex before executing the given function,\n * and release it after the function is resolved or after an\n * error is thrown.\n *\n * @param mutex - The mutex to lock.\n * @param callback - The function to execute while the mutex is locked.\n * @returns The result of the function.\n */\nasync function withLock<Result>(\n mutex: Mutex,\n callback: MutuallyExclusiveCallback<Result>,\n): Promise<Result> {\n const releaseLock = await mutex.acquire();\n\n try {\n return await callback({ releaseLock });\n } finally {\n releaseLock();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"MultichainAssetsController.mjs","sourceRoot":"","sources":["../../src/MultichainAssetsController/MultichainAssetsController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountsControllerAccountAddedEvent,\n AccountsControllerAccountAssetListUpdatedEvent,\n AccountsControllerAccountRemovedEvent,\n AccountsControllerListMultichainAccountsAction,\n} from '@metamask/accounts-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { isEvmAccountType } from '@metamask/keyring-api';\nimport type {\n AccountAssetListUpdatedEventPayload,\n CaipAssetType,\n CaipAssetTypeOrId,\n} from '@metamask/keyring-api';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport { KeyringClient } from '@metamask/keyring-snap-client';\nimport type { Messenger } from '@metamask/messenger';\nimport type {\n GetPermissions,\n PermissionConstraint,\n SubjectPermissions,\n} from '@metamask/permission-controller';\nimport type {\n GetAllSnaps,\n HandleSnapRequest,\n} from '@metamask/snaps-controllers';\nimport type { FungibleAssetMetadata, Snap, SnapId } from '@metamask/snaps-sdk';\nimport { HandlerType } from '@metamask/snaps-utils';\nimport { isCaipAssetType, parseCaipAssetType } from '@metamask/utils';\nimport type { CaipChainId } from '@metamask/utils';\nimport type { Json, JsonRpcRequest } from '@metamask/utils';\nimport type { MutexInterface } from 'async-mutex';\nimport { Mutex } from 'async-mutex';\n\nimport { getChainIdsCaveat } from './utils';\n\nconst controllerName = 'MultichainAssetsController';\n\nexport type MultichainAssetsControllerState = {\n assetsMetadata: {\n [asset: CaipAssetType]: FungibleAssetMetadata;\n };\n accountsAssets: { [account: string]: CaipAssetType[] };\n allIgnoredAssets: { [account: string]: CaipAssetType[] };\n};\n\n// Represents the response of the asset snap's onAssetLookup handler\nexport type AssetMetadataResponse = {\n assets: {\n [asset: CaipAssetType]: FungibleAssetMetadata;\n };\n};\n\nexport type MultichainAssetsControllerAccountAssetListUpdatedEvent = {\n type: `${typeof controllerName}:accountAssetListUpdated`;\n payload: AccountsControllerAccountAssetListUpdatedEvent['payload'];\n};\n\n/**\n * Constructs the default {@link MultichainAssetsController} state. This allows\n * consumers to provide a partial state object when initializing the controller\n * and also helps in constructing complete state objects for this controller in\n * tests.\n *\n * @returns The default {@link MultichainAssetsController} state.\n */\nexport function getDefaultMultichainAssetsControllerState(): MultichainAssetsControllerState {\n return { accountsAssets: {}, assetsMetadata: {}, allIgnoredAssets: {} };\n}\n\nexport type MultichainAssetsControllerGetAssetMetadataAction = {\n type: `${typeof controllerName}:getAssetMetadata`;\n handler: MultichainAssetsController['getAssetMetadata'];\n};\n\nexport type MultichainAssetsControllerIgnoreAssetsAction = {\n type: `${typeof controllerName}:ignoreAssets`;\n handler: MultichainAssetsController['ignoreAssets'];\n};\n\nexport type MultichainAssetsControllerAddAssetsAction = {\n type: `${typeof controllerName}:addAssets`;\n handler: MultichainAssetsController['addAssets'];\n};\n\n/**\n * Returns the state of the {@link MultichainAssetsController}.\n */\nexport type MultichainAssetsControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n MultichainAssetsControllerState\n>;\n\n/**\n * Event emitted when the state of the {@link MultichainAssetsController} changes.\n */\nexport type MultichainAssetsControllerStateChangeEvent =\n ControllerStateChangeEvent<\n typeof controllerName,\n MultichainAssetsControllerState\n >;\n\n/**\n * Actions exposed by the {@link MultichainAssetsController}.\n */\nexport type MultichainAssetsControllerActions =\n | MultichainAssetsControllerGetStateAction\n | MultichainAssetsControllerGetAssetMetadataAction\n | MultichainAssetsControllerIgnoreAssetsAction\n | MultichainAssetsControllerAddAssetsAction;\n\n/**\n * Events emitted by {@link MultichainAssetsController}.\n */\nexport type MultichainAssetsControllerEvents =\n | MultichainAssetsControllerStateChangeEvent\n | MultichainAssetsControllerAccountAssetListUpdatedEvent;\n\n/**\n * A function executed within a mutually exclusive lock, with\n * a mutex releaser in its option bag.\n *\n * @param releaseLock - A function to release the lock.\n */\ntype MutuallyExclusiveCallback<Result> = ({\n releaseLock,\n}: {\n releaseLock: MutexInterface.Releaser;\n}) => Promise<Result>;\n\n/**\n * Actions that this controller is allowed to call.\n */\ntype AllowedActions =\n | HandleSnapRequest\n | GetAllSnaps\n | GetPermissions\n | AccountsControllerListMultichainAccountsAction;\n\n/**\n * Events that this controller is allowed to subscribe.\n */\ntype AllowedEvents =\n | AccountsControllerAccountAddedEvent\n | AccountsControllerAccountRemovedEvent\n | AccountsControllerAccountAssetListUpdatedEvent;\n\n/**\n * Messenger type for the MultichainAssetsController.\n */\nexport type MultichainAssetsControllerMessenger = Messenger<\n typeof controllerName,\n MultichainAssetsControllerActions | AllowedActions,\n MultichainAssetsControllerEvents | AllowedEvents\n>;\n\n/**\n * {@link MultichainAssetsController}'s metadata.\n *\n * This allows us to choose if fields of the state should be persisted or not\n * using the `persist` flag; and if they can be sent to Sentry or not, using\n * the `anonymous` flag.\n */\nconst assetsControllerMetadata: StateMetadata<MultichainAssetsControllerState> =\n {\n assetsMetadata: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n accountsAssets: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n allIgnoredAssets: {\n includeInStateLogs: false,\n persist: true,\n includeInDebugSnapshot: false,\n usedInUi: true,\n },\n };\n\n// TODO: make this controller extends StaticIntervalPollingController and update all assetsMetadata once a day.\n\nexport class MultichainAssetsController extends BaseController<\n typeof controllerName,\n MultichainAssetsControllerState,\n MultichainAssetsControllerMessenger\n> {\n // Mapping of CAIP-2 Chain ID to Asset Snaps.\n #snaps: Record<CaipChainId, Snap[]>;\n\n readonly #controllerOperationMutex = new Mutex();\n\n constructor({\n messenger,\n state = {},\n }: {\n messenger: MultichainAssetsControllerMessenger;\n state?: Partial<MultichainAssetsControllerState>;\n }) {\n super({\n messenger,\n name: controllerName,\n metadata: assetsControllerMetadata,\n state: {\n ...getDefaultMultichainAssetsControllerState(),\n ...state,\n },\n });\n\n this.#snaps = {};\n\n this.messenger.subscribe(\n 'AccountsController:accountAdded',\n async (account) => await this.#handleOnAccountAddedEvent(account),\n );\n this.messenger.subscribe(\n 'AccountsController:accountRemoved',\n async (account) => await this.#handleOnAccountRemovedEvent(account),\n );\n this.messenger.subscribe(\n 'AccountsController:accountAssetListUpdated',\n async (event) => await this.#handleAccountAssetListUpdatedEvent(event),\n );\n\n this.#registerMessageHandlers();\n }\n\n async #handleAccountAssetListUpdatedEvent(\n event: AccountAssetListUpdatedEventPayload,\n ) {\n return this.#withControllerLock(async () =>\n this.#handleAccountAssetListUpdated(event),\n );\n }\n\n async #handleOnAccountAddedEvent(account: InternalAccount) {\n return this.#withControllerLock(async () =>\n this.#handleOnAccountAdded(account),\n );\n }\n\n /**\n * Constructor helper for registering the controller's messaging system\n * actions.\n */\n #registerMessageHandlers() {\n this.messenger.registerActionHandler(\n 'MultichainAssetsController:getAssetMetadata',\n this.getAssetMetadata.bind(this),\n );\n\n this.messenger.registerActionHandler(\n 'MultichainAssetsController:ignoreAssets',\n this.ignoreAssets.bind(this),\n );\n\n this.messenger.registerActionHandler(\n 'MultichainAssetsController:addAssets',\n this.addAssets.bind(this),\n );\n }\n\n /**\n * Returns the metadata for the given asset\n *\n * @param asset - The asset to get metadata for\n * @returns The metadata for the asset or undefined if not found.\n */\n getAssetMetadata(asset: CaipAssetType): FungibleAssetMetadata | undefined {\n return this.state.assetsMetadata[asset];\n }\n\n /**\n * Ignores a batch of assets for a specific account.\n *\n * @param assetsToIgnore - Array of asset IDs to ignore.\n * @param accountId - The account ID to ignore assets for.\n */\n ignoreAssets(assetsToIgnore: CaipAssetType[], accountId: string): void {\n this.update((state) => {\n if (state.accountsAssets[accountId]) {\n state.accountsAssets[accountId] = state.accountsAssets[\n accountId\n ].filter((asset) => !assetsToIgnore.includes(asset));\n }\n\n if (!state.allIgnoredAssets[accountId]) {\n state.allIgnoredAssets[accountId] = [];\n }\n\n const newIgnoredAssets = assetsToIgnore.filter(\n (asset) => !state.allIgnoredAssets[accountId].includes(asset),\n );\n state.allIgnoredAssets[accountId].push(...newIgnoredAssets);\n });\n }\n\n /**\n * Adds multiple assets to the stored asset list for a specific account.\n * All assets must belong to the same chain.\n *\n * @param assetIds - Array of CAIP asset IDs to add (must be from same chain).\n * @param accountId - The account ID to add the assets to.\n * @returns The updated asset list for the account.\n * @throws Error if assets are from different chains.\n */\n async addAssets(\n assetIds: CaipAssetType[],\n accountId: string,\n ): Promise<CaipAssetType[]> {\n if (assetIds.length === 0) {\n return this.state.accountsAssets[accountId] || [];\n }\n\n // Validate that all assets are from the same chain\n const chainIds = new Set(\n assetIds.map((assetId) => parseCaipAssetType(assetId).chainId),\n );\n if (chainIds.size > 1) {\n throw new Error(\n `All assets must belong to the same chain. Found assets from chains: ${Array.from(chainIds).join(', ')}`,\n );\n }\n\n return this.#withControllerLock(async () => {\n // Refresh metadata for all assets\n await this.#refreshAssetsMetadata(assetIds);\n\n const addedAssets: CaipAssetType[] = [];\n\n this.update((state) => {\n // Initialize account assets if it doesn't exist\n if (!state.accountsAssets[accountId]) {\n state.accountsAssets[accountId] = [];\n }\n\n // Add assets if they don't already exist\n for (const assetId of assetIds) {\n if (!state.accountsAssets[accountId].includes(assetId)) {\n state.accountsAssets[accountId].push(assetId);\n addedAssets.push(assetId);\n }\n }\n\n // Remove from ignored list if they exist there (inline logic like EVM)\n if (state.allIgnoredAssets[accountId]) {\n state.allIgnoredAssets[accountId] = state.allIgnoredAssets[\n accountId\n ].filter((asset) => !assetIds.includes(asset));\n\n // Clean up empty arrays\n if (state.allIgnoredAssets[accountId].length === 0) {\n delete state.allIgnoredAssets[accountId];\n }\n }\n });\n\n // Publish event to notify other controllers (balances, rates) about the new assets\n if (addedAssets.length > 0) {\n this.messenger.publish(`${controllerName}:accountAssetListUpdated`, {\n assets: {\n [accountId]: {\n added: addedAssets,\n removed: [],\n },\n },\n });\n }\n\n return this.state.accountsAssets[accountId] || [];\n });\n }\n\n /**\n * Checks if an asset is ignored for a specific account.\n *\n * @param asset - The asset ID to check.\n * @param accountId - The account ID to check for.\n * @returns True if the asset is ignored, false otherwise.\n */\n #isAssetIgnored(asset: CaipAssetType, accountId: string): boolean {\n return this.state.allIgnoredAssets[accountId]?.includes(asset) ?? false;\n }\n\n /**\n * Function to update the assets list for an account\n *\n * @param event - The list of assets to update\n */\n async #handleAccountAssetListUpdated(\n event: AccountAssetListUpdatedEventPayload,\n ) {\n this.#assertControllerMutexIsLocked();\n\n const assetsForMetadataRefresh = new Set<CaipAssetType>([]);\n const accountsAndAssetsToUpdate: AccountAssetListUpdatedEventPayload['assets'] =\n {};\n for (const [accountId, { added, removed }] of Object.entries(\n event.assets,\n )) {\n if (added.length > 0 || removed.length > 0) {\n const existing = this.state.accountsAssets[accountId] || [];\n\n // In case accountsAndAssetsToUpdate event is fired with \"added\" assets that already exist, we don't want to add them again\n // Also filter out ignored assets\n const filteredToBeAddedAssets = added.filter(\n (asset) =>\n !existing.includes(asset) &&\n isCaipAssetType(asset) &&\n !this.#isAssetIgnored(asset, accountId),\n );\n\n // In case accountsAndAssetsToUpdate event is fired with \"removed\" assets that don't exist, we don't want to remove them\n const filteredToBeRemovedAssets = removed.filter(\n (asset) => existing.includes(asset) && isCaipAssetType(asset),\n );\n\n if (\n filteredToBeAddedAssets.length > 0 ||\n filteredToBeRemovedAssets.length > 0\n ) {\n accountsAndAssetsToUpdate[accountId] = {\n added: filteredToBeAddedAssets,\n removed: filteredToBeRemovedAssets,\n };\n }\n\n for (const asset of existing) {\n assetsForMetadataRefresh.add(asset);\n }\n for (const asset of filteredToBeAddedAssets) {\n assetsForMetadataRefresh.add(asset);\n }\n for (const asset of filteredToBeRemovedAssets) {\n assetsForMetadataRefresh.delete(asset);\n }\n }\n }\n\n this.update((state) => {\n for (const [accountId, { added, removed }] of Object.entries(\n accountsAndAssetsToUpdate,\n )) {\n const assets = new Set([\n ...(state.accountsAssets[accountId] || []),\n ...added,\n ]);\n for (const asset of removed) {\n assets.delete(asset);\n }\n\n state.accountsAssets[accountId] = Array.from(assets);\n }\n });\n\n // Trigger fetching metadata for new assets\n await this.#refreshAssetsMetadata(Array.from(assetsForMetadataRefresh));\n\n this.messenger.publish(`${controllerName}:accountAssetListUpdated`, {\n assets: accountsAndAssetsToUpdate,\n });\n }\n\n /**\n * Checks for non-EVM accounts.\n *\n * @param account - The new account to be checked.\n * @returns True if the account is a non-EVM account, false otherwise.\n */\n #isNonEvmAccount(account: InternalAccount): boolean {\n return (\n !isEvmAccountType(account.type) &&\n // Non-EVM accounts are backed by a Snap for now\n account.metadata.snap !== undefined\n );\n }\n\n /**\n * Handles changes when a new account has been added.\n *\n * @param account - The new account being added.\n */\n async #handleOnAccountAdded(account: InternalAccount): Promise<void> {\n if (!this.#isNonEvmAccount(account)) {\n // Nothing to do here for EVM accounts\n return;\n }\n this.#assertControllerMutexIsLocked();\n\n // Get assets list\n if (account.metadata.snap) {\n const assets = await this.#getAssetsList(\n account.id,\n account.metadata.snap.id,\n );\n await this.#refreshAssetsMetadata(assets);\n this.update((state) => {\n state.accountsAssets[account.id] = assets;\n });\n this.messenger.publish(`${controllerName}:accountAssetListUpdated`, {\n assets: {\n [account.id]: {\n added: assets,\n removed: [],\n },\n },\n });\n }\n }\n\n /**\n * Handles changes when a new account has been removed.\n *\n * @param accountId - The new account id being removed.\n */\n async #handleOnAccountRemovedEvent(accountId: string): Promise<void> {\n this.update((state) => {\n if (state.accountsAssets[accountId]) {\n delete state.accountsAssets[accountId];\n }\n if (state.allIgnoredAssets[accountId]) {\n delete state.allIgnoredAssets[accountId];\n }\n // TODO: We are not deleting the assetsMetadata because we will soon make this controller extends StaticIntervalPollingController\n // and update all assetsMetadata once a day.\n });\n }\n\n /**\n * Refreshes the assets snaps and metadata for the given list of assets\n *\n * @param assets - The assets to refresh\n */\n async #refreshAssetsMetadata(assets: CaipAssetType[]) {\n this.#assertControllerMutexIsLocked();\n\n const assetsWithoutMetadata: CaipAssetType[] = assets.filter(\n (asset) => !this.state.assetsMetadata[asset],\n );\n\n // Call the snap to get the metadata\n if (assetsWithoutMetadata.length > 0) {\n // Check if for every asset in assetsWithoutMetadata there is a snap in snaps by chainId else call getAssetSnaps\n if (\n !assetsWithoutMetadata.every((asset: CaipAssetType) => {\n const { chainId } = parseCaipAssetType(asset);\n return Boolean(this.#getAssetSnapFor(chainId));\n })\n ) {\n this.#snaps = this.#getAssetSnaps();\n }\n await this.#updateAssetsMetadata(assetsWithoutMetadata);\n }\n }\n\n /**\n * Updates the assets metadata for the given list of assets\n *\n * @param assets - The assets to update\n */\n async #updateAssetsMetadata(assets: CaipAssetType[]) {\n // Creates a mapping of scope to their respective assets list.\n const assetsByScope: Record<CaipChainId, CaipAssetType[]> = {};\n for (const asset of assets) {\n const { chainId } = parseCaipAssetType(asset);\n if (!assetsByScope[chainId]) {\n assetsByScope[chainId] = [];\n }\n assetsByScope[chainId].push(asset);\n }\n\n let newMetadata: Record<CaipAssetType, FungibleAssetMetadata> = {};\n for (const chainId of Object.keys(assetsByScope) as CaipChainId[]) {\n const assetsForChain = assetsByScope[chainId];\n // Now fetch metadata from the associated asset Snaps:\n const snap = this.#getAssetSnapFor(chainId);\n if (snap) {\n const metadata = await this.#getAssetsMetadataFrom(\n assetsForChain,\n snap.id,\n );\n newMetadata = {\n ...newMetadata,\n ...(metadata?.assets ?? {}),\n };\n }\n }\n this.update((state) => {\n state.assetsMetadata = {\n ...this.state.assetsMetadata,\n ...newMetadata,\n };\n });\n }\n\n /**\n * Creates a mapping of CAIP-2 Chain ID to Asset Snaps.\n *\n * @returns A mapping of CAIP-2 Chain ID to Asset Snaps.\n */\n #getAssetSnaps(): Record<CaipChainId, Snap[]> {\n const snaps: Record<CaipChainId, Snap[]> = {};\n const allSnaps = this.#getAllSnaps();\n const allPermissions = allSnaps.map((snap) =>\n this.#getSnapsPermissions(snap.id),\n );\n\n for (const [index, permission] of allPermissions.entries()) {\n let scopes;\n for (const singlePermissionConstraint of Object.values(permission)) {\n scopes = getChainIdsCaveat(singlePermissionConstraint);\n if (!scopes) {\n continue;\n }\n for (const scope of scopes as CaipChainId[]) {\n if (!snaps[scope]) {\n snaps[scope] = [];\n }\n snaps[scope].push(allSnaps[index]);\n }\n }\n }\n return snaps;\n }\n\n /**\n * Returns the first asset snap for the given scope\n *\n * @param scope - The scope to get the asset snap for\n * @returns The asset snap for the given scope\n */\n #getAssetSnapFor(scope: CaipChainId): Snap | undefined {\n const allSnaps = this.#snaps[scope];\n // Pick only the first one, we ignore the other Snaps if there are multiple candidates for now.\n return allSnaps?.[0]; // Will be undefined if there's no Snaps candidate for this scope.\n }\n\n /**\n * Returns all the asset snaps\n *\n * @returns All the asset snaps\n */\n #getAllSnaps(): Snap[] {\n // TODO: Use dedicated SnapController's action once available for this:\n return this.messenger\n .call('SnapController:getAll')\n .filter((snap) => snap.enabled && !snap.blocked);\n }\n\n /**\n * Returns the permissions for the given origin\n *\n * @param origin - The origin to get the permissions for\n * @returns The permissions for the given origin\n */\n #getSnapsPermissions(\n origin: string,\n ): SubjectPermissions<PermissionConstraint> {\n return this.messenger.call(\n 'PermissionController:getPermissions',\n origin,\n ) as SubjectPermissions<PermissionConstraint>;\n }\n\n /**\n * Returns the metadata for the given assets\n *\n * @param assets - The assets to get metadata for\n * @param snapId - The snap ID to get metadata from\n * @returns The metadata for the assets\n */\n async #getAssetsMetadataFrom(\n assets: CaipAssetType[],\n snapId: string,\n ): Promise<AssetMetadataResponse | undefined> {\n try {\n return (await this.messenger.call('SnapController:handleRequest', {\n snapId: snapId as SnapId,\n origin: 'metamask',\n handler: HandlerType.OnAssetsLookup,\n request: {\n jsonrpc: '2.0',\n method: 'onAssetLookup',\n params: {\n assets,\n },\n },\n })) as Promise<AssetMetadataResponse>;\n } catch (error) {\n // Ignore\n console.error(error);\n return undefined;\n }\n }\n\n /**\n * Get assets list for an account\n *\n * @param accountId - AccountId to get assets for\n * @param snapId - Snap ID for the account\n * @returns list of assets\n */\n async #getAssetsList(\n accountId: string,\n snapId: string,\n ): Promise<CaipAssetTypeOrId[]> {\n return await this.#getClient(snapId).listAccountAssets(accountId);\n }\n\n /**\n * Gets a `KeyringClient` for a Snap.\n *\n * @param snapId - ID of the Snap to get the client for.\n * @returns A `KeyringClient` for the Snap.\n */\n #getClient(snapId: string): KeyringClient {\n return new KeyringClient({\n send: async (request: JsonRpcRequest) =>\n (await this.messenger.call('SnapController:handleRequest', {\n snapId: snapId as SnapId,\n origin: 'metamask',\n handler: HandlerType.OnKeyringRequest,\n request,\n })) as Promise<Json>,\n });\n }\n\n /**\n * Assert that the controller mutex is locked.\n *\n * @throws If the controller mutex is not locked.\n */\n #assertControllerMutexIsLocked() {\n if (!this.#controllerOperationMutex.isLocked()) {\n throw new Error(\n 'MultichainAssetsControllerError - Attempt to update state',\n );\n }\n }\n\n /**\n * Lock the controller mutex before executing the given function,\n * and release it after the function is resolved or after an\n * error is thrown.\n *\n * This wrapper ensures that each mutable operation that interacts with the\n * controller and that changes its state is executed in a mutually exclusive way,\n * preventing unsafe concurrent access that could lead to unpredictable behavior.\n *\n * @param callback - The function to execute while the controller mutex is locked.\n * @returns The result of the function.\n */\n async #withControllerLock<Result>(\n callback: MutuallyExclusiveCallback<Result>,\n ): Promise<Result> {\n return withLock(this.#controllerOperationMutex, callback);\n }\n}\n\n/**\n * Lock the given mutex before executing the given function,\n * and release it after the function is resolved or after an\n * error is thrown.\n *\n * @param mutex - The mutex to lock.\n * @param callback - The function to execute while the mutex is locked.\n * @returns The result of the function.\n */\nasync function withLock<Result>(\n mutex: Mutex,\n callback: MutuallyExclusiveCallback<Result>,\n): Promise<Result> {\n const releaseLock = await mutex.acquire();\n\n try {\n return await callback({ releaseLock });\n } finally {\n releaseLock();\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metamask-previews/assets-controllers",
|
|
3
|
-
"version": "99.3.2-preview-
|
|
3
|
+
"version": "99.3.2-preview-254d7df",
|
|
4
4
|
"description": "Controllers which manage interactions involving ERC-20, ERC-721, and ERC-1155 tokens (including NFTs)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"MetaMask",
|