@talismn/balances 0.0.0-pr2043-20250626090303 → 0.0.0-pr2043-20250626133240

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.
@@ -112,7 +112,7 @@ class EvmTokenFetcher {
112
112
 
113
113
  var pkg = {
114
114
  name: "@talismn/balances",
115
- version: "0.0.0-pr2043-20250626090303"};
115
+ version: "0.0.0-pr2043-20250626133240"};
116
116
 
117
117
  var log = anylogger(pkg.name);
118
118
 
@@ -3645,11 +3645,18 @@ const getAddresssesByTokenByNetwork = addressesByToken => {
3645
3645
  };
3646
3646
 
3647
3647
  async function subscribeBase(queries, chainConnector, callback) {
3648
- const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
3649
- if (error) callback(error);
3650
- if (result && result.length > 0) callback(null, result);
3651
- });
3652
- return unsubscribe;
3648
+ try {
3649
+ const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe((error, result) => {
3650
+ if (error) callback(error);
3651
+ if (result && result.length > 0) callback(null, result);
3652
+ });
3653
+ return unsubscribe;
3654
+ } catch (err) {
3655
+ if (!isAbortError(err)) log.error("Error subscribing to base queries", {
3656
+ err
3657
+ });
3658
+ return () => {};
3659
+ }
3653
3660
  }
3654
3661
 
