// React Query wrappers around the command client. Server state (what Rust // knows) lives in the query cache; component state stays local. import { useEffect } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import type { ChainId } from './generated/ChainId' import type { Settings } from './generated/Settings' import { onBalancesUpdated, onSessionLocked, subscribe } from './events' import { appInfo, balances, balancesWatch, chainConnect, chainStatus, chainsList, lock, reversiblePending, sessionStatus, settingsGet, settingsSet, unlock, walletAddAccount, walletDiscoverAccounts, walletsList, } from './wallet' export const queryKeys = { appInfo: ['app-info'] as const, settings: ['settings'] as const, chains: ['chains'] as const, session: ['session'] as const, wallets: ['wallets'] as const, chainStatus: (chain: ChainId) => ['chain-status', chain] as const, balances: (chain: ChainId) => ['balances', chain] as const, pendingReversible: (chain: ChainId) => ['pending-reversible', chain] as const, } export function useAppInfo() { return useQuery({ queryKey: queryKeys.appInfo, queryFn: appInfo, staleTime: Infinity }) } export function useSettings() { return useQuery({ queryKey: queryKeys.settings, queryFn: settingsGet, staleTime: Infinity }) } export function useSetSettings() { const qc = useQueryClient() return useMutation({ mutationFn: (s: Settings) => settingsSet(s), onSuccess: (s) => qc.setQueryData(queryKeys.settings, s), }) } export function useChains() { return useQuery({ queryKey: queryKeys.chains, queryFn: chainsList, staleTime: Infinity }) } export function useWallets() { return useQuery({ queryKey: queryKeys.wallets, queryFn: walletsList }) } export function useSession() { return useQuery({ queryKey: queryKeys.session, queryFn: sessionStatus, refetchInterval: 15_000 }) } export function useUnlock() { const qc = useQueryClient() return useMutation({ mutationFn: ({ name, password }: { name: string; password: string }) => unlock(name, password), onSuccess: (s) => qc.setQueryData(queryKeys.session, s), }) } /** Add the next account to an open wallet; the session and settings both change. */ export function useAddAccount() { const qc = useQueryClient() return useMutation({ mutationFn: (wallet: string) => walletAddAccount(wallet), onSuccess: (s) => { qc.setQueryData(queryKeys.session, s) qc.invalidateQueries({ queryKey: queryKeys.settings }) }, }) } /** Scan a wallet's phrase for account numbers already used on `chain`. */ export function useDiscoverAccounts() { const qc = useQueryClient() return useMutation({ mutationFn: ({ chain, wallet }: { chain: ChainId; wallet: string }) => walletDiscoverAccounts(chain, wallet), onSuccess: (scan) => { qc.setQueryData(queryKeys.session, scan.session) qc.invalidateQueries({ queryKey: queryKeys.settings }) }, }) } /** Lock one wallet (pass its name) or every open wallet (pass nothing). */ export function useLock() { const qc = useQueryClient() return useMutation({ mutationFn: (wallet?: string) => lock(wallet), onSuccess: () => qc.invalidateQueries({ queryKey: queryKeys.session }), }) } /// Connect the chain (idempotent on the Rust side) and poll its status. export function useChainStatus(chain: ChainId | undefined) { return useQuery({ queryKey: chain ? queryKeys.chainStatus(chain) : ['chain-status', 'none'], queryFn: () => chainConnect(chain!).then(() => chainStatus(chain!)), enabled: !!chain, refetchInterval: 5_000, }) } /// Balances of the open wallet on `chain`, refreshed by Rust on every head. export function useBalances(chain: ChainId | undefined, unlocked: boolean) { const qc = useQueryClient() useEffect(() => { if (!chain || !unlocked) return balancesWatch(chain).catch(() => undefined) return subscribe( onBalancesUpdated((b) => { if (b.length > 0 && b[0].chain === chain) qc.setQueryData(queryKeys.balances(chain), b) }), ) }, [chain, unlocked, qc]) return useQuery({ queryKey: chain ? queryKeys.balances(chain) : ['balances', 'none'], queryFn: () => balances(chain!), enabled: !!chain && unlocked, retry: 2, retryDelay: 1_500, }) } /// Drop the session in the cache when Rust says the idle rule fired. export function useSessionLockedListener() { const qc = useQueryClient() useEffect(() => { return subscribe(onSessionLocked(() => qc.invalidateQueries({ queryKey: queryKeys.session }))) }, [qc]) } /// Reversible transfers still pending from any of `addresses` on `chain`. export function usePendingReversible(chain: ChainId | undefined, addresses: string[]) { return useQuery({ queryKey: chain ? [...queryKeys.pendingReversible(chain), addresses] : ['pending-reversible', 'none'], queryFn: async () => { const lists = await Promise.all(addresses.map((a) => reversiblePending(chain!, a))) return lists.flat() }, enabled: !!chain && addresses.length > 0, refetchInterval: 5_000, }) }