The cookbook hosts small code snippets in a question-answer format. It does not walk you through the whole setup, it rather aims to answer some questions around specific uses.
The Cookbook Recipes are written to show developer patterns on how the SDK is expected to be used by a developer or an LLM agent.
Getting a client
Every recipe assumes an initialized NexusClient. Create one with createNexusClient(), then run the two-step init: initialize() loads deployment data (chains, tokens, vault contracts) and setEVMProvider() attaches a wallet. Call destroy() when you are done to flush analytics and release resources.
import { createNexusClient } from '@avail-project/nexus-core';const client = createNexusClient({ network: 'testnet' });await client.initialize(); // load deployment dataawait client.setEVMProvider(window.ethereum); // attach an EIP-1193 wallet// ... use the client ...client.destroy(); // clean up when finished
Note
Create a fresh client on account change. Calling setEVMProvider() again with the same provider instance is a no-op, so build a new client and re-run initialize() + setEVMProvider() when the connected account switches.
SDK operations
How to simulate an operation on Nexus?
Call client.simulateBridge(), client.simulateBridgeAndExecute(), or client.simulateBridgeAndTransfer() to preview an operation without sending a transaction. The result contains an intent with selectedSources, availableSources, fees, and destination details.
simulateBridge() returns { intent, token }. simulateBridgeAndExecute() and simulateBridgeAndTransfer() return { bridgeSimulation, executeSimulation }, where bridgeSimulation is null when no bridge is needed.
How do I get source chain and destination chain information from an intent?
After simulating, access intent.selectedSources to see which chains the SDK chose to pull funds from. Each source nests its chain and token: source.chain has id, name, and logo; source.token has symbol, decimals, logo, and contractAddress. Use intent.availableSources to see every chain that could be used.
const sim = await client.simulateBridge({ toTokenSymbol: 'USDC', toAmountRaw: 100_000_000n, toChainId: 421614,});// Sources chosen by the solversim.intent.selectedSources.forEach((source) => { console.log(source.chain.name); // "Ethereum Sepolia" console.log(source.chain.id); // 11155111 console.log(source.chain.logo); // "https://..." console.log(source.amount); // "100.000102" console.log(source.token.contractAddress); // "0x..."});// All chains that had balance (superset of selectedSources)console.log(sim.intent.availableSources);// Destination chain infoconsole.log(sim.intent.destination.chain.name);console.log(sim.intent.destination.chain.id);// Total amount pulled across all sourcesconsole.log(sim.intent.sourcesTotal);// ─── Alternative: use the intent hook during execution ───await client.bridge( { toTokenSymbol: 'USDC', toAmountRaw: 100_000_000n, toChainId: 421614 }, { hooks: { onIntent: ({ intent, allow, deny, refresh }) => { // Same BridgeIntent structure as the simulation console.log(intent.selectedSources); console.log(intent.destination); allow(); // or deny() to cancel }, }, });
How to fetch token logos and names?
Token metadata (name, symbol, decimals, logo) is available from multiple sources: the simulation result's token field, the nested token on each intent source/destination, or client.chainList.getTokenInfoBySymbol().
// ─── A: From a simulation result ───const sim = await client.simulateBridge({ toTokenSymbol: 'USDC', toAmountRaw: 100_000_000n, toChainId: 421614,});sim.token.name; // "USD Coin"sim.token.symbol; // "USDC"sim.token.decimals; // 6sim.token.logo; // "https://..."// Source chain logos and token infosim.intent.selectedSources[0].chain.logo; // "https://..."sim.intent.selectedSources[0].chain.name; // "Ethereum Sepolia"sim.intent.selectedSources[0].token.symbol; // "USDC"/*----------------------------------------*/// ─── B: From the chain catalogue on the client ───const usdc = client.chainList.getTokenInfoBySymbol(421614, 'USDC');// { contractAddress: "0x75faf...", decimals: 6, logo: "https://...", name: "USD Coin", symbol: "USDC" }const native = client.chainList.getNativeToken(421614);// native gas token info for the chain/*----------------------------------------*/// ─── C: getSupportedChains — chains with their token lists ───const chains = client.getSupportedChains();// [{ id: 421614, name: "Arbitrum Sepolia", logo: "...", swapSupported: true, tokens: [{ symbol, name, logo, contractAddress, ... }] }]
Note
If a logo URL is missing or 404s, getFallbackTokenLogoDataUri(symbol) (exported from the main entry) returns a deterministic gradient SVG data-URI as a stable placeholder.
How to get the fees incurred from an intent?
Every BridgeIntent includes a fees object with a full breakdown: total, protocol, solver, caGas, and totalValue. All values are human-readable strings denominated in the intent's token (e.g. USDC), except totalValue which is the USD value of the total.
const sim = await client.simulateBridge({ toTokenSymbol: 'USDC', toAmountRaw: 100_000_000n, toChainId: 421614,});const { fees } = sim.intent;console.log(fees.total); // Total fee in token units (e.g. "0.05")console.log(fees.protocol); // Protocol feeconsole.log(fees.solver); // Solver / fulfillment feeconsole.log(fees.caGas); // Chain-abstraction gas feeconsole.log(fees.totalValue); // Total fee expressed in USD
How to view a wallet's intent history?
Call client.listIntents({ page?, status? }) to fetch the connected wallet's past intents. It returns { intents, total }, where each entry is an IntentRecord with sources, destinations, a status, and an explorer link. Page size is fixed at 20 records — paginate by incrementing page. Filter by status using the exported IntentStatus enum.
The static metadata constants and helpers from v1 (TOKEN_METADATA, CHAIN_METADATA, TOKEN_CONTRACT_ADDRESSES, getChainMetadata, getTokenMetadata, chainIdToHex, isSupportedToken, getSwapSupportedChainsAndTokens) have been removed. The SDK ships no hard-coded chain or token tables — everything is fetched from the live deployment. Use client.getSupportedChains(), client.isSupportedChain(), and the client.chainList.* lookups instead.
How to get chain metadata?
Use client.chainList.getChainByID() for a full Chain object (name, native currency, block explorers) or client.getSupportedChains() for the lighter chain + token list.
// Full Chain object — name, native currency, block explorersconst chain = client.chainList.getChainByID(421614);console.log(chain.name); // "Arbitrum Sepolia"console.log(chain.custom.icon); // chain logo URLconsole.log(chain.nativeCurrency); // { name: "ETH", symbol: "ETH", decimals: 18, logo: "..." }console.log(chain.blockExplorers?.default?.url); // explorer base URL (guard: optional)// Lighter list entry from getSupportedChains()const entry = client.getSupportedChains().find((c) => c.id === 421614);console.log(entry?.name); // "Arbitrum Sepolia"console.log(entry?.logo); // "https://..."
How to get the list of supported chains and tokens?
Call client.getSupportedChains() to get all chains with their supported tokens. Each entry carries a swapSupported boolean — filter on it to show only the chains a swap can use as a source or destination.
// Get all supported chains with their tokensconst chains = client.getSupportedChains();chains.forEach((chain) => { console.log(`${chain.name} (${chain.id}) — swapSupported: ${chain.swapSupported}`); console.log(` Logo: ${chain.logo}`); console.log(` Tokens:`); chain.tokens.forEach((token) => { console.log(` - ${token.symbol}: ${token.name} @ ${token.contractAddress}`); });});// Chains a swap can useconst swapChains = chains.filter((chain) => chain.swapSupported);
Note
Prefer the async standalone helper when you need this before a client exists: import { getSupportedChains } from '@avail-project/nexus-core/utils' then await getSupportedChains('mainnet'). The client method is synchronous and uses cached deployment data.
How to get the chain ID of a supported chain?
Use client.getSupportedChains() to find a chain by name, then access its id. You can also validate chain support with client.isSupportedChain().
// Find chain ID by nameconst chains = client.getSupportedChains();const baseSepolia = chains.find((c) => c.name === 'Base Sepolia');console.log(baseSepolia?.id); // 84532// Validate if a chain ID is supportedconst isSupported = client.isSupportedChain(84532); // true// Convert chain ID to hex (useful for wallet_switchEthereumChain)const hexChainId = `0x${(84532).toString(16)}`; // "0x14a34"
How to get the token address of a supported token?
Use client.chainList.getTokenInfoBySymbol(chainId, symbol) for a token on a specific chain, or iterate getSupportedChains() to collect a token's address across chains.
// Option A: Token info for a SPECIFIC chainconst usdcOnArbitrum = client.chainList.getTokenInfoBySymbol(421614, 'USDC');console.log(usdcOnArbitrum.contractAddress); // "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d"console.log(usdcOnArbitrum.decimals); // 6// Option B: Look up a token by its on-chain addressconst byAddress = client.chainList.getTokenByAddress(421614, '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d');console.log(byAddress.symbol); // "USDC"// Option C: Get all addresses for a token across chainsconst chains = client.getSupportedChains();const usdcAddresses = chains .map((chain) => { const token = chain.tokens.find((t) => t.symbol === 'USDC'); return token ? { chainId: chain.id, chainName: chain.name, address: token.contractAddress } : null; }) .filter(Boolean);console.log(usdcAddresses);// [// { chainId: 421614, chainName: "Arbitrum Sepolia", address: "0x75faf..." },// { chainId: 84532, chainName: "Base Sepolia", address: "0x036cb..." },// ...// ]
How to get the vault contract address for a chain?
Use client.chainList.getVaultContractAddress(chainId) to get the Nexus vault contract deployed on a chain.
Use client.getBalancesForBridge() (or client.getBalancesForSwap() for the swap balance pipeline). Both return TokenBalance[] — each entry aggregates a token across chains with a per-chain breakdown in chainBalances.
Pass an onEvent callback in the options of a bridge or swap method. Events are a typed discriminated union keyed on event.type ('status', 'plan_preview', 'plan_confirmed', 'plan_progress'), emitted at key stages of the transaction lifecycle so you can build progress UIs.Bridge Events:
const result = await client.bridge( { toTokenSymbol: 'USDC', toAmountRaw: 1_000_000n, // 1 USDC (6 decimals) toChainId: 421614, }, { onEvent: (event) => { switch (event.type) { case 'status': // Lifecycle phase: intent_building → intent_ready → awaiting_approval → executing → completed console.log('Status:', event.status); break; case 'plan_preview': // Emitted once with the planned steps before execution console.log('Planned steps:', event.plan.steps); // Use this to initialize a progress stepper UI break; case 'plan_confirmed': // Emitted after user approval with the final steps console.log('Confirmed steps:', event.plan.steps); break; case 'plan_progress': // Per-step progress. chain lives on event.step.chain, NOT event.chain console.log(`Step ${event.stepType}: ${event.state}`); if ((event.state === 'submitted' || event.state === 'confirmed') && 'txHash' in event) { console.log(` Tx: ${event.txHash} (${event.explorerUrl})`); } if (event.state === 'failed' && 'error' in event) { console.error(` Failed: ${event.error}`); } break; } }, hooks: { onIntent: ({ allow }) => allow(), onAllowance: ({ allow }) => allow(['min']), }, });
Note
Both 'confirmed' and 'completed' are terminal-success states depending on the step type. On-chain transaction steps settle on confirmed; off-chain orchestration steps (and vault_deposit) settle on completed. For a robust progress UI, treat either as success.
Swap Events:Swaps emit the same typed event union. Swap plans carry hasBridge / hasDestinationSwap flags and swap-specific step types.
Hooks are passed per-operation via options.hooks (there are no global setOnIntentHook / setOnAllowanceHook setters). Use them to review a bridge/transfer intent before execution and to control approval amounts.
await client.bridge(params, { hooks: { // Intent approval hook — review before execution onIntent: async ({ intent, allow, deny, refresh }) => { console.log('Selected sources:', intent.selectedSources); console.log('Destination:', intent.destination); console.log('Fees:', intent.fees); // Optionally re-quote against different source chains // const updated = await refresh([8453, 42161]); if (userConfirmed) allow(); else deny(); }, // Allowance approval hook — control approval amounts onAllowance: ({ sources, allow, deny }) => { // Options: 'max', 'min', a bigint, or a per-source array allow(['min']); }, },});
Note
bridgeAndExecute() and swapAndExecute() use a top-level options.onIntent hook (not nested under hooks), and their intent data is a composite type that also reports whether a bridge/swap is actually needed. If no onAllowance hook is provided for a bridge operation, the SDK auto-approves with 'min'.
Troubleshooting
How to solve Vite polyfill issues?
If you see errors like ReferenceError: Buffer is not defined or process is not defined, Vite needs Node.js polyfills.Step 1: Install the polyfill plugin
Quick start alternative: Clone the Nexus Vite Template which has polyfills pre-configured.
How to solve Turbopack issues in Next.js?
Turbopack (Next.js experimental bundler) has limited support for Node.js polyfills. If you encounter module resolution errors:Option A: Disable Turbopack (Recommended)Remove the --turbo flag from your dev script: