@metamask-previews/assets-controller 16.1.2-preview-ff2ab0583 → 17.0.0-preview-c4789fdf4
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 +26 -1
- package/dist/AssetsController-method-action-types.d.ts +5 -1
- package/dist/AssetsController-method-action-types.d.ts.map +1 -1
- package/dist/AssetsController-method-action-types.js.map +1 -1
- package/dist/AssetsController.d.ts +8 -40
- package/dist/AssetsController.d.ts.map +1 -1
- package/dist/AssetsController.js +583 -239
- package/dist/AssetsController.js.map +1 -1
- package/dist/data-sources/AbstractDataSource.d.ts +1 -6
- package/dist/data-sources/AbstractDataSource.d.ts.map +1 -1
- package/dist/data-sources/AbstractDataSource.js.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.d.ts +17 -3
- package/dist/data-sources/AccountsApiDataSource.d.ts.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.js +275 -125
- package/dist/data-sources/AccountsApiDataSource.js.map +1 -1
- package/dist/data-sources/PriceDataSource.d.ts +7 -6
- package/dist/data-sources/PriceDataSource.d.ts.map +1 -1
- package/dist/data-sources/PriceDataSource.js +15 -20
- package/dist/data-sources/PriceDataSource.js.map +1 -1
- package/dist/data-sources/RpcDataSource.d.ts +18 -4
- package/dist/data-sources/RpcDataSource.d.ts.map +1 -1
- package/dist/data-sources/RpcDataSource.js +520 -121
- package/dist/data-sources/RpcDataSource.js.map +1 -1
- package/dist/data-sources/SnapDataSource.d.ts +11 -1
- package/dist/data-sources/SnapDataSource.d.ts.map +1 -1
- package/dist/data-sources/SnapDataSource.js +155 -0
- package/dist/data-sources/SnapDataSource.js.map +1 -1
- package/dist/data-sources/TokenDataSource.d.ts +3 -1
- package/dist/data-sources/TokenDataSource.d.ts.map +1 -1
- package/dist/data-sources/TokenDataSource.js +4 -2
- package/dist/data-sources/TokenDataSource.js.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.ts +11 -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 +51 -6
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.js.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/index.d.ts +1 -1
- package/dist/data-sources/evm-rpc-services/services/index.d.ts.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/middlewares/CustomAssetGraduationMiddleware.d.ts +3 -1
- package/dist/middlewares/CustomAssetGraduationMiddleware.d.ts.map +1 -1
- package/dist/middlewares/CustomAssetGraduationMiddleware.js +3 -1
- package/dist/middlewares/CustomAssetGraduationMiddleware.js.map +1 -1
- package/dist/middlewares/DetectionMiddleware.d.ts +10 -2
- package/dist/middlewares/DetectionMiddleware.d.ts.map +1 -1
- package/dist/middlewares/DetectionMiddleware.js +5 -23
- package/dist/middlewares/DetectionMiddleware.js.map +1 -1
- package/dist/middlewares/ParallelMiddleware.d.ts.map +1 -1
- package/dist/middlewares/ParallelMiddleware.js +0 -3
- package/dist/middlewares/ParallelMiddleware.js.map +1 -1
- package/dist/middlewares/RpcFallbackMiddleware.d.ts +16 -15
- package/dist/middlewares/RpcFallbackMiddleware.d.ts.map +1 -1
- package/dist/middlewares/RpcFallbackMiddleware.js +125 -107
- package/dist/middlewares/RpcFallbackMiddleware.js.map +1 -1
- package/dist/middlewares/index.d.ts +1 -0
- package/dist/middlewares/index.d.ts.map +1 -1
- package/dist/middlewares/index.js.map +1 -1
- package/dist/migrations/healAssetsInfoMetadata.d.ts +5 -5
- package/dist/migrations/healAssetsInfoMetadata.d.ts.map +1 -1
- package/dist/migrations/healAssetsInfoMetadata.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/pipeline/executeAssetsPipeline.d.ts +1 -3
- package/dist/pipeline/executeAssetsPipeline.d.ts.map +1 -1
- package/dist/pipeline/executeAssetsPipeline.js +1 -2
- package/dist/pipeline/executeAssetsPipeline.js.map +1 -1
- package/dist/selectors/balance.js +1 -1
- package/dist/selectors/balance.js.map +1 -1
- package/dist/types.d.ts +30 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/assetVisibility.d.ts +30 -0
- package/dist/utils/assetVisibility.d.ts.map +1 -0
- package/dist/utils/assetVisibility.js +84 -0
- package/dist/utils/assetVisibility.js.map +1 -0
- package/dist/utils/constants.d.ts +9 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +9 -0
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/filterFailedChainBalances.d.ts +11 -0
- package/dist/utils/filterFailedChainBalances.d.ts.map +1 -0
- package/dist/utils/filterFailedChainBalances.js +22 -0
- package/dist/utils/filterFailedChainBalances.js.map +1 -0
- package/dist/utils/getZeroAssetBalance.d.ts +56 -0
- package/dist/utils/getZeroAssetBalance.d.ts.map +1 -0
- package/dist/utils/getZeroAssetBalance.js +99 -0
- package/dist/utils/getZeroAssetBalance.js.map +1 -0
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/native-assets.d.ts +6 -19
- package/dist/utils/native-assets.d.ts.map +1 -1
- package/dist/utils/native-assets.js +8 -29
- package/dist/utils/native-assets.js.map +1 -1
- package/package.json +3 -3
package/dist/AssetsController.js
CHANGED
|
@@ -21,9 +21,10 @@ import { createParallelBalanceMiddleware, createParallelMiddleware, } from './mi
|
|
|
21
21
|
import { RpcFallbackMiddleware } from './middlewares/RpcFallbackMiddleware.js';
|
|
22
22
|
import { cleanSpamAssets, isUnlockCleanupEnabled, tempHealAssetsInfoMetadata, } from './migrations/healAssetsInfoMetadata.js';
|
|
23
23
|
import { buildFastFetchSources, executeAssetsPipeline, } from './pipeline/index.js';
|
|
24
|
-
import {
|
|
24
|
+
import { getAssetVisibility } from './utils/assetVisibility.js';
|
|
25
|
+
import { UNKNOWN_EVM_NATIVE_ASSET_REFERENCE, ZERO_ADDRESS, } from './utils/constants.js';
|
|
25
26
|
import { pickRpcCustomAssetsSupplement } from './utils/customAssetsRpcSupplement.js';
|
|
26
|
-
import { normalizeAmountString, normalizeAssetId, formatExchangeRatesForBridge, formatStateForTransactionPay, buildNativeAssetsFromConstant, buildNativeAssetsFromApi, getDefaultNativeAssetBalance, } from './utils/index.js';
|
|
27
|
+
import { normalizeAmountString, normalizeAssetId, formatExchangeRatesForBridge, formatStateForTransactionPay, buildNativeAssetsFromConstant, buildNativeAssetsFromApi, getDefaultNativeAssetBalance, getZeroAssetBalance, } from './utils/index.js';
|
|
27
28
|
import { emitTrace, withTrace } from './utils/trace.js';
|
|
28
29
|
const NATIVE_ASSETS_QUERY_KEY = ['nativeAssets'];
|
|
29
30
|
/** Stable empty map so memoized formatters are not busted by `?? {}` identity churn. */
|
|
@@ -190,16 +191,17 @@ function normalizeResponse(response) {
|
|
|
190
191
|
return normalized;
|
|
191
192
|
}
|
|
192
193
|
/**
|
|
193
|
-
*
|
|
194
|
+
* Compute the effective account balances from a data-source response and prior state.
|
|
194
195
|
*
|
|
195
196
|
* @param previousBalances - Balances already in state for this account.
|
|
196
197
|
* @param accountBalances - Balances from the incoming response.
|
|
197
|
-
* @param customAssetIds - Custom assets to preserve when replacing covered chains
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
198
|
+
* @param customAssetIds - Custom assets to preserve when replacing covered chains
|
|
199
|
+
* on the Accounts API v5 path (`replaceCoveredChainBalances`).
|
|
200
|
+
* @param replaceCoveredChains - When true (v5 force refresh), drop prior balances
|
|
201
|
+
* on chains present in the response before applying it, then restore custom assets.
|
|
202
|
+
* @returns The effective balance map for the account.
|
|
201
203
|
*/
|
|
202
|
-
function
|
|
204
|
+
function effectiveAccountBalancesV5(previousBalances, accountBalances, customAssetIds, replaceCoveredChains) {
|
|
203
205
|
if (!replaceCoveredChains) {
|
|
204
206
|
return { ...previousBalances, ...accountBalances };
|
|
205
207
|
}
|
|
@@ -214,7 +216,7 @@ function mergeAccountBalances(previousBalances, accountBalances, customAssetIds,
|
|
|
214
216
|
for (const customId of customAssetIds) {
|
|
215
217
|
if (!Object.prototype.hasOwnProperty.call(next, customId)) {
|
|
216
218
|
const prev = previousBalances[customId];
|
|
217
|
-
next[customId] = prev ??
|
|
219
|
+
next[customId] = prev ?? getZeroAssetBalance(customId);
|
|
218
220
|
}
|
|
219
221
|
}
|
|
220
222
|
// Staked vault balances are owned by StakedBalanceDataSource. When an
|
|
@@ -228,6 +230,43 @@ function mergeAccountBalances(previousBalances, accountBalances, customAssetIds,
|
|
|
228
230
|
}
|
|
229
231
|
return next;
|
|
230
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Compute the effective account balances for an Accounts API v6 update.
|
|
235
|
+
*
|
|
236
|
+
* `merge` overlays the incoming balances on prior state (Account Activity,
|
|
237
|
+
* Snap events, staking). `full` replaces each covered chain slice. v6 asks
|
|
238
|
+
* for natives, pins, and default tracked assets through `includeAssetIds`
|
|
239
|
+
* and either returns them (at zero when the account holds none) or names
|
|
240
|
+
* them in `unprocessedIncludeAssetIds`. Assets the client tracks on purpose
|
|
241
|
+
* survive a `full` replace even when the response omits them, so an upstream
|
|
242
|
+
* gap cannot wipe them — see `shouldKeepAsset`.
|
|
243
|
+
*
|
|
244
|
+
* @param previousBalances - Balances already in state for this account.
|
|
245
|
+
* @param updatedBalances - Balances from the incoming response.
|
|
246
|
+
* @param undeletableAssetIds - Visible assets the client asked for (native,
|
|
247
|
+
* pin, or default tracked). They survive a `full` replace if the response
|
|
248
|
+
* omits them. Hidden assets are excluded from snapshots and dropped here;
|
|
249
|
+
* unhide is expected to fetch a new snapshot.
|
|
250
|
+
* @param updateMode - `'merge'` overlays incoming balances; `'full'` replaces
|
|
251
|
+
* each updated chain slice except undeletable and staking assets.
|
|
252
|
+
* @returns The effective balance map for the account.
|
|
253
|
+
*/
|
|
254
|
+
function effectiveAccountBalancesV6(previousBalances, updatedBalances, undeletableAssetIds, updateMode) {
|
|
255
|
+
if (updateMode === 'merge') {
|
|
256
|
+
return { ...previousBalances, ...updatedBalances };
|
|
257
|
+
}
|
|
258
|
+
const updatedChains = new Set(Object.keys(updatedBalances).map((assetId) => extractChainId(assetId)));
|
|
259
|
+
const shouldKeepAsset = (assetId) => !updatedChains.has(extractChainId(assetId)) ||
|
|
260
|
+
undeletableAssetIds.has(assetId.toLowerCase()) ||
|
|
261
|
+
isStakingContractAssetId(assetId);
|
|
262
|
+
const keptBalances = {};
|
|
263
|
+
for (const [assetId, balance] of Object.entries(previousBalances)) {
|
|
264
|
+
if (shouldKeepAsset(assetId)) {
|
|
265
|
+
keptBalances[assetId] = balance;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return { ...keptBalances, ...updatedBalances };
|
|
269
|
+
}
|
|
231
270
|
// ============================================================================
|
|
232
271
|
// CONTROLLER IMPLEMENTATION
|
|
233
272
|
// ============================================================================
|
|
@@ -462,17 +501,25 @@ export class AssetsController extends BaseController {
|
|
|
462
501
|
queryApiClient,
|
|
463
502
|
onActiveChainsUpdated: this.#onActiveChainsUpdated,
|
|
464
503
|
...accountsApiDataSourceConfig,
|
|
504
|
+
isBalanceV6Enabled: () => this.#isBalanceV6Enabled(),
|
|
505
|
+
getAssetsState: () => this.state,
|
|
506
|
+
getAssetVisibility: this.#getAssetVisibility.bind(this),
|
|
465
507
|
});
|
|
466
508
|
this.#snapDataSource = new SnapDataSource({
|
|
467
509
|
messenger: this.messenger,
|
|
468
510
|
onActiveChainsUpdated: this.#onActiveChainsUpdated,
|
|
469
511
|
onAssetsUpdate: (response) => this.handleAssetsUpdate(response, 'SnapDataSource'),
|
|
512
|
+
isBalanceV6Enabled: () => this.#isBalanceV6Enabled(),
|
|
513
|
+
getAssetVisibility: this.#getAssetVisibility.bind(this),
|
|
514
|
+
getAssetsState: () => this.state,
|
|
470
515
|
});
|
|
471
516
|
this.#rpcDataSource = new RpcDataSource({
|
|
472
517
|
messenger: this.messenger,
|
|
518
|
+
getAssetsState: () => this.state,
|
|
519
|
+
isBalanceV6Enabled: () => this.#isBalanceV6Enabled(),
|
|
520
|
+
getAssetVisibility: this.#getAssetVisibility.bind(this),
|
|
473
521
|
onActiveChainsUpdated: this.#onActiveChainsUpdated,
|
|
474
|
-
getNativeAssetForChain: (chainId) => this.#
|
|
475
|
-
`${chainId}/erc20:${ZERO_ADDRESS}`,
|
|
522
|
+
getNativeAssetForChain: (chainId) => this.#getNativeAssetForChain(chainId),
|
|
476
523
|
// Share the API platform's TanStack Query client so the RPC token
|
|
477
524
|
// detector caches/dedupes its top-token-list fetches alongside the rest
|
|
478
525
|
// of the package's API calls. Caller-provided rpcConfig.queryClient
|
|
@@ -493,13 +540,17 @@ export class AssetsController extends BaseController {
|
|
|
493
540
|
return this.#getNativeAssetIdsForEnabledChains();
|
|
494
541
|
},
|
|
495
542
|
getAssetType: (assetId) => this.#getAssetType(assetId),
|
|
543
|
+
getAssetsState: () => this.state,
|
|
496
544
|
});
|
|
497
545
|
this.#priceDataSource = new PriceDataSource({
|
|
498
546
|
queryApiClient,
|
|
499
547
|
getSelectedCurrency: () => this.state.selectedCurrency,
|
|
548
|
+
getAssetsState: () => this.state,
|
|
500
549
|
...priceDataSourceConfig,
|
|
501
550
|
});
|
|
502
|
-
this.#detectionMiddleware = new DetectionMiddleware(
|
|
551
|
+
this.#detectionMiddleware = new DetectionMiddleware({
|
|
552
|
+
getAssetsState: () => this.state,
|
|
553
|
+
});
|
|
503
554
|
this.#customAssetGraduationMiddleware = new CustomAssetGraduationMiddleware({
|
|
504
555
|
getSelectedAccountId: () => {
|
|
505
556
|
try {
|
|
@@ -510,9 +561,12 @@ export class AssetsController extends BaseController {
|
|
|
510
561
|
}
|
|
511
562
|
},
|
|
512
563
|
removeCustomAsset: (accountId, assetId) => this.removeCustomAsset(accountId, assetId),
|
|
564
|
+
getAssetsState: () => this.state,
|
|
513
565
|
});
|
|
514
566
|
this.#rpcFallbackMiddleware = new RpcFallbackMiddleware({
|
|
515
567
|
rpcDataSource: this.#rpcDataSource,
|
|
568
|
+
isBalanceV6Enabled: () => this.#isBalanceV6Enabled(),
|
|
569
|
+
getAssetsState: () => this.state,
|
|
516
570
|
});
|
|
517
571
|
log('Initializing AssetsController', {
|
|
518
572
|
defaultUpdateInterval: this.#defaultUpdateInterval,
|
|
@@ -844,7 +898,7 @@ export class AssetsController extends BaseController {
|
|
|
844
898
|
* Returns response and exclusive duration per source (sum ≈ wall time).
|
|
845
899
|
*
|
|
846
900
|
* Thin wrapper over {@link executeAssetsPipeline} that supplies the
|
|
847
|
-
* controller-owned
|
|
901
|
+
* controller-owned exception reporter.
|
|
848
902
|
*
|
|
849
903
|
* @param params - Middleware execution options.
|
|
850
904
|
* @param params.sources - Data sources or middlewares with getName() and assetsMiddleware.
|
|
@@ -857,7 +911,6 @@ export class AssetsController extends BaseController {
|
|
|
857
911
|
async #executeMiddlewares(params) {
|
|
858
912
|
return executeAssetsPipeline({
|
|
859
913
|
...params,
|
|
860
|
-
getAssetsState: () => this.state,
|
|
861
914
|
captureException: this.#captureException,
|
|
862
915
|
});
|
|
863
916
|
}
|
|
@@ -871,143 +924,255 @@ export class AssetsController extends BaseController {
|
|
|
871
924
|
if (accounts.length === 0 || chainIds.length === 0) {
|
|
872
925
|
return this.#getAssetsFromState(accounts, chainIds, assetTypes);
|
|
873
926
|
}
|
|
874
|
-
// Collect custom assets for all requested accounts
|
|
875
|
-
const customAssets = [];
|
|
876
|
-
for (const account of accounts) {
|
|
877
|
-
const accountCustomAssets = this.getCustomAssets(account.id);
|
|
878
|
-
customAssets.push(...accountCustomAssets);
|
|
879
|
-
}
|
|
880
927
|
if (options?.forceUpdate) {
|
|
881
|
-
// Pipeline spans only on unlock/first-init fetch; later forceUpdates pass
|
|
882
|
-
// `undefined` so `emitTrace` / `withTrace` no-op without call-site if/else.
|
|
883
928
|
const pipelineTrace = this.#firstInitFetchReported
|
|
884
929
|
? undefined
|
|
885
930
|
: this.#trace;
|
|
886
|
-
const
|
|
931
|
+
const requestOptions = {
|
|
887
932
|
assetTypes,
|
|
888
933
|
dataTypes,
|
|
889
|
-
customAssets: customAssets.length > 0 ? customAssets : undefined,
|
|
890
934
|
forceUpdate: true,
|
|
891
935
|
bypassServerCache: options?.bypassServerCache,
|
|
892
936
|
assetsForPriceUpdate: options?.assetsForPriceUpdate,
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
priceDataSource: this.#priceDataSource,
|
|
910
|
-
}, { isBasicFunctionality: this.#isBasicFunctionality() });
|
|
911
|
-
const { response } = await withTrace({
|
|
912
|
-
name: TRACE_FETCH_PIPELINE,
|
|
913
|
-
trace: pipelineTrace,
|
|
914
|
-
data: {
|
|
915
|
-
chain_count: chainIds.length,
|
|
916
|
-
account_count: accounts.length,
|
|
917
|
-
basic_functionality: this.#isBasicFunctionality(),
|
|
918
|
-
},
|
|
919
|
-
fn: async (parentContext) => {
|
|
920
|
-
const startTime = performance.now();
|
|
921
|
-
const result = await this.#executeMiddlewares({
|
|
922
|
-
sources: fastSources,
|
|
923
|
-
request,
|
|
924
|
-
parentContext,
|
|
925
|
-
trace: pipelineTrace,
|
|
926
|
-
});
|
|
927
|
-
await this.#updateState({
|
|
928
|
-
...result.response,
|
|
929
|
-
updateMode: 'merge',
|
|
930
|
-
replaceCoveredChainBalances: true,
|
|
931
|
-
});
|
|
932
|
-
const durationMs = performance.now() - startTime;
|
|
933
|
-
// Summary fields for Assets Health (nested under the parent span).
|
|
934
|
-
emitTrace({
|
|
935
|
-
name: TRACE_FULL_FETCH,
|
|
936
|
-
trace: pipelineTrace,
|
|
937
|
-
data: {
|
|
938
|
-
duration_ms: durationMs,
|
|
939
|
-
chain_count: chainIds.length,
|
|
940
|
-
account_count: accounts.length,
|
|
941
|
-
basic_functionality: this.#isBasicFunctionality(),
|
|
942
|
-
asset_count: result.response.assetsBalance
|
|
943
|
-
? Object.values(result.response.assetsBalance).reduce((sum, acct) => sum + Object.keys(acct).length, 0)
|
|
944
|
-
: 0,
|
|
945
|
-
price_count: result.response.assetsPrice
|
|
946
|
-
? Object.keys(result.response.assetsPrice).length
|
|
947
|
-
: 0,
|
|
948
|
-
...result.durationByDataSource,
|
|
949
|
-
},
|
|
950
|
-
parentContext,
|
|
951
|
-
});
|
|
952
|
-
emitTrace({
|
|
953
|
-
name: TRACE_FIRST_INIT_FETCH,
|
|
954
|
-
trace: pipelineTrace,
|
|
955
|
-
data: {
|
|
956
|
-
duration_ms: durationMs,
|
|
957
|
-
chain_ids: JSON.stringify(chainIds),
|
|
958
|
-
...result.durationByDataSource,
|
|
959
|
-
},
|
|
960
|
-
parentContext,
|
|
961
|
-
});
|
|
962
|
-
return result;
|
|
963
|
-
},
|
|
964
|
-
});
|
|
965
|
-
// Mark after the unlock/first forceUpdate so later polls skip pipeline spans.
|
|
966
|
-
this.#firstInitFetchReported = true;
|
|
967
|
-
// Background (slow) lane — flattened sibling of the fast lane (not nested
|
|
968
|
-
// inside its withTrace callback). Still fire-and-forget so UI is not blocked.
|
|
969
|
-
const slowPipelineChainIds = this.#getSlowPipelineChainIds(chainIds, response);
|
|
970
|
-
if (slowPipelineChainIds.length > 0) {
|
|
971
|
-
const slowSources = this.#isBasicFunctionality()
|
|
972
|
-
? [this.#snapDataSource, this.#rpcDataSource]
|
|
973
|
-
: [this.#rpcDataSource];
|
|
974
|
-
const slowRequest = { ...request, chainIds: slowPipelineChainIds };
|
|
975
|
-
withTrace({
|
|
976
|
-
name: TRACE_BACKGROUND_FETCH,
|
|
977
|
-
trace: pipelineTrace,
|
|
978
|
-
data: {
|
|
979
|
-
chain_count: slowPipelineChainIds.length,
|
|
980
|
-
account_count: accounts.length,
|
|
981
|
-
},
|
|
982
|
-
fn: async (slowParentContext) => {
|
|
983
|
-
const { response: slowResponse } = await this.#executeMiddlewares({
|
|
984
|
-
sources: [
|
|
985
|
-
createParallelBalanceMiddleware(slowSources),
|
|
986
|
-
this.#detectionMiddleware,
|
|
987
|
-
...(this.#isBasicFunctionality()
|
|
988
|
-
? [
|
|
989
|
-
createParallelMiddleware([
|
|
990
|
-
this.#tokenDataSource,
|
|
991
|
-
this.#priceDataSource,
|
|
992
|
-
]),
|
|
993
|
-
]
|
|
994
|
-
: []),
|
|
995
|
-
],
|
|
996
|
-
request: slowRequest,
|
|
997
|
-
parentContext: slowParentContext,
|
|
998
|
-
trace: pipelineTrace,
|
|
999
|
-
});
|
|
1000
|
-
await this.#updateState({
|
|
1001
|
-
...slowResponse,
|
|
1002
|
-
updateMode: 'merge',
|
|
1003
|
-
});
|
|
1004
|
-
},
|
|
1005
|
-
}).catch((error) => log('Background pipeline failed', { error }));
|
|
937
|
+
};
|
|
938
|
+
if (this.#isBalanceV6Enabled()) {
|
|
939
|
+
await this.#forceUpdateAssetsV6({
|
|
940
|
+
accounts,
|
|
941
|
+
chainIds,
|
|
942
|
+
request: this.#buildDataRequest(accounts, chainIds, requestOptions),
|
|
943
|
+
pipelineTrace,
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
await this.#forceUpdateAssetsV5({
|
|
948
|
+
accounts,
|
|
949
|
+
chainIds,
|
|
950
|
+
request: this.#buildForceUpdateRequestV5(accounts, chainIds, requestOptions),
|
|
951
|
+
pipelineTrace,
|
|
952
|
+
});
|
|
1006
953
|
}
|
|
1007
954
|
}
|
|
1008
955
|
const result = this.#getAssetsFromState(accounts, chainIds, assetTypes);
|
|
1009
956
|
return result;
|
|
1010
957
|
}
|
|
958
|
+
/**
|
|
959
|
+
* v5 force-update request: every pin of the requested accounts, unscoped.
|
|
960
|
+
* Delete with the rest of the v5 path when `assetsAccountsApiV6` is the default.
|
|
961
|
+
*
|
|
962
|
+
* @param accounts - Accounts in this fetch.
|
|
963
|
+
* @param chainIds - Chains in this fetch.
|
|
964
|
+
* @param requestOptions - Shared force-update request fields.
|
|
965
|
+
* @param requestOptions.assetTypes - Asset types to fetch.
|
|
966
|
+
* @param requestOptions.dataTypes - Data types to fetch.
|
|
967
|
+
* @param requestOptions.forceUpdate - Always `true` to bypass caches.
|
|
968
|
+
* @param requestOptions.bypassServerCache - Also bypass server-side HTTP caches.
|
|
969
|
+
* @param requestOptions.assetsForPriceUpdate - Assets to refresh prices for.
|
|
970
|
+
* @returns The v5 data request.
|
|
971
|
+
*/
|
|
972
|
+
#buildForceUpdateRequestV5(accounts, chainIds, requestOptions) {
|
|
973
|
+
const customAssets = [];
|
|
974
|
+
for (const account of accounts) {
|
|
975
|
+
customAssets.push(...this.getCustomAssets(account.id));
|
|
976
|
+
}
|
|
977
|
+
return this.#buildDataRequest(accounts, chainIds, {
|
|
978
|
+
...requestOptions,
|
|
979
|
+
customAssets: customAssets.length > 0 ? customAssets : undefined,
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
async #forceUpdateAssetsV5({ accounts, chainIds, request, pipelineTrace, }) {
|
|
983
|
+
const isBasicFunctionality = this.#isBasicFunctionality();
|
|
984
|
+
const fastSources = buildFastFetchSources({
|
|
985
|
+
accountsApiDataSource: this.#accountsApiDataSource,
|
|
986
|
+
stakedBalanceDataSource: this.#stakedBalanceDataSource,
|
|
987
|
+
customAssetGraduationMiddleware: this.#customAssetGraduationMiddleware,
|
|
988
|
+
rpcFallbackMiddleware: this.#rpcFallbackMiddleware,
|
|
989
|
+
detectionMiddleware: this.#detectionMiddleware,
|
|
990
|
+
tokenDataSource: this.#tokenDataSource,
|
|
991
|
+
priceDataSource: this.#priceDataSource,
|
|
992
|
+
}, { isBasicFunctionality, includeCustomAssetGraduation: true });
|
|
993
|
+
const fastResponse = await this.#runFastFetchV5({
|
|
994
|
+
accounts,
|
|
995
|
+
chainIds,
|
|
996
|
+
request,
|
|
997
|
+
pipelineTrace,
|
|
998
|
+
fastSources,
|
|
999
|
+
isBasicFunctionality,
|
|
1000
|
+
});
|
|
1001
|
+
this.#firstInitFetchReported = true;
|
|
1002
|
+
this.#runBackgroundFetch({
|
|
1003
|
+
accounts,
|
|
1004
|
+
request,
|
|
1005
|
+
pipelineTrace,
|
|
1006
|
+
chainIds: this.#getSlowPipelineChainIds(chainIds, fastResponse),
|
|
1007
|
+
isBasicFunctionality,
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
async #forceUpdateAssetsV6({ accounts, chainIds, request, pipelineTrace, }) {
|
|
1011
|
+
const isBasicFunctionality = this.#isBasicFunctionality();
|
|
1012
|
+
const fastSources = buildFastFetchSources({
|
|
1013
|
+
accountsApiDataSource: this.#accountsApiDataSource,
|
|
1014
|
+
stakedBalanceDataSource: this.#stakedBalanceDataSource,
|
|
1015
|
+
customAssetGraduationMiddleware: this.#customAssetGraduationMiddleware,
|
|
1016
|
+
rpcFallbackMiddleware: this.#rpcFallbackMiddleware,
|
|
1017
|
+
detectionMiddleware: this.#detectionMiddleware,
|
|
1018
|
+
tokenDataSource: this.#tokenDataSource,
|
|
1019
|
+
priceDataSource: this.#priceDataSource,
|
|
1020
|
+
}, {
|
|
1021
|
+
isBasicFunctionality,
|
|
1022
|
+
includeCustomAssetGraduation: false,
|
|
1023
|
+
});
|
|
1024
|
+
const fastResponse = await this.#runFastFetchV6({
|
|
1025
|
+
accounts,
|
|
1026
|
+
chainIds,
|
|
1027
|
+
request,
|
|
1028
|
+
pipelineTrace,
|
|
1029
|
+
fastSources,
|
|
1030
|
+
isBasicFunctionality,
|
|
1031
|
+
});
|
|
1032
|
+
this.#firstInitFetchReported = true;
|
|
1033
|
+
this.#runBackgroundFetch({
|
|
1034
|
+
accounts,
|
|
1035
|
+
request,
|
|
1036
|
+
pipelineTrace,
|
|
1037
|
+
chainIds: this.#getSlowPipelineChainIds(chainIds, fastResponse),
|
|
1038
|
+
isBasicFunctionality,
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
async #runFastFetchV5({ accounts, chainIds, request, pipelineTrace, fastSources, isBasicFunctionality, }) {
|
|
1042
|
+
const { response } = await withTrace({
|
|
1043
|
+
name: TRACE_FETCH_PIPELINE,
|
|
1044
|
+
trace: pipelineTrace,
|
|
1045
|
+
data: {
|
|
1046
|
+
chain_count: chainIds.length,
|
|
1047
|
+
account_count: accounts.length,
|
|
1048
|
+
basic_functionality: isBasicFunctionality,
|
|
1049
|
+
},
|
|
1050
|
+
fn: async (parentContext) => {
|
|
1051
|
+
const startTime = performance.now();
|
|
1052
|
+
const result = await this.#executeMiddlewares({
|
|
1053
|
+
sources: fastSources,
|
|
1054
|
+
request,
|
|
1055
|
+
parentContext,
|
|
1056
|
+
trace: pipelineTrace,
|
|
1057
|
+
});
|
|
1058
|
+
await this.#updateState({
|
|
1059
|
+
...result.response,
|
|
1060
|
+
updateMode: 'merge',
|
|
1061
|
+
replaceCoveredChainBalances: true,
|
|
1062
|
+
});
|
|
1063
|
+
this.#emitFastFetchTraces({
|
|
1064
|
+
accounts,
|
|
1065
|
+
chainIds,
|
|
1066
|
+
result,
|
|
1067
|
+
durationMs: performance.now() - startTime,
|
|
1068
|
+
isBasicFunctionality,
|
|
1069
|
+
parentContext,
|
|
1070
|
+
pipelineTrace,
|
|
1071
|
+
});
|
|
1072
|
+
return result;
|
|
1073
|
+
},
|
|
1074
|
+
});
|
|
1075
|
+
return response;
|
|
1076
|
+
}
|
|
1077
|
+
async #runFastFetchV6({ accounts, chainIds, request, pipelineTrace, fastSources, isBasicFunctionality, }) {
|
|
1078
|
+
const { response } = await withTrace({
|
|
1079
|
+
name: TRACE_FETCH_PIPELINE,
|
|
1080
|
+
trace: pipelineTrace,
|
|
1081
|
+
data: {
|
|
1082
|
+
chain_count: chainIds.length,
|
|
1083
|
+
account_count: accounts.length,
|
|
1084
|
+
basic_functionality: isBasicFunctionality,
|
|
1085
|
+
},
|
|
1086
|
+
fn: async (parentContext) => {
|
|
1087
|
+
const startTime = performance.now();
|
|
1088
|
+
const result = await this.#executeMiddlewares({
|
|
1089
|
+
sources: fastSources,
|
|
1090
|
+
request,
|
|
1091
|
+
parentContext,
|
|
1092
|
+
trace: pipelineTrace,
|
|
1093
|
+
});
|
|
1094
|
+
await this.#updateState(result.response);
|
|
1095
|
+
this.#emitFastFetchTraces({
|
|
1096
|
+
accounts,
|
|
1097
|
+
chainIds,
|
|
1098
|
+
result,
|
|
1099
|
+
durationMs: performance.now() - startTime,
|
|
1100
|
+
isBasicFunctionality,
|
|
1101
|
+
parentContext,
|
|
1102
|
+
pipelineTrace,
|
|
1103
|
+
});
|
|
1104
|
+
return result;
|
|
1105
|
+
},
|
|
1106
|
+
});
|
|
1107
|
+
return response;
|
|
1108
|
+
}
|
|
1109
|
+
#runBackgroundFetch({ accounts, chainIds, request, pipelineTrace, isBasicFunctionality, }) {
|
|
1110
|
+
if (chainIds.length === 0) {
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
const slowSources = isBasicFunctionality
|
|
1114
|
+
? [this.#snapDataSource, this.#rpcDataSource]
|
|
1115
|
+
: [this.#rpcDataSource];
|
|
1116
|
+
const slowRequest = { ...request, chainIds };
|
|
1117
|
+
withTrace({
|
|
1118
|
+
name: TRACE_BACKGROUND_FETCH,
|
|
1119
|
+
trace: pipelineTrace,
|
|
1120
|
+
data: {
|
|
1121
|
+
chain_count: chainIds.length,
|
|
1122
|
+
account_count: accounts.length,
|
|
1123
|
+
},
|
|
1124
|
+
fn: async (parentContext) => {
|
|
1125
|
+
const { response } = await this.#executeMiddlewares({
|
|
1126
|
+
sources: [
|
|
1127
|
+
createParallelBalanceMiddleware(slowSources),
|
|
1128
|
+
this.#detectionMiddleware,
|
|
1129
|
+
...(isBasicFunctionality
|
|
1130
|
+
? [
|
|
1131
|
+
createParallelMiddleware([
|
|
1132
|
+
this.#tokenDataSource,
|
|
1133
|
+
this.#priceDataSource,
|
|
1134
|
+
]),
|
|
1135
|
+
]
|
|
1136
|
+
: []),
|
|
1137
|
+
],
|
|
1138
|
+
request: slowRequest,
|
|
1139
|
+
parentContext,
|
|
1140
|
+
trace: pipelineTrace,
|
|
1141
|
+
});
|
|
1142
|
+
await this.#updateState(response);
|
|
1143
|
+
},
|
|
1144
|
+
}).catch((error) => log('Background pipeline failed', { error }));
|
|
1145
|
+
}
|
|
1146
|
+
#emitFastFetchTraces({ accounts, chainIds, result, durationMs, isBasicFunctionality, parentContext, pipelineTrace, }) {
|
|
1147
|
+
emitTrace({
|
|
1148
|
+
name: TRACE_FULL_FETCH,
|
|
1149
|
+
trace: pipelineTrace,
|
|
1150
|
+
data: {
|
|
1151
|
+
duration_ms: durationMs,
|
|
1152
|
+
chain_count: chainIds.length,
|
|
1153
|
+
account_count: accounts.length,
|
|
1154
|
+
basic_functionality: isBasicFunctionality,
|
|
1155
|
+
asset_count: result.response.assetsBalance
|
|
1156
|
+
? Object.values(result.response.assetsBalance).reduce((sum, account) => sum + Object.keys(account).length, 0)
|
|
1157
|
+
: 0,
|
|
1158
|
+
price_count: result.response.assetsPrice
|
|
1159
|
+
? Object.keys(result.response.assetsPrice).length
|
|
1160
|
+
: 0,
|
|
1161
|
+
...result.durationByDataSource,
|
|
1162
|
+
},
|
|
1163
|
+
parentContext,
|
|
1164
|
+
});
|
|
1165
|
+
emitTrace({
|
|
1166
|
+
name: TRACE_FIRST_INIT_FETCH,
|
|
1167
|
+
trace: pipelineTrace,
|
|
1168
|
+
data: {
|
|
1169
|
+
duration_ms: durationMs,
|
|
1170
|
+
chain_ids: JSON.stringify(chainIds),
|
|
1171
|
+
...result.durationByDataSource,
|
|
1172
|
+
},
|
|
1173
|
+
parentContext,
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1011
1176
|
async getAssetsBalance(accounts, options) {
|
|
1012
1177
|
// Reuse getAssets with dataTypes: ['balance'] only
|
|
1013
1178
|
const assets = await this.getAssets(accounts, {
|
|
@@ -1216,7 +1381,8 @@ export class AssetsController extends BaseController {
|
|
|
1216
1381
|
/**
|
|
1217
1382
|
* Add a custom asset for an account.
|
|
1218
1383
|
* Custom assets are included in subscription and fetch operations.
|
|
1219
|
-
* Adding a custom asset also unhides it if it was previously hidden
|
|
1384
|
+
* Adding a custom asset also unhides it if it was previously hidden,
|
|
1385
|
+
* and force-fetches that asset's chain (including every visible pin).
|
|
1220
1386
|
*
|
|
1221
1387
|
* When `pendingMetadata` is provided (e.g. from the extension's pending-tokens
|
|
1222
1388
|
* flow), the token metadata is persisted immediately into `assetsInfo` so the
|
|
@@ -1273,22 +1439,19 @@ export class AssetsController extends BaseController {
|
|
|
1273
1439
|
// before the next pipeline fetch returns the real amount.
|
|
1274
1440
|
const balances = state.assetsBalance;
|
|
1275
1441
|
balances[accountId] ??= {};
|
|
1276
|
-
balances[accountId][normalizedAssetId] ??=
|
|
1442
|
+
balances[accountId][normalizedAssetId] ??=
|
|
1443
|
+
getZeroAssetBalance(normalizedAssetId);
|
|
1277
1444
|
});
|
|
1278
|
-
// Fetch data for the newly added custom asset (merge to preserve other chains)
|
|
1279
1445
|
const account = this.#getSelectedAccounts().find((a) => a.id === accountId);
|
|
1280
1446
|
if (account) {
|
|
1281
|
-
const chainId = extractChainId(normalizedAssetId);
|
|
1282
1447
|
await this.getAssets([account], {
|
|
1283
|
-
chainIds: [
|
|
1448
|
+
chainIds: [extractChainId(normalizedAssetId)],
|
|
1284
1449
|
dataTypes: ['balance', 'metadata', 'price'],
|
|
1285
1450
|
assetTypes: ['fungible'],
|
|
1286
1451
|
forceUpdate: true,
|
|
1287
|
-
updateMode: 'merge',
|
|
1288
1452
|
});
|
|
1289
1453
|
}
|
|
1290
|
-
// Re-evaluate subscriptions so
|
|
1291
|
-
// new customAsset on chains another data source already owns.
|
|
1454
|
+
// Re-evaluate subscriptions so polls pick up the new pin.
|
|
1292
1455
|
this.#subscribeAssets();
|
|
1293
1456
|
}
|
|
1294
1457
|
/**
|
|
@@ -1309,8 +1472,7 @@ export class AssetsController extends BaseController {
|
|
|
1309
1472
|
}
|
|
1310
1473
|
}
|
|
1311
1474
|
});
|
|
1312
|
-
// Re-evaluate subscriptions so
|
|
1313
|
-
// is torn down when no more customAssets remain there.
|
|
1475
|
+
// Re-evaluate subscriptions so polls drop the removed pin.
|
|
1314
1476
|
this.#subscribeAssets();
|
|
1315
1477
|
}
|
|
1316
1478
|
/**
|
|
@@ -1341,13 +1503,18 @@ export class AssetsController extends BaseController {
|
|
|
1341
1503
|
}
|
|
1342
1504
|
state.assetPreferences[normalizedAssetId].hidden = true;
|
|
1343
1505
|
});
|
|
1506
|
+
// Re-evaluate subscriptions so polls exclude the newly hidden asset.
|
|
1507
|
+
this.#subscribeAssets();
|
|
1344
1508
|
}
|
|
1345
1509
|
/**
|
|
1346
1510
|
* Unhide an asset globally.
|
|
1511
|
+
* Force-fetches that asset's chain (including every visible pin) so a
|
|
1512
|
+
* `full` snapshot can restore the balance immediately, then re-evaluates
|
|
1513
|
+
* subscriptions so later polls stop excluding it.
|
|
1347
1514
|
*
|
|
1348
1515
|
* @param assetId - The CAIP-19 asset ID to unhide.
|
|
1349
1516
|
*/
|
|
1350
|
-
unhideAsset(assetId) {
|
|
1517
|
+
async unhideAsset(assetId) {
|
|
1351
1518
|
const normalizedAssetId = normalizeAssetId(assetId);
|
|
1352
1519
|
log('Unhiding asset', { assetId: normalizedAssetId });
|
|
1353
1520
|
this.update((state) => {
|
|
@@ -1359,6 +1526,35 @@ export class AssetsController extends BaseController {
|
|
|
1359
1526
|
}
|
|
1360
1527
|
}
|
|
1361
1528
|
});
|
|
1529
|
+
const chainId = extractChainId(normalizedAssetId);
|
|
1530
|
+
const account = this.#getSelectedAccounts().find((selectedAccount) => this.#getEnabledChainsForAccount(selectedAccount).includes(chainId));
|
|
1531
|
+
if (account) {
|
|
1532
|
+
await this.getAssets([account], {
|
|
1533
|
+
chainIds: [chainId],
|
|
1534
|
+
dataTypes: ['balance', 'metadata', 'price'],
|
|
1535
|
+
assetTypes: ['fungible'],
|
|
1536
|
+
forceUpdate: true,
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
this.#subscribeAssets();
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Whether Accounts API v6 (and the v6 custom-asset path) is enabled.
|
|
1543
|
+
* Injected into AccountsApiDataSource and RpcFallbackMiddleware.
|
|
1544
|
+
*
|
|
1545
|
+
* `RemoteFeatureFlagController` resolves a threshold-scoped flag to the
|
|
1546
|
+
* selected group's `value`, so processed state is a boolean.
|
|
1547
|
+
*
|
|
1548
|
+
* @returns `true` when the v6 remote flag is on.
|
|
1549
|
+
*/
|
|
1550
|
+
#isBalanceV6Enabled() {
|
|
1551
|
+
try {
|
|
1552
|
+
const { remoteFeatureFlags } = this.messenger.call('RemoteFeatureFlagController:getState');
|
|
1553
|
+
return remoteFeatureFlags?.assetsAccountsApiV6 === true;
|
|
1554
|
+
}
|
|
1555
|
+
catch {
|
|
1556
|
+
return false;
|
|
1557
|
+
}
|
|
1362
1558
|
}
|
|
1363
1559
|
// ============================================================================
|
|
1364
1560
|
// CURRENT CURRENCY MANAGEMENT
|
|
@@ -1464,7 +1660,6 @@ export class AssetsController extends BaseController {
|
|
|
1464
1660
|
subscriptionId: subscriptionKey,
|
|
1465
1661
|
isUpdate,
|
|
1466
1662
|
onAssetsUpdate: (response) => this.handleAssetsUpdate(response, 'PriceDataSource'),
|
|
1467
|
-
getAssetsState: () => this.state,
|
|
1468
1663
|
};
|
|
1469
1664
|
this.#priceDataSource.subscribe(subscribeReq).catch(console.error);
|
|
1470
1665
|
// Track subscription
|
|
@@ -1504,6 +1699,63 @@ export class AssetsController extends BaseController {
|
|
|
1504
1699
|
#getNativeAssetMap() {
|
|
1505
1700
|
return (this.#queryApiClient.getCachedData(NATIVE_ASSETS_QUERY_KEY) ?? EMPTY_NATIVE_ASSET_MAP);
|
|
1506
1701
|
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Canonical native CAIP-19 ID for a chain. Uses the native asset map when
|
|
1704
|
+
* the chain is registered. An unregistered EVM chain falls back to the
|
|
1705
|
+
* zero-address ERC-20 encoding used for EVM natives without a SLIP-44 id;
|
|
1706
|
+
* an unregistered non-EVM chain resolves no native at all — fabricating an
|
|
1707
|
+
* `erc20:` asset on a non-EVM namespace would produce an ID no data source
|
|
1708
|
+
* ever writes.
|
|
1709
|
+
*
|
|
1710
|
+
* @param chainId - CAIP-2 chain ID.
|
|
1711
|
+
* @returns The native asset ID for the chain, or `undefined` when the
|
|
1712
|
+
* chain is unregistered and non-EVM.
|
|
1713
|
+
*/
|
|
1714
|
+
#getNativeAssetForChain(chainId) {
|
|
1715
|
+
const registered = this.#getNativeAssetMap()[chainId];
|
|
1716
|
+
if (registered) {
|
|
1717
|
+
return registered;
|
|
1718
|
+
}
|
|
1719
|
+
if (parseCaipChainId(chainId).namespace === KnownCaipNamespace.Eip155) {
|
|
1720
|
+
return `${chainId}/${UNKNOWN_EVM_NATIVE_ASSET_REFERENCE}`;
|
|
1721
|
+
}
|
|
1722
|
+
return undefined;
|
|
1723
|
+
}
|
|
1724
|
+
#getAssetVisibility(accountIds, chainIds) {
|
|
1725
|
+
return getAssetVisibility({
|
|
1726
|
+
state: this.state,
|
|
1727
|
+
accountIds,
|
|
1728
|
+
chainIds,
|
|
1729
|
+
getNativeAssetForChain: (chainId) => this.#getNativeAssetForChain(chainId),
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Lowercased asset IDs a v6 `full` snapshot must keep for an account, even
|
|
1734
|
+
* when the response omits them: natives, pins, and default tracked assets
|
|
1735
|
+
* the client asked for. Hidden assets are sent as `excludeAssetIds` and are
|
|
1736
|
+
* dropped here; unhide is expected to fetch a new snapshot. Everything else
|
|
1737
|
+
* on a covered chain was discovered by the source, so its absence means the
|
|
1738
|
+
* balance is gone.
|
|
1739
|
+
*
|
|
1740
|
+
* @param state - State being updated (the in-flight draft, not `this.state`).
|
|
1741
|
+
* @param accountId - Account whose pins apply.
|
|
1742
|
+
* @param accountBalances - Incoming balances, which define the covered chains.
|
|
1743
|
+
* @returns Lowercased CAIP-19 IDs that must survive the replace.
|
|
1744
|
+
*/
|
|
1745
|
+
#getUndeletableAssetIds(state, accountId, accountBalances) {
|
|
1746
|
+
const coveredChainIds = [
|
|
1747
|
+
...new Set(Object.keys(accountBalances).map((assetId) => assetId.split('/')[0])),
|
|
1748
|
+
];
|
|
1749
|
+
const { visibleAssetIds } = getAssetVisibility({
|
|
1750
|
+
state,
|
|
1751
|
+
accountIds: [accountId],
|
|
1752
|
+
chainIds: coveredChainIds,
|
|
1753
|
+
// Registered natives only: the unknown-native fallback is a fabricated
|
|
1754
|
+
// ID, so it must never shield a row from a `full` replace.
|
|
1755
|
+
getNativeAssetForChain: (chainId) => this.#getNativeAssetMap()[chainId],
|
|
1756
|
+
});
|
|
1757
|
+
return new Set(visibleAssetIds.map((assetId) => assetId.toLowerCase()));
|
|
1758
|
+
}
|
|
1507
1759
|
/**
|
|
1508
1760
|
* Checks whether the given CAIP-19 asset ID represents a native asset
|
|
1509
1761
|
* according to the cached native asset map, the asset namespace, and the asset reference.
|
|
@@ -1696,6 +1948,7 @@ export class AssetsController extends BaseController {
|
|
|
1696
1948
|
async #updateState(response) {
|
|
1697
1949
|
const normalizedResponse = normalizeResponse(response);
|
|
1698
1950
|
const mode = normalizedResponse.updateMode ?? 'merge';
|
|
1951
|
+
const isBalanceV6Enabled = this.#isBalanceV6Enabled();
|
|
1699
1952
|
const releaseLock = await this.#controllerMutex.acquire();
|
|
1700
1953
|
try {
|
|
1701
1954
|
const previousState = this.state;
|
|
@@ -1755,24 +2008,23 @@ export class AssetsController extends BaseController {
|
|
|
1755
2008
|
}
|
|
1756
2009
|
}
|
|
1757
2010
|
if (normalizedResponse.assetsBalance) {
|
|
1758
|
-
|
|
1759
|
-
const previousBalances = previousState.assetsBalance[accountId] ?? {};
|
|
2011
|
+
const applyV5AccountBalanceUpdate = (accountId, accountBalances, previousBalances) => {
|
|
1760
2012
|
const customAssetIds = state.customAssets[accountId] ?? [];
|
|
1761
2013
|
const replaceCoveredChains = mode === 'full' ||
|
|
1762
2014
|
normalizedResponse.replaceCoveredChainBalances === true;
|
|
1763
|
-
const
|
|
2015
|
+
const effectiveAccountBalances = effectiveAccountBalancesV5(previousBalances, accountBalances, customAssetIds, replaceCoveredChains);
|
|
1764
2016
|
// Ensure native tokens have an entry (0 if missing) for chains this account supports
|
|
1765
2017
|
const account = this.#getSelectedAccounts().find((a) => a.id === accountId);
|
|
1766
2018
|
const nativeAssetIdsForAccount = account
|
|
1767
2019
|
? this.#getNativeAssetIdsForAccount(account)
|
|
1768
2020
|
: this.#getNativeAssetIdsForEnabledChains();
|
|
1769
2021
|
for (const nativeAssetId of nativeAssetIdsForAccount) {
|
|
1770
|
-
if (!Object.prototype.hasOwnProperty.call(
|
|
1771
|
-
|
|
2022
|
+
if (!Object.prototype.hasOwnProperty.call(effectiveAccountBalances, nativeAssetId)) {
|
|
2023
|
+
effectiveAccountBalances[nativeAssetId] =
|
|
1772
2024
|
getDefaultNativeAssetBalance(nativeAssetId);
|
|
1773
2025
|
}
|
|
1774
2026
|
}
|
|
1775
|
-
for (const [assetId, balance] of Object.entries(
|
|
2027
|
+
for (const [assetId, balance] of Object.entries(effectiveAccountBalances)) {
|
|
1776
2028
|
const previousBalance = previousBalances[assetId];
|
|
1777
2029
|
// Coerce amounts (e.g. "1e-18" from a data source stringifying
|
|
1778
2030
|
// a JS Number) into a plain decimal so downstream BigInt()
|
|
@@ -1785,15 +2037,17 @@ export class AssetsController extends BaseController {
|
|
|
1785
2037
|
const newAmount = normalizeAmountString(balance.amount, assetDecimals);
|
|
1786
2038
|
const newMetadata = balance.metadata ??
|
|
1787
2039
|
previousBalance?.metadata;
|
|
1788
|
-
|
|
2040
|
+
effectiveAccountBalances[assetId] = {
|
|
1789
2041
|
amount: newAmount,
|
|
1790
2042
|
...(newMetadata === undefined ? {} : { metadata: newMetadata }),
|
|
1791
2043
|
};
|
|
1792
2044
|
const oldAmount = previousBalance?.amount;
|
|
1793
|
-
const
|
|
2045
|
+
const isNewSeededZero = oldAmount === undefined &&
|
|
1794
2046
|
newAmount === '0' &&
|
|
1795
|
-
nativeAssetIdsForAccount.includes(assetId)
|
|
1796
|
-
|
|
2047
|
+
(nativeAssetIdsForAccount.includes(assetId) ||
|
|
2048
|
+
getDefaultAssetMetadata(assetId) !==
|
|
2049
|
+
undefined);
|
|
2050
|
+
if (oldAmount !== newAmount && !isNewSeededZero) {
|
|
1797
2051
|
changedBalances.push({
|
|
1798
2052
|
accountId,
|
|
1799
2053
|
assetId,
|
|
@@ -1802,8 +2056,49 @@ export class AssetsController extends BaseController {
|
|
|
1802
2056
|
});
|
|
1803
2057
|
}
|
|
1804
2058
|
}
|
|
1805
|
-
if (!isEqual(previousBalances,
|
|
1806
|
-
balances[accountId] =
|
|
2059
|
+
if (!isEqual(previousBalances, effectiveAccountBalances)) {
|
|
2060
|
+
balances[accountId] = effectiveAccountBalances;
|
|
2061
|
+
}
|
|
2062
|
+
};
|
|
2063
|
+
// Delete with the rest of the v6 path if `assetsAccountsApiV6` is
|
|
2064
|
+
// rolled back; v5 keeps using `applyV5AccountBalanceUpdate`.
|
|
2065
|
+
const applyV6AccountBalanceUpdate = (accountId, accountBalances, previousBalances) => {
|
|
2066
|
+
const effectiveAccountBalances = effectiveAccountBalancesV6(previousBalances, accountBalances, this.#getUndeletableAssetIds(state, accountId, accountBalances), mode);
|
|
2067
|
+
for (const [assetId, balance] of Object.entries(effectiveAccountBalances)) {
|
|
2068
|
+
const previousBalance = previousBalances[assetId];
|
|
2069
|
+
const assetDecimals = metadata[assetId]?.decimals;
|
|
2070
|
+
const newAmount = normalizeAmountString(balance.amount, assetDecimals);
|
|
2071
|
+
const newMetadata = balance.metadata ??
|
|
2072
|
+
previousBalance?.metadata;
|
|
2073
|
+
effectiveAccountBalances[assetId] = {
|
|
2074
|
+
amount: newAmount,
|
|
2075
|
+
...(newMetadata === undefined ? {} : { metadata: newMetadata }),
|
|
2076
|
+
};
|
|
2077
|
+
const oldAmount = previousBalance?.amount;
|
|
2078
|
+
// A v6 snapshot reports assets the account holds none of (its
|
|
2079
|
+
// `includeAssetIds`) at zero. Publishing those as a change would
|
|
2080
|
+
// emit `0 -> 0`, since a missing previous amount reads as '0'.
|
|
2081
|
+
const isNewZero = oldAmount === undefined && newAmount === '0';
|
|
2082
|
+
if (oldAmount !== newAmount && !isNewZero) {
|
|
2083
|
+
changedBalances.push({
|
|
2084
|
+
accountId,
|
|
2085
|
+
assetId,
|
|
2086
|
+
oldAmount,
|
|
2087
|
+
newAmount,
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
if (!isEqual(previousBalances, effectiveAccountBalances)) {
|
|
2092
|
+
balances[accountId] = effectiveAccountBalances;
|
|
2093
|
+
}
|
|
2094
|
+
};
|
|
2095
|
+
for (const [accountId, accountBalances] of Object.entries(normalizedResponse.assetsBalance)) {
|
|
2096
|
+
const previousBalances = previousState.assetsBalance[accountId] ?? {};
|
|
2097
|
+
if (isBalanceV6Enabled) {
|
|
2098
|
+
applyV6AccountBalanceUpdate(accountId, accountBalances, previousBalances);
|
|
2099
|
+
}
|
|
2100
|
+
else {
|
|
2101
|
+
applyV5AccountBalanceUpdate(accountId, accountBalances, previousBalances);
|
|
1807
2102
|
}
|
|
1808
2103
|
}
|
|
1809
2104
|
}
|
|
@@ -1827,10 +2122,8 @@ export class AssetsController extends BaseController {
|
|
|
1827
2122
|
changedPriceAssets.length > 0) {
|
|
1828
2123
|
log('State updated', {
|
|
1829
2124
|
changedBalances: changedBalances.length > 0 ? changedBalances : undefined,
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
? changedPriceAssets.length
|
|
1833
|
-
: undefined,
|
|
2125
|
+
changedMetadata: changedMetadata.length > 0 ? changedMetadata : undefined,
|
|
2126
|
+
changedPrices: changedPriceAssets.length > 0 ? changedPriceAssets : undefined,
|
|
1834
2127
|
newAssets: Object.keys(detectedAssets).length > 0
|
|
1835
2128
|
? Object.entries(detectedAssets).map(([accountId, assets]) => ({
|
|
1836
2129
|
accountId,
|
|
@@ -2052,16 +2345,10 @@ export class AssetsController extends BaseController {
|
|
|
2052
2345
|
if (!subscriptionKey.startsWith('ds:')) {
|
|
2053
2346
|
continue;
|
|
2054
2347
|
}
|
|
2055
|
-
// Subscription keys take the form `ds:<SourceName
|
|
2056
|
-
// subscription or `ds:<SourceName>:<suffix>` for supplemental
|
|
2057
|
-
// subscriptions (e.g. `ds:RpcDataSource:custom`). Split on `:` and
|
|
2058
|
-
// pick the source-name segment so both shapes resolve correctly.
|
|
2348
|
+
// Subscription keys take the form `ds:<SourceName>`.
|
|
2059
2349
|
const [, sourceId] = subscriptionKey.split(':');
|
|
2060
2350
|
const source = allSources.find((ds) => ds.getName() === sourceId);
|
|
2061
2351
|
if (source) {
|
|
2062
|
-
// Unsubscribe by the actual key — `#unsubscribeDataSource` only
|
|
2063
|
-
// knows the regular `ds:<SourceName>` shape and would miss
|
|
2064
|
-
// supplemental subscriptions, leaking their polling timers.
|
|
2065
2352
|
this.#unsubscribeBySubscriptionKey(source, subscriptionKey);
|
|
2066
2353
|
}
|
|
2067
2354
|
}
|
|
@@ -2105,10 +2392,12 @@ export class AssetsController extends BaseController {
|
|
|
2105
2392
|
* Strategy to minimize data source calls:
|
|
2106
2393
|
* 1. Collect all chains to subscribe based on enabled networks
|
|
2107
2394
|
* 2. Map chains to accounts based on their scopes
|
|
2108
|
-
* 3. Split by data source (
|
|
2109
|
-
*
|
|
2110
|
-
*
|
|
2111
|
-
*
|
|
2395
|
+
* 3. Split by data source (priority order) — each source gets one
|
|
2396
|
+
* subscription for the chains it was assigned (accounts + chains only).
|
|
2397
|
+
* v6 sources read pins/hides/defaults from state themselves (Accounts API
|
|
2398
|
+
* sends `includeAssetIds` / `excludeAssetIds`; RPC polls native, balances,
|
|
2399
|
+
* pins, and default tracked assets). v5 uses a separate RPC
|
|
2400
|
+
* `customAssetsOnly` supplement for pins on chains another source owns.
|
|
2112
2401
|
*
|
|
2113
2402
|
* @param accounts - Accounts to subscribe balance updates for.
|
|
2114
2403
|
* @param chainIds - Chain IDs to subscribe for.
|
|
@@ -2118,10 +2407,7 @@ export class AssetsController extends BaseController {
|
|
|
2118
2407
|
#subscribeAssetsBalance(accounts, chainIds, options) {
|
|
2119
2408
|
const chainToAccounts = this.#buildChainToAccountsMap(accounts, new Set(chainIds));
|
|
2120
2409
|
const remainingChains = new Set(chainToAccounts.keys());
|
|
2121
|
-
|
|
2122
|
-
// RPC only.
|
|
2123
|
-
const isBasicFunctionality = this.#isBasicFunctionality();
|
|
2124
|
-
const balanceDataSources = isBasicFunctionality
|
|
2410
|
+
const balanceDataSources = this.#isBasicFunctionality()
|
|
2125
2411
|
? this.#allBalanceDataSources
|
|
2126
2412
|
: [this.#rpcDataSource];
|
|
2127
2413
|
let rpcAssignedChains = new Set();
|
|
@@ -2160,22 +2446,15 @@ export class AssetsController extends BaseController {
|
|
|
2160
2446
|
});
|
|
2161
2447
|
}
|
|
2162
2448
|
}
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
// AccountsApi is already covering the chain for normal balances. The
|
|
2167
|
-
// supplemental subscription runs in `customAssetsOnly` mode so it does
|
|
2168
|
-
// NOT double-poll the regular tracked balances.
|
|
2169
|
-
this.#subscribeRpcCustomAssetsSupplement(accounts, chainToAccounts, rpcAssignedChains);
|
|
2449
|
+
if (!this.#isBalanceV6Enabled()) {
|
|
2450
|
+
this.#subscribeRpcCustomAssetsSupplement(accounts, chainToAccounts, rpcAssignedChains);
|
|
2451
|
+
}
|
|
2170
2452
|
}
|
|
2171
2453
|
/**
|
|
2172
2454
|
* Guarantee that customAssets are **always** polled by RPC, even when
|
|
2173
2455
|
* AccountsApi or another data source has claimed the chain in the
|
|
2174
2456
|
* regular handoff. RPC is the sole balance fetcher for user-imported
|
|
2175
|
-
* tokens (see `pickRpcCustomAssetsSupplement`
|
|
2176
|
-
* so we run a dedicated subscription in `customAssetsOnly` mode under a
|
|
2177
|
-
* distinct subscription key (`ds:RpcDataSource:custom`) that does not
|
|
2178
|
-
* interfere with the regular RPC subscription.
|
|
2457
|
+
* tokens on the Accounts API v5 path (see `pickRpcCustomAssetsSupplement`).
|
|
2179
2458
|
*
|
|
2180
2459
|
* @param accounts - Accounts to consider for customAssets.
|
|
2181
2460
|
* @param chainToAccounts - Map of chain → accounts (built by caller).
|
|
@@ -2273,7 +2552,8 @@ export class AssetsController extends BaseController {
|
|
|
2273
2552
|
* @param chains - Array of chain IDs to subscribe for.
|
|
2274
2553
|
* @param options - Optional subscription overrides.
|
|
2275
2554
|
* @param options.subscriptionKey - Custom subscription key (default: `ds:<sourceId>`).
|
|
2276
|
-
* @param options.customAssetsOnly - When true, only poll customAssets for these
|
|
2555
|
+
* @param options.customAssetsOnly - When true, only poll customAssets for these
|
|
2556
|
+
* chains (Accounts API v5 supplemental RPC subscription).
|
|
2277
2557
|
* @param options.skipInitialFetch - When true, skip the data source's subscribe-time fetch.
|
|
2278
2558
|
*/
|
|
2279
2559
|
#subscribeDataSource(source, accounts, chains, options = {}) {
|
|
@@ -2302,7 +2582,6 @@ export class AssetsController extends BaseController {
|
|
|
2302
2582
|
subscriptionId: subscriptionKey,
|
|
2303
2583
|
isUpdate,
|
|
2304
2584
|
onAssetsUpdate: (response, request) => this.handleAssetsUpdate(response, sourceId, request),
|
|
2305
|
-
getAssetsState: () => this.state,
|
|
2306
2585
|
...(options.skipInitialFetch === true ? { skipInitialFetch: true } : {}),
|
|
2307
2586
|
};
|
|
2308
2587
|
source.subscribe(subscribeReq).catch((error) => {
|
|
@@ -2486,13 +2765,13 @@ export class AssetsController extends BaseController {
|
|
|
2486
2765
|
// The data will simply not be updated until the network is re-enabled.
|
|
2487
2766
|
// Refresh subscriptions for new chain set
|
|
2488
2767
|
this.#subscribeAssets();
|
|
2489
|
-
//
|
|
2768
|
+
// One-time fetch for newly enabled chains. v6 `full` replace is scoped to
|
|
2769
|
+
// those chains; existing balances on other chains are left untouched.
|
|
2490
2770
|
const accounts = this.#getSelectedAccounts();
|
|
2491
2771
|
if (addedChains.length > 0 && accounts.length > 0) {
|
|
2492
2772
|
await this.getAssets(accounts, {
|
|
2493
2773
|
chainIds: addedChains,
|
|
2494
2774
|
forceUpdate: true,
|
|
2495
|
-
updateMode: 'merge',
|
|
2496
2775
|
});
|
|
2497
2776
|
}
|
|
2498
2777
|
this.#ensureNativeBalancesDefaultZero();
|
|
@@ -2652,48 +2931,28 @@ export class AssetsController extends BaseController {
|
|
|
2652
2931
|
async handleAssetsUpdate(response, sourceId, request) {
|
|
2653
2932
|
log('Assets updated from data source', {
|
|
2654
2933
|
sourceId,
|
|
2655
|
-
|
|
2656
|
-
|
|
2934
|
+
assetsBalance: response.assetsBalance,
|
|
2935
|
+
assetsPrice: response.assetsPrice,
|
|
2657
2936
|
});
|
|
2658
|
-
|
|
2937
|
+
if (this.#isBalanceV6Enabled()) {
|
|
2938
|
+
await this.#handleAssetsUpdateV6(response, sourceId, request);
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2941
|
+
await this.#handleAssetsUpdateV5(response, sourceId, request);
|
|
2942
|
+
}
|
|
2943
|
+
async #handleAssetsUpdateV5(response, sourceId, request) {
|
|
2659
2944
|
const pipelineTrace = this.#firstInitFetchReported
|
|
2660
2945
|
? undefined
|
|
2661
2946
|
: this.#trace;
|
|
2662
2947
|
await withTrace({
|
|
2663
2948
|
name: TRACE_UPDATE_PARENT,
|
|
2664
2949
|
trace: pipelineTrace,
|
|
2665
|
-
data:
|
|
2666
|
-
source: sourceId,
|
|
2667
|
-
has_balance: Boolean(response.assetsBalance),
|
|
2668
|
-
has_price: Boolean(response.assetsPrice),
|
|
2669
|
-
balance_account_count: response.assetsBalance
|
|
2670
|
-
? Object.keys(response.assetsBalance).length
|
|
2671
|
-
: 0,
|
|
2672
|
-
},
|
|
2950
|
+
data: this.#getUpdateTraceData(response, sourceId),
|
|
2673
2951
|
fn: async (parentContext) => {
|
|
2674
2952
|
const updateStart = performance.now();
|
|
2675
|
-
const
|
|
2676
|
-
accountsWithSupportedChains: [],
|
|
2677
|
-
chainIds: [],
|
|
2678
|
-
dataTypes: ['balance', 'metadata', 'price'],
|
|
2679
|
-
};
|
|
2680
|
-
// RPC-only mode (basic functionality off): never run token/price APIs. Strip
|
|
2681
|
-
// those data types so downstream middleware cannot treat them as requested.
|
|
2682
|
-
const pipelineRequest = this.#isBasicFunctionality()
|
|
2683
|
-
? resolvedRequest
|
|
2684
|
-
: {
|
|
2685
|
-
...resolvedRequest,
|
|
2686
|
-
dataTypes: resolvedRequest.dataTypes.filter((dt) => dt !== 'metadata' && dt !== 'price'),
|
|
2687
|
-
};
|
|
2688
|
-
// Graduate custom assets only when AccountsAPI / AccountActivity reports
|
|
2689
|
-
// them. RPC already fetches custom assets on purpose, and Snap handles
|
|
2690
|
-
// non-EVM chains the rule does not apply to, so skip the middleware for
|
|
2691
|
-
// those.
|
|
2953
|
+
const pipelineRequest = this.#getUpdatePipelineRequest(request);
|
|
2692
2954
|
const shouldGraduateCustomAssets = sourceId === 'AccountsApiDataSource' ||
|
|
2693
2955
|
sourceId === 'AccountActivityDataSource';
|
|
2694
|
-
// Websocket updates can carry brand-new spam airdrops: enrich them
|
|
2695
|
-
// with Token API occurrences and drop below-floor tokens BEFORE
|
|
2696
|
-
// detection, so spam is never detected, enriched, priced or persisted.
|
|
2697
2956
|
const shouldFilterOccurrences = sourceId === 'AccountActivityDataSource' &&
|
|
2698
2957
|
this.#isBasicFunctionality();
|
|
2699
2958
|
const shouldRunRpcFallback = sourceId === 'AccountsApiDataSource';
|
|
@@ -2729,25 +2988,110 @@ export class AssetsController extends BaseController {
|
|
|
2729
2988
|
...enrichedResponse,
|
|
2730
2989
|
replaceCoveredChainBalances: response.replaceCoveredChainBalances,
|
|
2731
2990
|
});
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2991
|
+
this.#emitUpdateTrace({
|
|
2992
|
+
response,
|
|
2993
|
+
sourceId,
|
|
2994
|
+
enrichedResponse,
|
|
2995
|
+
updateStart,
|
|
2996
|
+
pipelineTrace,
|
|
2997
|
+
parentContext,
|
|
2998
|
+
});
|
|
2999
|
+
},
|
|
3000
|
+
});
|
|
3001
|
+
}
|
|
3002
|
+
async #handleAssetsUpdateV6(response, sourceId, request) {
|
|
3003
|
+
const pipelineTrace = this.#firstInitFetchReported
|
|
3004
|
+
? undefined
|
|
3005
|
+
: this.#trace;
|
|
3006
|
+
await withTrace({
|
|
3007
|
+
name: TRACE_UPDATE_PARENT,
|
|
3008
|
+
trace: pipelineTrace,
|
|
3009
|
+
data: this.#getUpdateTraceData(response, sourceId),
|
|
3010
|
+
fn: async (parentContext) => {
|
|
3011
|
+
const updateStart = performance.now();
|
|
3012
|
+
const pipelineRequest = this.#getUpdatePipelineRequest(request);
|
|
3013
|
+
const shouldFilterOccurrences = sourceId === 'AccountActivityDataSource' &&
|
|
3014
|
+
this.#isBasicFunctionality();
|
|
3015
|
+
const enrichmentSources = [
|
|
3016
|
+
...(shouldFilterOccurrences
|
|
3017
|
+
? [
|
|
3018
|
+
{
|
|
3019
|
+
getName: () => 'OccurrenceFloorFilter',
|
|
3020
|
+
assetsMiddleware: this.#tokenDataSource.occurrenceFilterMiddleware,
|
|
3021
|
+
},
|
|
3022
|
+
]
|
|
3023
|
+
: []),
|
|
3024
|
+
];
|
|
3025
|
+
if (this.#isBasicFunctionality()) {
|
|
3026
|
+
enrichmentSources.push(this.#rpcFallbackMiddleware);
|
|
3027
|
+
}
|
|
3028
|
+
enrichmentSources.push(this.#detectionMiddleware);
|
|
3029
|
+
if (this.#isBasicFunctionality()) {
|
|
3030
|
+
enrichmentSources.push(createParallelMiddleware([
|
|
3031
|
+
this.#tokenDataSource,
|
|
3032
|
+
this.#priceDataSource,
|
|
3033
|
+
]));
|
|
3034
|
+
}
|
|
3035
|
+
const { response: enrichedResponse } = await this.#executeMiddlewares({
|
|
3036
|
+
sources: enrichmentSources,
|
|
3037
|
+
request: pipelineRequest,
|
|
3038
|
+
initialResponse: response,
|
|
3039
|
+
parentContext,
|
|
2735
3040
|
trace: pipelineTrace,
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
: 0,
|
|
2745
|
-
},
|
|
3041
|
+
});
|
|
3042
|
+
await this.#updateState(enrichedResponse);
|
|
3043
|
+
this.#emitUpdateTrace({
|
|
3044
|
+
response,
|
|
3045
|
+
sourceId,
|
|
3046
|
+
enrichedResponse,
|
|
3047
|
+
updateStart,
|
|
3048
|
+
pipelineTrace,
|
|
2746
3049
|
parentContext,
|
|
2747
3050
|
});
|
|
2748
3051
|
},
|
|
2749
3052
|
});
|
|
2750
3053
|
}
|
|
3054
|
+
#getUpdatePipelineRequest(request) {
|
|
3055
|
+
const resolvedRequest = request ?? {
|
|
3056
|
+
accountsWithSupportedChains: [],
|
|
3057
|
+
chainIds: [],
|
|
3058
|
+
dataTypes: ['balance', 'metadata', 'price'],
|
|
3059
|
+
};
|
|
3060
|
+
if (this.#isBasicFunctionality()) {
|
|
3061
|
+
return resolvedRequest;
|
|
3062
|
+
}
|
|
3063
|
+
return {
|
|
3064
|
+
...resolvedRequest,
|
|
3065
|
+
dataTypes: resolvedRequest.dataTypes.filter((dataType) => dataType !== 'metadata' && dataType !== 'price'),
|
|
3066
|
+
};
|
|
3067
|
+
}
|
|
3068
|
+
#getUpdateTraceData(response, sourceId) {
|
|
3069
|
+
return {
|
|
3070
|
+
source: sourceId,
|
|
3071
|
+
has_balance: Boolean(response.assetsBalance),
|
|
3072
|
+
has_price: Boolean(response.assetsPrice),
|
|
3073
|
+
balance_account_count: response.assetsBalance
|
|
3074
|
+
? Object.keys(response.assetsBalance).length
|
|
3075
|
+
: 0,
|
|
3076
|
+
};
|
|
3077
|
+
}
|
|
3078
|
+
#emitUpdateTrace({ response, sourceId, enrichedResponse, updateStart, pipelineTrace, parentContext, }) {
|
|
3079
|
+
emitTrace({
|
|
3080
|
+
name: TRACE_UPDATE_PIPELINE,
|
|
3081
|
+
trace: pipelineTrace,
|
|
3082
|
+
data: {
|
|
3083
|
+
source: sourceId,
|
|
3084
|
+
duration_ms: performance.now() - updateStart,
|
|
3085
|
+
has_balance: Boolean(response.assetsBalance),
|
|
3086
|
+
has_price: Boolean(response.assetsPrice),
|
|
3087
|
+
has_metadata: Boolean(enrichedResponse.assetsInfo),
|
|
3088
|
+
balance_account_count: response.assetsBalance
|
|
3089
|
+
? Object.keys(response.assetsBalance).length
|
|
3090
|
+
: 0,
|
|
3091
|
+
},
|
|
3092
|
+
parentContext,
|
|
3093
|
+
});
|
|
3094
|
+
}
|
|
2751
3095
|
// ============================================================================
|
|
2752
3096
|
// CLEANUP
|
|
2753
3097
|
// ============================================================================
|