@talismn/balances 0.0.0-pr2075-20250710091134 → 0.0.0-pr2075-20250710124714

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.
@@ -24,7 +24,7 @@ export declare class BalancesProvider {
24
24
  private getStoredMiniMetadatas$;
25
25
  private getDefaultMiniMetadatas$;
26
26
  private getStoredBalances;
27
- private cleanupAddressesByTokenId;
27
+ private cleanupAddressesByTokenId$;
28
28
  }
29
29
  export declare const isAddressCompatibleWithNetwork: (network: Network, address: Address) => boolean;
30
30
  export {};
@@ -6202,6 +6202,7 @@ const POOL = new PQueue__default.default({
6202
6202
  const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, specVersion) => {
6203
6203
  if (specVersion === undefined) specVersion = await getSpecVersion(chainConnector, networkId);
6204
6204
  const cacheKey = getCacheKey(networkId, specVersion);
6205
+ if (CACHE.has(cacheKey)) return CACHE.get(cacheKey);
6205
6206
  const pResult = POOL.add(() => fetchMiniMetadatas(chainConnector, chaindataProvider, networkId, specVersion));
6206
6207
 
6207
6208
  // keep the results in cache (unless call fails) as observables call this function a lot of times
@@ -6215,15 +6216,13 @@ const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, sp
6215
6216
  });
6216
6217
  }
6217
6218
  };
6218
- const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion, signal) => {
6219
+ const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion) => {
6219
6220
  const start = performance.now();
6220
6221
  log.info("[miniMetadata] fetching minimetadatas for %s", chainId);
6221
6222
  try {
6222
6223
  const network = await chaindataProvider.getNetworkById(chainId, "polkadot");
6223
6224
  if (!network) throw new Error(`Network ${chainId} not found in chaindataProvider`);
6224
- signal?.throwIfAborted();
6225
6225
  const metadataRpc = await getMetadataRpc(chainConnector, chainId);
6226
- signal?.throwIfAborted();
6227
6226
  return Promise.all(BALANCE_MODULES.filter(m => m.platform === "polkadot").map(mod => mod.getMiniMetadata({
6228
6227
  networkId: chainId,
6229
6228
  metadataRpc,
@@ -6256,9 +6255,9 @@ class BalancesProvider {
6256
6255
  balances,
6257
6256
  miniMetadatas
6258
6257
  }) => ({
6259
- balances: lodashEs.values(balances).filter(util.isNotNil),
6260
- miniMetadatas: lodashEs.values(miniMetadatas).filter(util.isNotNil)
6261
- })));
6258
+ balances: lodashEs.values(balances).filter(util.isNotNil).sort(sortByBalanceId),
6259
+ miniMetadatas: lodashEs.values(miniMetadatas).filter(util.isNotNil).sort(sortByMiniMetadataId)
6260
+ }), rxjs.shareReplay(1)));
6262
6261
  }
6263
6262
  get storedMiniMetadataMapById$() {
6264
6263
  return this.#storage.pipe(rxjs.map(storage => lodashEs.keyBy(storage.miniMetadatas, m => m.id)), rxjs.distinctUntilChanged(lodashEs.isEqual), rxjs.shareReplay(1));
@@ -6266,39 +6265,35 @@ class BalancesProvider {
6266
6265
 
6267
6266
  // this is the only public method
6268
6267
  getBalances$(addressesByTokenId) {
6269
- // TODO move the getSharedObservable caching down to this.getNetworkBalances$ to prevent network-level subscriptions to restart when enabling/disabling other networks
6270
- // this will require addressesByTokenId arg to be normalized/sorted so the cache key can be compared properly, seems a bit random atm
6271
- return util.getSharedObservable("BalancesProvider.getBalances$", addressesByTokenId, () => {
6272
- return this.cleanupAddressesByTokenId(addressesByTokenId).pipe(rxjs.map(
6273
- // split by network
6274
- addressesByTokenId => lodashEs.toPairs(addressesByTokenId).reduce((acc, [tokenId, addresses]) => {
6275
- const networkId = chaindataProvider.parseTokenId(tokenId).networkId;
6276
- if (!acc[networkId]) acc[networkId] = {};
6277
- acc[networkId][tokenId] = addresses;
6278
- return acc;
6279
- }, {})), rxjs.switchMap(addressesByTokenIdByNetworkId =>
6280
- // fetch balances and start a 30s timer to mark the whole subscription live after 30s
6281
- rxjs.combineLatest({
6282
- isStale: rxjs.timer(30_000).pipe(rxjs.map(() => true), rxjs.startWith(false)),
6283
- results: rxjs.combineLatest(lodashEs.toPairs(addressesByTokenIdByNetworkId).map(([networkId]) => this.getNetworkBalances$(networkId, addressesByTokenIdByNetworkId[networkId])))
6284
- })), rxjs.map(
6285
- // combine
6286
- ({
6287
- isStale,
6288
- results
6289
- }) => ({
6290
- status: !isStale && results.some(({
6291
- status
6292
- }) => status === "initialising") ? "initialising" : "live",
6293
- balances: results.flatMap(result => result.balances.map(b => isStale && b.status !== "live" ? {
6294
- ...b,
6295
- status: "stale"
6296
- } : b)).sort((a, b) => getBalanceId(a).localeCompare(getBalanceId(b)))
6297
- })), rxjs.startWith({
6298
- status: "initialising",
6299
- balances: this.getStoredBalances(addressesByTokenId)
6300
- }), rxjs.distinctUntilChanged(lodashEs.isEqual));
6301
- });
6268
+ return this.cleanupAddressesByTokenId$(addressesByTokenId).pipe(rxjs.map(
6269
+ // split by network
6270
+ addressesByTokenId => lodashEs.toPairs(addressesByTokenId).reduce((acc, [tokenId, addresses]) => {
6271
+ const networkId = chaindataProvider.parseTokenId(tokenId).networkId;
6272
+ if (!acc[networkId]) acc[networkId] = {};
6273
+ acc[networkId][tokenId] = addresses;
6274
+ return acc;
6275
+ }, {})), rxjs.switchMap(addressesByTokenIdByNetworkId =>
6276
+ // fetch balances and start a 30s timer to mark the whole subscription live after 30s
6277
+ rxjs.combineLatest({
6278
+ isStale: rxjs.timer(30_000).pipe(rxjs.map(() => true), rxjs.startWith(false)),
6279
+ results: rxjs.combineLatest(lodashEs.toPairs(addressesByTokenIdByNetworkId).map(([networkId]) => this.getNetworkBalances$(networkId, addressesByTokenIdByNetworkId[networkId])))
6280
+ })), rxjs.map(
6281
+ // combine
6282
+ ({
6283
+ isStale,
6284
+ results
6285
+ }) => ({
6286
+ status: !isStale && results.some(({
6287
+ status
6288
+ }) => status === "initialising") ? "initialising" : "live",
6289
+ balances: results.flatMap(result => result.balances.map(b => isStale && b.status !== "live" ? {
6290
+ ...b,
6291
+ status: "stale"
6292
+ } : b)).sort(sortByBalanceId)
6293
+ })), rxjs.startWith({
6294
+ status: "initialising",
6295
+ balances: this.getStoredBalances(addressesByTokenId)
6296
+ }), rxjs.distinctUntilChanged(lodashEs.isEqual));
6302
6297
  }
6303
6298
  fetchBalances(addressesByTokenId) {
6304
6299
  // TODO: better
@@ -6309,84 +6304,95 @@ class BalancesProvider {
6309
6304
  }) => balances)));
6310
6305
  }
