@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.
- package/dist/talismn-balances.cjs.dev.js +496 -483
- package/dist/talismn-balances.cjs.prod.js +496 -483
- package/dist/talismn-balances.esm.js +496 -483
- package/package.json +7 -7
@@ -112,7 +112,7 @@ class EvmTokenFetcher {
|
|
112
112
|
|
113
113
|
var pkg = {
|
114
114
|
name: "@talismn/balances",
|
115
|
-
version: "0.0.0-pr2043-
|
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
|
-
|
3649
|
-
|
3650
|
-
|
3651
|
-
|
3652
|
-
|
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
|
-
|
3690
|
-
|
3691
|
-
|
3692
|
-
|
3693
|
-
|
3694
|
-
|
3695
|
-
|
3696
|
-
|
3697
|
-
|
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
|
-
|
3750
|
-
const {
|
3751
|
-
|
3752
|
-
|
3753
|
-
|
3754
|
-
|
3755
|
-
const
|
3756
|
-
|
3757
|
-
|
3758
|
-
|
3759
|
-
|
3760
|
-
|
3761
|
-
|
3762
|
-
|
3763
|
-
|
3764
|
-
|
3765
|
-
|
3766
|
-
|
3767
|
-
|
3768
|
-
|
3769
|
-
|
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
|
-
|
3772
|
-
|
3784
|
+
tokenId,
|
3785
|
+
address,
|
3786
|
+
poolId,
|
3787
|
+
points,
|
3788
|
+
unbondingEras
|
3773
3789
|
};
|
3774
|
-
}
|
3790
|
+
};
|
3775
3791
|
return {
|
3776
|
-
|
3777
|
-
|
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
|
-
|
3784
|
-
|
3785
|
-
|
3786
|
-
|
3787
|
-
}
|
3788
|
-
|
3789
|
-
|
3790
|
-
|
3791
|
-
|
3792
|
-
|
3793
|
-
|
3794
|
-
const
|
3795
|
-
|
3796
|
-
|
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
|
-
|
3809
|
-
|
3810
|
-
|
3811
|
-
|
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
|
-
|
3836
|
-
|
3837
|
-
|
3838
|
-
|
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
|
-
|
3862
|
-
|
3863
|
-
|
3864
|
-
|
3865
|
-
|
3866
|
-
|
3867
|
-
|
3868
|
-
|
3869
|
-
|
3870
|
-
|
3871
|
-
|
3872
|
-
|
3873
|
-
|
3874
|
-
|
3875
|
-
|
3876
|
-
|
3877
|
-
|
3878
|
-
|
3879
|
-
|
3880
|
-
|
3881
|
-
|
3882
|
-
|
3883
|
-
|
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
|
-
|
3945
|
-
|
3946
|
-
|
3947
|
-
|
3948
|
-
|
3949
|
-
|
3950
|
-
|
3951
|
-
|
3952
|
-
|
3953
|
-
|
3954
|
-
|
3955
|
-
|
3956
|
-
|
3957
|
-
|
3958
|
-
|
3959
|
-
|
3960
|
-
|
3961
|
-
|
3962
|
-
|
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
|
-
|
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
|
-
|
4011
|
-
|
4012
|
-
|
4013
|
-
|
4014
|
-
|
4015
|
-
|
4016
|
-
|
4017
|
-
|
4018
|
-
|
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
|
-
|
4060
|
-
|
4061
|
-
|
4062
|
-
|
4063
|
-
|
4064
|
-
)
|
4065
|
-
|
4066
|
-
|
4067
|
-
|
4068
|
-
|
4069
|
-
|
4070
|
-
|
4071
|
-
|
4072
|
-
|
4073
|
-
|
4074
|
-
|
4075
|
-
|
4076
|
-
|
4077
|
-
|
4078
|
-
|
4079
|
-
|
4080
|
-
|
4081
|
-
|
4082
|
-
|
4083
|
-
|
4084
|
-
|
4085
|
-
|
4086
|
-
|
4087
|
-
|
4088
|
-
|
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
|
-
|
4093
|
-
|
4094
|
-
|
4095
|
-
|
4096
|
-
|
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
|
-
|
4099
|
-
|
4100
|
-
|
4101
|
-
|
4102
|
-
|
4103
|
-
|
4104
|
-
|
4105
|
-
|
4106
|
-
|
4107
|
-
|
4108
|
-
|
4109
|
-
|
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
|
4120
|
-
stake
|
4121
|
-
|
4122
|
-
|
4123
|
-
|
4124
|
-
|
4125
|
-
|
4126
|
-
|
4127
|
-
|
4128
|
-
|
4129
|
-
|
4130
|
-
|
4131
|
-
|
4132
|
-
return
|
4133
|
-
}
|
4134
|
-
|
4135
|
-
|
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
|
-
|
4140
|
-
|
4141
|
-
|
4142
|
-
|
4143
|
-
|
4144
|
-
|
4145
|
-
|
4146
|
-
|
4147
|
-
|
4148
|
-
|
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
|
-
|
4185
|
-
|
4186
|
-
|
4187
|
-
|
4188
|
-
|
4189
|
-
|
4190
|
-
|
4191
|
-
|
4192
|
-
|
4193
|
-
|
4194
|
-
|
4195
|
-
|
4196
|
-
|
4197
|
-
|
4198
|
-
|
4199
|
-
|
4200
|
-
|
4201
|
-
|
4202
|
-
|
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
|
-
|
4211
|
-
|
4212
|
-
|
4213
|
-
|
4214
|
-
|
4215
|
-
|
4216
|
-
|
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
|
-
|
4220
|
-
|
4221
|
-
|
4222
|
-
|
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
|
-
|
4226
|
-
|
4227
|
-
|
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
|
}));
|