3655
3662
  /**
@@ -3686,284 +3693,291 @@ const nompoolStashAccountId = (palletId, poolId) => nompoolAccountId(palletId, p
3686
3693
 
3687
3694
  // TODO make this method chain-specific
3688
3695
  async function subscribeNompoolStaking(chaindataProvider, chainConnector, addressesByToken, callback, signal) {
3689
- const allChains = await chaindataProvider.getNetworksMapById("polkadot");
3690
- const tokens = await chaindataProvider.getTokensMapById();
3691
-
3692
- // there should be only one network here when subscribing to balances, we've split it up by network at the top level
3693
- const networkIds = keys(addressesByToken).map(tokenId => parseTokenId(tokenId).networkId);
3694
- const miniMetadatas = new Map();
3695
- for (const networkId of networkIds) {
3696
- const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, "substrate-native");
3697
- miniMetadatas.set(networkId, miniMetadata);
3698
- }
3699
- signal?.throwIfAborted();
3700
- const nomPoolTokenIds = Object.entries(tokens).filter(([, token]) => {
3701
- // ignore non-native tokens
3702
- if (token.type !== "substrate-native") return false;
3703
-
3704
- // ignore tokens on chains with no nompools pallet
3705
- const miniMetadata = miniMetadatas.get(token.networkId);
3706
- return typeof miniMetadata?.extra?.nominationPoolsPalletId === "string";
3707
- }).map(([tokenId]) => tokenId);
3708
-
3709
- // staking can only be done by the native token on chains with the staking pallet
3710
- const addressesByNomPoolToken = Object.fromEntries(Object.entries(addressesByToken)
3711
- // remove ethereum addresses
3712
- .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !isEthereumAddress(address))])
3713
- // remove tokens which aren't nom pool tokens
3714
- .filter(([tokenId]) => nomPoolTokenIds.includes(tokenId)));
3715
- const uniqueChainIds = getUniqueChainIds(addressesByNomPoolToken, tokens);
3716
- const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
3717
- const chainStorageCoders = buildStorageCoders({
3718
- chainIds: uniqueChainIds,
3719
- chains,
3720
- miniMetadatas,
3721
- coders: {
3722
- poolMembers: ["NominationPools", "PoolMembers"],
3723
- bondedPools: ["NominationPools", "BondedPools"],
3724
- ledger: ["Staking", "Ledger"],
3725
- metadata: ["NominationPools", "Metadata"]
3726
- }
3727
- });
3728
- const resultUnsubscribes = [];
3729
- for (const [tokenId, addresses] of Object.entries(addressesByNomPoolToken)) {
3730
- const token = tokens[tokenId];
3731
- if (!token) {
3732
- log.warn(`Token ${tokenId} not found`);
3733
- continue;
3734
- }
3735
- if (token.type !== "substrate-native") {
3736
- log.debug(`This module doesn't handle tokens of type ${token.type}`);
3737
- continue;
3738
- }
3739
- const chainId = token.networkId;
3740
- if (!chainId) {
3741
- log.warn(`Token ${tokenId} has no chain`);
3742
- continue;
3743
- }
3744
- const chain = chains[chainId];
3745
- if (!chain) {
3746
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
3747
- continue;
3696
+ try {
3697
+ const allChains = await chaindataProvider.getNetworksMapById("polkadot");
3698
+ const tokens = await chaindataProvider.getTokensMapById();
3699
+
3700
+ // there should be only one network here when subscribing to balances, we've split it up by network at the top level
3701
+ const networkIds = keys(addressesByToken).map(tokenId => parseTokenId(tokenId).networkId);
3702
+ const miniMetadatas = new Map();
3703
+ for (const networkId of networkIds) {
3704
+ const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, "substrate-native");
3705
+ miniMetadatas.set(networkId, miniMetadata);
3748
3706
  }
3749
- const miniMetadata = miniMetadatas.get(chainId);
3750
- const {
3751
- nominationPoolsPalletId
3752
- } = miniMetadata?.extra ?? {};
3753
- const subscribePoolMembers = (addresses, callback) => {
3754
- const scaleCoder = chainStorageCoders.get(chainId)?.poolMembers;
3755
- const queries = addresses.flatMap(address => {
3756
- const stateKey = encodeStateKey(scaleCoder, `Invalid address in ${chainId} poolMembers query ${address}`, address);
3757
- if (!stateKey) return [];
3758
- const decodeResult = change => {
3759
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3760
-
3761
- const decoded = decodeScale(scaleCoder, change, `Failed to decode poolMembers on chain ${chainId}`);
3762
- const poolId = decoded?.pool_id?.toString?.();
3763
- const points = decoded?.points?.toString?.();
3764
- const unbondingEras = Array.from(decoded?.unbonding_eras ?? []).flatMap(entry => {
3765
- if (entry === undefined) return [];
3766
- const [key, value] = Array.from(entry);
3767
- const era = key?.toString?.();
3768
- const amount = value?.toString?.();
3769
- if (typeof era !== "string" || typeof amount !== "string") return [];
3707
+ signal?.throwIfAborted();
3708
+ const nomPoolTokenIds = Object.entries(tokens).filter(([, token]) => {
3709
+ // ignore non-native tokens
3710
+ if (token.type !== "substrate-native") return false;
3711
+
3712
+ // ignore tokens on chains with no nompools pallet
3713
+ const miniMetadata = miniMetadatas.get(token.networkId);
3714
+ return typeof miniMetadata?.extra?.nominationPoolsPalletId === "string";
3715
+ }).map(([tokenId]) => tokenId);
3716
+
3717
+ // staking can only be done by the native token on chains with the staking pallet
3718
+ const addressesByNomPoolToken = Object.fromEntries(Object.entries(addressesByToken)
3719
+ // remove ethereum addresses
3720
+ .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !isEthereumAddress(address))])
3721
+ // remove tokens which aren't nom pool tokens
3722
+ .filter(([tokenId]) => nomPoolTokenIds.includes(tokenId)));
3723
+ const uniqueChainIds = getUniqueChainIds(addressesByNomPoolToken, tokens);
3724
+ const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
3725
+ const chainStorageCoders = buildStorageCoders({
3726
+ chainIds: uniqueChainIds,
3727
+ chains,
3728
+ miniMetadatas,
3729
+ coders: {
3730
+ poolMembers: ["NominationPools", "PoolMembers"],
3731
+ bondedPools: ["NominationPools", "BondedPools"],
3732
+ ledger: ["Staking", "Ledger"],
3733
+ metadata: ["NominationPools", "Metadata"]
3734
+ }
3735
+ });
3736
+ const resultUnsubscribes = [];
3737
+ for (const [tokenId, addresses] of Object.entries(addressesByNomPoolToken)) {
3738
+ const token = tokens[tokenId];
3739
+ if (!token) {
3740
+ log.warn(`Token ${tokenId} not found`);
3741
+ continue;
3742
+ }
3743
+ if (token.type !== "substrate-native") {
3744
+ log.debug(`This module doesn't handle tokens of type ${token.type}`);
3745
+ continue;
3746
+ }
3747
+ const chainId = token.networkId;
3748
+ if (!chainId) {
3749
+ log.warn(`Token ${tokenId} has no chain`);
3750
+ continue;
3751
+ }
3752
+ const chain = chains[chainId];
3753
+ if (!chain) {
3754
+ log.warn(`Chain ${chainId} for token ${tokenId} not found`);
3755
+ continue;
3756
+ }
3757
+ const miniMetadata = miniMetadatas.get(chainId);
3758
+ const {
3759
+ nominationPoolsPalletId
3760
+ } = miniMetadata?.extra ?? {};
3761
+ const subscribePoolMembers = (addresses, callback) => {
3762
+ const scaleCoder = chainStorageCoders.get(chainId)?.poolMembers;
3763
+ const queries = addresses.flatMap(address => {
3764
+ const stateKey = encodeStateKey(scaleCoder, `Invalid address in ${chainId} poolMembers query ${address}`, address);
3765
+ if (!stateKey) return [];
3766
+ const decodeResult = change => {
3767
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3768
+
3769
+ const decoded = decodeScale(scaleCoder, change, `Failed to decode poolMembers on chain ${chainId}`);
3770
+ const poolId = decoded?.pool_id?.toString?.();
3771
+ const points = decoded?.points?.toString?.();
3772
+ const unbondingEras = Array.from(decoded?.unbonding_eras ?? []).flatMap(entry => {
3773
+ if (entry === undefined) return [];
3774
+ const [key, value] = Array.from(entry);
3775
+ const era = key?.toString?.();
3776
+ const amount = value?.toString?.();
3777
+ if (typeof era !== "string" || typeof amount !== "string") return [];
3778
+ return {
3779
+ era,
3780
+ amount
3781
+ };
3782
+ });
3770
3783
  return {
3771
- era,
3772
- amount
3784
+ tokenId,
3785
+ address,
3786
+ poolId,
3787
+ points,
3788
+ unbondingEras
3773
3789
  };
3774
- });
3790
+ };
3775
3791
  return {
3776
- tokenId,
3777
- address,
3792
+ chainId,
3793
+ stateKey,
3794
+ decodeResult
3795
+ };
3796
+ });
3797
+ const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3798
+ return () => subscription.then(unsubscribe => unsubscribe());
3799
+ };
3800
+ const subscribePoolPoints = (poolIds, callback) => {
3801
+ if (poolIds.length === 0) callback(null, []);
3802
+ const scaleCoder = chainStorageCoders.get(chainId)?.bondedPools;
3803
+ const queries = poolIds.flatMap(poolId => {
3804
+ const stateKey = encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} bondedPools query ${poolId}`, poolId);
3805
+ if (!stateKey) return [];
3806
+ const decodeResult = change => {
3807
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3808
+
3809
+ const decoded = decodeScale(scaleCoder, change, `Failed to decode bondedPools on chain ${chainId}`);
3810
+ const points = decoded?.points?.toString?.();
3811
+ return {
3812
+ poolId,
3813
+ points
3814
+ };
3815
+ };
3816
+ return {
3817
+ chainId,
3818
+ stateKey,
3819
+ decodeResult
3820
+ };
3821
+ });
3822
+ const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3823
+ return () => subscription.then(unsubscribe => unsubscribe());
3824
+ };
3825
+ const subscribePoolStake = (poolIds, callback) => {
3826
+ if (poolIds.length === 0) callback(null, []);
3827
+ const scaleCoder = chainStorageCoders.get(chainId)?.ledger;
3828
+ const queries = poolIds.flatMap(poolId => {
3829
+ if (!nominationPoolsPalletId) return [];
3830
+ const stashAddress = nompoolStashAccountId(nominationPoolsPalletId, poolId);
3831
+ const stateKey = encodeStateKey(scaleCoder, `Invalid address in ${chainId} ledger query ${stashAddress}`, stashAddress);
3832
+ if (!stateKey) return [];
3833
+ const decodeResult = change => {
3834
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3835
+
3836
+ const decoded = decodeScale(scaleCoder, change, `Failed to decode ledger on chain ${chainId}`);
3837
+ const activeStake = decoded?.active?.toString?.();
3838
+ return {
3839
+ poolId,
3840
+ activeStake
3841
+ };
3842
+ };
3843
+ return {
3844
+ chainId,
3845
+ stateKey,
3846
+ decodeResult
3847
+ };
3848
+ });
3849
+ const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3850
+ return () => subscription.then(unsubscribe => unsubscribe());
3851
+ };
3852
+ const subscribePoolMetadata = (poolIds, callback) => {
3853
+ if (poolIds.length === 0) callback(null, []);
3854
+ const scaleCoder = chainStorageCoders.get(chainId)?.metadata;
3855
+ const queries = poolIds.flatMap(poolId => {
3856
+ if (!nominationPoolsPalletId) return [];
3857
+ const stateKey = encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} metadata query ${poolId}`, poolId);
3858
+ if (!stateKey) return [];
3859
+ const decodeResult = change => {
3860
+ /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3861
+
3862
+ const decoded = decodeScale(scaleCoder, change, `Failed to decode metadata on chain ${chainId}`);
3863
+ const metadata = decoded?.asText?.();
3864
+ return {
3865
+ poolId,
3866
+ metadata
3867
+ };
3868
+ };
3869
+ return {
3870
+ chainId,
3871
+ stateKey,
3872
+ decodeResult
3873
+ };
3874
+ });
3875
+ const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3876
+ return () => subscription.then(unsubscribe => unsubscribe());
3877
+ };
3878
+ const poolMembersByAddress$ = asObservable(subscribePoolMembers)(addresses).pipe(scan((state, next) => {
3879
+ for (const poolMembers of next) {
3880
+ const {
3778
3881
  poolId,
3779
3882
  points,
3780
3883
  unbondingEras
3781
- };
3782
- };
3783
- return {
3784
- chainId,
3785
- stateKey,
3786
- decodeResult
3787
- };
3788
- });
3789
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3790
- return () => subscription.then(unsubscribe => unsubscribe());
3791
- };
3792
- const subscribePoolPoints = (poolIds, callback) => {
3793
- if (poolIds.length === 0) callback(null, []);
3794
- const scaleCoder = chainStorageCoders.get(chainId)?.bondedPools;
3795
- const queries = poolIds.flatMap(poolId => {
3796
- const stateKey = encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} bondedPools query ${poolId}`, poolId);
3797
- if (!stateKey) return [];
3798
- const decodeResult = change => {
3799
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3800
-
3801
- const decoded = decodeScale(scaleCoder, change, `Failed to decode bondedPools on chain ${chainId}`);
3802
- const points = decoded?.points?.toString?.();
3803
- return {
3884
+ } = poolMembers;
3885
+ if (typeof poolId === "string" && typeof points === "string") state.set(poolMembers.address, {
3886
+ poolId,
3887
+ points,
3888
+ unbondingEras
3889
+ });else state.set(poolMembers.address, null);
3890
+ }
3891
+ return state;
3892
+ }, new Map()), share());
3893
+ const poolIdByAddress$ = poolMembersByAddress$.pipe(map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.poolId ?? null]))));
3894
+ const pointsByAddress$ = poolMembersByAddress$.pipe(map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.points ?? null]))));
3895
+ const unbondingErasByAddress$ = poolMembersByAddress$.pipe(map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.unbondingEras ?? null]))));
3896
+ const poolIds$ = poolIdByAddress$.pipe(map(byAddress => [...new Set(Array.from(byAddress.values()).flatMap(poolId => poolId ?? []))]));
3897
+ const pointsByPool$ = poolIds$.pipe(map(poolIds => asObservable(subscribePoolPoints)(poolIds)), switchAll(), scan((state, next) => {
3898
+ for (const poolPoints of next) {
3899
+ const {
3804
3900
  poolId,
3805
3901
  points
3806
- };
3807
- };
3808
- return {
3809
- chainId,
3810
- stateKey,
3811
- decodeResult
3812
- };
3813
- });
3814
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3815
- return () => subscription.then(unsubscribe => unsubscribe());
3816
- };
3817
- const subscribePoolStake = (poolIds, callback) => {
3818
- if (poolIds.length === 0) callback(null, []);
3819
- const scaleCoder = chainStorageCoders.get(chainId)?.ledger;
3820
- const queries = poolIds.flatMap(poolId => {
3821
- if (!nominationPoolsPalletId) return [];
3822
- const stashAddress = nompoolStashAccountId(nominationPoolsPalletId, poolId);
3823
- const stateKey = encodeStateKey(scaleCoder, `Invalid address in ${chainId} ledger query ${stashAddress}`, stashAddress);
3824
- if (!stateKey) return [];
3825
- const decodeResult = change => {
3826
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3827
-
3828
- const decoded = decodeScale(scaleCoder, change, `Failed to decode ledger on chain ${chainId}`);
3829
- const activeStake = decoded?.active?.toString?.();
3830
- return {
3902
+ } = poolPoints;
3903
+ if (typeof points === "string") state.set(poolId, points);else state.delete(poolId);
3904
+ }
3905
+ return state;
3906
+ }, new Map()));
3907
+ const stakeByPool$ = poolIds$.pipe(map(poolIds => asObservable(subscribePoolStake)(poolIds)), switchAll(), scan((state, next) => {
3908
+ for (const poolStake of next) {
3909
+ const {
3831
3910
  poolId,
3832
3911
  activeStake
3833
- };
3834
- };
3835
- return {
3836
- chainId,
3837
- stateKey,
3838
- decodeResult
3839
- };
3840
- });
3841
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3842
- return () => subscription.then(unsubscribe => unsubscribe());
3843
- };
3844
- const subscribePoolMetadata = (poolIds, callback) => {
3845
- if (poolIds.length === 0) callback(null, []);
3846
- const scaleCoder = chainStorageCoders.get(chainId)?.metadata;
3847
- const queries = poolIds.flatMap(poolId => {
3848
- if (!nominationPoolsPalletId) return [];
3849
- const stateKey = encodeStateKey(scaleCoder, `Invalid poolId in ${chainId} metadata query ${poolId}`, poolId);
3850
- if (!stateKey) return [];
3851
- const decodeResult = change => {
3852
- /** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
3853
-
3854
- const decoded = decodeScale(scaleCoder, change, `Failed to decode metadata on chain ${chainId}`);
3855
- const metadata = decoded?.asText?.();
3856
- return {
3912
+ } = poolStake;
3913
+ if (typeof activeStake === "string") state.set(poolId, activeStake);else state.delete(poolId);
3914
+ }
3915
+ return state;
3916
+ }, new Map()));
3917
+ const metadataByPool$ = poolIds$.pipe(map(poolIds => asObservable(subscribePoolMetadata)(poolIds)), switchAll(), scan((state, next) => {
3918
+ for (const poolMetadata of next) {
3919
+ const {
3857
3920
  poolId,
3858
3921
  metadata
3859
- };
3860
- };
3861
- return {
3862
- chainId,
3863
- stateKey,
3864
- decodeResult
3865
- };
3866
- });
3867
- const subscription = new RpcStateQueryHelper(chainConnector, queries).subscribe(callback);
3868
- return () => subscription.then(unsubscribe => unsubscribe());
3869
- };
3870
- const poolMembersByAddress$ = asObservable(subscribePoolMembers)(addresses).pipe(scan((state, next) => {
3871
- for (const poolMembers of next) {
3872
- const {
3873
- poolId,
3874
- points,
3875
- unbondingEras
3876
- } = poolMembers;
3877
- if (typeof poolId === "string" && typeof points === "string") state.set(poolMembers.address, {
3878
- poolId,
3879
- points,
3880
- unbondingEras
3881
- });else state.set(poolMembers.address, null);
3882
- }
3883
- return state;
3884
- }, new Map()), share());
3885
- const poolIdByAddress$ = poolMembersByAddress$.pipe(map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.poolId ?? null]))));
3886
- const pointsByAddress$ = poolMembersByAddress$.pipe(map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.points ?? null]))));
3887
- const unbondingErasByAddress$ = poolMembersByAddress$.pipe(map(pm => new Map(Array.from(pm).map(([address, pm]) => [address, pm?.unbondingEras ?? null]))));
3888
- const poolIds$ = poolIdByAddress$.pipe(map(byAddress => [...new Set(Array.from(byAddress.values()).flatMap(poolId => poolId ?? []))]));
3889
- const pointsByPool$ = poolIds$.pipe(map(poolIds => asObservable(subscribePoolPoints)(poolIds)), switchAll(), scan((state, next) => {
3890
- for (const poolPoints of next) {
3891
- const {
3892
- poolId,
3893
- points
3894
- } = poolPoints;
3895
- if (typeof points === "string") state.set(poolId, points);else state.delete(poolId);
3896
- }
3897
- return state;
3898
- }, new Map()));
3899
- const stakeByPool$ = poolIds$.pipe(map(poolIds => asObservable(subscribePoolStake)(poolIds)), switchAll(), scan((state, next) => {
3900
- for (const poolStake of next) {
3901
- const {
3902
- poolId,
3903
- activeStake
3904
- } = poolStake;
3905
- if (typeof activeStake === "string") state.set(poolId, activeStake);else state.delete(poolId);
3906
- }
3907
- return state;
3908
- }, new Map()));
3909
- const metadataByPool$ = poolIds$.pipe(map(poolIds => asObservable(subscribePoolMetadata)(poolIds)), switchAll(), scan((state, next) => {
3910
- for (const poolMetadata of next) {
3911
- const {
3912
- poolId,
3913
- metadata
3914
- } = poolMetadata;
3915
- if (typeof metadata === "string") state.set(poolId, metadata);else state.delete(poolId);
3916
- }
3917
- return state;
3918
- }, new Map()));
3919
- const subscription = combineLatest([poolIdByAddress$, pointsByAddress$, unbondingErasByAddress$, pointsByPool$, stakeByPool$, metadataByPool$]).subscribe({
3920
- next: ([poolIdByAddress, pointsByAddress, unbondingErasByAddress, pointsByPool, stakeByPool, metadataByPool]) => {
3921
- const balances = Array.from(poolIdByAddress).map(([address, poolId]) => {
3922
- const parsedPoolId = poolId === null ? undefined : parseInt(poolId);
3923
- const points = pointsByAddress.get(address) ?? "0";
3924
- const poolPoints = pointsByPool.get(poolId ?? "") ?? "0";
3925
- const poolStake = stakeByPool.get(poolId ?? "") ?? "0";
3926
- const poolMetadata = poolId ? metadataByPool.get(poolId) ?? `Pool ${poolId}` : undefined;
3927
- const amount = points === "0" || poolPoints === "0" || poolStake === "0" ? 0n : BigInt(poolStake) * BigInt(points) / BigInt(poolPoints);
3928
- const unbondingAmount = (unbondingErasByAddress.get(address) ?? []).reduce((total, {
3929
- amount
3930
- }) => total + BigInt(amount ?? "0"), 0n);
3931
- return {
3932
- source: "substrate-native",
3933
- status: "live",
3934
- address,
3935
- networkId: chainId,
3936
- tokenId,
3937
- values: [{
3938
- source: "nompools-staking",
3939
- type: "nompool",
3940
- label: "nompools-staking",
3941
- amount: amount.toString(),
3942
- meta: {
3922
+ } = poolMetadata;
3923
+ if (typeof metadata === "string") state.set(poolId, metadata);else state.delete(poolId);
3924
+ }
3925
+ return state;
3926
+ }, new Map()));
3927
+ const subscription = combineLatest([poolIdByAddress$, pointsByAddress$, unbondingErasByAddress$, pointsByPool$, stakeByPool$, metadataByPool$]).subscribe({
3928
+ next: ([poolIdByAddress, pointsByAddress, unbondingErasByAddress, pointsByPool, stakeByPool, metadataByPool]) => {
3929
+ const balances = Array.from(poolIdByAddress).map(([address, poolId]) => {
3930
+ const parsedPoolId = poolId === null ? undefined : parseInt(poolId);
3931
+ const points = pointsByAddress.get(address) ?? "0";
3932
+ const poolPoints = pointsByPool.get(poolId ?? "") ?? "0";
3933
+ const poolStake = stakeByPool.get(poolId ?? "") ?? "0";
3934
+ const poolMetadata = poolId ? metadataByPool.get(poolId) ?? `Pool ${poolId}` : undefined;
3935
+ const amount = points === "0" || poolPoints === "0" || poolStake === "0" ? 0n : BigInt(poolStake) * BigInt(points) / BigInt(poolPoints);
3936
+ const unbondingAmount = (unbondingErasByAddress.get(address) ?? []).reduce((total, {
3937
+ amount
3938
+ }) => total + BigInt(amount ?? "0"), 0n);
3939
+ return {
3940
+ source: "substrate-native",
3941
+ status: "live",
3942
+ address,
3943
+ networkId: chainId,
3944
+ tokenId,
3945
+ values: [{
3946
+ source: "nompools-staking",
3943
3947
  type: "nompool",
3944
- poolId: parsedPoolId,
3945
- description: poolMetadata
3946
- }
3947
- }, {
3948
- source: "nompools-staking",
3949
- type: "nompool",
3950
- label: "nompools-unbonding",
3951
- amount: unbondingAmount.toString(),
3952
- meta: {
3953
- poolId: parsedPoolId,
3954
- description: poolMetadata,
3955
- unbonding: true
3956
- }
3957
- }]
3958
- };
3959
- }).filter(isNotNil);
3960
- if (balances.length > 0) callback(null, balances);
3961
- },
3962
- error: error => callback(error)
3948
+ label: "nompools-staking",
3949
+ amount: amount.toString(),
3950
+ meta: {
3951
+ type: "nompool",
3952
+ poolId: parsedPoolId,
3953
+ description: poolMetadata
3954
+ }
3955
+ }, {
3956
+ source: "nompools-staking",
3957
+ type: "nompool",
3958
+ label: "nompools-unbonding",
3959
+ amount: unbondingAmount.toString(),
3960
+ meta: {
3961
+ poolId: parsedPoolId,
3962
+ description: poolMetadata,
3963
+ unbonding: true
3964
+ }
3965
+ }]
3966
+ };
3967
+ }).filter(isNotNil);
3968
+ if (balances.length > 0) callback(null, balances);
3969
+ },
3970
+ error: error => callback(error)
3971
+ });
3972
+ resultUnsubscribes.push(() => subscription.unsubscribe());
3973
+ }
3974
+ return () => resultUnsubscribes.forEach(unsub => unsub());
3975
+ } catch (err) {
3976
+ if (!isAbortError(err)) log.error("Error subscribing to nom pool staking", {
3977
+ err
3963
3978
  });
3964
- resultUnsubscribes.push(() => subscription.unsubscribe());
3979
+ return () => {};
3965
3980
  }
3966
- return () => resultUnsubscribes.forEach(unsub => unsub());
3967
3981
  }
3968
3982
 
3969
3983
  const SUBTENSOR_ROOT_NETUID = 0;
@@ -4007,227 +4021,234 @@ const calculateTaoFromDynamicInfo = ({
4007
4021
 
4008
4022
  // TODO make this method chain-specific
4009
4023
  async function subscribeSubtensorStaking(chaindataProvider, chainConnector, addressesByToken, callback, signal) {
4010
- const allChains = await chaindataProvider.getNetworksMapById("polkadot");
4011
- const tokens = await chaindataProvider.getTokensMapById();
4012
-
4013
- // there should be only one network here when subscribing to balances, we've split it up by network at the top level
4014
- const networkIds = keys(addressesByToken).map(tokenId => parseTokenId(tokenId).networkId);
4015
- const miniMetadatas = new Map();
4016
- for (const networkId of networkIds) {
4017
- const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, "substrate-native", signal);
4018
- miniMetadatas.set(networkId, miniMetadata);
4019
- }
4020
- signal?.throwIfAborted();
4021
- const subtensorTokenIds = Object.entries(tokens).filter(([, token]) => {
4022
- // ignore non-native tokens
4023
- if (token.type !== "substrate-native") return false;
4024
- // ignore tokens on chains with no subtensor pallet
4025
- const miniMetadata = miniMetadatas.get(token.networkId);
4026
- return miniMetadata?.extra?.hasSubtensorPallet === true;
4027
- }).map(([tokenId]) => tokenId);
4028
-
4029
- // staking can only be done by the native token on chains with the subtensor pallet
4030
- const addressesBySubtensorToken = Object.fromEntries(Object.entries(addressesByToken)
4031
- // remove ethereum addresses
4032
- .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !isEthereumAddress(address))])
4033
- // remove tokens which aren't subtensor staking tokens
4034
- .filter(([tokenId]) => subtensorTokenIds.includes(tokenId)));
4035
- const uniqueChainIds = getUniqueChainIds(addressesBySubtensorToken, tokens);
4036
- const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
4037
- const abortController = new AbortController();
4038
- for (const [tokenId, addresses] of Object.entries(addressesBySubtensorToken)) {
4039
- const token = tokens[tokenId];
4040
- if (!token) {
4041
- log.warn(`Token ${tokenId} not found`);
4042
- continue;
4043
- }
4044
- if (token.type !== "substrate-native") {
4045
- log.debug(`This module doesn't handle tokens of type ${token.type}`);
4046
- continue;
4047
- }
4048
- const chainId = token.networkId;
4049
- const chain = chains[chainId];
4050
- if (!chain) {
4051
- log.warn(`Chain ${chainId} for token ${tokenId} not found`);
4052
- continue;
4053
- }
4054
- const miniMetadata = miniMetadatas.get(token.networkId);
4055
- if (!miniMetadata?.data) {
4056
- log.warn(`MiniMetadata for chain ${chainId} not found`);
4057
- continue;
4024
+ try {
4025
+ const allChains = await chaindataProvider.getNetworksMapById("polkadot");
4026
+ const tokens = await chaindataProvider.getTokensMapById();
4027
+
4028
+ // there should be only one network here when subscribing to balances, we've split it up by network at the top level
4029
+ const networkIds = keys(addressesByToken).map(tokenId => parseTokenId(tokenId).networkId);
4030
+ const miniMetadatas = new Map();
4031
+ for (const networkId of networkIds) {
4032
+ const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, "substrate-native", signal);
4033
+ miniMetadatas.set(networkId, miniMetadata);
4058
4034
  }
4059
- const scaleApi = getScaleApi({
4060
- chainId,
4061
- send: (...args) => chainConnector.send(chainId, ...args, {
4062
- expectErrors: true
4063
- } // don't pollute the wallet logs when this request fails
4064
- )
4065
- }, miniMetadata.data, token, chain.hasCheckMetadataHash, chain.signedExtensions, chain.registryTypes);
4066
-
4067
- // sets the number of addresses to query in parallel (per chain, since each chain runs in parallel to the others)
4068
- const concurrency = 4;
4069
- // In-memory cache for successful dynamic info results
4070
- const dynamicInfoCache = new Map();
4071
- const fetchDynamicInfoForNetuids = async uniqueNetuids => {
4072
- const MAX_RETRIES = 3;
4073
- const RETRY_DELAY_MS = 500;
4074
- const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
4075
- const fetchInfo = async netuid => {
4076
- if (netuid === 0) return null;
4077
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
4078
- try {
4079
- const params = [netuid];
4080
- const result = await scaleApi.getRuntimeCallValue("SubnetInfoRuntimeApi", "get_dynamic_info", params);
4081
- dynamicInfoCache.set(netuid, result); // Cache successful response
4082
- return result;
4083
- } catch (error) {
4084
- log.trace(`Attempt ${attempt} failed for netuid ${netuid}:`, error);
4085
- if (attempt < MAX_RETRIES) {
4086
- const backoffTime = RETRY_DELAY_MS * 2 ** (attempt - 1);
4087
- log.trace(`Retrying in ${backoffTime}ms...`);
4088
- await delay(backoffTime);
4035
+ signal?.throwIfAborted();
4036
+ const subtensorTokenIds = Object.entries(tokens).filter(([, token]) => {
4037
+ // ignore non-native tokens
4038
+ if (token.type !== "substrate-native") return false;
4039
+ // ignore tokens on chains with no subtensor pallet
4040
+ const miniMetadata = miniMetadatas.get(token.networkId);
4041
+ return miniMetadata?.extra?.hasSubtensorPallet === true;
4042
+ }).map(([tokenId]) => tokenId);
4043
+
4044
+ // staking can only be done by the native token on chains with the subtensor pallet
4045
+ const addressesBySubtensorToken = Object.fromEntries(Object.entries(addressesByToken)
4046
+ // remove ethereum addresses
4047
+ .map(([tokenId, addresses]) => [tokenId, addresses.filter(address => !isEthereumAddress(address))])
4048
+ // remove tokens which aren't subtensor staking tokens
4049
+ .filter(([tokenId]) => subtensorTokenIds.includes(tokenId)));
4050
+ const uniqueChainIds = getUniqueChainIds(addressesBySubtensorToken, tokens);
4051
+ const chains = Object.fromEntries(Object.entries(allChains).filter(([chainId]) => uniqueChainIds.includes(chainId)));
4052
+ const abortController = new AbortController();
4053
+ for (const [tokenId, addresses] of Object.entries(addressesBySubtensorToken)) {
4054
+ const token = tokens[tokenId];
4055
+ if (!token) {
4056
+ log.warn(`Token ${tokenId} not found`);
4057
+ continue;
4058
+ }
4059
+ if (token.type !== "substrate-native") {
4060
+ log.debug(`This module doesn't handle tokens of type ${token.type}`);
4061
+ continue;
4062
+ }
4063
+ const chainId = token.networkId;
4064
+ const chain = chains[chainId];
4065
+ if (!chain) {
4066
+ log.warn(`Chain ${chainId} for token ${tokenId} not found`);
4067
+ continue;
4068
+ }
4069
+ const miniMetadata = miniMetadatas.get(token.networkId);
4070
+ if (!miniMetadata?.data) {
4071
+ log.warn(`MiniMetadata for chain ${chainId} not found`);
4072
+ continue;
4073
+ }
4074
+ const scaleApi = getScaleApi({
4075
+ chainId,
4076
+ send: (...args) => chainConnector.send(chainId, ...args, {
4077
+ expectErrors: true
4078
+ } // don't pollute the wallet logs when this request fails
4079
+ )
4080
+ }, miniMetadata.data, token, chain.hasCheckMetadataHash, chain.signedExtensions, chain.registryTypes);
4081
+
4082
+ // sets the number of addresses to query in parallel (per chain, since each chain runs in parallel to the others)
4083
+ const concurrency = 4;
4084
+ // In-memory cache for successful dynamic info results
4085
+ const dynamicInfoCache = new Map();
4086
+ const fetchDynamicInfoForNetuids = async uniqueNetuids => {
4087
+ const MAX_RETRIES = 3;
4088
+ const RETRY_DELAY_MS = 500;
4089
+ const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
4090
+ const fetchInfo = async netuid => {
4091
+ if (netuid === 0) return null;
4092
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
4093
+ try {
4094
+ const params = [netuid];
4095
+ const result = await scaleApi.getRuntimeCallValue("SubnetInfoRuntimeApi", "get_dynamic_info", params);
4096
+ dynamicInfoCache.set(netuid, result); // Cache successful response
4097
+ return result;
4098
+ } catch (error) {
4099
+ log.trace(`Attempt ${attempt} failed for netuid ${netuid}:`, error);
4100
+ if (attempt < MAX_RETRIES) {
4101
+ const backoffTime = RETRY_DELAY_MS * 2 ** (attempt - 1);
4102
+ log.trace(`Retrying in ${backoffTime}ms...`);
4103
+ await delay(backoffTime);
4104
+ }
4089
4105
  }
4090
4106
  }
4091
- }
4092
- if (dynamicInfoCache.has(netuid)) {
4093
- return dynamicInfoCache.get(netuid); // Use cached value on failure
4094
- }
4095
- log.trace(`Failed to fetch dynamic info for netuid ${netuid} after ${MAX_RETRIES} attempts.`);
4096
- return null;
4107
+ if (dynamicInfoCache.has(netuid)) {
4108
+ return dynamicInfoCache.get(netuid); // Use cached value on failure
4109
+ }
4110
+ log.trace(`Failed to fetch dynamic info for netuid ${netuid} after ${MAX_RETRIES} attempts.`);
4111
+ return null;
4112
+ };
4113
+ return Promise.all(uniqueNetuids.map(fetchInfo));
4097
4114
  };
4098
- return Promise.all(uniqueNetuids.map(fetchInfo));
4099
- };
4100
- const subtensorQueries = from(addresses).pipe(
4101
- // mergeMap lets us run N concurrent queries, where N is the value of `concurrency`
4102
- mergeMap(async address => {
4103
- const queryMethods = [async () => {
4104
- if (chain.isTestnet) return [];
4105
- const params = [address];
4106
- const result = await scaleApi.getRuntimeCallValue("StakeInfoRuntimeApi", "get_stake_info_for_coldkey", params);
4107
- if (!Array.isArray(result)) return [];
4108
- const uniqueNetuids = Array.from(new Set(result.map(item => Number(item.netuid)).filter(netuid => netuid !== SUBTENSOR_ROOT_NETUID)));
4109
- await fetchDynamicInfoForNetuids(uniqueNetuids);
4110
- const stakes = result?.map(({
4111
- coldkey,
4112
- hotkey,
4113
- netuid,
4114
- stake
4115
- }) => {
4116
- return {
4117
- address: coldkey,
4115
+ const subtensorQueries = from(addresses).pipe(
4116
+ // mergeMap lets us run N concurrent queries, where N is the value of `concurrency`
4117
+ mergeMap(async address => {
4118
+ const queryMethods = [async () => {
4119
+ if (chain.isTestnet) return [];
4120
+ const params = [address];
4121
+ const result = await scaleApi.getRuntimeCallValue("StakeInfoRuntimeApi", "get_stake_info_for_coldkey", params);
4122
+ if (!Array.isArray(result)) return [];
4123
+ const uniqueNetuids = Array.from(new Set(result.map(item => Number(item.netuid)).filter(netuid => netuid !== SUBTENSOR_ROOT_NETUID)));
4124
+ await fetchDynamicInfoForNetuids(uniqueNetuids);
4125
+ const stakes = result?.map(({
4126
+ coldkey,
4118
4127
  hotkey,
4119
- netuid: Number(netuid),
4120
- stake: BigInt(stake),
4121
- dynamicInfo: dynamicInfoCache.get(Number(netuid))
4122
- };
4123
- }).filter(({
4124
- stake
4125
- }) => stake >= SUBTENSOR_MIN_STAKE_AMOUNT_PLANK);
4126
- return stakes;
4127
- }];
4128
- const errors = [];
4129
- for (const queryMethod of queryMethods) {
4130
- try {
4131
- // try each query method
4132
- return await queryMethod();
4133
- } catch (cause) {
4134
- // if it fails, keep track of the error and try the next one
4135
- errors.push(cause);
4128
+ netuid,
4129
+ stake
4130
+ }) => {
4131
+ return {
4132
+ address: coldkey,
4133
+ hotkey,
4134
+ netuid: Number(netuid),
4135
+ stake: BigInt(stake),
4136
+ dynamicInfo: dynamicInfoCache.get(Number(netuid))
4137
+ };
4138
+ }).filter(({
4139
+ stake
4140
+ }) => stake >= SUBTENSOR_MIN_STAKE_AMOUNT_PLANK);
4141
+ return stakes;
4142
+ }];
4143
+ const errors = [];
4144
+ for (const queryMethod of queryMethods) {
4145
+ try {
4146
+ // try each query method
4147
+ return await queryMethod();
4148
+ } catch (cause) {
4149
+ // if it fails, keep track of the error and try the next one
4150
+ errors.push(cause);
4151
+ }
4136
4152
  }
4137
- }
4138
4153
 
4139
- // if we get to here, that means that all query methods failed
4140
- // let's throw the errors back to the native balance module
4141
- throw new Error([`Failed to fetch ${tokenId} subtensor staked balance for ${address}:`, ...errors.map(error => String(error))].join("\n\t"));
4142
- }, concurrency),
4143
- // instead of emitting each balance as it's fetched, toArray waits for them all to fetch and then it collects them into an array
4144
- toArray(),
4145
- // this mergeMap flattens our Array<Array<Stakes>> into just an Array<Stakes>
4146
- mergeMap(stakes => stakes),
4147
- // convert our Array<Stakes> into Array<Balances>, which we can then return to the native balance module
4148
- map(stakes => stakes.map(({
4149
- address,
4150
- hotkey,
4151
- stake,
4152
- netuid,
4153
- dynamicInfo
4154
- }) => {
4155
- const {
4156
- token_symbol,
4157
- subnet_name,
4158
- subnet_identity
4159
- } = dynamicInfo ?? {};
4160
- const tokenSymbol = new TextDecoder().decode(Uint8Array.from(token_symbol ?? []));
4161
- const subnetName = new TextDecoder().decode(Uint8Array.from(subnet_name ?? []));
4162
-
4163
- /** Map from Record<string, Binary> to Record<string, string> */
4164
- const binaryToText = input => Object.entries(input).reduce((acc, [key, value]) => {
4165
- acc[key] = value.asText();
4166
- return acc;
4167
- }, {});
4168
- const subnetIdentity = subnet_identity ? binaryToText(subnet_identity) : undefined;
4169
-
4170
- // Add 1n balance if failed to fetch dynamic info, so the position is not ignored by Balance lib and is displayed in the UI.
4171
- const alphaStakedInTao = dynamicInfo ? calculateTaoFromDynamicInfo({
4172
- dynamicInfo,
4173
- alphaStaked: stake
4174
- }) : 1n;
4175
- const alphaToTaoRate = calculateTaoFromDynamicInfo({
4176
- dynamicInfo: dynamicInfo ?? null,
4177
- alphaStaked: ONE_ALPHA_TOKEN
4178
- }).toString();
4179
- const stakeByNetuid = Number(netuid) === SUBTENSOR_ROOT_NETUID ? stake : alphaStakedInTao;
4180
- return {
4181
- source: "substrate-native",
4182
- status: "live",
4154
+ // if we get to here, that means that all query methods failed
4155
+ // let's throw the errors back to the native balance module
4156
+ throw new Error([`Failed to fetch ${tokenId} subtensor staked balance for ${address}:`, ...errors.map(error => String(error))].join("\n\t"));
4157
+ }, concurrency),
4158
+ // instead of emitting each balance as it's fetched, toArray waits for them all to fetch and then it collects them into an array
4159
+ toArray(),
4160
+ // this mergeMap flattens our Array<Array<Stakes>> into just an Array<Stakes>
4161
+ mergeMap(stakes => stakes),
4162
+ // convert our Array<Stakes> into Array<Balances>, which we can then return to the native balance module
4163
+ map(stakes => stakes.map(({
4183
4164
  address,
4184
- networkId: chainId,
4185
- tokenId,
4186
- values: [{
4187
- source: "subtensor-staking",
4188
- type: "subtensor",
4189
- label: "subtensor-staking",
4190
- amount: stakeByNetuid.toString(),
4191
- meta: {
4192
- type: "subtensor-staking",
4193
- hotkey,
4194
- netuid,
4195
- amountStaked: stake.toString(),
4196
- alphaToTaoRate,
4197
- dynamicInfo: {
4198
- tokenSymbol,
4199
- subnetName,
4200
- subnetIdentity: {
4201
- ...subnetIdentity,
4202
- subnetName: subnetIdentity?.subnet_name || subnetName
4165
+ hotkey,
4166
+ stake,
4167
+ netuid,
4168
+ dynamicInfo
4169
+ }) => {
4170
+ const {
4171
+ token_symbol,
4172
+ subnet_name,
4173
+ subnet_identity
4174
+ } = dynamicInfo ?? {};
4175
+ const tokenSymbol = new TextDecoder().decode(Uint8Array.from(token_symbol ?? []));
4176
+ const subnetName = new TextDecoder().decode(Uint8Array.from(subnet_name ?? []));
4177
+
4178
+ /** Map from Record<string, Binary> to Record<string, string> */
4179
+ const binaryToText = input => Object.entries(input).reduce((acc, [key, value]) => {
4180
+ acc[key] = value.asText();
4181
+ return acc;
4182
+ }, {});
4183
+ const subnetIdentity = subnet_identity ? binaryToText(subnet_identity) : undefined;
4184
+
4185
+ // Add 1n balance if failed to fetch dynamic info, so the position is not ignored by Balance lib and is displayed in the UI.
4186
+ const alphaStakedInTao = dynamicInfo ? calculateTaoFromDynamicInfo({
4187
+ dynamicInfo,
4188
+ alphaStaked: stake
4189
+ }) : 1n;
4190
+ const alphaToTaoRate = calculateTaoFromDynamicInfo({
4191
+ dynamicInfo: dynamicInfo ?? null,
4192
+ alphaStaked: ONE_ALPHA_TOKEN
4193
+ }).toString();
4194
+ const stakeByNetuid = Number(netuid) === SUBTENSOR_ROOT_NETUID ? stake : alphaStakedInTao;
4195
+ return {
4196
+ source: "substrate-native",
4197
+ status: "live",
4198
+ address,
4199
+ networkId: chainId,
4200
+ tokenId,
4201
+ values: [{
4202
+ source: "subtensor-staking",
4203
+ type: "subtensor",
4204
+ label: "subtensor-staking",
4205
+ amount: stakeByNetuid.toString(),
4206
+ meta: {
4207
+ type: "subtensor-staking",
4208
+ hotkey,
4209
+ netuid,
4210
+ amountStaked: stake.toString(),
4211
+ alphaToTaoRate,
4212
+ dynamicInfo: {
4213
+ tokenSymbol,
4214
+ subnetName,
4215
+ subnetIdentity: {
4216
+ ...subnetIdentity,
4217
+ subnetName: subnetIdentity?.subnet_name || subnetName
4218
+ }
4203
4219
  }
4204
4220
  }
4205
- }
4206
- }]
4207
- };
4208
- })));
4209
-
4210
- // This observable will run the subtensorQueries on a 30s (30_000ms) interval.
4211
- // However, if the last run has not yet completed (e.g. its been 30s but we're still fetching some balances),
4212
- // then exhaustMap will wait until the next interval (so T: 60s, T: 90s, T: 120s, etc) before re-executing the subtensorQueries.
4213
- const subtensorQueriesInterval = interval(30_000).pipe(startWith(0),
4214
- // start immediately
4215
- exhaustMap(() => {
4216
- return subtensorQueries;
4217
- }));
4221
+ }]
4222
+ };
4223
+ })));
4224
+
4225
+ // This observable will run the subtensorQueries on a 30s (30_000ms) interval.
4226
+ // However, if the last run has not yet completed (e.g. its been 30s but we're still fetching some balances),
4227
+ // then exhaustMap will wait until the next interval (so T: 60s, T: 90s, T: 120s, etc) before re-executing the subtensorQueries.
4228
+ const subtensorQueriesInterval = interval(30_000).pipe(startWith(0),
4229
+ // start immediately
4230
+ exhaustMap(() => {
4231
+ return subtensorQueries;
4232
+ }));
4218
4233
 