6311
6306
  getNetworkBalances$(networkId, addressesByTokenId) {
6312
- const network$ = this.#chaindataProvider.getNetworkById$(networkId);
6313
- const tokensMapById$ = this.#chaindataProvider.getTokensMapById$();
6314
- const miniMetadatas$ = this.getNetworkMiniMetadatas$(networkId);
6315
- return rxjs.combineLatest([network$, miniMetadatas$, tokensMapById$]).pipe(rxjs.switchMap(([network, miniMetadatas, tokensMapById]) => {
6316
- const tokensAndAddresses = lodashEs.toPairs(addressesByTokenId).map(([tokenId, addresses]) => [tokensMapById[tokenId], addresses]);
6317
- return rxjs.combineLatest(BALANCE_MODULES.filter(mod => mod.platform === network?.platform).map(mod => {
6318
- const tokensWithAddresses = tokensAndAddresses.filter(([token]) => token.type === mod.type);
6319
- const moduleAddressesByTokenId = lodashEs.fromPairs(tokensWithAddresses.map(([token, addresses]) => [token.id, addresses]));
6320
- const miniMetadata = miniMetadatas.find(m => m.source === mod.type);
6321
-
6322
- // all balance ids expected in result set
6323
- const balanceIds = lodashEs.toPairs(moduleAddressesByTokenId).flatMap(([tokenId, addresses]) => addresses.map(address => getBalanceId({
6324
- tokenId,
6325
- address
6326
- })));
6327
- const initValue = {
6328
- status: "initialising",
6329
- balances: this.getStoredBalances(moduleAddressesByTokenId)
6330
- };
6307
+ return util.getSharedObservable(`BalancesProvider.getNetorkBalances$`, {
6308
+ networkId,
6309
+ addressesByTokenId
6310
+ }, () => {
6311
+ const network$ = this.#chaindataProvider.getNetworkById$(networkId);
6312
+ const tokensMapById$ = this.#chaindataProvider.getTokensMapById$();
6313
+ const miniMetadatas$ = this.getNetworkMiniMetadatas$(networkId);
6314
+ return rxjs.combineLatest([network$, miniMetadatas$, tokensMapById$]).pipe(rxjs.switchMap(([network, miniMetadatas, tokensMapById]) => {
6315
+ const tokensAndAddresses = lodashEs.toPairs(addressesByTokenId).map(([tokenId, addresses]) => [tokensMapById[tokenId], addresses]);
6316
+ return rxjs.combineLatest(BALANCE_MODULES.filter(mod => mod.platform === network?.platform).map(mod => {
6317
+ const tokensWithAddresses = tokensAndAddresses.filter(([token]) => token.type === mod.type);
6318
+ const moduleAddressesByTokenId = lodashEs.fromPairs(tokensWithAddresses.map(([token, addresses]) => [token.id, addresses]));
6319
+ const miniMetadata = miniMetadatas.find(m => m.source === mod.type);
6320
+
6321
+ // all balance ids expected in result set
6322
+ const balanceIds = lodashEs.toPairs(moduleAddressesByTokenId).flatMap(([tokenId, addresses]) => addresses.map(address => getBalanceId({
6323
+ tokenId,
6324
+ address
6325
+ })));
6326
+ const initValue = {
6327
+ status: "initialising",
6328
+ balances: this.getStoredBalances(moduleAddressesByTokenId)
6329
+ };
6331
6330
 
6332
- // updating storage has to be done on a per-module basis, so we know which balances can be deleted
6333
- const updateStorage = results => {
6334
- if (results.status !== "live") return;
6335
- const storage = this.#storage.getValue();
6336
- const balances = lodashEs.assign({}, storage.balances,
6337
- // delete all balances expected in the result set. because if they are not present it means they are empty.
6338
- lodashEs.fromPairs(balanceIds.map(balanceId => [balanceId, undefined])), lodashEs.keyBy(
6339
- // storage balances must have status "cache", because they are used as start value when initialising subsequent subscriptions
6340
- results.balances.map(b => ({
6341
- ...b,
6342
- status: "cache"
6343
- })), b => getBalanceId(b)));
6344
- this.#storage.next(lodashEs.assign({}, storage, {
6345
- balances
6346
- }));
6347
- };
6348
- switch (mod.platform) {
6349
- case "ethereum":
6350
- {
6351
- if (!this.#chainConnectors.evm) return rxjs.of(initValue);
6331
+ // updating storage has to be done on a per-module basis, so we know which balances can be deleted
6332
+ const updateStorage = results => {
6333
+ if (results.status !== "live") return;
6334
+ const storage = this.#storage.getValue();
6335
+ const balances = lodashEs.assign({}, storage.balances,
6336
+ // delete all balances expected in the result set. because if they are not present it means they are empty.
6337
+ lodashEs.fromPairs(balanceIds.map(balanceId => [balanceId, undefined])), lodashEs.keyBy(
6338
+ // storage balances must have status "cache", because they are used as start value when initialising subsequent subscriptions
6339
+ results.balances.map(b => ({
6340
+ ...b,
6341
+ status: "cache"
6342
+ })), b => getBalanceId(b)));
6343
+ this.#storage.next(lodashEs.assign({}, storage, {
6344
+ balances
6345
+ }));
6346
+ };
6347
+ switch (mod.platform) {
6348
+ case "ethereum":
6349
+ {
6350
+ if (!this.#chainConnectors.evm) return rxjs.of(initValue);
6351
+ return mod.subscribeBalances({
6352
+ networkId,
6353
+ tokensWithAddresses,
6354
+ connector: this.#chainConnectors.evm
6355
+ }).pipe(rxjs.map(results => ({
6356
+ status: "live",
6357
+ // exclude zero balances
6358
+ balances: results.success.filter(b => new Balance(b).total.planck > 0n)
6359
+ })), rxjs.tap(updateStorage), rxjs.startWith(initValue));
6360
+ }
6361
+ case "polkadot":
6362
+ if (!this.#chainConnectors.substrate || !miniMetadata) {
6363
+ log.debug("[balances] no substrate connector or miniMetadata for polkadot", mod.type);
6364
+ return rxjs.of(initValue);
6365
+ }
6352
6366
  return mod.subscribeBalances({
6353
6367
  networkId,
6354
6368
  tokensWithAddresses,
6355
- connector: this.#chainConnectors.evm
6369
+ connector: this.#chainConnectors.substrate,
6370
+ miniMetadata: miniMetadata
6356
6371
  }).pipe(rxjs.map(results => ({
6357
6372
  status: "live",
6358
6373
  // exclude zero balances
6359
6374
  balances: results.success.filter(b => new Balance(b).total.planck > 0n)
6360
6375
  })), rxjs.tap(updateStorage), rxjs.startWith(initValue));
6361
- }
6362
- case "polkadot":
6363
- if (!this.#chainConnectors.substrate || !miniMetadata) {
6364
- log.debug("[balances] no substrate connector or miniMetadata for polkadot", mod.type);
6365
- return rxjs.of(initValue);
6366
- }
6367
- return mod.subscribeBalances({
6368
- networkId,
6369
- tokensWithAddresses,
6370
- connector: this.#chainConnectors.substrate,
6371
- miniMetadata: miniMetadata
6372
- }).pipe(rxjs.map(results => ({
6373
- status: "live",
6374
- // exclude zero balances
6375
- balances: results.success.filter(b => new Balance(b).total.planck > 0n)
6376
- })), rxjs.tap(updateStorage), rxjs.startWith(initValue));
6377
- }
6378
- }));
6379
- }), rxjs.map(results => {
6380
- return {
6381
- status: results.some(({
6382
- status
6383
- }) => status === "initialising") ? "initialising" : "live",
6384
- balances: results.flatMap(result => result.balances)
6385
- };
6386
- }), rxjs.startWith({
6387
- status: "initialising",
6388
- balances: this.getStoredBalances(addressesByTokenId)
6389
- }));
6376
+ }
6377
+ }));
6378
+ }), rxjs.map(results => {
6379
+ return {
6380
+ status: results.some(({
6381
+ status
6382
+ }) => status === "initialising") ? "initialising" : "live",
6383
+ balances: results.flatMap(result => result.balances).sort(sortByBalanceId)
6384
+ };
6385
+ }), rxjs.startWith({
6386
+ status: "initialising",
6387
+ balances: this.getStoredBalances(addressesByTokenId)
6388
+ }), rxjs.distinctUntilChanged(lodashEs.isEqual),
6389
+ // shareReplay + keepAlive allow for subscription to not restart as long as the inputs dont change
6390
+ // for example, if another network is enabled/disabled
6391
+ rxjs.shareReplay({
6392
+ refCount: true,
6393
+ bufferSize: 1
6394
+ }), util.keepAlive(2_000));
6395
+ });
6390
6396
  }
