@metamask-previews/assets-controller 16.0.0-preview-ff6e6f34a → 16.0.0-preview-95104ffdb
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 +4 -10
- package/dist/AssetsController.d.ts +3 -7
- package/dist/AssetsController.d.ts.map +1 -1
- package/dist/AssetsController.js +240 -671
- package/dist/AssetsController.js.map +1 -1
- package/dist/data-sources/AbstractDataSource.d.ts +1 -13
- package/dist/data-sources/AbstractDataSource.d.ts.map +1 -1
- package/dist/data-sources/AbstractDataSource.js +0 -23
- package/dist/data-sources/AbstractDataSource.js.map +1 -1
- package/dist/data-sources/AccountActivityDataSource.d.ts.map +1 -1
- package/dist/data-sources/AccountActivityDataSource.js +1 -2
- package/dist/data-sources/AccountActivityDataSource.js.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.d.ts +2 -18
- package/dist/data-sources/AccountsApiDataSource.d.ts.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.js +41 -165
- package/dist/data-sources/AccountsApiDataSource.js.map +1 -1
- package/dist/data-sources/RpcDataSource.d.ts +0 -17
- package/dist/data-sources/RpcDataSource.d.ts.map +1 -1
- package/dist/data-sources/RpcDataSource.js +23 -200
- package/dist/data-sources/RpcDataSource.js.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.ts +2 -7
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.ts.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.js +12 -23
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.js.map +1 -1
- package/dist/data-sources/evm-rpc-services/types/state.d.ts +2 -3
- package/dist/data-sources/evm-rpc-services/types/state.d.ts.map +1 -1
- package/dist/data-sources/evm-rpc-services/types/state.js.map +1 -1
- package/dist/middlewares/ParallelMiddleware.d.ts.map +1 -1
- package/dist/middlewares/ParallelMiddleware.js +0 -9
- package/dist/middlewares/ParallelMiddleware.js.map +1 -1
- package/dist/middlewares/RpcFallbackMiddleware.d.ts +14 -10
- package/dist/middlewares/RpcFallbackMiddleware.d.ts.map +1 -1
- package/dist/middlewares/RpcFallbackMiddleware.js +123 -157
- package/dist/middlewares/RpcFallbackMiddleware.js.map +1 -1
- package/dist/types.d.ts +24 -28
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ChainId, DataRequest, DataResponse, AssetsControllerStateInternal } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Subscription request from AssetsController.
|
|
4
4
|
*/
|
|
@@ -74,18 +74,6 @@ export declare abstract class AbstractDataSource<Name extends string, State exte
|
|
|
74
74
|
* @returns Array of currently active chain IDs.
|
|
75
75
|
*/
|
|
76
76
|
getActiveChainsSync(): ChainId[];
|
|
77
|
-
/**
|
|
78
|
-
* Claim the pinned assets this source commits to serving. Called during the
|
|
79
|
-
* subscription handoff; claimed assets are not offered to lower-priority
|
|
80
|
-
* sources. Assets a source cannot resolve at fetch time are released via
|
|
81
|
-
* `DataResponse.unprocessedCustomAssets`. Default: claim assets on this
|
|
82
|
-
* source's assigned chains.
|
|
83
|
-
*
|
|
84
|
-
* @param customAssets - Candidate CAIP-19 asset IDs still unclaimed.
|
|
85
|
-
* @param assignedChains - Chains assigned to this source in the handoff.
|
|
86
|
-
* @returns The claimed subset of `customAssets`.
|
|
87
|
-
*/
|
|
88
|
-
claimCustomAssets(customAssets: Caip19AssetId[], assignedChains: ChainId[]): Caip19AssetId[];
|
|
89
77
|
/**
|
|
90
78
|
* Subscribe to updates for the given request.
|
|
91
79
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AbstractDataSource.d.ts","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AbstractDataSource.d.ts","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,WAAW,EACX,YAAY,EACZ,6BAA6B,EAC9B,MAAM,aAAa,CAAC;AAMrB;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,WAAW,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,gMAAgM;IAChM,cAAc,EAAE,CACd,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,WAAW,KAClB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,6BAA6B,CAAC;IACrD;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,sCAAsC;IACtC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,kCAAkC;IAClC,MAAM,EAAE,OAAO,EAAE,CAAC;IAClB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,kEAAkE;IAClE,cAAc,EAAE,CACd,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,WAAW,KAClB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,wDAAwD;IACxD,YAAY,EAAE,OAAO,EAAE,CAAC;CACzB,CAAC;AAMF;;;;;;GAMG;AACH,8BAAsB,kBAAkB,CACtC,IAAI,SAAS,MAAM,EACnB,KAAK,SAAS,eAAe,GAAG,eAAe;IAE/C,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IAE9B,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;IAEvB,iCAAiC;IACjC,SAAS,CAAC,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAC3D;IAEZ,YAAY,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAG1C;IAED;;;;OAIG;IACH,OAAO,IAAI,IAAI,CAEd;IAED;;;;OAIG;IACG,eAAe,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAE1C;IAED;;;;OAIG;IACH,mBAAmB,IAAI,OAAO,EAAE,CAE/B;IAED;;OAEG;IACH,QAAQ,CAAC,SAAS,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhE;;;;OAIG;IACG,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMvD;IAED;;;;;OAKG;IACH,SAAS,CAAC,kBAAkB,CAC1B,MAAM,EAAE,OAAO,EAAE,EACjB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI,CAgBN;IAED;;;;;OAKG;IACH,SAAS,CAAC,cAAc,CACtB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI,CAKN;IAED;;;;;OAKG;IACH,SAAS,CAAC,iBAAiB,CACzB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,GACxC,IAAI,CAON;IAED;;OAEG;IACH,OAAO,IAAI,IAAI,CAKd;CACF"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { parseCaipAssetType } from '@metamask/utils';
|
|
2
1
|
// ============================================================================
|
|
3
2
|
// ABSTRACT DATA SOURCE
|
|
4
3
|
// ============================================================================
|
|
@@ -42,28 +41,6 @@ export class AbstractDataSource {
|
|
|
42
41
|
getActiveChainsSync() {
|
|
43
42
|
return this.state.activeChains;
|
|
44
43
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Claim the pinned assets this source commits to serving. Called during the
|
|
47
|
-
* subscription handoff; claimed assets are not offered to lower-priority
|
|
48
|
-
* sources. Assets a source cannot resolve at fetch time are released via
|
|
49
|
-
* `DataResponse.unprocessedCustomAssets`. Default: claim assets on this
|
|
50
|
-
* source's assigned chains.
|
|
51
|
-
*
|
|
52
|
-
* @param customAssets - Candidate CAIP-19 asset IDs still unclaimed.
|
|
53
|
-
* @param assignedChains - Chains assigned to this source in the handoff.
|
|
54
|
-
* @returns The claimed subset of `customAssets`.
|
|
55
|
-
*/
|
|
56
|
-
claimCustomAssets(customAssets, assignedChains) {
|
|
57
|
-
const assigned = new Set(assignedChains);
|
|
58
|
-
return customAssets.filter((assetId) => {
|
|
59
|
-
try {
|
|
60
|
-
return assigned.has(parseCaipAssetType(assetId).chainId);
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
44
|
/**
|
|
68
45
|
* Unsubscribe from updates.
|
|
69
46
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AbstractDataSource.js","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AbstractDataSource.js","sourceRoot":"","sources":["../../src/data-sources/AbstractDataSource.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ChainId,\n DataRequest,\n DataResponse,\n AssetsControllerStateInternal,\n} from '../types.js';\n\n// ============================================================================\n// DATA SOURCE BASE TYPES\n// ============================================================================\n\n/**\n * Subscription request from AssetsController.\n */\nexport type SubscriptionRequest = {\n request: DataRequest;\n subscriptionId: string;\n isUpdate: boolean;\n /** Called when this data source has new asset data. Passed by the controller when subscribing. Optional request describes the update scope (e.g. dataTypes: ['balance'] to skip enrichment). */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n /**\n * Optional state access (e.g. for price/token data sources that need assetsBalance).\n * Provided by the controller when subscribing.\n */\n getAssetsState?: () => AssetsControllerStateInternal;\n /**\n * When true, skip the one-shot fetch that normally runs when a subscription\n * is created. Used after the controller has already force-fetched balances\n * for the same accounts/chains so we do not immediately hit the API again.\n */\n skipInitialFetch?: boolean;\n};\n\n/**\n * Active subscription entry stored by data sources.\n * Data sources can extend this with additional fields as needed.\n */\nexport type ActiveSubscription = {\n /** Cleanup function to unsubscribe */\n cleanup: () => void;\n /** Currently subscribed chains */\n chains: ChainId[];\n /** Original request (for polling data sources to handle account changes) */\n request?: DataRequest;\n /** Account addresses (for WebSocket data sources to detect account changes) */\n addresses?: string[];\n /** Callback to report asset updates (from SubscriptionRequest) */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n};\n\n/**\n * Base state for all data sources.\n */\nexport type DataSourceState = {\n /** Currently active chains (supported AND available) */\n activeChains: ChainId[];\n};\n\n// ============================================================================\n// ABSTRACT DATA SOURCE\n// ============================================================================\n\n/**\n * Abstract base class for data sources.\n *\n * Data sources communicate with AssetsController via Messenger:\n * - Register actions that AssetsController can call\n * - Publish events that AssetsController subscribes to\n */\nexport abstract class AbstractDataSource<\n Name extends string,\n State extends DataSourceState = DataSourceState,\n> {\n protected readonly name: Name;\n\n protected state: State;\n\n /** Active subscriptions by ID */\n protected readonly activeSubscriptions: Map<string, ActiveSubscription> =\n new Map();\n\n constructor(name: Name, initialState: State) {\n this.name = name;\n this.state = initialState;\n }\n\n /**\n * Get the data source name/ID.\n *\n * @returns The name of this data source.\n */\n getName(): Name {\n return this.name;\n }\n\n /**\n * Get currently active chains (supported AND available).\n *\n * @returns Array of currently active chain IDs.\n */\n async getActiveChains(): Promise<ChainId[]> {\n return this.state.activeChains;\n }\n\n /**\n * Get currently active chains synchronously (no state duplication in controller).\n *\n * @returns Array of currently active chain IDs.\n */\n getActiveChainsSync(): ChainId[] {\n return this.state.activeChains;\n }\n\n /**\n * Subscribe to updates for the given request.\n */\n abstract subscribe(request: SubscriptionRequest): Promise<void>;\n\n /**\n * Unsubscribe from updates.\n *\n * @param subscriptionId - The ID of the subscription to cancel.\n */\n async unsubscribe(subscriptionId: string): Promise<void> {\n const subscription = this.activeSubscriptions.get(subscriptionId);\n if (subscription) {\n subscription.cleanup();\n this.activeSubscriptions.delete(subscriptionId);\n }\n }\n\n /**\n * Update active chains and notify listeners only if changed.\n *\n * @param chains - Array of chain IDs to set as active.\n * @param publishEvent - Callback to publish chain changes to listeners.\n */\n protected updateActiveChains(\n chains: ChainId[],\n publishEvent: (chains: ChainId[]) => void,\n ): void {\n const previousChains = new Set(this.state.activeChains);\n const newChains = new Set(chains);\n\n // Check if chains have actually changed\n const hasChanges =\n previousChains.size !== newChains.size ||\n chains.some((chain) => !previousChains.has(chain));\n\n // Always update state\n this.state.activeChains = chains;\n\n // Only publish event if there are actual changes\n if (hasChanges) {\n publishEvent(chains);\n }\n }\n\n /**\n * Add a chain to active chains.\n *\n * @param chainId - The chain ID to add.\n * @param publishEvent - Callback to publish chain changes to listeners.\n */\n protected addActiveChain(\n chainId: ChainId,\n publishEvent: (chains: ChainId[]) => void,\n ): void {\n if (!this.state.activeChains.includes(chainId)) {\n this.state.activeChains = [...this.state.activeChains, chainId];\n publishEvent(this.state.activeChains);\n }\n }\n\n /**\n * Remove a chain from active chains.\n *\n * @param chainId - The chain ID to remove.\n * @param publishEvent - Callback to publish chain changes to listeners.\n */\n protected removeActiveChain(\n chainId: ChainId,\n publishEvent: (chains: ChainId[]) => void,\n ): void {\n if (this.state.activeChains.includes(chainId)) {\n this.state.activeChains = this.state.activeChains.filter(\n (chain) => chain !== chainId,\n );\n publishEvent(this.state.activeChains);\n }\n }\n\n /**\n * Destroy the data source and clean up resources.\n */\n destroy(): void {\n for (const subscription of this.activeSubscriptions.values()) {\n subscription.cleanup();\n }\n this.activeSubscriptions.clear();\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountActivityDataSource.d.ts","sourceRoot":"","sources":["../../src/data-sources/AccountActivityDataSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yCAAyC,EACzC,wCAAwC,EAEzC,MAAM,wBAAwB,CAAC;AAKhC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAExE,OAAO,KAAK,EAGV,OAAO,EACP,aAAa,EACb,WAAW,EACX,YAAY,EACb,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAM/D,QAAA,MAAM,eAAe,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"AccountActivityDataSource.d.ts","sourceRoot":"","sources":["../../src/data-sources/AccountActivityDataSource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yCAAyC,EACzC,wCAAwC,EAEzC,MAAM,wBAAwB,CAAC;AAKhC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAExE,OAAO,KAAK,EAGV,OAAO,EACP,aAAa,EACb,WAAW,EACX,YAAY,EACb,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAM/D,QAAA,MAAM,eAAe,8BAA8B,CAAC;AAsFpD,mEAAmE;AACnE,MAAM,MAAM,sCAAsC,GAC9C,yCAAyC,GACzC,wCAAwC,CAAC;AAM7C,MAAM,MAAM,8BAA8B,GAAG,eAAe,CAAC;AAU7D,MAAM,MAAM,gCAAgC,GAAG;IAC7C,mEAAmE;IACnE,SAAS,EAAE,yBAAyB,CAAC;IACrC,qGAAqG;IACrG,qBAAqB,EAAE,CACrB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,OAAO,EAAE,EACjB,cAAc,EAAE,OAAO,EAAE,KACtB,IAAI,CAAC;IACV,wFAAwF;IACxF,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;IACrE;;;;;OAKG;IACH,cAAc,EAAE,CACd,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,WAAW,KAClB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC,8BAA8B,CAAC,CAAC;CACjD,CAAC;AAMF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,yBAA0B,SAAQ,kBAAkB,CAC/D,OAAO,eAAe,EACtB,8BAA8B,CAC/B;;IAwBC,YAAY,OAAO,EAAE,gCAAgC,EAapD;IAqBD;;;;;;;OAOG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAE/B;IAwJD,OAAO,IAAI,IAAI,CAoBd;CACF;AAMD;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,yBAAyB,CAE3B"}
|
|
@@ -45,6 +45,7 @@ function processAccountActivityBalanceUpdates(updates, accountId, getAssetType)
|
|
|
45
45
|
.toFixed();
|
|
46
46
|
assetsBalance[accountId][assetId] = {
|
|
47
47
|
amount: humanReadableAmount,
|
|
48
|
+
...(postBalance.metadata ? { metadata: postBalance.metadata } : {}),
|
|
48
49
|
};
|
|
49
50
|
assetsMetadata[assetId] = {
|
|
50
51
|
type: getAssetType(assetId),
|
|
@@ -126,8 +127,6 @@ export class AccountActivityDataSource extends AbstractDataSource {
|
|
|
126
127
|
// ============================================================================
|
|
127
128
|
// SUBSCRIBE / UNSUBSCRIBE
|
|
128
129
|
// ============================================================================
|
|
129
|
-
// Pins on active chains are claimed via the inherited `claimCustomAssets`:
|
|
130
|
-
// the activity stream pushes all activity for the address, pins included.
|
|
131
130
|
/**
|
|
132
131
|
* AADS is event-driven and chain-agnostic: it never participates in the
|
|
133
132
|
* controller's subscribe/unsubscribe handoff. Incoming `balanceUpdated`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountActivityDataSource.js","sourceRoot":"","sources":["../../src/data-sources/AccountActivityDataSource.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountActivityServiceBalanceUpdatedEvent,\n AccountActivityServiceStatusChangedEvent,\n BalanceUpdate,\n} from '@metamask/core-backend';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport { isCaipChainId } from '@metamask/utils';\nimport { BigNumber as BigNumberJS } from 'bignumber.js';\n\nimport type { AssetsControllerMessenger } from '../AssetsController.js';\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport type {\n AssetBalance,\n AssetMetadata,\n ChainId,\n Caip19AssetId,\n DataRequest,\n DataResponse,\n} from '../types.js';\nimport { safeNormalizeAssetId } from '../utils/index.js';\nimport { AbstractDataSource } from './AbstractDataSource.js';\nimport type { DataSourceState } from './AbstractDataSource.js';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'AccountActivityDataSource';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// BALANCE UPDATE PROCESSING\n// ============================================================================\n\n/**\n * Convert AccountActivityMessage balance updates into a {@link DataResponse}\n * for AssetsController.\n *\n * @param updates - Balance updates from account-activity websocket payload.\n * @param accountId - Internal account UUID.\n * @param getAssetType - Resolver for asset metadata type.\n * @returns DataResponse with merge mode when balances are present.\n */\nfunction processAccountActivityBalanceUpdates(\n updates: BalanceUpdate[],\n accountId: string,\n getAssetType: (assetId: Caip19AssetId) => 'native' | 'erc20' | 'spl',\n): DataResponse {\n const assetsBalance = Object.create(null) as Record<\n string,\n Record<Caip19AssetId, AssetBalance>\n >;\n assetsBalance[accountId] = Object.create(null) as Record<\n Caip19AssetId,\n AssetBalance\n >;\n const assetsMetadata = Object.create(null) as Record<\n Caip19AssetId,\n AssetMetadata\n >;\n\n for (const update of updates) {\n const { asset, postBalance } = update;\n\n if (!asset || !postBalance) {\n continue;\n }\n\n // The websocket sends lower-case ERC-20 addresses, while state and the\n // rest of the pipeline key assets by their checksummed ID. Normalizing\n // here keeps middleware comparisons (detection, custom-asset graduation,\n // occurrence filtering) from treating an existing holding as brand new.\n const assetId = safeNormalizeAssetId(asset.type as Caip19AssetId);\n\n if (asset.decimals === undefined) {\n continue;\n }\n\n const rawBalanceStr = postBalance.amount.startsWith('0x')\n ? BigInt(postBalance.amount).toString()\n : postBalance.amount;\n\n const humanReadableAmount = new BigNumberJS(rawBalanceStr)\n .dividedBy(new BigNumberJS(10).pow(asset.decimals))\n .toFixed();\n\n assetsBalance[accountId][assetId] = {\n amount: humanReadableAmount,\n };\n\n assetsMetadata[assetId] = {\n type: getAssetType(assetId),\n symbol: asset.unit,\n name: asset.unit,\n decimals: asset.decimals,\n };\n }\n\n const response: DataResponse = { updateMode: 'merge' };\n if (Object.keys(assetsBalance[accountId]).length > 0) {\n response.assetsBalance = assetsBalance;\n response.assetsInfo = assetsMetadata;\n }\n\n return response;\n}\n\n// ============================================================================\n// MESSENGER TYPES\n// ============================================================================\n\n/** Allowed events that AccountActivityDataSource subscribes to. */\nexport type AccountActivityDataSourceAllowedEvents =\n | AccountActivityServiceBalanceUpdatedEvent\n | AccountActivityServiceStatusChangedEvent;\n\n// ============================================================================\n// STATE\n// ============================================================================\n\nexport type AccountActivityDataSourceState = DataSourceState;\n\nconst defaultState: AccountActivityDataSourceState = {\n activeChains: [],\n};\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport type AccountActivityDataSourceOptions = {\n /** The AssetsController messenger (shared by all data sources). */\n messenger: AssetsControllerMessenger;\n /** Called when active chains are updated. Pass dataSourceName so the controller knows the source. */\n onActiveChainsUpdated: (\n dataSourceName: string,\n chains: ChainId[],\n previousChains: ChainId[],\n ) => void;\n /** Returns the asset type ('native' | 'erc20' | 'spl') for a given CAIP-19 asset ID. */\n getAssetType: (assetId: Caip19AssetId) => 'native' | 'erc20' | 'spl';\n /**\n * Pushes decoded balance updates to the controller (bound to\n * `AssetsController.handleAssetsUpdate`). AADS is event-driven and never\n * takes part in the controller's subscribe handoff, so it receives this\n * callback directly instead of via a `SubscriptionRequest`.\n */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n state?: Partial<AccountActivityDataSourceState>;\n};\n\n// ============================================================================\n// ACCOUNT ACTIVITY DATA SOURCE\n// ============================================================================\n\n/**\n * Data source that consumes real-time updates from `AccountActivityService`.\n *\n * `AccountActivityService` owns the WebSocket connection and channel\n * subscriptions; this data source is a thin consumer of the two high-level\n * events that it publishes:\n *\n * - `AccountActivityService:balanceUpdated` — post-transaction balances for the\n * subscribed account(s). The address is resolved against the wallet's\n * selected account group, transformed into a {@link DataResponse} (via\n * {@link processAccountActivityBalanceUpdates}), and pushed to the controller\n * through the injected `onAssetsUpdate` callback.\n * - `AccountActivityService:statusChanged` — per-chain \"up\"/\"down\" notifications.\n * A chain reported \"up\" is claimed as an active chain (WebSocket is providing\n * real-time data); a chain reported \"down\" is released so polling data sources\n * can take over. Each change is applied directly via `updateActiveChains`,\n * which notifies the controller through `onActiveChainsUpdated`.\n *\n * This data source does NOT debounce, jitter, or gate chain updates itself.\n * Coalescing (collapsing bursts) and jitter (staggering the WS-subscribe herd\n * across clients) live in `AssetsController`, where the expensive re-subscribe\n * happens and where updates from all data sources converge. `activeChains` are\n * never seeded from the accounts API; they only ever reflect live\n * `statusChanged` events (the service flushes all tracked chains as \"down\" when\n * the WebSocket disconnects, releasing them).\n *\n * It subscribes to these events in its constructor (the same way\n * `TokenBalancesController` does). Unlike the polling data sources it does not\n * take part in the controller's subscribe/unsubscribe handoff: `subscribe` is a\n * no-op, balance updates are routed by resolving the address against the\n * wallet, and updates are pushed through the injected `onAssetsUpdate` callback.\n */\nexport class AccountActivityDataSource extends AbstractDataSource<\n typeof CONTROLLER_NAME,\n AccountActivityDataSourceState\n> {\n readonly #messenger: AssetsControllerMessenger;\n\n readonly #onActiveChainsUpdated: (\n dataSourceName: string,\n chains: ChainId[],\n previousChains: ChainId[],\n ) => void;\n\n readonly #getAssetType: (\n assetId: Caip19AssetId,\n ) => 'native' | 'erc20' | 'spl';\n\n readonly #onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n\n readonly #onBalanceUpdatedBound: (event: {\n address: string;\n chain: string;\n updates: BalanceUpdate[];\n }) => void;\n\n constructor(options: AccountActivityDataSourceOptions) {\n super(CONTROLLER_NAME, {\n ...defaultState,\n ...options.state,\n });\n\n this.#messenger = options.messenger;\n this.#onActiveChainsUpdated = options.onActiveChainsUpdated;\n this.#getAssetType = options.getAssetType;\n this.#onAssetsUpdate = options.onAssetsUpdate;\n this.#onBalanceUpdatedBound = this.#onBalanceUpdated.bind(this);\n\n this.#subscribeToEvents();\n }\n\n // ============================================================================\n // EVENT SUBSCRIPTIONS\n // ============================================================================\n\n #subscribeToEvents(): void {\n this.#messenger.subscribe(\n 'AccountActivityService:balanceUpdated',\n this.#onBalanceUpdatedBound,\n );\n this.#messenger.subscribe(\n 'AccountActivityService:statusChanged',\n this.#onAccountActivityStatusChanged,\n );\n }\n\n // ============================================================================\n // SUBSCRIBE / UNSUBSCRIBE\n // ============================================================================\n\n // Pins on active chains are claimed via the inherited `claimCustomAssets`:\n // the activity stream pushes all activity for the address, pins included.\n\n /**\n * AADS is event-driven and chain-agnostic: it never participates in the\n * controller's subscribe/unsubscribe handoff. Incoming `balanceUpdated`\n * events are routed by resolving the address against the wallet's accounts\n * (see `#onBalanceUpdated`), and updates are pushed through the injected\n * `#onAssetsUpdate` callback. This override exists only to satisfy the\n * abstract contract and is intentionally a no-op.\n */\n async subscribe(): Promise<void> {\n // Intentionally empty — see method doc.\n }\n\n // ============================================================================\n // BALANCE UPDATES\n // ============================================================================\n\n #onBalanceUpdated({\n address,\n chain,\n updates,\n }: {\n address: string;\n chain: string;\n updates: BalanceUpdate[];\n }): void {\n try {\n if (!address || !chain || !updates || updates.length === 0) {\n return;\n }\n\n const account = this.#findAccountForAddress(address);\n if (!account) {\n return;\n }\n\n const chainId = chain as ChainId;\n\n const response = processAccountActivityBalanceUpdates(\n updates,\n account.id,\n (assetId) => this.#getAssetType(assetId),\n );\n\n if (!response.assetsBalance) {\n return;\n }\n\n const request: DataRequest = {\n accountsWithSupportedChains: [{ account, supportedChains: [chainId] }],\n chainIds: [chainId],\n dataTypes: ['balance', 'metadata'],\n };\n\n Promise.resolve(this.#onAssetsUpdate(response, request)).catch(\n (error) => {\n log('Failed to report balance update', { error });\n },\n );\n } catch (error) {\n log('Error handling balance update', error);\n }\n }\n\n /**\n * Find the wallet account matching the given activity address. EVM addresses\n * are matched case-insensitively; other namespaces are matched exactly.\n *\n * The candidate set is the accounts of the selected account group, resolved\n * from the wallet at event time (rather than from a stored subscription), so\n * AADS does not need to be re-subscribed whenever the account set changes.\n *\n * @param address - The account address from the activity message.\n * @returns The matching account, or null.\n */\n #findAccountForAddress(address: string): InternalAccount | null {\n const isEvm = address.startsWith('0x');\n\n return (\n this.#getWalletAccounts().find((candidate) =>\n isEvm\n ? candidate.address.toLowerCase() === address.toLowerCase()\n : candidate.address === address,\n ) ?? null\n );\n }\n\n /**\n * Resolve the accounts of the selected account group from the wallet,\n * mirroring `AssetsController.#getSelectedAccounts`.\n *\n * @returns The accounts of the selected account group.\n */\n #getWalletAccounts(): InternalAccount[] {\n const accounts = this.#messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n if (accounts.length > 0) {\n return accounts;\n }\n\n const selectedAccount = this.#messenger.call(\n 'AccountsController:getSelectedAccount',\n );\n return selectedAccount ? [selectedAccount] : [];\n }\n\n // ============================================================================\n // STATUS CHANGES\n // ============================================================================\n\n /**\n * Handle a `statusChanged` notification. Chains reported \"up\" are claimed as\n * active (WebSocket provides real-time data); chains reported \"down\" are\n * released so polling data sources take over. The change is applied directly;\n * coalescing and jitter are handled by `AssetsController`.\n *\n * @param options - The status change notification.\n * @param options.chainIds - The CAIP-2 chain IDs whose status changed.\n * @param options.status - Whether the chains are `up` or `down`.\n * @param options.timestamp - Optional timestamp of the status change.\n */\n readonly #onAccountActivityStatusChanged = ({\n chainIds,\n status,\n }: {\n chainIds: string[];\n status: 'up' | 'down';\n timestamp?: number;\n }): void => {\n try {\n // Act on every namespace (eip155, solana, etc.); AssetsController is\n // multichain. Only skip identifiers that are not valid CAIP-2 chain IDs.\n const validChains = chainIds.filter((chainId) => isCaipChainId(chainId));\n\n if (validChains.length === 0) {\n return;\n }\n\n const next = new Set(this.state.activeChains);\n if (status === 'up') {\n for (const chainId of validChains) {\n next.add(chainId);\n }\n } else {\n for (const chainId of validChains) {\n next.delete(chainId);\n }\n }\n\n const previous = [...this.state.activeChains];\n this.updateActiveChains(Array.from(next), (updatedChains) =>\n this.#onActiveChainsUpdated(this.getName(), updatedChains, previous),\n );\n } catch (error) {\n log('Error handling status change', error);\n }\n };\n\n // ============================================================================\n // CLEANUP\n // ============================================================================\n\n destroy(): void {\n try {\n this.#messenger.unsubscribe(\n 'AccountActivityService:balanceUpdated',\n this.#onBalanceUpdatedBound,\n );\n } catch (error) {\n log('Failed to unsubscribe from balanceUpdated', { error });\n }\n\n try {\n this.#messenger.unsubscribe(\n 'AccountActivityService:statusChanged',\n this.#onAccountActivityStatusChanged,\n );\n } catch (error) {\n log('Failed to unsubscribe from statusChanged', { error });\n }\n\n super.destroy();\n }\n}\n\n// ============================================================================\n// FACTORY FUNCTION\n// ============================================================================\n\n/**\n * Creates an AccountActivityDataSource instance.\n *\n * @param options - Configuration options for the data source.\n * @returns A new AccountActivityDataSource instance.\n */\nexport function createAccountActivityDataSource(\n options: AccountActivityDataSourceOptions,\n): AccountActivityDataSource {\n return new AccountActivityDataSource(options);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"AccountActivityDataSource.js","sourceRoot":"","sources":["../../src/data-sources/AccountActivityDataSource.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AccountActivityServiceBalanceUpdatedEvent,\n AccountActivityServiceStatusChangedEvent,\n BalanceUpdate,\n} from '@metamask/core-backend';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport { isCaipChainId } from '@metamask/utils';\nimport { BigNumber as BigNumberJS } from 'bignumber.js';\n\nimport type { AssetsControllerMessenger } from '../AssetsController.js';\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport type {\n AssetBalance,\n AssetMetadata,\n ChainId,\n Caip19AssetId,\n DataRequest,\n DataResponse,\n} from '../types.js';\nimport { safeNormalizeAssetId } from '../utils/index.js';\nimport { AbstractDataSource } from './AbstractDataSource.js';\nimport type { DataSourceState } from './AbstractDataSource.js';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'AccountActivityDataSource';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// BALANCE UPDATE PROCESSING\n// ============================================================================\n\n/**\n * Convert AccountActivityMessage balance updates into a {@link DataResponse}\n * for AssetsController.\n *\n * @param updates - Balance updates from account-activity websocket payload.\n * @param accountId - Internal account UUID.\n * @param getAssetType - Resolver for asset metadata type.\n * @returns DataResponse with merge mode when balances are present.\n */\nfunction processAccountActivityBalanceUpdates(\n updates: BalanceUpdate[],\n accountId: string,\n getAssetType: (assetId: Caip19AssetId) => 'native' | 'erc20' | 'spl',\n): DataResponse {\n const assetsBalance = Object.create(null) as Record<\n string,\n Record<Caip19AssetId, AssetBalance>\n >;\n assetsBalance[accountId] = Object.create(null) as Record<\n Caip19AssetId,\n AssetBalance\n >;\n const assetsMetadata = Object.create(null) as Record<\n Caip19AssetId,\n AssetMetadata\n >;\n\n for (const update of updates) {\n const { asset, postBalance } = update;\n\n if (!asset || !postBalance) {\n continue;\n }\n\n // The websocket sends lower-case ERC-20 addresses, while state and the\n // rest of the pipeline key assets by their checksummed ID. Normalizing\n // here keeps middleware comparisons (detection, custom-asset graduation,\n // occurrence filtering) from treating an existing holding as brand new.\n const assetId = safeNormalizeAssetId(asset.type as Caip19AssetId);\n\n if (asset.decimals === undefined) {\n continue;\n }\n\n const rawBalanceStr = postBalance.amount.startsWith('0x')\n ? BigInt(postBalance.amount).toString()\n : postBalance.amount;\n\n const humanReadableAmount = new BigNumberJS(rawBalanceStr)\n .dividedBy(new BigNumberJS(10).pow(asset.decimals))\n .toFixed();\n\n assetsBalance[accountId][assetId] = {\n amount: humanReadableAmount,\n ...(postBalance.metadata ? { metadata: postBalance.metadata } : {}),\n };\n\n assetsMetadata[assetId] = {\n type: getAssetType(assetId),\n symbol: asset.unit,\n name: asset.unit,\n decimals: asset.decimals,\n };\n }\n\n const response: DataResponse = { updateMode: 'merge' };\n if (Object.keys(assetsBalance[accountId]).length > 0) {\n response.assetsBalance = assetsBalance;\n response.assetsInfo = assetsMetadata;\n }\n\n return response;\n}\n\n// ============================================================================\n// MESSENGER TYPES\n// ============================================================================\n\n/** Allowed events that AccountActivityDataSource subscribes to. */\nexport type AccountActivityDataSourceAllowedEvents =\n | AccountActivityServiceBalanceUpdatedEvent\n | AccountActivityServiceStatusChangedEvent;\n\n// ============================================================================\n// STATE\n// ============================================================================\n\nexport type AccountActivityDataSourceState = DataSourceState;\n\nconst defaultState: AccountActivityDataSourceState = {\n activeChains: [],\n};\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport type AccountActivityDataSourceOptions = {\n /** The AssetsController messenger (shared by all data sources). */\n messenger: AssetsControllerMessenger;\n /** Called when active chains are updated. Pass dataSourceName so the controller knows the source. */\n onActiveChainsUpdated: (\n dataSourceName: string,\n chains: ChainId[],\n previousChains: ChainId[],\n ) => void;\n /** Returns the asset type ('native' | 'erc20' | 'spl') for a given CAIP-19 asset ID. */\n getAssetType: (assetId: Caip19AssetId) => 'native' | 'erc20' | 'spl';\n /**\n * Pushes decoded balance updates to the controller (bound to\n * `AssetsController.handleAssetsUpdate`). AADS is event-driven and never\n * takes part in the controller's subscribe handoff, so it receives this\n * callback directly instead of via a `SubscriptionRequest`.\n */\n onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n state?: Partial<AccountActivityDataSourceState>;\n};\n\n// ============================================================================\n// ACCOUNT ACTIVITY DATA SOURCE\n// ============================================================================\n\n/**\n * Data source that consumes real-time updates from `AccountActivityService`.\n *\n * `AccountActivityService` owns the WebSocket connection and channel\n * subscriptions; this data source is a thin consumer of the two high-level\n * events that it publishes:\n *\n * - `AccountActivityService:balanceUpdated` — post-transaction balances for the\n * subscribed account(s). The address is resolved against the wallet's\n * selected account group, transformed into a {@link DataResponse} (via\n * {@link processAccountActivityBalanceUpdates}), and pushed to the controller\n * through the injected `onAssetsUpdate` callback.\n * - `AccountActivityService:statusChanged` — per-chain \"up\"/\"down\" notifications.\n * A chain reported \"up\" is claimed as an active chain (WebSocket is providing\n * real-time data); a chain reported \"down\" is released so polling data sources\n * can take over. Each change is applied directly via `updateActiveChains`,\n * which notifies the controller through `onActiveChainsUpdated`.\n *\n * This data source does NOT debounce, jitter, or gate chain updates itself.\n * Coalescing (collapsing bursts) and jitter (staggering the WS-subscribe herd\n * across clients) live in `AssetsController`, where the expensive re-subscribe\n * happens and where updates from all data sources converge. `activeChains` are\n * never seeded from the accounts API; they only ever reflect live\n * `statusChanged` events (the service flushes all tracked chains as \"down\" when\n * the WebSocket disconnects, releasing them).\n *\n * It subscribes to these events in its constructor (the same way\n * `TokenBalancesController` does). Unlike the polling data sources it does not\n * take part in the controller's subscribe/unsubscribe handoff: `subscribe` is a\n * no-op, balance updates are routed by resolving the address against the\n * wallet, and updates are pushed through the injected `onAssetsUpdate` callback.\n */\nexport class AccountActivityDataSource extends AbstractDataSource<\n typeof CONTROLLER_NAME,\n AccountActivityDataSourceState\n> {\n readonly #messenger: AssetsControllerMessenger;\n\n readonly #onActiveChainsUpdated: (\n dataSourceName: string,\n chains: ChainId[],\n previousChains: ChainId[],\n ) => void;\n\n readonly #getAssetType: (\n assetId: Caip19AssetId,\n ) => 'native' | 'erc20' | 'spl';\n\n readonly #onAssetsUpdate: (\n response: DataResponse,\n request?: DataRequest,\n ) => void | Promise<void>;\n\n readonly #onBalanceUpdatedBound: (event: {\n address: string;\n chain: string;\n updates: BalanceUpdate[];\n }) => void;\n\n constructor(options: AccountActivityDataSourceOptions) {\n super(CONTROLLER_NAME, {\n ...defaultState,\n ...options.state,\n });\n\n this.#messenger = options.messenger;\n this.#onActiveChainsUpdated = options.onActiveChainsUpdated;\n this.#getAssetType = options.getAssetType;\n this.#onAssetsUpdate = options.onAssetsUpdate;\n this.#onBalanceUpdatedBound = this.#onBalanceUpdated.bind(this);\n\n this.#subscribeToEvents();\n }\n\n // ============================================================================\n // EVENT SUBSCRIPTIONS\n // ============================================================================\n\n #subscribeToEvents(): void {\n this.#messenger.subscribe(\n 'AccountActivityService:balanceUpdated',\n this.#onBalanceUpdatedBound,\n );\n this.#messenger.subscribe(\n 'AccountActivityService:statusChanged',\n this.#onAccountActivityStatusChanged,\n );\n }\n\n // ============================================================================\n // SUBSCRIBE / UNSUBSCRIBE\n // ============================================================================\n\n /**\n * AADS is event-driven and chain-agnostic: it never participates in the\n * controller's subscribe/unsubscribe handoff. Incoming `balanceUpdated`\n * events are routed by resolving the address against the wallet's accounts\n * (see `#onBalanceUpdated`), and updates are pushed through the injected\n * `#onAssetsUpdate` callback. This override exists only to satisfy the\n * abstract contract and is intentionally a no-op.\n */\n async subscribe(): Promise<void> {\n // Intentionally empty — see method doc.\n }\n\n // ============================================================================\n // BALANCE UPDATES\n // ============================================================================\n\n #onBalanceUpdated({\n address,\n chain,\n updates,\n }: {\n address: string;\n chain: string;\n updates: BalanceUpdate[];\n }): void {\n try {\n if (!address || !chain || !updates || updates.length === 0) {\n return;\n }\n\n const account = this.#findAccountForAddress(address);\n if (!account) {\n return;\n }\n\n const chainId = chain as ChainId;\n\n const response = processAccountActivityBalanceUpdates(\n updates,\n account.id,\n (assetId) => this.#getAssetType(assetId),\n );\n\n if (!response.assetsBalance) {\n return;\n }\n\n const request: DataRequest = {\n accountsWithSupportedChains: [{ account, supportedChains: [chainId] }],\n chainIds: [chainId],\n dataTypes: ['balance', 'metadata'],\n };\n\n Promise.resolve(this.#onAssetsUpdate(response, request)).catch(\n (error) => {\n log('Failed to report balance update', { error });\n },\n );\n } catch (error) {\n log('Error handling balance update', error);\n }\n }\n\n /**\n * Find the wallet account matching the given activity address. EVM addresses\n * are matched case-insensitively; other namespaces are matched exactly.\n *\n * The candidate set is the accounts of the selected account group, resolved\n * from the wallet at event time (rather than from a stored subscription), so\n * AADS does not need to be re-subscribed whenever the account set changes.\n *\n * @param address - The account address from the activity message.\n * @returns The matching account, or null.\n */\n #findAccountForAddress(address: string): InternalAccount | null {\n const isEvm = address.startsWith('0x');\n\n return (\n this.#getWalletAccounts().find((candidate) =>\n isEvm\n ? candidate.address.toLowerCase() === address.toLowerCase()\n : candidate.address === address,\n ) ?? null\n );\n }\n\n /**\n * Resolve the accounts of the selected account group from the wallet,\n * mirroring `AssetsController.#getSelectedAccounts`.\n *\n * @returns The accounts of the selected account group.\n */\n #getWalletAccounts(): InternalAccount[] {\n const accounts = this.#messenger.call(\n 'AccountTreeController:getAccountsFromSelectedAccountGroup',\n );\n if (accounts.length > 0) {\n return accounts;\n }\n\n const selectedAccount = this.#messenger.call(\n 'AccountsController:getSelectedAccount',\n );\n return selectedAccount ? [selectedAccount] : [];\n }\n\n // ============================================================================\n // STATUS CHANGES\n // ============================================================================\n\n /**\n * Handle a `statusChanged` notification. Chains reported \"up\" are claimed as\n * active (WebSocket provides real-time data); chains reported \"down\" are\n * released so polling data sources take over. The change is applied directly;\n * coalescing and jitter are handled by `AssetsController`.\n *\n * @param options - The status change notification.\n * @param options.chainIds - The CAIP-2 chain IDs whose status changed.\n * @param options.status - Whether the chains are `up` or `down`.\n * @param options.timestamp - Optional timestamp of the status change.\n */\n readonly #onAccountActivityStatusChanged = ({\n chainIds,\n status,\n }: {\n chainIds: string[];\n status: 'up' | 'down';\n timestamp?: number;\n }): void => {\n try {\n // Act on every namespace (eip155, solana, etc.); AssetsController is\n // multichain. Only skip identifiers that are not valid CAIP-2 chain IDs.\n const validChains = chainIds.filter((chainId) => isCaipChainId(chainId));\n\n if (validChains.length === 0) {\n return;\n }\n\n const next = new Set(this.state.activeChains);\n if (status === 'up') {\n for (const chainId of validChains) {\n next.add(chainId);\n }\n } else {\n for (const chainId of validChains) {\n next.delete(chainId);\n }\n }\n\n const previous = [...this.state.activeChains];\n this.updateActiveChains(Array.from(next), (updatedChains) =>\n this.#onActiveChainsUpdated(this.getName(), updatedChains, previous),\n );\n } catch (error) {\n log('Error handling status change', error);\n }\n };\n\n // ============================================================================\n // CLEANUP\n // ============================================================================\n\n destroy(): void {\n try {\n this.#messenger.unsubscribe(\n 'AccountActivityService:balanceUpdated',\n this.#onBalanceUpdatedBound,\n );\n } catch (error) {\n log('Failed to unsubscribe from balanceUpdated', { error });\n }\n\n try {\n this.#messenger.unsubscribe(\n 'AccountActivityService:statusChanged',\n this.#onAccountActivityStatusChanged,\n );\n } catch (error) {\n log('Failed to unsubscribe from statusChanged', { error });\n }\n\n super.destroy();\n }\n}\n\n// ============================================================================\n// FACTORY FUNCTION\n// ============================================================================\n\n/**\n * Creates an AccountActivityDataSource instance.\n *\n * @param options - Configuration options for the data source.\n * @returns A new AccountActivityDataSource instance.\n */\nexport function createAccountActivityDataSource(\n options: AccountActivityDataSourceOptions,\n): AccountActivityDataSource {\n return new AccountActivityDataSource(options);\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ApiPlatformClient } from '@metamask/core-backend';
|
|
2
2
|
import type { RemoteFeatureFlagControllerGetStateAction, RemoteFeatureFlagControllerStateChangeEvent } from '@metamask/remote-feature-flag-controller';
|
|
3
3
|
import type { AssetsControllerMessenger } from '../AssetsController.js';
|
|
4
|
-
import type { ChainId,
|
|
4
|
+
import type { ChainId, DataRequest, DataResponse, Middleware, AssetsControllerStateInternal } from '../types.js';
|
|
5
5
|
import type { DataSourceState, SubscriptionRequest } from './AbstractDataSource.js';
|
|
6
6
|
import { AbstractDataSource } from './AbstractDataSource.js';
|
|
7
7
|
declare const CONTROLLER_NAME = "AccountsApiDataSource";
|
|
@@ -28,19 +28,13 @@ export type AccountsApiDataSourceConfig = {
|
|
|
28
28
|
export type AccountsApiDataSourceOptions = AccountsApiDataSourceConfig & {
|
|
29
29
|
/**
|
|
30
30
|
* The AssetsController messenger (shared by all data sources). Used to read
|
|
31
|
-
*
|
|
31
|
+
* the `assetsAccountsApiV6` remote feature flag.
|
|
32
32
|
*/
|
|
33
33
|
messenger: AssetsControllerMessenger;
|
|
34
34
|
/** ApiPlatformClient for API calls with caching */
|
|
35
35
|
queryApiClient: ApiPlatformClient;
|
|
36
36
|
/** Called when active chains are updated. Pass dataSourceName so the controller knows the source. */
|
|
37
37
|
onActiveChainsUpdated: (dataSourceName: string, chains: ChainId[], previousChains: ChainId[]) => void;
|
|
38
|
-
/**
|
|
39
|
-
* Whether to use the Accounts API v6 balances endpoint (and `includeAssetIds`).
|
|
40
|
-
* Injected by AssetsController so the flag is read in one place. Defaults to
|
|
41
|
-
* `false` (v5). Read on demand, not cached.
|
|
42
|
-
*/
|
|
43
|
-
isBalanceV6Enabled?: () => boolean;
|
|
44
38
|
state?: Partial<AccountsApiDataSourceState>;
|
|
45
39
|
};
|
|
46
40
|
/**
|
|
@@ -81,16 +75,6 @@ export declare class AccountsApiDataSource extends AbstractDataSource<typeof CON
|
|
|
81
75
|
* @returns The middleware function for the assets pipeline.
|
|
82
76
|
*/
|
|
83
77
|
get assetsMiddleware(): Middleware;
|
|
84
|
-
/**
|
|
85
|
-
* Claim EVM pins on assigned chains (sent to v6 as `includeAssetIds`).
|
|
86
|
-
* v5 has no `includeAssetIds`, so with the v6 flag off nothing is claimed
|
|
87
|
-
* and pins fall through to RPC.
|
|
88
|
-
*
|
|
89
|
-
* @param customAssets - Candidate CAIP-19 asset IDs still unclaimed.
|
|
90
|
-
* @param assignedChains - Chains assigned to this source in the handoff.
|
|
91
|
-
* @returns The claimed subset of `customAssets`.
|
|
92
|
-
*/
|
|
93
|
-
claimCustomAssets(customAssets: Caip19AssetId[], assignedChains: ChainId[]): Caip19AssetId[];
|
|
94
78
|
subscribe(subscriptionRequest: SubscriptionRequest): Promise<void>;
|
|
95
79
|
destroy(): void;
|
|
96
80
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccountsApiDataSource.d.ts","sourceRoot":"","sources":["../../src/data-sources/AccountsApiDataSource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EACV,yCAAyC,EACzC,2CAA2C,EAC5C,MAAM,0CAA0C,CAAC;AAQlD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAExE,OAAO,KAAK,EACV,OAAO,
|
|
1
|
+
{"version":3,"file":"AccountsApiDataSource.d.ts","sourceRoot":"","sources":["../../src/data-sources/AccountsApiDataSource.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EACV,yCAAyC,EACzC,2CAA2C,EAC5C,MAAM,0CAA0C,CAAC;AAQlD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAExE,OAAO,KAAK,EACV,OAAO,EAGP,WAAW,EACX,YAAY,EACZ,UAAU,EACV,6BAA6B,EAC9B,MAAM,aAAa,CAAC;AAMrB,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAO7D,QAAA,MAAM,eAAe,0BAA0B,CAAC;AAehD,MAAM,MAAM,mCAAmC,GAC7C,yCAAyC,CAAC;AAI5C,MAAM,MAAM,kCAAkC,GAC5C,2CAA2C,CAAC;AAM9C,MAAM,MAAM,0BAA0B,GAAG,eAAe,CAAC;AAUzD,wDAAwD;AACxD,MAAM,MAAM,2BAA2B,GAAG;IACxC,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,OAAO,CAAC;IACtC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG,2BAA2B,GAAG;IACvE;;;OAGG;IACH,SAAS,EAAE,yBAAyB,CAAC;IACrC,mDAAmD;IACnD,cAAc,EAAE,iBAAiB,CAAC;IAClC,qGAAqG;IACrG,qBAAqB,EAAE,CACrB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,OAAO,EAAE,EACjB,cAAc,EAAE,OAAO,EAAE,KACtB,IAAI,CAAC;IACV,KAAK,CAAC,EAAE,OAAO,CAAC,0BAA0B,CAAC,CAAC;CAC7C,CAAC;AAgCF;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,YAAY,EACtB,WAAW,EAAE,6BAA6B,GACzC,YAAY,CAqCd;AAMD;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,kBAAkB,CAC3D,OAAO,eAAe,EACtB,0BAA0B,CAC3B;;IA0BC,YAAY,OAAO,EAAE,4BAA4B,EA+BhD;IA8FD;;;;;;OAMG;IACH,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnC;IA0BK,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CA4FvD;IA2OD;;;;;;;;;OASG;IACH,IAAI,gBAAgB,IAAI,UAAU,CAuEjC;IAMK,SAAS,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoGvE;IAMD,OAAO,IAAI,IAAI,CAMd;CACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isCaipChainId, KnownCaipNamespace,
|
|
1
|
+
import { isCaipChainId, KnownCaipNamespace, toCaipChainId, } from '@metamask/utils';
|
|
2
2
|
import { projectLogger, createModuleLogger } from '../logger.js';
|
|
3
3
|
import { fetchWithTimeout, normalizeAssetId } from '../utils/index.js';
|
|
4
4
|
import { getMigrationStages, shouldSupportChain, } from '../utils/snaps-assets-migration.js';
|
|
@@ -93,8 +93,6 @@ export class AccountsApiDataSource extends AbstractDataSource {
|
|
|
93
93
|
#tokenDetectionEnabled;
|
|
94
94
|
/** Shared AssetsController messenger, used to read remote feature flags. */
|
|
95
95
|
#messenger;
|
|
96
|
-
/** Injected by AssetsController; `true` when the v6 balances endpoint should be used. */
|
|
97
|
-
#isBalanceV6Enabled;
|
|
98
96
|
/** ApiPlatformClient for cached API calls */
|
|
99
97
|
#apiClient;
|
|
100
98
|
/** Chains refresh timer */
|
|
@@ -112,8 +110,6 @@ export class AccountsApiDataSource extends AbstractDataSource {
|
|
|
112
110
|
this.#tokenDetectionEnabled =
|
|
113
111
|
options.tokenDetectionEnabled ?? (() => true);
|
|
114
112
|
this.#messenger = options.messenger;
|
|
115
|
-
this.#isBalanceV6Enabled =
|
|
116
|
-
options.isBalanceV6Enabled ?? (() => false);
|
|
117
113
|
this.#apiClient = options.queryApiClient;
|
|
118
114
|
// The Snaps → AssetsController migration flags gate which migration networks
|
|
119
115
|
// (Solana, Stellar, Tron) are surfaced as active chains (see
|
|
@@ -146,6 +142,31 @@ export class AccountsApiDataSource extends AbstractDataSource {
|
|
|
146
142
|
});
|
|
147
143
|
}
|
|
148
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Whether the Accounts API v6 balances endpoint is enabled, read from the
|
|
147
|
+
* RemoteFeatureFlagController (`assetsAccountsApiV6`). Read on demand (per
|
|
148
|
+
* fetch) rather than cached so the value can be toggled at runtime, and behaves
|
|
149
|
+
* the same across clients (extension, mobile). Defaults to `false` when the
|
|
150
|
+
* flag is unset or the controller is unavailable.
|
|
151
|
+
*
|
|
152
|
+
* The flag is a LaunchDarkly JSON variation shaped `{ value: boolean }` (same
|
|
153
|
+
* shape as `backendWebSocketConnection`), so the nested `value` is read rather
|
|
154
|
+
* than treating the flag as a plain boolean.
|
|
155
|
+
*
|
|
156
|
+
* @returns `true` when the v6 balances endpoint should be used.
|
|
157
|
+
*/
|
|
158
|
+
#isBalanceV6Enabled() {
|
|
159
|
+
try {
|
|
160
|
+
const { remoteFeatureFlags } = this.#messenger.call('RemoteFeatureFlagController:getState');
|
|
161
|
+
const flag = remoteFeatureFlags?.assetsAccountsApiV6;
|
|
162
|
+
return (typeof flag === 'object' &&
|
|
163
|
+
flag !== null &&
|
|
164
|
+
Boolean(flag.value));
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
149
170
|
// ============================================================================
|
|
150
171
|
// INITIALIZATION
|
|
151
172
|
// ============================================================================
|
|
@@ -243,39 +264,11 @@ export class AccountsApiDataSource extends AbstractDataSource {
|
|
|
243
264
|
...(request.bypassServerCache ? { bypassServerCache: true } : {}),
|
|
244
265
|
}
|
|
245
266
|
: undefined;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const excludeAssetIds = this.#getExcludeAssetIds(request, chainsToFetch);
|
|
252
|
-
// User-pinned assets on the fetched chains, sent to v6 as
|
|
253
|
-
// `includeAssetIds` so the backend returns them even at zero balance.
|
|
254
|
-
// A hide wins over a pin, so hidden assets are never fetched.
|
|
255
|
-
const includeAssetIds = this.#getIncludeAssetIds(request, chainsToFetch, excludeAssetIds);
|
|
256
|
-
fetchResult = await this.#fetchV6Balances(accountIds, fetchOptions, request, includeAssetIds, excludeAssetIds);
|
|
257
|
-
const validUnprocessedAssetIds = fetchResult.unprocessedIncludeAssetIds.filter((assetId) => {
|
|
258
|
-
try {
|
|
259
|
-
parseCaipAssetType(assetId);
|
|
260
|
-
return true;
|
|
261
|
-
}
|
|
262
|
-
catch {
|
|
263
|
-
return false;
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
if (validUnprocessedAssetIds.length > 0) {
|
|
267
|
-
response.unprocessedCustomAssets = [
|
|
268
|
-
...(response.unprocessedCustomAssets ?? []),
|
|
269
|
-
...validUnprocessedAssetIds,
|
|
270
|
-
];
|
|
271
|
-
}
|
|
272
|
-
response.updateMode = 'full';
|
|
273
|
-
}
|
|
274
|
-
else {
|
|
275
|
-
fetchResult = await this.#fetchV5Balances(accountIds, fetchOptions, request);
|
|
276
|
-
response.updateMode = 'merge';
|
|
277
|
-
}
|
|
278
|
-
const { unprocessedNetworks, assetsBalance } = fetchResult;
|
|
267
|
+
// Feature-flagged: v6 endpoint with a fallback to legacy v5. The flag is
|
|
268
|
+
// read here (not cached) so a runtime toggle can revert v6 -> v5.
|
|
269
|
+
const { unprocessedNetworks, assetsBalance } = this.#isBalanceV6Enabled()
|
|
270
|
+
? await this.#fetchV6Balances(accountIds, fetchOptions, request)
|
|
271
|
+
: await this.#fetchV5Balances(accountIds, fetchOptions, request);
|
|
279
272
|
// Handle unprocessed networks - these will be passed to next middleware
|
|
280
273
|
if (unprocessedNetworks.length > 0) {
|
|
281
274
|
const unprocessedChainIds = unprocessedNetworks.map(caipChainIdToChainId);
|
|
@@ -286,6 +279,7 @@ export class AccountsApiDataSource extends AbstractDataSource {
|
|
|
286
279
|
}
|
|
287
280
|
}
|
|
288
281
|
response.assetsBalance = assetsBalance;
|
|
282
|
+
response.updateMode = 'merge';
|
|
289
283
|
}
|
|
290
284
|
catch (error) {
|
|
291
285
|
log('Fetch FAILED', { error, chains: chainsToFetch });
|
|
@@ -309,70 +303,6 @@ export class AccountsApiDataSource extends AbstractDataSource {
|
|
|
309
303
|
}
|
|
310
304
|
return response;
|
|
311
305
|
}
|
|
312
|
-
/**
|
|
313
|
-
* Collect the pinned EVM assets on the fetched chains to send to the v6
|
|
314
|
-
* endpoint as `includeAssetIds`; malformed IDs are skipped and hidden
|
|
315
|
-
* assets are left out (a hide wins).
|
|
316
|
-
*
|
|
317
|
-
* @param request - The data request (carries `customAssets`).
|
|
318
|
-
* @param chainsToFetch - Chains being requested this fetch.
|
|
319
|
-
* @param excludeAssetIds - Hidden asset IDs that must not be included.
|
|
320
|
-
* @returns Deduplicated asset IDs, or `undefined` when none.
|
|
321
|
-
*/
|
|
322
|
-
#getIncludeAssetIds(request, chainsToFetch, excludeAssetIds) {
|
|
323
|
-
if (!request.customAssets || request.customAssets.length === 0) {
|
|
324
|
-
return undefined;
|
|
325
|
-
}
|
|
326
|
-
const chainsToFetchSet = new Set(chainsToFetch);
|
|
327
|
-
const excludeSet = new Set(excludeAssetIds ?? []);
|
|
328
|
-
const includeAssetIds = new Set();
|
|
329
|
-
for (const assetId of request.customAssets) {
|
|
330
|
-
if (excludeSet.has(assetId)) {
|
|
331
|
-
continue;
|
|
332
|
-
}
|
|
333
|
-
let chainId;
|
|
334
|
-
try {
|
|
335
|
-
chainId = parseCaipAssetType(assetId).chainId;
|
|
336
|
-
}
|
|
337
|
-
catch {
|
|
338
|
-
continue;
|
|
339
|
-
}
|
|
340
|
-
if (chainId.startsWith(`${KnownCaipNamespace.Eip155}:`) &&
|
|
341
|
-
chainsToFetchSet.has(chainId)) {
|
|
342
|
-
includeAssetIds.add(assetId);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
return includeAssetIds.size > 0 ? [...includeAssetIds] : undefined;
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Collect the hidden EVM assets on the fetched chains to send to the v6
|
|
349
|
-
* endpoint as `excludeAssetIds`; malformed IDs are skipped.
|
|
350
|
-
*
|
|
351
|
-
* @param request - The data request (carries `excludeAssetIds`).
|
|
352
|
-
* @param chainsToFetch - Chains being requested this fetch.
|
|
353
|
-
* @returns Deduplicated asset IDs, or `undefined` when none.
|
|
354
|
-
*/
|
|
355
|
-
#getExcludeAssetIds(request, chainsToFetch) {
|
|
356
|
-
if (!request.excludeAssetIds || request.excludeAssetIds.length === 0) {
|
|
357
|
-
return undefined;
|
|
358
|
-
}
|
|
359
|
-
const chainsToFetchSet = new Set(chainsToFetch);
|
|
360
|
-
const excludeAssetIds = new Set();
|
|
361
|
-
for (const assetId of request.excludeAssetIds) {
|
|
362
|
-
let chainId;
|
|
363
|
-
try {
|
|
364
|
-
chainId = parseCaipAssetType(assetId).chainId;
|
|
365
|
-
}
|
|
366
|
-
catch {
|
|
367
|
-
continue;
|
|
368
|
-
}
|
|
369
|
-
if (chainId.startsWith(`${KnownCaipNamespace.Eip155}:`) &&
|
|
370
|
-
chainsToFetchSet.has(chainId)) {
|
|
371
|
-
excludeAssetIds.add(assetId);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
return excludeAssetIds.size > 0 ? [...excludeAssetIds] : undefined;
|
|
375
|
-
}
|
|
376
306
|
/**
|
|
377
307
|
* Fetch balances from the legacy v5 endpoint and process them.
|
|
378
308
|
*
|
|
@@ -386,8 +316,6 @@ export class AccountsApiDataSource extends AbstractDataSource {
|
|
|
386
316
|
const { assetsBalance } = this.#processV5Balances(apiResponse.balances, request);
|
|
387
317
|
return {
|
|
388
318
|
unprocessedNetworks: apiResponse.unprocessedNetworks,
|
|
389
|
-
// v5 has no `includeAssetIds` support.
|
|
390
|
-
unprocessedIncludeAssetIds: [],
|
|
391
319
|
assetsBalance,
|
|
392
320
|
};
|
|
393
321
|
}
|
|
@@ -397,23 +325,13 @@ export class AccountsApiDataSource extends AbstractDataSource {
|
|
|
397
325
|
* @param accountIds - CAIP-10 account IDs to fetch balances for.
|
|
398
326
|
* @param fetchOptions - Cache/fetch options (e.g. force update settings).
|
|
399
327
|
* @param request - The original data request containing accounts to map.
|
|
400
|
-
* @
|
|
401
|
-
* @param excludeAssetIds - Hidden asset IDs the backend must drop.
|
|
402
|
-
* @returns Unprocessed networks, unprocessed pinned assets, and processed
|
|
403
|
-
* asset balances by account.
|
|
328
|
+
* @returns Unprocessed networks and processed asset balances by account.
|
|
404
329
|
*/
|
|
405
|
-
async #fetchV6Balances(accountIds, fetchOptions, request
|
|
406
|
-
const
|
|
407
|
-
? {
|
|
408
|
-
...(includeAssetIds && { includeAssetIds }),
|
|
409
|
-
...(excludeAssetIds && { excludeAssetIds }),
|
|
410
|
-
}
|
|
411
|
-
: undefined;
|
|
412
|
-
const apiResponse = await fetchWithTimeout(() => this.#apiClient.accounts.fetchV6MultiAccountBalances(accountIds, params, fetchOptions), this.#fetchTimeoutMs);
|
|
330
|
+
async #fetchV6Balances(accountIds, fetchOptions, request) {
|
|
331
|
+
const apiResponse = await fetchWithTimeout(() => this.#apiClient.accounts.fetchV6MultiAccountBalances(accountIds, undefined, fetchOptions), this.#fetchTimeoutMs);
|
|
413
332
|
const { assetsBalance } = this.#processV6Balances(apiResponse.balances, request);
|
|
414
333
|
return {
|
|
415
334
|
unprocessedNetworks: apiResponse.unprocessedNetworks,
|
|
416
|
-
unprocessedIncludeAssetIds: apiResponse.unprocessedIncludeAssetIds,
|
|
417
335
|
assetsBalance,
|
|
418
336
|
};
|
|
419
337
|
}
|
|
@@ -469,9 +387,11 @@ export class AccountsApiDataSource extends AbstractDataSource {
|
|
|
469
387
|
if (isStakingContractAssetId(normalizedAssetId)) {
|
|
470
388
|
continue;
|
|
471
389
|
}
|
|
472
|
-
// Store balance as returned by API
|
|
390
|
+
// Store balance as returned by API, along with any network-specific
|
|
391
|
+
// metadata (e.g. Stellar trustline / native reserve fields).
|
|
473
392
|
assetsBalance[accountId][normalizedAssetId] = {
|
|
474
393
|
amount: item.balance,
|
|
394
|
+
...(item.metadata ? { metadata: item.metadata } : {}),
|
|
475
395
|
};
|
|
476
396
|
}
|
|
477
397
|
return { assetsBalance };
|
|
@@ -518,9 +438,11 @@ export class AccountsApiDataSource extends AbstractDataSource {
|
|
|
518
438
|
if (isStakingContractAssetId(normalizedAssetId)) {
|
|
519
439
|
continue;
|
|
520
440
|
}
|
|
521
|
-
// Store balance as returned by API
|
|
441
|
+
// Store balance as returned by API, along with any network-specific
|
|
442
|
+
// metadata (e.g. Stellar trustline / native reserve fields).
|
|
522
443
|
assetsBalance[accountId][normalizedAssetId] = {
|
|
523
444
|
amount: item.balance,
|
|
445
|
+
...(item.metadata ? { metadata: item.metadata } : {}),
|
|
524
446
|
};
|
|
525
447
|
}
|
|
526
448
|
return { assetsBalance };
|
|
@@ -562,27 +484,6 @@ export class AccountsApiDataSource extends AbstractDataSource {
|
|
|
562
484
|
};
|
|
563
485
|
}
|
|
564
486
|
}
|
|
565
|
-
if (response.updateMode === 'full') {
|
|
566
|
-
context.response = {
|
|
567
|
-
...context.response,
|
|
568
|
-
updateMode: 'full',
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
else if (response.updateMode === 'merge' &&
|
|
572
|
-
context.response.updateMode !== 'full') {
|
|
573
|
-
context.response = {
|
|
574
|
-
...context.response,
|
|
575
|
-
updateMode: 'merge',
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
// Forward the asset-axis signal so the RPC fallback recovers these pins.
|
|
579
|
-
if (response.unprocessedCustomAssets &&
|
|
580
|
-
response.unprocessedCustomAssets.length > 0) {
|
|
581
|
-
context.response.unprocessedCustomAssets = [
|
|
582
|
-
...(context.response.unprocessedCustomAssets ?? []),
|
|
583
|
-
...response.unprocessedCustomAssets,
|
|
584
|
-
];
|
|
585
|
-
}
|
|
586
487
|
// Determine successfully handled chains (exclude unprocessed/error chains)
|
|
587
488
|
const unprocessedChains = new Set(Object.keys(response.errors ?? {}));
|
|
588
489
|
successfullyHandledChains = request.chainIds.filter((chainId) => !unprocessedChains.has(chainId));
|
|
@@ -617,31 +518,6 @@ export class AccountsApiDataSource extends AbstractDataSource {
|
|
|
617
518
|
// ============================================================================
|
|
618
519
|
// SUBSCRIBE
|
|
619
520
|
// ============================================================================
|
|
620
|
-
/**
|
|
621
|
-
* Claim EVM pins on assigned chains (sent to v6 as `includeAssetIds`).
|
|
622
|
-
* v5 has no `includeAssetIds`, so with the v6 flag off nothing is claimed
|
|
623
|
-
* and pins fall through to RPC.
|
|
624
|
-
*
|
|
625
|
-
* @param customAssets - Candidate CAIP-19 asset IDs still unclaimed.
|
|
626
|
-
* @param assignedChains - Chains assigned to this source in the handoff.
|
|
627
|
-
* @returns The claimed subset of `customAssets`.
|
|
628
|
-
*/
|
|
629
|
-
claimCustomAssets(customAssets, assignedChains) {
|
|
630
|
-
if (!this.#isBalanceV6Enabled()) {
|
|
631
|
-
return [];
|
|
632
|
-
}
|
|
633
|
-
const assigned = new Set(assignedChains);
|
|
634
|
-
return customAssets.filter((assetId) => {
|
|
635
|
-
try {
|
|
636
|
-
const parsed = parseCaipAssetType(assetId);
|
|
637
|
-
return (parsed.chain.namespace === KnownCaipNamespace.Eip155 &&
|
|
638
|
-
assigned.has(parsed.chainId));
|
|
639
|
-
}
|
|
640
|
-
catch {
|
|
641
|
-
return false;
|
|
642
|
-
}
|
|
643
|
-
});
|
|
644
|
-
}
|
|
645
521
|
async subscribe(subscriptionRequest) {
|
|
646
522
|
const { request, subscriptionId, isUpdate, skipInitialFetch } = subscriptionRequest;
|
|
647
523
|
// Store state accessor for filtering when tokenDetectionEnabled is false
|