4219
- // subscribe to the balances
4220
- const subscription = subtensorQueriesInterval.subscribe({
4221
- next: balances => callback(null, balances),
4222
- error: error => callback(error)
4223
- });
4234
+ // subscribe to the balances
4235
+ const subscription = subtensorQueriesInterval.subscribe({
4236
+ next: balances => callback(null, balances),
4237
+ error: error => callback(error)
4238
+ });
4224
4239
 
4225
- // use the abortController to tear the subscription down when we don't need it anymore
4226
- abortController.signal.addEventListener("abort", () => {
4227
- subscription.unsubscribe();
4240
+ // use the abortController to tear the subscription down when we don't need it anymore
4241
+ abortController.signal.addEventListener("abort", () => {
4242
+ subscription.unsubscribe();
4243
+ });
4244
+ }
4245
+ return () => abortController.abort();
4246
+ } catch (err) {
4247
+ if (!isAbortError(err)) log.error("Error subscribing to subtensor staking", {
4248
+ err
4228
4249
  });
4250
+ return () => {};
4229
4251
  }
4230
- return () => abortController.abort();
4231
4252
  }
4232
4253
 
4233
4254
  const getOtherType = input => `other-${input}`;
@@ -4906,17 +4927,9 @@ const SubNativeModule = hydrate => {
4906
4927
  if (!chainConnectors.substrate) return;
4907
4928
  const unsubSubtensorStaking = subscribeSubtensorStaking(chaindataProvider, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("subtensor-staking"), signal);
4908
4929
  const unsubNompoolStaking = subscribeNompoolStaking(chaindataProvider, chainConnectors.substrate, newAddressesByToken, handleUpdateForSource("nompools-staking"), signal);
4909
- // const unsubCrowdloans = subscribeCrowdloans(
4910
- // chaindataProvider,
4911
- // chainConnectors.substrate,
4912
- // newAddressesByToken,
4913
- // handleUpdateForSource("crowdloan"),
4914
- // signal,
4915
- // )
4916
4930
  const unsubBase = subscribeBase(baseQueries, chainConnectors.substrate, handleUpdateForSource("base"));
4917
4931
  subscriber.add(async () => (await unsubSubtensorStaking)());
4918
4932
  subscriber.add(async () => (await unsubNompoolStaking)());
4919
- // subscriber.add(async () => (await unsubCrowdloans)())
4920
4933
  subscriber.add(async () => (await unsubBase)());
4921
4934
  });
4922
4935
  }));