6391
6397
  getNetworkMiniMetadatas$(networkId) {
6392
6398
  return this.#chaindataProvider.getNetworkById$(networkId).pipe(rxjs.switchMap(network => chaindataProvider.isNetworkDot(network) && this.#chainConnectors.substrate ? rxjs.from(getSpecVersion(this.#chainConnectors.substrate, networkId)).pipe(rxjs.switchMap(specVersion => this.getMiniMetadatas$(networkId, specVersion))) : rxjs.of([])));
@@ -6421,13 +6427,17 @@ class BalancesProvider {
6421
6427
  miniMetadatas
6422
6428
  }));
6423
6429
  }));
6424
- }));
6430
+ }),
6431
+ // emit only when mini metadata changes, as a change here would restart all subscriptions for the network
6432
+ rxjs.distinctUntilChanged(lodashEs.isEqual));
6425
6433
  }
6426
6434
  getStoredMiniMetadatas$(miniMetadataIds) {
6427
6435
  return this.storedMiniMetadataMapById$.pipe(rxjs.map(mapById => {
6428
6436
  const miniMetadatas = miniMetadataIds.map(id => mapById[id]);
6429
6437
  return miniMetadatas.length && miniMetadatas.every(util.isTruthy) ? miniMetadatas : null;
6430
- }));
6438
+ }),
6439
+ // source changes very often
6440
+ rxjs.distinctUntilChanged(lodashEs.isEqual));
6431
6441
  }
6432
6442
  getDefaultMiniMetadatas$(miniMetadataIds) {
6433
6443
  return this.#chaindataProvider.miniMetadatasMapById$.pipe(rxjs.map(mapById => {
@@ -6440,9 +6450,9 @@ class BalancesProvider {
6440
6450
  return balanceDefs.map(([tokenId, address]) => this.#storage.value.balances[getBalanceId({
6441
6451
  address,
6442
6452
  tokenId
6443
- })]).filter(util.isNotNil).sort((a, b) => getBalanceId(a).localeCompare(getBalanceId(b)));
6453
+ })]).filter(util.isNotNil).sort(sortByBalanceId);
6444
6454
  }
6445
- cleanupAddressesByTokenId(addressesByTokenId) {
6455
+ cleanupAddressesByTokenId$(addressesByTokenId) {
6446
6456
  return this.#chaindataProvider.getNetworksMapById$().pipe(rxjs.map(networksById => {
6447
6457
  return lodashEs.fromPairs(lodashEs.toPairs(addressesByTokenId).map(([tokenId, addresses]) => {
6448
6458
  const networkId = chaindataProvider.parseTokenId(tokenId).networkId;
@@ -6463,6 +6473,8 @@ const isAddressCompatibleWithNetwork = (network, address) => {
6463
6473
  throw new Error("Unsupported network platform");
6464
6474
  }
6465
6475
  };
6476
+ const sortByBalanceId = (a, b) => getBalanceId(a).localeCompare(getBalanceId(b));
6477
+ const sortByMiniMetadataId = (a, b) => a.id.localeCompare(b.id);
6466
6478
 
6467
6479
  Object.defineProperty(exports, "MINIMETADATA_VERSION", {
6468
6480
  enumerable: true,
@@ -6202,6 +6202,7 @@ const POOL = new PQueue__default.default({
6202
6202
  const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, specVersion) => {
6203
6203
  if (specVersion === undefined) specVersion = await getSpecVersion(chainConnector, networkId);
6204
6204
  const cacheKey = getCacheKey(networkId, specVersion);
6205
+ if (CACHE.has(cacheKey)) return CACHE.get(cacheKey);
6205
6206
  const pResult = POOL.add(() => fetchMiniMetadatas(chainConnector, chaindataProvider, networkId, specVersion));
6206
6207
 
6207
6208
  // keep the results in cache (unless call fails) as observables call this function a lot of times
@@ -6215,15 +6216,13 @@ const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, sp
6215
6216
  });
6216
6217
  }
6217
6218
  };
6218
- const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion, signal) => {
6219
+ const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion) => {
6219
6220
  const start = performance.now();
6220
6221
  log.info("[miniMetadata] fetching minimetadatas for %s", chainId);
6221
6222
  try {
6222
6223
  const network = await chaindataProvider.getNetworkById(chainId, "polkadot");
6223
6224
  if (!network) throw new Error(`Network ${chainId} not found in chaindataProvider`);
6224
- signal?.throwIfAborted();
6225
6225
  const metadataRpc = await getMetadataRpc(chainConnector, chainId);
6226
- signal?.throwIfAborted();
6227
6226
  return Promise.all(BALANCE_MODULES.filter(m => m.platform === "polkadot").map(mod => mod.getMiniMetadata({
6228
6227
  networkId: chainId,
6229
6228
  metadataRpc,
@@ -6256,9 +6255,9 @@ class BalancesProvider {
6256
6255
  balances,
6257
6256
  miniMetadatas
6258
6257
  }) => ({
6259
- balances: lodashEs.values(balances).filter(util.isNotNil),
6260
- miniMetadatas: lodashEs.values(miniMetadatas).filter(util.isNotNil)
6261
- })));
6258
+ balances: lodashEs.values(balances).filter(util.isNotNil).sort(sortByBalanceId),
6259
+ miniMetadatas: lodashEs.values(miniMetadatas).filter(util.isNotNil).sort(sortByMiniMetadataId)
6260
+ }), rxjs.shareReplay(1)));
6262
6261
  }
6263
6262
  get storedMiniMetadataMapById$() {
6264
6263
  return this.#storage.pipe(rxjs.map(storage => lodashEs.keyBy(storage.miniMetadatas, m => m.id)), rxjs.distinctUntilChanged(lodashEs.isEqual), rxjs.shareReplay(1));
@@ -6266,39 +6265,35 @@ class BalancesProvider {
6266
6265
 
6267
6266
  // this is the only public method
6268
6267
  getBalances$(addressesByTokenId) {
6269
- // TODO move the getSharedObservable caching down to this.getNetworkBalances$ to prevent network-level subscriptions to restart when enabling/disabling other networks
6270
- // this will require addressesByTokenId arg to be normalized/sorted so the cache key can be compared properly, seems a bit random atm
6271
- return util.getSharedObservable("BalancesProvider.getBalances$", addressesByTokenId, () => {
6272
- return this.cleanupAddressesByTokenId(addressesByTokenId).pipe(rxjs.map(
6273
- // split by network
6274
- addressesByTokenId => lodashEs.toPairs(addressesByTokenId).reduce((acc, [tokenId, addresses]) => {
6275
- const networkId = chaindataProvider.parseTokenId(tokenId).networkId;
6276
- if (!acc[networkId]) acc[networkId] = {};
6277
- acc[networkId][tokenId] = addresses;
6278
- return acc;
6279
- }, {})), rxjs.switchMap(addressesByTokenIdByNetworkId =>
6280
- // fetch balances and start a 30s timer to mark the whole subscription live after 30s
6281
- rxjs.combineLatest({
6282
- isStale: rxjs.timer(30_000).pipe(rxjs.map(() => true), rxjs.startWith(false)),
6283
- results: rxjs.combineLatest(lodashEs.toPairs(addressesByTokenIdByNetworkId).map(([networkId]) => this.getNetworkBalances$(networkId, addressesByTokenIdByNetworkId[networkId])))
6284
- })), rxjs.map(
6285
- // combine
6286
- ({
6287
- isStale,
6288
- results
6289
- }) => ({
6290
- status: !isStale && results.some(({
6291
- status
6292
- }) => status === "initialising") ? "initialising" : "live",
6293
- balances: results.flatMap(result => result.balances.map(b => isStale && b.status !== "live" ? {
6294
- ...b,
6295
- status: "stale"
6296
- } : b)).sort((a, b) => getBalanceId(a).localeCompare(getBalanceId(b)))
6297
- })), rxjs.startWith({
6298
- status: "initialising",
6299
- balances: this.getStoredBalances(addressesByTokenId)
6300
- }), rxjs.distinctUntilChanged(lodashEs.isEqual));
6301
- });
6268
+ return this.cleanupAddressesByTokenId$(addressesByTokenId).pipe(rxjs.map(
6269
+ // split by network
6270
+ addressesByTokenId => lodashEs.toPairs(addressesByTokenId).reduce((acc, [tokenId, addresses]) => {
6271
+ const networkId = chaindataProvider.parseTokenId(tokenId).networkId;
6272
+ if (!acc[networkId]) acc[networkId] = {};
6273
+ acc[networkId][tokenId] = addresses;
6274
+ return acc;
6275
+ }, {})), rxjs.switchMap(addressesByTokenIdByNetworkId =>
6276
+ // fetch balances and start a 30s timer to mark the whole subscription live after 30s
6277
+ rxjs.combineLatest({
6278
+ isStale: rxjs.timer(30_000).pipe(rxjs.map(() => true), rxjs.startWith(false)),
6279
+ results: rxjs.combineLatest(lodashEs.toPairs(addressesByTokenIdByNetworkId).map(([networkId]) => this.getNetworkBalances$(networkId, addressesByTokenIdByNetworkId[networkId])))
6280
+ })), rxjs.map(
6281
+ // combine
6282
+ ({
6283
+ isStale,
6284
+ results
6285
+ }) => ({
6286
+ status: !isStale && results.some(({
6287
+ status
6288
+ }) => status === "initialising") ? "initialising" : "live",
6289
+ balances: results.flatMap(result => result.balances.map(b => isStale && b.status !== "live" ? {
6290
+ ...b,
6291
+ status: "stale"
6292
+ } : b)).sort(sortByBalanceId)
6293
+ })), rxjs.startWith({
6294
+ status: "initialising",
6295
+ balances: this.getStoredBalances(addressesByTokenId)
6296
+ }), rxjs.distinctUntilChanged(lodashEs.isEqual));
6302
6297
  }
6303
6298
  fetchBalances(addressesByTokenId) {
6304
6299
  // TODO: better
@@ -6309,84 +6304,95 @@ class BalancesProvider {
6309
6304
  }) => balances)));
