@metamask-previews/assets-controller 14.0.3-preview-ea6a28c61 → 14.0.3-preview-e12b5ece6

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/dist/AssetsController.cjs +4 -0
  3. package/dist/AssetsController.cjs.map +1 -1
  4. package/dist/AssetsController.d.cts +7 -0
  5. package/dist/AssetsController.d.cts.map +1 -1
  6. package/dist/AssetsController.d.mts +7 -0
  7. package/dist/AssetsController.d.mts.map +1 -1
  8. package/dist/AssetsController.mjs +4 -0
  9. package/dist/AssetsController.mjs.map +1 -1
  10. package/dist/data-sources/AccountsApiDataSource.cjs +9 -2
  11. package/dist/data-sources/AccountsApiDataSource.cjs.map +1 -1
  12. package/dist/data-sources/AccountsApiDataSource.d.cts.map +1 -1
  13. package/dist/data-sources/AccountsApiDataSource.d.mts.map +1 -1
  14. package/dist/data-sources/AccountsApiDataSource.mjs +9 -2
  15. package/dist/data-sources/AccountsApiDataSource.mjs.map +1 -1
  16. package/dist/data-sources/RpcDataSource.cjs +9 -0
  17. package/dist/data-sources/RpcDataSource.cjs.map +1 -1
  18. package/dist/data-sources/RpcDataSource.d.cts.map +1 -1
  19. package/dist/data-sources/RpcDataSource.d.mts.map +1 -1
  20. package/dist/data-sources/RpcDataSource.mjs +9 -0
  21. package/dist/data-sources/RpcDataSource.mjs.map +1 -1
  22. package/dist/middlewares/RpcFallbackMiddleware.cjs +142 -14
  23. package/dist/middlewares/RpcFallbackMiddleware.cjs.map +1 -1
  24. package/dist/middlewares/RpcFallbackMiddleware.d.cts +14 -6
  25. package/dist/middlewares/RpcFallbackMiddleware.d.cts.map +1 -1
  26. package/dist/middlewares/RpcFallbackMiddleware.d.mts +14 -6
  27. package/dist/middlewares/RpcFallbackMiddleware.d.mts.map +1 -1
  28. package/dist/middlewares/RpcFallbackMiddleware.mjs +142 -14
  29. package/dist/middlewares/RpcFallbackMiddleware.mjs.map +1 -1
  30. package/dist/selectors/balance.cjs +9 -4
  31. package/dist/selectors/balance.cjs.map +1 -1
  32. package/dist/selectors/balance.d.cts.map +1 -1
  33. package/dist/selectors/balance.d.mts.map +1 -1
  34. package/dist/selectors/balance.mjs +9 -4
  35. package/dist/selectors/balance.mjs.map +1 -1
  36. package/dist/types.cjs.map +1 -1
  37. package/dist/types.d.cts +8 -0
  38. package/dist/types.d.cts.map +1 -1
  39. package/dist/types.d.mts +8 -0
  40. package/dist/types.d.mts.map +1 -1
  41. package/dist/types.mjs.map +1 -1
  42. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"RpcFallbackMiddleware.cjs","sourceRoot":"","sources":["../../src/middlewares/RpcFallbackMiddleware.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,6CAAiE;AACjE,2CAA2C;AAO3C,oEAA6D;AAE7D,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAEhD,MAAM,GAAG,GAAG,IAAA,8BAAkB,EAAC,yBAAa,EAAE,eAAe,CAAC,CAAC;AAO/D;;;;;;;;;GASG;AACH,MAAa,qBAAqB;IAKhC,YAAY,OAAqC;QAJxC,SAAI,GAAG,eAAe,CAAC;QAEvB,uDAAiC;QAGxC,uBAAA,IAAI,wCAAkB,OAAO,CAAC,aAAa,MAAA,CAAC;IAC9C,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED,IAAI,gBAAgB;QAClB,OAAO,IAAA,uBAAY,EAAC,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YACnD,MAAM,aAAa,GAAG,IAAI,GAAG,CAC3B,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAc,CACpD,CAAC;YACF,IAAI,aAAa,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBAC7B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;YAED,GAAG,CAAC,+BAA+B,EAAE;gBACnC,MAAM,EAAE,CAAC,GAAG,aAAa,CAAC;aAC3B,CAAC,CAAC;YAEH,MAAM,eAAe,GAAG;gBACtB,GAAG,GAAG,CAAC,OAAO;gBACd,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;aACrE,CAAC;YAEF,MAAM,QAAQ,GAAG,KAAK,EAAE,KAAiB,EAAuB,EAAE,CAAC,KAAK,CAAC;YACzE,MAAM,SAAS,GAAG,MAAM,uBAAA,IAAI,4CAAe,CAAC,gBAAgB,CAC1D;gBACE,GAAG,GAAG;gBACN,OAAO,EAAE,eAAe;gBACxB,QAAQ,EAAE,EAAE;aACb,EACD,QAAQ,CACT,CAAC;YAEF,MAAM,MAAM,GAAiB,IAAA,0CAAkB,EAAC;gBAC9C,GAAG,CAAC,QAAQ;gBACZ,SAAS,CAAC,QAAQ;aACnB,CAAC,CAAC;YAEH,qEAAqE;YACrE,mEAAmE;YACnE,kEAAkE;YAClE,kEAAkE;YAClE,sDAAsD;YACtD,iEAAiE;YACjE,mEAAmE;YACnE,MAAM,gBAAgB,GAAG,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC;YAC1D,IAAI,MAAM,CAAC,MAAM,IAAI,gBAAgB,EAAE,CAAC;gBACtC,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAU,CAAC;gBAC/C,KAAK,MAAM,eAAe,IAAI,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC;oBAC9D,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;wBACnD,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;oBAClD,CAAC;gBACH,CAAC;gBACD,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;oBACpC,IAAI,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;wBACtC,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;oBAChC,CAAC;gBACH,CAAC;YACH,CAAC;YAED,OAAO,IAAI,CAAC,EAAE,GAAG,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAvED,sDAuEC","sourcesContent":["import { projectLogger, createModuleLogger } from '../logger.js';\nimport { forDataTypes } from '../types.js';\nimport type {\n AssetsDataSource,\n ChainId,\n DataResponse,\n Middleware,\n} from '../types.js';\nimport { mergeDataResponses } from './ParallelMiddleware.js';\n\nconst CONTROLLER_NAME = 'RpcFallbackMiddleware';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\nexport type RpcFallbackMiddlewareOptions = {\n /** The RPC data source to use as a fallback. */\n rpcDataSource: AssetsDataSource;\n};\n\n/**\n * RpcFallbackMiddleware retries chains that failed upstream on the RPC data\n * source. Any chain present in `response.errors` (network error,\n * unprocessedNetworks, timeout, …) is handed off to RPC with the request\n * filtered to just those chains. Successful RPC results are merged into the\n * response and their entries are cleared from `response.errors`.\n *\n * Place this immediately after `createParallelBalanceMiddleware` in the fast\n * pipeline.\n */\nexport class RpcFallbackMiddleware {\n readonly name = CONTROLLER_NAME;\n\n readonly #rpcDataSource: AssetsDataSource;\n\n constructor(options: RpcFallbackMiddlewareOptions) {\n this.#rpcDataSource = options.rpcDataSource;\n }\n\n getName(): string {\n return this.name;\n }\n\n get assetsMiddleware(): Middleware {\n return forDataTypes(['balance'], async (ctx, next) => {\n const erroredChains = new Set<ChainId>(\n Object.keys(ctx.response.errors ?? {}) as ChainId[],\n );\n if (erroredChains.size === 0) {\n return next(ctx);\n }\n\n log('Retrying failed chains on RPC', {\n chains: [...erroredChains],\n });\n\n const filteredRequest = {\n ...ctx.request,\n chainIds: ctx.request.chainIds.filter((id) => erroredChains.has(id)),\n };\n\n const noopNext = async (inner: typeof ctx): Promise<typeof ctx> => inner;\n const rpcResult = await this.#rpcDataSource.assetsMiddleware(\n {\n ...ctx,\n request: filteredRequest,\n response: {},\n },\n noopNext,\n );\n\n const merged: DataResponse = mergeDataResponses([\n ctx.response,\n rpcResult.response,\n ]);\n\n // Clear errors only for chains RPC actually recovered a balance for.\n // We must inspect rpcResult.response — NOT merged — because merged\n // also contains balances from the upstream sources (AccountsApi /\n // Websocket / Staked). If those sources returned partial data for\n // a chain that they also flagged as errored (e.g. via\n // unprocessedNetworks), and RPC then failed for that same chain,\n // looking at merged would incorrectly mark the error as recovered.\n const rpcAssetsBalance = rpcResult.response.assetsBalance;\n if (merged.errors && rpcAssetsBalance) {\n const chainsRecoveredByRpc = new Set<string>();\n for (const accountBalances of Object.values(rpcAssetsBalance)) {\n for (const assetId of Object.keys(accountBalances)) {\n chainsRecoveredByRpc.add(assetId.split('/')[0]);\n }\n }\n for (const chainId of erroredChains) {\n if (chainsRecoveredByRpc.has(chainId)) {\n delete merged.errors[chainId];\n }\n }\n }\n\n return next({ ...ctx, response: merged });\n });\n }\n}\n"]}
