@talismn/balances 1.3.0 → 1.3.1

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.
@@ -1,11 +1,12 @@
1
1
  import { GetDynamicInfosResult, SubDTaoBalance } from "./types";
2
2
  type DynamicInfo = NonNullable<GetDynamicInfosResult[number]>;
3
- export declare const calculatePendingRootClaimable: ({ stake, hotkey, address, networkId, validatorRootClaimableRate, dynamicInfoByNetuid, }: {
3
+ export declare const calculatePendingRootClaimable: ({ stake, hotkey, address, networkId, validatorRootClaimableRate, dynamicInfoByNetuid, alreadyClaimedByNetuid, }: {
4
4
  stake: bigint;
5
5
  hotkey: string;
6
6
  address: string;
7
7
  networkId: string;
8
8
  validatorRootClaimableRate: Map<number, bigint>;
9
9
  dynamicInfoByNetuid: Record<number, DynamicInfo | undefined>;
10
+ alreadyClaimedByNetuid: Map<number, bigint>;
10
11
  }) => SubDTaoBalance[];
11
12
  export {};
@@ -2301,7 +2301,8 @@ const calculatePendingRootClaimable = ({
2301
2301
  address,
2302
2302
  networkId,
2303
2303
  validatorRootClaimableRate,
2304
- dynamicInfoByNetuid
2304
+ dynamicInfoByNetuid,
2305
+ alreadyClaimedByNetuid
2305
2306
  }) => {
2306
2307
  const pendingRootClaimBalances = [];
2307
2308
  for (const [netuid, claimableRate] of validatorRootClaimableRate) {
@@ -2315,7 +2316,11 @@ const calculatePendingRootClaimable = ({
2315
2316
 
2316
2317
  // Multiply claimable_rate by root_stake
2317
2318
  // I96F32 multiplication: round((a * b) / 2^32)
2318
- const pendingRootClaim = stake * claimableRate + (1n << 31n) >> 32n;
2319
+ const totalClaimable = stake * claimableRate + (1n << 31n) >> 32n;
2320
+
2321
+ // Subtract already claimed amount to get net pending claimable
2322
+ const alreadyClaimed = alreadyClaimedByNetuid.get(netuid) ?? 0n;
2323
+ const pendingRootClaim = totalClaimable > alreadyClaimed ? totalClaimable - alreadyClaimed : 0n;
2319
2324
  pendingRootClaimBalances.push({
2320
2325
  address,
2321
2326
  tokenId: chaindataProvider.subDTaoTokenId(networkId, netuid, hotkey),
@@ -2382,6 +2387,23 @@ const fetchBalances$5 = async ({
2382
2387
  const [stakeInfos, dynamicInfos] = await Promise.all([fetchRuntimeCallResult(connector, networkId, miniMetadata.data, "StakeInfoRuntimeApi", "get_stake_info_for_coldkeys", [addresses]), fetchRuntimeCallResult(connector, networkId, miniMetadata.data, "SubnetInfoRuntimeApi", "get_all_dynamic_info", [])]);
2383
2388
  const rootHotkeys = lodashEs.uniq(stakeInfos.flatMap(([, stakes]) => stakes.filter(stake => stake.netuid === ROOT_NETUID).map(stake => stake.hotkey)));
2384
2389
  const rootClaimableRatesByHotkey = rootHotkeys.length && miniMetadata.data ? await fetchRootClaimableRates(connector, networkId, miniMetadata.data, rootHotkeys) : new Map();
2390
+
2391
+ // Collect all (address, hotkey, netuid) pairs for root stakes to fetch RootClaimed amounts
2392
+ const addressHotkeyNetuidPairs = [];
2393
+ for (const [address, stakes] of stakeInfos) {
2394
+ for (const stake of stakes) {
2395
+ if (stake.netuid === ROOT_NETUID) {
2396
+ const claimableRates = rootClaimableRatesByHotkey.get(stake.hotkey);
2397
+ if (claimableRates) {
2398
+ // For each netuid that has a claimable rate, we need to check RootClaimed
2399
+ for (const netuid of claimableRates.keys()) {
2400
+ addressHotkeyNetuidPairs.push([address, stake.hotkey, netuid]);
2401
+ }
2402
+ }
2403
+ }
2404
+ }
2405
+ }
2406
+ const rootClaimedAmounts = addressHotkeyNetuidPairs.length && miniMetadata.data ? await fetchRootClaimedAmounts(connector, networkId, miniMetadata.data, addressHotkeyNetuidPairs) : new Map();
2385
2407
  const dynamicInfoByNetuid = lodashEs.keyBy(dynamicInfos.filter(util.isNotNil), info => info.netuid);
2386
2408
 
2387
2409
  // Upserts a balance into the accumulator, merging stake values if the balance already exists.
@@ -2421,13 +2443,16 @@ const fetchBalances$5 = async ({
2421
2443
 
2422
2444
  // Root stake cases, we need to calculate the pending root claim and add to the balances
2423
2445
  if (stake.netuid === ROOT_NETUID) {
2446
+ const claimableRates = rootClaimableRatesByHotkey.get(stake.hotkey) ?? new Map();
2447
+ const alreadyClaimedMap = rootClaimedAmounts.get(address)?.get(stake.hotkey) ?? new Map();
2424
2448
  const pendingRootClaimBalances = calculatePendingRootClaimable({
2425
2449
  stake: stake.stake,
2426
2450
  hotkey: stake.hotkey,
2427
2451
  address,
2428
2452
  networkId,
2429
- validatorRootClaimableRate: rootClaimableRatesByHotkey.get(stake.hotkey) ?? new Map(),
2430
- dynamicInfoByNetuid
2453
+ validatorRootClaimableRate: claimableRates,
2454
+ dynamicInfoByNetuid,
2455
+ alreadyClaimedByNetuid: alreadyClaimedMap
2431
2456
  });
2432
2457
  pendingRootClaimBalances.forEach(balance => {
2433
2458
  upsertBalance(acc, address, balance.tokenId, balance);
@@ -2542,6 +2567,19 @@ const buildRootClaimableStorageCoder = async (connector, networkId, metadataRpc)
2542
2567
  }
2543
2568
  return storageCoder;
2544
2569
  };
2570
+ const buildRootClaimedStorageCoder = async (networkId, metadataRpc) => {
2571
+ let storageCoder = null;
2572
+ if (metadataRpc) {
2573
+ try {
2574
+ storageCoder = buildStorageCoder(metadataRpc, "SubtensorModule", "RootClaimed");
2575
+ } catch (cause) {
2576
+ log.warn(`Failed to build storage coder for SubtensorModule.RootClaimed using provided metadata on ${networkId}`, {
2577
+ cause
2578
+ });
2579
+ }
2580
+ }
2581
+ return storageCoder;
2582
+ };
2545
2583
  const buildRootClaimableQueries = (networkId, hotkeys, storageCoder) => {
2546
2584
  return hotkeys.map(hotkey => {
2547
2585
  let stateKey = null;
@@ -2585,6 +2623,74 @@ const fetchRootClaimableRates = async (connector, networkId, metadataRpc, hotkey
2585
2623
  return new Map(hotkeys.map(hotkey => [hotkey, new Map()]));
2586
2624
  }
2587
2625
  };
2626
+ const buildRootClaimedQueries = (networkId, addressHotkeyNetuidPairs, storageCoder) => {
2627
+ return addressHotkeyNetuidPairs.map(([address, hotkey, netuid]) => {
2628
+ let stateKey = null;
2629
+ try {
2630
+ // RootClaimed storage takes params: [netuid, hotkey, coldkey_ss58]
2631
+ stateKey = storageCoder.keys.enc(netuid, hotkey, address);
2632
+ } catch (cause) {
2633
+ log.warn(`Failed to encode storage key for RootClaimed (netuid=${netuid}, hotkey=${hotkey}, address=${address}) on ${networkId}`, {
2634
+ cause
2635
+ });
2636
+ }
2637
+ const decodeResult = changes => {
2638
+ const hexValue = changes[0];
2639
+ if (!hexValue) {
2640
+ return [address, hotkey, netuid, 0n];
2641
+ }
2642
+ const decoded = scale.decodeScale(storageCoder, hexValue, `Failed to decode RootClaimed for (netuid=${netuid}, hotkey=${hotkey}, address=${address}) on ${networkId}`);
2643
+ return [address, hotkey, netuid, decoded ?? 0n];
2644
+ };
2645
+ return {
2646
+ stateKeys: [stateKey],
2647
+ decodeResult
2648
+ };
2649
+ });
2650
+ };
2651
+ const fetchRootClaimedAmounts = async (connector, networkId, metadataRpc, addressHotkeyNetuidPairs) => {
2652
+ if (!addressHotkeyNetuidPairs.length) {
2653
+ return new Map();
2654
+ }
2655
+ const storageCoder = await buildRootClaimedStorageCoder(networkId, metadataRpc);
2656
+ if (!storageCoder) {
2657
+ // Fallback: return empty map for all pairs
2658
+ const result = new Map();
2659
+ for (const [address, hotkey, netuid] of addressHotkeyNetuidPairs) {
2660
+ if (!result.has(address)) result.set(address, new Map());
2661
+ const addressMap = result.get(address);
2662
+ if (!addressMap.has(hotkey)) addressMap.set(hotkey, new Map());
2663
+ addressMap.get(hotkey).set(netuid, 0n);
2664
+ }
2665
+ return result;
2666
+ }
2667
+ const queries = buildRootClaimedQueries(networkId, addressHotkeyNetuidPairs, storageCoder);
2668
+ try {
2669
+ const results = await fetchRpcQueryPack(connector, networkId, queries);
2670
+ // Build a nested map: address -> hotkey -> netuid -> claimed amount
2671
+ const result = new Map();
2672
+ for (const [address, hotkey, netuid, claimed] of results) {
2673
+ if (!result.has(address)) result.set(address, new Map());
2674
+ const addressMap = result.get(address);
2675
+ if (!addressMap.has(hotkey)) addressMap.set(hotkey, new Map());
2676
+ addressMap.get(hotkey).set(netuid, claimed);
2677
+ }
2678
+ return result;
2679
+ } catch (cause) {
2680
+ log.warn(`Failed to fetch RootClaimed for address-hotkey-netuid pairs on ${networkId}`, {
2681
+ cause
2682
+ });
2683
+ // Fallback: return empty map for all pairs
2684
+ const result = new Map();
2685
+ for (const [address, hotkey, netuid] of addressHotkeyNetuidPairs) {
2686
+ if (!result.has(address)) result.set(address, new Map());
2687
+ const addressMap = result.get(address);
2688
+ if (!addressMap.has(hotkey)) addressMap.set(hotkey, new Map());
2689
+ addressMap.get(hotkey).set(netuid, 0n);
2690
+ }
2691
+ return result;
2692
+ }
2693
+ };
2588
2694
 
2589
2695
  // hardcoded because we dont have access to native tokens from the balance module
2590
2696
  const NATIVE_TOKEN_SYMBOLS = {
@@ -2684,7 +2790,7 @@ const getData$4 = metadataRpc => {
2684
2790
  if (!isBittensor) return null;
2685
2791
  scale.compactMetadata(metadata, [{
2686
2792
  pallet: "SubtensorModule",
2687
- items: ["TransferToggle", "RootClaimable"]
2793
+ items: ["TransferToggle", "RootClaimable", "RootClaimed"]
2688
2794
  }], [{
2689
2795
  runtimeApi: "StakeInfoRuntimeApi",
2690
2796
  methods: ["get_stake_info_for_coldkeys"]
@@ -2301,7 +2301,8 @@ const calculatePendingRootClaimable = ({
2301
2301
  address,
2302
2302
  networkId,
2303
2303
  validatorRootClaimableRate,
2304
- dynamicInfoByNetuid
2304
+ dynamicInfoByNetuid,
2305
+ alreadyClaimedByNetuid
2305
2306
  }) => {
2306
2307
  const pendingRootClaimBalances = [];
2307
2308
  for (const [netuid, claimableRate] of validatorRootClaimableRate) {
@@ -2315,7 +2316,11 @@ const calculatePendingRootClaimable = ({
2315
2316
 
2316
2317
  // Multiply claimable_rate by root_stake
2317
2318
  // I96F32 multiplication: round((a * b) / 2^32)
2318
- const pendingRootClaim = stake * claimableRate + (1n << 31n) >> 32n;
2319
+ const totalClaimable = stake * claimableRate + (1n << 31n) >> 32n;
2320
+
2321
+ // Subtract already claimed amount to get net pending claimable
2322
+ const alreadyClaimed = alreadyClaimedByNetuid.get(netuid) ?? 0n;
2323
+ const pendingRootClaim = totalClaimable > alreadyClaimed ? totalClaimable - alreadyClaimed : 0n;
2319
2324
  pendingRootClaimBalances.push({
2320
2325
  address,
2321
2326
  tokenId: chaindataProvider.subDTaoTokenId(networkId, netuid, hotkey),
@@ -2382,6 +2387,23 @@ const fetchBalances$5 = async ({
2382
2387
  const [stakeInfos, dynamicInfos] = await Promise.all([fetchRuntimeCallResult(connector, networkId, miniMetadata.data, "StakeInfoRuntimeApi", "get_stake_info_for_coldkeys", [addresses]), fetchRuntimeCallResult(connector, networkId, miniMetadata.data, "SubnetInfoRuntimeApi", "get_all_dynamic_info", [])]);
2383
2388
  const rootHotkeys = lodashEs.uniq(stakeInfos.flatMap(([, stakes]) => stakes.filter(stake => stake.netuid === ROOT_NETUID).map(stake => stake.hotkey)));
2384
2389
  const rootClaimableRatesByHotkey = rootHotkeys.length && miniMetadata.data ? await fetchRootClaimableRates(connector, networkId, miniMetadata.data, rootHotkeys) : new Map();
2390
+
2391
+ // Collect all (address, hotkey, netuid) pairs for root stakes to fetch RootClaimed amounts
2392
+ const addressHotkeyNetuidPairs = [];
2393
+ for (const [address, stakes] of stakeInfos) {
2394
+ for (const stake of stakes) {
2395
+ if (stake.netuid === ROOT_NETUID) {
2396
+ const claimableRates = rootClaimableRatesByHotkey.get(stake.hotkey);
2397
+ if (claimableRates) {
2398
+ // For each netuid that has a claimable rate, we need to check RootClaimed
2399
+ for (const netuid of claimableRates.keys()) {
2400
+ addressHotkeyNetuidPairs.push([address, stake.hotkey, netuid]);
2401
+ }
2402
+ }
2403
+ }
2404
+ }
2405
+ }
2406
+ const rootClaimedAmounts = addressHotkeyNetuidPairs.length && miniMetadata.data ? await fetchRootClaimedAmounts(connector, networkId, miniMetadata.data, addressHotkeyNetuidPairs) : new Map();
2385
2407
  const dynamicInfoByNetuid = lodashEs.keyBy(dynamicInfos.filter(util.isNotNil), info => info.netuid);
2386
2408
 
2387
2409
  // Upserts a balance into the accumulator, merging stake values if the balance already exists.
@@ -2421,13 +2443,16 @@ const fetchBalances$5 = async ({
2421
2443
 
2422
2444
  // Root stake cases, we need to calculate the pending root claim and add to the balances
2423
2445
  if (stake.netuid === ROOT_NETUID) {
2446
+ const claimableRates = rootClaimableRatesByHotkey.get(stake.hotkey) ?? new Map();
2447
+ const alreadyClaimedMap = rootClaimedAmounts.get(address)?.get(stake.hotkey) ?? new Map();
2424
2448
  const pendingRootClaimBalances = calculatePendingRootClaimable({
2425
2449
  stake: stake.stake,
2426
2450
  hotkey: stake.hotkey,
2427
2451
  address,
2428
2452
  networkId,
2429
- validatorRootClaimableRate: rootClaimableRatesByHotkey.get(stake.hotkey) ?? new Map(),
2430
- dynamicInfoByNetuid
2453
+ validatorRootClaimableRate: claimableRates,
2454
+ dynamicInfoByNetuid,
2455
+ alreadyClaimedByNetuid: alreadyClaimedMap
2431
2456
  });
2432
2457
  pendingRootClaimBalances.forEach(balance => {
2433
2458
  upsertBalance(acc, address, balance.tokenId, balance);
@@ -2542,6 +2567,19 @@ const buildRootClaimableStorageCoder = async (connector, networkId, metadataRpc)
2542
2567
  }
2543
2568
  return storageCoder;
2544
2569
  };
2570
+ const buildRootClaimedStorageCoder = async (networkId, metadataRpc) => {
2571
+ let storageCoder = null;
2572
+ if (metadataRpc) {
2573
+ try {
2574
+ storageCoder = buildStorageCoder(metadataRpc, "SubtensorModule", "RootClaimed");
2575
+ } catch (cause) {
2576
+ log.warn(`Failed to build storage coder for SubtensorModule.RootClaimed using provided metadata on ${networkId}`, {
2577
+ cause
2578
+ });
2579
+ }
2580
+ }
2581
+ return storageCoder;
2582
+ };
2545
2583
  const buildRootClaimableQueries = (networkId, hotkeys, storageCoder) => {
2546
2584
  return hotkeys.map(hotkey => {
2547
2585
  let stateKey = null;
@@ -2585,6 +2623,74 @@ const fetchRootClaimableRates = async (connector, networkId, metadataRpc, hotkey
2585
2623
  return new Map(hotkeys.map(hotkey => [hotkey, new Map()]));
2586
2624
  }
2587
2625
  };
2626
+ const buildRootClaimedQueries = (networkId, addressHotkeyNetuidPairs, storageCoder) => {
2627
+ return addressHotkeyNetuidPairs.map(([address, hotkey, netuid]) => {
2628
+ let stateKey = null;
2629
+ try {
2630
+ // RootClaimed storage takes params: [netuid, hotkey, coldkey_ss58]
2631
+ stateKey = storageCoder.keys.enc(netuid, hotkey, address);
2632
+ } catch (cause) {
2633
+ log.warn(`Failed to encode storage key for RootClaimed (netuid=${netuid}, hotkey=${hotkey}, address=${address}) on ${networkId}`, {
2634
+ cause
2635
+ });
2636
+ }
2637
+ const decodeResult = changes => {
2638
+ const hexValue = changes[0];
2639
+ if (!hexValue) {
2640
+ return [address, hotkey, netuid, 0n];
2641
+ }
2642
+ const decoded = scale.decodeScale(storageCoder, hexValue, `Failed to decode RootClaimed for (netuid=${netuid}, hotkey=${hotkey}, address=${address}) on ${networkId}`);
2643
+ return [address, hotkey, netuid, decoded ?? 0n];
2644
+ };
2645
+ return {
2646
+ stateKeys: [stateKey],
2647
+ decodeResult
2648
+ };
2649
+ });
2650
+ };
2651
+ const fetchRootClaimedAmounts = async (connector, networkId, metadataRpc, addressHotkeyNetuidPairs) => {
2652
+ if (!addressHotkeyNetuidPairs.length) {
2653
+ return new Map();
2654
+ }
2655
+ const storageCoder = await buildRootClaimedStorageCoder(networkId, metadataRpc);
2656
+ if (!storageCoder) {
2657
+ // Fallback: return empty map for all pairs
2658
+ const result = new Map();
2659
+ for (const [address, hotkey, netuid] of addressHotkeyNetuidPairs) {
2660
+ if (!result.has(address)) result.set(address, new Map());
2661
+ const addressMap = result.get(address);
2662
+ if (!addressMap.has(hotkey)) addressMap.set(hotkey, new Map());
2663
+ addressMap.get(hotkey).set(netuid, 0n);
2664
+ }
2665
+ return result;
2666
+ }
2667
+ const queries = buildRootClaimedQueries(networkId, addressHotkeyNetuidPairs, storageCoder);
2668
+ try {
2669
+ const results = await fetchRpcQueryPack(connector, networkId, queries);
2670
+ // Build a nested map: address -> hotkey -> netuid -> claimed amount
2671
+ const result = new Map();
2672
+ for (const [address, hotkey, netuid, claimed] of results) {
2673
+ if (!result.has(address)) result.set(address, new Map());
2674
+ const addressMap = result.get(address);
2675
+ if (!addressMap.has(hotkey)) addressMap.set(hotkey, new Map());
2676
+ addressMap.get(hotkey).set(netuid, claimed);
2677
+ }
2678
+ return result;
2679
+ } catch (cause) {
2680
+ log.warn(`Failed to fetch RootClaimed for address-hotkey-netuid pairs on ${networkId}`, {
2681
+ cause
2682
+ });
2683
+ // Fallback: return empty map for all pairs
2684
+ const result = new Map();
2685
+ for (const [address, hotkey, netuid] of addressHotkeyNetuidPairs) {
2686
+ if (!result.has(address)) result.set(address, new Map());
2687
+ const addressMap = result.get(address);
2688
+ if (!addressMap.has(hotkey)) addressMap.set(hotkey, new Map());
2689
+ addressMap.get(hotkey).set(netuid, 0n);
2690
+ }
2691
+ return result;
2692
+ }
2693
+ };
2588
2694
 
2589
2695
  // hardcoded because we dont have access to native tokens from the balance module
2590
2696
  const NATIVE_TOKEN_SYMBOLS = {
@@ -2684,7 +2790,7 @@ const getData$4 = metadataRpc => {
2684
2790
  if (!isBittensor) return null;
2685
2791
  scale.compactMetadata(metadata, [{
2686
2792
  pallet: "SubtensorModule",
2687
- items: ["TransferToggle", "RootClaimable"]
2793
+ items: ["TransferToggle", "RootClaimable", "RootClaimed"]
2688
2794
  }], [{
2689
2795
  runtimeApi: "StakeInfoRuntimeApi",
2690
2796
  methods: ["get_stake_info_for_coldkeys"]
@@ -2292,7 +2292,8 @@ const calculatePendingRootClaimable = ({
2292
2292
  address,
2293
2293
  networkId,
2294
2294
  validatorRootClaimableRate,
2295
- dynamicInfoByNetuid
2295
+ dynamicInfoByNetuid,
2296
+ alreadyClaimedByNetuid
2296
2297
  }) => {
2297
2298
  const pendingRootClaimBalances = [];
2298
2299
  for (const [netuid, claimableRate] of validatorRootClaimableRate) {
@@ -2306,7 +2307,11 @@ const calculatePendingRootClaimable = ({
2306
2307
 
2307
2308
  // Multiply claimable_rate by root_stake
2308
2309
  // I96F32 multiplication: round((a * b) / 2^32)
2309
- const pendingRootClaim = stake * claimableRate + (1n << 31n) >> 32n;
2310
+ const totalClaimable = stake * claimableRate + (1n << 31n) >> 32n;
2311
+
2312
+ // Subtract already claimed amount to get net pending claimable
2313
+ const alreadyClaimed = alreadyClaimedByNetuid.get(netuid) ?? 0n;
2314
+ const pendingRootClaim = totalClaimable > alreadyClaimed ? totalClaimable - alreadyClaimed : 0n;
2310
2315
  pendingRootClaimBalances.push({
2311
2316
  address,
2312
2317
  tokenId: subDTaoTokenId(networkId, netuid, hotkey),
@@ -2373,6 +2378,23 @@ const fetchBalances$5 = async ({
2373
2378
  const [stakeInfos, dynamicInfos] = await Promise.all([fetchRuntimeCallResult(connector, networkId, miniMetadata.data, "StakeInfoRuntimeApi", "get_stake_info_for_coldkeys", [addresses]), fetchRuntimeCallResult(connector, networkId, miniMetadata.data, "SubnetInfoRuntimeApi", "get_all_dynamic_info", [])]);
2374
2379
  const rootHotkeys = uniq(stakeInfos.flatMap(([, stakes]) => stakes.filter(stake => stake.netuid === ROOT_NETUID).map(stake => stake.hotkey)));
2375
2380
  const rootClaimableRatesByHotkey = rootHotkeys.length && miniMetadata.data ? await fetchRootClaimableRates(connector, networkId, miniMetadata.data, rootHotkeys) : new Map();
2381
+
2382
+ // Collect all (address, hotkey, netuid) pairs for root stakes to fetch RootClaimed amounts
2383
+ const addressHotkeyNetuidPairs = [];
2384
+ for (const [address, stakes] of stakeInfos) {
2385
+ for (const stake of stakes) {
2386
+ if (stake.netuid === ROOT_NETUID) {
2387
+ const claimableRates = rootClaimableRatesByHotkey.get(stake.hotkey);
2388
+ if (claimableRates) {
2389
+ // For each netuid that has a claimable rate, we need to check RootClaimed
2390
+ for (const netuid of claimableRates.keys()) {
2391
+ addressHotkeyNetuidPairs.push([address, stake.hotkey, netuid]);
2392
+ }
2393
+ }
2394
+ }
2395
+ }
2396
+ }
2397
+ const rootClaimedAmounts = addressHotkeyNetuidPairs.length && miniMetadata.data ? await fetchRootClaimedAmounts(connector, networkId, miniMetadata.data, addressHotkeyNetuidPairs) : new Map();
2376
2398
  const dynamicInfoByNetuid = keyBy(dynamicInfos.filter(isNotNil), info => info.netuid);
2377
2399
 
2378
2400
  // Upserts a balance into the accumulator, merging stake values if the balance already exists.
@@ -2412,13 +2434,16 @@ const fetchBalances$5 = async ({
2412
2434
 
2413
2435
  // Root stake cases, we need to calculate the pending root claim and add to the balances
2414
2436
  if (stake.netuid === ROOT_NETUID) {
2437
+ const claimableRates = rootClaimableRatesByHotkey.get(stake.hotkey) ?? new Map();
2438
+ const alreadyClaimedMap = rootClaimedAmounts.get(address)?.get(stake.hotkey) ?? new Map();
2415
2439
  const pendingRootClaimBalances = calculatePendingRootClaimable({
2416
2440
  stake: stake.stake,
2417
2441
  hotkey: stake.hotkey,
2418
2442
  address,
2419
2443
  networkId,
2420
- validatorRootClaimableRate: rootClaimableRatesByHotkey.get(stake.hotkey) ?? new Map(),
2421
- dynamicInfoByNetuid
2444
+ validatorRootClaimableRate: claimableRates,
2445
+ dynamicInfoByNetuid,
2446
+ alreadyClaimedByNetuid: alreadyClaimedMap
2422
2447
  });
2423
2448
  pendingRootClaimBalances.forEach(balance => {
2424
2449
  upsertBalance(acc, address, balance.tokenId, balance);
@@ -2533,6 +2558,19 @@ const buildRootClaimableStorageCoder = async (connector, networkId, metadataRpc)
2533
2558
  }
2534
2559
  return storageCoder;
2535
2560
  };
2561
+ const buildRootClaimedStorageCoder = async (networkId, metadataRpc) => {
2562
+ let storageCoder = null;
2563
+ if (metadataRpc) {
2564
+ try {
2565
+ storageCoder = buildStorageCoder(metadataRpc, "SubtensorModule", "RootClaimed");
2566
+ } catch (cause) {
2567
+ log.warn(`Failed to build storage coder for SubtensorModule.RootClaimed using provided metadata on ${networkId}`, {
2568
+ cause
2569
+ });
2570
+ }
2571
+ }
2572
+ return storageCoder;
2573
+ };
2536
2574
  const buildRootClaimableQueries = (networkId, hotkeys, storageCoder) => {
2537
2575
  return hotkeys.map(hotkey => {
2538
2576
  let stateKey = null;
@@ -2576,6 +2614,74 @@ const fetchRootClaimableRates = async (connector, networkId, metadataRpc, hotkey
2576
2614
  return new Map(hotkeys.map(hotkey => [hotkey, new Map()]));
2577
2615
  }
2578
2616
  };
2617
+ const buildRootClaimedQueries = (networkId, addressHotkeyNetuidPairs, storageCoder) => {
2618
+ return addressHotkeyNetuidPairs.map(([address, hotkey, netuid]) => {
2619
+ let stateKey = null;
2620
+ try {
2621
+ // RootClaimed storage takes params: [netuid, hotkey, coldkey_ss58]
2622
+ stateKey = storageCoder.keys.enc(netuid, hotkey, address);
2623
+ } catch (cause) {
2624
+ log.warn(`Failed to encode storage key for RootClaimed (netuid=${netuid}, hotkey=${hotkey}, address=${address}) on ${networkId}`, {
2625
+ cause
2626
+ });
2627
+ }
2628
+ const decodeResult = changes => {
2629
+ const hexValue = changes[0];
2630
+ if (!hexValue) {
2631
+ return [address, hotkey, netuid, 0n];
2632
+ }
2633
+ const decoded = decodeScale(storageCoder, hexValue, `Failed to decode RootClaimed for (netuid=${netuid}, hotkey=${hotkey}, address=${address}) on ${networkId}`);
2634
+ return [address, hotkey, netuid, decoded ?? 0n];
2635
+ };
2636
+ return {
2637
+ stateKeys: [stateKey],
2638
+ decodeResult
2639
+ };
2640
+ });
2641
+ };
2642
+ const fetchRootClaimedAmounts = async (connector, networkId, metadataRpc, addressHotkeyNetuidPairs) => {
2643
+ if (!addressHotkeyNetuidPairs.length) {
2644
+ return new Map();
2645
+ }
2646
+ const storageCoder = await buildRootClaimedStorageCoder(networkId, metadataRpc);
2647
+ if (!storageCoder) {
2648
+ // Fallback: return empty map for all pairs
2649
+ const result = new Map();
2650
+ for (const [address, hotkey, netuid] of addressHotkeyNetuidPairs) {
2651
+ if (!result.has(address)) result.set(address, new Map());
2652
+ const addressMap = result.get(address);
2653
+ if (!addressMap.has(hotkey)) addressMap.set(hotkey, new Map());
2654
+ addressMap.get(hotkey).set(netuid, 0n);
2655
+ }
2656
+ return result;
2657
+ }
2658
+ const queries = buildRootClaimedQueries(networkId, addressHotkeyNetuidPairs, storageCoder);
2659
+ try {
2660
+ const results = await fetchRpcQueryPack(connector, networkId, queries);
2661
+ // Build a nested map: address -> hotkey -> netuid -> claimed amount
2662
+ const result = new Map();
2663
+ for (const [address, hotkey, netuid, claimed] of results) {
2664
+ if (!result.has(address)) result.set(address, new Map());
2665
+ const addressMap = result.get(address);
2666
+ if (!addressMap.has(hotkey)) addressMap.set(hotkey, new Map());
2667
+ addressMap.get(hotkey).set(netuid, claimed);
2668
+ }
2669
+ return result;
2670
+ } catch (cause) {
2671
+ log.warn(`Failed to fetch RootClaimed for address-hotkey-netuid pairs on ${networkId}`, {
2672
+ cause
2673
+ });
2674
+ // Fallback: return empty map for all pairs
2675
+ const result = new Map();
2676
+ for (const [address, hotkey, netuid] of addressHotkeyNetuidPairs) {
2677
+ if (!result.has(address)) result.set(address, new Map());
2678
+ const addressMap = result.get(address);
2679
+ if (!addressMap.has(hotkey)) addressMap.set(hotkey, new Map());
2680
+ addressMap.get(hotkey).set(netuid, 0n);
2681
+ }
2682
+ return result;
2683
+ }
2684
+ };
2579
2685
 
2580
2686
  // hardcoded because we dont have access to native tokens from the balance module
2581
2687
  const NATIVE_TOKEN_SYMBOLS = {
@@ -2675,7 +2781,7 @@ const getData$4 = metadataRpc => {
2675
2781
  if (!isBittensor) return null;
2676
2782
  compactMetadata(metadata, [{
2677
2783
  pallet: "SubtensorModule",
2678
- items: ["TransferToggle", "RootClaimable"]
2784
+ items: ["TransferToggle", "RootClaimable", "RootClaimed"]
2679
2785
  }], [{
2680
2786
  runtimeApi: "StakeInfoRuntimeApi",
2681
2787
  methods: ["get_stake_info_for_coldkeys"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talismn/balances",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "GPL-3.0-or-later",
@@ -38,13 +38,13 @@
38
38
  "scale-ts": "^1.6.1",
39
39
  "viem": "^2.27.3",
40
40
  "zod": "^3.25.76",
41
- "@talismn/chain-connectors": "0.0.12",
42
- "@talismn/sapi": "0.1.0",
43
- "@talismn/chaindata-provider": "1.3.3",
41
+ "@talismn/chaindata-provider": "1.3.4",
44
42
  "@talismn/crypto": "0.3.0",
43
+ "@talismn/chain-connectors": "0.0.13",
44
+ "@talismn/sapi": "0.1.0",
45
45
  "@talismn/scale": "0.3.0",
46
- "@talismn/token-rates": "3.0.14",
47
46
  "@talismn/solana": "0.0.5",
47
+ "@talismn/token-rates": "3.0.15",
48
48
  "@talismn/util": "0.5.6"
49
49
  },
50
50
  "devDependencies": {
@@ -60,8 +60,8 @@
60
60
  "jest": "^29.7.0",
61
61
  "ts-jest": "^29.2.5",
62
62
  "typescript": "^5.6.3",
63
- "@talismn/tsconfig": "0.0.3",
64
- "@talismn/eslint-config": "0.0.3"
63
+ "@talismn/eslint-config": "0.0.3",
64
+ "@talismn/tsconfig": "0.0.3"
65
65
  },
66
66
  "peerDependencies": {
67
67
  "@polkadot/api-contract": "*",