6310
6305
  }
6311
6306
  getNetworkBalances$(networkId, addressesByTokenId) {
6312
- const network$ = this.#chaindataProvider.getNetworkById$(networkId);
6313
- const tokensMapById$ = this.#chaindataProvider.getTokensMapById$();
6314
- const miniMetadatas$ = this.getNetworkMiniMetadatas$(networkId);
6315
- return rxjs.combineLatest([network$, miniMetadatas$, tokensMapById$]).pipe(rxjs.switchMap(([network, miniMetadatas, tokensMapById]) => {
6316
- const tokensAndAddresses = lodashEs.toPairs(addressesByTokenId).map(([tokenId, addresses]) => [tokensMapById[tokenId], addresses]);
6317
- return rxjs.combineLatest(BALANCE_MODULES.filter(mod => mod.platform === network?.platform).map(mod => {
6318
- const tokensWithAddresses = tokensAndAddresses.filter(([token]) => token.type === mod.type);
6319
- const moduleAddressesByTokenId = lodashEs.fromPairs(tokensWithAddresses.map(([token, addresses]) => [token.id, addresses]));
6320
- const miniMetadata = miniMetadatas.find(m => m.source === mod.type);
6321
-
6322
- // all balance ids expected in result set
6323
- const balanceIds = lodashEs.toPairs(moduleAddressesByTokenId).flatMap(([tokenId, addresses]) => addresses.map(address => getBalanceId({
6324
- tokenId,
6325
- address
6326
- })));
6327
- const initValue = {
6328
- status: "initialising",
6329
- balances: this.getStoredBalances(moduleAddressesByTokenId)
6330
- };
6307
+ return util.getSharedObservable(`BalancesProvider.getNetorkBalances$`, {
6308
+ networkId,
6309
+ addressesByTokenId
6310
+ }, () => {
6311
+ const network$ = this.#chaindataProvider.getNetworkById$(networkId);
6312
+ const tokensMapById$ = this.#chaindataProvider.getTokensMapById$();
6313
+ const miniMetadatas$ = this.getNetworkMiniMetadatas$(networkId);
6314
+ return rxjs.combineLatest([network$, miniMetadatas$, tokensMapById$]).pipe(rxjs.switchMap(([network, miniMetadatas, tokensMapById]) => {
6315
+ const tokensAndAddresses = lodashEs.toPairs(addressesByTokenId).map(([tokenId, addresses]) => [tokensMapById[tokenId], addresses]);
6316
+ return rxjs.combineLatest(BALANCE_MODULES.filter(mod => mod.platform === network?.platform).map(mod => {
6317
+ const tokensWithAddresses = tokensAndAddresses.filter(([token]) => token.type === mod.type);
6318
+ const moduleAddressesByTokenId = lodashEs.fromPairs(tokensWithAddresses.map(([token, addresses]) => [token.id, addresses]));
6319
+ const miniMetadata = miniMetadatas.find(m => m.source === mod.type);
6320
+
6321
+ // all balance ids expected in result set
6322
+ const balanceIds = lodashEs.toPairs(moduleAddressesByTokenId).flatMap(([tokenId, addresses]) => addresses.map(address => getBalanceId({
6323
+ tokenId,
6324
+ address
6325
+ })));
6326
+ const initValue = {
6327
+ status: "initialising",
6328
+ balances: this.getStoredBalances(moduleAddressesByTokenId)
6329
+ };
6331
6330
 
6332
- // updating storage has to be done on a per-module basis, so we know which balances can be deleted
6333
- const updateStorage = results => {
6334
- if (results.status !== "live") return;
6335
- const storage = this.#storage.getValue();
6336
- const balances = lodashEs.assign({}, storage.balances,
6337
- // delete all balances expected in the result set. because if they are not present it means they are empty.
6338
- lodashEs.fromPairs(balanceIds.map(balanceId => [balanceId, undefined])), lodashEs.keyBy(
6339
- // storage balances must have status "cache", because they are used as start value when initialising subsequent subscriptions
6340
- results.balances.map(b => ({
6341
- ...b,
6342
- status: "cache"
6343
- })), b => getBalanceId(b)));
6344
- this.#storage.next(lodashEs.assign({}, storage, {
6345
- balances
6346
- }));
6347
- };
6348
- switch (mod.platform) {
6349
- case "ethereum":
6350
- {
6351
- if (!this.#chainConnectors.evm) return rxjs.of(initValue);
6331
+ // updating storage has to be done on a per-module basis, so we know which balances can be deleted
6332
+ const updateStorage = results => {
6333
+ if (results.status !== "live") return;
6334
+ const storage = this.#storage.getValue();
6335
+ const balances = lodashEs.assign({}, storage.balances,
6336
+ // delete all balances expected in the result set. because if they are not present it means they are empty.
6337
+ lodashEs.fromPairs(balanceIds.map(balanceId => [balanceId, undefined])), lodashEs.keyBy(
6338
+ // storage balances must have status "cache", because they are used as start value when initialising subsequent subscriptions
6339
+ results.balances.map(b => ({
6340
+ ...b,
6341
+ status: "cache"
6342
+ })), b => getBalanceId(b)));
6343
+ this.#storage.next(lodashEs.assign({}, storage, {
6344
+ balances
6345
+ }));
6346
+ };
6347
+ switch (mod.platform) {
6348
+ case "ethereum":
6349
+ {
6350
+ if (!this.#chainConnectors.evm) return rxjs.of(initValue);
6351
+ return mod.subscribeBalances({
6352
+ networkId,
6353
+ tokensWithAddresses,
6354
+ connector: this.#chainConnectors.evm
6355
+ }).pipe(rxjs.map(results => ({
6356
+ status: "live",
6357
+ // exclude zero balances
6358
+ balances: results.success.filter(b => new Balance(b).total.planck > 0n)
6359
+ })), rxjs.tap(updateStorage), rxjs.startWith(initValue));
6360
+ }
6361
+ case "polkadot":
6362
+ if (!this.#chainConnectors.substrate || !miniMetadata) {
6363
+ log.debug("[balances] no substrate connector or miniMetadata for polkadot", mod.type);
6364
+ return rxjs.of(initValue);
6365
+ }
6352
6366
  return mod.subscribeBalances({
6353
6367
  networkId,
6354
6368
  tokensWithAddresses,
6355
- connector: this.#chainConnectors.evm
6369
+ connector: this.#chainConnectors.substrate,
6370
+ miniMetadata: miniMetadata
6356
6371
  }).pipe(rxjs.map(results => ({
6357
6372
  status: "live",
6358
6373
  // exclude zero balances
6359
6374
  balances: results.success.filter(b => new Balance(b).total.planck > 0n)
6360
6375
  })), rxjs.tap(updateStorage), rxjs.startWith(initValue));
6361
- }
6362
- case "polkadot":
6363
- if (!this.#chainConnectors.substrate || !miniMetadata) {
6364
- log.debug("[balances] no substrate connector or miniMetadata for polkadot", mod.type);
6365
- return rxjs.of(initValue);
6366
- }
6367
- return mod.subscribeBalances({
6368
- networkId,
6369
- tokensWithAddresses,
6370
- connector: this.#chainConnectors.substrate,
6371
- miniMetadata: miniMetadata
6372
- }).pipe(rxjs.map(results => ({
6373
- status: "live",
6374
- // exclude zero balances
6375
- balances: results.success.filter(b => new Balance(b).total.planck > 0n)
6376
- })), rxjs.tap(updateStorage), rxjs.startWith(initValue));
6377
- }
6378
- }));
6379
- }), rxjs.map(results => {
6380
- return {
6381
- status: results.some(({
6382
- status
6383
- }) => status === "initialising") ? "initialising" : "live",
6384
- balances: results.flatMap(result => result.balances)
6385
- };
6386
- }), rxjs.startWith({
6387
- status: "initialising",
6388
- balances: this.getStoredBalances(addressesByTokenId)
6389
- }));
6376
+ }
6377
+ }));
6378
+ }), rxjs.map(results => {
6379
+ return {
6380
+ status: results.some(({
6381
+ status
6382
+ }) => status === "initialising") ? "initialising" : "live",
6383
+ balances: results.flatMap(result => result.balances).sort(sortByBalanceId)
6384
+ };
6385
+ }), rxjs.startWith({
6386
+ status: "initialising",
6387
+ balances: this.getStoredBalances(addressesByTokenId)
6388
+ }), rxjs.distinctUntilChanged(lodashEs.isEqual),
6389
+ // shareReplay + keepAlive allow for subscription to not restart as long as the inputs dont change
6390
+ // for example, if another network is enabled/disabled
6391
+ rxjs.shareReplay({
6392
+ refCount: true,
6393
+ bufferSize: 1
6394
+ }), util.keepAlive(2_000));
6395
+ });
6390
6396
  }