1
+ {"version":3,"file":"RpcFallbackMiddleware.cjs","sourceRoot":"","sources":["../../src/middlewares/RpcFallbackMiddleware.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAqD;AAErD,yEAAqF;AACrF,6CAAiE;AACjE,2CAA2C;AAU3C,oEAA6D;AAE7D,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAEhD,MAAM,GAAG,GAAG,IAAA,8BAAkB,EAAC,yBAAa,EAAE,eAAe,CAAC,CAAC;AAO/D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAa,qBAAqB;IAKhC,YAAY,OAAqC;QAJxC,SAAI,GAAG,eAAe,CAAC;QAEvB,uDAAiC;QAGxC,uBAAA,IAAI,wCAAkB,OAAO,CAAC,aAAa,MAAA,CAAC;IAC9C,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED,IAAI,gBAAgB;QAClB,OAAO,IAAA,uBAAY,EAAC,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YACnD,MAAM,aAAa,GAAG,IAAI,GAAG,CAC3B,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAc,CACpD,CAAC;YACF,MAAM,WAAW,GAAG,yBAAyB,CAAC,GAAG,CAAC,CAAC;YAEnD,MAAM,aAAa,GAAG;gBACpB,GAAG,IAAI,GAAG,CAAC;oBACT,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBAC7D,iEAAiE;oBACjE,oEAAoE;oBACpE,2BAA2B;oBAC3B,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAY,CAAC;iBAClE,CAAC;aACH,CAAC;YAEF,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC/B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;YAED,GAAG,CAAC,4BAA4B,EAAE;gBAChC,aAAa,EAAE,CAAC,GAAG,aAAa,CAAC;gBACjC,WAAW;gBACX,MAAM,EAAE,aAAa;aACtB,CAAC,CAAC;YAEH,MAAM,eAAe,GAAG;gBACtB,GAAG,GAAG,CAAC,OAAO;gBACd,QAAQ,EAAE,aAAa;gBACvB,YAAY,EAAE;oBACZ,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC;iBAClE;aACF,CAAC;YAEF,MAAM,QAAQ,GAAG,KAAK,EAAE,KAAiB,EAAuB,EAAE,CAAC,KAAK,CAAC;YACzE,MAAM,SAAS,GAAG,MAAM,uBAAA,IAAI,4CAAe,CAAC,gBAAgB,CAC1D;gBACE,GAAG,GAAG;gBACN,OAAO,EAAE,eAAe;gBACxB,QAAQ,EAAE,EAAE;aACb,EACD,QAAQ,CACT,CAAC;YAEF,oEAAoE;YACpE,qEAAqE;YACrE,mEAAmE;YACnE,uEAAuE;YACvE,0DAA0D;YAC1D,MAAM,eAAe,GAAG,IAAI,GAAG,CAC7B,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAc,CAC1D,CAAC;YACF,MAAM,gBAAgB,GAAG,sBAAsB,CAC7C,SAAS,CAAC,QAAQ,CAAC,aAAa,EAChC,eAAe,CAChB,CAAC;YAEF,gEAAgE;YAChE,mEAAmE;YACnE,wEAAwE;YACxE,6DAA6D;YAC7D,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,CAClC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,CACnE,aAAa,CAAC,GAAG,CAAC,OAAkB,CAAC,CACtC,CACF,CAAC;YAEF,MAAM,MAAM,GAAiB,IAAA,0CAAkB,EAAC;gBAC9C,GAAG,CAAC,QAAQ;gBACZ;oBACE,GAAG,SAAS,CAAC,QAAQ;oBACrB,aAAa,EAAE,gBAAgB;oBAC/B,MAAM,EAAE,SAAS;iBAClB;aACF,CAAC,CAAC;YAEH,qEAAqE;YACrE,qEAAqE;YACrE,yEAAyE;YACzE,kEAAkE;YAClE,sDAAsD;YACtD,iEAAiE;YACjE,mEAAmE;YACnE,IAAI,MAAM,CAAC,MAAM,IAAI,gBAAgB,EAAE,CAAC;gBACtC,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAU,CAAC;gBAC/C,KAAK,MAAM,eAAe,IAAI,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC;oBAC9D,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;wBACnD,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;oBAClD,CAAC;gBACH,CAAC;gBACD,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;oBACpC,IAAI,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;wBACtC,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;oBAChC,CAAC;gBACH,CAAC;YACH,CAAC;YAED,OAAO,IAAI,CAAC,EAAE,GAAG,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAlHD,sDAkHC;;AAED;;;;;;;;;GASG;AACH,SAAS,sBAAsB,CAC7B,aAA4C,EAC5C,QAAsB;IAEtB,IAAI,CAAC,aAAa,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QAC1C,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,MAAM,QAAQ,GAA+C,EAAE,CAAC;IAChE,KAAK,MAAM,CAAC,SAAS,EAAE,eAAe,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;QACzE,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAC7B,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,MAAM,CACpC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAY,CAAC,CAC/D,CACqC,CAAC;QACzC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjC,QAAQ,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,yBAAyB,CAAC,GAAY;IAC7C,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,YAAY,EAAE,iBAAiB,EAAE,GAC1E,GAAG,CAAC,cAAc,EAAE,CAAC;IAEvB,MAAM,WAAW,GAAG,IAAI,GAAG,EAAiB,CAAC;IAE7C,KAAK,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,IAAI,GAAG,CAAC,OAAO;SACnD,2BAA2B,EAAE,CAAC;QAC/B,MAAM,SAAS,GAAG,OAAO,CAAC,EAAE,CAAC;QAC7B,MAAM,eAAe,GAAG,IAAI,GAAG,CAAgB;YAC7C,GAAI,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC,IAAI,EAAE,CAAqB;YACxE,GAAG,CAAC,iBAAiB,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;SAC1C,CAAC,CAAC;QAEH,MAAM,gBAAgB,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAC1D,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CACvC,CAAC;QAEF,KAAK,MAAM,OAAO,IAAI,eAAe,EAAE,CAAC;YACtC,IACE,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,CAAC;gBAC7C,kEAAkE;gBAClE,qDAAqD;gBACrD,CAAC,IAAA,mCAAwB,EAAC,OAAO,CAAC;gBAClC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,EAChE,CAAC;gBACD,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,WAAW,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,cAAc,CACrB,QAAkD,EAClD,OAAsB;IAEtB,IAAI,MAAM,GAAG,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IACzC,IAAI,MAAM,KAAK,SAAS,IAAI,QAAQ,EAAE,CAAC;QACrC,MAAM,iBAAiB,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;QAChD,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CACpC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,iBAAiB,CACjD,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC;IACD,OAAO,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;;GAMG;AACH,SAAS,kBAAkB,CACzB,OAAsB,EACtB,QAAmB;IAEnB,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,0BAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAY,CAAC,CAAC;AAC7D,CAAC","sourcesContent":["import { KnownCaipNamespace } from '@metamask/utils';\n\nimport { isStakingContractAssetId } from '../data-sources/evm-rpc-services/index.js';\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport { forDataTypes } from '../types.js';\nimport type {\n AssetBalance,\n AssetsDataSource,\n Caip19AssetId,\n ChainId,\n Context,\n DataResponse,\n Middleware,\n} from '../types.js';\nimport { mergeDataResponses } from './ParallelMiddleware.js';\n\nconst CONTROLLER_NAME = 'RpcFallbackMiddleware';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\nexport type RpcFallbackMiddlewareOptions = {\n /** The RPC data source to use as a fallback. */\n rpcDataSource: AssetsDataSource;\n};\n\n/**\n * RpcFallbackMiddleware re-reads balances from the RPC data source in two\n * cases:\n *\n * 1. Chains present in `response.errors` (network error, unprocessedNetworks,\n * timeout, …). Successful RPC results are merged into the response and\n * their entries are cleared from `response.errors`.\n * 2. EVM assets tracked in state (`state.assetsBalance` or\n * `state.customAssets`) that this balance response left empty. The Accounts\n * API omits tokens it does not index, and a returned `0` is\n * indistinguishable from \"not indexed\", so the `merge` state update would\n * otherwise keep a stale amount forever. They are passed to RPC as\n * `customAssets` so the balance fetcher includes them in its multicall.\n *\n * Place this immediately after `createParallelBalanceMiddleware` in the fast\n * pipeline, after `CustomAssetGraduationMiddleware` so the custom assets RPC\n * carries never trigger graduation.\n */\nexport class RpcFallbackMiddleware {\n readonly name = CONTROLLER_NAME;\n\n readonly #rpcDataSource: AssetsDataSource;\n\n constructor(options: RpcFallbackMiddlewareOptions) {\n this.#rpcDataSource = options.rpcDataSource;\n }\n\n getName(): string {\n return this.name;\n }\n\n get assetsMiddleware(): Middleware {\n return forDataTypes(['balance'], async (ctx, next) => {\n const erroredChains = new Set<ChainId>(\n Object.keys(ctx.response.errors ?? {}) as ChainId[],\n );\n const staleAssets = collectStaleTrackedAssets(ctx);\n\n const chainsToFetch = [\n ...new Set([\n ...ctx.request.chainIds.filter((id) => erroredChains.has(id)),\n // Already restricted to requested chains. Their chain may not be\n // errored: the Accounts API can answer for a chain while omitting a\n // token it does not index.\n ...staleAssets.map((assetId) => assetId.split('/')[0] as ChainId),\n ]),\n ];\n\n if (chainsToFetch.length === 0) {\n return next(ctx);\n }\n\n log('Re-reading balances on RPC', {\n erroredChains: [...erroredChains],\n staleAssets,\n chains: chainsToFetch,\n });\n\n const filteredRequest = {\n ...ctx.request,\n chainIds: chainsToFetch,\n customAssets: [\n ...new Set([...(ctx.request.customAssets ?? []), ...staleAssets]),\n ],\n };\n\n const noopNext = async (inner: typeof ctx): Promise<typeof ctx> => inner;\n const rpcResult = await this.#rpcDataSource.assetsMiddleware(\n {\n ...ctx,\n request: filteredRequest,\n response: {},\n },\n noopNext,\n );\n\n // A chain RPC itself failed on contributed nothing trustworthy: its\n // balances are failure stubs (native 0) that would overwrite correct\n // upstream amounts and, with replaceCoveredChainBalances, wipe the\n // chain's token slice from state. Drop them before merging — this also\n // keeps failed chains from counting as \"recovered\" below.\n const rpcFailedChains = new Set<ChainId>(\n Object.keys(rpcResult.response.errors ?? {}) as ChainId[],\n );\n const rpcAssetsBalance = filterOutChainBalances(\n rpcResult.response.assetsBalance,\n rpcFailedChains,\n );\n\n // RPC errors are kept only for chains that were already errored\n // upstream. For chains fetched solely for stale tracked assets the\n // upstream response succeeded and stays authoritative — the stale asset\n // keeps its previous amount and is retried on the next pass.\n const rpcErrors = Object.fromEntries(\n Object.entries(rpcResult.response.errors ?? {}).filter(([chainId]) =>\n erroredChains.has(chainId as ChainId),\n ),\n );\n\n const merged: DataResponse = mergeDataResponses([\n ctx.response,\n {\n ...rpcResult.response,\n assetsBalance: rpcAssetsBalance,\n errors: rpcErrors,\n },\n ]);\n\n // Clear errors only for chains RPC actually recovered a balance for.\n // We must inspect the (filtered) RPC balances — NOT merged — because\n // merged also contains balances from the upstream sources (AccountsApi /\n // Websocket / Staked). If those sources returned partial data for\n // a chain that they also flagged as errored (e.g. via\n // unprocessedNetworks), and RPC then failed for that same chain,\n // looking at merged would incorrectly mark the error as recovered.\n if (merged.errors && rpcAssetsBalance) {\n const chainsRecoveredByRpc = new Set<string>();\n for (const accountBalances of Object.values(rpcAssetsBalance)) {\n for (const assetId of Object.keys(accountBalances)) {\n chainsRecoveredByRpc.add(assetId.split('/')[0]);\n }\n }\n for (const chainId of erroredChains) {\n if (chainsRecoveredByRpc.has(chainId)) {\n delete merged.errors[chainId];\n }\n }\n }\n\n return next({ ...ctx, response: merged });\n });\n }\n}\n\n/**\n * Remove all balances that belong to the given chains.\n *\n * Used to discard results for chains the RPC source itself failed on, whose\n * entries are failure stubs (native `0`) rather than real readings.\n *\n * @param assetsBalance - Balances by account from the RPC response.\n * @param chainIds - Chains whose balances should be dropped.\n * @returns The filtered balance map, without accounts left empty.\n */\nfunction filterOutChainBalances(\n assetsBalance: DataResponse['assetsBalance'],\n chainIds: Set<ChainId>,\n): DataResponse['assetsBalance'] {\n if (!assetsBalance || chainIds.size === 0) {\n return assetsBalance;\n }\n\n const filtered: NonNullable<DataResponse['assetsBalance']> = {};\n for (const [accountId, accountBalances] of Object.entries(assetsBalance)) {\n const kept = Object.fromEntries(\n Object.entries(accountBalances).filter(\n ([assetId]) => !chainIds.has(assetId.split('/')[0] as ChainId),\n ),\n ) as Record<Caip19AssetId, AssetBalance>;\n if (Object.keys(kept).length > 0) {\n filtered[accountId] = kept;\n }\n }\n return filtered;\n}\n\n/**\n * EVM assets tracked in state that this balance response left empty and RPC\n * should re-read.\n *\n * Limited to chains both requested and supported by the owning account:\n * RpcDataSource fetches per account and skips chains outside its supported\n * set, so anything else would be queued and then silently dropped.\n *\n * @param ctx - Pipeline context.\n * @returns Asset IDs to hand to the RPC data source.\n */\nfunction collectStaleTrackedAssets(ctx: Context): Caip19AssetId[] {\n const { assetsBalance: stateAssetsBalance, customAssets: stateCustomAssets } =\n ctx.getAssetsState();\n\n const staleAssets = new Set<Caip19AssetId>();\n\n for (const { account, supportedChains } of ctx.request\n .accountsWithSupportedChains) {\n const accountId = account.id;\n const trackedAssetIds = new Set<Caip19AssetId>([\n ...(Object.keys(stateAssetsBalance[accountId] ?? {}) as Caip19AssetId[]),\n ...(stateCustomAssets?.[accountId] ?? []),\n ]);\n\n const chainsForAccount = supportedChains.filter((chainId) =>\n ctx.request.chainIds.includes(chainId),\n );\n\n for (const assetId of trackedAssetIds) {\n if (\n isEvmAssetOnChains(assetId, chainsForAccount) &&\n // Staked vault balances belong to StakedBalanceDataSource; an RPC\n // ERC-20 read of the share token would clobber them.\n !isStakingContractAssetId(assetId) &&\n isBalanceEmpty(ctx.response.assetsBalance?.[accountId], assetId)\n ) {\n staleAssets.add(assetId);\n }\n }\n }\n\n return [...staleAssets];\n}\n\n/**\n * Whether a balance response carries no positive amount for an asset. A\n * returned `0` cannot be distinguished from \"not indexed\", so both count as\n * empty. Asset IDs are matched case-insensitively: state keys ERC-20 assets by\n * checksummed address, while some data sources return them lower-cased.\n *\n * @param balances - Balance map for a single account from the response.\n * @param assetId - Asset ID to check.\n * @returns True when the response holds no positive amount for the asset.\n */\nfunction isBalanceEmpty(\n balances: Record<string, AssetBalance> | undefined,\n assetId: Caip19AssetId,\n): boolean {\n let amount = balances?.[assetId]?.amount;\n if (amount === undefined && balances) {\n const lowerCasedAssetId = assetId.toLowerCase();\n amount = Object.entries(balances).find(\n ([id]) => id.toLowerCase() === lowerCasedAssetId,\n )?.[1]?.amount;\n }\n return !(Number(amount) > 0);\n}\n\n/**\n * Whether an asset is an EVM asset on one of the given chains.\n *\n * @param assetId - CAIP-19 asset ID.\n * @param chainIds - Chains to match against.\n * @returns True for EVM assets whose chain is in `chainIds`.\n */\nfunction isEvmAssetOnChains(\n assetId: Caip19AssetId,\n chainIds: ChainId[],\n): boolean {\n if (!assetId.startsWith(`${KnownCaipNamespace.Eip155}:`)) {\n return false;\n }\n return chainIds.includes(assetId.split('/')[0] as ChainId);\n}\n"]}
@@ -4,14 +4,22 @@ export type RpcFallbackMiddlewareOptions = {
4
4
  rpcDataSource: AssetsDataSource;
5
5
  };
6
6
  /**
7
- * RpcFallbackMiddleware retries chains that failed upstream on the RPC data
8
- * source. Any chain present in `response.errors` (network error,
9
- * unprocessedNetworks, timeout, …) is handed off to RPC with the request
10
- * filtered to just those chains. Successful RPC results are merged into the
11
- * response and their entries are cleared from `response.errors`.
7
+ * RpcFallbackMiddleware re-reads balances from the RPC data source in two
8
+ * cases:
9
+ *
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.
12
19
  *
13
20
  * Place this immediately after `createParallelBalanceMiddleware` in the fast
14
- * pipeline.
21
+ * pipeline, after `CustomAssetGraduationMiddleware` so the custom assets RPC
22
+ * carries never trigger graduation.
15
23
  */
16
24
  export declare class RpcFallbackMiddleware {
17
25
  #private;
@@ -1 +1 @@
1
- {"version":3,"file":"RpcFallbackMiddleware.d.cts","sourceRoot":"","sources":["../../src/middlewares/RpcFallbackMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAGhB,UAAU,EACX,qBAAoB;AAOrB,MAAM,MAAM,4BAA4B,GAAG;IACzC,gDAAgD;IAChD,aAAa,EAAE,gBAAgB,CAAC;CACjC,CAAC;AAEF;;;;;;;;;GASG;AACH,qBAAa,qBAAqB;;IAChC,QAAQ,CAAC,IAAI,2BAAmB;gBAIpB,OAAO,EAAE,4BAA4B;IAIjD,OAAO,IAAI,MAAM;IAIjB,IAAI,gBAAgB,IAAI,UAAU,CAyDjC;CACF"}
1
+ {"version":3,"file":"RpcFallbackMiddleware.d.cts","sourceRoot":"","sources":["../../src/middlewares/RpcFallbackMiddleware.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEV,gBAAgB,EAKhB,UAAU,EACX,qBAAoB;AAOrB,MAAM,MAAM,4BAA4B,GAAG;IACzC,gDAAgD;IAChD,aAAa,EAAE,gBAAgB,CAAC;CACjC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,qBAAqB;;IAChC,QAAQ,CAAC,IAAI,2BAAmB;gBAIpB,OAAO,EAAE,4BAA4B;IAIjD,OAAO,IAAI,MAAM;IAIjB,IAAI,gBAAgB,IAAI,UAAU,CAoGjC;CACF"}
@@ -4,14 +4,22 @@ export type RpcFallbackMiddlewareOptions = {
4
4
  rpcDataSource: AssetsDataSource;
5
5
  };
6
6
  /**
7
- * RpcFallbackMiddleware retries chains that failed upstream on the RPC data
8
- * source. Any chain present in `response.errors` (network error,
9
- * unprocessedNetworks, timeout, …) is handed off to RPC with the request
10
- * filtered to just those chains. Successful RPC results are merged into the
11
- * response and their entries are cleared from `response.errors`.
7
+ * RpcFallbackMiddleware re-reads balances from the RPC data source in two
8
+ * cases:
9
+ *
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.
12
19
  *
13
20
  * Place this immediately after `createParallelBalanceMiddleware` in the fast
14
- * pipeline.
21
+ * pipeline, after `CustomAssetGraduationMiddleware` so the custom assets RPC
22
+ * carries never trigger graduation.
15
23
  */
16
24
  export declare class RpcFallbackMiddleware {
17
25
  #private;
@@ -1 +1 @@
1
- {"version":3,"file":"RpcFallbackMiddleware.d.mts","sourceRoot":"","sources":["../../src/middlewares/RpcFallbackMiddleware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAGhB,UAAU,EACX,qBAAoB;AAOrB,MAAM,MAAM,4BAA4B,GAAG;IACzC,gDAAgD;IAChD,aAAa,EAAE,gBAAgB,CAAC;CACjC,CAAC;AAEF;;;;;;;;;GASG;AACH,qBAAa,qBAAqB;;IAChC,QAAQ,CAAC,IAAI,2BAAmB;gBAIpB,OAAO,EAAE,4BAA4B;IAIjD,OAAO,IAAI,MAAM;IAIjB,IAAI,gBAAgB,IAAI,UAAU,CAyDjC;CACF"}
1
+ {"version":3,"file":"RpcFallbackMiddleware.d.mts","sourceRoot":"","sources":["../../src/middlewares/RpcFallbackMiddleware.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEV,gBAAgB,EAKhB,UAAU,EACX,qBAAoB;AAOrB,MAAM,MAAM,4BAA4B,GAAG;IACzC,gDAAgD;IAChD,aAAa,EAAE,gBAAgB,CAAC;CACjC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,qBAAqB;;IAChC,QAAQ,CAAC,IAAI,2BAAmB;gBAIpB,OAAO,EAAE,4BAA4B;IAIjD,OAAO,IAAI,MAAM;IAIjB,IAAI,gBAAgB,IAAI,UAAU,CAoGjC;CACF"}
@@ -10,20 +10,30 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
12
  var _RpcFallbackMiddleware_rpcDataSource;
13
+ import { KnownCaipNamespace } from "@metamask/utils";
14
+ import { isStakingContractAssetId } from "../data-sources/evm-rpc-services/index.mjs";
13
15
  import { projectLogger, createModuleLogger } from "../logger.mjs";
14
16
  import { forDataTypes } from "../types.mjs";
15
17
  import { mergeDataResponses } from "./ParallelMiddleware.mjs";
16
18
  const CONTROLLER_NAME = 'RpcFallbackMiddleware';
17
19
  const log = createModuleLogger(projectLogger, CONTROLLER_NAME);
18
20
  /**
19
- * RpcFallbackMiddleware retries chains that failed upstream on the RPC data
20
- * source. Any chain present in `response.errors` (network error,
21
- * unprocessedNetworks, timeout, …) is handed off to RPC with the request
22
- * filtered to just those chains. Successful RPC results are merged into the
23
- * response and their entries are cleared from `response.errors`.
21
+ * RpcFallbackMiddleware re-reads balances from the RPC data source in two
22
+ * cases:
23
+ *
24
+ * 1. Chains present in `response.errors` (network error, unprocessedNetworks,
25
+ * timeout, …). Successful RPC results are merged into the response and
26
+ * their entries are cleared from `response.errors`.
27
+ * 2. EVM assets tracked in state (`state.assetsBalance` or
28
+ * `state.customAssets`) that this balance response left empty. The Accounts
29
+ * API omits tokens it does not index, and a returned `0` is
30
+ * indistinguishable from "not indexed", so the `merge` state update would
31
+ * otherwise keep a stale amount forever. They are passed to RPC as
32
+ * `customAssets` so the balance fetcher includes them in its multicall.
24
33
  *
25
34
  * Place this immediately after `createParallelBalanceMiddleware` in the fast
26
- * pipeline.
35
+ * pipeline, after `CustomAssetGraduationMiddleware` so the custom assets RPC
36
+ * carries never trigger graduation.
27
37
  */
28
38
  export class RpcFallbackMiddleware {
29
39
  constructor(options) {
@@ -37,15 +47,30 @@ export class RpcFallbackMiddleware {
37
47
  get assetsMiddleware() {
38
48
  return forDataTypes(['balance'], async (ctx, next) => {
39
49
  const erroredChains = new Set(Object.keys(ctx.response.errors ?? {}));
40
- if (erroredChains.size === 0) {
50
+ const staleAssets = collectStaleTrackedAssets(ctx);
51
+ const chainsToFetch = [
52
+ ...new Set([
53
+ ...ctx.request.chainIds.filter((id) => erroredChains.has(id)),
54
+ // Already restricted to requested chains. Their chain may not be
55
+ // errored: the Accounts API can answer for a chain while omitting a
56
+ // token it does not index.
57
+ ...staleAssets.map((assetId) => assetId.split('/')[0]),
58
+ ]),
59
+ ];
60
+ if (chainsToFetch.length === 0) {
41
61
  return next(ctx);
42
62
  }
43
- log('Retrying failed chains on RPC', {
44
- chains: [...erroredChains],
63
+ log('Re-reading balances on RPC', {
64
+ erroredChains: [...erroredChains],
65
+ staleAssets,
66
+ chains: chainsToFetch,
45
67
  });
46
68
  const filteredRequest = {
47
69
  ...ctx.request,
48
- chainIds: ctx.request.chainIds.filter((id) => erroredChains.has(id)),
70
+ chainIds: chainsToFetch,
71
+ customAssets: [
72
+ ...new Set([...(ctx.request.customAssets ?? []), ...staleAssets]),
73
+ ],
49
74
  };
50
75
  const noopNext = async (inner) => inner;
51
76
  const rpcResult = await __classPrivateFieldGet(this, _RpcFallbackMiddleware_rpcDataSource, "f").assetsMiddleware({
@@ -53,18 +78,33 @@ export class RpcFallbackMiddleware {
53
78
  request: filteredRequest,
54
79
  response: {},
55
80
  }, noopNext);
81
+ // A chain RPC itself failed on contributed nothing trustworthy: its
82
+ // balances are failure stubs (native 0) that would overwrite correct
83
+ // upstream amounts and, with replaceCoveredChainBalances, wipe the
84
+ // chain's token slice from state. Drop them before merging — this also
85
+ // keeps failed chains from counting as "recovered" below.
86
+ const rpcFailedChains = new Set(Object.keys(rpcResult.response.errors ?? {}));
87
+ const rpcAssetsBalance = filterOutChainBalances(rpcResult.response.assetsBalance, rpcFailedChains);
88
+ // RPC errors are kept only for chains that were already errored
89
+ // upstream. For chains fetched solely for stale tracked assets the
90
+ // upstream response succeeded and stays authoritative — the stale asset
91
+ // keeps its previous amount and is retried on the next pass.
92
+ const rpcErrors = Object.fromEntries(Object.entries(rpcResult.response.errors ?? {}).filter(([chainId]) => erroredChains.has(chainId)));
56
93
  const merged = mergeDataResponses([
57
94
  ctx.response,
58
- rpcResult.response,
95
+ {
96
+ ...rpcResult.response,
97
+ assetsBalance: rpcAssetsBalance,
98
+ errors: rpcErrors,
99
+ },
59
100
  ]);
60
101
  // Clear errors only for chains RPC actually recovered a balance for.
61
- // We must inspect rpcResult.response — NOT merged — because merged
62
- // also contains balances from the upstream sources (AccountsApi /
102
+ // We must inspect the (filtered) RPC balances — NOT merged — because
103
+ // merged also contains balances from the upstream sources (AccountsApi /
63
104
  // Websocket / Staked). If those sources returned partial data for
64
105
  // a chain that they also flagged as errored (e.g. via
65
106
  // unprocessedNetworks), and RPC then failed for that same chain,
66
107
  // looking at merged would incorrectly mark the error as recovered.
67
- const rpcAssetsBalance = rpcResult.response.assetsBalance;
68
108
  if (merged.errors && rpcAssetsBalance) {
69
109
  const chainsRecoveredByRpc = new Set();
70
110
  for (const accountBalances of Object.values(rpcAssetsBalance)) {
@@ -83,4 +123,92 @@ export class RpcFallbackMiddleware {
83
123
  }
84
124
  }
85
125
  _RpcFallbackMiddleware_rpcDataSource = new WeakMap();
126
+ /**
127
+ * Remove all balances that belong to the given chains.
128
+ *
129
+ * Used to discard results for chains the RPC source itself failed on, whose
130
+ * entries are failure stubs (native `0`) rather than real readings.
131
+ *
132
+ * @param assetsBalance - Balances by account from the RPC response.
133
+ * @param chainIds - Chains whose balances should be dropped.
134
+ * @returns The filtered balance map, without accounts left empty.
135
+ */
136
+ function filterOutChainBalances(assetsBalance, chainIds) {
137
+ if (!assetsBalance || chainIds.size === 0) {
138
+ return assetsBalance;
139
+ }
140
+ const filtered = {};
141
+ for (const [accountId, accountBalances] of Object.entries(assetsBalance)) {
142
+ const kept = Object.fromEntries(Object.entries(accountBalances).filter(([assetId]) => !chainIds.has(assetId.split('/')[0])));
143
+ if (Object.keys(kept).length > 0) {
144
+ filtered[accountId] = kept;
145
+ }
146
+ }
147
+ return filtered;
148
+ }
149
+ /**
150
+ * EVM assets tracked in state that this balance response left empty and RPC
151
+ * should re-read.
152
+ *
153
+ * Limited to chains both requested and supported by the owning account:
154
+ * RpcDataSource fetches per account and skips chains outside its supported
155
+ * set, so anything else would be queued and then silently dropped.
156
+ *
157
+ * @param ctx - Pipeline context.
158
+ * @returns Asset IDs to hand to the RPC data source.
159
+ */
160
+ function collectStaleTrackedAssets(ctx) {
161
+ const { assetsBalance: stateAssetsBalance, customAssets: stateCustomAssets } = ctx.getAssetsState();
162
+ const staleAssets = new Set();
163
+ for (const { account, supportedChains } of ctx.request
164
+ .accountsWithSupportedChains) {
165
+ const accountId = account.id;
166
+ const trackedAssetIds = new Set([
167
+ ...Object.keys(stateAssetsBalance[accountId] ?? {}),
168
+ ...(stateCustomAssets?.[accountId] ?? []),
169
+ ]);
170
+ const chainsForAccount = supportedChains.filter((chainId) => ctx.request.chainIds.includes(chainId));
171
+ for (const assetId of trackedAssetIds) {
172
+ if (isEvmAssetOnChains(assetId, chainsForAccount) &&
173
+ // Staked vault balances belong to StakedBalanceDataSource; an RPC
174
+ // ERC-20 read of the share token would clobber them.
175
+ !isStakingContractAssetId(assetId) &&
176
+ isBalanceEmpty(ctx.response.assetsBalance?.[accountId], assetId)) {
177
+ staleAssets.add(assetId);
178
+ }
179
+ }
180
+ }
181
+ return [...staleAssets];
182
+ }
183
+ /**
184
+ * Whether a balance response carries no positive amount for an asset. A
185
+ * returned `0` cannot be distinguished from "not indexed", so both count as
186
+ * empty. Asset IDs are matched case-insensitively: state keys ERC-20 assets by
187
+ * checksummed address, while some data sources return them lower-cased.
188
+ *
189
+ * @param balances - Balance map for a single account from the response.
190
+ * @param assetId - Asset ID to check.
191
+ * @returns True when the response holds no positive amount for the asset.
192
+ */
193
+ function isBalanceEmpty(balances, assetId) {
194
+ let amount = balances?.[assetId]?.amount;
195
+ if (amount === undefined && balances) {
196
+ const lowerCasedAssetId = assetId.toLowerCase();
197
+ amount = Object.entries(balances).find(([id]) => id.toLowerCase() === lowerCasedAssetId)?.[1]?.amount;
198
+ }
199
+ return !(Number(amount) > 0);
200
+ }
201
+ /**
202
+ * Whether an asset is an EVM asset on one of the given chains.
203
+ *
204
+ * @param assetId - CAIP-19 asset ID.
205
+ * @param chainIds - Chains to match against.
206
+ * @returns True for EVM assets whose chain is in `chainIds`.
207
+ */
208
+ function isEvmAssetOnChains(assetId, chainIds) {
209
+ if (!assetId.startsWith(`${KnownCaipNamespace.Eip155}:`)) {
210
+ return false;
211
+ }
212
+ return chainIds.includes(assetId.split('/')[0]);
213
+ }
86
214
  //# sourceMappingURL=RpcFallbackMiddleware.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"RpcFallbackMiddleware.mjs","sourceRoot":"","sources":["../../src/middlewares/RpcFallbackMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { projectLogger, createModuleLogger } from '../logger.js';\nimport { forDataTypes } from '../types.js';\nimport type {\n AssetsDataSource,\n ChainId,\n DataResponse,\n Middleware,\n} from '../types.js';\nimport { mergeDataResponses } from './ParallelMiddleware.js';\n\nconst CONTROLLER_NAME = 'RpcFallbackMiddleware';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\nexport type RpcFallbackMiddlewareOptions = {\n /** The RPC data source to use as a fallback. */\n rpcDataSource: AssetsDataSource;\n};\n\n/**\n * RpcFallbackMiddleware retries chains that failed upstream on the RPC data\n * source. Any chain present in `response.errors` (network error,\n * unprocessedNetworks, timeout, …) is handed off to RPC with the request\n * filtered to just those chains. Successful RPC results are merged into the\n * response and their entries are cleared from `response.errors`.\n *\n * Place this immediately after `createParallelBalanceMiddleware` in the fast\n * pipeline.\n */\nexport class RpcFallbackMiddleware {\n readonly name = CONTROLLER_NAME;\n\n readonly #rpcDataSource: AssetsDataSource;\n\n constructor(options: RpcFallbackMiddlewareOptions) {\n this.#rpcDataSource = options.rpcDataSource;\n }\n\n getName(): string {\n return this.name;\n }\n\n get assetsMiddleware(): Middleware {\n return forDataTypes(['balance'], async (ctx, next) => {\n const erroredChains = new Set<ChainId>(\n Object.keys(ctx.response.errors ?? {}) as ChainId[],\n );\n if (erroredChains.size === 0) {\n return next(ctx);\n }\n\n log('Retrying failed chains on RPC', {\n chains: [...erroredChains],\n });\n\n const filteredRequest = {\n ...ctx.request,\n chainIds: ctx.request.chainIds.filter((id) => erroredChains.has(id)),\n };\n\n const noopNext = async (inner: typeof ctx): Promise<typeof ctx> => inner;\n const rpcResult = await this.#rpcDataSource.assetsMiddleware(\n {\n ...ctx,\n request: filteredRequest,\n response: {},\n },\n noopNext,\n );\n\n const merged: DataResponse = mergeDataResponses([\n ctx.response,\n rpcResult.response,\n ]);\n\n // Clear errors only for chains RPC actually recovered a balance for.\n // We must inspect rpcResult.response — NOT merged — because merged\n // also contains balances from the upstream sources (AccountsApi /\n // Websocket / Staked). If those sources returned partial data for\n // a chain that they also flagged as errored (e.g. via\n // unprocessedNetworks), and RPC then failed for that same chain,\n // looking at merged would incorrectly mark the error as recovered.\n const rpcAssetsBalance = rpcResult.response.assetsBalance;\n if (merged.errors && rpcAssetsBalance) {\n const chainsRecoveredByRpc = new Set<string>();\n for (const accountBalances of Object.values(rpcAssetsBalance)) {\n for (const assetId of Object.keys(accountBalances)) {\n chainsRecoveredByRpc.add(assetId.split('/')[0]);\n }\n }\n for (const chainId of erroredChains) {\n if (chainsRecoveredByRpc.has(chainId)) {\n delete merged.errors[chainId];\n }\n }\n }\n\n return next({ ...ctx, response: merged });\n });\n }\n}\n"]}
1
+ {"version":3,"file":"RpcFallbackMiddleware.mjs","sourceRoot":"","sources":["../../src/middlewares/RpcFallbackMiddleware.ts"],"names":[],"mappings":"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","sourcesContent":["import { KnownCaipNamespace } from '@metamask/utils';\n\nimport { isStakingContractAssetId } from '../data-sources/evm-rpc-services/index.js';\nimport { projectLogger, createModuleLogger } from '../logger.js';\nimport { forDataTypes } from '../types.js';\nimport type {\n AssetBalance,\n AssetsDataSource,\n Caip19AssetId,\n ChainId,\n Context,\n DataResponse,\n Middleware,\n} from '../types.js';\nimport { mergeDataResponses } from './ParallelMiddleware.js';\n\nconst CONTROLLER_NAME = 'RpcFallbackMiddleware';\n\nconst log = createModuleLogger(projectLogger, CONTROLLER_NAME);\n\nexport type RpcFallbackMiddlewareOptions = {\n /** The RPC data source to use as a fallback. */\n rpcDataSource: AssetsDataSource;\n};\n\n/**\n * RpcFallbackMiddleware re-reads balances from the RPC data source in two\n * cases:\n *\n * 1. Chains present in `response.errors` (network error, unprocessedNetworks,\n * timeout, …). Successful RPC results are merged into the response and\n * their entries are cleared from `response.errors`.\n * 2. EVM assets tracked in state (`state.assetsBalance` or\n * `state.customAssets`) that this balance response left empty. The Accounts\n * API omits tokens it does not index, and a returned `0` is\n * indistinguishable from \"not indexed\", so the `merge` state update would\n * otherwise keep a stale amount forever. They are passed to RPC as\n * `customAssets` so the balance fetcher includes them in its multicall.\n *\n * Place this immediately after `createParallelBalanceMiddleware` in the fast\n * pipeline, after `CustomAssetGraduationMiddleware` so the custom assets RPC\n * carries never trigger graduation.\n */\nexport class RpcFallbackMiddleware {\n readonly name = CONTROLLER_NAME;\n\n readonly #rpcDataSource: AssetsDataSource;\n\n constructor(options: RpcFallbackMiddlewareOptions) {\n this.#rpcDataSource = options.rpcDataSource;\n }\n\n getName(): string {\n return this.name;\n }\n\n get assetsMiddleware(): Middleware {\n return forDataTypes(['balance'], async (ctx, next) => {\n const erroredChains = new Set<ChainId>(\n Object.keys(ctx.response.errors ?? {}) as ChainId[],\n );\n const staleAssets = collectStaleTrackedAssets(ctx);\n\n const chainsToFetch = [\n ...new Set([\n ...ctx.request.chainIds.filter((id) => erroredChains.has(id)),\n // Already restricted to requested chains. Their chain may not be\n // errored: the Accounts API can answer for a chain while omitting a\n // token it does not index.\n ...staleAssets.map((assetId) => assetId.split('/')[0] as ChainId),\n ]),\n ];\n\n if (chainsToFetch.length === 0) {\n return next(ctx);\n }\n\n log('Re-reading balances on RPC', {\n erroredChains: [...erroredChains],\n staleAssets,\n chains: chainsToFetch,\n });\n\n const filteredRequest = {\n ...ctx.request,\n chainIds: chainsToFetch,\n customAssets: [\n ...new Set([...(ctx.request.customAssets ?? []), ...staleAssets]),\n ],\n };\n\n const noopNext = async (inner: typeof ctx): Promise<typeof ctx> => inner;\n const rpcResult = await this.#rpcDataSource.assetsMiddleware(\n {\n ...ctx,\n request: filteredRequest,\n response: {},\n },\n noopNext,\n );\n\n // A chain RPC itself failed on contributed nothing trustworthy: its\n // balances are failure stubs (native 0) that would overwrite correct\n // upstream amounts and, with replaceCoveredChainBalances, wipe the\n // chain's token slice from state. Drop them before merging — this also\n // keeps failed chains from counting as \"recovered\" below.\n const rpcFailedChains = new Set<ChainId>(\n Object.keys(rpcResult.response.errors ?? {}) as ChainId[],\n );\n const rpcAssetsBalance = filterOutChainBalances(\n rpcResult.response.assetsBalance,\n rpcFailedChains,\n );\n\n // RPC errors are kept only for chains that were already errored\n // upstream. For chains fetched solely for stale tracked assets the\n // upstream response succeeded and stays authoritative — the stale asset\n // keeps its previous amount and is retried on the next pass.\n const rpcErrors = Object.fromEntries(\n Object.entries(rpcResult.response.errors ?? {}).filter(([chainId]) =>\n erroredChains.has(chainId as ChainId),\n ),\n );\n\n const merged: DataResponse = mergeDataResponses([\n ctx.response,\n {\n ...rpcResult.response,\n assetsBalance: rpcAssetsBalance,\n errors: rpcErrors,\n },\n ]);\n\n // Clear errors only for chains RPC actually recovered a balance for.\n // We must inspect the (filtered) RPC balances — NOT merged — because\n // merged also contains balances from the upstream sources (AccountsApi /\n // Websocket / Staked). If those sources returned partial data for\n // a chain that they also flagged as errored (e.g. via\n // unprocessedNetworks), and RPC then failed for that same chain,\n // looking at merged would incorrectly mark the error as recovered.\n if (merged.errors && rpcAssetsBalance) {\n const chainsRecoveredByRpc = new Set<string>();\n for (const accountBalances of Object.values(rpcAssetsBalance)) {\n for (const assetId of Object.keys(accountBalances)) {\n chainsRecoveredByRpc.add(assetId.split('/')[0]);\n }\n }\n for (const chainId of erroredChains) {\n if (chainsRecoveredByRpc.has(chainId)) {\n delete merged.errors[chainId];\n }\n }\n }\n\n return next({ ...ctx, response: merged });\n });\n }\n}\n\n/**\n * Remove all balances that belong to the given chains.\n *\n * Used to discard results for chains the RPC source itself failed on, whose\n * entries are failure stubs (native `0`) rather than real readings.\n *\n * @param assetsBalance - Balances by account from the RPC response.\n * @param chainIds - Chains whose balances should be dropped.\n * @returns The filtered balance map, without accounts left empty.\n */\nfunction filterOutChainBalances(\n assetsBalance: DataResponse['assetsBalance'],\n chainIds: Set<ChainId>,\n): DataResponse['assetsBalance'] {\n if (!assetsBalance || chainIds.size === 0) {\n return assetsBalance;\n }\n\n const filtered: NonNullable<DataResponse['assetsBalance']> = {};\n for (const [accountId, accountBalances] of Object.entries(assetsBalance)) {\n const kept = Object.fromEntries(\n Object.entries(accountBalances).filter(\n ([assetId]) => !chainIds.has(assetId.split('/')[0] as ChainId),\n ),\n ) as Record<Caip19AssetId, AssetBalance>;\n if (Object.keys(kept).length > 0) {\n filtered[accountId] = kept;\n }\n }\n return filtered;\n}\n\n/**\n * EVM assets tracked in state that this balance response left empty and RPC\n * should re-read.\n *\n * Limited to chains both requested and supported by the owning account:\n * RpcDataSource fetches per account and skips chains outside its supported\n * set, so anything else would be queued and then silently dropped.\n *\n * @param ctx - Pipeline context.\n * @returns Asset IDs to hand to the RPC data source.\n */\nfunction collectStaleTrackedAssets(ctx: Context): Caip19AssetId[] {\n const { assetsBalance: stateAssetsBalance, customAssets: stateCustomAssets } =\n ctx.getAssetsState();\n\n const staleAssets = new Set<Caip19AssetId>();\n\n for (const { account, supportedChains } of ctx.request\n .accountsWithSupportedChains) {\n const accountId = account.id;\n const trackedAssetIds = new Set<Caip19AssetId>([\n ...(Object.keys(stateAssetsBalance[accountId] ?? {}) as Caip19AssetId[]),\n ...(stateCustomAssets?.[accountId] ?? []),\n ]);\n\n const chainsForAccount = supportedChains.filter((chainId) =>\n ctx.request.chainIds.includes(chainId),\n );\n\n for (const assetId of trackedAssetIds) {\n if (\n isEvmAssetOnChains(assetId, chainsForAccount) &&\n // Staked vault balances belong to StakedBalanceDataSource; an RPC\n // ERC-20 read of the share token would clobber them.\n !isStakingContractAssetId(assetId) &&\n isBalanceEmpty(ctx.response.assetsBalance?.[accountId], assetId)\n ) {\n staleAssets.add(assetId);\n }\n }\n }\n\n return [...staleAssets];\n}\n\n/**\n * Whether a balance response carries no positive amount for an asset. A\n * returned `0` cannot be distinguished from \"not indexed\", so both count as\n * empty. Asset IDs are matched case-insensitively: state keys ERC-20 assets by\n * checksummed address, while some data sources return them lower-cased.\n *\n * @param balances - Balance map for a single account from the response.\n * @param assetId - Asset ID to check.\n * @returns True when the response holds no positive amount for the asset.\n */\nfunction isBalanceEmpty(\n balances: Record<string, AssetBalance> | undefined,\n assetId: Caip19AssetId,\n): boolean {\n let amount = balances?.[assetId]?.amount;\n if (amount === undefined && balances) {\n const lowerCasedAssetId = assetId.toLowerCase();\n amount = Object.entries(balances).find(\n ([id]) => id.toLowerCase() === lowerCasedAssetId,\n )?.[1]?.amount;\n }\n return !(Number(amount) > 0);\n}\n\n/**\n * Whether an asset is an EVM asset on one of the given chains.\n *\n * @param assetId - CAIP-19 asset ID.\n * @param chainIds - Chains to match against.\n * @returns True for EVM assets whose chain is in `chainIds`.\n */\nfunction isEvmAssetOnChains(\n assetId: Caip19AssetId,\n chainIds: ChainId[],\n): boolean {\n if (!assetId.startsWith(`${KnownCaipNamespace.Eip155}:`)) {\n return false;\n }\n return chainIds.includes(assetId.split('/')[0] as ChainId);\n}\n"]}
@@ -135,13 +135,18 @@ function mergeBalancesIntoMap(args) {
135
135
  if (assetPreferences?.[typedAssetId]?.hidden) {
136
136
  continue;
137
137
  }
138
+ // Balances without a matching `assetsInfo` entry are not renderable
139
+ // (no symbol/name/decimals), so they are excluded from aggregation.
140
+ const meta = metadata[typedAssetId];
141
+ if (!meta) {
142
+ continue;
143
+ }
138
144
  const info = getAssetInfo(assetInfoCache, typedAssetId);
139
145
  if (!isChainEnabledByMap(enabledNetworkMap, info.chainId)) {
140
146
  continue;
141
147
  }
142
148
  const amountStr = getAmountFromBalance(accountBalances[typedAssetId]);
143
149
  const amountBn = toBigNumberOrZero(amountStr);
144
- const meta = metadata[typedAssetId];
145
150
  if (amountBn.isZero()) {
146
151
  continue; // skip zeros early to reduce map pressure
147
152
  }
@@ -152,9 +157,9 @@ function mergeBalancesIntoMap(args) {
152
157
  }
153
158
  out.set(typedAssetId, {
154
159
  amount: amountBn,
155
- decimals: meta?.decimals,
156
- symbol: meta?.symbol,
157
- name: meta?.name,
160
+ decimals: meta.decimals,
161
+ symbol: meta.symbol,
162
+ name: meta.name,
158
163
  });
159
164
  }
160
165
  }
@@ -1 +1 @@
1
- {"version":3,"file":"balance.cjs","sourceRoot":"","sources":["../../src/selectors/balance.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccountTreeControllerState } from '@metamask/account-tree-controller';\nimport { toHex } from '@metamask/controller-utils';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { CaipChainId, Hex } from '@metamask/utils';\nimport {\n KnownCaipNamespace,\n isStrictHexString,\n parseCaipAssetType,\n parseCaipChainId,\n} from '@metamask/utils';\nimport BigNumberJS from 'bignumber.js';\n\nimport type { AssetsControllerState } from '../AssetsController.js';\nimport type {\n AccountId,\n AssetBalance,\n AssetMetadata,\n AssetPreferences,\n Caip19AssetId,\n} from '../types.js';\nimport { emitTrace, withTrace } from '../utils/trace.js';\n\n// ============================================================================\n// TRACE NAMES — used in Sentry spans (search these strings in Discover)\n// ============================================================================\n/** Parent span that nests {@link TRACE_AGGREGATED_BALANCE_SELECTOR}. */\nconst TRACE_AGGREGATED_BALANCE = 'AggregatedBalance';\nconst TRACE_AGGREGATED_BALANCE_SELECTOR = 'AggregatedBalanceSelector';\n\n/**\n * Emit `AggregatedBalanceSelector` as a **subspan** under an `AggregatedBalance`\n * parent (via `parentContext`).\n *\n * Work is timed outside the callback (selectors are sync), so we backdate\n * `startTime` via `duration_ms` — MetaMask's Sentry adapter promotes numeric\n * tags to measurements, which Spans dashboard widgets chart as `duration_ms`.\n *\n * @param trace - Trace callback from the client.\n * @param durationMs - Measured compute time in milliseconds.\n * @param data - Additional span attributes (counts, etc.).\n */\nfunction emitAggregatedBalanceSelectorTrace(\n trace: TraceCallback,\n durationMs: number,\n data: Record<string, number | string | boolean>,\n): void {\n const spanData = {\n duration_ms: durationMs,\n ...data,\n };\n\n withTrace({\n name: TRACE_AGGREGATED_BALANCE,\n trace,\n data: spanData,\n fn: async (parentContext) => {\n emitTrace({\n name: TRACE_AGGREGATED_BALANCE_SELECTOR,\n trace,\n data: spanData,\n parentContext,\n });\n },\n }).catch(() => {\n // Telemetry failure must not break.\n });\n}\n\nexport type EnabledNetworkMap =\n | Record<string, Record<string, boolean>>\n | undefined;\n\nexport type AggregatedBalanceEntry = {\n assetId: Caip19AssetId;\n amount: string;\n decimals?: number;\n symbol?: string;\n name?: string;\n};\n\nexport type AggregatedBalanceForAccount = {\n entries: AggregatedBalanceEntry[];\n totalBalanceInFiat?: number;\n pricePercentChange1d?: number;\n previousTotalInFiat?: number;\n};\n\ntype AccountLike = { id: AccountId };\nexport type AccountsById = Record<AccountId, InternalAccount>;\n\ntype PriceDatum = { price: number; pricePercentChange1d: number };\n\nconst ZERO_PRICE: PriceDatum = { price: 0, pricePercentChange1d: 0 };\n\nconst getAmountFromBalance = (balance: AssetBalance): string => {\n if (typeof balance === 'object' && balance !== null && 'amount' in balance) {\n const { amount } = balance as { amount?: unknown };\n return typeof amount === 'string' ? amount : '0';\n }\n return '0';\n};\n\nconst toBigNumberOrZero = (value: string): BigNumberJS => {\n const parsed = new BigNumberJS(value);\n return parsed.isNaN() || !parsed.isFinite() ? new BigNumberJS(0) : parsed;\n};\n\nconst getPriceDatumFast = (\n assetsPrice: AssetsControllerState['assetsPrice'] | undefined,\n assetId: Caip19AssetId,\n): PriceDatum => {\n const raw = assetsPrice?.[assetId];\n if (!raw || typeof raw !== 'object') {\n return ZERO_PRICE;\n }\n const { price, pricePercentChange1d } = raw as {\n price?: unknown;\n pricePercentChange1d?: unknown;\n };\n return {\n price: typeof price === 'number' && Number.isFinite(price) ? price : 0,\n pricePercentChange1d:\n typeof pricePercentChange1d === 'number' &&\n Number.isFinite(pricePercentChange1d)\n ? pricePercentChange1d\n : 0,\n };\n};\n\n/**\n * Enablement lookup optimized:\n * - no array allocations\n * - early returns\n *\n * @param map - Enabled network map (namespace -> storageKey -> boolean)\n * @param id - Chain id as Hex or CAIP-2\n * @returns True if the chain is enabled or map is undefined\n */\nconst isChainEnabledByMap = (\n map: EnabledNetworkMap,\n id: Hex | CaipChainId,\n): boolean => {\n if (!map) {\n return true;\n }\n\n if (isStrictHexString(id)) {\n return Boolean(map[KnownCaipNamespace.Eip155]?.[id]);\n }\n\n const { namespace, reference } = parseCaipChainId(id);\n const namespaceMap = map[namespace];\n if (!namespaceMap) {\n return false;\n }\n\n // exact CAIP-2 id / reference\n if (namespaceMap[id] || namespaceMap[reference]) {\n return true;\n }\n\n // EVM: hex storage key\n if (namespace === KnownCaipNamespace.Eip155) {\n try {\n const storageKey = toHex(reference);\n return Boolean(namespaceMap[storageKey]);\n } catch {\n const parsedRef = Number.parseInt(reference, 10);\n if (Number.isFinite(parsedRef)) {\n return Boolean(namespaceMap[`0x${parsedRef.toString(16)}`]);\n }\n }\n }\n\n return false;\n};\n\n/**\n * Cache parsing results per assetId to avoid parseCaipAssetType/parseCaipChainId\n * being called multiple times across filtering + grouping + pricing.\n */\ntype AssetParseInfo = {\n chainId: CaipChainId;\n isEvm: boolean;\n};\n\nconst makeAssetInfoCache = (): Map<Caip19AssetId, AssetParseInfo> =>\n new Map<Caip19AssetId, AssetParseInfo>();\n\nconst getAssetInfo = (\n cache: Map<Caip19AssetId, AssetParseInfo>,\n assetId: Caip19AssetId,\n): AssetParseInfo => {\n const cached = cache.get(assetId);\n if (cached) {\n return cached;\n }\n const { chainId } = parseCaipAssetType(assetId);\n const { namespace } = parseCaipChainId(chainId);\n const info = { chainId, isEvm: namespace === KnownCaipNamespace.Eip155 };\n cache.set(assetId, info);\n return info;\n};\n\n/**\n * Merge across accounts without building per-account entries arrays.\n * Uses BigNumber for amount accumulation to avoid float precision loss.\n */\ntype AggRow = {\n amount: BigNumberJS;\n decimals?: number;\n symbol?: string;\n name?: string;\n};\n\nfunction mergeBalancesIntoMap(args: {\n out: Map<Caip19AssetId, AggRow>;\n accountBalances: Record<Caip19AssetId, AssetBalance>;\n metadata: Record<Caip19AssetId, AssetMetadata>;\n assetPreferences?: Record<Caip19AssetId, AssetPreferences>;\n enabledNetworkMap?: EnabledNetworkMap;\n assetInfoCache: Map<Caip19AssetId, AssetParseInfo>;\n}): void {\n const {\n out,\n accountBalances,\n metadata,\n assetPreferences,\n enabledNetworkMap,\n assetInfoCache,\n } = args;\n\n for (const assetId in accountBalances) {\n if (!Object.prototype.hasOwnProperty.call(accountBalances, assetId)) {\n continue;\n }\n const typedAssetId = assetId as Caip19AssetId;\n\n if (assetPreferences?.[typedAssetId]?.hidden) {\n continue;\n }\n\n const info = getAssetInfo(assetInfoCache, typedAssetId);\n if (!isChainEnabledByMap(enabledNetworkMap, info.chainId)) {\n continue;\n }\n\n const amountStr = getAmountFromBalance(accountBalances[typedAssetId]);\n const amountBn = toBigNumberOrZero(amountStr);\n const meta = metadata[typedAssetId];\n if (amountBn.isZero()) {\n continue; // skip zeros early to reduce map pressure\n }\n\n const existing = out.get(typedAssetId);\n if (existing) {\n existing.amount = existing.amount.plus(amountBn);\n continue;\n }\n\n out.set(typedAssetId, {\n amount: amountBn,\n decimals: meta?.decimals,\n symbol: meta?.symbol,\n name: meta?.name,\n });\n }\n}\n\nexport function getGroupIdForAccount(\n accountTreeState: AccountTreeControllerState,\n accountId: AccountId,\n): string | undefined {\n const wallets = accountTreeState.accountTree?.wallets;\n if (!wallets) {\n return undefined;\n }\n for (const wallet of Object.values(wallets)) {\n for (const [groupId, group] of Object.entries(wallet?.groups ?? {})) {\n const accounts = group?.accounts;\n if (Array.isArray(accounts) && accounts.includes(accountId)) {\n return groupId;\n }\n }\n }\n return undefined;\n}\n\nexport function getInternalAccountsForGroup(\n accountTreeState: AccountTreeControllerState,\n accountsById: AccountsById,\n groupId: string,\n): InternalAccount[] {\n const wallets = accountTreeState.accountTree?.wallets;\n if (!wallets) {\n return [];\n }\n type GroupWithAccounts = { accounts?: AccountId[] };\n const getGroups = (\n walletItem: (typeof wallets)[keyof typeof wallets],\n ): Record<string, GroupWithAccounts> =>\n (walletItem?.groups ?? {}) as Record<string, GroupWithAccounts>;\n\n for (const wallet of Object.values(wallets)) {\n const group = getGroups(wallet)[groupId];\n if (!group?.accounts) {\n continue;\n }\n const result: InternalAccount[] = [];\n for (const id of group.accounts) {\n const acct = accountsById[id];\n if (acct) {\n result.push(acct);\n }\n }\n return result;\n }\n return [];\n}\n\nfunction getAccountIdsInSameGroup(\n accountTreeState: AccountTreeControllerState,\n selectedAccountId: AccountId,\n): AccountId[] | undefined {\n const groupId = getGroupIdForAccount(accountTreeState, selectedAccountId);\n if (!groupId) {\n return undefined;\n }\n const wallets = accountTreeState.accountTree?.wallets;\n if (!wallets) {\n return undefined;\n }\n type GroupWithAccounts = { accounts?: AccountId[] };\n const getGroups = (\n walletItem: (typeof wallets)[keyof typeof wallets],\n ): Record<string, GroupWithAccounts> =>\n (walletItem?.groups ?? {}) as Record<string, GroupWithAccounts>;\n\n for (const wallet of Object.values(wallets)) {\n const group = getGroups(wallet)[groupId];\n if (group?.accounts) {\n return [...group.accounts];\n }\n }\n return undefined;\n}\n\nfunction resolveAccountsToAggregate(args: {\n selectedInternalAccount: InternalAccount;\n accountTreeState?: AccountTreeControllerState;\n internalAccountsOrAccountIds?: InternalAccount[] | AccountId[];\n accountsById?: AccountsById;\n}): AccountLike[] {\n const {\n selectedInternalAccount,\n accountTreeState,\n internalAccountsOrAccountIds,\n accountsById,\n } = args;\n\n if (internalAccountsOrAccountIds && internalAccountsOrAccountIds.length > 0) {\n const first = internalAccountsOrAccountIds[0];\n if (typeof first === 'string') {\n const ids = internalAccountsOrAccountIds as AccountId[];\n const out: AccountLike[] = new Array(ids.length);\n for (let i = 0; i < ids.length; i++) {\n out[i] = { id: ids[i] };\n }\n return out;\n }\n return internalAccountsOrAccountIds as InternalAccount[];\n }\n\n if (\n accountTreeState &&\n accountsById &&\n Object.keys(accountsById).length > 0\n ) {\n const groupId = getGroupIdForAccount(\n accountTreeState,\n selectedInternalAccount.id,\n );\n if (groupId) {\n const groupAccounts = getInternalAccountsForGroup(\n accountTreeState,\n accountsById,\n groupId,\n );\n if (groupAccounts.length > 0) {\n return groupAccounts;\n }\n }\n }\n\n if (accountTreeState) {\n const ids = getAccountIdsInSameGroup(\n accountTreeState,\n selectedInternalAccount.id,\n );\n if (ids && ids.length > 0) {\n const out: AccountLike[] = new Array(ids.length);\n for (let i = 0; i < ids.length; i++) {\n out[i] = { id: ids[i] };\n }\n return out;\n }\n }\n\n return [selectedInternalAccount];\n}\n\n/**\n * Get account ids that belong to a group, reading directly from the account\n * tree. Unlike {@link getInternalAccountsForGroup}, this does not require an\n * `accountsById` lookup map, which makes it convenient for balance aggregation\n * where only the ids are needed.\n *\n * @param accountTreeState - AccountTreeController state.\n * @param groupId - The account group id to look up.\n * @returns The list of account ids in the group, or an empty array.\n */\nexport function getAccountIdsForGroup(\n accountTreeState: AccountTreeControllerState,\n groupId: string,\n): AccountId[] {\n const wallets = accountTreeState.accountTree?.wallets ?? {};\n type GroupWithAccounts = { accounts?: AccountId[] };\n for (const wallet of Object.values(wallets)) {\n const groups = (wallet?.groups ?? {}) as Record<string, GroupWithAccounts>;\n const group = groups[groupId];\n if (group?.accounts) {\n return [...group.accounts];\n }\n }\n return [];\n}\n\n/**\n * Aggregate balances across an explicit list of accounts.\n *\n * This is the core aggregation engine shared by the account- and group-level\n * selectors. It intentionally takes a fully-resolved list of accounts so that\n * callers control account resolution and no \"selected account\" placeholder is\n * required.\n *\n * @param state - AssetsController state slice.\n * @param accounts - Accounts whose balances should be aggregated.\n * @param enabledNetworkMap - Optional map of enabled networks keyed by namespace.\n * @param trace - Optional trace callback for telemetry spans.\n * @returns Aggregated balance entries plus fiat totals when prices are present.\n */\nfunction aggregateBalances(\n state: AssetsControllerState,\n accounts: AccountLike[],\n enabledNetworkMap?: EnabledNetworkMap,\n trace?: TraceCallback,\n): AggregatedBalanceForAccount {\n const startTime = trace ? performance.now() : 0;\n const { assetsBalance, assetsInfo, assetPreferences, assetsPrice } = state;\n\n const metadata = (assetsInfo ?? {}) as Record<Caip19AssetId, AssetMetadata>;\n\n const hasPrices =\n Boolean(assetsPrice) &&\n ((): boolean => {\n for (const key in assetsPrice) {\n if (Object.prototype.hasOwnProperty.call(assetsPrice, key)) {\n return true;\n }\n }\n return false;\n })();\n\n const accountsToAggregate = accounts;\n\n const assetInfoCache = makeAssetInfoCache();\n const merged = new Map<Caip19AssetId, AggRow>();\n\n for (const account of accountsToAggregate) {\n const accountId = account.id;\n const accountBalances =\n assetsBalance?.[accountId] ?? ({} as Record<Caip19AssetId, AssetBalance>);\n\n mergeBalancesIntoMap({\n out: merged,\n accountBalances,\n metadata,\n assetPreferences,\n enabledNetworkMap,\n assetInfoCache,\n });\n }\n\n // Materialize entries once\n const entries: AggregatedBalanceEntry[] = new Array(merged.size);\n let idx = 0;\n\n // If prices exist, compute totals in a single pass over merged.\n let totalBalanceInFiat = 0;\n let previousTotalInFiat = 0;\n let weightedNumerator = 0;\n\n for (const [assetId, row] of merged.entries()) {\n const { amount } = row;\n const entry: AggregatedBalanceEntry = {\n assetId,\n amount: amount.toFixed(),\n ...(row.decimals === undefined ? {} : { decimals: row.decimals }),\n ...(row.symbol === undefined ? {} : { symbol: row.symbol }),\n ...(row.name === undefined ? {} : { name: row.name }),\n };\n entries[idx] = entry;\n idx += 1;\n\n if (hasPrices) {\n const { price, pricePercentChange1d } = getPriceDatumFast(\n assetsPrice,\n assetId,\n );\n const contribution = amount.multipliedBy(price).toNumber();\n if (contribution > 0) {\n totalBalanceInFiat += contribution;\n weightedNumerator += contribution * pricePercentChange1d;\n\n // A -100% move makes the denominator zero; treat it as flat so the\n // previous total never goes to infinity.\n const denom = Number((1 + pricePercentChange1d / 100).toFixed(8));\n previousTotalInFiat +=\n denom === 0 ? contribution : contribution / denom;\n }\n }\n }\n\n if (trace) {\n const durationMs = performance.now() - startTime;\n const uniqueNetworks = new Set<CaipChainId>();\n for (const assetId of merged.keys()) {\n const info = getAssetInfo(assetInfoCache, assetId);\n uniqueNetworks.add(info.chainId);\n }\n emitAggregatedBalanceSelectorTrace(trace, durationMs, {\n asset_count: merged.size,\n network_count: uniqueNetworks.size,\n account_count: accountsToAggregate.length,\n });\n }\n\n if (hasPrices) {\n const pricePercentChange1d =\n totalBalanceInFiat > 0 ? weightedNumerator / totalBalanceInFiat : 0;\n return {\n entries,\n totalBalanceInFiat,\n pricePercentChange1d,\n previousTotalInFiat,\n };\n }\n\n return { entries };\n}\n\n/**\n * Aggregate balances for an explicit list of account ids.\n *\n * Prefer this over {@link getAggregatedBalanceForAccount} when the set of\n * accounts to aggregate is already known (e.g. all accounts in a group), since\n * it does not require a \"selected account\" and therefore avoids the brittle\n * account-resolution heuristics.\n *\n * @param state - AssetsController state slice.\n * @param accountIds - Account ids whose balances should be aggregated.\n * @param enabledNetworkMap - Optional map of enabled networks keyed by namespace.\n * @param trace - Optional trace callback for telemetry spans.\n * @returns Aggregated balance entries plus fiat totals when prices are present.\n */\nexport function getAggregatedBalanceForAccountIds(\n state: AssetsControllerState,\n accountIds: AccountId[],\n enabledNetworkMap?: EnabledNetworkMap,\n trace?: TraceCallback,\n): AggregatedBalanceForAccount {\n const accounts: AccountLike[] = accountIds.map((id) => ({ id }));\n return aggregateBalances(state, accounts, enabledNetworkMap, trace);\n}\n\nexport function getAggregatedBalanceForAccount(\n state: AssetsControllerState,\n selectedInternalAccount: InternalAccount,\n enabledNetworkMap?: EnabledNetworkMap,\n accountTreeState?: AccountTreeControllerState,\n internalAccountsOrAccountIds?: InternalAccount[] | AccountId[],\n accountsById?: AccountsById,\n trace?: TraceCallback,\n): AggregatedBalanceForAccount {\n const accountsToAggregate = resolveAccountsToAggregate({\n selectedInternalAccount,\n accountTreeState,\n internalAccountsOrAccountIds,\n accountsById,\n });\n return aggregateBalances(\n state,\n accountsToAggregate,\n enabledNetworkMap,\n trace,\n );\n}\n\n// ============================================================================\n// WALLET- AND GROUP-LEVEL BALANCE CALCULATIONS\n// ============================================================================\n\n/**\n * Default user currency used when {@link AssetsControllerState.selectedCurrency}\n * is not set.\n */\nconst DEFAULT_USER_CURRENCY = 'usd';\n\nexport type AccountGroupBalance = {\n walletId: string;\n groupId: string;\n totalBalanceInUserCurrency: number;\n userCurrency: string;\n};\n\nexport type WalletBalance = {\n walletId: string;\n groups: Record<string, AccountGroupBalance>;\n totalBalanceInUserCurrency: number;\n userCurrency: string;\n};\n\nexport type AllWalletsBalance = {\n wallets: Record<string, WalletBalance>;\n totalBalanceInUserCurrency: number;\n userCurrency: string;\n};\n\nexport type BalanceChangePeriod = '1d' | '7d' | '30d';\n\nexport type BalanceChangeResult = {\n period: BalanceChangePeriod;\n currentTotalInUserCurrency: number;\n previousTotalInUserCurrency: number;\n amountChangeInUserCurrency: number;\n percentChange: number;\n userCurrency: string;\n};\n\n/**\n * Resolve the user currency for a balance calculation, falling back to a\n * sensible default when the controller has no selected currency.\n *\n * @param assetsControllerState - AssetsController state slice.\n * @returns The user currency code.\n */\nfunction getUserCurrency(assetsControllerState: AssetsControllerState): string {\n return assetsControllerState.selectedCurrency ?? DEFAULT_USER_CURRENCY;\n}\n\n/**\n * Resolve the current and previous totals for a change calculation.\n *\n * The AssetsController state only exposes a 1d price change, so non-`1d`\n * periods produce a zeroed change (previous equals current).\n *\n * @param totalBalanceInFiat - Aggregated current balance in user currency.\n * @param previousTotalInFiat - Aggregated prior balance summed per asset.\n * @param period - Period to compute the change for.\n * @returns The current and previous totals in user currency.\n */\nfunction getCurrentAndPrevious(\n totalBalanceInFiat: number,\n previousTotalInFiat: number,\n period: BalanceChangePeriod,\n): { current: number; previous: number } {\n const current = totalBalanceInFiat;\n const previous = period === '1d' ? previousTotalInFiat : current;\n return { current, previous };\n}\n\n/**\n * Build a {@link BalanceChangeResult} from current/previous totals.\n *\n * @param current - Current total in user currency.\n * @param previous - Previous total in user currency.\n * @param period - Period the change was computed for.\n * @param userCurrency - User currency code.\n * @returns The change result with delta and percent change.\n */\nfunction buildBalanceChangeResult(\n current: number,\n previous: number,\n period: BalanceChangePeriod,\n userCurrency: string,\n): BalanceChangeResult {\n const amountChange = current - previous;\n const percentChange = previous === 0 ? 0 : (amountChange / previous) * 100;\n return {\n period,\n currentTotalInUserCurrency: Number(current.toFixed(8)),\n previousTotalInUserCurrency: Number(previous.toFixed(8)),\n amountChangeInUserCurrency: Number(amountChange.toFixed(8)),\n percentChange: Number(percentChange.toFixed(8)),\n userCurrency,\n };\n}\n\n/**\n * Calculate aggregated balances for all wallets and groups.\n *\n * Mirrors the legacy `@metamask/assets-controllers` `calculateBalanceForAllWallets`\n * output shape, but sources every group total from the unified AssetsController\n * state via {@link getAggregatedBalanceForAccountIds}. The account tree is walked\n * to aggregate each group individually.\n *\n * @param assetsControllerState - AssetsController state slice.\n * @param accountTreeState - AccountTreeController state.\n * @param enabledNetworkMap - Map of enabled networks keyed by namespace.\n * @param trace - Optional trace callback forwarded to the aggregation selector.\n * @returns Aggregated balances for all wallets and groups.\n */\nexport function calculateBalanceForAllWallets(\n assetsControllerState: AssetsControllerState,\n accountTreeState: AccountTreeControllerState,\n enabledNetworkMap?: EnabledNetworkMap,\n trace?: TraceCallback,\n): AllWalletsBalance {\n const startTime = performance.now();\n const userCurrency = getUserCurrency(assetsControllerState);\n const wallets: AllWalletsBalance['wallets'] = {};\n let totalBalanceInUserCurrency = 0;\n let groupCount = 0;\n\n type WalletWithGroups = { groups?: Record<string, unknown> };\n for (const [walletId, wallet] of Object.entries(\n accountTreeState.accountTree?.wallets ?? {},\n )) {\n const walletBalance: WalletBalance = {\n walletId,\n groups: {},\n totalBalanceInUserCurrency: 0,\n userCurrency,\n };\n\n const groups = (wallet as WalletWithGroups)?.groups ?? {};\n for (const groupId of Object.keys(groups)) {\n groupCount += 1;\n const accountIds = getAccountIdsForGroup(accountTreeState, groupId);\n // Do not forward `trace` per group — that created one root Sentry span\n // per account group and hit rate limits. Emit a single span below.\n const { totalBalanceInFiat = 0 } = getAggregatedBalanceForAccountIds(\n assetsControllerState,\n accountIds,\n enabledNetworkMap,\n );\n\n walletBalance.groups[groupId] = {\n walletId,\n groupId,\n totalBalanceInUserCurrency: totalBalanceInFiat,\n userCurrency,\n };\n walletBalance.totalBalanceInUserCurrency += totalBalanceInFiat;\n }\n\n wallets[walletId] = walletBalance;\n totalBalanceInUserCurrency += walletBalance.totalBalanceInUserCurrency;\n }\n\n if (trace) {\n emitAggregatedBalanceSelectorTrace(trace, performance.now() - startTime, {\n wallet_count: Object.keys(wallets).length,\n group_count: groupCount,\n });\n }\n\n return { wallets, totalBalanceInUserCurrency, userCurrency };\n}\n\n/**\n * Calculate the portfolio value change for a single account group and period.\n *\n * @param assetsControllerState - AssetsController state slice.\n * @param accountTreeState - AccountTreeController state.\n * @param groupId - Account group id to compute the change for.\n * @param period - Change period (`1d` | `7d` | `30d`).\n * @param enabledNetworkMap - Map of enabled networks keyed by namespace.\n * @param trace - Optional trace callback forwarded to the aggregation selector.\n * @returns The change result for the requested period.\n */\nexport function calculateBalanceChangeForAccountGroup(\n assetsControllerState: AssetsControllerState,\n accountTreeState: AccountTreeControllerState,\n groupId: string,\n period: BalanceChangePeriod,\n enabledNetworkMap?: EnabledNetworkMap,\n trace?: TraceCallback,\n): BalanceChangeResult {\n const userCurrency = getUserCurrency(assetsControllerState);\n const accountIds = getAccountIdsForGroup(accountTreeState, groupId);\n const { totalBalanceInFiat = 0, previousTotalInFiat = totalBalanceInFiat } =\n getAggregatedBalanceForAccountIds(\n assetsControllerState,\n accountIds,\n enabledNetworkMap,\n trace,\n );\n\n const { current, previous } = getCurrentAndPrevious(\n totalBalanceInFiat,\n previousTotalInFiat,\n period,\n );\n\n return buildBalanceChangeResult(current, previous, period, userCurrency);\n}\n"]}
1
+ {"version":3,"file":"balance.cjs","sourceRoot":"","sources":["../../src/selectors/balance.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccountTreeControllerState } from '@metamask/account-tree-controller';\nimport { toHex } from '@metamask/controller-utils';\nimport type { TraceCallback } from '@metamask/controller-utils';\nimport type { InternalAccount } from '@metamask/keyring-internal-api';\nimport type { CaipChainId, Hex } from '@metamask/utils';\nimport {\n KnownCaipNamespace,\n isStrictHexString,\n parseCaipAssetType,\n parseCaipChainId,\n} from '@metamask/utils';\nimport BigNumberJS from 'bignumber.js';\n\nimport type { AssetsControllerState } from '../AssetsController.js';\nimport type {\n AccountId,\n AssetBalance,\n AssetMetadata,\n AssetPreferences,\n Caip19AssetId,\n} from '../types.js';\nimport { emitTrace, withTrace } from '../utils/trace.js';\n\n// ============================================================================\n// TRACE NAMES — used in Sentry spans (search these strings in Discover)\n// ============================================================================\n/** Parent span that nests {@link TRACE_AGGREGATED_BALANCE_SELECTOR}. */\nconst TRACE_AGGREGATED_BALANCE = 'AggregatedBalance';\nconst TRACE_AGGREGATED_BALANCE_SELECTOR = 'AggregatedBalanceSelector';\n\n/**\n * Emit `AggregatedBalanceSelector` as a **subspan** under an `AggregatedBalance`\n * parent (via `parentContext`).\n *\n * Work is timed outside the callback (selectors are sync), so we backdate\n * `startTime` via `duration_ms` — MetaMask's Sentry adapter promotes numeric\n * tags to measurements, which Spans dashboard widgets chart as `duration_ms`.\n *\n * @param trace - Trace callback from the client.\n * @param durationMs - Measured compute time in milliseconds.\n * @param data - Additional span attributes (counts, etc.).\n */\nfunction emitAggregatedBalanceSelectorTrace(\n trace: TraceCallback,\n durationMs: number,\n data: Record<string, number | string | boolean>,\n): void {\n const spanData = {\n duration_ms: durationMs,\n ...data,\n };\n\n withTrace({\n name: TRACE_AGGREGATED_BALANCE,\n trace,\n data: spanData,\n fn: async (parentContext) => {\n emitTrace({\n name: TRACE_AGGREGATED_BALANCE_SELECTOR,\n trace,\n data: spanData,\n parentContext,\n });\n },\n }).catch(() => {\n // Telemetry failure must not break.\n });\n}\n\nexport type EnabledNetworkMap =\n | Record<string, Record<string, boolean>>\n | undefined;\n\nexport type AggregatedBalanceEntry = {\n assetId: Caip19AssetId;\n amount: string;\n decimals?: number;\n symbol?: string;\n name?: string;\n};\n\nexport type AggregatedBalanceForAccount = {\n entries: AggregatedBalanceEntry[];\n totalBalanceInFiat?: number;\n pricePercentChange1d?: number;\n previousTotalInFiat?: number;\n};\n\ntype AccountLike = { id: AccountId };\nexport type AccountsById = Record<AccountId, InternalAccount>;\n\ntype PriceDatum = { price: number; pricePercentChange1d: number };\n\nconst ZERO_PRICE: PriceDatum = { price: 0, pricePercentChange1d: 0 };\n\nconst getAmountFromBalance = (balance: AssetBalance): string => {\n if (typeof balance === 'object' && balance !== null && 'amount' in balance) {\n const { amount } = balance as { amount?: unknown };\n return typeof amount === 'string' ? amount : '0';\n }\n return '0';\n};\n\nconst toBigNumberOrZero = (value: string): BigNumberJS => {\n const parsed = new BigNumberJS(value);\n return parsed.isNaN() || !parsed.isFinite() ? new BigNumberJS(0) : parsed;\n};\n\nconst getPriceDatumFast = (\n assetsPrice: AssetsControllerState['assetsPrice'] | undefined,\n assetId: Caip19AssetId,\n): PriceDatum => {\n const raw = assetsPrice?.[assetId];\n if (!raw || typeof raw !== 'object') {\n return ZERO_PRICE;\n }\n const { price, pricePercentChange1d } = raw as {\n price?: unknown;\n pricePercentChange1d?: unknown;\n };\n return {\n price: typeof price === 'number' && Number.isFinite(price) ? price : 0,\n pricePercentChange1d:\n typeof pricePercentChange1d === 'number' &&\n Number.isFinite(pricePercentChange1d)\n ? pricePercentChange1d\n : 0,\n };\n};\n\n/**\n * Enablement lookup optimized:\n * - no array allocations\n * - early returns\n *\n * @param map - Enabled network map (namespace -> storageKey -> boolean)\n * @param id - Chain id as Hex or CAIP-2\n * @returns True if the chain is enabled or map is undefined\n */\nconst isChainEnabledByMap = (\n map: EnabledNetworkMap,\n id: Hex | CaipChainId,\n): boolean => {\n if (!map) {\n return true;\n }\n\n if (isStrictHexString(id)) {\n return Boolean(map[KnownCaipNamespace.Eip155]?.[id]);\n }\n\n const { namespace, reference } = parseCaipChainId(id);\n const namespaceMap = map[namespace];\n if (!namespaceMap) {\n return false;\n }\n\n // exact CAIP-2 id / reference\n if (namespaceMap[id] || namespaceMap[reference]) {\n return true;\n }\n\n // EVM: hex storage key\n if (namespace === KnownCaipNamespace.Eip155) {\n try {\n const storageKey = toHex(reference);\n return Boolean(namespaceMap[storageKey]);\n } catch {\n const parsedRef = Number.parseInt(reference, 10);\n if (Number.isFinite(parsedRef)) {\n return Boolean(namespaceMap[`0x${parsedRef.toString(16)}`]);\n }\n }\n }\n\n return false;\n};\n\n/**\n * Cache parsing results per assetId to avoid parseCaipAssetType/parseCaipChainId\n * being called multiple times across filtering + grouping + pricing.\n */\ntype AssetParseInfo = {\n chainId: CaipChainId;\n isEvm: boolean;\n};\n\nconst makeAssetInfoCache = (): Map<Caip19AssetId, AssetParseInfo> =>\n new Map<Caip19AssetId, AssetParseInfo>();\n\nconst getAssetInfo = (\n cache: Map<Caip19AssetId, AssetParseInfo>,\n assetId: Caip19AssetId,\n): AssetParseInfo => {\n const cached = cache.get(assetId);\n if (cached) {\n return cached;\n }\n const { chainId } = parseCaipAssetType(assetId);\n const { namespace } = parseCaipChainId(chainId);\n const info = { chainId, isEvm: namespace === KnownCaipNamespace.Eip155 };\n cache.set(assetId, info);\n return info;\n};\n\n/**\n * Merge across accounts without building per-account entries arrays.\n * Uses BigNumber for amount accumulation to avoid float precision loss.\n */\ntype AggRow = {\n amount: BigNumberJS;\n decimals?: number;\n symbol?: string;\n name?: string;\n};\n\nfunction mergeBalancesIntoMap(args: {\n out: Map<Caip19AssetId, AggRow>;\n accountBalances: Record<Caip19AssetId, AssetBalance>;\n metadata: Record<Caip19AssetId, AssetMetadata>;\n assetPreferences?: Record<Caip19AssetId, AssetPreferences>;\n enabledNetworkMap?: EnabledNetworkMap;\n assetInfoCache: Map<Caip19AssetId, AssetParseInfo>;\n}): void {\n const {\n out,\n accountBalances,\n metadata,\n assetPreferences,\n enabledNetworkMap,\n assetInfoCache,\n } = args;\n\n for (const assetId in accountBalances) {\n if (!Object.prototype.hasOwnProperty.call(accountBalances, assetId)) {\n continue;\n }\n const typedAssetId = assetId as Caip19AssetId;\n\n if (assetPreferences?.[typedAssetId]?.hidden) {\n continue;\n }\n\n // Balances without a matching `assetsInfo` entry are not renderable\n // (no symbol/name/decimals), so they are excluded from aggregation.\n const meta = metadata[typedAssetId];\n if (!meta) {\n continue;\n }\n\n const info = getAssetInfo(assetInfoCache, typedAssetId);\n if (!isChainEnabledByMap(enabledNetworkMap, info.chainId)) {\n continue;\n }\n\n const amountStr = getAmountFromBalance(accountBalances[typedAssetId]);\n const amountBn = toBigNumberOrZero(amountStr);\n if (amountBn.isZero()) {\n continue; // skip zeros early to reduce map pressure\n }\n\n const existing = out.get(typedAssetId);\n if (existing) {\n existing.amount = existing.amount.plus(amountBn);\n continue;\n }\n\n out.set(typedAssetId, {\n amount: amountBn,\n decimals: meta.decimals,\n symbol: meta.symbol,\n name: meta.name,\n });\n }\n}\n\nexport function getGroupIdForAccount(\n accountTreeState: AccountTreeControllerState,\n accountId: AccountId,\n): string | undefined {\n const wallets = accountTreeState.accountTree?.wallets;\n if (!wallets) {\n return undefined;\n }\n for (const wallet of Object.values(wallets)) {\n for (const [groupId, group] of Object.entries(wallet?.groups ?? {})) {\n const accounts = group?.accounts;\n if (Array.isArray(accounts) && accounts.includes(accountId)) {\n return groupId;\n }\n }\n }\n return undefined;\n}\n\nexport function getInternalAccountsForGroup(\n accountTreeState: AccountTreeControllerState,\n accountsById: AccountsById,\n groupId: string,\n): InternalAccount[] {\n const wallets = accountTreeState.accountTree?.wallets;\n if (!wallets) {\n return [];\n }\n type GroupWithAccounts = { accounts?: AccountId[] };\n const getGroups = (\n walletItem: (typeof wallets)[keyof typeof wallets],\n ): Record<string, GroupWithAccounts> =>\n (walletItem?.groups ?? {}) as Record<string, GroupWithAccounts>;\n\n for (const wallet of Object.values(wallets)) {\n const group = getGroups(wallet)[groupId];\n if (!group?.accounts) {\n continue;\n }\n const result: InternalAccount[] = [];\n for (const id of group.accounts) {\n const acct = accountsById[id];\n if (acct) {\n result.push(acct);\n }\n }\n return result;\n }\n return [];\n}\n\nfunction getAccountIdsInSameGroup(\n accountTreeState: AccountTreeControllerState,\n selectedAccountId: AccountId,\n): AccountId[] | undefined {\n const groupId = getGroupIdForAccount(accountTreeState, selectedAccountId);\n if (!groupId) {\n return undefined;\n }\n const wallets = accountTreeState.accountTree?.wallets;\n if (!wallets) {\n return undefined;\n }\n type GroupWithAccounts = { accounts?: AccountId[] };\n const getGroups = (\n walletItem: (typeof wallets)[keyof typeof wallets],\n ): Record<string, GroupWithAccounts> =>\n (walletItem?.groups ?? {}) as Record<string, GroupWithAccounts>;\n\n for (const wallet of Object.values(wallets)) {\n const group = getGroups(wallet)[groupId];\n if (group?.accounts) {\n return [...group.accounts];\n }\n }\n return undefined;\n}\n\nfunction resolveAccountsToAggregate(args: {\n selectedInternalAccount: InternalAccount;\n accountTreeState?: AccountTreeControllerState;\n internalAccountsOrAccountIds?: InternalAccount[] | AccountId[];\n accountsById?: AccountsById;\n}): AccountLike[] {\n const {\n selectedInternalAccount,\n accountTreeState,\n internalAccountsOrAccountIds,\n accountsById,\n } = args;\n\n if (internalAccountsOrAccountIds && internalAccountsOrAccountIds.length > 0) {\n const first = internalAccountsOrAccountIds[0];\n if (typeof first === 'string') {\n const ids = internalAccountsOrAccountIds as AccountId[];\n const out: AccountLike[] = new Array(ids.length);\n for (let i = 0; i < ids.length; i++) {\n out[i] = { id: ids[i] };\n }\n return out;\n }\n return internalAccountsOrAccountIds as InternalAccount[];\n }\n\n if (\n accountTreeState &&\n accountsById &&\n Object.keys(accountsById).length > 0\n ) {\n const groupId = getGroupIdForAccount(\n accountTreeState,\n selectedInternalAccount.id,\n );\n if (groupId) {\n const groupAccounts = getInternalAccountsForGroup(\n accountTreeState,\n accountsById,\n groupId,\n );\n if (groupAccounts.length > 0) {\n return groupAccounts;\n }\n }\n }\n\n if (accountTreeState) {\n const ids = getAccountIdsInSameGroup(\n accountTreeState,\n selectedInternalAccount.id,\n );\n if (ids && ids.length > 0) {\n const out: AccountLike[] = new Array(ids.length);\n for (let i = 0; i < ids.length; i++) {\n out[i] = { id: ids[i] };\n }\n return out;\n }\n }\n\n return [selectedInternalAccount];\n}\n\n/**\n * Get account ids that belong to a group, reading directly from the account\n * tree. Unlike {@link getInternalAccountsForGroup}, this does not require an\n * `accountsById` lookup map, which makes it convenient for balance aggregation\n * where only the ids are needed.\n *\n * @param accountTreeState - AccountTreeController state.\n * @param groupId - The account group id to look up.\n * @returns The list of account ids in the group, or an empty array.\n */\nexport function getAccountIdsForGroup(\n accountTreeState: AccountTreeControllerState,\n groupId: string,\n): AccountId[] {\n const wallets = accountTreeState.accountTree?.wallets ?? {};\n type GroupWithAccounts = { accounts?: AccountId[] };\n for (const wallet of Object.values(wallets)) {\n const groups = (wallet?.groups ?? {}) as Record<string, GroupWithAccounts>;\n const group = groups[groupId];\n if (group?.accounts) {\n return [...group.accounts];\n }\n }\n return [];\n}\n\n/**\n * Aggregate balances across an explicit list of accounts.\n *\n * This is the core aggregation engine shared by the account- and group-level\n * selectors. It intentionally takes a fully-resolved list of accounts so that\n * callers control account resolution and no \"selected account\" placeholder is\n * required.\n *\n * @param state - AssetsController state slice.\n * @param accounts - Accounts whose balances should be aggregated.\n * @param enabledNetworkMap - Optional map of enabled networks keyed by namespace.\n * @param trace - Optional trace callback for telemetry spans.\n * @returns Aggregated balance entries plus fiat totals when prices are present.\n */\nfunction aggregateBalances(\n state: AssetsControllerState,\n accounts: AccountLike[],\n enabledNetworkMap?: EnabledNetworkMap,\n trace?: TraceCallback,\n): AggregatedBalanceForAccount {\n const startTime = trace ? performance.now() : 0;\n const { assetsBalance, assetsInfo, assetPreferences, assetsPrice } = state;\n\n const metadata = (assetsInfo ?? {}) as Record<Caip19AssetId, AssetMetadata>;\n\n const hasPrices =\n Boolean(assetsPrice) &&\n ((): boolean => {\n for (const key in assetsPrice) {\n if (Object.prototype.hasOwnProperty.call(assetsPrice, key)) {\n return true;\n }\n }\n return false;\n })();\n\n const accountsToAggregate = accounts;\n\n const assetInfoCache = makeAssetInfoCache();\n const merged = new Map<Caip19AssetId, AggRow>();\n\n for (const account of accountsToAggregate) {\n const accountId = account.id;\n const accountBalances =\n assetsBalance?.[accountId] ?? ({} as Record<Caip19AssetId, AssetBalance>);\n\n mergeBalancesIntoMap({\n out: merged,\n accountBalances,\n metadata,\n assetPreferences,\n enabledNetworkMap,\n assetInfoCache,\n });\n }\n\n // Materialize entries once\n const entries: AggregatedBalanceEntry[] = new Array(merged.size);\n let idx = 0;\n\n // If prices exist, compute totals in a single pass over merged.\n let totalBalanceInFiat = 0;\n let previousTotalInFiat = 0;\n let weightedNumerator = 0;\n\n for (const [assetId, row] of merged.entries()) {\n const { amount } = row;\n const entry: AggregatedBalanceEntry = {\n assetId,\n amount: amount.toFixed(),\n ...(row.decimals === undefined ? {} : { decimals: row.decimals }),\n ...(row.symbol === undefined ? {} : { symbol: row.symbol }),\n ...(row.name === undefined ? {} : { name: row.name }),\n };\n entries[idx] = entry;\n idx += 1;\n\n if (hasPrices) {\n const { price, pricePercentChange1d } = getPriceDatumFast(\n assetsPrice,\n assetId,\n );\n const contribution = amount.multipliedBy(price).toNumber();\n if (contribution > 0) {\n totalBalanceInFiat += contribution;\n weightedNumerator += contribution * pricePercentChange1d;\n\n // A -100% move makes the denominator zero; treat it as flat so the\n // previous total never goes to infinity.\n const denom = Number((1 + pricePercentChange1d / 100).toFixed(8));\n previousTotalInFiat +=\n denom === 0 ? contribution : contribution / denom;\n }\n }\n }\n\n if (trace) {\n const durationMs = performance.now() - startTime;\n const uniqueNetworks = new Set<CaipChainId>();\n for (const assetId of merged.keys()) {\n const info = getAssetInfo(assetInfoCache, assetId);\n uniqueNetworks.add(info.chainId);\n }\n emitAggregatedBalanceSelectorTrace(trace, durationMs, {\n asset_count: merged.size,\n network_count: uniqueNetworks.size,\n account_count: accountsToAggregate.length,\n });\n }\n\n if (hasPrices) {\n const pricePercentChange1d =\n totalBalanceInFiat > 0 ? weightedNumerator / totalBalanceInFiat : 0;\n return {\n entries,\n totalBalanceInFiat,\n pricePercentChange1d,\n previousTotalInFiat,\n };\n }\n\n return { entries };\n}\n\n/**\n * Aggregate balances for an explicit list of account ids.\n *\n * Prefer this over {@link getAggregatedBalanceForAccount} when the set of\n * accounts to aggregate is already known (e.g. all accounts in a group), since\n * it does not require a \"selected account\" and therefore avoids the brittle\n * account-resolution heuristics.\n *\n * @param state - AssetsController state slice.\n * @param accountIds - Account ids whose balances should be aggregated.\n * @param enabledNetworkMap - Optional map of enabled networks keyed by namespace.\n * @param trace - Optional trace callback for telemetry spans.\n * @returns Aggregated balance entries plus fiat totals when prices are present.\n */\nexport function getAggregatedBalanceForAccountIds(\n state: AssetsControllerState,\n accountIds: AccountId[],\n enabledNetworkMap?: EnabledNetworkMap,\n trace?: TraceCallback,\n): AggregatedBalanceForAccount {\n const accounts: AccountLike[] = accountIds.map((id) => ({ id }));\n return aggregateBalances(state, accounts, enabledNetworkMap, trace);\n}\n\nexport function getAggregatedBalanceForAccount(\n state: AssetsControllerState,\n selectedInternalAccount: InternalAccount,\n enabledNetworkMap?: EnabledNetworkMap,\n accountTreeState?: AccountTreeControllerState,\n internalAccountsOrAccountIds?: InternalAccount[] | AccountId[],\n accountsById?: AccountsById,\n trace?: TraceCallback,\n): AggregatedBalanceForAccount {\n const accountsToAggregate = resolveAccountsToAggregate({\n selectedInternalAccount,\n accountTreeState,\n internalAccountsOrAccountIds,\n accountsById,\n });\n return aggregateBalances(\n state,\n accountsToAggregate,\n enabledNetworkMap,\n trace,\n );\n}\n\n// ============================================================================\n// WALLET- AND GROUP-LEVEL BALANCE CALCULATIONS\n// ============================================================================\n\n/**\n * Default user currency used when {@link AssetsControllerState.selectedCurrency}\n * is not set.\n */\nconst DEFAULT_USER_CURRENCY = 'usd';\n\nexport type AccountGroupBalance = {\n walletId: string;\n groupId: string;\n totalBalanceInUserCurrency: number;\n userCurrency: string;\n};\n\nexport type WalletBalance = {\n walletId: string;\n groups: Record<string, AccountGroupBalance>;\n totalBalanceInUserCurrency: number;\n userCurrency: string;\n};\n\nexport type AllWalletsBalance = {\n wallets: Record<string, WalletBalance>;\n totalBalanceInUserCurrency: number;\n userCurrency: string;\n};\n\nexport type BalanceChangePeriod = '1d' | '7d' | '30d';\n\nexport type BalanceChangeResult = {\n period: BalanceChangePeriod;\n currentTotalInUserCurrency: number;\n previousTotalInUserCurrency: number;\n amountChangeInUserCurrency: number;\n percentChange: number;\n userCurrency: string;\n};\n\n/**\n * Resolve the user currency for a balance calculation, falling back to a\n * sensible default when the controller has no selected currency.\n *\n * @param assetsControllerState - AssetsController state slice.\n * @returns The user currency code.\n */\nfunction getUserCurrency(assetsControllerState: AssetsControllerState): string {\n return assetsControllerState.selectedCurrency ?? DEFAULT_USER_CURRENCY;\n}\n\n/**\n * Resolve the current and previous totals for a change calculation.\n *\n * The AssetsController state only exposes a 1d price change, so non-`1d`\n * periods produce a zeroed change (previous equals current).\n *\n * @param totalBalanceInFiat - Aggregated current balance in user currency.\n * @param previousTotalInFiat - Aggregated prior balance summed per asset.\n * @param period - Period to compute the change for.\n * @returns The current and previous totals in user currency.\n */\nfunction getCurrentAndPrevious(\n totalBalanceInFiat: number,\n previousTotalInFiat: number,\n period: BalanceChangePeriod,\n): { current: number; previous: number } {\n const current = totalBalanceInFiat;\n const previous = period === '1d' ? previousTotalInFiat : current;\n return { current, previous };\n}\n\n/**\n * Build a {@link BalanceChangeResult} from current/previous totals.\n *\n * @param current - Current total in user currency.\n * @param previous - Previous total in user currency.\n * @param period - Period the change was computed for.\n * @param userCurrency - User currency code.\n * @returns The change result with delta and percent change.\n */\nfunction buildBalanceChangeResult(\n current: number,\n previous: number,\n period: BalanceChangePeriod,\n userCurrency: string,\n): BalanceChangeResult {\n const amountChange = current - previous;\n const percentChange = previous === 0 ? 0 : (amountChange / previous) * 100;\n return {\n period,\n currentTotalInUserCurrency: Number(current.toFixed(8)),\n previousTotalInUserCurrency: Number(previous.toFixed(8)),\n amountChangeInUserCurrency: Number(amountChange.toFixed(8)),\n percentChange: Number(percentChange.toFixed(8)),\n userCurrency,\n };\n}\n\n/**\n * Calculate aggregated balances for all wallets and groups.\n *\n * Mirrors the legacy `@metamask/assets-controllers` `calculateBalanceForAllWallets`\n * output shape, but sources every group total from the unified AssetsController\n * state via {@link getAggregatedBalanceForAccountIds}. The account tree is walked\n * to aggregate each group individually.\n *\n * @param assetsControllerState - AssetsController state slice.\n * @param accountTreeState - AccountTreeController state.\n * @param enabledNetworkMap - Map of enabled networks keyed by namespace.\n * @param trace - Optional trace callback forwarded to the aggregation selector.\n * @returns Aggregated balances for all wallets and groups.\n */\nexport function calculateBalanceForAllWallets(\n assetsControllerState: AssetsControllerState,\n accountTreeState: AccountTreeControllerState,\n enabledNetworkMap?: EnabledNetworkMap,\n trace?: TraceCallback,\n): AllWalletsBalance {\n const startTime = performance.now();\n const userCurrency = getUserCurrency(assetsControllerState);\n const wallets: AllWalletsBalance['wallets'] = {};\n let totalBalanceInUserCurrency = 0;\n let groupCount = 0;\n\n type WalletWithGroups = { groups?: Record<string, unknown> };\n for (const [walletId, wallet] of Object.entries(\n accountTreeState.accountTree?.wallets ?? {},\n )) {\n const walletBalance: WalletBalance = {\n walletId,\n groups: {},\n totalBalanceInUserCurrency: 0,\n userCurrency,\n };\n\n const groups = (wallet as WalletWithGroups)?.groups ?? {};\n for (const groupId of Object.keys(groups)) {\n groupCount += 1;\n const accountIds = getAccountIdsForGroup(accountTreeState, groupId);\n // Do not forward `trace` per group — that created one root Sentry span\n // per account group and hit rate limits. Emit a single span below.\n const { totalBalanceInFiat = 0 } = getAggregatedBalanceForAccountIds(\n assetsControllerState,\n accountIds,\n enabledNetworkMap,\n );\n\n walletBalance.groups[groupId] = {\n walletId,\n groupId,\n totalBalanceInUserCurrency: totalBalanceInFiat,\n userCurrency,\n };\n walletBalance.totalBalanceInUserCurrency += totalBalanceInFiat;\n }\n\n wallets[walletId] = walletBalance;\n totalBalanceInUserCurrency += walletBalance.totalBalanceInUserCurrency;\n }\n\n if (trace) {\n emitAggregatedBalanceSelectorTrace(trace, performance.now() - startTime, {\n wallet_count: Object.keys(wallets).length,\n group_count: groupCount,\n });\n }\n\n return { wallets, totalBalanceInUserCurrency, userCurrency };\n}\n\n/**\n * Calculate the portfolio value change for a single account group and period.\n *\n * @param assetsControllerState - AssetsController state slice.\n * @param accountTreeState - AccountTreeController state.\n * @param groupId - Account group id to compute the change for.\n * @param period - Change period (`1d` | `7d` | `30d`).\n * @param enabledNetworkMap - Map of enabled networks keyed by namespace.\n * @param trace - Optional trace callback forwarded to the aggregation selector.\n * @returns The change result for the requested period.\n */\nexport function calculateBalanceChangeForAccountGroup(\n assetsControllerState: AssetsControllerState,\n accountTreeState: AccountTreeControllerState,\n groupId: string,\n period: BalanceChangePeriod,\n enabledNetworkMap?: EnabledNetworkMap,\n trace?: TraceCallback,\n): BalanceChangeResult {\n const userCurrency = getUserCurrency(assetsControllerState);\n const accountIds = getAccountIdsForGroup(accountTreeState, groupId);\n const { totalBalanceInFiat = 0, previousTotalInFiat = totalBalanceInFiat } =\n getAggregatedBalanceForAccountIds(\n assetsControllerState,\n accountIds,\n enabledNetworkMap,\n trace,\n );\n\n const { current, previous } = getCurrentAndPrevious(\n totalBalanceInFiat,\n previousTotalInFiat,\n period,\n );\n\n return buildBalanceChangeResult(current, previous, period, userCurrency);\n}\n"]}