From f667e658d76a0ac0cfaf07f79057b9c360931c3a Mon Sep 17 00:00:00 2001 From: rajk93 Date: Thu, 11 Dec 2025 17:52:06 +0530 Subject: [PATCH] fix: Correct bag threshold display with CurrencyToVote conversion (#12058) * fix: bag threshold display with CurrencyToVote conversion * chore: update comments --- packages/page-staking/src/Bags/useBagsList.ts | 34 +++++++++-- packages/react-hooks/src/index.ts | 1 + packages/react-hooks/src/useCurrencyToVote.ts | 58 +++++++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 packages/react-hooks/src/useCurrencyToVote.ts diff --git a/packages/page-staking/src/Bags/useBagsList.ts b/packages/page-staking/src/Bags/useBagsList.ts index 95db6871d0..28dabc4429 100644 --- a/packages/page-staking/src/Bags/useBagsList.ts +++ b/packages/page-staking/src/Bags/useBagsList.ts @@ -6,9 +6,9 @@ import type { PalletBagsListListBag } from '@polkadot/types/lookup'; import type { BN } from '@polkadot/util'; import type { BagInfo } from './types.js'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; -import { createNamedHook, useCall, useMapKeys } from '@polkadot/react-hooks'; +import { createNamedHook, useApi, useCall, useCurrencyToVote, useMapKeys } from '@polkadot/react-hooks'; import { BN_ZERO } from '@polkadot/util'; import useQueryModule from './useQueryModule.js'; @@ -54,13 +54,39 @@ function merge (prev: BagInfo[] | undefined, curr: BagInfo[]): BagInfo[] { function useBagsListImpl (): BagInfo[] | undefined { const mod = useQueryModule(); + const { api } = useApi(); + const { toCurrency } = useCurrencyToVote(); const [result, setResult] = useState(); const ids = useMapKeys(mod.listBags, [], KEY_OPTS); const query = useCall(ids && ids.length !== 0 && mod.listBags.multi, [ids], MULTI_OPTS); + const converted = useMemo( + () => { + if (!query || query.length === 0) { + return undefined; + } + + // Westend Asset Hub uses SaturatingCurrencyToVote (no conversion needed) + // See: https://github.com/paritytech/polkadot-sdk/pull/8307 + const chainName = api.runtimeChain.toString().toLowerCase(); + const isWestendAssetHub = chainName.includes('westend') && chainName.includes('asset'); + + if (isWestendAssetHub) { + return query; + } + + return query.map((bag) => ({ + ...bag, + bagLower: toCurrency(bag.bagLower), + bagUpper: toCurrency(bag.bagUpper) + })); + }, + [api.runtimeChain, query, toCurrency] + ); + useEffect((): void => { - query && setResult((prev) => merge(prev, query)); - }, [query]); + converted && setResult((prev) => merge(prev, converted)); + }, [converted]); return result; } diff --git a/packages/react-hooks/src/index.ts b/packages/react-hooks/src/index.ts index c6d38094c1..2516ed1c0d 100644 --- a/packages/react-hooks/src/index.ts +++ b/packages/react-hooks/src/index.ts @@ -38,6 +38,7 @@ export { useCoreDescriptor } from './useCoreDescriptor.js'; export { useCoretimeConsts } from './useCoretimeConsts.js'; export { useCoretimeEndpoint } from './useCoretimeEndpoint.js'; export { useCoretimeInformation } from './useCoretimeInformation.js'; +export { useCurrencyToVote } from './useCurrencyToVote.js'; export { useDebounce } from './useDebounce.js'; export { useDelegations } from './useDelegations.js'; export { useDeriveAccountFlags } from './useDeriveAccountFlags.js'; diff --git a/packages/react-hooks/src/useCurrencyToVote.ts b/packages/react-hooks/src/useCurrencyToVote.ts new file mode 100644 index 0000000000..eb6a403dfd --- /dev/null +++ b/packages/react-hooks/src/useCurrencyToVote.ts @@ -0,0 +1,58 @@ +// Copyright 2017-2025 @polkadot/react-hooks authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { useMemo } from 'react'; + +import { BN, BN_ONE } from '@polkadot/util'; + +import { createNamedHook } from './createNamedHook.js'; +import { useApi } from './useApi.js'; +import { useCall } from './useCall.js'; + +// u64::MAX = 2^64 - 1 = 18,446,744,073,709,551,615 +const U64_MAX = new BN('18446744073709551615'); + +interface CurrencyToVote { + // The factor to convert from currency to vote + factor: BN; + // Convert a currency balance to voting power + toVote: (balance: BN) => BN; + // Convert voting power back to currency balance + toCurrency: (vote: BN) => BN; +} + +/** + * Calculate the CurrencyToVote factor based on total issuance. + * + * - If total_issuance < 2^64, then factor = 1 (no conversion needed) + * - If total_issuance >= 2^64, then factor = (total_issuance / 2^64).max(1) + * + * The conversion formulas is: + * - vote = balance / factor + */ +function calculateFactor (totalIssuance: BN): BN { + if (totalIssuance.lte(U64_MAX)) { + return BN_ONE; + } + + const factor = totalIssuance.div(U64_MAX); + + return factor.isZero() ? BN_ONE : factor; +} + +function useCurrencyToVoteImpl (): CurrencyToVote { + const { api } = useApi(); + const totalIssuance = useCall(api.query.balances?.totalIssuance); + + return useMemo((): CurrencyToVote => { + const factor = totalIssuance ? calculateFactor(totalIssuance) : BN_ONE; + + return { + factor, + toCurrency: (vote: BN) => vote.mul(factor), + toVote: (balance: BN) => balance.div(factor) + }; + }, [totalIssuance]); +} + +export const useCurrencyToVote = createNamedHook('useCurrencyToVote', useCurrencyToVoteImpl);