@metamask-previews/assets-controller 16.1.0-preview-b1e33e9f1 → 16.1.0-preview-d2ea3cc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -28
- package/dist/AssetsController-method-action-types.d.ts +1 -5
- 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 +40 -8
- package/dist/AssetsController.d.ts.map +1 -1
- package/dist/AssetsController.js +235 -570
- package/dist/AssetsController.js.map +1 -1
- package/dist/data-sources/AbstractDataSource.d.ts +6 -1
- 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 +3 -17
- package/dist/data-sources/AccountsApiDataSource.d.ts.map +1 -1
- package/dist/data-sources/AccountsApiDataSource.js +125 -275
- package/dist/data-sources/AccountsApiDataSource.js.map +1 -1
- package/dist/data-sources/PriceDataSource.d.ts +6 -7
- package/dist/data-sources/PriceDataSource.d.ts.map +1 -1
- package/dist/data-sources/PriceDataSource.js +20 -15
- package/dist/data-sources/PriceDataSource.js.map +1 -1
- package/dist/data-sources/RpcDataSource.d.ts +1 -15
- package/dist/data-sources/RpcDataSource.d.ts.map +1 -1
- package/dist/data-sources/RpcDataSource.js +107 -398
- package/dist/data-sources/RpcDataSource.js.map +1 -1
- package/dist/data-sources/SnapDataSource.d.ts +0 -5
- package/dist/data-sources/SnapDataSource.d.ts.map +1 -1
- package/dist/data-sources/SnapDataSource.js +0 -114
- package/dist/data-sources/SnapDataSource.js.map +1 -1
- package/dist/data-sources/TokenDataSource.d.ts +1 -3
- package/dist/data-sources/TokenDataSource.d.ts.map +1 -1
- package/dist/data-sources/TokenDataSource.js +2 -4
- package/dist/data-sources/TokenDataSource.js.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.ts +0 -8
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.d.ts.map +1 -1
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.js +2 -47
- package/dist/data-sources/evm-rpc-services/services/BalanceFetcher.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 +1 -3
- package/dist/middlewares/CustomAssetGraduationMiddleware.d.ts.map +1 -1
- package/dist/middlewares/CustomAssetGraduationMiddleware.js +1 -3
- package/dist/middlewares/CustomAssetGraduationMiddleware.js.map +1 -1
- package/dist/middlewares/DetectionMiddleware.d.ts +2 -10
- package/dist/middlewares/DetectionMiddleware.d.ts.map +1 -1
- package/dist/middlewares/DetectionMiddleware.js +23 -5
- package/dist/middlewares/DetectionMiddleware.js.map +1 -1
- package/dist/middlewares/ParallelMiddleware.d.ts.map +1 -1
- package/dist/middlewares/ParallelMiddleware.js +3 -0
- package/dist/middlewares/ParallelMiddleware.js.map +1 -1
- package/dist/middlewares/RpcFallbackMiddleware.d.ts +15 -16
- package/dist/middlewares/RpcFallbackMiddleware.d.ts.map +1 -1
- package/dist/middlewares/RpcFallbackMiddleware.js +107 -125
- package/dist/middlewares/RpcFallbackMiddleware.js.map +1 -1
- package/dist/middlewares/index.d.ts +0 -1
- 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 +0 -5
- package/dist/pipeline/buildFastFetchSources.d.ts.map +1 -1
- package/dist/pipeline/buildFastFetchSources.js +1 -7
- package/dist/pipeline/buildFastFetchSources.js.map +1 -1
- package/dist/pipeline/executeAssetsPipeline.d.ts +3 -1
- package/dist/pipeline/executeAssetsPipeline.d.ts.map +1 -1
- package/dist/pipeline/executeAssetsPipeline.js +2 -1
- 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 +23 -29
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/dist/utils/assetVisibility.d.ts +0 -29
- package/dist/utils/assetVisibility.d.ts.map +0 -1
- package/dist/utils/assetVisibility.js +0 -80
- package/dist/utils/assetVisibility.js.map +0 -1
- package/dist/utils/filterFailedChainBalances.d.ts +0 -11
- package/dist/utils/filterFailedChainBalances.d.ts.map +0 -1
- package/dist/utils/filterFailedChainBalances.js +0 -22
- package/dist/utils/filterFailedChainBalances.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Middleware } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* DetectionMiddleware builds the set of assets that downstream sources use for
|
|
4
4
|
* metadata and price fetching.
|
|
@@ -14,20 +14,12 @@ import type { AssetsControllerState, Middleware } from '../types.js';
|
|
|
14
14
|
*
|
|
15
15
|
* Usage:
|
|
16
16
|
* ```typescript
|
|
17
|
-
* const detectionMiddleware = new DetectionMiddleware(
|
|
18
|
-
* getAssetsState: () => this.state,
|
|
19
|
-
* });
|
|
17
|
+
* const detectionMiddleware = new DetectionMiddleware();
|
|
20
18
|
* const middleware = detectionMiddleware.assetsMiddleware;
|
|
21
19
|
* ```
|
|
22
20
|
*/
|
|
23
|
-
export type DetectionMiddlewareOptions = {
|
|
24
|
-
/** Current AssetsController state. Used to distinguish newly detected assets. */
|
|
25
|
-
getAssetsState: () => AssetsControllerState;
|
|
26
|
-
};
|
|
27
21
|
export declare class DetectionMiddleware {
|
|
28
|
-
#private;
|
|
29
22
|
readonly name = "DetectionMiddleware";
|
|
30
|
-
constructor(options: DetectionMiddlewareOptions);
|
|
31
23
|
getName(): string;
|
|
32
24
|
/**
|
|
33
25
|
* Get the middleware that builds detectedAssets for metadata and price fetching.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DetectionMiddleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/DetectionMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"DetectionMiddleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/DetectionMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA4B,UAAU,EAAE,MAAM,aAAa,CAAC;AAgBxE;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,mBAAmB;IAC9B,QAAQ,CAAC,IAAI,yBAAmB;IAEhC,OAAO,IAAI,MAAM,CAEhB;IAED;;;;;;;;;;;;;OAaG;IACH,IAAI,gBAAgB,IAAI,UAAU,CA+HjC;CACF"}
|
|
@@ -7,12 +7,30 @@ import { normalizeAssetId } from '../utils/index.js';
|
|
|
7
7
|
const CONTROLLER_NAME = 'DetectionMiddleware';
|
|
8
8
|
// Logger for debugging
|
|
9
9
|
createModuleLogger(projectLogger, CONTROLLER_NAME);
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// DETECTION MIDDLEWARE
|
|
12
|
+
// ============================================================================
|
|
13
|
+
/**
|
|
14
|
+
* DetectionMiddleware builds the set of assets that downstream sources use for
|
|
15
|
+
* metadata and price fetching.
|
|
16
|
+
*
|
|
17
|
+
* An asset is included in `detectedAssets` only when it is genuinely new:
|
|
18
|
+
* - Assets from `response.assetsBalance` are included only if they are absent
|
|
19
|
+
* from BOTH `state.assetsBalance` (never tracked before) AND `state.assetsInfo`
|
|
20
|
+
* (no metadata yet). Assets already present in either collection are considered
|
|
21
|
+
* known and are intentionally excluded — PriceDataSource's own subscription
|
|
22
|
+
* handles periodic refreshes for those.
|
|
23
|
+
* - Each account's custom assets from state are always included because they
|
|
24
|
+
* may have no balance yet and are explicitly managed by the user.
|
|
25
|
+
*
|
|
26
|
+
* Usage:
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const detectionMiddleware = new DetectionMiddleware();
|
|
29
|
+
* const middleware = detectionMiddleware.assetsMiddleware;
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
10
32
|
export class DetectionMiddleware {
|
|
11
33
|
name = CONTROLLER_NAME;
|
|
12
|
-
#getAssetsState;
|
|
13
|
-
constructor(options) {
|
|
14
|
-
this.#getAssetsState = options.getAssetsState;
|
|
15
|
-
}
|
|
16
34
|
getName() {
|
|
17
35
|
return this.name;
|
|
18
36
|
}
|
|
@@ -34,7 +52,7 @@ export class DetectionMiddleware {
|
|
|
34
52
|
return forDataTypes(['balance'], async (ctx, next) => {
|
|
35
53
|
const { request, response } = ctx;
|
|
36
54
|
// Get state for custom assets, existing balances, and existing metadata
|
|
37
|
-
const state =
|
|
55
|
+
const state = ctx.getAssetsState();
|
|
38
56
|
const { customAssets: stateCustomAssets, assetsBalance: stateAssetsBalance, assetsInfo: stateAssetsInfo, assetsPrice: stateAssetsPrice, } = state;
|
|
39
57
|
const detectedAssets = {};
|
|
40
58
|
// 1. From balance response: only include assets that are genuinely new —
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DetectionMiddleware.js","sourceRoot":"","sources":["../../src/middlewares/DetectionMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { projectLogger, createModuleLogger } from '../logger.js';\nimport { forDataTypes } from '../types.js';\nimport type {\n AccountId,\n AssetsControllerState,\n Caip19AssetId,\n Middleware,\n} from '../types.js';\nimport { normalizeAssetId } from '../utils/index.js';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'DetectionMiddleware';\n\n// Logger for debugging\ncreateModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// DETECTION MIDDLEWARE\n// ============================================================================\n\n/**\n * DetectionMiddleware builds the set of assets that downstream sources use for\n * metadata and price fetching.\n *\n * An asset is included in `detectedAssets` only when it is genuinely new:\n * - Assets from `response.assetsBalance` are included only if they are absent\n * from BOTH `state.assetsBalance` (never tracked before) AND `state.assetsInfo`\n * (no metadata yet). Assets already present in either collection are considered\n * known and are intentionally excluded — PriceDataSource's own subscription\n * handles periodic refreshes for those.\n * - Each account's custom assets from state are always included because they\n * may have no balance yet and are explicitly managed by the user.\n *\n * Usage:\n * ```typescript\n * const detectionMiddleware = new DetectionMiddleware({\n * getAssetsState: () => this.state,\n * });\n * const middleware = detectionMiddleware.assetsMiddleware;\n * ```\n */\nexport type DetectionMiddlewareOptions = {\n /** Current AssetsController state. Used to distinguish newly detected assets. */\n getAssetsState: () => AssetsControllerState;\n};\n\nexport class DetectionMiddleware {\n readonly name = CONTROLLER_NAME;\n\n readonly #getAssetsState: () => AssetsControllerState;\n\n constructor(options: DetectionMiddlewareOptions) {\n this.#getAssetsState = options.getAssetsState;\n }\n\n getName(): string {\n return this.name;\n }\n\n /**\n * Get the middleware that builds detectedAssets for metadata and price fetching.\n *\n * This middleware:\n * 1. Includes assets from response.assetsBalance that are absent from both\n * state.assetsBalance and state.assetsInfo (brand-new assets only)\n * 2. Always includes each account's custom assets from state\n * 3. Fills response.detectedAssets with the resulting asset IDs per account\n * 4. Queues detected assets that lack a price in state on\n * request.assetsForPriceUpdate so PriceDataSource fetches them in the same\n * pipeline pass (including the background RPC detection path)\n *\n * @returns The middleware function for the assets pipeline.\n */\n get assetsMiddleware(): Middleware {\n return forDataTypes(['balance'], async (ctx, next) => {\n const { request, response } = ctx;\n\n // Get state for custom assets, existing balances, and existing metadata\n const state = this.#getAssetsState();\n const {\n customAssets: stateCustomAssets,\n assetsBalance: stateAssetsBalance,\n assetsInfo: stateAssetsInfo,\n assetsPrice: stateAssetsPrice,\n } = state;\n\n const detectedAssets: Record<AccountId, Caip19AssetId[]> = {};\n\n // 1. From balance response: only include assets that are genuinely new —\n // not already present in state.assetsBalance or state.assetsInfo.\n if (response.assetsBalance) {\n for (const [accountId, accountBalances] of Object.entries(\n response.assetsBalance,\n )) {\n const detected: Caip19AssetId[] = [];\n\n const stateAccountBalances = stateAssetsBalance[accountId] ?? {};\n\n for (const assetId of Object.keys(\n accountBalances as Record<string, unknown>,\n )) {\n const caipAssetId = assetId as Caip19AssetId;\n // Skip if already tracked in state balances or already has metadata\n if (\n stateAccountBalances[caipAssetId] !== undefined ||\n stateAssetsInfo[caipAssetId] !== undefined\n ) {\n continue;\n }\n detected.push(caipAssetId);\n }\n\n // Merge custom assets for this account, applying the same filter:\n // skip if already in state balance or already has metadata.\n const customForAccount = stateCustomAssets?.[accountId] ?? [];\n for (const assetId of customForAccount) {\n if (detected.includes(assetId)) {\n continue;\n }\n if (\n stateAccountBalances[assetId] !== undefined ||\n stateAssetsInfo[assetId] !== undefined\n ) {\n continue;\n }\n detected.push(assetId);\n }\n\n if (detected.length > 0) {\n detectedAssets[accountId] = detected;\n }\n }\n }\n\n // 2. Accounts in request that weren't in balance response: include their\n // custom assets that are not yet in state.\n for (const { account } of request.accountsWithSupportedChains) {\n const accountId = account.id;\n if (detectedAssets[accountId]) {\n continue;\n }\n const stateAccountBalances = stateAssetsBalance[accountId] ?? {};\n const customForAccount = stateCustomAssets?.[accountId] ?? [];\n const newCustomAssets = customForAccount.filter((assetId) => {\n const inBalance = stateAccountBalances[assetId] !== undefined;\n const inInfo = stateAssetsInfo[assetId] !== undefined;\n return !inBalance && !inInfo;\n });\n if (newCustomAssets.length > 0) {\n detectedAssets[accountId] = newCustomAssets;\n }\n }\n\n if (Object.keys(detectedAssets).length > 0) {\n response.detectedAssets = detectedAssets;\n }\n\n // Queue prices for:\n // 1) newly detected assets missing a price, and\n // 2) assets in this balance response that already exist in state but still\n // lack a price (e.g. natives seeded before the first price poll).\n // PriceDataSource filters non-priceable IDs before calling the API.\n const prices = stateAssetsPrice as Record<string, unknown>;\n const missingPriceAssets = new Set<Caip19AssetId>();\n\n const maybeQueue = (assetId: Caip19AssetId): void => {\n const normalizedAssetId = normalizeAssetId(assetId);\n if (\n prices[normalizedAssetId] === undefined &&\n prices[assetId] === undefined\n ) {\n missingPriceAssets.add(normalizedAssetId);\n }\n };\n\n for (const accountAssetIds of Object.values(detectedAssets)) {\n for (const assetId of accountAssetIds) {\n maybeQueue(assetId);\n }\n }\n\n if (response.assetsBalance) {\n for (const accountBalances of Object.values(response.assetsBalance)) {\n for (const assetId of Object.keys(\n accountBalances as Record<string, unknown>,\n )) {\n maybeQueue(assetId as Caip19AssetId);\n }\n }\n }\n\n if (missingPriceAssets.size > 0) {\n request.assetsForPriceUpdate = [\n ...(request.assetsForPriceUpdate ?? []),\n ...missingPriceAssets,\n ];\n }\n\n return next(ctx);\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"DetectionMiddleware.js","sourceRoot":"","sources":["../../src/middlewares/DetectionMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { projectLogger, createModuleLogger } from '../logger.js';\nimport { forDataTypes } from '../types.js';\nimport type { AccountId, Caip19AssetId, Middleware } from '../types.js';\nimport { normalizeAssetId } from '../utils/index.js';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst CONTROLLER_NAME = 'DetectionMiddleware';\n\n// Logger for debugging\ncreateModuleLogger(projectLogger, CONTROLLER_NAME);\n\n// ============================================================================\n// DETECTION MIDDLEWARE\n// ============================================================================\n\n/**\n * DetectionMiddleware builds the set of assets that downstream sources use for\n * metadata and price fetching.\n *\n * An asset is included in `detectedAssets` only when it is genuinely new:\n * - Assets from `response.assetsBalance` are included only if they are absent\n * from BOTH `state.assetsBalance` (never tracked before) AND `state.assetsInfo`\n * (no metadata yet). Assets already present in either collection are considered\n * known and are intentionally excluded — PriceDataSource's own subscription\n * handles periodic refreshes for those.\n * - Each account's custom assets from state are always included because they\n * may have no balance yet and are explicitly managed by the user.\n *\n * Usage:\n * ```typescript\n * const detectionMiddleware = new DetectionMiddleware();\n * const middleware = detectionMiddleware.assetsMiddleware;\n * ```\n */\nexport class DetectionMiddleware {\n readonly name = CONTROLLER_NAME;\n\n getName(): string {\n return this.name;\n }\n\n /**\n * Get the middleware that builds detectedAssets for metadata and price fetching.\n *\n * This middleware:\n * 1. Includes assets from response.assetsBalance that are absent from both\n * state.assetsBalance and state.assetsInfo (brand-new assets only)\n * 2. Always includes each account's custom assets from state\n * 3. Fills response.detectedAssets with the resulting asset IDs per account\n * 4. Queues detected assets that lack a price in state on\n * request.assetsForPriceUpdate so PriceDataSource fetches them in the same\n * pipeline pass (including the background RPC detection path)\n *\n * @returns The middleware function for the assets pipeline.\n */\n get assetsMiddleware(): Middleware {\n return forDataTypes(['balance'], async (ctx, next) => {\n const { request, response } = ctx;\n\n // Get state for custom assets, existing balances, and existing metadata\n const state = ctx.getAssetsState();\n const {\n customAssets: stateCustomAssets,\n assetsBalance: stateAssetsBalance,\n assetsInfo: stateAssetsInfo,\n assetsPrice: stateAssetsPrice,\n } = state;\n\n const detectedAssets: Record<AccountId, Caip19AssetId[]> = {};\n\n // 1. From balance response: only include assets that are genuinely new —\n // not already present in state.assetsBalance or state.assetsInfo.\n if (response.assetsBalance) {\n for (const [accountId, accountBalances] of Object.entries(\n response.assetsBalance,\n )) {\n const detected: Caip19AssetId[] = [];\n\n const stateAccountBalances = stateAssetsBalance[accountId] ?? {};\n\n for (const assetId of Object.keys(\n accountBalances as Record<string, unknown>,\n )) {\n const caipAssetId = assetId as Caip19AssetId;\n // Skip if already tracked in state balances or already has metadata\n if (\n stateAccountBalances[caipAssetId] !== undefined ||\n stateAssetsInfo[caipAssetId] !== undefined\n ) {\n continue;\n }\n detected.push(caipAssetId);\n }\n\n // Merge custom assets for this account, applying the same filter:\n // skip if already in state balance or already has metadata.\n const customForAccount = stateCustomAssets?.[accountId] ?? [];\n for (const assetId of customForAccount) {\n if (detected.includes(assetId)) {\n continue;\n }\n if (\n stateAccountBalances[assetId] !== undefined ||\n stateAssetsInfo[assetId] !== undefined\n ) {\n continue;\n }\n detected.push(assetId);\n }\n\n if (detected.length > 0) {\n detectedAssets[accountId] = detected;\n }\n }\n }\n\n // 2. Accounts in request that weren't in balance response: include their\n // custom assets that are not yet in state.\n for (const { account } of request.accountsWithSupportedChains) {\n const accountId = account.id;\n if (detectedAssets[accountId]) {\n continue;\n }\n const stateAccountBalances = stateAssetsBalance[accountId] ?? {};\n const customForAccount = stateCustomAssets?.[accountId] ?? [];\n const newCustomAssets = customForAccount.filter((assetId) => {\n const inBalance = stateAccountBalances[assetId] !== undefined;\n const inInfo = stateAssetsInfo[assetId] !== undefined;\n return !inBalance && !inInfo;\n });\n if (newCustomAssets.length > 0) {\n detectedAssets[accountId] = newCustomAssets;\n }\n }\n\n if (Object.keys(detectedAssets).length > 0) {\n response.detectedAssets = detectedAssets;\n }\n\n // Queue prices for:\n // 1) newly detected assets missing a price, and\n // 2) assets in this balance response that already exist in state but still\n // lack a price (e.g. natives seeded before the first price poll).\n // PriceDataSource filters non-priceable IDs before calling the API.\n const prices = stateAssetsPrice as Record<string, unknown>;\n const missingPriceAssets = new Set<Caip19AssetId>();\n\n const maybeQueue = (assetId: Caip19AssetId): void => {\n const normalizedAssetId = normalizeAssetId(assetId);\n if (\n prices[normalizedAssetId] === undefined &&\n prices[assetId] === undefined\n ) {\n missingPriceAssets.add(normalizedAssetId);\n }\n };\n\n for (const accountAssetIds of Object.values(detectedAssets)) {\n for (const assetId of accountAssetIds) {\n maybeQueue(assetId);\n }\n }\n\n if (response.assetsBalance) {\n for (const accountBalances of Object.values(response.assetsBalance)) {\n for (const assetId of Object.keys(\n accountBalances as Record<string, unknown>,\n )) {\n maybeQueue(assetId as Caip19AssetId);\n }\n }\n }\n\n if (missingPriceAssets.size > 0) {\n request.assetsForPriceUpdate = [\n ...(request.assetsForPriceUpdate ?? []),\n ...missingPriceAssets,\n ];\n }\n\n return next(ctx);\n });\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ParallelMiddleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/ParallelMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,OAAO,EAGP,YAAY,EACZ,UAAU,EACV,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAMrB;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,YAAY,CAsD1E;AAWD,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,IAAI,MAAM,CAAC;IAClB,gHAAgH;IAChH,mBAAmB,IAAI,OAAO,EAAE,CAAC;IACjC,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAAC;AAwDF;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG;IACzE,OAAO,IAAI,MAAM,CAAC;IAClB,gBAAgB,EAAE,UAAU,CAAC;CAC9B,
|
|
1
|
+
{"version":3,"file":"ParallelMiddleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/ParallelMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,OAAO,EAGP,YAAY,EACZ,UAAU,EACV,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAMrB;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,YAAY,CAsD1E;AAWD,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,IAAI,MAAM,CAAC;IAClB,gHAAgH;IAChH,mBAAmB,IAAI,OAAO,EAAE,CAAC;IACjC,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAAC;AAwDF;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG;IACzE,OAAO,IAAI,MAAM,CAAC;IAClB,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAiHA;AAWD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG;IACrE,OAAO,IAAI,MAAM,CAAC;IAClB,gBAAgB,EAAE,UAAU,CAAC;CAC9B,CAqDA"}
|
|
@@ -133,6 +133,7 @@ export function createParallelBalanceMiddleware(sources) {
|
|
|
133
133
|
const result = await source.assetsMiddleware({
|
|
134
134
|
request: requests[i],
|
|
135
135
|
response: {},
|
|
136
|
+
getAssetsState: context.getAssetsState,
|
|
136
137
|
}, noopNext);
|
|
137
138
|
const durationMs = Date.now() - start;
|
|
138
139
|
return { result, sourceName: source.getName(), durationMs };
|
|
@@ -160,6 +161,7 @@ export function createParallelBalanceMiddleware(sources) {
|
|
|
160
161
|
const result = await source.assetsMiddleware({
|
|
161
162
|
request: fallbackRequests[i],
|
|
162
163
|
response: {},
|
|
164
|
+
getAssetsState: context.getAssetsState,
|
|
163
165
|
}, noopNext);
|
|
164
166
|
const durationMs = Date.now() - start;
|
|
165
167
|
return { result, sourceName: source.getName(), durationMs };
|
|
@@ -227,6 +229,7 @@ export function createParallelMiddleware(sources) {
|
|
|
227
229
|
const result = await source.assetsMiddleware({
|
|
228
230
|
request: context.request,
|
|
229
231
|
response: { ...context.response },
|
|
232
|
+
getAssetsState: context.getAssetsState,
|
|
230
233
|
}, noopNext);
|
|
231
234
|
const durationMs = Date.now() - start;
|
|
232
235
|
return { result, sourceName: source.getName(), durationMs };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ParallelMiddleware.js","sourceRoot":"","sources":["../../src/middlewares/ParallelMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { Semaphore } from 'async-mutex';\n\nimport type {\n ChainId,\n Context,\n DataRequest,\n DataResponse,\n Middleware,\n AssetsDataSource,\n} from '../types.js';\n\n// ============================================================================\n// MERGE HELPER\n// ============================================================================\n\n/**\n * Deep-merge multiple DataResponses into one.\n * Used when running balance data sources in parallel.\n *\n * @param responses - Array of DataResponse from each source.\n * @returns Single merged DataResponse.\n */\nexport function mergeDataResponses(responses: DataResponse[]): DataResponse {\n const merged: DataResponse = {};\n\n for (const response of responses) {\n if (response.assetsBalance) {\n merged.assetsBalance ??= {};\n for (const [accountId, accountBalances] of Object.entries(\n response.assetsBalance,\n )) {\n merged.assetsBalance[accountId] = {\n ...(merged.assetsBalance[accountId] ?? {}),\n ...accountBalances,\n };\n }\n }\n if (response.assetsInfo) {\n merged.assetsInfo = {\n ...(merged.assetsInfo ?? {}),\n ...response.assetsInfo,\n };\n }\n if (response.assetsPrice) {\n merged.assetsPrice = {\n ...(merged.assetsPrice ?? {}),\n ...response.assetsPrice,\n };\n }\n if (response.errors) {\n merged.errors = {\n ...(merged.errors ?? {}),\n ...response.errors,\n };\n }\n if (response.detectedAssets) {\n merged.detectedAssets = {\n ...(merged.detectedAssets ?? {}),\n ...response.detectedAssets,\n };\n }\n if (response.updateMode === 'full') {\n merged.updateMode = 'full';\n } else if (\n response.updateMode === 'merge' &&\n merged.updateMode !== 'full'\n ) {\n merged.updateMode = 'merge';\n }\n if (response.replaceCoveredChainBalances) {\n merged.replaceCoveredChainBalances = true;\n }\n }\n merged.updateMode ??= 'merge';\n\n return merged;\n}\n\n// ============================================================================\n// PARALLEL BALANCE MIDDLEWARE\n// ============================================================================\n\nconst PARALLEL_BALANCE_MIDDLEWARE_NAME = 'ParallelBalanceMiddleware';\n\n/** Max concurrent balance source calls (round 1 and fallback). */\nconst BALANCE_CONCURRENCY = 3;\n\nexport type BalanceSource = {\n getName(): string;\n /** Chains this source can fetch (e.g. from getActiveChainsSync()). Used to partition chains with no overlap. */\n getActiveChainsSync(): ChainId[];\n assetsMiddleware: Middleware;\n};\n\n/**\n * Partition request.chainIds so each chain is assigned to exactly one source\n * (by source order: first source that supports the chain gets it). Ensures no\n * chain overlap across data source calls.\n *\n * @param request - The data request with chainIds to partition.\n * @param sources - Balance sources in priority order (e.g. AccountsAPI, Snap, Rpc).\n * @returns Array of requests, one per source, each with only that source's assigned chainIds.\n */\nfunction partitionChainsBySource(\n request: DataRequest,\n sources: BalanceSource[],\n): DataRequest[] {\n const { chainIds } = request;\n const assigned = new Set<ChainId>();\n\n return sources.map((source) => {\n const supported = new Set(source.getActiveChainsSync());\n const chainsForSource = chainIds.filter(\n (id) => supported.has(id) && !assigned.has(id),\n );\n chainsForSource.forEach((id) => assigned.add(id));\n\n return {\n ...request,\n chainIds: chainsForSource,\n };\n });\n}\n\n/**\n * Collect chain IDs that failed in the first round (present in response.errors).\n * Used to run a fallback round with remaining sources.\n *\n * @param requests - Partitioned requests, one per source (same order as results).\n * @param results - Results from each source; chain IDs in requests[i] that have errors in results[i].response.errors are considered failed.\n * @returns Set of chain IDs that had errors in the first round.\n */\nfunction getFailedChainIds(\n requests: DataRequest[],\n results: { response: DataResponse }[],\n): Set<ChainId> {\n const failed = new Set<ChainId>();\n for (let i = 0; i < results.length; i++) {\n const errors = results[i].response.errors ?? {};\n for (const chainId of requests[i].chainIds) {\n if (errors[chainId]) {\n failed.add(chainId);\n }\n }\n }\n return failed;\n}\n\n/**\n * Middleware that runs multiple balance data source middlewares in parallel,\n * with no chain overlap. Chains that fail (response.errors) are re-partitioned\n * and fetched again in a fallback round so lower-priority sources can try them.\n *\n * @param sources - Array of balance sources in priority order (each with getName(), getActiveChainsSync(), assetsMiddleware).\n * @returns A single middleware that runs all sources in parallel and merges responses.\n */\nexport function createParallelBalanceMiddleware(sources: BalanceSource[]): {\n getName(): string;\n assetsMiddleware: Middleware;\n} {\n return {\n getName(): string {\n return PARALLEL_BALANCE_MIDDLEWARE_NAME;\n },\n\n assetsMiddleware: async (context, next): Promise<Context> => {\n if (sources.length === 0) {\n return next(context);\n }\n\n const noopNext = async (ctx: typeof context): Promise<typeof context> =>\n ctx;\n const semaphore = new Semaphore(BALANCE_CONCURRENCY);\n\n // Round 1: partition chains (no overlap), run with limited concurrency\n const requests = partitionChainsBySource(context.request, sources);\n const round1Timed = await Promise.all(\n sources.map((source, i) =>\n semaphore.runExclusive(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: requests[i],\n response: {},\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n const results = round1Timed.map((entry) => entry.result);\n\n const durationByDataSource: Record<string, number> = {\n ...context.durationByDataSource,\n };\n for (const { sourceName, durationMs } of round1Timed) {\n const key = `${PARALLEL_BALANCE_MIDDLEWARE_NAME}.${sourceName}`;\n durationByDataSource[key] =\n (durationByDataSource[key] ?? 0) + durationMs;\n }\n\n let mergedResponse = mergeDataResponses(\n results.map((result) => result.response),\n );\n\n // Fallback: chains that failed (in errors) get re-partitioned and tried again\n const failedChainIds = getFailedChainIds(requests, results);\n if (failedChainIds.size > 0) {\n const fallbackRequest: DataRequest = {\n ...context.request,\n chainIds: [...failedChainIds],\n };\n const fallbackRequests = partitionChainsBySource(\n fallbackRequest,\n sources,\n );\n const fallbackTimed = await Promise.all(\n sources.map((source, i) =>\n semaphore.runExclusive(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: fallbackRequests[i],\n response: {},\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n const fallbackResults = fallbackTimed.map((entry) => entry.result);\n for (const { sourceName, durationMs } of fallbackTimed) {\n const key = `${PARALLEL_BALANCE_MIDDLEWARE_NAME}.${sourceName}`;\n durationByDataSource[key] =\n (durationByDataSource[key] ?? 0) + durationMs;\n }\n const fallbackMerged = mergeDataResponses(\n fallbackResults.map((result) => result.response),\n );\n mergedResponse = mergeDataResponses([mergedResponse, fallbackMerged]);\n // Remove errors for chains we successfully got balance for in fallback\n if (mergedResponse.errors && mergedResponse.assetsBalance) {\n const chainsWithBalance = new Set<ChainId>();\n for (const accountBalances of Object.values(\n mergedResponse.assetsBalance,\n )) {\n for (const assetId of Object.keys(accountBalances)) {\n const chainId = assetId.split('/')[0] as ChainId;\n chainsWithBalance.add(chainId);\n }\n }\n for (const chainId of failedChainIds) {\n if (chainsWithBalance.has(chainId)) {\n delete mergedResponse.errors[chainId];\n }\n }\n }\n }\n\n return next({\n ...context,\n response: mergeDataResponses([context.response, mergedResponse]),\n durationByDataSource,\n });\n },\n };\n}\n\n// ============================================================================\n// PARALLEL TOKEN/PRICE MIDDLEWARE\n// ============================================================================\n\nconst PARALLEL_MIDDLEWARE_NAME = 'ParallelMiddleware';\n\n/** Max concurrent token/price source calls. */\nconst CONCURRENCY = 2;\n\n/**\n * Middleware that runs multiple data source middlewares (e.g. TokenDataSource,\n * PriceDataSource) in parallel with the same request. Responses are merged so\n * that assetsInfo (token metadata) and assetsPrice are combined. Use this to\n * fetch token and price data concurrently instead of sequentially.\n *\n * @param sources - Array of named middleware sources (getName + assetsMiddleware).\n * @returns A single middleware that runs all sources in parallel and merges responses.\n */\nexport function createParallelMiddleware(sources: AssetsDataSource[]): {\n getName(): string;\n assetsMiddleware: Middleware;\n} {\n return {\n getName(): string {\n return PARALLEL_MIDDLEWARE_NAME;\n },\n\n assetsMiddleware: async (context, next): Promise<Context> => {\n if (sources.length === 0) {\n return next(context);\n }\n\n const noopNext = async (ctx: typeof context): Promise<typeof context> =>\n ctx;\n const semaphore = new Semaphore(CONCURRENCY);\n\n const timedResults = await Promise.all(\n sources.map((source) =>\n semaphore.runExclusive(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: context.request,\n response: { ...context.response },\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n\n const results = timedResults.map((entry) => entry.result);\n const mergedResponse = mergeDataResponses(\n results.map((result) => result.response),\n );\n\n const durationByDataSource: Record<string, number> = {\n ...context.durationByDataSource,\n };\n for (const { sourceName, durationMs } of timedResults) {\n durationByDataSource[`${PARALLEL_MIDDLEWARE_NAME}.${sourceName}`] =\n durationMs;\n }\n\n return next({\n ...context,\n response: mergeDataResponses([context.response, mergedResponse]),\n durationByDataSource,\n });\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ParallelMiddleware.js","sourceRoot":"","sources":["../../src/middlewares/ParallelMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { Semaphore } from 'async-mutex';\n\nimport type {\n ChainId,\n Context,\n DataRequest,\n DataResponse,\n Middleware,\n AssetsDataSource,\n} from '../types.js';\n\n// ============================================================================\n// MERGE HELPER\n// ============================================================================\n\n/**\n * Deep-merge multiple DataResponses into one.\n * Used when running balance data sources in parallel.\n *\n * @param responses - Array of DataResponse from each source.\n * @returns Single merged DataResponse.\n */\nexport function mergeDataResponses(responses: DataResponse[]): DataResponse {\n const merged: DataResponse = {};\n\n for (const response of responses) {\n if (response.assetsBalance) {\n merged.assetsBalance ??= {};\n for (const [accountId, accountBalances] of Object.entries(\n response.assetsBalance,\n )) {\n merged.assetsBalance[accountId] = {\n ...(merged.assetsBalance[accountId] ?? {}),\n ...accountBalances,\n };\n }\n }\n if (response.assetsInfo) {\n merged.assetsInfo = {\n ...(merged.assetsInfo ?? {}),\n ...response.assetsInfo,\n };\n }\n if (response.assetsPrice) {\n merged.assetsPrice = {\n ...(merged.assetsPrice ?? {}),\n ...response.assetsPrice,\n };\n }\n if (response.errors) {\n merged.errors = {\n ...(merged.errors ?? {}),\n ...response.errors,\n };\n }\n if (response.detectedAssets) {\n merged.detectedAssets = {\n ...(merged.detectedAssets ?? {}),\n ...response.detectedAssets,\n };\n }\n if (response.updateMode === 'full') {\n merged.updateMode = 'full';\n } else if (\n response.updateMode === 'merge' &&\n merged.updateMode !== 'full'\n ) {\n merged.updateMode = 'merge';\n }\n if (response.replaceCoveredChainBalances) {\n merged.replaceCoveredChainBalances = true;\n }\n }\n merged.updateMode ??= 'merge';\n\n return merged;\n}\n\n// ============================================================================\n// PARALLEL BALANCE MIDDLEWARE\n// ============================================================================\n\nconst PARALLEL_BALANCE_MIDDLEWARE_NAME = 'ParallelBalanceMiddleware';\n\n/** Max concurrent balance source calls (round 1 and fallback). */\nconst BALANCE_CONCURRENCY = 3;\n\nexport type BalanceSource = {\n getName(): string;\n /** Chains this source can fetch (e.g. from getActiveChainsSync()). Used to partition chains with no overlap. */\n getActiveChainsSync(): ChainId[];\n assetsMiddleware: Middleware;\n};\n\n/**\n * Partition request.chainIds so each chain is assigned to exactly one source\n * (by source order: first source that supports the chain gets it). Ensures no\n * chain overlap across data source calls.\n *\n * @param request - The data request with chainIds to partition.\n * @param sources - Balance sources in priority order (e.g. AccountsAPI, Snap, Rpc).\n * @returns Array of requests, one per source, each with only that source's assigned chainIds.\n */\nfunction partitionChainsBySource(\n request: DataRequest,\n sources: BalanceSource[],\n): DataRequest[] {\n const { chainIds } = request;\n const assigned = new Set<ChainId>();\n\n return sources.map((source) => {\n const supported = new Set(source.getActiveChainsSync());\n const chainsForSource = chainIds.filter(\n (id) => supported.has(id) && !assigned.has(id),\n );\n chainsForSource.forEach((id) => assigned.add(id));\n\n return {\n ...request,\n chainIds: chainsForSource,\n };\n });\n}\n\n/**\n * Collect chain IDs that failed in the first round (present in response.errors).\n * Used to run a fallback round with remaining sources.\n *\n * @param requests - Partitioned requests, one per source (same order as results).\n * @param results - Results from each source; chain IDs in requests[i] that have errors in results[i].response.errors are considered failed.\n * @returns Set of chain IDs that had errors in the first round.\n */\nfunction getFailedChainIds(\n requests: DataRequest[],\n results: { response: DataResponse }[],\n): Set<ChainId> {\n const failed = new Set<ChainId>();\n for (let i = 0; i < results.length; i++) {\n const errors = results[i].response.errors ?? {};\n for (const chainId of requests[i].chainIds) {\n if (errors[chainId]) {\n failed.add(chainId);\n }\n }\n }\n return failed;\n}\n\n/**\n * Middleware that runs multiple balance data source middlewares in parallel,\n * with no chain overlap. Chains that fail (response.errors) are re-partitioned\n * and fetched again in a fallback round so lower-priority sources can try them.\n *\n * @param sources - Array of balance sources in priority order (each with getName(), getActiveChainsSync(), assetsMiddleware).\n * @returns A single middleware that runs all sources in parallel and merges responses.\n */\nexport function createParallelBalanceMiddleware(sources: BalanceSource[]): {\n getName(): string;\n assetsMiddleware: Middleware;\n} {\n return {\n getName(): string {\n return PARALLEL_BALANCE_MIDDLEWARE_NAME;\n },\n\n assetsMiddleware: async (context, next): Promise<Context> => {\n if (sources.length === 0) {\n return next(context);\n }\n\n const noopNext = async (ctx: typeof context): Promise<typeof context> =>\n ctx;\n const semaphore = new Semaphore(BALANCE_CONCURRENCY);\n\n // Round 1: partition chains (no overlap), run with limited concurrency\n const requests = partitionChainsBySource(context.request, sources);\n const round1Timed = await Promise.all(\n sources.map((source, i) =>\n semaphore.runExclusive(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: requests[i],\n response: {},\n getAssetsState: context.getAssetsState,\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n const results = round1Timed.map((entry) => entry.result);\n\n const durationByDataSource: Record<string, number> = {\n ...context.durationByDataSource,\n };\n for (const { sourceName, durationMs } of round1Timed) {\n const key = `${PARALLEL_BALANCE_MIDDLEWARE_NAME}.${sourceName}`;\n durationByDataSource[key] =\n (durationByDataSource[key] ?? 0) + durationMs;\n }\n\n let mergedResponse = mergeDataResponses(\n results.map((result) => result.response),\n );\n\n // Fallback: chains that failed (in errors) get re-partitioned and tried again\n const failedChainIds = getFailedChainIds(requests, results);\n if (failedChainIds.size > 0) {\n const fallbackRequest: DataRequest = {\n ...context.request,\n chainIds: [...failedChainIds],\n };\n const fallbackRequests = partitionChainsBySource(\n fallbackRequest,\n sources,\n );\n const fallbackTimed = await Promise.all(\n sources.map((source, i) =>\n semaphore.runExclusive(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: fallbackRequests[i],\n response: {},\n getAssetsState: context.getAssetsState,\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n const fallbackResults = fallbackTimed.map((entry) => entry.result);\n for (const { sourceName, durationMs } of fallbackTimed) {\n const key = `${PARALLEL_BALANCE_MIDDLEWARE_NAME}.${sourceName}`;\n durationByDataSource[key] =\n (durationByDataSource[key] ?? 0) + durationMs;\n }\n const fallbackMerged = mergeDataResponses(\n fallbackResults.map((result) => result.response),\n );\n mergedResponse = mergeDataResponses([mergedResponse, fallbackMerged]);\n // Remove errors for chains we successfully got balance for in fallback\n if (mergedResponse.errors && mergedResponse.assetsBalance) {\n const chainsWithBalance = new Set<ChainId>();\n for (const accountBalances of Object.values(\n mergedResponse.assetsBalance,\n )) {\n for (const assetId of Object.keys(accountBalances)) {\n const chainId = assetId.split('/')[0] as ChainId;\n chainsWithBalance.add(chainId);\n }\n }\n for (const chainId of failedChainIds) {\n if (chainsWithBalance.has(chainId)) {\n delete mergedResponse.errors[chainId];\n }\n }\n }\n }\n\n return next({\n ...context,\n response: mergeDataResponses([context.response, mergedResponse]),\n durationByDataSource,\n });\n },\n };\n}\n\n// ============================================================================\n// PARALLEL TOKEN/PRICE MIDDLEWARE\n// ============================================================================\n\nconst PARALLEL_MIDDLEWARE_NAME = 'ParallelMiddleware';\n\n/** Max concurrent token/price source calls. */\nconst CONCURRENCY = 2;\n\n/**\n * Middleware that runs multiple data source middlewares (e.g. TokenDataSource,\n * PriceDataSource) in parallel with the same request. Responses are merged so\n * that assetsInfo (token metadata) and assetsPrice are combined. Use this to\n * fetch token and price data concurrently instead of sequentially.\n *\n * @param sources - Array of named middleware sources (getName + assetsMiddleware).\n * @returns A single middleware that runs all sources in parallel and merges responses.\n */\nexport function createParallelMiddleware(sources: AssetsDataSource[]): {\n getName(): string;\n assetsMiddleware: Middleware;\n} {\n return {\n getName(): string {\n return PARALLEL_MIDDLEWARE_NAME;\n },\n\n assetsMiddleware: async (context, next): Promise<Context> => {\n if (sources.length === 0) {\n return next(context);\n }\n\n const noopNext = async (ctx: typeof context): Promise<typeof context> =>\n ctx;\n const semaphore = new Semaphore(CONCURRENCY);\n\n const timedResults = await Promise.all(\n sources.map((source) =>\n semaphore.runExclusive(async () => {\n const start = Date.now();\n const result = await source.assetsMiddleware(\n {\n request: context.request,\n response: { ...context.response },\n getAssetsState: context.getAssetsState,\n },\n noopNext,\n );\n const durationMs = Date.now() - start;\n return { result, sourceName: source.getName(), durationMs };\n }),\n ),\n );\n\n const results = timedResults.map((entry) => entry.result);\n const mergedResponse = mergeDataResponses(\n results.map((result) => result.response),\n );\n\n const durationByDataSource: Record<string, number> = {\n ...context.durationByDataSource,\n };\n for (const { sourceName, durationMs } of timedResults) {\n durationByDataSource[`${PARALLEL_MIDDLEWARE_NAME}.${sourceName}`] =\n durationMs;\n }\n\n return next({\n ...context,\n response: mergeDataResponses([context.response, mergedResponse]),\n durationByDataSource,\n });\n },\n };\n}\n"]}
|
|
@@ -1,26 +1,25 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AssetsDataSource, Middleware } from '../types.js';
|
|
2
2
|
export type RpcFallbackMiddlewareOptions = {
|
|
3
3
|
/** The RPC data source to use as a fallback. */
|
|
4
4
|
rpcDataSource: AssetsDataSource;
|
|
5
|
-
/**
|
|
6
|
-
* When true, recover failed and unprocessed chains with a full RPC refetch
|
|
7
|
-
* (Accounts API v6). When false, re-read stale tracked assets the v5 API
|
|
8
|
-
* omitted (legacy path).
|
|
9
|
-
*/
|
|
10
|
-
isBalanceV6Enabled?: () => boolean;
|
|
11
|
-
/** Current AssetsController state. Used to find tracked assets the response left empty. */
|
|
12
|
-
getAssetsState: () => AssetsControllerState;
|
|
13
5
|
};
|
|
14
6
|
/**
|
|
15
|
-
* RpcFallbackMiddleware
|
|
7
|
+
* RpcFallbackMiddleware re-reads balances from the RPC data source in two
|
|
8
|
+
* cases:
|
|
16
9
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
10
|
+
* 1. Chains present in `response.errors` (network error, unprocessedNetworks,
|
|
11
|
+
* timeout, …). Successful RPC results are merged into the response and
|
|
12
|
+
* their entries are cleared from `response.errors`.
|
|
13
|
+
* 2. EVM assets tracked in state (`state.assetsBalance` or
|
|
14
|
+
* `state.customAssets`) that this balance response left empty. The Accounts
|
|
15
|
+
* API omits tokens it does not index, and a returned `0` is
|
|
16
|
+
* indistinguishable from "not indexed", so the `merge` state update would
|
|
17
|
+
* otherwise keep a stale amount forever. They are passed to RPC as
|
|
18
|
+
* `customAssets` so the balance fetcher includes them in its multicall.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
* `
|
|
22
|
-
*
|
|
23
|
-
* scoped via `customAssets`.
|
|
20
|
+
* Place this immediately after `createParallelBalanceMiddleware` in the fast
|
|
21
|
+
* pipeline, after `CustomAssetGraduationMiddleware` so the custom assets RPC
|
|
22
|
+
* carries never trigger graduation.
|
|
24
23
|
*/
|
|
25
24
|
export declare class RpcFallbackMiddleware {
|
|
26
25
|
#private;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RpcFallbackMiddleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/RpcFallbackMiddleware.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEV,
|
|
1
|
+
{"version":3,"file":"RpcFallbackMiddleware.d.ts","sourceRoot":"","sources":["../../src/middlewares/RpcFallbackMiddleware.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEV,gBAAgB,EAKhB,UAAU,EACX,MAAM,aAAa,CAAC;AAOrB,MAAM,MAAM,4BAA4B,GAAG;IACzC,gDAAgD;IAChD,aAAa,EAAE,gBAAgB,CAAC;CACjC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,qBAAqB;;IAChC,QAAQ,CAAC,IAAI,2BAAmB;IAIhC,YAAY,OAAO,EAAE,4BAA4B,EAEhD;IAED,OAAO,IAAI,MAAM,CAEhB;IAED,IAAI,gBAAgB,IAAI,UAAU,CAoGjC;CACF"}
|
|
@@ -2,153 +2,136 @@ import { KnownCaipNamespace } from '@metamask/utils';
|
|
|
2
2
|
import { isStakingContractAssetId } from '../data-sources/evm-rpc-services/index.js';
|
|
3
3
|
import { projectLogger, createModuleLogger } from '../logger.js';
|
|
4
4
|
import { forDataTypes } from '../types.js';
|
|
5
|
-
import { filterFailedChainBalances } from '../utils/filterFailedChainBalances.js';
|
|
6
5
|
import { mergeDataResponses } from './ParallelMiddleware.js';
|
|
7
6
|
const CONTROLLER_NAME = 'RpcFallbackMiddleware';
|
|
8
7
|
const log = createModuleLogger(projectLogger, CONTROLLER_NAME);
|
|
9
|
-
const noopNext = async (ctx) => ctx;
|
|
10
8
|
/**
|
|
11
|
-
* RpcFallbackMiddleware
|
|
9
|
+
* RpcFallbackMiddleware re-reads balances from the RPC data source in two
|
|
10
|
+
* cases:
|
|
12
11
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* 1. Chains present in `response.errors` (network error, unprocessedNetworks,
|
|
13
|
+
* timeout, …). Successful RPC results are merged into the response and
|
|
14
|
+
* their entries are cleared from `response.errors`.
|
|
15
|
+
* 2. EVM assets tracked in state (`state.assetsBalance` or
|
|
16
|
+
* `state.customAssets`) that this balance response left empty. The Accounts
|
|
17
|
+
* API omits tokens it does not index, and a returned `0` is
|
|
18
|
+
* indistinguishable from "not indexed", so the `merge` state update would
|
|
19
|
+
* otherwise keep a stale amount forever. They are passed to RPC as
|
|
20
|
+
* `customAssets` so the balance fetcher includes them in its multicall.
|
|
15
21
|
*
|
|
16
|
-
*
|
|
17
|
-
* `
|
|
18
|
-
*
|
|
19
|
-
* scoped via `customAssets`.
|
|
22
|
+
* Place this immediately after `createParallelBalanceMiddleware` in the fast
|
|
23
|
+
* pipeline, after `CustomAssetGraduationMiddleware` so the custom assets RPC
|
|
24
|
+
* carries never trigger graduation.
|
|
20
25
|
*/
|
|
21
26
|
export class RpcFallbackMiddleware {
|
|
22
27
|
name = CONTROLLER_NAME;
|
|
23
28
|
#rpcDataSource;
|
|
24
|
-
#isBalanceV6Enabled;
|
|
25
|
-
#getAssetsState;
|
|
26
29
|
constructor(options) {
|
|
27
30
|
this.#rpcDataSource = options.rpcDataSource;
|
|
28
|
-
this.#isBalanceV6Enabled =
|
|
29
|
-
options.isBalanceV6Enabled ?? (() => false);
|
|
30
|
-
this.#getAssetsState = options.getAssetsState;
|
|
31
31
|
}
|
|
32
32
|
getName() {
|
|
33
33
|
return this.name;
|
|
34
34
|
}
|
|
35
35
|
get assetsMiddleware() {
|
|
36
36
|
return forDataTypes(['balance'], async (ctx, next) => {
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
const erroredChains = new Set(Object.keys(ctx.response.errors ?? {}));
|
|
38
|
+
const staleAssets = collectStaleTrackedAssets(ctx);
|
|
39
|
+
const chainsToFetch = [
|
|
40
|
+
...new Set([
|
|
41
|
+
...ctx.request.chainIds.filter((id) => erroredChains.has(id)),
|
|
42
|
+
// Already restricted to requested chains. Their chain may not be
|
|
43
|
+
// errored: the Accounts API can answer for a chain while omitting a
|
|
44
|
+
// token it does not index.
|
|
45
|
+
...staleAssets.map((assetId) => assetId.split('/')[0]),
|
|
46
|
+
]),
|
|
47
|
+
];
|
|
48
|
+
if (chainsToFetch.length === 0) {
|
|
49
|
+
return next(ctx);
|
|
39
50
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
for
|
|
66
|
-
|
|
67
|
-
|
|
51
|
+
log('Re-reading balances on RPC', {
|
|
52
|
+
erroredChains: [...erroredChains],
|
|
53
|
+
staleAssets,
|
|
54
|
+
chains: chainsToFetch,
|
|
55
|
+
});
|
|
56
|
+
const filteredRequest = {
|
|
57
|
+
...ctx.request,
|
|
58
|
+
chainIds: chainsToFetch,
|
|
59
|
+
customAssets: [
|
|
60
|
+
...new Set([...(ctx.request.customAssets ?? []), ...staleAssets]),
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
const noopNext = async (inner) => inner;
|
|
64
|
+
const rpcResult = await this.#rpcDataSource.assetsMiddleware({
|
|
65
|
+
...ctx,
|
|
66
|
+
request: filteredRequest,
|
|
67
|
+
response: {},
|
|
68
|
+
}, noopNext);
|
|
69
|
+
// A chain RPC itself failed on contributed nothing trustworthy: its
|
|
70
|
+
// balances are failure stubs (native 0) that would overwrite correct
|
|
71
|
+
// upstream amounts and, with replaceCoveredChainBalances, wipe the
|
|
72
|
+
// chain's token slice from state. Drop them before merging — this also
|
|
73
|
+
// keeps failed chains from counting as "recovered" below.
|
|
74
|
+
const rpcFailedChains = new Set(Object.keys(rpcResult.response.errors ?? {}));
|
|
75
|
+
const rpcAssetsBalance = filterOutChainBalances(rpcResult.response.assetsBalance, rpcFailedChains);
|
|
76
|
+
// RPC errors are kept only for chains that were already errored
|
|
77
|
+
// upstream. For chains fetched solely for stale tracked assets the
|
|
78
|
+
// upstream response succeeded and stays authoritative — the stale asset
|
|
79
|
+
// keeps its previous amount and is retried on the next pass.
|
|
80
|
+
const rpcErrors = Object.fromEntries(Object.entries(rpcResult.response.errors ?? {}).filter(([chainId]) => erroredChains.has(chainId)));
|
|
81
|
+
const merged = mergeDataResponses([
|
|
82
|
+
ctx.response,
|
|
83
|
+
{
|
|
84
|
+
...rpcResult.response,
|
|
85
|
+
assetsBalance: rpcAssetsBalance,
|
|
86
|
+
errors: rpcErrors,
|
|
87
|
+
},
|
|
88
|
+
]);
|
|
89
|
+
// Clear errors only for chains RPC actually recovered a balance for.
|
|
90
|
+
// We must inspect the (filtered) RPC balances — NOT merged — because
|
|
91
|
+
// merged also contains balances from the upstream sources (AccountsApi /
|
|
92
|
+
// Websocket / Staked). If those sources returned partial data for
|
|
93
|
+
// a chain that they also flagged as errored (e.g. via
|
|
94
|
+
// unprocessedNetworks), and RPC then failed for that same chain,
|
|
95
|
+
// looking at merged would incorrectly mark the error as recovered.
|
|
96
|
+
if (merged.errors && rpcAssetsBalance) {
|
|
97
|
+
const chainsRecoveredByRpc = new Set();
|
|
98
|
+
for (const accountBalances of Object.values(rpcAssetsBalance)) {
|
|
99
|
+
for (const assetId of Object.keys(accountBalances)) {
|
|
100
|
+
chainsRecoveredByRpc.add(assetId.split('/')[0]);
|
|
101
|
+
}
|
|
68
102
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
103
|
+
for (const chainId of erroredChains) {
|
|
104
|
+
if (chainsRecoveredByRpc.has(chainId)) {
|
|
105
|
+
delete merged.errors[chainId];
|
|
106
|
+
}
|
|
73
107
|
}
|
|
74
108
|
}
|
|
75
|
-
|
|
76
|
-
return next({ ...ctx, response: merged });
|
|
77
|
-
}
|
|
78
|
-
async #recoverV5(ctx, next) {
|
|
79
|
-
const erroredChains = new Set(Object.keys(ctx.response.errors ?? {}));
|
|
80
|
-
const staleAssets = collectStaleTrackedAssets(ctx, this.#getAssetsState());
|
|
81
|
-
const chainsToFetch = [
|
|
82
|
-
...new Set([
|
|
83
|
-
...ctx.request.chainIds.filter((id) => erroredChains.has(id)),
|
|
84
|
-
// Already restricted to requested chains. Their chain may not be
|
|
85
|
-
// errored: the Accounts API can answer for a chain while omitting a
|
|
86
|
-
// token it does not index.
|
|
87
|
-
...staleAssets.map((assetId) => assetId.split('/')[0]),
|
|
88
|
-
]),
|
|
89
|
-
];
|
|
90
|
-
if (chainsToFetch.length === 0) {
|
|
91
|
-
return next(ctx);
|
|
92
|
-
}
|
|
93
|
-
log('Re-reading balances on RPC', {
|
|
94
|
-
erroredChains: [...erroredChains],
|
|
95
|
-
staleAssets,
|
|
96
|
-
chains: chainsToFetch,
|
|
109
|
+
return next({ ...ctx, response: merged });
|
|
97
110
|
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// upstream response succeeded and stays authoritative — the stale asset
|
|
120
|
-
// keeps its previous amount and is retried on the next pass.
|
|
121
|
-
const rpcErrors = Object.fromEntries(Object.entries(rpcResult.response.errors ?? {}).filter(([chainId]) => erroredChains.has(chainId)));
|
|
122
|
-
const merged = mergeDataResponses([
|
|
123
|
-
ctx.response,
|
|
124
|
-
{
|
|
125
|
-
...rpcResult.response,
|
|
126
|
-
assetsBalance: rpcAssetsBalance,
|
|
127
|
-
errors: rpcErrors,
|
|
128
|
-
},
|
|
129
|
-
]);
|
|
130
|
-
// Clear errors only for chains RPC actually recovered a balance for.
|
|
131
|
-
// We must inspect the (filtered) RPC balances — NOT merged — because
|
|
132
|
-
// merged also contains balances from the upstream sources (AccountsApi /
|
|
133
|
-
// Websocket / Staked). If those sources returned partial data for
|
|
134
|
-
// a chain that they also flagged as errored (e.g. via
|
|
135
|
-
// unprocessedNetworks), and RPC then failed for that same chain,
|
|
136
|
-
// looking at merged would incorrectly mark the error as recovered.
|
|
137
|
-
if (merged.errors && rpcAssetsBalance) {
|
|
138
|
-
const chainsRecoveredByRpc = new Set();
|
|
139
|
-
for (const accountBalances of Object.values(rpcAssetsBalance)) {
|
|
140
|
-
for (const assetId of Object.keys(accountBalances)) {
|
|
141
|
-
chainsRecoveredByRpc.add(assetId.split('/')[0]);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
for (const chainId of erroredChains) {
|
|
145
|
-
if (chainsRecoveredByRpc.has(chainId)) {
|
|
146
|
-
delete merged.errors[chainId];
|
|
147
|
-
}
|
|
148
|
-
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Remove all balances that belong to the given chains.
|
|
115
|
+
*
|
|
116
|
+
* Used to discard results for chains the RPC source itself failed on, whose
|
|
117
|
+
* entries are failure stubs (native `0`) rather than real readings.
|
|
118
|
+
*
|
|
119
|
+
* @param assetsBalance - Balances by account from the RPC response.
|
|
120
|
+
* @param chainIds - Chains whose balances should be dropped.
|
|
121
|
+
* @returns The filtered balance map, without accounts left empty.
|
|
122
|
+
*/
|
|
123
|
+
function filterOutChainBalances(assetsBalance, chainIds) {
|
|
124
|
+
if (!assetsBalance || chainIds.size === 0) {
|
|
125
|
+
return assetsBalance;
|
|
126
|
+
}
|
|
127
|
+
const filtered = {};
|
|
128
|
+
for (const [accountId, accountBalances] of Object.entries(assetsBalance)) {
|
|
129
|
+
const kept = Object.fromEntries(Object.entries(accountBalances).filter(([assetId]) => !chainIds.has(assetId.split('/')[0])));
|
|
130
|
+
if (Object.keys(kept).length > 0) {
|
|
131
|
+
filtered[accountId] = kept;
|
|
149
132
|
}
|
|
150
|
-
return next({ ...ctx, response: merged });
|
|
151
133
|
}
|
|
134
|
+
return filtered;
|
|
152
135
|
}
|
|
153
136
|
/**
|
|
154
137
|
* EVM assets tracked in state that this balance response left empty and RPC
|
|
@@ -159,11 +142,10 @@ export class RpcFallbackMiddleware {
|
|
|
159
142
|
* set, so anything else would be queued and then silently dropped.
|
|
160
143
|
*
|
|
161
144
|
* @param ctx - Pipeline context.
|
|
162
|
-
* @param state - Current AssetsController state.
|
|
163
145
|
* @returns Asset IDs to hand to the RPC data source.
|
|
164
146
|
*/
|
|
165
|
-
function collectStaleTrackedAssets(ctx
|
|
166
|
-
const { assetsBalance: stateAssetsBalance, customAssets: stateCustomAssets } =
|
|
147
|
+
function collectStaleTrackedAssets(ctx) {
|
|
148
|
+
const { assetsBalance: stateAssetsBalance, customAssets: stateCustomAssets } = ctx.getAssetsState();
|
|
167
149
|
const staleAssets = new Set();
|
|
168
150
|
for (const { account, supportedChains } of ctx.request
|
|
169
151
|
.accountsWithSupportedChains) {
|