@metamask-previews/assets-controller 16.1.0-preview-c6cdff1dd → 16.1.0-preview-9d2a9bdf5
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 +10 -0
- package/dist/AssetsController.d.ts +7 -3
- package/dist/AssetsController.d.ts.map +1 -1
- package/dist/AssetsController.js +520 -223
- package/dist/AssetsController.js.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.d.ts +12 -1
- package/dist/data-sources/AccountsApiDataSource.d.ts.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.js +155 -47
- package/dist/data-sources/AccountsApiDataSource.js.map +1 -1
- package/dist/data-sources/RpcDataSource.d.ts.map +1 -1
- package/dist/data-sources/RpcDataSource.js +34 -24
- package/dist/data-sources/RpcDataSource.js.map +1 -1
- package/dist/middlewares/ParallelMiddleware.d.ts.map +1 -1
- package/dist/middlewares/ParallelMiddleware.js +9 -0
- package/dist/middlewares/ParallelMiddleware.js.map +1 -1
- package/dist/middlewares/RpcFallbackMiddleware.d.ts +10 -14
- package/dist/middlewares/RpcFallbackMiddleware.d.ts.map +1 -1
- package/dist/middlewares/RpcFallbackMiddleware.js +157 -123
- package/dist/middlewares/RpcFallbackMiddleware.js.map +1 -1
- package/dist/pipeline/buildFastFetchSources.d.ts +5 -0
- package/dist/pipeline/buildFastFetchSources.d.ts.map +1 -1
- package/dist/pipeline/buildFastFetchSources.js +7 -1
- package/dist/pipeline/buildFastFetchSources.js.map +1 -1
- package/dist/types.d.ts +28 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/AssetsController.js
CHANGED
|
@@ -13,7 +13,7 @@ import { RpcDataSource } from './data-sources/RpcDataSource.js';
|
|
|
13
13
|
import { SnapDataSource } from './data-sources/SnapDataSource.js';
|
|
14
14
|
import { StakedBalanceDataSource } from './data-sources/StakedBalanceDataSource.js';
|
|
15
15
|
import { CaipAssetNamespace, TokenDataSource, } from './data-sources/TokenDataSource.js';
|
|
16
|
-
import { CHAINS_WITH_DEFAULT_TRACKED_ASSETS, DEFAULT_TRACKED_ASSETS_BY_CHAIN, buildDefaultAssetsInfo, getDefaultAssetMetadata, } from './defaults.js';
|
|
16
|
+
import { CHAINS_WITH_DEFAULT_TRACKED_ASSETS, DEFAULT_TRACKED_ASSETS_BY_CHAIN, buildDefaultAssetsInfo, getDefaultAssetMetadata, getDefaultTrackedAssetsForChain, } from './defaults.js';
|
|
17
17
|
import { projectLogger, createModuleLogger } from './logger.js';
|
|
18
18
|
import { CustomAssetGraduationMiddleware } from './middlewares/CustomAssetGraduationMiddleware.js';
|
|
19
19
|
import { DetectionMiddleware } from './middlewares/DetectionMiddleware.js';
|
|
@@ -187,6 +187,10 @@ function normalizeResponse(response) {
|
|
|
187
187
|
if (response.replaceCoveredChainBalances) {
|
|
188
188
|
normalized.replaceCoveredChainBalances = true;
|
|
189
189
|
}
|
|
190
|
+
if (response.unprocessedCustomAssets &&
|
|
191
|
+
response.unprocessedCustomAssets.length > 0) {
|
|
192
|
+
normalized.unprocessedCustomAssets = response.unprocessedCustomAssets.map((assetId) => normalizeAssetId(assetId));
|
|
193
|
+
}
|
|
190
194
|
return normalized;
|
|
191
195
|
}
|
|
192
196
|
/**
|
|
@@ -194,12 +198,13 @@ function normalizeResponse(response) {
|
|
|
194
198
|
*
|
|
195
199
|
* @param previousBalances - Balances already in state for this account.
|
|
196
200
|
* @param accountBalances - Balances from the incoming response.
|
|
197
|
-
* @param customAssetIds - Custom assets to preserve when replacing covered chains
|
|
198
|
-
*
|
|
199
|
-
*
|
|
201
|
+
* @param customAssetIds - Custom assets to preserve when replacing covered chains
|
|
202
|
+
* on the Accounts API v5 path (`replaceCoveredChainBalances`).
|
|
203
|
+
* @param replaceCoveredChains - When true (v5 force refresh), drop prior balances
|
|
204
|
+
* on chains present in the response before applying it, then restore custom assets.
|
|
200
205
|
* @returns The merged balance map for the account.
|
|
201
206
|
*/
|
|
202
|
-
function
|
|
207
|
+
function mergeAccountBalancesV5(previousBalances, accountBalances, customAssetIds, replaceCoveredChains) {
|
|
203
208
|
if (!replaceCoveredChains) {
|
|
204
209
|
return { ...previousBalances, ...accountBalances };
|
|
205
210
|
}
|
|
@@ -228,6 +233,53 @@ function mergeAccountBalances(previousBalances, accountBalances, customAssetIds,
|
|
|
228
233
|
}
|
|
229
234
|
return next;
|
|
230
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Apply an Accounts API v6 authoritative chain snapshot.
|
|
238
|
+
*
|
|
239
|
+
* @param previousBalances - Balances already in state for this account.
|
|
240
|
+
* @param accountBalances - Balances from the incoming response.
|
|
241
|
+
* @param unprocessedCustomAssets - Pins that v6 and RPC could not resolve.
|
|
242
|
+
* @returns The merged balance map for the account.
|
|
243
|
+
*/
|
|
244
|
+
function mergeAccountBalancesV6(previousBalances, accountBalances, unprocessedCustomAssets = []) {
|
|
245
|
+
const coveredChains = new Set(Object.keys(accountBalances).map((assetId) => assetId.split('/')[0]));
|
|
246
|
+
const next = {};
|
|
247
|
+
for (const [assetId, balance] of Object.entries(previousBalances)) {
|
|
248
|
+
if (!coveredChains.has(assetId.split('/')[0])) {
|
|
249
|
+
next[assetId] = balance;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
Object.assign(next, accountBalances);
|
|
253
|
+
const unprocessedSet = new Set(unprocessedCustomAssets.map((assetId) => normalizeAssetId(assetId)));
|
|
254
|
+
for (const [assetId, balance] of Object.entries(previousBalances)) {
|
|
255
|
+
if (unprocessedSet.has(normalizeAssetId(assetId)) &&
|
|
256
|
+
!Object.prototype.hasOwnProperty.call(next, assetId)) {
|
|
257
|
+
next[assetId] = balance;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
for (const [assetId, balance] of Object.entries(previousBalances)) {
|
|
261
|
+
if (isStakingContractAssetId(assetId) &&
|
|
262
|
+
!Object.prototype.hasOwnProperty.call(next, assetId)) {
|
|
263
|
+
next[assetId] = balance;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Default tracked assets (mUSD) are controller-managed and must render at
|
|
267
|
+
// zero when the account holds none. The v6 snapshot omits them in that
|
|
268
|
+
// case, so re-assert them on covered chains — otherwise a force refresh
|
|
269
|
+
// would drop them from the token list.
|
|
270
|
+
// TODO: seed defaults as visible pins in `customAssets` instead (skip if
|
|
271
|
+
// already present or `assetPreferences[assetId].hidden`), then drop this
|
|
272
|
+
// merge special case.
|
|
273
|
+
for (const chainId of coveredChains) {
|
|
274
|
+
for (const assetId of getDefaultTrackedAssetsForChain(chainId)) {
|
|
275
|
+
if (!Object.prototype.hasOwnProperty.call(next, assetId)) {
|
|
276
|
+
next[assetId] =
|
|
277
|
+
previousBalances[assetId] ?? { amount: '0' };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return next;
|
|
282
|
+
}
|
|
231
283
|
// ============================================================================
|
|
232
284
|
// CONTROLLER IMPLEMENTATION
|
|
233
285
|
// ============================================================================
|
|
@@ -463,6 +515,8 @@ export class AssetsController extends BaseController {
|
|
|
463
515
|
queryApiClient,
|
|
464
516
|
onActiveChainsUpdated: this.#onActiveChainsUpdated,
|
|
465
517
|
...accountsApiDataSourceConfig,
|
|
518
|
+
isBalanceV6Enabled: () => this.#isBalanceV6Enabled(),
|
|
519
|
+
getAssetsState: () => this.state,
|
|
466
520
|
});
|
|
467
521
|
this.#snapDataSource = new SnapDataSource({
|
|
468
522
|
messenger: this.messenger,
|
|
@@ -514,6 +568,7 @@ export class AssetsController extends BaseController {
|
|
|
514
568
|
});
|
|
515
569
|
this.#rpcFallbackMiddleware = new RpcFallbackMiddleware({
|
|
516
570
|
rpcDataSource: this.#rpcDataSource,
|
|
571
|
+
isBalanceV6Enabled: () => this.#isBalanceV6Enabled(),
|
|
517
572
|
});
|
|
518
573
|
log('Initializing AssetsController', {
|
|
519
574
|
defaultUpdateInterval: this.#defaultUpdateInterval,
|
|
@@ -868,143 +923,298 @@ export class AssetsController extends BaseController {
|
|
|
868
923
|
if (accounts.length === 0 || chainIds.length === 0) {
|
|
869
924
|
return this.#getAssetsFromState(accounts, chainIds, assetTypes);
|
|
870
925
|
}
|
|
871
|
-
// Collect custom assets for all requested accounts
|
|
872
|
-
const customAssets = [];
|
|
873
|
-
for (const account of accounts) {
|
|
874
|
-
const accountCustomAssets = this.getCustomAssets(account.id);
|
|
875
|
-
customAssets.push(...accountCustomAssets);
|
|
876
|
-
}
|
|
877
926
|
if (options?.forceUpdate) {
|
|
878
|
-
// Pipeline spans only on unlock/first-init fetch; later forceUpdates pass
|
|
879
|
-
// `undefined` so `emitTrace` / `withTrace` no-op without call-site if/else.
|
|
880
927
|
const pipelineTrace = this.#firstInitFetchReported
|
|
881
928
|
? undefined
|
|
882
929
|
: this.#trace;
|
|
883
|
-
const
|
|
930
|
+
const requestOptions = {
|
|
884
931
|
assetTypes,
|
|
885
932
|
dataTypes,
|
|
886
|
-
customAssets: customAssets.length > 0 ? customAssets : undefined,
|
|
887
933
|
forceUpdate: true,
|
|
888
934
|
bypassServerCache: options?.bypassServerCache,
|
|
889
935
|
assetsForPriceUpdate: options?.assetsForPriceUpdate,
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
name: TRACE_FETCH_PIPELINE,
|
|
910
|
-
trace: pipelineTrace,
|
|
911
|
-
data: {
|
|
912
|
-
chain_count: chainIds.length,
|
|
913
|
-
account_count: accounts.length,
|
|
914
|
-
basic_functionality: this.#isBasicFunctionality(),
|
|
915
|
-
},
|
|
916
|
-
fn: async (parentContext) => {
|
|
917
|
-
const startTime = performance.now();
|
|
918
|
-
const result = await this.#executeMiddlewares({
|
|
919
|
-
sources: fastSources,
|
|
920
|
-
request,
|
|
921
|
-
parentContext,
|
|
922
|
-
trace: pipelineTrace,
|
|
923
|
-
});
|
|
924
|
-
await this.#updateState({
|
|
925
|
-
...result.response,
|
|
926
|
-
updateMode: 'merge',
|
|
927
|
-
replaceCoveredChainBalances: true,
|
|
928
|
-
});
|
|
929
|
-
const durationMs = performance.now() - startTime;
|
|
930
|
-
// Summary fields for Assets Health (nested under the parent span).
|
|
931
|
-
emitTrace({
|
|
932
|
-
name: TRACE_FULL_FETCH,
|
|
933
|
-
trace: pipelineTrace,
|
|
934
|
-
data: {
|
|
935
|
-
duration_ms: durationMs,
|
|
936
|
-
chain_count: chainIds.length,
|
|
937
|
-
account_count: accounts.length,
|
|
938
|
-
basic_functionality: this.#isBasicFunctionality(),
|
|
939
|
-
asset_count: result.response.assetsBalance
|
|
940
|
-
? Object.values(result.response.assetsBalance).reduce((sum, acct) => sum + Object.keys(acct).length, 0)
|
|
941
|
-
: 0,
|
|
942
|
-
price_count: result.response.assetsPrice
|
|
943
|
-
? Object.keys(result.response.assetsPrice).length
|
|
944
|
-
: 0,
|
|
945
|
-
...result.durationByDataSource,
|
|
946
|
-
},
|
|
947
|
-
parentContext,
|
|
948
|
-
});
|
|
949
|
-
emitTrace({
|
|
950
|
-
name: TRACE_FIRST_INIT_FETCH,
|
|
951
|
-
trace: pipelineTrace,
|
|
952
|
-
data: {
|
|
953
|
-
duration_ms: durationMs,
|
|
954
|
-
chain_ids: JSON.stringify(chainIds),
|
|
955
|
-
...result.durationByDataSource,
|
|
956
|
-
},
|
|
957
|
-
parentContext,
|
|
958
|
-
});
|
|
959
|
-
return result;
|
|
960
|
-
},
|
|
961
|
-
});
|
|
962
|
-
// Mark after the unlock/first forceUpdate so later polls skip pipeline spans.
|
|
963
|
-
this.#firstInitFetchReported = true;
|
|
964
|
-
// Background (slow) lane — flattened sibling of the fast lane (not nested
|
|
965
|
-
// inside its withTrace callback). Still fire-and-forget so UI is not blocked.
|
|
966
|
-
const slowPipelineChainIds = this.#getSlowPipelineChainIds(chainIds, response);
|
|
967
|
-
if (slowPipelineChainIds.length > 0) {
|
|
968
|
-
const slowSources = this.#isBasicFunctionality()
|
|
969
|
-
? [this.#snapDataSource, this.#rpcDataSource]
|
|
970
|
-
: [this.#rpcDataSource];
|
|
971
|
-
const slowRequest = { ...request, chainIds: slowPipelineChainIds };
|
|
972
|
-
withTrace({
|
|
973
|
-
name: TRACE_BACKGROUND_FETCH,
|
|
974
|
-
trace: pipelineTrace,
|
|
975
|
-
data: {
|
|
976
|
-
chain_count: slowPipelineChainIds.length,
|
|
977
|
-
account_count: accounts.length,
|
|
978
|
-
},
|
|
979
|
-
fn: async (slowParentContext) => {
|
|
980
|
-
const { response: slowResponse } = await this.#executeMiddlewares({
|
|
981
|
-
sources: [
|
|
982
|
-
createParallelBalanceMiddleware(slowSources),
|
|
983
|
-
this.#detectionMiddleware,
|
|
984
|
-
...(this.#isBasicFunctionality()
|
|
985
|
-
? [
|
|
986
|
-
createParallelMiddleware([
|
|
987
|
-
this.#tokenDataSource,
|
|
988
|
-
this.#priceDataSource,
|
|
989
|
-
]),
|
|
990
|
-
]
|
|
991
|
-
: []),
|
|
992
|
-
],
|
|
993
|
-
request: slowRequest,
|
|
994
|
-
parentContext: slowParentContext,
|
|
995
|
-
trace: pipelineTrace,
|
|
996
|
-
});
|
|
997
|
-
await this.#updateState({
|
|
998
|
-
...slowResponse,
|
|
999
|
-
updateMode: 'merge',
|
|
1000
|
-
});
|
|
1001
|
-
},
|
|
1002
|
-
}).catch((error) => log('Background pipeline failed', { error }));
|
|
936
|
+
};
|
|
937
|
+
if (this.#isBalanceV6Enabled()) {
|
|
938
|
+
await this.#forceUpdateAssetsV6({
|
|
939
|
+
accounts,
|
|
940
|
+
chainIds,
|
|
941
|
+
request: this.#buildForceUpdateRequestV6(accounts, chainIds, {
|
|
942
|
+
...requestOptions,
|
|
943
|
+
customAssetsOverride: options?.customAssets,
|
|
944
|
+
}),
|
|
945
|
+
pipelineTrace,
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
await this.#forceUpdateAssetsV5({
|
|
950
|
+
accounts,
|
|
951
|
+
chainIds,
|
|
952
|
+
request: this.#buildForceUpdateRequestV5(accounts, chainIds, requestOptions),
|
|
953
|
+
pipelineTrace,
|
|
954
|
+
});
|
|
1003
955
|
}
|
|
1004
956
|
}
|
|
1005
957
|
const result = this.#getAssetsFromState(accounts, chainIds, assetTypes);
|
|
1006
958
|
return result;
|
|
1007
959
|
}
|
|
960
|
+
/**
|
|
961
|
+
* v5 force-update request: every pin of the requested accounts, unscoped.
|
|
962
|
+
* Delete with the rest of the v5 path when `assetsAccountsApiV6` is the default.
|
|
963
|
+
*
|
|
964
|
+
* @param accounts - Accounts in this fetch.
|
|
965
|
+
* @param chainIds - Chains in this fetch.
|
|
966
|
+
* @param requestOptions - Shared force-update request fields.
|
|
967
|
+
* @param requestOptions.assetTypes - Asset types to fetch.
|
|
968
|
+
* @param requestOptions.dataTypes - Data types to fetch.
|
|
969
|
+
* @param requestOptions.forceUpdate - Always `true` to bypass caches.
|
|
970
|
+
* @param requestOptions.bypassServerCache - Also bypass server-side HTTP caches.
|
|
971
|
+
* @param requestOptions.assetsForPriceUpdate - Assets to refresh prices for.
|
|
972
|
+
* @returns The v5 data request.
|
|
973
|
+
*/
|
|
974
|
+
#buildForceUpdateRequestV5(accounts, chainIds, requestOptions) {
|
|
975
|
+
const customAssets = [];
|
|
976
|
+
for (const account of accounts) {
|
|
977
|
+
customAssets.push(...this.getCustomAssets(account.id));
|
|
978
|
+
}
|
|
979
|
+
return this.#buildDataRequest(accounts, chainIds, {
|
|
980
|
+
...requestOptions,
|
|
981
|
+
customAssets: customAssets.length > 0 ? customAssets : undefined,
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* v6 force-update request. Pins and hides are read from state by the
|
|
986
|
+
* Accounts API unless `customAssetsOverride` scopes this fetch.
|
|
987
|
+
*
|
|
988
|
+
* @param accounts - Accounts in this fetch.
|
|
989
|
+
* @param chainIds - Chains in this fetch.
|
|
990
|
+
* @param requestOptions - Shared force-update request fields and optional pin scope.
|
|
991
|
+
* @param requestOptions.assetTypes - Asset types to fetch.
|
|
992
|
+
* @param requestOptions.dataTypes - Data types to fetch.
|
|
993
|
+
* @param requestOptions.forceUpdate - Always `true` to bypass caches.
|
|
994
|
+
* @param requestOptions.bypassServerCache - Also bypass server-side HTTP caches.
|
|
995
|
+
* @param requestOptions.assetsForPriceUpdate - Assets to refresh prices for.
|
|
996
|
+
* @param requestOptions.customAssetsOverride - When set, fetch only these pins.
|
|
997
|
+
* @returns The v6 data request.
|
|
998
|
+
*/
|
|
999
|
+
#buildForceUpdateRequestV6(accounts, chainIds, requestOptions) {
|
|
1000
|
+
const requestedChains = new Set(chainIds);
|
|
1001
|
+
const customAssetsSet = new Set();
|
|
1002
|
+
for (const assetId of requestOptions.customAssetsOverride ?? []) {
|
|
1003
|
+
try {
|
|
1004
|
+
const normalizedAssetId = normalizeAssetId(assetId);
|
|
1005
|
+
if (requestedChains.has(parseCaipAssetType(normalizedAssetId).chainId) &&
|
|
1006
|
+
!this.state.assetPreferences[normalizedAssetId]?.hidden) {
|
|
1007
|
+
customAssetsSet.add(normalizedAssetId);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
catch {
|
|
1011
|
+
// Skip unparseable asset IDs
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const customAssets = [...customAssetsSet];
|
|
1015
|
+
return this.#buildDataRequest(accounts, chainIds, {
|
|
1016
|
+
assetTypes: requestOptions.assetTypes,
|
|
1017
|
+
dataTypes: requestOptions.dataTypes,
|
|
1018
|
+
forceUpdate: requestOptions.forceUpdate,
|
|
1019
|
+
bypassServerCache: requestOptions.bypassServerCache,
|
|
1020
|
+
assetsForPriceUpdate: requestOptions.assetsForPriceUpdate,
|
|
1021
|
+
customAssets: customAssets.length > 0 ? customAssets : undefined,
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
async #forceUpdateAssetsV5({ accounts, chainIds, request, pipelineTrace, }) {
|
|
1025
|
+
const isBasicFunctionality = this.#isBasicFunctionality();
|
|
1026
|
+
const fastSources = buildFastFetchSources({
|
|
1027
|
+
accountsApiDataSource: this.#accountsApiDataSource,
|
|
1028
|
+
stakedBalanceDataSource: this.#stakedBalanceDataSource,
|
|
1029
|
+
customAssetGraduationMiddleware: this.#customAssetGraduationMiddleware,
|
|
1030
|
+
rpcFallbackMiddleware: this.#rpcFallbackMiddleware,
|
|
1031
|
+
detectionMiddleware: this.#detectionMiddleware,
|
|
1032
|
+
tokenDataSource: this.#tokenDataSource,
|
|
1033
|
+
priceDataSource: this.#priceDataSource,
|
|
1034
|
+
}, { isBasicFunctionality, includeCustomAssetGraduation: true });
|
|
1035
|
+
const fastResponse = await this.#runFastFetchV5({
|
|
1036
|
+
accounts,
|
|
1037
|
+
chainIds,
|
|
1038
|
+
request,
|
|
1039
|
+
pipelineTrace,
|
|
1040
|
+
fastSources,
|
|
1041
|
+
isBasicFunctionality,
|
|
1042
|
+
});
|
|
1043
|
+
this.#firstInitFetchReported = true;
|
|
1044
|
+
this.#runBackgroundFetch({
|
|
1045
|
+
accounts,
|
|
1046
|
+
request,
|
|
1047
|
+
pipelineTrace,
|
|
1048
|
+
chainIds: this.#getSlowPipelineChainIds(chainIds, fastResponse),
|
|
1049
|
+
isBasicFunctionality,
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
async #forceUpdateAssetsV6({ accounts, chainIds, request, pipelineTrace, }) {
|
|
1053
|
+
const isBasicFunctionality = this.#isBasicFunctionality();
|
|
1054
|
+
const fastSources = buildFastFetchSources({
|
|
1055
|
+
accountsApiDataSource: this.#accountsApiDataSource,
|
|
1056
|
+
stakedBalanceDataSource: this.#stakedBalanceDataSource,
|
|
1057
|
+
customAssetGraduationMiddleware: this.#customAssetGraduationMiddleware,
|
|
1058
|
+
rpcFallbackMiddleware: this.#rpcFallbackMiddleware,
|
|
1059
|
+
detectionMiddleware: this.#detectionMiddleware,
|
|
1060
|
+
tokenDataSource: this.#tokenDataSource,
|
|
1061
|
+
priceDataSource: this.#priceDataSource,
|
|
1062
|
+
}, {
|
|
1063
|
+
isBasicFunctionality,
|
|
1064
|
+
includeCustomAssetGraduation: false,
|
|
1065
|
+
});
|
|
1066
|
+
const fastResponse = await this.#runFastFetchV6({
|
|
1067
|
+
accounts,
|
|
1068
|
+
chainIds,
|
|
1069
|
+
request,
|
|
1070
|
+
pipelineTrace,
|
|
1071
|
+
fastSources,
|
|
1072
|
+
isBasicFunctionality,
|
|
1073
|
+
});
|
|
1074
|
+
this.#firstInitFetchReported = true;
|
|
1075
|
+
this.#runBackgroundFetch({
|
|
1076
|
+
accounts,
|
|
1077
|
+
request,
|
|
1078
|
+
pipelineTrace,
|
|
1079
|
+
chainIds: this.#getSlowPipelineChainIds(chainIds, fastResponse),
|
|
1080
|
+
isBasicFunctionality,
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
async #runFastFetchV5({ accounts, chainIds, request, pipelineTrace, fastSources, isBasicFunctionality, }) {
|
|
1084
|
+
const { response } = await withTrace({
|
|
1085
|
+
name: TRACE_FETCH_PIPELINE,
|
|
1086
|
+
trace: pipelineTrace,
|
|
1087
|
+
data: {
|
|
1088
|
+
chain_count: chainIds.length,
|
|
1089
|
+
account_count: accounts.length,
|
|
1090
|
+
basic_functionality: isBasicFunctionality,
|
|
1091
|
+
},
|
|
1092
|
+
fn: async (parentContext) => {
|
|
1093
|
+
const startTime = performance.now();
|
|
1094
|
+
const result = await this.#executeMiddlewares({
|
|
1095
|
+
sources: fastSources,
|
|
1096
|
+
request,
|
|
1097
|
+
parentContext,
|
|
1098
|
+
trace: pipelineTrace,
|
|
1099
|
+
});
|
|
1100
|
+
await this.#updateState({
|
|
1101
|
+
...result.response,
|
|
1102
|
+
updateMode: 'merge',
|
|
1103
|
+
replaceCoveredChainBalances: true,
|
|
1104
|
+
});
|
|
1105
|
+
this.#emitFastFetchTraces({
|
|
1106
|
+
accounts,
|
|
1107
|
+
chainIds,
|
|
1108
|
+
result,
|
|
1109
|
+
durationMs: performance.now() - startTime,
|
|
1110
|
+
isBasicFunctionality,
|
|
1111
|
+
parentContext,
|
|
1112
|
+
pipelineTrace,
|
|
1113
|
+
});
|
|
1114
|
+
return result;
|
|
1115
|
+
},
|
|
1116
|
+
});
|
|
1117
|
+
return response;
|
|
1118
|
+
}
|
|
1119
|
+
async #runFastFetchV6({ accounts, chainIds, request, pipelineTrace, fastSources, isBasicFunctionality, }) {
|
|
1120
|
+
const { response } = await withTrace({
|
|
1121
|
+
name: TRACE_FETCH_PIPELINE,
|
|
1122
|
+
trace: pipelineTrace,
|
|
1123
|
+
data: {
|
|
1124
|
+
chain_count: chainIds.length,
|
|
1125
|
+
account_count: accounts.length,
|
|
1126
|
+
basic_functionality: isBasicFunctionality,
|
|
1127
|
+
},
|
|
1128
|
+
fn: async (parentContext) => {
|
|
1129
|
+
const startTime = performance.now();
|
|
1130
|
+
const result = await this.#executeMiddlewares({
|
|
1131
|
+
sources: fastSources,
|
|
1132
|
+
request,
|
|
1133
|
+
parentContext,
|
|
1134
|
+
trace: pipelineTrace,
|
|
1135
|
+
});
|
|
1136
|
+
await this.#updateState(result.response);
|
|
1137
|
+
this.#emitFastFetchTraces({
|
|
1138
|
+
accounts,
|
|
1139
|
+
chainIds,
|
|
1140
|
+
result,
|
|
1141
|
+
durationMs: performance.now() - startTime,
|
|
1142
|
+
isBasicFunctionality,
|
|
1143
|
+
parentContext,
|
|
1144
|
+
pipelineTrace,
|
|
1145
|
+
});
|
|
1146
|
+
return result;
|
|
1147
|
+
},
|
|
1148
|
+
});
|
|
1149
|
+
return response;
|
|
1150
|
+
}
|
|
1151
|
+
#runBackgroundFetch({ accounts, chainIds, request, pipelineTrace, isBasicFunctionality, }) {
|
|
1152
|
+
if (chainIds.length === 0) {
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
const slowSources = isBasicFunctionality
|
|
1156
|
+
? [this.#snapDataSource, this.#rpcDataSource]
|
|
1157
|
+
: [this.#rpcDataSource];
|
|
1158
|
+
const slowRequest = { ...request, chainIds };
|
|
1159
|
+
withTrace({
|
|
1160
|
+
name: TRACE_BACKGROUND_FETCH,
|
|
1161
|
+
trace: pipelineTrace,
|
|
1162
|
+
data: {
|
|
1163
|
+
chain_count: chainIds.length,
|
|
1164
|
+
account_count: accounts.length,
|
|
1165
|
+
},
|
|
1166
|
+
fn: async (parentContext) => {
|
|
1167
|
+
const { response } = await this.#executeMiddlewares({
|
|
1168
|
+
sources: [
|
|
1169
|
+
createParallelBalanceMiddleware(slowSources),
|
|
1170
|
+
this.#detectionMiddleware,
|
|
1171
|
+
...(isBasicFunctionality
|
|
1172
|
+
? [
|
|
1173
|
+
createParallelMiddleware([
|
|
1174
|
+
this.#tokenDataSource,
|
|
1175
|
+
this.#priceDataSource,
|
|
1176
|
+
]),
|
|
1177
|
+
]
|
|
1178
|
+
: []),
|
|
1179
|
+
],
|
|
1180
|
+
request: slowRequest,
|
|
1181
|
+
parentContext,
|
|
1182
|
+
trace: pipelineTrace,
|
|
1183
|
+
});
|
|
1184
|
+
await this.#updateState({ ...response, updateMode: 'merge' });
|
|
1185
|
+
},
|
|
1186
|
+
}).catch((error) => log('Background pipeline failed', { error }));
|
|
1187
|
+
}
|
|
1188
|
+
#emitFastFetchTraces({ accounts, chainIds, result, durationMs, isBasicFunctionality, parentContext, pipelineTrace, }) {
|
|
1189
|
+
emitTrace({
|
|
1190
|
+
name: TRACE_FULL_FETCH,
|
|
1191
|
+
trace: pipelineTrace,
|
|
1192
|
+
data: {
|
|
1193
|
+
duration_ms: durationMs,
|
|
1194
|
+
chain_count: chainIds.length,
|
|
1195
|
+
account_count: accounts.length,
|
|
1196
|
+
basic_functionality: isBasicFunctionality,
|
|
1197
|
+
asset_count: result.response.assetsBalance
|
|
1198
|
+
? Object.values(result.response.assetsBalance).reduce((sum, account) => sum + Object.keys(account).length, 0)
|
|
1199
|
+
: 0,
|
|
1200
|
+
price_count: result.response.assetsPrice
|
|
1201
|
+
? Object.keys(result.response.assetsPrice).length
|
|
1202
|
+
: 0,
|
|
1203
|
+
...result.durationByDataSource,
|
|
1204
|
+
},
|
|
1205
|
+
parentContext,
|
|
1206
|
+
});
|
|
1207
|
+
emitTrace({
|
|
1208
|
+
name: TRACE_FIRST_INIT_FETCH,
|
|
1209
|
+
trace: pipelineTrace,
|
|
1210
|
+
data: {
|
|
1211
|
+
duration_ms: durationMs,
|
|
1212
|
+
chain_ids: JSON.stringify(chainIds),
|
|
1213
|
+
...result.durationByDataSource,
|
|
1214
|
+
},
|
|
1215
|
+
parentContext,
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1008
1218
|
async getAssetsBalance(accounts, options) {
|
|
1009
1219
|
// Reuse getAssets with dataTypes: ['balance'] only
|
|
1010
1220
|
const assets = await this.getAssets(accounts, {
|
|
@@ -1272,20 +1482,28 @@ export class AssetsController extends BaseController {
|
|
|
1272
1482
|
balances[accountId] ??= {};
|
|
1273
1483
|
balances[accountId][normalizedAssetId] ??= { amount: '0' };
|
|
1274
1484
|
});
|
|
1275
|
-
// Fetch data for the newly added custom asset (merge to preserve other chains)
|
|
1276
1485
|
const account = this.#getSelectedAccounts().find((a) => a.id === accountId);
|
|
1277
1486
|
if (account) {
|
|
1278
1487
|
const chainId = extractChainId(normalizedAssetId);
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1488
|
+
if (this.#isBalanceV6Enabled()) {
|
|
1489
|
+
await this.getAssets([account], {
|
|
1490
|
+
chainIds: [chainId],
|
|
1491
|
+
dataTypes: ['balance', 'metadata', 'price'],
|
|
1492
|
+
assetTypes: ['fungible'],
|
|
1493
|
+
forceUpdate: true,
|
|
1494
|
+
customAssets: [normalizedAssetId],
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
else {
|
|
1498
|
+
await this.getAssets([account], {
|
|
1499
|
+
chainIds: [chainId],
|
|
1500
|
+
dataTypes: ['balance', 'metadata', 'price'],
|
|
1501
|
+
assetTypes: ['fungible'],
|
|
1502
|
+
forceUpdate: true,
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1286
1505
|
}
|
|
1287
|
-
// Re-evaluate subscriptions so
|
|
1288
|
-
// new customAsset on chains another data source already owns.
|
|
1506
|
+
// Re-evaluate subscriptions so polls pick up the new pin.
|
|
1289
1507
|
this.#subscribeAssets();
|
|
1290
1508
|
}
|
|
1291
1509
|
/**
|
|
@@ -1306,8 +1524,7 @@ export class AssetsController extends BaseController {
|
|
|
1306
1524
|
}
|
|
1307
1525
|
}
|
|
1308
1526
|
});
|
|
1309
|
-
// Re-evaluate subscriptions so
|
|
1310
|
-
// is torn down when no more customAssets remain there.
|
|
1527
|
+
// Re-evaluate subscriptions so polls drop the removed pin.
|
|
1311
1528
|
this.#subscribeAssets();
|
|
1312
1529
|
}
|
|
1313
1530
|
/**
|
|
@@ -1338,6 +1555,8 @@ export class AssetsController extends BaseController {
|
|
|
1338
1555
|
}
|
|
1339
1556
|
state.assetPreferences[normalizedAssetId].hidden = true;
|
|
1340
1557
|
});
|
|
1558
|
+
// Re-evaluate subscriptions so polls exclude the newly hidden asset.
|
|
1559
|
+
this.#subscribeAssets();
|
|
1341
1560
|
}
|
|
1342
1561
|
/**
|
|
1343
1562
|
* Unhide an asset globally.
|
|
@@ -1356,6 +1575,26 @@ export class AssetsController extends BaseController {
|
|
|
1356
1575
|
}
|
|
1357
1576
|
}
|
|
1358
1577
|
});
|
|
1578
|
+
// Re-evaluate subscriptions so polls stop excluding the asset.
|
|
1579
|
+
this.#subscribeAssets();
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Whether Accounts API v6 (and the v6 custom-asset path) is enabled.
|
|
1583
|
+
* Injected into AccountsApiDataSource and RpcFallbackMiddleware.
|
|
1584
|
+
*
|
|
1585
|
+
* `RemoteFeatureFlagController` resolves a threshold-scoped flag to the
|
|
1586
|
+
* selected group's `value`, so processed state is a boolean.
|
|
1587
|
+
*
|
|
1588
|
+
* @returns `true` when the v6 remote flag is on.
|
|
1589
|
+
*/
|
|
1590
|
+
#isBalanceV6Enabled() {
|
|
1591
|
+
try {
|
|
1592
|
+
const { remoteFeatureFlags } = this.messenger.call('RemoteFeatureFlagController:getState');
|
|
1593
|
+
return remoteFeatureFlags?.assetsAccountsApiV6 === true;
|
|
1594
|
+
}
|
|
1595
|
+
catch {
|
|
1596
|
+
return false;
|
|
1597
|
+
}
|
|
1359
1598
|
}
|
|
1360
1599
|
// ============================================================================
|
|
1361
1600
|
// CURRENT CURRENCY MANAGEMENT
|
|
@@ -1589,10 +1828,16 @@ export class AssetsController extends BaseController {
|
|
|
1589
1828
|
*/
|
|
1590
1829
|
#getSlowPipelineChainIds(chainIds, fastResponse) {
|
|
1591
1830
|
const accountsApiChains = new Set(this.#accountsApiDataSource.getActiveChainsSync());
|
|
1831
|
+
// Chains whose pins went unresolved (`unprocessedCustomAssets`): route
|
|
1832
|
+
// them to the slow pipeline so RPC fetches the pins.
|
|
1833
|
+
const unprocessedCustomAssetChains = new Set((fastResponse.unprocessedCustomAssets ?? []).map((assetId) => assetId.split('/')[0]));
|
|
1592
1834
|
return chainIds.filter((chainId) => {
|
|
1593
1835
|
if (fastResponse.errors?.[chainId]) {
|
|
1594
1836
|
return true;
|
|
1595
1837
|
}
|
|
1838
|
+
if (unprocessedCustomAssetChains.has(chainId)) {
|
|
1839
|
+
return true;
|
|
1840
|
+
}
|
|
1596
1841
|
if (!accountsApiChains.has(chainId)) {
|
|
1597
1842
|
return true;
|
|
1598
1843
|
}
|
|
@@ -1755,9 +2000,9 @@ export class AssetsController extends BaseController {
|
|
|
1755
2000
|
for (const [accountId, accountBalances] of Object.entries(normalizedResponse.assetsBalance)) {
|
|
1756
2001
|
const previousBalances = previousState.assetsBalance[accountId] ?? {};
|
|
1757
2002
|
const customAssetIds = state.customAssets[accountId] ?? [];
|
|
1758
|
-
const
|
|
1759
|
-
normalizedResponse.
|
|
1760
|
-
|
|
2003
|
+
const effective = mode === 'full'
|
|
2004
|
+
? mergeAccountBalancesV6(previousBalances, accountBalances, normalizedResponse.unprocessedCustomAssets ?? [])
|
|
2005
|
+
: mergeAccountBalancesV5(previousBalances, accountBalances, customAssetIds, normalizedResponse.replaceCoveredChainBalances === true);
|
|
1761
2006
|
// Ensure native tokens have an entry (0 if missing) for chains this account supports
|
|
1762
2007
|
const account = this.#getSelectedAccounts().find((a) => a.id === accountId);
|
|
1763
2008
|
const nativeAssetIdsForAccount = account
|
|
@@ -1787,10 +2032,12 @@ export class AssetsController extends BaseController {
|
|
|
1787
2032
|
...(newMetadata === undefined ? {} : { metadata: newMetadata }),
|
|
1788
2033
|
};
|
|
1789
2034
|
const oldAmount = previousBalance?.amount;
|
|
1790
|
-
const
|
|
2035
|
+
const isNewSeededZero = oldAmount === undefined &&
|
|
1791
2036
|
newAmount === '0' &&
|
|
1792
|
-
nativeAssetIdsForAccount.includes(assetId)
|
|
1793
|
-
|
|
2037
|
+
(nativeAssetIdsForAccount.includes(assetId) ||
|
|
2038
|
+
getDefaultAssetMetadata(assetId) !==
|
|
2039
|
+
undefined);
|
|
2040
|
+
if (oldAmount !== newAmount && !isNewSeededZero) {
|
|
1794
2041
|
changedBalances.push({
|
|
1795
2042
|
accountId,
|
|
1796
2043
|
assetId,
|
|
@@ -1824,10 +2071,8 @@ export class AssetsController extends BaseController {
|
|
|
1824
2071
|
changedPriceAssets.length > 0) {
|
|
1825
2072
|
log('State updated', {
|
|
1826
2073
|
changedBalances: changedBalances.length > 0 ? changedBalances : undefined,
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
? changedPriceAssets.length
|
|
1830
|
-
: undefined,
|
|
2074
|
+
changedMetadata: changedMetadata.length > 0 ? changedMetadata : undefined,
|
|
2075
|
+
changedPrices: changedPriceAssets.length > 0 ? changedPriceAssets : undefined,
|
|
1831
2076
|
newAssets: Object.keys(detectedAssets).length > 0
|
|
1832
2077
|
? Object.entries(detectedAssets).map(([accountId, assets]) => ({
|
|
1833
2078
|
accountId,
|
|
@@ -2049,16 +2294,10 @@ export class AssetsController extends BaseController {
|
|
|
2049
2294
|
if (!subscriptionKey.startsWith('ds:')) {
|
|
2050
2295
|
continue;
|
|
2051
2296
|
}
|
|
2052
|
-
// Subscription keys take the form `ds:<SourceName
|
|
2053
|
-
// subscription or `ds:<SourceName>:<suffix>` for supplemental
|
|
2054
|
-
// subscriptions (e.g. `ds:RpcDataSource:custom`). Split on `:` and
|
|
2055
|
-
// pick the source-name segment so both shapes resolve correctly.
|
|
2297
|
+
// Subscription keys take the form `ds:<SourceName>`.
|
|
2056
2298
|
const [, sourceId] = subscriptionKey.split(':');
|
|
2057
2299
|
const source = allSources.find((ds) => ds.getName() === sourceId);
|
|
2058
2300
|
if (source) {
|
|
2059
|
-
// Unsubscribe by the actual key — `#unsubscribeDataSource` only
|
|
2060
|
-
// knows the regular `ds:<SourceName>` shape and would miss
|
|
2061
|
-
// supplemental subscriptions, leaking their polling timers.
|
|
2062
2301
|
this.#unsubscribeBySubscriptionKey(source, subscriptionKey);
|
|
2063
2302
|
}
|
|
2064
2303
|
}
|
|
@@ -2102,10 +2341,11 @@ export class AssetsController extends BaseController {
|
|
|
2102
2341
|
* Strategy to minimize data source calls:
|
|
2103
2342
|
* 1. Collect all chains to subscribe based on enabled networks
|
|
2104
2343
|
* 2. Map chains to accounts based on their scopes
|
|
2105
|
-
* 3. Split by data source (
|
|
2106
|
-
*
|
|
2107
|
-
*
|
|
2108
|
-
*
|
|
2344
|
+
* 3. Split by data source (priority order) — each source gets one
|
|
2345
|
+
* subscription for the chains it was assigned (accounts + chains only).
|
|
2346
|
+
* v6 sources read pins/hides from state themselves (Accounts API sends
|
|
2347
|
+
* `includeAssetIds` / `excludeAssetIds`). v5 uses a separate RPC
|
|
2348
|
+
* `customAssetsOnly` supplement for pins on chains another source owns.
|
|
2109
2349
|
*
|
|
2110
2350
|
* @param accounts - Accounts to subscribe balance updates for.
|
|
2111
2351
|
* @param chainIds - Chain IDs to subscribe for.
|
|
@@ -2115,10 +2355,7 @@ export class AssetsController extends BaseController {
|
|
|
2115
2355
|
#subscribeAssetsBalance(accounts, chainIds, options) {
|
|
2116
2356
|
const chainToAccounts = this.#buildChainToAccountsMap(accounts, new Set(chainIds));
|
|
2117
2357
|
const remainingChains = new Set(chainToAccounts.keys());
|
|
2118
|
-
|
|
2119
|
-
// RPC only.
|
|
2120
|
-
const isBasicFunctionality = this.#isBasicFunctionality();
|
|
2121
|
-
const balanceDataSources = isBasicFunctionality
|
|
2358
|
+
const balanceDataSources = this.#isBasicFunctionality()
|
|
2122
2359
|
? this.#allBalanceDataSources
|
|
2123
2360
|
: [this.#rpcDataSource];
|
|
2124
2361
|
let rpcAssignedChains = new Set();
|
|
@@ -2157,22 +2394,15 @@ export class AssetsController extends BaseController {
|
|
|
2157
2394
|
});
|
|
2158
2395
|
}
|
|
2159
2396
|
}
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
// AccountsApi is already covering the chain for normal balances. The
|
|
2164
|
-
// supplemental subscription runs in `customAssetsOnly` mode so it does
|
|
2165
|
-
// NOT double-poll the regular tracked balances.
|
|
2166
|
-
this.#subscribeRpcCustomAssetsSupplement(accounts, chainToAccounts, rpcAssignedChains);
|
|
2397
|
+
if (!this.#isBalanceV6Enabled()) {
|
|
2398
|
+
this.#subscribeRpcCustomAssetsSupplement(accounts, chainToAccounts, rpcAssignedChains);
|
|
2399
|
+
}
|
|
2167
2400
|
}
|
|
2168
2401
|
/**
|
|
2169
2402
|
* Guarantee that customAssets are **always** polled by RPC, even when
|
|
2170
2403
|
* AccountsApi or another data source has claimed the chain in the
|
|
2171
2404
|
* regular handoff. RPC is the sole balance fetcher for user-imported
|
|
2172
|
-
* tokens (see `pickRpcCustomAssetsSupplement`
|
|
2173
|
-
* so we run a dedicated subscription in `customAssetsOnly` mode under a
|
|
2174
|
-
* distinct subscription key (`ds:RpcDataSource:custom`) that does not
|
|
2175
|
-
* interfere with the regular RPC subscription.
|
|
2405
|
+
* tokens on the Accounts API v5 path (see `pickRpcCustomAssetsSupplement`).
|
|
2176
2406
|
*
|
|
2177
2407
|
* @param accounts - Accounts to consider for customAssets.
|
|
2178
2408
|
* @param chainToAccounts - Map of chain → accounts (built by caller).
|
|
@@ -2270,7 +2500,8 @@ export class AssetsController extends BaseController {
|
|
|
2270
2500
|
* @param chains - Array of chain IDs to subscribe for.
|
|
2271
2501
|
* @param options - Optional subscription overrides.
|
|
2272
2502
|
* @param options.subscriptionKey - Custom subscription key (default: `ds:<sourceId>`).
|
|
2273
|
-
* @param options.customAssetsOnly - When true, only poll customAssets for these
|
|
2503
|
+
* @param options.customAssetsOnly - When true, only poll customAssets for these
|
|
2504
|
+
* chains (Accounts API v5 supplemental RPC subscription).
|
|
2274
2505
|
* @param options.skipInitialFetch - When true, skip the data source's subscribe-time fetch.
|
|
2275
2506
|
*/
|
|
2276
2507
|
#subscribeDataSource(source, accounts, chains, options = {}) {
|
|
@@ -2483,13 +2714,13 @@ export class AssetsController extends BaseController {
|
|
|
2483
2714
|
// The data will simply not be updated until the network is re-enabled.
|
|
2484
2715
|
// Refresh subscriptions for new chain set
|
|
2485
2716
|
this.#subscribeAssets();
|
|
2486
|
-
//
|
|
2717
|
+
// One-time fetch for newly enabled chains. v6 `full` replace is scoped to
|
|
2718
|
+
// those chains; existing balances on other chains are left untouched.
|
|
2487
2719
|
const accounts = this.#getSelectedAccounts();
|
|
2488
2720
|
if (addedChains.length > 0 && accounts.length > 0) {
|
|
2489
2721
|
await this.getAssets(accounts, {
|
|
2490
2722
|
chainIds: addedChains,
|
|
2491
2723
|
forceUpdate: true,
|
|
2492
|
-
updateMode: 'merge',
|
|
2493
2724
|
});
|
|
2494
2725
|
}
|
|
2495
2726
|
this.#ensureNativeBalancesDefaultZero();
|
|
@@ -2649,48 +2880,28 @@ export class AssetsController extends BaseController {
|
|
|
2649
2880
|
async handleAssetsUpdate(response, sourceId, request) {
|
|
2650
2881
|
log('Assets updated from data source', {
|
|
2651
2882
|
sourceId,
|
|
2652
|
-
|
|
2653
|
-
|
|
2883
|
+
assetsBalance: response.assetsBalance,
|
|
2884
|
+
assetsPrice: response.assetsPrice,
|
|
2654
2885
|
});
|
|
2655
|
-
|
|
2886
|
+
if (this.#isBalanceV6Enabled()) {
|
|
2887
|
+
await this.#handleAssetsUpdateV6(response, sourceId, request);
|
|
2888
|
+
return;
|
|
2889
|
+
}
|
|
2890
|
+
await this.#handleAssetsUpdateV5(response, sourceId, request);
|
|
2891
|
+
}
|
|
2892
|
+
async #handleAssetsUpdateV5(response, sourceId, request) {
|
|
2656
2893
|
const pipelineTrace = this.#firstInitFetchReported
|
|
2657
2894
|
? undefined
|
|
2658
2895
|
: this.#trace;
|
|
2659
2896
|
await withTrace({
|
|
2660
2897
|
name: TRACE_UPDATE_PARENT,
|
|
2661
2898
|
trace: pipelineTrace,
|
|
2662
|
-
data:
|
|
2663
|
-
source: sourceId,
|
|
2664
|
-
has_balance: Boolean(response.assetsBalance),
|
|
2665
|
-
has_price: Boolean(response.assetsPrice),
|
|
2666
|
-
balance_account_count: response.assetsBalance
|
|
2667
|
-
? Object.keys(response.assetsBalance).length
|
|
2668
|
-
: 0,
|
|
2669
|
-
},
|
|
2899
|
+
data: this.#getUpdateTraceData(response, sourceId),
|
|
2670
2900
|
fn: async (parentContext) => {
|
|
2671
2901
|
const updateStart = performance.now();
|
|
2672
|
-
const
|
|
2673
|
-
accountsWithSupportedChains: [],
|
|
2674
|
-
chainIds: [],
|
|
2675
|
-
dataTypes: ['balance', 'metadata', 'price'],
|
|
2676
|
-
};
|
|
2677
|
-
// RPC-only mode (basic functionality off): never run token/price APIs. Strip
|
|
2678
|
-
// those data types so downstream middleware cannot treat them as requested.
|
|
2679
|
-
const pipelineRequest = this.#isBasicFunctionality()
|
|
2680
|
-
? resolvedRequest
|
|
2681
|
-
: {
|
|
2682
|
-
...resolvedRequest,
|
|
2683
|
-
dataTypes: resolvedRequest.dataTypes.filter((dt) => dt !== 'metadata' && dt !== 'price'),
|
|
2684
|
-
};
|
|
2685
|
-
// Graduate custom assets only when AccountsAPI / AccountActivity reports
|
|
2686
|
-
// them. RPC already fetches custom assets on purpose, and Snap handles
|
|
2687
|
-
// non-EVM chains the rule does not apply to, so skip the middleware for
|
|
2688
|
-
// those.
|
|
2902
|
+
const pipelineRequest = this.#getUpdatePipelineRequest(request);
|
|
2689
2903
|
const shouldGraduateCustomAssets = sourceId === 'AccountsApiDataSource' ||
|
|
2690
2904
|
sourceId === 'AccountActivityDataSource';
|
|
2691
|
-
// Websocket updates can carry brand-new spam airdrops: enrich them
|
|
2692
|
-
// with Token API occurrences and drop below-floor tokens BEFORE
|
|
2693
|
-
// detection, so spam is never detected, enriched, priced or persisted.
|
|
2694
2905
|
const shouldFilterOccurrences = sourceId === 'AccountActivityDataSource' &&
|
|
2695
2906
|
this.#isBasicFunctionality();
|
|
2696
2907
|
const shouldRunRpcFallback = sourceId === 'AccountsApiDataSource';
|
|
@@ -2709,6 +2920,7 @@ export class AssetsController extends BaseController {
|
|
|
2709
2920
|
...(shouldRunRpcFallback ? [this.#rpcFallbackMiddleware] : []),
|
|
2710
2921
|
this.#detectionMiddleware,
|
|
2711
2922
|
];
|
|
2923
|
+
enrichmentSources.push(this.#detectionMiddleware);
|
|
2712
2924
|
if (this.#isBasicFunctionality()) {
|
|
2713
2925
|
enrichmentSources.push(createParallelMiddleware([
|
|
2714
2926
|
this.#tokenDataSource,
|
|
@@ -2726,25 +2938,110 @@ export class AssetsController extends BaseController {
|
|
|
2726
2938
|
...enrichedResponse,
|
|
2727
2939
|
replaceCoveredChainBalances: response.replaceCoveredChainBalances,
|
|
2728
2940
|
});
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2941
|
+
this.#emitUpdateTrace({
|
|
2942
|
+
response,
|
|
2943
|
+
sourceId,
|
|
2944
|
+
enrichedResponse,
|
|
2945
|
+
updateStart,
|
|
2946
|
+
pipelineTrace,
|
|
2947
|
+
parentContext,
|
|
2948
|
+
});
|
|
2949
|
+
},
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
async #handleAssetsUpdateV6(response, sourceId, request) {
|
|
2953
|
+
const pipelineTrace = this.#firstInitFetchReported
|
|
2954
|
+
? undefined
|
|
2955
|
+
: this.#trace;
|
|
2956
|
+
await withTrace({
|
|
2957
|
+
name: TRACE_UPDATE_PARENT,
|
|
2958
|
+
trace: pipelineTrace,
|
|
2959
|
+
data: this.#getUpdateTraceData(response, sourceId),
|
|
2960
|
+
fn: async (parentContext) => {
|
|
2961
|
+
const updateStart = performance.now();
|
|
2962
|
+
const pipelineRequest = this.#getUpdatePipelineRequest(request);
|
|
2963
|
+
const shouldFilterOccurrences = sourceId === 'AccountActivityDataSource' &&
|
|
2964
|
+
this.#isBasicFunctionality();
|
|
2965
|
+
const enrichmentSources = [
|
|
2966
|
+
...(shouldFilterOccurrences
|
|
2967
|
+
? [
|
|
2968
|
+
{
|
|
2969
|
+
getName: () => 'OccurrenceFloorFilter',
|
|
2970
|
+
assetsMiddleware: this.#tokenDataSource.occurrenceFilterMiddleware,
|
|
2971
|
+
},
|
|
2972
|
+
]
|
|
2973
|
+
: []),
|
|
2974
|
+
];
|
|
2975
|
+
if (this.#isBasicFunctionality()) {
|
|
2976
|
+
enrichmentSources.push(this.#rpcFallbackMiddleware);
|
|
2977
|
+
}
|
|
2978
|
+
enrichmentSources.push(this.#detectionMiddleware);
|
|
2979
|
+
if (this.#isBasicFunctionality()) {
|
|
2980
|
+
enrichmentSources.push(createParallelMiddleware([
|
|
2981
|
+
this.#tokenDataSource,
|
|
2982
|
+
this.#priceDataSource,
|
|
2983
|
+
]));
|
|
2984
|
+
}
|
|
2985
|
+
const { response: enrichedResponse } = await this.#executeMiddlewares({
|
|
2986
|
+
sources: enrichmentSources,
|
|
2987
|
+
request: pipelineRequest,
|
|
2988
|
+
initialResponse: response,
|
|
2989
|
+
parentContext,
|
|
2732
2990
|
trace: pipelineTrace,
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
: 0,
|
|
2742
|
-
},
|
|
2991
|
+
});
|
|
2992
|
+
await this.#updateState(enrichedResponse);
|
|
2993
|
+
this.#emitUpdateTrace({
|
|
2994
|
+
response,
|
|
2995
|
+
sourceId,
|
|
2996
|
+
enrichedResponse,
|
|
2997
|
+
updateStart,
|
|
2998
|
+
pipelineTrace,
|
|
2743
2999
|
parentContext,
|
|
2744
3000
|
});
|
|
2745
3001
|
},
|
|
2746
3002
|
});
|
|
2747
3003
|
}
|
|
3004
|
+
#getUpdatePipelineRequest(request) {
|
|
3005
|
+
const resolvedRequest = request ?? {
|
|
3006
|
+
accountsWithSupportedChains: [],
|
|
3007
|
+
chainIds: [],
|
|
3008
|
+
dataTypes: ['balance', 'metadata', 'price'],
|
|
3009
|
+
};
|
|
3010
|
+
if (this.#isBasicFunctionality()) {
|
|
3011
|
+
return resolvedRequest;
|
|
3012
|
+
}
|
|
3013
|
+
return {
|
|
3014
|
+
...resolvedRequest,
|
|
3015
|
+
dataTypes: resolvedRequest.dataTypes.filter((dataType) => dataType !== 'metadata' && dataType !== 'price'),
|
|
3016
|
+
};
|
|
3017
|
+
}
|
|
3018
|
+
#getUpdateTraceData(response, sourceId) {
|
|
3019
|
+
return {
|
|
3020
|
+
source: sourceId,
|
|
3021
|
+
has_balance: Boolean(response.assetsBalance),
|
|
3022
|
+
has_price: Boolean(response.assetsPrice),
|
|
3023
|
+
balance_account_count: response.assetsBalance
|
|
3024
|
+
? Object.keys(response.assetsBalance).length
|
|
3025
|
+
: 0,
|
|
3026
|
+
};
|
|
3027
|
+
}
|
|
3028
|
+
#emitUpdateTrace({ response, sourceId, enrichedResponse, updateStart, pipelineTrace, parentContext, }) {
|
|
3029
|
+
emitTrace({
|
|
3030
|
+
name: TRACE_UPDATE_PIPELINE,
|
|
3031
|
+
trace: pipelineTrace,
|
|
3032
|
+
data: {
|
|
3033
|
+
source: sourceId,
|
|
3034
|
+
duration_ms: performance.now() - updateStart,
|
|
3035
|
+
has_balance: Boolean(response.assetsBalance),
|
|
3036
|
+
has_price: Boolean(response.assetsPrice),
|
|
3037
|
+
has_metadata: Boolean(enrichedResponse.assetsInfo),
|
|
3038
|
+
balance_account_count: response.assetsBalance
|
|
3039
|
+
? Object.keys(response.assetsBalance).length
|
|
3040
|
+
: 0,
|
|
3041
|
+
},
|
|
3042
|
+
parentContext,
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
2748
3045
|
// ============================================================================
|
|
2749
3046
|
// CLEANUP
|
|
2750
3047
|
// ============================================================================
|