From 52b634461414649267235fc2aa035ebddc538302 Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Sun, 14 Jun 2026 05:41:47 -0700 Subject: [PATCH] fix(wallet): live multi-chain HEZ balances (real-time, connection-aware) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Asset Hub / People Chain HEZ balances were fetched on [address, rpcConnected] + a 30s poll, so they didn't react to the Asset Hub/People connection becoming ready — People HEZ could sit at '--' until a later trigger (e.g. a transaction). Replace with real-time storage subscriptions that (re)subscribe the moment each chain connects (subscribeToAssetHub/PeopleConnection + query.system.account(addr, cb)). Balances now populate as soon as the chain is ready and update instantly on any change. --- src/components/wallet/TokensCard.tsx | 84 +++++++++++++++------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/src/components/wallet/TokensCard.tsx b/src/components/wallet/TokensCard.tsx index 2cfdea5..1b4babf 100644 --- a/src/components/wallet/TokensCard.tsx +++ b/src/components/wallet/TokensCard.tsx @@ -26,6 +26,8 @@ import { useTelegram } from '@/hooks/useTelegram'; import { useTranslation } from '@/i18n'; import { subscribeToConnection, + subscribeToAssetHubConnection, + subscribeToPeopleConnection, getLastError, getAssetHubAPI, getPeopleAPI, @@ -212,51 +214,55 @@ export function TokensCard({ onSendToken }: Props) { return () => unsubscribe(); }, []); - // Fetch multi-chain HEZ balances (Asset Hub & People Chain) + // Live multi-chain HEZ balances (Asset Hub & People Chain). + // Uses real-time storage subscriptions and (re)subscribes the moment each + // chain connects — so balances populate as soon as the chain is ready and + // update instantly on any change (no 30s polling lag, no stuck "--"). useEffect(() => { if (!address) return; + let cancelled = false; + let ahBalUnsub: (() => void) | null = null; + let peopleBalUnsub: (() => void) | null = null; - const fetchMultiChainBalances = async () => { - // Asset Hub HEZ balance - const assetHubApi = getAssetHubAPI(); - if (assetHubApi) { - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const accountInfo = (await (assetHubApi.query.system as any).account(address)) as { - data: { free: { toString(): string } }; - }; - const free = accountInfo.data.free.toString(); - const balanceNum = Number(free) / 1e12; - setAssetHubHezBalance(balanceNum.toFixed(4)); - } catch (err) { - console.error('Error fetching Asset Hub HEZ balance:', err); - setAssetHubHezBalance('0.0000'); - } - } - - // People Chain HEZ balance - const peopleApi = getPeopleAPI(); - if (peopleApi) { - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const accountInfo = (await (peopleApi.query.system as any).account(address)) as { - data: { free: { toString(): string } }; - }; - const free = accountInfo.data.free.toString(); - const balanceNum = Number(free) / 1e12; - setPeopleHezBalance(balanceNum.toFixed(4)); - } catch (err) { - console.error('Error fetching People Chain HEZ balance:', err); - setPeopleHezBalance('0.0000'); - } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const liveBalance = async (api: any, setBalance: (v: string) => void, label: string) => { + if (!api) return null; + try { + // callback form = live subscription, fires on every change + return (await api.query.system.account(address, (info: any) => { + const balanceNum = Number(info.data.free.toString()) / 1e12; + setBalance(balanceNum.toFixed(4)); + })) as () => void; + } catch (err) { + console.error(`Error subscribing to ${label} HEZ balance:`, err); + return null; } }; - fetchMultiChainBalances(); - // Refresh every 30 seconds - const interval = setInterval(fetchMultiChainBalances, 30000); - return () => clearInterval(interval); - }, [address, rpcConnected]); + const unsubAhConn = subscribeToAssetHubConnection(async (connected) => { + if (ahBalUnsub) { ahBalUnsub(); ahBalUnsub = null; } + if (connected) { + const u = await liveBalance(getAssetHubAPI(), setAssetHubHezBalance, 'Asset Hub'); + if (cancelled) u?.(); else ahBalUnsub = u; + } + }); + + const unsubPeopleConn = subscribeToPeopleConnection(async (connected) => { + if (peopleBalUnsub) { peopleBalUnsub(); peopleBalUnsub = null; } + if (connected) { + const u = await liveBalance(getPeopleAPI(), setPeopleHezBalance, 'People Chain'); + if (cancelled) u?.(); else peopleBalUnsub = u; + } + }); + + return () => { + cancelled = true; + if (ahBalUnsub) ahBalUnsub(); + if (peopleBalUnsub) peopleBalUnsub(); + unsubAhConn(); + unsubPeopleConn(); + }; + }, [address]); // Initialize with default tokens immediately (no API required) const [tokens, setTokens] = useState(() =>