6391
6397
  getNetworkMiniMetadatas$(networkId) {
6392
6398
  return this.#chaindataProvider.getNetworkById$(networkId).pipe(rxjs.switchMap(network => chaindataProvider.isNetworkDot(network) && this.#chainConnectors.substrate ? rxjs.from(getSpecVersion(this.#chainConnectors.substrate, networkId)).pipe(rxjs.switchMap(specVersion => this.getMiniMetadatas$(networkId, specVersion))) : rxjs.of([])));
@@ -6421,13 +6427,17 @@ class BalancesProvider {
6421
6427
  miniMetadatas
6422
6428
  }));
6423
6429
  }));
6424
- }));
6430
+ }),
6431
+ // emit only when mini metadata changes, as a change here would restart all subscriptions for the network
6432
+ rxjs.distinctUntilChanged(lodashEs.isEqual));
6425
6433
  }
6426
6434
  getStoredMiniMetadatas$(miniMetadataIds) {
6427
6435
  return this.storedMiniMetadataMapById$.pipe(rxjs.map(mapById => {
6428
6436
  const miniMetadatas = miniMetadataIds.map(id => mapById[id]);
6429
6437
  return miniMetadatas.length && miniMetadatas.every(util.isTruthy) ? miniMetadatas : null;
6430
- }));
6438
+ }),
6439
+ // source changes very often
6440
+ rxjs.distinctUntilChanged(lodashEs.isEqual));
6431
6441
  }
6432
6442
  getDefaultMiniMetadatas$(miniMetadataIds) {
6433
6443
  return this.#chaindataProvider.miniMetadatasMapById$.pipe(rxjs.map(mapById => {
@@ -6440,9 +6450,9 @@ class BalancesProvider {
6440
6450
  return balanceDefs.map(([tokenId, address]) => this.#storage.value.balances[getBalanceId({
6441
6451
  address,
6442
6452
  tokenId
6443
- })]).filter(util.isNotNil).sort((a, b) => getBalanceId(a).localeCompare(getBalanceId(b)));
6453
+ })]).filter(util.isNotNil).sort(sortByBalanceId);
6444
6454
  }
6445
- cleanupAddressesByTokenId(addressesByTokenId) {
6455
+ cleanupAddressesByTokenId$(addressesByTokenId) {
6446
6456
  return this.#chaindataProvider.getNetworksMapById$().pipe(rxjs.map(networksById => {
6447
6457
  return lodashEs.fromPairs(lodashEs.toPairs(addressesByTokenId).map(([tokenId, addresses]) => {
6448
6458
  const networkId = chaindataProvider.parseTokenId(tokenId).networkId;
@@ -6463,6 +6473,8 @@ const isAddressCompatibleWithNetwork = (network, address) => {
6463
6473
  throw new Error("Unsupported network platform");
6464
6474
  }
6465
6475
  };
6476
+ const sortByBalanceId = (a, b) => getBalanceId(a).localeCompare(getBalanceId(b));
6477
+ const sortByMiniMetadataId = (a, b) => a.id.localeCompare(b.id);
6466
6478
 
6467
6479
  Object.defineProperty(exports, "MINIMETADATA_VERSION", {
6468
6480
  enumerable: true,
@@ -1,6 +1,6 @@
1
1
  import { EvmErc20TokenSchema, parseTokenId, parseEvmErc20TokenId, evmErc20TokenId, isTokenOfType, TokenBaseSchema, EvmNativeTokenSchema, evmNativeTokenId, EvmUniswapV2TokenSchema, evmUniswapV2TokenId, SubAssetsTokenSchema, subAssetTokenId, MINIMETADATA_VERSION, SubForeignAssetsTokenSchema, subForeignAssetTokenId, SubHydrationTokenSchema, subHydrationTokenId, SubNativeTokenSchema, subNativeTokenId, SubPsp22TokenSchema, subPsp22TokenId, SubTokensTokenSchema, subTokensTokenId, isNetworkDot } from '@talismn/chaindata-provider';
2
2
  export { MINIMETADATA_VERSION } from '@talismn/chaindata-provider';
3
- import { isEthereumAddress, isNotNil, BigMath, isArrayOf, isBigInt, planckToTokens, isAbortError, getSharedObservable, isTruthy } from '@talismn/util';
3
+ import { isEthereumAddress, isNotNil, BigMath, isArrayOf, isBigInt, planckToTokens, isAbortError, getSharedObservable, keepAlive, isTruthy } from '@talismn/util';
4
4
  import { parseAbi, erc20Abi, getContract, ContractFunctionExecutionError, hexToString, erc20Abi_bytes32, encodeFunctionData, withRetry } from 'viem';
5
5
  import { assign, omit, isEqual, keyBy, keys, uniq, fromPairs, values, toPairs } from 'lodash-es';
6
6
  import z from 'zod/v4';
@@ -6193,6 +6193,7 @@ const POOL = new PQueue({
6193
6193
  const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, specVersion) => {
6194
6194
  if (specVersion === undefined) specVersion = await getSpecVersion(chainConnector, networkId);
6195
6195
  const cacheKey = getCacheKey(networkId, specVersion);
6196
+ if (CACHE.has(cacheKey)) return CACHE.get(cacheKey);
6196
6197
  const pResult = POOL.add(() => fetchMiniMetadatas(chainConnector, chaindataProvider, networkId, specVersion));
6197
6198
 
6198
6199
  // keep the results in cache (unless call fails) as observables call this function a lot of times
@@ -6206,15 +6207,13 @@ const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, sp
6206
6207
  });
6207
6208
  }
6208
6209
  };
6209
- const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion, signal) => {
6210
+ const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion) => {
6210
6211
  const start = performance.now();
6211
6212
  log.info("[miniMetadata] fetching minimetadatas for %s", chainId);
6212
6213
  try {
6213
6214
  const network = await chaindataProvider.getNetworkById(chainId, "polkadot");
6214
6215
  if (!network) throw new Error(`Network ${chainId} not found in chaindataProvider`);
6215
- signal?.throwIfAborted();
6216
6216
  const metadataRpc = await getMetadataRpc(chainConnector, chainId);
6217
- signal?.throwIfAborted();
6218
6217
  return Promise.all(BALANCE_MODULES.filter(m => m.platform === "polkadot").map(mod => mod.getMiniMetadata({
6219
6218
  networkId: chainId,
6220
6219
  metadataRpc,
@@ -6247,9 +6246,9 @@ class BalancesProvider {
6247
6246
  balances,
6248
6247
  miniMetadatas
6249
6248
  }) => ({
6250
- balances: values(balances).filter(isNotNil),
6251
- miniMetadatas: values(miniMetadatas).filter(isNotNil)
6252
- })));
6249
+ balances: values(balances).filter(isNotNil).sort(sortByBalanceId),
6250
+ miniMetadatas: values(miniMetadatas).filter(isNotNil).sort(sortByMiniMetadataId)
6251
+ }), shareReplay(1)));
6253
6252
  }
6254
6253
  get storedMiniMetadataMapById$() {
6255
6254
  return this.#storage.pipe(map(storage => keyBy(storage.miniMetadatas, m => m.id)), distinctUntilChanged(isEqual), shareReplay(1));
@@ -6257,39 +6256,35 @@ class BalancesProvider {
6257
6256
 
6258
6257
  // this is the only public method
6259
6258
  getBalances$(addressesByTokenId) {
6260
- // TODO move the getSharedObservable caching down to this.getNetworkBalances$ to prevent network-level subscriptions to restart when enabling/disabling other networks
6261
- // this will require addressesByTokenId arg to be normalized/sorted so the cache key can be compared properly, seems a bit random atm
6262
- return getSharedObservable("BalancesProvider.getBalances$", addressesByTokenId, () => {
6263
- return this.cleanupAddressesByTokenId(addressesByTokenId).pipe(map(
6264
- // split by network
6265
- addressesByTokenId => toPairs(addressesByTokenId).reduce((acc, [tokenId, addresses]) => {
6266
- const networkId = parseTokenId(tokenId).networkId;
6267
- if (!acc[networkId]) acc[networkId] = {};
6268
- acc[networkId][tokenId] = addresses;
6269
- return acc;
6270
- }, {})), switchMap(addressesByTokenIdByNetworkId =>
6271
- // fetch balances and start a 30s timer to mark the whole subscription live after 30s
6272
- combineLatest({
6273
- isStale: timer(30_000).pipe(map(() => true), startWith(false)),
6274
- results: combineLatest(toPairs(addressesByTokenIdByNetworkId).map(([networkId]) => this.getNetworkBalances$(networkId, addressesByTokenIdByNetworkId[networkId])))
6275
- })), map(
6276
- // combine
6277
- ({
6278
- isStale,
6279
- results
6280
- }) => ({
6281
- status: !isStale && results.some(({
6282
- status
6283
- }) => status === "initialising") ? "initialising" : "live",
6284
- balances: results.flatMap(result => result.balances.map(b => isStale && b.status !== "live" ? {
6285
- ...b,
6286
- status: "stale"
6287
- } : b)).sort((a, b) => getBalanceId(a).localeCompare(getBalanceId(b)))
6288
- })), startWith({
6289
- status: "initialising",
6290
- balances: this.getStoredBalances(addressesByTokenId)
6291
- }), distinctUntilChanged(isEqual));
6292
- });
6259
+ return this.cleanupAddressesByTokenId$(addressesByTokenId).pipe(map(
6260
+ // split by network
6261
+ addressesByTokenId => toPairs(addressesByTokenId).reduce((acc, [tokenId, addresses]) => {
6262
+ const networkId = parseTokenId(tokenId).networkId;
6263
+ if (!acc[networkId]) acc[networkId] = {};
6264
+ acc[networkId][tokenId] = addresses;
6265
+ return acc;
6266
+ }, {})), switchMap(addressesByTokenIdByNetworkId =>
6267
+ // fetch balances and start a 30s timer to mark the whole subscription live after 30s
6268
+ combineLatest({
6269
+ isStale: timer(30_000).pipe(map(() => true), startWith(false)),
6270
+ results: combineLatest(toPairs(addressesByTokenIdByNetworkId).map(([networkId]) => this.getNetworkBalances$(networkId, addressesByTokenIdByNetworkId[networkId])))
6271
+ })), map(
6272
+ // combine
6273
+ ({
6274
+ isStale,
6275
+ results
6276
+ }) => ({
6277
+ status: !isStale && results.some(({
6278
+ status
6279
+ }) => status === "initialising") ? "initialising" : "live",
6280
+ balances: results.flatMap(result => result.balances.map(b => isStale && b.status !== "live" ? {
6281
+ ...b,
6282
+ status: "stale"
6283
+ } : b)).sort(sortByBalanceId)
6284
+ })), startWith({
6285
+ status: "initialising",
6286
+ balances: this.getStoredBalances(addressesByTokenId)
6287
+ }), distinctUntilChanged(isEqual));
6293
6288
  }
6294
6289
  fetchBalances(addressesByTokenId) {
6295
6290
  // TODO: better
@@ -6300,84 +6295,95 @@ class BalancesProvider {
6300
6295
  }) => balances)));
