fix: query PEZ balance from Asset Hub and prevent localhost WebSocket errors

- Add Asset Hub API connection for PEZ token queries (Asset ID 1 is on Asset Hub)
- Update AccountBalance to fetch PEZ balance from Asset Hub instead of main chain
- WebSocket: Only try localhost endpoints when running locally, use production
  endpoint directly when on domain
This commit is contained in:
2026-02-04 09:24:03 +03:00
parent 9bb71c436b
commit 3642ee0cd1
3 changed files with 99 additions and 37 deletions
+38 -31
View File
@@ -18,7 +18,7 @@ interface TokenBalance {
} }
export const AccountBalance: React.FC = () => { export const AccountBalance: React.FC = () => {
const { api, isApiReady, selectedAccount } = usePezkuwi(); const { api, assetHubApi, isApiReady, isAssetHubReady, selectedAccount } = usePezkuwi();
const [balance, setBalance] = useState<{ const [balance, setBalance] = useState<{
free: string; free: string;
reserved: string; reserved: string;
@@ -318,21 +318,26 @@ export const AccountBalance: React.FC = () => {
total: totalTokens, total: totalTokens,
}); });
// Fetch PEZ balance (Asset ID: 1) // Fetch PEZ balance (Asset ID: 1) from Asset Hub
try { try {
const pezAssetBalance = await api.query.assets.account(1, selectedAccount.address); if (assetHubApi && isAssetHubReady) {
const pezAssetBalance = await assetHubApi.query.assets.account(1, selectedAccount.address);
if (pezAssetBalance.isSome) { if (pezAssetBalance.isSome) {
const assetData = pezAssetBalance.unwrap(); const assetData = pezAssetBalance.unwrap();
const pezAmount = assetData.balance.toString(); const pezAmount = assetData.balance.toString();
const pezTokens = (parseInt(pezAmount) / divisor).toFixed(4); const pezTokens = (parseInt(pezAmount) / divisor).toFixed(4);
setPezBalance(pezTokens); setPezBalance(pezTokens);
} else {
setPezBalance('0.0000');
}
} else { } else {
setPezBalance('0'); if (import.meta.env.DEV) console.log('Asset Hub not ready, PEZ balance pending...');
setPezBalance('0.0000');
} }
} catch (error) { } catch (error) {
if (import.meta.env.DEV) console.error('Failed to fetch PEZ balance:', error); if (import.meta.env.DEV) console.error('Failed to fetch PEZ balance from Asset Hub:', error);
setPezBalance('0'); setPezBalance('0.0000');
} }
// Fetch USDT balance (wUSDT - Asset ID: 1000) // Fetch USDT balance (wUSDT - Asset ID: 1000)
@@ -460,26 +465,28 @@ export const AccountBalance: React.FC = () => {
} }
); );
// Subscribe to PEZ balance (Asset ID: 1) // Subscribe to PEZ balance (Asset ID: 1) from Asset Hub
try { if (assetHubApi && isAssetHubReady) {
unsubscribePez = await api.query.assets.account( try {
1, unsubscribePez = await assetHubApi.query.assets.account(
selectedAccount.address, 1,
(assetBalance) => { selectedAccount.address,
if (assetBalance.isSome) { (assetBalance) => {
const assetData = assetBalance.unwrap(); if (assetBalance.isSome) {
const pezAmount = assetData.balance.toString(); const assetData = assetBalance.unwrap();
const decimals = 12; const pezAmount = assetData.balance.toString();
const divisor = Math.pow(10, decimals); const decimals = 12;
const pezTokens = (parseInt(pezAmount) / divisor).toFixed(4); const divisor = Math.pow(10, decimals);
setPezBalance(pezTokens); const pezTokens = (parseInt(pezAmount) / divisor).toFixed(4);
} else { setPezBalance(pezTokens);
setPezBalance('0'); } else {
setPezBalance('0.0000');
}
} }
} );
); } catch (error) {
} catch (error) { if (import.meta.env.DEV) console.error('Failed to subscribe to PEZ balance from Asset Hub:', error);
if (import.meta.env.DEV) console.error('Failed to subscribe to PEZ balance:', error); }
} }
// Subscribe to USDT balance (wUSDT - Asset ID: 2) // Subscribe to USDT balance (wUSDT - Asset ID: 2)
@@ -513,7 +520,7 @@ export const AccountBalance: React.FC = () => {
if (unsubscribeUsdt) unsubscribeUsdt(); if (unsubscribeUsdt) unsubscribeUsdt();
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [api, isApiReady, selectedAccount]); }, [api, assetHubApi, isApiReady, isAssetHubReady, selectedAccount]);
if (!selectedAccount) { if (!selectedAccount) {
return ( return (
+47
View File
@@ -5,9 +5,14 @@ import type { InjectedAccountWithMeta } from '@pezkuwi/extension-inject/types';
import { DEFAULT_ENDPOINT } from '../../../shared/blockchain/pezkuwi'; import { DEFAULT_ENDPOINT } from '../../../shared/blockchain/pezkuwi';
import { isMobileApp, getNativeWalletAddress, getNativeAccountName } from '@/lib/mobile-bridge'; import { isMobileApp, getNativeWalletAddress, getNativeAccountName } from '@/lib/mobile-bridge';
// Asset Hub endpoint for PEZ token queries
const ASSET_HUB_ENDPOINT = 'wss://asset-hub-rpc.pezkuwichain.io';
interface PezkuwiContextType { interface PezkuwiContextType {
api: ApiPromise | null; api: ApiPromise | null;
assetHubApi: ApiPromise | null;
isApiReady: boolean; isApiReady: boolean;
isAssetHubReady: boolean;
isConnected: boolean; isConnected: boolean;
accounts: InjectedAccountWithMeta[]; accounts: InjectedAccountWithMeta[];
selectedAccount: InjectedAccountWithMeta | null; selectedAccount: InjectedAccountWithMeta | null;
@@ -30,7 +35,9 @@ export const PezkuwiProvider: React.FC<PezkuwiProviderProps> = ({
endpoint = DEFAULT_ENDPOINT // Beta testnet RPC from shared config endpoint = DEFAULT_ENDPOINT // Beta testnet RPC from shared config
}) => { }) => {
const [api, setApi] = useState<ApiPromise | null>(null); const [api, setApi] = useState<ApiPromise | null>(null);
const [assetHubApi, setAssetHubApi] = useState<ApiPromise | null>(null);
const [isApiReady, setIsApiReady] = useState(false); const [isApiReady, setIsApiReady] = useState(false);
const [isAssetHubReady, setIsAssetHubReady] = useState(false);
const [accounts, setAccounts] = useState<InjectedAccountWithMeta[]>([]); const [accounts, setAccounts] = useState<InjectedAccountWithMeta[]>([]);
const [selectedAccount, setSelectedAccount] = useState<InjectedAccountWithMeta | null>(null); const [selectedAccount, setSelectedAccount] = useState<InjectedAccountWithMeta | null>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@@ -127,12 +134,50 @@ export const PezkuwiProvider: React.FC<PezkuwiProviderProps> = ({
setIsApiReady(false); setIsApiReady(false);
}; };
// Initialize Asset Hub API for PEZ token
const initAssetHubApi = async () => {
try {
if (import.meta.env.DEV) {
console.log('🔗 Connecting to Asset Hub:', ASSET_HUB_ENDPOINT);
}
const provider = new WsProvider(ASSET_HUB_ENDPOINT);
const assetHubApiInstance = await ApiPromise.create({
provider,
signedExtensions: {
AuthorizeCall: {
extrinsic: {},
payload: {},
},
},
});
await assetHubApiInstance.isReady;
setAssetHubApi(assetHubApiInstance);
setIsAssetHubReady(true);
if (import.meta.env.DEV) {
console.log('✅ Connected to Asset Hub for PEZ token');
}
} catch (err) {
if (import.meta.env.DEV) {
console.error('❌ Failed to connect to Asset Hub:', err);
}
// Don't set error - PEZ features just won't work
}
};
initApi(); initApi();
initAssetHubApi();
return () => { return () => {
if (api) { if (api) {
api.disconnect(); api.disconnect();
} }
if (assetHubApi) {
assetHubApi.disconnect();
}
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [endpoint]); }, [endpoint]);
@@ -311,7 +356,9 @@ export const PezkuwiProvider: React.FC<PezkuwiProviderProps> = ({
const value: PezkuwiContextType = { const value: PezkuwiContextType = {
api, api,
assetHubApi,
isApiReady, isApiReady,
isAssetHubReady,
isConnected: isApiReady, // Alias for backward compatibility isConnected: isApiReady, // Alias for backward compatibility
accounts, accounts,
selectedAccount, selectedAccount,
+14 -6
View File
@@ -17,12 +17,20 @@ interface WebSocketContextType {
const WebSocketContext = createContext<WebSocketContextType | null>(null); const WebSocketContext = createContext<WebSocketContextType | null>(null);
const ENDPOINTS = [ // Only use localhost endpoints if running locally
'ws://localhost:8082', // Local Vite dev server const isLocalhost = typeof window !== 'undefined' &&
'ws://127.0.0.1:9944', // Local development node (primary) (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1');
'ws://localhost:9944', // Local development node (alternative)
'wss://ws.pezkuwichain.io', // Production WebSocket (fallback) const ENDPOINTS = isLocalhost
]; ? [
'ws://localhost:8082', // Local Vite dev server
'ws://127.0.0.1:9944', // Local development node (primary)
'ws://localhost:9944', // Local development node (alternative)
'wss://ws.pezkuwichain.io', // Production WebSocket (fallback)
]
: [
'wss://ws.pezkuwichain.io', // Production WebSocket only
];
export const useWebSocket = () => { export const useWebSocket = () => {
const context = useContext(WebSocketContext); const context = useContext(WebSocketContext);