React Hooks (@veridex/react)
@veridex/react provides pre-built hooks that handle loading states, error boundaries,
and polling — so you don't write boilerplate for every SDK call.
This package is a thin wrapper around @veridex/sdk. For manual integration
without the hooks package, see React Integration.
Installation
npm install @veridex/react @veridex/sdk react@veridex/sdk and react are peer dependencies.
Quick Start
Wrap Your App with VeridexProvider
'use client';
import { VeridexProvider } from '@veridex/react';
import { createSDK } from '@veridex/sdk';
const sdk = createSDK('base', {
network: 'testnet',
relayerUrl: process.env.NEXT_PUBLIC_RELAYER_URL,
});
export default function App({ children }: { children: React.ReactNode }) {
return (
<VeridexProvider sdk={sdk} network="testnet">
{children}
</VeridexProvider>
);
}Use Hooks in Components
'use client';
import { usePasskey, useBalance, useTransfer } from '@veridex/react';
export function Wallet() {
const { credential, register, authenticate, loading } = usePasskey();
const { balance } = useBalance({ pollInterval: 10_000 });
const { prepare, execute, step, error } = useTransfer();
if (!credential) {
return (
<div>
<button onClick={() => register('alice', 'Alice')} disabled={loading}>
Create Passkey
</button>
<button onClick={authenticate} disabled={loading}>
Sign In
</button>
</div>
);
}
return (
<div>
<h2>Balance</h2>
{balance?.tokens.map(t => (
<p key={t.token.address}>{t.token.symbol}: {t.formatted}</p>
))}
<h2>Send</h2>
<button onClick={() => prepare({
targetChain: 'base',
token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
recipient: '0x...',
amount: 1_000_000n,
})}>
Prepare Transfer
</button>
</div>
);
}Hooks Reference
usePasskey
Handles passkey registration and authentication state.
const {
credential, // PasskeyCredential | null
loading, // boolean
error, // Error | null
register, // (username, displayName) => Promise<void>
authenticate, // () => Promise<void>
clear, // () => void
} = usePasskey();Example: Login / Register flow
function AuthScreen() {
const { credential, register, authenticate, loading, error } = usePasskey();
if (credential) return <p>Logged in as {credential.credentialId.slice(0, 8)}...</p>;
return (
<div>
<button onClick={() => register('user', 'User')} disabled={loading}>
Create Account
</button>
<button onClick={authenticate} disabled={loading}>
Sign In
</button>
{error && <p className="text-red-500">{error.message}</p>}
</div>
);
}useNetwork
Returns the current network type from the provider context.
import { useNetwork } from '@veridex/react';
const network = useNetwork(); // 'testnet' | 'mainnet'Useful for displaying network indicators or conditionally rendering testnet-specific UI.
useBalance
Auto-polling vault balance with configurable interval.
const {
balance, // PortfolioBalance | null
loading, // boolean
error, // Error | null
refetch, // () => Promise<void>
} = useBalance({
pollInterval: 15_000, // ms (default: 15000)
enabled: true, // disable polling (default: true)
});Example: Token list
function TokenList() {
const { balance, loading, refetch } = useBalance({ pollInterval: 10_000 });
if (loading) return <Spinner />;
if (!balance) return <p>No wallet connected</p>;
return (
<div>
{balance.tokens.map(t => (
<div key={t.token.address} className="flex justify-between">
<span>{t.token.symbol}</span>
<span>{t.formatted}</span>
</div>
))}
<button onClick={refetch}>Refresh</button>
</div>
);
}useTransfer
State machine for the prepare → review → execute transfer flow. Uses TransferInput with
ChainTarget (chain name or numeric Wormhole chain ID) for targetChain.
import type { TransferInput } from '@veridex/react';
const {
step, // 'idle' | 'preparing' | 'prepared' | 'executing' | 'confirmed' | 'error'
prepared, // PreparedTransfer | null
result, // TransferResult | null
error, // Error | null
loading, // boolean
prepare, // (params: TransferInput) => Promise<void>
execute, // (signer: any) => Promise<void>
transferViaRelayer,// (params: TransferInput) => Promise<void>
reset, // () => void
} = useTransfer();The TransferInput type accepts a ChainTarget for targetChain — either a chain name
string ('base', 'optimism', 'arbitrum', 'solana', etc.) or a numeric Wormhole chain ID.
The hook resolves chain names to numeric IDs internally using the provider's network context.
interface TransferInput {
targetChain: ChainTarget; // 'base' | 'optimism' | 10004 | ...
token: string;
recipient: string;
amount: bigint;
}
type ChainTarget = ChainName | number;Example: Gasless transfer
function SendForm() {
const { transferViaRelayer, step, result, error, reset } = useTransfer();
const handleSend = () => {
transferViaRelayer({
targetChain: 'base',
token: USDC_ADDRESS,
recipient: recipientAddress,
amount: parseUnits(amount, 6),
});
};
if (step === 'confirmed') {
return (
<div>
<p>Sent! Tx: {result?.transactionHash}</p>
<button onClick={reset}>Send Another</button>
</div>
);
}
return (
<div>
<button onClick={handleSend} disabled={step === 'executing'}>
{step === 'executing' ? 'Sending...' : 'Send USDC'}
</button>
{error && <p className="text-red-500">{error.message}</p>}
</div>
);
}Example: Prepare → Review → Execute
function TransferWithReview() {
const { prepare, execute, step, prepared, error } = useTransfer();
return (
<div>
{step === 'idle' && (
<button onClick={() => prepare(transferParams)}>Prepare</button>
)}
{step === 'prepared' && prepared && (
<div>
<p>Gas cost: {prepared.formattedCost}</p>
<p>Expires: {new Date(prepared.expiresAt).toLocaleString()}</p>
<button onClick={() => execute(signer)}>Confirm & Send</button>
</div>
)}
{step === 'executing' && <p>Submitting transaction...</p>}
{step === 'confirmed' && <p>Done!</p>}
{error && <p className="text-red-500">{error.message}</p>}
</div>
);
}useMultiChainPortfolio
Combined portfolio and addresses across multiple chains. Accepts optional chains parameter
using ChainTarget (chain names or numeric IDs).
const {
portfolio, // PortfolioBalance[] | null
addresses, // Record<number, string> | null (chainId → vault address)
loading, // boolean
error, // Error | null
refetch, // () => Promise<void>
} = useMultiChainPortfolio({
chains: ['base', 'optimism', 'arbitrum'], // ChainTarget[] — optional
pollInterval: 30_000,
enabled: true,
});Example: Multi-chain dashboard
function Dashboard() {
const { portfolio, loading } = useMultiChainPortfolio({
chains: ['base', 'optimism', 'solana'],
pollInterval: 30_000,
});
if (loading || !portfolio) return <Spinner />;
return (
<div>
{portfolio.map(chain => (
<div key={chain.wormholeChainId}>
<h3>{chain.chainName}</h3>
{chain.tokens.map(t => (
<p key={t.token.address}>{t.token.symbol}: {t.formatted}</p>
))}
</div>
))}
</div>
);
}useSpendingLimits
Formatted spending limits with amount checking.
const {
limits, // { dailyLimit, dailySpent, dailyRemaining, dailyUsedPercentage, transactionLimit, timeUntilReset } | null
loading, // boolean
error, // Error | null
checkAmount, // (amount: bigint) => Promise<{ allowed: boolean; message?: string }>
refetch, // () => Promise<void>
} = useSpendingLimits();Example: Spending limit bar
function SpendingLimitBar() {
const { limits, loading } = useSpendingLimits();
if (loading || !limits) return null;
return (
<div>
<progress value={limits.dailyUsedPercentage} max={100} />
<p>
{limits.dailyUsedPercentage}% used — resets in {limits.timeUntilReset}
</p>
<p>Remaining: {limits.dailyRemaining}</p>
</div>
);
}Chain Name Resolution
The React hooks support human-readable chain names via the ChainTarget type.
Instead of remembering Wormhole chain IDs, use names like 'base', 'optimism', or 'solana'.
import { resolveChainId, resolveChainIds } from '@veridex/react';
import type { ChainTarget } from '@veridex/react';
// Resolve a single chain
const baseId = resolveChainId('base', 'testnet'); // → 10004
const opId = resolveChainId('optimism', 'testnet'); // → 10005
// Resolve multiple chains
const ids = resolveChainIds(['base', 'optimism', 'solana'], 'testnet');
// → [10004, 10005, 1]
// Numeric IDs pass through unchanged
const id = resolveChainId(10004, 'testnet'); // → 10004The hooks resolve chain names automatically using the network prop from VeridexProvider.
You can also import and use the resolution utilities directly.
Accessing the SDK Directly
Use useVeridexSDK() for operations not covered by hooks:
import { useVeridexSDK } from '@veridex/react';
function CustomComponent() {
const sdk = useVeridexSDK();
const handleBridge = async () => {
const result = await sdk.bridgeViaRelayer({
sourceChain: 10004,
destinationChain: 10005,
token: USDC,
recipient: '0x...',
amount: 1_000_000n,
});
};
return <button onClick={handleBridge}>Bridge USDC</button>;
}When using useVeridexSDK() directly, you work with the raw SDK API which
uses numeric Wormhole chain IDs. Chain name resolution is a feature of
the React hooks layer only.
Next.js Setup
The SDK uses browser APIs (WebAuthn, localStorage). In Next.js App Router,
add 'use client' to any component that uses Veridex hooks.
// app/providers.tsx
'use client';
import { VeridexProvider } from '@veridex/react';
import { createSDK } from '@veridex/sdk';
import { useMemo } from 'react';
export function Providers({ children }: { children: React.ReactNode }) {
const sdk = useMemo(() => createSDK('base', {
network: 'testnet',
relayerUrl: process.env.NEXT_PUBLIC_RELAYER_URL,
}), []);
return <VeridexProvider sdk={sdk} network="testnet">{children}</VeridexProvider>;
}// app/layout.tsx
import { Providers } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Full Exports
Everything exported from @veridex/react:
// Components
import { VeridexProvider } from '@veridex/react';
// Hooks
import {
useVeridexSDK,
useNetwork,
usePasskey,
useBalance,
useTransfer,
useMultiChainPortfolio,
useSpendingLimits,
} from '@veridex/react';
// Utilities
import { resolveChainId, resolveChainIds } from '@veridex/react';
// Types
import type { TransferInput, ChainTarget } from '@veridex/react';Agent SDK Hooks
The Agent SDK (@veridex/agentic-payments) provides additional hooks for agent dashboard UIs. These are separate from the core @veridex/react hooks and focus on agent-specific functionality like spending, payment, and protocol detection.
import {
AgentWalletProvider,
useAgentWallet,
useAgentWalletContext,
usePayment,
useSessionStatus,
useFetchWithPayment,
useCostEstimate,
useProtocolDetection,
useMultiChainBalance,
usePaymentHistory,
useSpendingAlerts,
useCanPay,
} from '@veridex/agentic-payments';AgentWalletProvider
Wraps your app to provide a shared AgentWallet instance via context:
<AgentWalletProvider config={walletConfig}>
<AgentDashboard />
</AgentWalletProvider>usePayment
Make payments with loading and error state:
const { pay, isPaying, lastReceipt, error } = usePayment(wallet);
await pay({ amount: '1000000', token: 'USDC', recipient: '0x...', chain: 10004 });useFetchWithPayment
Make paid HTTP requests with automatic protocol detection:
const { fetchWithPayment, data, isPending, detectedProtocol, costEstimate } =
useFetchWithPayment(wallet);
await fetchWithPayment('https://api.example.com/premium', {
onBeforePayment: async (estimate) => estimate.amountUSD < 10,
});useCostEstimate
Preview payment cost before executing:
const { estimate, isLoading } = useCostEstimate(wallet, 'https://api.example.com/data');
// estimate?.amountUSD, estimate?.schemeuseProtocolDetection
Detect which payment protocol a URL uses:
const { protocol, isDetecting } = useProtocolDetection(wallet, url);
// protocol: 'x402' | 'ucp' | 'acp' | 'ap2' | 'mpp' | nulluseCanPay
Check if the wallet can afford a payment:
const { canPay, reason } = useCanPay(wallet, 5.00);
// canPay: boolean, reason: 'Exceeds daily limit' | nulluseSessionStatus
Get session status with auto-refresh (every 30s):
const { status, isExpired, refreshStatus } = useSessionStatus(wallet);
// status.isValid, status.remainingDailyLimitUSD, status.expiryuseMultiChainBalance
Fetch balances across all configured chains:
const { balances, totalUSD, isLoading } = useMultiChainBalance(wallet);useSpendingAlerts
Subscribe to real-time spending alerts:
const { alerts, latestAlert, clearAlerts } = useSpendingAlerts(wallet);