6301
6296
  }
6302
6297
  getNetworkBalances$(networkId, addressesByTokenId) {
6303
- const network$ = this.#chaindataProvider.getNetworkById$(networkId);
6304
- const tokensMapById$ = this.#chaindataProvider.getTokensMapById$();
6305
- const miniMetadatas$ = this.getNetworkMiniMetadatas$(networkId);
6306
- return combineLatest([network$, miniMetadatas$, tokensMapById$]).pipe(switchMap(([network, miniMetadatas, tokensMapById]) => {
6307
- const tokensAndAddresses = toPairs(addressesByTokenId).map(([tokenId, addresses]) => [tokensMapById[tokenId], addresses]);
6308
- return combineLatest(BALANCE_MODULES.filter(mod => mod.platform === network?.platform).map(mod => {
6309
- const tokensWithAddresses = tokensAndAddresses.filter(([token]) => token.type === mod.type);
6310
- const moduleAddressesByTokenId = fromPairs(tokensWithAddresses.map(([token, addresses]) => [token.id, addresses]));
6311
- const miniMetadata = miniMetadatas.find(m => m.source === mod.type);
6312
-
6313
- // all balance ids expected in result set
6314
- const balanceIds = toPairs(moduleAddressesByTokenId).flatMap(([tokenId, addresses]) => addresses.map(address => getBalanceId({
6315
- tokenId,
6316
- address
6317
- })));
6318
- const initValue = {
6319
- status: "initialising",
6320
- balances: this.getStoredBalances(moduleAddressesByTokenId)
6321
- };
6298
+ return getSharedObservable(`BalancesProvider.getNetorkBalances$`, {
6299
+ networkId,
6300
+ addressesByTokenId
6301
+ }, () => {
6302
+ const network$ = this.#chaindataProvider.getNetworkById$(networkId);
6303
+ const tokensMapById$ = this.#chaindataProvider.getTokensMapById$();
6304
+ const miniMetadatas$ = this.getNetworkMiniMetadatas$(networkId);
6305
+ return combineLatest([network$, miniMetadatas$, tokensMapById$]).pipe(switchMap(([network, miniMetadatas, tokensMapById]) => {
6306
+ const tokensAndAddresses = toPairs(addressesByTokenId).map(([tokenId, addresses]) => [tokensMapById[tokenId], addresses]);
6307
+ return combineLatest(BALANCE_MODULES.filter(mod => mod.platform === network?.platform).map(mod => {
6308
+ const tokensWithAddresses = tokensAndAddresses.filter(([token]) => token.type === mod.type);
6309
+ const moduleAddressesByTokenId = fromPairs(tokensWithAddresses.map(([token, addresses]) => [token.id, addresses]));
6310
+ const miniMetadata = miniMetadatas.find(m => m.source === mod.type);
6311
+
6312
+ // all balance ids expected in result set
6313
+ const balanceIds = toPairs(moduleAddressesByTokenId).flatMap(([tokenId, addresses]) => addresses.map(address => getBalanceId({
6314
+ tokenId,
6315
+ address
6316
+ })));
6317
+ const initValue = {
6318
+ status: "initialising",
6319
+ balances: this.getStoredBalances(moduleAddressesByTokenId)
6320
+ };
6322
6321
 
6323
- // updating storage has to be done on a per-module basis, so we know which balances can be deleted
6324
- const updateStorage = results => {
6325
- if (results.status !== "live") return;
6326
- const storage = this.#storage.getValue();
6327
- const balances = assign({}, storage.balances,
6328
- // delete all balances expected in the result set. because if they are not present it means they are empty.
6329
- fromPairs(balanceIds.map(balanceId => [balanceId, undefined])), keyBy(
6330
- // storage balances must have status "cache", because they are used as start value when initialising subsequent subscriptions
6331
- results.balances.map(b => ({
6332
- ...b,
6333
- status: "cache"
6334
- })), b => getBalanceId(b)));
6335
- this.#storage.next(assign({}, storage, {
6336
- balances
6337
- }));
6338
- };
6339
- switch (mod.platform) {
6340
- case "ethereum":
6341
- {
6342
- if (!this.#chainConnectors.evm) return of(initValue);
6322
+ // updating storage has to be done on a per-module basis, so we know which balances can be deleted
6323
+ const updateStorage = results => {
6324
+ if (results.status !== "live") return;
6325
+ const storage = this.#storage.getValue();
6326
+ const balances = assign({}, storage.balances,
6327
+ // delete all balances expected in the result set. because if they are not present it means they are empty.
6328
+ fromPairs(balanceIds.map(balanceId => [balanceId, undefined])), keyBy(
6329
+ // storage balances must have status "cache", because they are used as start value when initialising subsequent subscriptions
6330
+ results.balances.map(b => ({
6331
+ ...b,
6332
+ status: "cache"
6333
+ })), b => getBalanceId(b)));
6334
+ this.#storage.next(assign({}, storage, {
6335
+ balances
6336
+ }));
6337
+ };
6338
+ switch (mod.platform) {
6339
+ case "ethereum":
6340
+ {
6341
+ if (!this.#chainConnectors.evm) return of(initValue);
6342
+ return mod.subscribeBalances({
6343
+ networkId,
6344
+ tokensWithAddresses,
6345
+ connector: this.#chainConnectors.evm
6346
+ }).pipe(map(results => ({
6347
+ status: "live",
6348
+ // exclude zero balances
6349
+ balances: results.success.filter(b => new Balance(b).total.planck > 0n)
6350
+ })), tap(updateStorage), startWith(initValue));
6351
+ }
6352
+ case "polkadot":
6353
+ if (!this.#chainConnectors.substrate || !miniMetadata) {
6354
+ log.debug("[balances] no substrate connector or miniMetadata for polkadot", mod.type);
6355
+ return of(initValue);
6356
+ }
6343
6357
  return mod.subscribeBalances({
6344
6358
  networkId,
6345
6359
  tokensWithAddresses,
6346
- connector: this.#chainConnectors.evm
6360
+ connector: this.#chainConnectors.substrate,
6361
+ miniMetadata: miniMetadata
6347
6362
  }).pipe(map(results => ({
6348
6363
  status: "live",
6349
6364
  // exclude zero balances
6350
6365
  balances: results.success.filter(b => new Balance(b).total.planck > 0n)
6351
6366
  })), tap(updateStorage), startWith(initValue));
6352
- }
6353
- case "polkadot":
6354
- if (!this.#chainConnectors.substrate || !miniMetadata) {
6355
- log.debug("[balances] no substrate connector or miniMetadata for polkadot", mod.type);
6356
- return of(initValue);
6357
- }
6358
- return mod.subscribeBalances({
6359
- networkId,
6360
- tokensWithAddresses,
6361
- connector: this.#chainConnectors.substrate,
6362
- miniMetadata: miniMetadata
6363
- }).pipe(map(results => ({
6364
- status: "live",
6365
- // exclude zero balances
6366
- balances: results.success.filter(b => new Balance(b).total.planck > 0n)
6367
- })), tap(updateStorage), startWith(initValue));
6368
- }
6369
- }));
6370
- }), map(results => {
6371
- return {
6372
- status: results.some(({
6373
- status
6374
- }) => status === "initialising") ? "initialising" : "live",
6375
- balances: results.flatMap(result => result.balances)
6376
- };
6377
- }), startWith({
6378
- status: "initialising",
6379
- balances: this.getStoredBalances(addressesByTokenId)
6380
- }));
6367
+ }
6368
+ }));
6369
+ }), map(results => {
6370
+ return {
6371
+ status: results.some(({
6372
+ status
6373
+ }) => status === "initialising") ? "initialising" : "live",
6374
+ balances: results.flatMap(result => result.balances).sort(sortByBalanceId)
6375
+ };
6376
+ }), startWith({
6377
+ status: "initialising",
6378
+ balances: this.getStoredBalances(addressesByTokenId)
6379
+ }), distinctUntilChanged(isEqual),
6380
+ // shareReplay + keepAlive allow for subscription to not restart as long as the inputs dont change
6381
+ // for example, if another network is enabled/disabled
6382
+ shareReplay({
6383
+ refCount: true,
6384
+ bufferSize: 1
6385
+ }), keepAlive(2_000));
6386
+ });
6381
6387
  }
