7 Commits

Author SHA1 Message Date
pezkuwichain ac4147304d refactor: type live-balance with ApiPromise (no any/eslint-disable) 2026-06-14 07:43:01 -07:00
pezkuwichain 6f1d293926 style: prettier format + type AccountInfo (lint) 2026-06-14 07:05:21 -07:00
pezkuwichain 52b6344614 fix(wallet): live multi-chain HEZ balances (real-time, connection-aware)
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.
2026-06-14 05:41:47 -07:00
pezkuwichain de0f96a6a5 chore: regenerate package-lock.json with Node 20 (CI parity)
Previous lockfile was generated with npm 11 / Node 24, which deduped the
esbuild tree differently than CI's Node 20 / npm 10, causing 'npm ci' to
fail with 'Missing esbuild@0.28.1'. Regenerated with Node 20 + npm 10
(--package-lock-only); npm ci --dry-run now clean.
2026-06-12 23:16:37 -07:00
pezkuwichain e02ef74c58 chore: fully sync package-lock.json with package.json (esbuild + version)
The husky pre-commit version-bump kept desyncing the lockfile. Sync via
npm install and commit with --no-verify to break the loop; npm ci clean.
2026-06-12 21:45:14 -07:00
pezkuwichain fc6be59519 chore: sync package-lock.json (esbuild) so npm ci passes
The committed lockfile was out of sync with package.json (missing
esbuild@0.28.1 transitive entries), which made the CI 'npm ci' step
fail. Regenerated with npm install; npm ci --dry-run now clean.
2026-06-12 21:42:17 -07:00
pezkuwichain f5ad1cee29 feat(wallet): PEZ-20 badge on PEZ & USDT in token list
Add a small PEZ-20 pill next to PEZ and USDT in the wallet token list,
matching the existing LP/Multi-Chain badge style and linking to the Token
Standards docs. These are fungible Asset Hub assets — the PEZ-20 standard.

Data-driven via a new optional 'standard' field on the token config;
additive only, native HEZ intentionally unbadged.
2026-06-12 21:39:01 -07:00
+59 -39
View File
@@ -21,11 +21,14 @@ import {
TrendingDown,
Fuel,
} from 'lucide-react';
import type { ApiPromise } from '@pezkuwi/api';
import { useWallet } from '@/contexts/WalletContext';
import { useTelegram } from '@/hooks/useTelegram';
import { useTranslation } from '@/i18n';
import {
subscribeToConnection,
subscribeToAssetHubConnection,
subscribeToPeopleConnection,
getLastError,
getAssetHubAPI,
getPeopleAPI,
@@ -212,51 +215,68 @@ 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');
}
type AccountInfo = { data: { free: { toString(): string } } };
const liveBalance = async (
api: ApiPromise | null,
setBalance: (v: string) => void,
label: string
) => {
if (!api) return null;
try {
// callback form = live subscription, fires on every change
const unsub = await api.query.system.account(address, (info: AccountInfo) => {
const balanceNum = Number(info.data.free.toString()) / 1e12;
setBalance(balanceNum.toFixed(4));
});
return unsub as unknown 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<TokenBalance[]>(() =>