6382
6388
  getNetworkMiniMetadatas$(networkId) {
6383
6389
  return this.#chaindataProvider.getNetworkById$(networkId).pipe(switchMap(network => isNetworkDot(network) && this.#chainConnectors.substrate ? from(getSpecVersion(this.#chainConnectors.substrate, networkId)).pipe(switchMap(specVersion => this.getMiniMetadatas$(networkId, specVersion))) : of([])));
@@ -6412,13 +6418,17 @@ class BalancesProvider {
6412
6418
  miniMetadatas
6413
6419
  }));
6414
6420
  }));
6415
- }));
6421
+ }),
6422
+ // emit only when mini metadata changes, as a change here would restart all subscriptions for the network
6423
+ distinctUntilChanged(isEqual));
6416
6424
  }
6417
6425
  getStoredMiniMetadatas$(miniMetadataIds) {
6418
6426
  return this.storedMiniMetadataMapById$.pipe(map(mapById => {
6419
6427
  const miniMetadatas = miniMetadataIds.map(id => mapById[id]);
6420
6428
  return miniMetadatas.length && miniMetadatas.every(isTruthy) ? miniMetadatas : null;
6421
- }));
6429
+ }),
6430
+ // source changes very often
6431
+ distinctUntilChanged(isEqual));
6422
6432
  }
6423
6433
  getDefaultMiniMetadatas$(miniMetadataIds) {
6424
6434
  return this.#chaindataProvider.miniMetadatasMapById$.pipe(map(mapById => {
@@ -6431,9 +6441,9 @@ class BalancesProvider {
6431
6441
  return balanceDefs.map(([tokenId, address]) => this.#storage.value.balances[getBalanceId({
6432
6442
  address,
6433
6443
  tokenId
6434
- })]).filter(isNotNil).sort((a, b) => getBalanceId(a).localeCompare(getBalanceId(b)));
6444
+ })]).filter(isNotNil).sort(sortByBalanceId);
6435
6445
  }
6436
- cleanupAddressesByTokenId(addressesByTokenId) {
6446
+ cleanupAddressesByTokenId$(addressesByTokenId) {
6437
6447
  return this.#chaindataProvider.getNetworksMapById$().pipe(map(networksById => {
6438
6448
  return fromPairs(toPairs(addressesByTokenId).map(([tokenId, addresses]) => {
6439
6449
  const networkId = parseTokenId(tokenId).networkId;
@@ -6454,5 +6464,7 @@ const isAddressCompatibleWithNetwork = (network, address) => {
6454
6464
  throw new Error("Unsupported network platform");
6455
6465
  }
6456
6466
  };
6467
+ const sortByBalanceId = (a, b) => getBalanceId(a).localeCompare(getBalanceId(b));
6468
+ const sortByMiniMetadataId = (a, b) => a.id.localeCompare(b.id);
6457
6469
 
6458
6470
  export { BALANCE_MODULES, Balance, BalanceFormatter, BalanceValueGetter, Balances, BalancesProvider, Change24hCurrencyFormatter, EvmErc20BalanceModule, EvmErc20TokenConfigSchema, EvmNativeBalanceModule, EvmNativeTokenConfigSchema, EvmUniswapV2BalanceModule, EvmUniswapV2TokenConfigSchema, FiatSumBalancesFormatter, ONE_ALPHA_TOKEN, PlanckSumBalancesFormatter, SCALE_FACTOR, SUBTENSOR_MIN_STAKE_AMOUNT_PLANK, SUBTENSOR_ROOT_NETUID, SubAssetsBalanceModule, SubAssetsTokenConfigSchema, SubForeignAssetsBalanceModule, SubForeignAssetsTokenConfigSchema, SubHydrationBalanceModule, SubHydrationTokenConfigSchema, SubNativeBalanceModule, SubNativeMiniMetadataExtraSchema, SubNativeModuleConfigSchema, SubNativeTokenConfigSchema, SubPsp22BalanceModule, SubPsp22TokenConfigSchema, SubTokensBalanceModule, SubTokensMiniMetadataExtraSchema, SubTokensModuleConfigSchema, SubTokensTokenConfigSchema, SumBalancesFormatter, abiMulticall, calculateAlphaPrice, calculateTaoAmountFromAlpha, calculateTaoFromDynamicInfo, deriveMiniMetadataId, erc20BalancesAggregatorAbi, excludeFromFeePayableLocks, excludeFromTransferableAmount, filterBaseLocks, filterMirrorTokens, getBalanceId, getLockTitle, getLockedType, getValueId, includeInTotalExtraAmount, isAddressCompatibleWithNetwork, uniswapV2PairAbi };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talismn/balances",
3
- "version": "0.0.0-pr2075-20250710091134",
3
+ "version": "0.0.0-pr2075-20250710124714",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "GPL-3.0-or-later",
@@ -34,13 +34,13 @@
34
34
  "scale-ts": "^1.6.1",
35
35
  "viem": "^2.27.3",
36
36
  "zod": "^3.25.62",
37
- "@talismn/chain-connector": "0.0.0-pr2075-20250710091134",
38
- "@talismn/chain-connector-evm": "0.0.0-pr2075-20250710091134",
39
- "@talismn/chaindata-provider": "0.0.0-pr2075-20250710091134",
40
- "@talismn/sapi": "0.0.0-pr2075-20250710091134",
41
- "@talismn/token-rates": "0.0.0-pr2075-20250710091134",
42
- "@talismn/util": "0.0.0-pr2075-20250710091134",
43
- "@talismn/scale": "0.0.0-pr2075-20250710091134"
37
+ "@talismn/chain-connector": "0.0.0-pr2075-20250710124714",
38
+ "@talismn/chaindata-provider": "0.0.0-pr2075-20250710124714",
39
+ "@talismn/chain-connector-evm": "0.0.0-pr2075-20250710124714",
40
+ "@talismn/sapi": "0.0.0-pr2075-20250710124714",
41
+ "@talismn/token-rates": "0.0.0-pr2075-20250710124714",
42
+ "@talismn/util": "0.0.0-pr2075-20250710124714",
43
+ "@talismn/scale": "0.0.0-pr2075-20250710124714"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@polkadot/api-contract": "16.1.2",
@@ -55,8 +55,8 @@
55
55
  "jest": "^29.7.0",
56
56
  "ts-jest": "^29.2.5",
57
57
  "typescript": "^5.6.3",
58
- "@talismn/eslint-config": "0.0.3",
59
- "@talismn/tsconfig": "0.0.2"
58
+ "@talismn/tsconfig": "0.0.2",
59
+ "@talismn/eslint-config": "0.0.3"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "@polkadot/api-contract": "*",