# Avail Nexus Documentation > Cross-chain unification — SDK, concepts, contracts, and UI elements. Full SDK API reference (TypeDoc): https://availproject.github.io/nexus-sdk/ ## Avail Nexus - [**Get Started**](/docs/nexus/get-started) — Understand what Nexus does, how it works, and choose the right integration path for your app. - [**Concepts**](/docs/nexus/concepts) — Learn about chain abstraction, intents, solvers, and cross-chain operations. - [**Nexus SDK**](/docs/nexus/nexus-sdk/get-started/installation) — TypeScript API reference for bridging, swaps, intent management, and balance queries. - [**Widgets**](/docs/nexus/widgets) — Pre-built React components for cross-chain bridging, swaps, and transaction management. - [**Cookbook Recipes**](/docs/nexus/cookbook-recipes) — Ready-to-use code snippets and patterns for common SDK use cases and integrations. ## Concepts - [**What is Chain Abstraction?**](/docs/nexus/concepts/chain-abstraction) — Spend balances from many chains with one review and one intent signature. - [**What is an Intent?**](/docs/nexus/concepts/intent) — A quoted request describing source inputs, destination output, fees, and the provider that fills it. - [**Solvers and Providers**](/docs/nexus/concepts/solvers) — Who delivers funds on the destination chain, and how they are repaid. - [**How Intents are Processed**](/docs/nexus/concepts/intent-lifecycle) — From quote to fulfillment: approvals, one signature, source deposits, and status updates. - [**Bridge, Transfer & Execute**](/docs/nexus/concepts/nexus-ops) — Map bridging, funding contract calls, and destination execution to SDK methods. - [**Crosschain Swaps**](/docs/nexus/concepts/xcs-swaps) — Swap any supported token on one or more chains into a token on another, by exact input or exact output. - [**Liquidity Routing**](/docs/nexus/concepts/source-chain-selection) — Let the quote choose source balances, or restrict which chains and tokens it may use. - [**Token Allowances**](/docs/nexus/concepts/allowances) — Which approvals and permits a quote needs, and how the SDK sizes them. ## Cookbook Recipes Each recipe assumes an initialized `client` with a connected wallet unless it says otherwise. See [SDK setup](/docs/nexus/nexus-sdk/get-started/nexus-sdk-setup). Names such as `showError`, `openReview`, `params`, and `userAddress` stand in for your app's code. The recipes share these constants: ```typescript title="TypeScript" import { parseUnits, formatUnits } from '@avail-project/nexus-core/utils'; const ETH_USDC = '0xA0b86991c6218b36c1d19d4a2e9eb0cE3606eB48'; const ARB_USDC = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; const BASE_USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; const BASE_WETH = '0x4200000000000000000000000000000000000006'; const AAVE_POOL = '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5'; // Aave v3 Pool on Base ``` ## Show balances in a token picker List spendable, verified balances, largest first. ```typescript title="TypeScript" const chainNames = new Map(client.getSupportedChains().map((c) => [c.id, c.name])); const balances = await client.getBalances(); const rows = balances .filter((b) => b.usable && b.verified && b.usableBalanceRaw > 0n) .sort((a, b) => (b.valueUsd ?? 0) - (a.valueUsd ?? 0)) .map((b) => ({ key: `${b.chainId}:${b.tokenAddress}`, label: `${b.symbol} on ${chainNames.get(b.chainId)}`, amount: formatUnits(b.usableBalanceRaw, b.decimals), valueUsd: b.valueUsd, // null when no price is available })); ``` `usableBalanceRaw` excludes gas reserves. Show `actualBalanceRaw` if you also display the full wallet balance. ## Show only Relay-supported balances ```typescript title="TypeScript" const relayBalances = (await client.getBalances()).filter( (b) => b.usable && b.providers.some((p) => p.id === 'relay'), ); ``` Provider support does not guarantee destination-call support. Each quote reports that in `quote.supportsDestinationCalls`. ## Preview a rate before wallet connection Call `swapWithExactIn` after `initialize()` and before `setEVMProvider()`. ```typescript title="TypeScript" import { UserActionError } from '@avail-project/nexus-core'; const from = { chainId: 1, tokenAddress: ETH_USDC } as const; const [fromToken, toToken] = await Promise.all([ client.getToken(from), client.getToken({ chainId: 8453, tokenAddress: BASE_WETH }), ]); await client .swapWithExactIn( { sources: [{ ...from, amountRaw: parseUnits('100', fromToken.decimals) }], toChainId: 8453, toTokenAddress: BASE_WETH, }, { hooks: { onIntent: (hook) => { showRate(formatUnits(hook.quote.output.amountRaw, toToken.decimals), hook.quote.fees); hook.deny(); }, }, }, ) .catch((error) => { if (!(error instanceof UserActionError)) showQuoteError(error); }); ``` A disconnected preview always rejects. After the wallet connects, start a new swap to get an executable quote. ## Swap with a review UI ```typescript title="TypeScript" const usdc = await client.getToken({ chainId: 8453, tokenAddress: BASE_USDC }); const result = await client.swapWithExactOut( { toChainId: 8453, toTokenAddress: BASE_USDC, toAmountRaw: parseUnits('25', usdc.decimals) }, { hooks: { onIntent: (hook) => openReview({ quote: hook.quote, canConfirm: hook.execution.possible, onConfirm: hook.allow, onCancel: hook.deny, onRefresh: () => hook.refresh(), // resolves with the replacement quote }), }, }, ); ``` The swap waits until you call `allow()` or `deny()`. Await `refresh()` before allowing, render the quote it returns, and reread `hook.execution`. ## Bridge USDC to Base ```typescript title="TypeScript" const usdc = await client.getToken({ chainId: 8453, tokenAddress: BASE_USDC }); const result = await client.swapWithExactOut({ toChainId: 8453, toTokenAddress: BASE_USDC, toAmountRaw: parseUnits('100', usdc.decimals), sources: [ { chainId: 1, tokenAddress: ETH_USDC }, { chainId: 42161, tokenAddress: ARB_USDC }, ], }); console.log(result.intentExplorerUrl); ``` A bridge is a swap with the same asset's address on each chain. Omit `sources` to let the quote draw from any wallet balance. ## Deposit into Aave with your wallet `swapAndExecute` funds the shortfall on Base, then your wallet calls the contract. ```typescript title="TypeScript" import { encodeFunctionData, parseAbi, type Hex } from 'viem'; const poolAbi = parseAbi([ 'function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)', ]); const supplyData = (amount: bigint, user: Hex) => encodeFunctionData({ abi: poolAbi, functionName: 'supply', args: [BASE_USDC, amount, user, 0] }); const usdc = await client.getToken({ chainId: 8453, tokenAddress: BASE_USDC }); const amount = parseUnits('50', usdc.decimals); const result = await client.swapAndExecute({ toChainId: 8453, toTokenAddress: BASE_USDC, toAmountRaw: amount, execute: { to: AAVE_POOL, data: supplyData(amount, userAddress), gas: 300_000n, tokenApproval: { toTokenAddress: BASE_USDC, amount, spender: AAVE_POOL }, }, }); if (result.executionMode === 'wallet') console.log(result.execute.txExplorerUrl); ``` `result.swapSkipped` is `true` when the wallet already holds enough on Base. Narrow on `executionMode` before reading `execute`. ## Deposit into Aave through Relay With `useProviderDestinationExecution: true`, Relay executes the call during fulfillment. This reuses `supplyData` from the previous recipe. ```typescript title="TypeScript" const arbUsdc = await client.getToken({ chainId: 42161, tokenAddress: ARB_USDC }); const result = await client.swapAndExecute( { tradeType: 'exactInput', sources: [{ chainId: 42161, tokenAddress: ARB_USDC, amountRaw: parseUnits('50', arbUsdc.decimals) }], toChainId: 8453, toTokenAddress: BASE_USDC, execute: { to: AAVE_POOL, gas: 300_000n, tokenApproval: { toTokenAddress: BASE_USDC, amount: 0n, spender: AAVE_POOL }, }, }, { useProviderDestinationExecution: true, beforeExecute: async ({ executeAmountRaw }) => ({ data: supplyData(executeAmountRaw, userAddress) }), }, ); console.log(result.executionMode, result.executeAmountRaw); // 'provider', fixed deposit amount ``` Relay's router is the caller, so the contract must credit the user (here `onBehalfOf`), not `msg.sender`. Unsupported routes reject before signing; see [provider destination execution](/docs/nexus/nexus-sdk/reference/swap-methods/swap-and-execute#provider-destination-execution). ## Drive a progress UI from events ```typescript title="TypeScript" import type { IntentEvent } from '@avail-project/nexus-core'; const onEvent = (event: IntentEvent) => { switch (event.type) { case 'quote': setSteps(event.quote.plan.steps.map((s) => ({ id: s.id, type: s.type, state: 'pending' }))); break; case 'step': updateStep(event.step.id, event.state, event.errorDetails?.message); break; case 'status': setStatus(event.status, event.legs.map((leg) => leg.txExplorerUrl)); break; } }; await client.swapWithExactOut(params, { onEvent }); ``` Errors thrown in `onEvent` do not interrupt the swap. A failed step with `committed: true` may still settle, so check its status before retrying. ## Handle quote failures ```typescript title="TypeScript" import { ERROR_CODES, NexusError, UserActionError, getIntentQuoteFailure } from '@avail-project/nexus-core'; try { await client.swapWithExactOut(params); } catch (error) { if (error instanceof UserActionError) return; // user cancelled if (!(error instanceof NexusError)) throw error; const failure = getIntentQuoteFailure(error); switch (error.code) { case ERROR_CODES.BACKEND_VALUE_ABOVE_CEILING: showError(`Maximum is $${String(failure?.details.maxValueUsd)}`); break; case ERROR_CODES.BACKEND_INPUT_BELOW_DEPOSIT_FEE: showError('Increase the amount to cover the deposit fee.'); break; case ERROR_CODES.BACKEND_NO_ROUTABLE_SOURCE: showSourceIssues(failure?.sourceVerdicts.filter((v) => v.state === 'unroutable')); break; default: showError(error.message); } } ``` `getIntentQuoteFailure` returns `null` for errors that are not quote failures. These failures have `retryable: false`; change the request before retrying. ## Paginate a token picker ```typescript title="TypeScript" import type { IntentToken } from '@avail-project/nexus-core'; let tokens: IntentToken[] = []; let offset = 0; let total = Infinity; async function loadMore(chainId: number, symbol?: string) { if (offset >= total) return; const page = await client.getTokensByChain(chainId, { symbol, offset, limit: 50 }); tokens = [...tokens, ...page.tokens]; offset = page.offset + page.limit; total = page.total; } ``` Reset `tokens`, `offset`, and `total` when the chain or search changes. Pages list verified tokens only; pass `includeUnverified: true` to include the rest. ## Show intent history ```typescript title="TypeScript" const { intents, total } = await client.listIntents({ page: 1, status: 'fulfilled' }); for (const intent of intents) { console.log(intent.id, intent.status, intent.provider, intent.explorerUrl); } ``` Pages hold 20 intents for the connected wallet. `provider` is present only when the middleware reports it. ## Get Started with Avail Nexus Nexus lets your app move assets and execute transactions across chains without requiring users to bridge manually. Your users see their balances across every supported chain, review one quote, and sign once. Nexus turns that into an intent that a provider fills on the destination chain, usually in seconds. ## Quick Look — How the SDK Works ```typescript title="TypeScript" import { createNexusClient } from '@avail-project/nexus-core'; import { parseUnits } from '@avail-project/nexus-core/utils'; // 1. Create the client and connect a wallet const client = createNexusClient({ clientId: 'your-app-name', network: 'mainnet' }); await client.initialize(); await client.setEVMProvider(provider); // EIP-1193 provider from your wallet connector // 2. See balances across all supported chains const balances = await client.getBalances(); // 3. Get 100 USDC on Arbitrum, funded from any chain const usdc = { chainId: 42161, tokenAddress: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' } as const; const token = await client.getToken(usdc); await client.swapWithExactOut( { toChainId: usdc.chainId, toTokenAddress: usdc.tokenAddress, toAmountRaw: parseUnits('100', token.decimals), }, { hooks: { // Show the quote, then call allow() when the user confirms onIntent: ({ quote, allow, deny }) => allow(), }, }, ); ``` ## What Changes for Your Users | Without Nexus | With Nexus | | ------------------------------------------------------------- | ------------------------------------------------------------------------ | | User bridges manually, switches chains, manages gas per chain | User reviews one quote and signs once — Nexus routes funds across chains | | Dev writes bridge logic per chain pair | Dev calls one SDK method with a destination token and amount | | Balances scattered across a dozen chains | One unified balance | ## Choose Your Path - [**Add Cross-Chain to My App**](/docs/nexus/nexus-sdk/get-started/installation) — Use the Nexus SDK directly: a TypeScript API for swaps, bridging, and contract calls. ~15 min. - [**Drop in Widgets**](/docs/nexus/widgets) — A pre-built UI element for bridging, swaps, and balances — shadcn-based. ~10 min. - [**Cookbook & Recipes**](/docs/nexus/cookbook-recipes) — Copy-paste patterns for common cross-chain flows — payments, swaps, and more. ## Prerequisites Node.js 18+, a package manager (npm/pnpm/yarn), and an EVM wallet (MetaMask, WalletConnect, etc.). ## How Nexus Works When a user starts a cross-chain action, Nexus handles the flow behind one review and one signature: ### The SDK Requests a Quote The user asks for "100 USDC on Arbitrum". The Nexus middleware picks the source balances, the fees, and one provider to fill the intent. ### The User Approves and Signs Once Your app shows the [intent](/docs/nexus/concepts/intent) through the `onIntent` hook. The user approves and signs one message. Token approvals and native-token deposits may add wallet prompts on source chains. ### The Provider Fills on the Destination Chain The signed intent is submitted and the source funds are deposited. The provider delivers the output on the destination chain, usually within seconds. For Nexus routes, a [solver](/docs/nexus/concepts/solvers) fronts its own capital. ### Settlement Happens in the Background For Nexus routes, the solver is repaid from the deposited source funds through an on-chain settlement authorized by Nexus validators. The user's transfer is already complete. > **Note** > > Each quote is filled by one provider: Nexus solvers (`nexus-v2`), Mayan (`mayan`), or Relay (`relay`). The middleware picks it per quote, and your integration code stays the same. ## Supported Chains & Tokens Nexus supports swaps, same-asset bridging, and contract execution across major EVM chains, including Ethereum, Arbitrum, Base, Optimism, and Arc. Call `client.getSupportedChains()` and `client.getTokens()` for the live list. > **Note** > > For the full list, see [Supported Chains & Tokens](/docs/nexus/supported-chains-and-tokens). ## Next Steps - [**Explore Concepts**](/docs/nexus/concepts) — Chain abstraction, intents, solvers — understand the full system. - [**SDK Reference**](/docs/nexus/nexus-sdk/reference) — API docs for balances, tokens, swaps, execution, and errors. - [**Cookbook**](/docs/nexus/cookbook-recipes) — Practical recipes for common cross-chain patterns. ## Contract Addresses Avail Nexus deploys Vault contracts on each supported chain. These contracts escrow user funds during cross-chain operations. ### Vault Contracts — Mainnet | Chain | Chain ID | Vault Contract Address | |-------|----------|----------------------| | Arc Mainnet | 5042 | 0xa009A9Fdc0eF474e7A3938a89f80329139291568 | | Ethereum | 1 | 0xa009A9Fdc0eF474e7A3938a89f80329139291568 | | Optimism | 10 | 0xa009A9Fdc0eF474e7A3938a89f80329139291568 | | BNB Chain | 56 | 0xa009A9Fdc0eF474e7A3938a89f80329139291568 | | Polygon | 137 | 0xa009A9Fdc0eF474e7A3938a89f80329139291568 | | Monad | 143 | 0xa009A9Fdc0eF474e7A3938a89f80329139291568 | | Hyperliquid (HyperEVM) | 999 | 0xa009A9Fdc0eF474e7A3938a89f80329139291568 | | Citrea | 4114 | 0xa009A9Fdc0eF474e7A3938a89f80329139291568 | | MegaETH | 4326 | 0xa009A9Fdc0eF474e7A3938a89f80329139291568 | | Base | 8453 | 0xa009A9Fdc0eF474e7A3938a89f80329139291568 | | Arbitrum One | 42161 | 0xa009A9Fdc0eF474e7A3938a89f80329139291568 | | Avalanche C-Chain | 43114 | 0xa009A9Fdc0eF474e7A3938a89f80329139291568 | ### Vault Contracts — Testnet | Chain | Chain ID | Vault Contract Address | |-------|----------|----------------------| | Arc Testnet | 5042002 | 0x86B60E813f9b739516dDbDc443526be5Ef8336aa | | Polygon Amoy | 80002 | 0x86B60E813f9b739516dDbDc443526be5Ef8336aa | | Monad Testnet | 10143 | 0x86B60E813f9b739516dDbDc443526be5Ef8336aa | | Base Sepolia | 84532 | 0x86B60E813f9b739516dDbDc443526be5Ef8336aa | | Ethereum Sepolia | 11155111 | 0x86B60E813f9b739516dDbDc443526be5Ef8336aa | | OP Sepolia | 11155420 | 0x86B60E813f9b739516dDbDc443526be5Ef8336aa | | Arbitrum Sepolia | 421614 | 0x86B60E813f9b739516dDbDc443526be5Ef8336aa | | Citrea Testnet | 5115 | 0x86B60E813f9b739516dDbDc443526be5Ef8336aa | ### USDC Token Addresses — Mainnet | Chain | Token | Contract Address | |-------|-------|-----------------| | Ethereum | USDC | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 | | Base | USDC | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | | Polygon | USDC | 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 | | Arbitrum One | USDC | 0xaf88d065e77c8cC2239327C5EDb3A432268e5831 | | Optimism | USDC | 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85 | | Avalanche C-Chain | USDC | 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E | | BNB Chain | Binance-Peg USDC | 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d | | Hyperliquid (HyperEVM) | USDC | 0xb88339CB7199b77E23DB6E890353E22632Ba630f | | Monad | USDC | 0x754704Bc059F8C67012fEd69BC8A327a5aafb603 | | Citrea | USDC.e | 0xE045e6c36cF77FAA2CfB54466D71A3aEF7bbE839 | | MegaETH | USDm | 0xFAfDdbb3FC7688494971a79cc65DCa3EF82079E7 | ### USDC Token Addresses — Testnet | Chain | Token | Contract Address | |-------|-------|-----------------| | Ethereum Sepolia | USDC | 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 | | Base Sepolia | USDC | 0x036CbD53842c5426634e7929541eC2318f3dCF7e | | Arbitrum Sepolia | USDC | 0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d | | OP Sepolia | USDC | 0x5fd84259d66Cd46123540766Be93DFE6D43130D7 | | Polygon Amoy | USDC | 0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582 | | Monad Testnet | USDC | 0x534b2f3A21130d7a60830c2Df862319e593943A3 | | Citrea Testnet | USDC | 0xb669dC8cC6D044307Ba45366C0c836eC3c7e31AA | ### USDT Token Addresses — Mainnet | Chain | Token | Contract Address | |-------|-------|-----------------| | Ethereum | USDT | 0xdAC17F958D2ee523a2206206994597C13D831ec7 | | Polygon | USDT0 | 0xc2132D05D31c914a87C6611C10748AEb04B58e8F | | Arbitrum One | USDT0 | 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9 | | Optimism | USDT | 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58 | | Avalanche C-Chain | USDT | 0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7 | | BNB Chain | Binance-Peg USDT | 0x55d398326f99059fF775485246999027B3197955 | | Hyperliquid (HyperEVM) | USDT0 | 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb | | Monad | USDT0 | 0xe7cd86e13AC4309349F30B3435a9d337750fC82D | | MegaETH | USDT0 | 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb | | Citrea | USDT.e | 0x9f3096Bac87e7F03DC09b0B416eB0DF837304dc4 | | Base | USDT | 0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2 | ### USDT Token Addresses — Testnet | Chain | Token | Contract Address | |-------|-------|-----------------| | Arbitrum Sepolia | USDT | 0xF954d4A5859b37De88a91bdbb8Ad309056FB04B1 | | OP Sepolia | USDT | 0x6462693c2F21AC0E517f12641D404895030F7426 | ## Migration Guides Each guide covers one upgrade step — what changed, why, and the exact renames and rewrites to apply. New guides are added here as new major versions ship. ## Current versions | Surface | Package / registry | Current | | ------------- | -------------------------------------------------------------------------------------- | ------------ | | Nexus SDK | [`@avail-project/nexus-core`](https://www.npmjs.com/package/@avail-project/nexus-core) | 3.x | | Avail Widgets | `availproject/widgets/nexus` (shadcn registry) | Nexus Widget | ## Guides - [**Migrate to Nexus SDK v3**](/docs/nexus/migration/sdk-v2-to-v3) — nexus-core 2.x to 3.x: address-based swaps replace bridge methods, one balance query, hooks under options.hooks, intent events. - [**Migrate to Nexus SDK v2**](/docs/nexus/migration/sdk-v1-to-v2) — nexus-core 1.x to 2.x — factory client, param renames, per-operation hooks, plan-based events. - [**Migrate to Nexus Widget**](/docs/nexus/migration/widgets-legacy-to-nexus-widget) — Standalone SwapWidget/FastBridge/FastTransfer/Deposit components to the single unified Nexus Widget. ## Deposit (exact-input-mode) ## How exact-input mode works Choose one or more tokens to fund your deposit and enter how much to spend from each. The widget quotes how much of the destination token you’ll receive to deposit. Review the quote, then confirm your deposit. > **Note** > > Looking for exact-output mode instead? See [Deposit (exact-output-mode)](/docs/nexus/widgets/deposit) to enter the exact amount you want to deposit. ## Installation #### NPM (Recommended) ```bash npm install @avail-project/widgets viem wagmi @tanstack/react-query ``` #### shadcn/ui (Power Users) ```bash npx shadcn@latest add availproject/widgets/nexus ``` ## Usage `NexusWidget` must be rendered inside `NexusProvider` (mandatory — it initializes the SDK), and the provider should receive your `identity.clientId`. See [Get Started](/docs/nexus/widgets) for provider setup and [Client ID](/docs/nexus/widgets#client-id) for details. In exact-input mode, set `mode: "deposit"` and `route: "exactInput"`. The user selects one or more tokens to spend and specifies input amounts. The widget quotes the destination deposit amount, handles quoting and routing, and dynamically provides the execution amount to `executeDeposit`. #### NPM ```tsx showLineNumbers import { NexusProvider, NexusWidget } from "@avail-project/widgets"; import { encodeFunctionData } from "viem"; const APP_DEPOSIT_CONTRACT = "0x..."; const DESTINATION_TOKEN = "0x..."; const DESTINATION_CHAIN_ID = 42161; const APP_DEPOSIT_ABI = [ { inputs: [ { internalType: "address", name: "token", type: "address" }, { internalType: "uint256", name: "amount", type: "uint256" }, { internalType: "address", name: "user", type: "address" }, ], name: "deposit", outputs: [], stateMutability: "nonpayable", type: "function", }, ] as const; const depositConfig = { mode: "deposit", route: "exactInput", // Enables exact input deposit mode destination: { chain: DESTINATION_CHAIN_ID, tokens: [ { address: DESTINATION_TOKEN, symbol: "USDT", decimals: 6, logo: "https://example.com/usdt.svg", }, ], }, depositAddress: APP_DEPOSIT_CONTRACT, executeDeposit: (_symbol, tokenAddress, amount, _chainId, user) => ({ to: APP_DEPOSIT_CONTRACT, gas: 400_000n, data: encodeFunctionData({ abi: APP_DEPOSIT_ABI, functionName: "deposit", args: [tokenAddress, amount, user], }), tokenApproval: { toTokenAddress: tokenAddress, amount, spender: APP_DEPOSIT_CONTRACT, }, }), validation: { minAmount: "1", maxAmount: "1000", }, appearance: { appName: "Your App", appLogoURL: "https://example.com/logo.svg", heading: "Deposit", mode: "system", primaryColor: "#0A6BEB", }, } as const; ; ``` #### shadcn/ui ```tsx showLineNumbers import NexusProvider from "@/components/nexus/NexusProvider"; import { NexusWidget } from "@/components/nexus/nexus"; import { encodeFunctionData } from "viem"; const APP_DEPOSIT_CONTRACT = "0x..."; const DESTINATION_TOKEN = "0x..."; const DESTINATION_CHAIN_ID = 42161; const APP_DEPOSIT_ABI = [ { inputs: [ { internalType: "address", name: "token", type: "address" }, { internalType: "uint256", name: "amount", type: "uint256" }, { internalType: "address", name: "user", type: "address" }, ], name: "deposit", outputs: [], stateMutability: "nonpayable", type: "function", }, ] as const; const depositConfig = { mode: "deposit", route: "exactInput", // Enables exact input deposit mode enableOnRamp: true, // Optional; defaults to false (wallet funding only). destination: { chain: DESTINATION_CHAIN_ID, tokens: [ { address: DESTINATION_TOKEN, symbol: "USDT", decimals: 6, logo: "https://example.com/usdt.svg", }, ], }, depositAddress: APP_DEPOSIT_CONTRACT, executeDeposit: (_symbol, tokenAddress, amount, _chainId, user) => ({ to: APP_DEPOSIT_CONTRACT, gas: 400_000n, data: encodeFunctionData({ abi: APP_DEPOSIT_ABI, functionName: "deposit", args: [tokenAddress, amount, user], }), tokenApproval: { toTokenAddress: tokenAddress, amount, spender: APP_DEPOSIT_CONTRACT, }, }), validation: { minAmount: "1", maxAmount: "1000", }, appearance: { appName: "Your App", appLogoURL: "https://example.com/logo.svg", heading: "Deposit", mode: "system", primaryColor: "#0A6BEB", }, } as const; ; ``` ## Configuration In exact-input mode, users specify what source assets and amounts they want to spend. The widget queries Nexus intent routes, quotes the destination amount to be deposited, and submits the intent via `swapAndExecute`. | Prop | Required | Description | | ---------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `config.mode` | Yes | Must be `"deposit"`. | | `config.route` | Yes | Set to `"exactInput"` for exact input mode. | | `config.destination.chain` | Yes | Chain ID of the fixed destination network. Deposit destination chain cannot be changed by the user. | | `config.destination.tokens` | Yes | One or more accepted destination tokens. The token dropdown is limited to this list and is editable only when multiple tokens are supplied. | | `config.depositAddress` | Yes | Smart contract address for the deposit target. | | `config.executeDeposit` | Yes | Callback builder returning `{ to, gas, data?, value?, tokenApproval? }` for the final app transaction. `gas` must be a positive `bigint`. | | `config.enableOnRamp` | Optional | Defaults to `false`. Set `true` to display "Deposit with Cash" alongside wallet funding. | | `config.validation.minAmount` | Optional | Minimum deposit amount. Must be `0` or greater. | | `config.validation.maxAmount` | Optional | Maximum deposit amount. Must be greater than `0`. | | `config.theme` | Optional | `"dark"`, `"light"`, or `"system"`. Defaults to light; system follows OS changes. Overrides `appearance.mode`. | | `config.appearance` | Optional | Supports `heading`, `appName`, `appLogoURL`, `mode`, and `primaryColor`. | | `connectedAddress` | Optional | Wallet address to use. If omitted, the connected wagmi account is used. | | `embed` | Optional | Defaults to `true`. Set `false` to render as a modal surface. | | `open`, `onOpenChange`, `defaultOpen` | Optional | Control the modal when `embed={false}`. | | `onStart`, `onComplete`, `onError`, `onClose`, `onConnectClick`, `onConnectWallet` | Optional | Host app callbacks. Use `onConnectClick` to open your app/header wallet modal from the element CTA. | ### Dynamic `executeDeposit` Execution In exact-input mode, the exact destination deposit amount depends on the quoted return of the input tokens. The Nexus SDK supports exact-input deposits via dynamic execution hooks (`beforeExecute`): ```ts type ExecuteDeposit = ( tokenSymbol: string, tokenAddress: `0x${string}`, amount: bigint, chainId: number, user: `0x${string}`, ) => { to: `0x${string}`; gas: bigint; data?: `0x${string}`; value?: bigint; tokenApproval?: { toTokenAddress: `0x${string}`; amount: bigint; spender: `0x${string}`; }; }; ``` 1. **Quoting**: As the user selects funding tokens and inputs amounts to spend, Nexus generates a quote for the destination token. 2. **Dynamic Calldata Construction**: The SDK invokes `beforeExecute` with the settled destination amount (`context.executeAmountRaw`), calling your `executeDeposit` callback with the actual quoted `amount`. 3. **Execution**: The widget executes token approvals (if required) and calls the deposit contract with the encoded calldata. 4. **Gas Requirement**: `executeDeposit` must return a positive `bigint` for `gas` (e.g. `400_000n`). ### Onramp & Diagnostics * **Local Currency Funding**: Setting `enableOnRamp: true` allows users to fund deposits with cash or card in addition to cross-chain wallet balances. * **Provider Verification**: Onramp checks the active wallet provider instead of trusting a persisted address. Disconnected users see "Connect Wallet" after quotes arrive; reconnecting refreshes the quote for the verified account before Pay is enabled. * **Wallet Disconnect Recovery**: If the wallet disconnects during checkout, reconnect the funded wallet from the settled purchase screen to finish the deposit. * **Modal Dismissal**: Popup widgets (`embed={false}`) dismiss through the header close button, not outside clicks or Escape. * **Console Tracing**: Filter the browser console by `[Nexus Onramp]` to trace payment status, SDK gas-swap events, wallet approval, and the final deposit receipt. ## Modal Usage Render `embed={false}` to invoke Deposit from a button and show the element as a modal. The widget still needs to sit under `NexusProvider`. ```tsx ``` Control the modal from app state when needed: ```tsx const [open, setOpen] = useState(false); ; ``` ## Supporting Multiple Opportunities / Selector UI Since `NexusWidget` executes a single targeted deposit flow at a time, build any opportunity selector in your parent component and render `NexusWidget` with the selected config: ```tsx import { useState } from "react"; import { NexusProvider, NexusWidget } from "@avail-project/widgets"; import { encodeFunctionData } from "viem"; const OPPORTUNITIES = { aave: { chain: 42161, token: { address: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", symbol: "USDT", decimals: 6, }, appName: "Aave", depositAddress: "0x794a61358D6845594F94dc1DB02A252b5b4814aD", executeDeposit: (symbol, tokenAddress, amount, chainId, user) => ({ to: "0x794a61358D6845594F94dc1DB02A252b5b4814aD", gas: 400_000n, data: encodeFunctionData({ /* Aave supply arguments */ }), tokenApproval: { toTokenAddress: tokenAddress, amount, spender: "0x794a61358D6845594F94dc1DB02A252b5b4814aD", }, }), }, compound: { chain: 8453, token: { address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", symbol: "USDC", decimals: 6, }, appName: "Compound", depositAddress: "0xc3d688B66703497DAA19211EEdff47f25384cdc3", executeDeposit: (symbol, tokenAddress, amount, chainId, user) => ({ to: "0xc3d688B66703497DAA19211EEdff47f25384cdc3", gas: 400_000n, data: encodeFunctionData({ /* Compound supply arguments */ }), tokenApproval: { toTokenAddress: tokenAddress, amount, spender: "0xc3d688B66703497DAA19211EEdff47f25384cdc3", }, }), }, }; export function MultiDepositShowcase({ address }) { const [selectedOpt, setSelectedOpt] = useState("aave"); return (
); } ``` ## Deposit (exact-output-mode) ## How exact-output mode works Enter and receive the exact amount you want to deposit. The widget suggests tokens to spend and quotes how much of each you’ll spend. You can change which tokens you pay with and get a new quote for the same deposit amount. > **Note** > > Looking for exact-input mode? See [Deposit (exact-input-mode)](/docs/nexus/widgets/deposit-exact-input) to fund deposits by specifying input tokens and amounts to spend. ## Installation #### NPM (Recommended) ```bash npm install @avail-project/widgets viem wagmi @tanstack/react-query ``` #### shadcn/ui (Power Users) ```bash npx shadcn@latest add availproject/widgets/nexus ``` ## Usage `NexusWidget` must be rendered inside `NexusProvider` (mandatory — it initializes the SDK), and the provider should receive your `identity.clientId`. See [Get Started](/docs/nexus/widgets) for provider setup and [Client ID](/docs/nexus/widgets#client-id) for details. #### NPM ```tsx showLineNumbers import { NexusProvider, NexusWidget } from "@avail-project/widgets"; import { encodeFunctionData } from "viem"; const APP_DEPOSIT_CONTRACT = "0x..."; const DESTINATION_TOKEN = "0x..."; const DESTINATION_CHAIN_ID = 42161; const APP_DEPOSIT_ABI = [ { inputs: [ { internalType: "address", name: "token", type: "address" }, { internalType: "uint256", name: "amount", type: "uint256" }, { internalType: "address", name: "user", type: "address" }, ], name: "deposit", outputs: [], stateMutability: "nonpayable", type: "function", }, ] as const; const depositConfig = { mode: "deposit", route: "exactOutput", // Optional; defaults to "exactOutput" destination: { chain: DESTINATION_CHAIN_ID, tokens: [ { address: DESTINATION_TOKEN, symbol: "USDT", decimals: 6, logo: "https://example.com/usdt.svg", }, ], }, depositAddress: APP_DEPOSIT_CONTRACT, executeDeposit: (_symbol, tokenAddress, amount, _chainId, user) => ({ to: APP_DEPOSIT_CONTRACT, gas: 400_000n, data: encodeFunctionData({ abi: APP_DEPOSIT_ABI, functionName: "deposit", args: [tokenAddress, amount, user], }), tokenApproval: { toTokenAddress: tokenAddress, amount, spender: APP_DEPOSIT_CONTRACT, }, }), prefill: { amount: "10", }, validation: { minAmount: "1", maxAmount: "1000", }, appearance: { appName: "Your App", appLogoURL: "https://example.com/logo.svg", heading: "Deposit", mode: "system", primaryColor: "#0A6BEB", }, } as const; ; ``` #### shadcn/ui ```tsx showLineNumbers import NexusProvider from "@/components/nexus/NexusProvider"; import { NexusWidget } from "@/components/nexus/nexus"; import { encodeFunctionData } from "viem"; const APP_DEPOSIT_CONTRACT = "0x..."; const DESTINATION_TOKEN = "0x..."; const DESTINATION_CHAIN_ID = 42161; const APP_DEPOSIT_ABI = [ { inputs: [ { internalType: "address", name: "token", type: "address" }, { internalType: "uint256", name: "amount", type: "uint256" }, { internalType: "address", name: "user", type: "address" }, ], name: "deposit", outputs: [], stateMutability: "nonpayable", type: "function", }, ] as const; const depositConfig = { mode: "deposit", route: "exactOutput", // Optional; defaults to "exactOutput" enableOnRamp: true, // Optional; defaults to false (wallet funding only). destination: { chain: DESTINATION_CHAIN_ID, tokens: [ { address: DESTINATION_TOKEN, symbol: "USDT", decimals: 6, logo: "https://example.com/usdt.svg", }, ], }, depositAddress: APP_DEPOSIT_CONTRACT, executeDeposit: (_symbol, tokenAddress, amount, _chainId, user) => ({ to: APP_DEPOSIT_CONTRACT, gas: 400_000n, data: encodeFunctionData({ abi: APP_DEPOSIT_ABI, functionName: "deposit", args: [tokenAddress, amount, user], }), tokenApproval: { toTokenAddress: tokenAddress, amount, spender: APP_DEPOSIT_CONTRACT, }, }), prefill: { amount: "10", }, validation: { minAmount: "1", maxAmount: "1000", }, appearance: { appName: "Your App", appLogoURL: "https://example.com/logo.svg", heading: "Deposit", mode: "system", primaryColor: "#0A6BEB", }, } as const; ; ``` ## Configuration Deposit widget configuration editor showing appearance and generated config options Generate the exact Deposit configuration needed for your app on the demo app. It generates the complete `depositConfig` object. Update your app’s branding, enabled funding options, and deposit destination. See your changes in the live preview and copy the config. Deposit uses `mode: "deposit"`. In exact-output mode (`route: "exactOutput"`, default), users enter the destination deposit amount, paying tokens and spend amounts are suggested and quoted, and the transaction submits through `swapAndExecute`. | Prop | Required | Description | | ---------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `config.mode` | Yes | Must be `"deposit"`. | | `config.route` | Optional | `"exactOutput"` (default) or `"exactInput"`. Exact-output mode lets users enter the target deposit amount to receive. | | `config.destination.chain` | Yes | Chain ID of the fixed destination network. Deposit destination chain cannot be changed by the user. | | `config.destination.tokens` | Yes | One or more accepted destination tokens. The token dropdown is limited to this list and is editable only when multiple tokens are supplied. | | `config.depositAddress` | Yes | Smart contract address for the deposit target. | | `config.executeDeposit` | Yes | Callback builder returning `{ to, gas, data?, value?, tokenApproval? }` for the final app transaction. `gas` must be a positive `bigint`. | | `config.enableOnRamp` | Optional | Defaults to `false`. Set `true` to display "Deposit with Cash" alongside wallet funding. | | `config.prefill.amount` | Optional | Prefills the exact output deposit amount. Must be greater than `0`. | | `config.validation.minAmount` | Optional | Minimum deposit amount. Must be `0` or greater. | | `config.validation.maxAmount` | Optional | Maximum deposit amount. Must be greater than `0`. | | `config.theme` | Optional | `"dark"`, `"light"`, or `"system"`. Defaults to light; system follows OS changes. Overrides `appearance.mode`. | | `config.appearance` | Optional | Supports `heading`, `appName`, `appLogoURL`, `mode`, and `primaryColor`. | | `connectedAddress` | Optional | Wallet address to use. If omitted, the connected wagmi account is used. | | `embed` | Optional | Defaults to `true`. Set `false` to render as a modal surface. | | `open`, `onOpenChange`, `defaultOpen` | Optional | Control the modal when `embed={false}`. | | `onStart`, `onComplete`, `onError`, `onClose`, `onConnectClick`, `onConnectWallet` | Optional | Host app callbacks. Use `onConnectClick` to open your app/header wallet modal from the element CTA. | ### Onramp & Diagnostics * **Local Currency Onramp**: Set `enableOnRamp: true` to offer local currency funding alongside wallet funding. Defaults to `false`, opening directly in the wallet deposit flow. * **Provider Verification**: Onramp checks the active wallet provider instead of trusting a persisted address. Disconnected users see "Connect Wallet" after quotes arrive; reconnecting refreshes the quote for the verified account before Pay is enabled. * **Wallet Disconnect Recovery**: If the wallet disconnects during checkout, reconnect the funded wallet from the settled purchase screen to finish the deposit. * **Modal Dismissal**: Popup widgets (`embed={false}`) dismiss through the header close button, not outside clicks or Escape. * **Console Tracing**: Filter the browser console by `[Nexus Onramp]` to trace payment status, SDK gas-swap events, wallet approval, and the final deposit receipt. ### `executeDeposit` ```ts type ExecuteDeposit = ( tokenSymbol: string, tokenAddress: `0x${string}`, amount: bigint, chainId: number, user: `0x${string}`, ) => { to: `0x${string}`; gas: bigint; data?: `0x${string}`; value?: bigint; tokenApproval?: { toTokenAddress: `0x${string}`; amount: bigint; spender: `0x${string}`; }; }; ``` ## Modal Usage Render `embed={false}` to invoke Deposit from a button and show the element as a modal. The widget still needs to sit under `NexusProvider`. ```tsx ``` Control the modal from app state when needed. ```tsx const [open, setOpen] = useState(false); ; ``` ## Supporting Multiple Opportunities / Selector UI Since `NexusWidget` executes a single targeted deposit flow at a time, build any opportunity selector in your parent component and render `NexusWidget` with the selected config: ```tsx import { useState } from "react"; import { NexusProvider, NexusWidget } from "@avail-project/widgets"; import { encodeFunctionData } from "viem"; // Define the Aave & Compound pools/ABIs... const OPPORTUNITIES = { aave: { chain: 42161, token: { address: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", symbol: "USDT", decimals: 6, }, appName: "Aave", depositAddress: "0x794a61358D6845594F94dc1DB02A252b5b4814aD", executeDeposit: (symbol, tokenAddress, amount, chainId, user) => ({ to: "0x794a61358D6845594F94dc1DB02A252b5b4814aD", gas: 400_000n, data: encodeFunctionData({ /* Aave supply arguments */ }), tokenApproval: { toTokenAddress: tokenAddress, amount, spender: "0x794a61358D6845594F94dc1DB02A252b5b4814aD", }, }), }, compound: { chain: 8453, token: { address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", symbol: "USDC", decimals: 6, }, appName: "Compound", depositAddress: "0xc3d688B66703497DAA19211EEdff47f25384cdc3", executeDeposit: (symbol, tokenAddress, amount, chainId, user) => ({ to: "0xc3d688B66703497DAA19211EEdff47f25384cdc3", gas: 400_000n, data: encodeFunctionData({ /* Compound supply arguments */ }), tokenApproval: { toTokenAddress: tokenAddress, amount, spender: "0xc3d688B66703497DAA19211EEdff47f25384cdc3", }, }), }, }; export function MultiDepositShowcase({ address }) { const [selectedOpt, setSelectedOpt] = useState("aave"); return (
); } ``` ## Get Started Avail Widgets are shadcn-based components built on the Nexus SDK. **Nexus Widget** — the single unified component — handles swap, send, and deposit across chains. - [**Deposit (exact-output-mode)**](/docs/nexus/widgets/deposit) — Enter the exact amount to deposit; widget suggests and quotes tokens to spend. - [**Deposit (exact-input-mode)**](/docs/nexus/widgets/deposit-exact-input) — Choose tokens and amounts to spend; widget quotes and deposits the received amount. - [**Swap and Bridge Widget**](/docs/nexus/widgets/swaps) — Swap and bridge assets across chains with the unified Nexus intent flow. - [**Send Widget**](/docs/nexus/widgets/transfer) — Send assets to any recipient on any chain with built-in progress tracking. ## Try It Live ## Install Nexus Widget **Nexus Widget** is the single unified component for all swap, send, and deposit flows. Choose your preferred install method: #### NPM (Recommended) ### Install the package ```bash npm install @avail-project/widgets ``` > **Peer dependencies:** `viem` and `wagmi` are required peer dependencies. Install them if you haven't already: > > ```bash > npm install @avail-project/widgets viem wagmi @tanstack/react-query > ``` The package ships pre-compiled — no additional dependency installs or file copying required. ### Wrap your app with the provider ```tsx showLineNumbers "use client"; import { NexusProvider } from "@avail-project/widgets"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` `NexusProvider` is mandatory — it creates the Nexus SDK client, exposes `useNexus()`, and every `NexusWidget` must be rendered inside it. See [Client ID](#client-id) for what `clientId` is and how to get one. ### Initialize on wallet connect ```tsx showLineNumbers "use client"; import { useEffect } from "react"; import { useAccount, useConnectorClient } from "wagmi"; import type { EthereumProvider } from "@avail-project/nexus-core"; import { useNexus } from "@avail-project/widgets"; export function InitNexusOnConnect() { const { status, connector } = useAccount(); const { data: walletClient } = useConnectorClient(); const { handleInit } = useNexus(); useEffect(() => { if (status !== "connected") return; void (async () => { const mobileProvider = walletClient ? ({ request: (args: unknown) => walletClient.request(args as never) } as EthereumProvider) : undefined; const desktopProvider = await connector?.getProvider(); const provider = mobileProvider ?? (desktopProvider as EthereumProvider | undefined); if (!provider || typeof provider.request !== "function") return; await handleInit(provider); })(); }, [status, connector, walletClient, handleInit]); return null; } ``` ### Use the widget ```tsx showLineNumbers import { NexusProvider, NexusWidget } from "@avail-project/widgets"; export function App({ address }: { address?: `0x${string}` }) { return ( ); } ``` If `NexusProvider` already wraps your app (as in the layout above), render `NexusWidget` anywhere beneath it and omit the extra provider. #### shadcn/ui (Power Users) > **Note** > > **For power users** who want full source control — copy the component files into your project and customize freely. ### Install shadcn/ui first

If you haven't already, set up shadcn/ui in your project first. Follow the official installation guide

Once installed, come back to continue with Avail Widgets.

### Install Nexus Widget via CLI ```bash npx shadcn@latest add availproject/widgets/nexus ``` This installs from the public GitHub registry and copies all required files and dependencies into your project. ### Wrap your app with the provider ```tsx showLineNumbers "use client"; import NexusProvider from "@/components/nexus/NexusProvider"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` `NexusProvider` is mandatory — it creates the Nexus SDK client, exposes `useNexus()`, and every `NexusWidget` must be rendered inside it. See [Client ID](#client-id) for what `clientId` is and how to get one. ### Initialize on wallet connect ```tsx showLineNumbers "use client"; import { useEffect } from "react"; import { useAccount, useConnectorClient } from "wagmi"; import type { EthereumProvider } from "@avail-project/nexus-core"; import { useNexus } from "@/components/nexus/NexusProvider"; export function InitNexusOnConnect() { const { status, connector } = useAccount(); const { data: walletClient } = useConnectorClient(); const { handleInit } = useNexus(); useEffect(() => { if (status !== "connected") return; void (async () => { const mobileProvider = walletClient ? ({ request: (args: unknown) => walletClient.request(args as never) } as EthereumProvider) : undefined; const desktopProvider = await connector?.getProvider(); const provider = mobileProvider ?? (desktopProvider as EthereumProvider | undefined); if (!provider || typeof provider.request !== "function") return; await handleInit(provider); })(); }, [status, connector, walletClient, handleInit]); return null; } ``` ### Use the widget ```tsx showLineNumbers import NexusProvider from "@/components/nexus/NexusProvider"; import { NexusWidget } from "@/components/nexus/nexus"; export function App({ address }: { address?: `0x${string}` }) { return ( ); } ``` If `NexusProvider` already wraps your app (as in the layout above), render `NexusWidget` anywhere beneath it and omit the extra provider. * [Deposit (exact-output-mode)](/docs/nexus/widgets/deposit): `config.mode = "deposit"` (`route: "exactOutput"`, default) — enter the exact amount you want to deposit. * [Deposit (exact-input-mode)](/docs/nexus/widgets/deposit-exact-input): `config.mode = "deposit"` (`route: "exactInput"`) — choose one or more tokens to fund your deposit and review quoted returns. * [Swap and Bridge](/docs/nexus/widgets/swaps): `config.mode = "swap"` — swap and bridge assets through one unified intent flow. * [Send](/docs/nexus/widgets/transfer): `config.mode = "send"` — send assets to a recipient. ## Client ID `clientId` identifies **your app** (not an end user or wallet) to Nexus. Set it once on `NexusProvider`: ```tsx {children} ``` > **Treat clientId as required** > > If you omit it, `NexusProvider` falls back to your site's hostname (for example `example.com`, or `localhost` for local and non-browser environments). Widgets still work, but the fallback is not a stable identity, and widget telemetry is only recorded when you pass an explicit `identity.clientId`. Always set it in production. * **One stable ID per integrating app.** Reuse the same value across environments and deployments of that app. * **Getting an ID.** Client IDs are currently allocated by the Nexus team — reach out to Avail to get one for your app. Don't ship the `YOUR_NEXUS_CLIENT_ID` placeholder. * **Both install methods.** The npm package and the shadcn component accept the same `config.identity` shape. * **Opting out.** To disable widget telemetry, set `observability: { disableLogging: true }` in the provider config. ## Swap and Bridge Widget Swap and Bridge widget showing a multi-asset swap ## Installation #### NPM (Recommended) ```bash npm install @avail-project/widgets viem wagmi @tanstack/react-query ``` #### shadcn/ui (Power Users) ```bash npx shadcn@latest add availproject/widgets/nexus ``` ## Usage `NexusWidget` must be rendered inside `NexusProvider` (mandatory — it initializes the SDK), and the provider should receive your `identity.clientId`. See [Get Started](/docs/nexus/widgets) for provider setup and [Client ID](/docs/nexus/widgets#client-id) for details. #### NPM ```tsx showLineNumbers import { NexusProvider, NexusWidget } from "@avail-project/widgets"; const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" as const; export function SwapAndBridgeExample({ address }: { address?: `0x${string}` }) { return ( ); } ``` #### shadcn/ui ```tsx showLineNumbers import NexusProvider from "@/components/nexus/NexusProvider"; import { NexusWidget } from "@/components/nexus/nexus"; const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" as const; export function SwapAndBridgeExample({ address }: { address?: `0x${string}` }) { return ( ); } ``` ## Configuration Swap and Bridge widget configuration editor showing appearance and multi-asset options Generate the exact Swap and Bridge configuration needed for your app on the demo app. It generates the complete config object. Update the widget appearance, multi-asset mode, receive token, and receiving address behavior. See your changes in the live preview and copy the config. Swap and Bridge uses only `mode: "swap"`. This mode is exact-in: users enter the source amount, the quote determines the receive amount, and the flow submits through `swapWithExactIn`. | Prop | Required | Description | | --------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | | `config.mode` | Yes | Must be `"swap"`. | | `config.destination.chain` | Optional | Restricts the receive chain when supplied. | | `config.destination.tokens` | Optional | Restricts receive tokens when supplied with `destination.chain`. Each token needs `address`, `symbol`, and `decimals`. | | `config.prefill.token` | Optional | Sets the initial receive token without restricting later choices. Ignored when `config.destination.tokens` is supplied. | | `config.recipientAddress` | Optional | Prefills the recipient address for custom-recipient swaps. | | `config.theme` | Optional | `"dark"`, `"light"`, or `"system"`. Defaults to light; system follows OS changes. Overrides `appearance.mode`. | | `config.appearance` | Optional | Supports `heading`, `appName`, `appLogoURL`, `mode`, and `primaryColor`. | | `connectedAddress` | Optional | Wallet address to use. If omitted, the connected wagmi account is used. | | `embed` | Optional | Defaults to `true`. Set `false` to render as a modal surface. | | `onStart`, `onComplete`, `onError`, `onClose` | Optional | Lifecycle callbacks for host apps. | Swap supports `prefill.token` only. It does not support `prefill.amount` or validation yet. ```ts title="SwapAndBridgeConfig" type DestinationToken = { address: `0x${string}`; symbol: string; decimals: number; logo?: string; }; interface SwapAndBridgeConfig { mode: "swap"; theme?: "dark" | "light" | "system"; destination?: { chain?: number; tokens?: DestinationToken[]; }; prefill?: { token?: DestinationToken & { chain: number }; }; recipientAddress?: `0x${string}`; appearance?: { heading?: string; appName?: string; appLogoURL?: string; mode?: "system" | "light" | "dark"; primaryColor?: string; }; } ``` Use `destination.tokens` when you want to restrict the receive selector. Use `prefill.token` when you only want the initial receive token selected and still want users to choose from the full supported list. If both are supplied, `destination.tokens[0]` is honored and `prefill.token` is ignored. ## Send Widget Send widget showing recipient, amount, and payment assets ## Installation #### NPM (Recommended) ```bash npm install @avail-project/widgets viem wagmi @tanstack/react-query ``` #### shadcn/ui (Power Users) ```bash npx shadcn@latest add availproject/widgets/nexus ``` ## Usage `NexusWidget` must be rendered inside `NexusProvider` (mandatory — it initializes the SDK), and the provider should receive your `identity.clientId`. See [Get Started](/docs/nexus/widgets) for provider setup and [Client ID](/docs/nexus/widgets#client-id) for details. #### NPM ```tsx showLineNumbers import { NexusProvider, NexusWidget } from "@avail-project/widgets"; const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" as const; const RECIPIENT = "0xF3a15b38e63dBb1a1b2d7842CcD9B9dD8fB9b2E" as const; export function SendExample({ address }: { address?: `0x${string}` }) { return ( ); } ``` #### shadcn/ui ```tsx showLineNumbers import NexusProvider from "@/components/nexus/NexusProvider"; import { NexusWidget } from "@/components/nexus/nexus"; const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" as const; const RECIPIENT = "0xF3a15b38e63dBb1a1b2d7842CcD9B9dD8fB9b2E" as const; export function SendExample({ address }: { address?: `0x${string}` }) { return ( ); } ``` ## Configuration Send widget configuration editor showing appearance, token, amount, and recipient options Generate the exact Send configuration needed for your app on the demo app. It generates the complete config object. Update the widget appearance, default send token, amount defaults, and recipient. See your changes in the live preview and copy the config. Send uses only `mode: "send"`. This mode is exact-out: users enter the token amount to send, pay-with sources are selected, and the flow submits through the v2 transfer execution path. | Prop | Required | Description | | --------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- | | `config.mode` | Yes | Must be `"send"`. | | `config.destination.chain` | Optional | Restricts the destination chain when supplied. If omitted, users can change chains. | | `config.destination.tokens` | Optional | Restricts destination tokens when supplied with `destination.chain`. Each token needs `address`, `symbol`, and `decimals`. | | `config.recipientAddress` | Optional | Prefills and locks the recipient address. A recipient is required before users can submit. | | `config.prefill.amount` | Optional | Prefills the exact output send amount. Must be greater than `0`. | | `config.prefill.token` | Optional | Sets the initial destination token without restricting later choices. Ignored when `config.destination.tokens` is supplied. | | `config.validation.minAmount` | Optional | Minimum amount. Must be `0` or greater. | | `config.validation.maxAmount` | Optional | Maximum amount. Must be greater than `0`. | | `config.theme` | Optional | `"dark"`, `"light"`, or `"system"`. Defaults to light; system follows OS changes. Overrides `appearance.mode`. | | `config.appearance` | Optional | Supports `heading`, `appName`, `appLogoURL`, `mode`, and `primaryColor`. | | `connectedAddress` | Optional | Wallet address to use. If omitted, the connected wagmi account is used. | | `embed` | Optional | Defaults to `true`. Set `false` to render as a modal surface. | | `onStart`, `onComplete`, `onError`, `onClose` | Optional | Lifecycle callbacks for host apps. | ```ts title="SendConfig" type DestinationToken = { address: `0x${string}`; symbol: string; decimals: number; logo?: string; }; interface SendConfig { mode: "send"; destination?: { chain?: number; tokens?: DestinationToken[]; }; recipientAddress?: `0x${string}`; prefill?: { amount?: string; token?: DestinationToken & { chain: number }; }; validation?: { minAmount?: string; maxAmount?: string; }; } ``` Use `destination.tokens` when you want to restrict the selector. Use `prefill.token` when you only want the initial token selected and still want users to choose from the full supported list. If both are supplied, `destination.tokens[0]` is honored and `prefill.token` is ignored. ## Unified Balance > **Warning** > > **Deprecated.** The standalone Unified Balance component has been removed in favor of > **Nexus Widget**. The cross-chain unified balance is rendered inline as part of the > swap, send, and deposit flows — see [Swap and Bridge Widget](/docs/nexus/widgets/swaps). ## Migrating Remove the standalone `` component and install Nexus Widget instead: ```bash npx shadcn@latest add availproject/widgets/nexus ``` The unified balance view (per-token, per-chain breakdown across connected chains) is shown automatically inside the widget. See [Get Started](/docs/nexus/widgets) to wire it up, and the [Widgets migration guide](/docs/nexus/migration/widgets-legacy-to-nexus-widget) for the full legacy-to-widget mapping. For programmatic access to balances, use the SDK directly — see [Get Balances](/docs/nexus/nexus-sdk/reference/balances-and-tokens/get-balances). ## FastBridge [FastBridge](https://fastbridge.availproject.org/) is a unified cross-chain bridge built by Avail. Send tokens from multiple source chains and receive a single output on any supported destination, all in one transaction. It supports swaps natively, so you can send one token and receive a completely different one at the destination. Unlike traditional bridges, FastBridge never requires native gas tokens. Fees are paid directly from your stablecoin balance (USDC or USDT), and transactions settle in approximately 10 to 20 seconds through a solver-fronted liquidity model. Launch FastBridge > **Note** > > FastBridge is available today as a standalone interface, and is also built into the [FastBridge Widget](/docs/nexus/widgets) and the [Nexus SDK](/docs/nexus/nexus-sdk/get-started/installation) for embedding into any app. ## How it works FastBridge uses intent-based bridging. You declare which assets to send from which source chains, and the protocol determines the best route to deliver your chosen output token to the destination chain. Solvers compete to fill the intent using native assets, with no wrapping or synthetic tokens. * **Multi-source input** — Combine balances across multiple chains and tokens into a single transaction. * **Exact-In and Exact-Out** — Specify either the amount you want to send, or the exact amount you want to receive. FastBridge calculates the rest. * **Gas abstraction** — Gas is deducted from your stablecoin balance. No ETH, AVAX, or other native gas tokens are needed. * **Zero slippage on stablecoins** — Stablecoin-to-stablecoin transfers settle at exactly the quoted amount. Other swaps show price impact up front before confirmation. ## Supported networks, tokens, and wallets | | | | -------------- | ----------------------------------------------------------------------------------------------------------- | | **Chains** | Monad, MegaETH, Citrea, Arbitrum, Avalanche, Ethereum, Optimism, Base, Arc Mainnet, Polygon, BNB, HyperEVM. | | **Tokens** | All tokens on all supported chains. | | **Amounts** | No minimum. Maximum $10M per transfer. | | **Wallets** | Any EVM-compatible wallet (MetaMask, Rabby, Coinbase Wallet, WalletConnect). | | **Settlement** | \~10–20 seconds. | ## Security model FastBridge is fully non-custodial. Funds are never held by a centralised party; the protocol routes and settles trustlessly without taking custody at any point. If a transaction fails to settle at the destination, it reverts and your assets remain on the source chain. Security is backed by [Avail Nexus](/docs/nexus), which provides unified proof verification and intent settlement across chains via a decentralised coordination layer rather than a multisig or federated validator set. ## What makes FastBridge different | | | | --------------------------- | --------------------------------------------------------------------------------------------------------------------- | | **Multi-source with swaps** | Send multiple different tokens from multiple chains and receive a single token at the destination in one transaction. | | **Unified balance view** | Balances across all connected chains are read simultaneously and shown as a single consolidated total. | | **Gas abstraction** | Pay gas in stablecoins. No native gas tokens required on any source chain. | | **Exact-In and Exact-Out** | Choose the send or receive amount and FastBridge handles multi-source aggregation automatically. | ## Developer integrations - [**FastBridge Widget**](/docs/nexus/widgets/swaps) — Pre-built React component for cross-chain bridging and swaps. - [**Nexus SDK**](/docs/nexus/nexus-sdk/get-started/installation) — TypeScript SDK for building custom multi-chain experiences. ## FastBridge at a glance | | | | ---------------- | ---------------------------------------------------------------------------------------------------------- | | **Developer** | Avail Project | | **Type** | Unified bridge and swap | | **Architecture** | Intent-based, solver-fronted | | **Tokens** | All tokens on all supported chains | | **Custody** | Non-custodial | | **Settlement** | \~10–20 seconds | | **Built on** | Avail Nexus Protocol | | **Chains** | Ethereum, Optimism, Base, Monad, MegaETH, Citrea, Arbitrum, Avalanche, Arc Mainnet, Polygon, BNB, HyperEVM | | **Min / Max** | No minimum, $10M max per transfer | | **Wallets** | MetaMask, Rabby, Coinbase Wallet, WalletConnect | | **Multi-source** | Yes | | **Gas fees** | Paid in stablecoins (USDC / USDT) | | **Wrapping** | None, native assets only | ## Supported Chains & Tokens ### Mainnet Chains | Network | Chain ID | Native | Supported Tokens | Swaps | |---------|----------|--------|-----------------|-------| | Arc Mainnet | 5042 | USDC | USDC | Yes | | Ethereum | 1 | ETH | ETH, USDC, USDT | Yes | | Optimism | 10 | ETH | ETH, USDC, USDT | Yes | | Polygon | 137 | POL | POL, USDC, USDT | Yes | | Arbitrum One | 42161 | ETH | ETH, USDC, USDT | Yes | | Avalanche C-Chain | 43114 | AVAX | AVAX, USDC, USDT | Yes | | Base | 8453 | ETH | ETH, USDC, USDT | Yes | | BNB Chain | 56 | BNB | BNB, USDC, USDT | Yes | | HyperEVM | 999 | HYPE | HYPE, USDC, USDT | Yes | | Monad | 143 | MON | MON, USDC, USDT | Yes | | Citrea | 4114 | cBTC | cBTC, USDC.e, USDT.e | Yes | | MegaETH | 4326 | ETH | ETH, USDM, USDT | Yes | ### Testnet Chains > Swaps are not supported in testnet environments. | Network | Chain ID | Native | Supported Tokens | |---------|----------|--------|-----------------| | Arc Testnet | 5042002 | USDC | USDC | | Ethereum Sepolia | 11155111 | ETH | ETH, USDC | | Optimism Sepolia | 11155420 | ETH | ETH, USDC, USDT | | Polygon Amoy | 80002 | POL | POL, USDC | | Arbitrum Sepolia | 421614 | ETH | ETH, USDC, USDT | | Base Sepolia | 84532 | ETH | ETH, USDC | | Monad Testnet | 10143 | MON | MON, USDC | | Citrea Testnet | 5115 | cBTC | cBTC, USDC | ### Token Reference | Token | Name | |-------|------| | ETH | Ethereum | | USDC | USD Coin | | USDT | Tether USD | | USDC.e | Bridged USDC (Citrea) | | USDT.e | Bridged USDT (Citrea) | | USDM | USDm | | POL | Polygon | | AVAX | Avalanche | | BNB | BNB | | HYPE | Hyperliquid | | MON | Monad | | cBTC | Citrea BTC | ## Token Allowances Before an ERC-20 source can fund an intent, the spender named in the quote needs permission to move it. Native-token sources need no allowance; the wallet sends them directly. The quote lists what is needed in `quote.allowances`. Each entry has the `chainId`, `tokenAddress`, `spender`, `currentRaw`, `requiredRaw`, and `deficitRaw`. The SDK only acts on entries with a positive `deficitRaw`. ## How the SDK grants allowances After the user approves the quote, the SDK handles each missing allowance: * **Permit tokens.** The wallet signs an EIP-712 message (`source_approval_signature` step). No transaction and no gas. * **Other tokens.** The wallet sends an ERC-20 `approve` for exactly `requiredRaw` (`erc20_approval` step). The SDK never grants unlimited approvals. Your app does not choose approval amounts. The approvals appear in `quote.plan.steps`, so show them in your review UI. > **Warning** > > An `approve` transaction needs native gas on the source chain. Without it, the quote fails with `BACKEND_INSUFFICIENT_APPROVAL_GAS`, even for a rate preview. Fund the source chain or choose another source. ## Failed approvals If an approval fails, the error carries `error.details.approvals` with each broadcast approval's state: `confirmed`, `reverted`, or `unconfirmed`. An unconfirmed approval may still confirm, so request a fresh quote before trying again. ## Destination approvals Contract calls on the destination chain use a separate approval. Set `execute.tokenApproval` in `swapAndExecute()`, or `tokenApproval` in `execute()`, and the SDK approves the contract when the current allowance is too low. See [swap and execute](/docs/nexus/nexus-sdk/reference/swap-methods/swap-and-execute). ## What is Chain Abstraction? Users often hold enough funds in total, but spread across chains. Moving them by hand means bridging chain by chain, switching networks, and paying gas on each. Chain abstraction removes those steps. Your app asks for an outcome, such as "100 USDC on Base", and Nexus works out where the funds come from: 1. The SDK requests a quote. The Nexus middleware picks the source balances and one provider to fill the [intent](/docs/nexus/concepts/intent). 2. Your app shows the quote, and the user approves it. 3. The user signs the intent once. Token approvals and native-token deposits may add wallet prompts on source chains. 4. The provider delivers funds on the destination chain, usually within seconds. See the [intent lifecycle](/docs/nexus/concepts/intent-lifecycle). *Showing a subset of supported chains. See [Supported Chains & Tokens](/docs/nexus/supported-chains-and-tokens) for the full list.* For users, this means one review instead of a bridge per chain. For developers, it means one SDK call per operation, with no per-chain bridge logic and no contract changes when Nexus adds a chain. ## How Intents are Processed Every swap, bridge, and funded contract call runs the same lifecycle. The SDK reports each step through `onEvent`, and the operation's promise resolves only after the intent is fulfilled. ## Stages ### Quote The SDK requests a quote. The middleware selects source balances and a [provider](/docs/nexus/concepts/solvers), and returns the [intent](/docs/nexus/concepts/intent) with its fees and plan. ### Review Your `onIntent` hook shows the quote. The user approves with `allow()`, cancels with `deny()`, or requests a new quote with `refresh()`. Without a hook, the SDK accepts the quote. ### Approvals For each source token without enough allowance, the wallet sends an ERC-20 approval or signs a permit. See [allowances](/docs/nexus/concepts/allowances). ### Signature The user signs the intent once with `personal_sign`. ### Native deposits Native-token sources are sent from the wallet as source-chain transactions. ### Submission and fulfillment The SDK submits the signed intent and polls its status: `created`, then `deposited` once source funds arrive, then `fulfilled` when the provider delivers on the destination chain. The SDK polls every 2 seconds (`pollingIntervalMs`) and waits up to 2 minutes (`fillTimeoutMinutes`). An intent that is not filled before its expiry becomes `expired`. > **Warning** > > A timeout or rejected promise after submission does not mean the intent failed. It may still fulfill. Check `listIntents()` or the [Nexus Explorer](https://nexus-v2.mainnet.avail.so/) before retrying. `listIntents()` returns the same statuses: `created`, `deposited`, `fulfilled`, or `expired`. ## What is an Intent? An intent is a request to receive a token on a destination chain, paid for with tokens the user holds on one or more source chains. The user signs it once, and a provider fills it. Every intent starts as a quote from the Nexus middleware. The SDK passes the quote to your `onIntent` hook so the user can review it before anything is signed. ## What a quote contains | Field | Meaning | | ---------------- | ----------------------------------------------------------------------------- | | `provider` | The provider that fills this quote: `nexus-v2`, `mayan`, or `relay` | | `tradeType` | `exactInput` or `exactOutput` | | `input` | One entry per source chain and token: amount, deposit fee, and total required | | `output` | Destination chain, token, quoted `amountRaw`, and guaranteed `minAmountRaw` | | `fees` | Deposit, fulfillment, protocol, and solver fees, in raw units and USD | | `allowances` | Approvals the user still needs on source chains | | `plan` | The ordered steps the SDK will run | | `sourceVerdicts` | Whether each candidate source was selected, unused, or unroutable, and why | | `expiresAt` | Quote expiry, in Unix seconds | Amounts ending in `Raw` are `bigint` values in the token's smallest unit. USD amounts are decimal strings. ```typescript title="TypeScript" onIntent: ({ quote, allow }) => { console.log(quote.provider, quote.output.minAmountRaw, quote.fees.solverUsd); allow(); }; ``` The user can ask for a new quote with `refresh()` before approving. After approval, the quote is fixed. See [review quotes](/docs/nexus/nexus-sdk/reference/swap-methods/review-quotes) for the full hook. ## Bridge, Transfer & Execute Every cross-chain operation in the SDK is an [intent](/docs/nexus/concepts/intent). Bridging, transfers, and contract calls are different ways to use one. | Operation | Method | | ---------------- | -------------------------------------------------------------------------- | | Bridge | `swapWithExactOut` or `swapWithExactIn`, with the same asset on each chain | | Transfer | `swapAndExecute`, with a token transfer as the destination call | | Execute | `execute`, using funds already on the destination chain | | Fund and execute | `swapAndExecute` | ## Bridge A bridge is a same-asset swap. Pass the asset's address on each source chain and on the destination chain. Funds always arrive at the connected wallet. ```typescript title="TypeScript" const BASE_USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; const usdc = await client.getToken({ chainId: 8453, tokenAddress: BASE_USDC }); await client.swapWithExactOut({ toChainId: 8453, toTokenAddress: BASE_USDC, toAmountRaw: parseUnits('100', usdc.decimals), }); ``` ## Transfer Swaps have no recipient field. To deliver funds to another address, use `swapAndExecute` and make the destination call a transfer. Your wallet funds itself on the destination chain, then sends the call. ```typescript title="TypeScript" import { encodeFunctionData, erc20Abi } from 'viem'; await client.swapAndExecute({ toChainId: 8453, toTokenAddress: BASE_USDC, toAmountRaw: amount, execute: { to: BASE_USDC, data: encodeFunctionData({ abi: erc20Abi, functionName: 'transfer', args: [recipient, amount] }), gas: 100_000n, }, }); ``` For a native-token transfer, set `execute.to` to the recipient and `execute.value` to the amount. ## Execute `execute()` calls any contract on a supported chain with funds the wallet already holds there. You supply the contract address, calldata, and an optional token approval. ## Fund and execute `swapAndExecute()` funds a destination contract call, then runs it. With exact output, it funds only the shortfall and skips the swap when the wallet already holds enough (`result.swapSkipped`). By default the connected wallet sends the call after fulfillment (`executionMode: 'wallet'`). With `useProviderDestinationExecution: true`, Relay executes the call as part of fulfillment instead (`executionMode: 'provider'`). See [swap and execute](/docs/nexus/nexus-sdk/reference/swap-methods/swap-and-execute). ## Liquidity Routing Every intent draws funds from one or more source balances. By default the Nexus middleware chooses them from the wallet's balances. Pass `sources` to limit its choice. ## Exact output: optional sources For `swapWithExactOut` and exact-output `swapAndExecute`, `sources` is optional. Each entry needs a `chainId` and can add a `tokenAddress`. ```typescript title="TypeScript" sources?: { chainId: number; tokenAddress?: Hex }[]; ``` * Omit `sources`, or pass `[]`, to let the quote use any wallet balance. * Pass entries to restrict the quote to those chains or tokens. They are candidates; the quote decides how much to take from each. ## Exact input: required sources For `swapWithExactIn` and exact-input `swapAndExecute`, you choose every source and its amount. ```typescript title="TypeScript" sources: { chainId: number; tokenAddress: Hex; amountRaw: bigint }[]; ``` All sources must share one provider with the destination. In a picker, pass the sources already selected to `getAvailableSourceTokens(destination, selected)` so it only offers compatible tokens. ## Changing sources during review The `onIntent` hook's `refresh(sources)` returns a new quote for a different selection, so users can change sources without restarting the operation. Render the quote it returns. For exact input, every replacement source still needs `tokenAddress` and `amountRaw`. `quote.sourceVerdicts` explains each candidate: `selected`, `unused`, or `unroutable` with a `reason` such as `BELOW_DEPOSIT_FEE` or `INSUFFICIENT_APPROVAL_GAS`. Use it to show why a balance was skipped. ## Solvers and Providers Each quote names one provider in `quote.provider`. The provider is responsible for delivering the destination output. The middleware chooses it per quote; your integration code does not change. | Provider | Who fills the intent | | ---------- | -------------------- | | `nexus-v2` | Nexus solvers | | `mayan` | Mayan's network | | `relay` | Relay's network | ## Nexus solvers Solvers are market makers. Once the user's funds are deposited on the source chains, a solver fronts its own capital on the destination chain to fill the intent. Users receive funds in seconds instead of waiting for cross-chain finality. The protocol then repays the solver from the deposited source funds through an on-chain settlement authorized by Nexus validators. Settlement runs in the background; the user's transfer is already complete. Avail runs the Nexus solvers today. As the solver network decentralizes, multiple solvers will compete to fill each intent. To fill an intent means to deliver the funds it requests on the destination chain. ## Crosschain Swaps A crosschain swap converts tokens on one or more source chains into a token on a destination chain, in a single [intent](/docs/nexus/concepts/intent). Tokens are identified by chain ID and contract address, not symbol. ## How swaps are routed The Nexus middleware quotes each swap. It picks the source balances and one provider (`nexus-v2`, `mayan`, or `relay`) that fills the whole swap from source to destination. The quote names it in `quote.provider` and is fixed once the user approves. You do not choose or combine routes. To steer a quote, restrict its sources; see [liquidity routing](/docs/nexus/concepts/source-chain-selection). Before quoting, the catalog shows which tokens can pair: * `getAvailableSourceTokens(destination)` lists sources that can reach a destination, grouped by provider. * `getAvailableDestinationTokens(sources)` lists destinations reachable from the given sources. * `confirmRouteExists(sources, destination)` checks catalog compatibility. It does not guarantee a quote. ## Exact input `swapWithExactIn` spends a fixed raw amount from every source. The output varies with price and fees. All sources and the destination must share one provider. ## Exact output `swapWithExactOut` delivers a fixed raw amount on the destination chain. Omit `sources` to let the quote draw from any wallet balance, or pass candidate tokens to restrict it. The quote decides how much to take from each. ## Slippage `slippageBps` defaults to `50` (0.5%) and accepts 0 to 10000 or `'auto'`. The user receives at least `quote.output.minAmountRaw`. ## Swap and execute `swapAndExecute` swaps into the destination token, then calls a contract with it. See [Bridge, Transfer & Execute](/docs/nexus/concepts/nexus-ops). Read the [swap methods reference](/docs/nexus/nexus-sdk/reference/swap-methods/swap-exact-in) for parameters and examples. ## Migrate to Nexus SDK v2 `@avail-project/nexus-core` 2.0.0 is a full rewrite of the SDK surface. The package name is unchanged — upgrading the dependency is the whole install step — but most call sites need mechanical renames. This page walks you through the breaking changes and how to update for each. The [full migration reference](https://github.com/availproject/nexus-sdk/blob/main/MIGRATION.md) is the complete, version-pinned list — including simulation and event-level detail, every changed field, and a step-by-step checklist. ## Installation ```bash npm install @avail-project/nexus-core@^2 ``` ## Client lifecycle v1 used a class constructor with a single `initialize(provider)`. v2 uses a factory function with a two-step init, so you can fetch deployment data before a wallet connects: ```ts // v1 const sdk = new NexusSDK({ network: "mainnet" }); await sdk.initialize(provider); await sdk.deinit(); // v2 const client = createNexusClient({ network: "mainnet" }); await client.initialize(); // fetches deployment info await client.setEVMProvider(provider); // connects wallet (can be called later) client.destroy(); // synchronous ``` On account change, create a fresh client and re-run `initialize()` + `setEVMProvider()` — `triggerAccountChange()` is removed. ## Parameter renames The most common breaking change, applied consistently across `bridge`, `bridgeAndTransfer`, and `bridgeAndExecute`: | v1 field | v2 field | | -------------- | --------------- | | `token` | `toTokenSymbol` | | `amount` | `toAmountRaw` | | `sourceChains` | `sources` | ```ts // v1 await sdk.bridge({ token: "USDC", amount: 1000000n, toChainId: 8453, sourceChains: [1, 42161] }); // v2 await client.bridge({ toTokenSymbol: "USDC", toAmountRaw: 1000000n, toChainId: 8453, sources: [1, 42161] }); ``` `toAmountRaw` is in raw base units as a `bigint`, not a human amount — 1 USDC (6 decimals) is `1000000n`, not `1`. Convert with `client.convertTokenReadableAmountToBigInt(amount, tokenSymbol, chainId)`, whose signature is unchanged from v1. In `bridgeAndExecute`, the `execute` block no longer takes its own `toChainId` — it inherits the top-level one. Swap inputs were renamed too, and each variant has its own field changes: | v1 type | v2 type | field changes | | ------------------- | -------------------- | --------------------------------------------------------------------------------------------- | | `ExactInSwapInput` | `SwapExactInParams` | `from` → `sources`, `from[].amount` → `sources[].amountRaw` | | `ExactOutSwapInput` | `SwapExactOutParams` | `fromSources` → `sources`, `toAmount` → `toAmountRaw`, `toNativeAmount` → `toNativeAmountRaw` | | `MaxSwapInput` | `SwapMaxParams` | `fromSources` → `sources` | ## Hooks: global setters → per-operation options v1 registered hooks globally on the SDK instance; v2 passes them per operation. When no hooks are provided, v2 auto-approves (intents allowed, allowances set to `'min'`). ```ts // v1 sdk.setOnIntentHook((data) => data.allow()); sdk.setOnAllowanceHook((data) => data.allow(data.sources.map(() => "min"))); await sdk.bridge(params); // v2 await client.bridge(params, { hooks: { onIntent: (data) => data.allow(), onAllowance: (data) => data.allow(data.sources.map(() => "min")), }, }); ``` See [Hooks and Errors](/docs/nexus/nexus-sdk/reference/hooks-and-errors) for the v2 hook contract. ## Events: step callbacks → plan-based events v1's flat `{ name, args }` union (`STEPS_LIST`, `STEP_COMPLETE`, …) is replaced by a typed discriminated union: `status`, `plan_preview`, `plan_confirmed`, and `plan_progress`. Detect a skipped bridge via `result.bridgeSkipped` — there are no `*_SKIPPED` events. See [Bridge Events](/docs/nexus/nexus-sdk/v2/reference/bridge-methods/bridge-events) for the full v2 event reference. ## Results and errors * Operations **throw typed `NexusError` subclasses** on failure instead of returning `{ success: false }` — use try/catch and branch on `error.category` / `error.code`. * Operation results nest transactions: `executeResponse` → `execute`, `executeTransactionHash` → `execute.txHash`, `explorerUrl` → `intentExplorerUrl`. * Bridge `result.sourceTxs` is now an **array** (was a single object): `sourceTxs.hash` → `sourceTxs[].txHash`, `sourceTxs.explorerUrl` → `sourceTxs[].txExplorerUrl`. * User-denial error codes were renamed. Match with `error instanceof UserActionError`, or branch on the specific `error.code`: | v1 code | v2 code | | ------------------------------ | -------------------------------- | | `USER_DENIED_INTENT` | `USER_INTENT_HOOK_DENIED` | | `USER_DENIED_ALLOWANCE` | `USER_ALLOWANCE_APPROVAL_DENIED` | | `USER_DENIED_INTENT_SIGNATURE` | `USER_INTENT_SIGNATURE_DENIED` | | `USER_DENIED_SIWE_SIGNATURE` | `USER_SIWE_SIGNATURE_DENIED` | ## Balance APIs The balance methods keep their names — what changed is the return type and one dropped parameter. * `getBalancesForBridge()` — same call shape; elements are now `TokenBalance` instead of `UserAssetDatum`. * `getBalancesForSwap()` — the `onlyNativesAndStables` boolean was removed; call it with no arguments. See the `UserAsset` → `TokenBalance` row below for the field renames (`icon` → `logo`, `balanceInFiat` → `value`, `breakdown` → `chainBalances`). ## Type and API renames | v1 | v2 | | --------------------------------------------- | ------------------------------------------------------------------------------------------ | | `getMyIntents(page)` | `listIntents({ page, status })` → `{ intents, total }` (page size fixed at 20) | | `ReadableIntent` | `BridgeIntent` (`sources` → `selectedSources`, `allSources` → `availableSources`) | | `UserAsset` / `UserAssetDatum` | `TokenBalance` (`icon` → `logo`, `balanceInFiat` → `value`, `breakdown` → `chainBalances`) | | `RFF` / `RequestForFunds` | `IntentRecord` (`id` → `requestHash`, status booleans collapsed into `status`) | | `SuccessfulSwapResult` | `SwapResult` | | `getSwapSupportedChains()` | removed — `getSupportedChains()` includes per-chain token info | | `CHAIN_METADATA` / `TOKEN_METADATA` constants | removed — use `client.chainList` | | `sdk.utils.isSupportedChain(...)` | `client.isSupportedChain(chainId)` | ## Full reference The repository's [MIGRATION.md](https://github.com/availproject/nexus-sdk/blob/main/MIGRATION.md) covers every breaking change — simulation result shapes, swap option additions, event step-type mappings, and a complete migration checklist. The [SDK reference](/docs/nexus/nexus-sdk/reference) documents the v2 surface in full. ## Migrate to Nexus SDK v3 In v3, quotes and routing come from the Avail intent service. Bridging is a swap where the source and destination use the same asset. Tokens are identified by chain ID and contract address, not by symbol. To stay on v2, see the [v2 docs](/docs/nexus/nexus-sdk/v2/get-started/installation). ```bash npm install @avail-project/nexus-core@^3 ``` ## API mapping | v2 | v3 | | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `createNexusClient({ network })` | `createNexusClient({ clientId, network })`; `clientId` is required | | `channel`, `domain`, `forceMayan` config | Removed | | `getBalancesForBridge()`, `getBalancesForSwap()` | `getBalances()`; `getBalancesForSwap()` remains as a deprecated alias | | `TokenBalance` grouped by symbol, decimal strings | `IntentBalance`, one per chain and token, raw `bigint` (`actualBalanceRaw`, `usableBalanceRaw`); `balanceRaw` is a deprecated alias of `usableBalanceRaw` | | `bridge(params)` | `swapWithExactOut` with the same asset's address on each chain | | `toTokenSymbol`, `sources: number[]` | `toTokenAddress`, `sources: [{ chainId, tokenAddress }]` | | `bridgeAndTransfer`, `recipient` | No equivalent; output goes to the connected wallet | | `bridgeAndExecute` | `swapAndExecute` | | `simulateBridge`, `simulateBridgeAndTransfer`, `simulateBridgeAndExecute` | Review the quote in `hooks.onIntent` | | `calculateMaxForBridge`, `calculateMaxForSwap` | `usableBalanceRaw` from `getBalances()` | | `swapWithExactIn` optional `sources` and `amountRaw` | `sources` required, each with a positive `amountRaw` | | `hooks.onAllowance` | Removed; the SDK approves the minimum required amount | | `onIntent` receives `intent` (swap methods) | Receives `quote` and `execution` | | Top-level `options.onIntent` on composites | `options.hooks.onIntent` | | `slippageTolerance: 0.005` | `slippageBps: 50` (0–10000 or `'auto'`) | | `plan_preview`, `plan_confirmed`, `plan_progress`, `status` events | `quote`, `step`, `status` events (`IntentEvent`) | | `BridgeResult` / `SwapResult` with `sourceTxs` | `IntentResult`: `intentId`, `intentExplorerUrl`, `quote`, `status`, `approvals`, `nativeTransactions` | | `bridgeSkipped`, `bridgeResult` | `swapSkipped`, `swapResult` | | `result.execute` always present | Narrow on `result.executionMode === 'wallet'` first | | `execute.gas` optional on composites | Required on `swapAndExecute` | | `tokenApproval.toTokenSymbol` | `tokenApproval.toTokenAddress` | | `convertTokenReadableAmountToBigInt(amount, symbol, chainId)` | `parseUnits(amount, token.decimals)` from `@avail-project/nexus-core/utils` | | `getSupportedChains()` with tokens | `getSupportedChains()` returns chains only; use `getTokens` or `getTokensByChain` | | `IntentRecord` with `requestHash`, `sources`, `destinations` | `IntentRecord` with `id`, `provider`, `status`, `explorerUrl`, `createdAt`, `updatedAt` | ## Client creation ```typescript title="TypeScript" // v2 const client = createNexusClient({ network: 'mainnet' }); // v3 const client = createNexusClient({ clientId: 'your-app-name', network: 'mainnet' }); ``` `initialize()`, `setEVMProvider()`, and `destroy()` are unchanged. ## Balances ```typescript title="TypeScript" // v2 const assets = await client.getBalancesForBridge(); for (const asset of assets) console.log(asset.symbol, asset.usableBalance); // v3 import { formatUnits } from '@avail-project/nexus-core/utils'; const balances = await client.getBalances(); for (const b of balances) { console.log(b.chainId, b.symbol, formatUnits(b.usableBalanceRaw, b.decimals)); } ``` ## Bridge to swap ```typescript title="TypeScript" // v2 await client.bridge( { toTokenSymbol: 'USDC', toAmountRaw: 100_000_000n, toChainId: 8453, sources: [1] }, { hooks: { onIntent: ({ intent, allow }) => allow(), onAllowance: ({ allow }) => allow(['min']), }, }, ); // v3 import { parseUnits } from '@avail-project/nexus-core/utils'; const baseUsdc = { chainId: 8453, tokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' } as const; const token = await client.getToken(baseUsdc); await client.swapWithExactOut( { toChainId: baseUsdc.chainId, toTokenAddress: baseUsdc.tokenAddress, toAmountRaw: parseUnits('100', token.decimals), sources: [{ chainId: 1, tokenAddress: '0xA0b86991c6218b36c1d19d4a2e9eb0cE3606eB48' }], }, { hooks: { onIntent: ({ quote, allow }) => allow() }, slippageBps: 50, }, ); ``` ## bridgeAndExecute to swapAndExecute ```typescript title="TypeScript" // v2 const result = await client.bridgeAndExecute( { toTokenSymbol: 'USDC', toAmountRaw: amountRaw, toChainId: 8453, execute: { to: vault, data, tokenApproval: { toTokenSymbol: 'USDC', amount: amountRaw, spender: vault }, }, }, { onIntent: ({ allow }) => allow() }, ); if (!result.bridgeSkipped) console.log(result.bridgeResult.intentExplorerUrl); console.log(result.execute.txHash); // v3 const result = await client.swapAndExecute( { toChainId: baseUsdc.chainId, toTokenAddress: baseUsdc.tokenAddress, toAmountRaw: amountRaw, execute: { to: vault, data, gas: 350_000n, tokenApproval: { toTokenAddress: baseUsdc.tokenAddress, amount: amountRaw, spender: vault }, }, }, { hooks: { onIntent: ({ intent, allow }) => allow() } }, ); if (!result.swapSkipped) console.log(result.swapResult.intentExplorerUrl); if (result.executionMode === 'wallet') console.log(result.execute.txHash); ``` `swapAndExecute` defaults to `tradeType: 'exactOutput'`. With `useProviderDestinationExecution: true`, the result has `executionMode: 'provider'` and no `execute` or `approval`. See [Swap and execute](/docs/nexus/nexus-sdk/reference/swap-methods/swap-and-execute). ## Events ```typescript title="TypeScript" // v2 onEvent: (event) => { if (event.type === 'plan_preview') renderPlan(event.plan.steps); if (event.type === 'plan_progress') renderStep(event.step.type, event.state); if (event.type === 'status') renderStatus(event.status); }, // v3 onEvent: (event) => { if (event.type === 'quote') renderPlan(event.quote.plan.steps); if (event.type === 'step') renderStep(event.step.type, event.state); if (event.type === 'status') renderStatus(event.status, event.substatus); }, ``` `status` events carry the intent lifecycle (`created`, `deposited`, `fulfilled`, `expired`) and per-source `legs`. See [Swap events](/docs/nexus/nexus-sdk/reference/swap-methods/swap-events). ## Errors `NexusError`, its subclasses, and `ERROR_CODES` keep the same shape. Quote failures carry new `BACKEND_*` codes; read diagnostics with `getIntentQuoteFailure(error)`. See [Hooks and Errors](/docs/nexus/nexus-sdk/reference/hooks-and-errors). ## Migrate to Nexus Widget Avail Widgets consolidates all standalone cross-chain widgets into a single component: **Nexus Widget**. If your app uses `SwapWidget`, `FastBridge`, `FastTransfer`, `NexusDeposit`, `UnifiedBalance`, or `ViewHistory`, this guide maps each to its replacement. ## What changed | Legacy component | Status | Replacement | | -------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------ | | `SwapWidget` | Removed | `NexusWidget` with `config.mode = "swap"` | | `FastBridge` | Removed | `NexusWidget` with `config.mode = "swap"` | | `FastTransfer` | Removed | `NexusWidget` with `config.mode = "send"` | | `NexusDeposit` / `BridgeDeposit` | Removed | `NexusWidget` with `config.mode = "deposit"` | | `UnifiedBalance` | Removed | Inline balance views inside Nexus Widget | | `ViewHistory` | Removed | For programmatic intent history, use the SDK's [`listIntents`](/docs/nexus/nexus-sdk/reference/list-intents) | ## 1. Install Nexus Widget ```bash npx shadcn@latest add availproject/widgets/nexus ``` The install places the widget under `components/nexus/` and pins `@avail-project/nexus-core` v2. If you are also upgrading direct SDK usage, see the [SDK v1 → v2 guide](/docs/nexus/migration/sdk-v1-to-v2). ## 2. Replace components ### Swaps and bridges ```diff -import { SwapWidget } from "@/components/avail-widgets/swaps"; +import { NexusWidget } from "@/components/nexus/nexus"; - + ``` ### Sends and transfers ```diff -import { FastTransfer } from "@/components/avail-widgets/transfer"; +import { NexusWidget } from "@/components/nexus/nexus"; - + ``` ### Protocol deposits ```diff -import { NexusDeposit } from "@/components/avail-widgets/deposit"; +import { NexusWidget } from "@/components/nexus/nexus"; - + ({ + to: "0xContract...", + data: "0xCalldata...", + tokenApproval: { toTokenAddress: tokenAddress, amount, spender: "0xContract..." }, + }), + }} +/> ``` See the [Deposit Widget page](/docs/nexus/widgets/deposit) for the full `executeDeposit` contract. ## 3. Provider updates `NexusProvider` is still required — `NexusWidget` must be rendered inside it. Update its import to the new unified location and pass your `clientId` through `config.identity`: ```tsx import NexusProvider from "@/components/nexus/NexusProvider"; {children} ; ``` If you don't pass a `clientId`, the provider falls back to your site's hostname, which is not a stable app identity and doesn't enable widget telemetry. See [Client ID](/docs/nexus/widgets#client-id). SDK initialization on wallet connect is unchanged in shape — resolve the EIP-1193 provider from your wallet library and pass it to `handleInit` from `useNexus()`. See [Get Started](/docs/nexus/widgets). ## 4. Custom modifications > **Local modifications are not carried over** > > The install writes new files under `components/nexus/`. Customizations you made inside the legacy `components/avail-widgets/` folders (theme colors, analytics hooks, transaction overrides) are **not** transferred automatically. Back up the legacy folders before deleting them. Recommended workflow: 1. **Back up** your existing legacy component folders outside the git workspace. 2. **Install** Nexus Widget with the command above. 3. **Prefer callbacks over forks** — `onStart`, `onComplete(explorerUrl)`, `onError(message)`, and `onConnectClick` cover the common customization points without modifying widget source. 4. **Port UI styles** into `components/nexus/nexus.tsx` and its child files using your Tailwind theme. 5. **Delete** the legacy folders once the new widget is verified. ## Cookbook Recipes Each recipe assumes an initialized `client` with a connected wallet unless it says otherwise. See [SDK setup](/docs/nexus/nexus-sdk/get-started/nexus-sdk-setup). Names such as `showError`, `openReview`, `params`, and `userAddress` stand in for your app's code. The recipes share these constants: ```typescript title="TypeScript" import { parseUnits, formatUnits } from '@avail-project/nexus-core/utils'; const ETH_USDC = '0xA0b86991c6218b36c1d19d4a2e9eb0cE3606eB48'; const ARB_USDC = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; const BASE_USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; const BASE_WETH = '0x4200000000000000000000000000000000000006'; const AAVE_POOL = '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5'; // Aave v3 Pool on Base ``` ## Show balances in a token picker List spendable, verified balances, largest first. ```typescript title="TypeScript" const chainNames = new Map(client.getSupportedChains().map((c) => [c.id, c.name])); const balances = await client.getBalances(); const rows = balances .filter((b) => b.usable && b.verified && b.usableBalanceRaw > 0n) .sort((a, b) => (b.valueUsd ?? 0) - (a.valueUsd ?? 0)) .map((b) => ({ key: `${b.chainId}:${b.tokenAddress}`, label: `${b.symbol} on ${chainNames.get(b.chainId)}`, amount: formatUnits(b.usableBalanceRaw, b.decimals), valueUsd: b.valueUsd, // null when no price is available })); ``` `usableBalanceRaw` excludes gas reserves. Show `actualBalanceRaw` if you also display the full wallet balance. ## Show only Relay-supported balances ```typescript title="TypeScript" const relayBalances = (await client.getBalances()).filter( (b) => b.usable && b.providers.some((p) => p.id === 'relay'), ); ``` Provider support does not guarantee destination-call support. Each quote reports that in `quote.supportsDestinationCalls`. ## Preview a rate before wallet connection Call `swapWithExactIn` after `initialize()` and before `setEVMProvider()`. ```typescript title="TypeScript" import { UserActionError } from '@avail-project/nexus-core'; const from = { chainId: 1, tokenAddress: ETH_USDC } as const; const [fromToken, toToken] = await Promise.all([ client.getToken(from), client.getToken({ chainId: 8453, tokenAddress: BASE_WETH }), ]); await client .swapWithExactIn( { sources: [{ ...from, amountRaw: parseUnits('100', fromToken.decimals) }], toChainId: 8453, toTokenAddress: BASE_WETH, }, { hooks: { onIntent: (hook) => { showRate(formatUnits(hook.quote.output.amountRaw, toToken.decimals), hook.quote.fees); hook.deny(); }, }, }, ) .catch((error) => { if (!(error instanceof UserActionError)) showQuoteError(error); }); ``` A disconnected preview always rejects. After the wallet connects, start a new swap to get an executable quote. ## Swap with a review UI ```typescript title="TypeScript" const usdc = await client.getToken({ chainId: 8453, tokenAddress: BASE_USDC }); const result = await client.swapWithExactOut( { toChainId: 8453, toTokenAddress: BASE_USDC, toAmountRaw: parseUnits('25', usdc.decimals) }, { hooks: { onIntent: (hook) => openReview({ quote: hook.quote, canConfirm: hook.execution.possible, onConfirm: hook.allow, onCancel: hook.deny, onRefresh: () => hook.refresh(), // resolves with the replacement quote }), }, }, ); ``` The swap waits until you call `allow()` or `deny()`. Await `refresh()` before allowing, render the quote it returns, and reread `hook.execution`. ## Bridge USDC to Base ```typescript title="TypeScript" const usdc = await client.getToken({ chainId: 8453, tokenAddress: BASE_USDC }); const result = await client.swapWithExactOut({ toChainId: 8453, toTokenAddress: BASE_USDC, toAmountRaw: parseUnits('100', usdc.decimals), sources: [ { chainId: 1, tokenAddress: ETH_USDC }, { chainId: 42161, tokenAddress: ARB_USDC }, ], }); console.log(result.intentExplorerUrl); ``` A bridge is a swap with the same asset's address on each chain. Omit `sources` to let the quote draw from any wallet balance. ## Deposit into Aave with your wallet `swapAndExecute` funds the shortfall on Base, then your wallet calls the contract. ```typescript title="TypeScript" import { encodeFunctionData, parseAbi, type Hex } from 'viem'; const poolAbi = parseAbi([ 'function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)', ]); const supplyData = (amount: bigint, user: Hex) => encodeFunctionData({ abi: poolAbi, functionName: 'supply', args: [BASE_USDC, amount, user, 0] }); const usdc = await client.getToken({ chainId: 8453, tokenAddress: BASE_USDC }); const amount = parseUnits('50', usdc.decimals); const result = await client.swapAndExecute({ toChainId: 8453, toTokenAddress: BASE_USDC, toAmountRaw: amount, execute: { to: AAVE_POOL, data: supplyData(amount, userAddress), gas: 300_000n, tokenApproval: { toTokenAddress: BASE_USDC, amount, spender: AAVE_POOL }, }, }); if (result.executionMode === 'wallet') console.log(result.execute.txExplorerUrl); ``` `result.swapSkipped` is `true` when the wallet already holds enough on Base. Narrow on `executionMode` before reading `execute`. ## Deposit into Aave through Relay With `useProviderDestinationExecution: true`, Relay executes the call during fulfillment. This reuses `supplyData` from the previous recipe. ```typescript title="TypeScript" const arbUsdc = await client.getToken({ chainId: 42161, tokenAddress: ARB_USDC }); const result = await client.swapAndExecute( { tradeType: 'exactInput', sources: [{ chainId: 42161, tokenAddress: ARB_USDC, amountRaw: parseUnits('50', arbUsdc.decimals) }], toChainId: 8453, toTokenAddress: BASE_USDC, execute: { to: AAVE_POOL, gas: 300_000n, tokenApproval: { toTokenAddress: BASE_USDC, amount: 0n, spender: AAVE_POOL }, }, }, { useProviderDestinationExecution: true, beforeExecute: async ({ executeAmountRaw }) => ({ data: supplyData(executeAmountRaw, userAddress) }), }, ); console.log(result.executionMode, result.executeAmountRaw); // 'provider', fixed deposit amount ``` Relay's router is the caller, so the contract must credit the user (here `onBehalfOf`), not `msg.sender`. Unsupported routes reject before signing; see [provider destination execution](/docs/nexus/nexus-sdk/reference/swap-methods/swap-and-execute#provider-destination-execution). ## Drive a progress UI from events ```typescript title="TypeScript" import type { IntentEvent } from '@avail-project/nexus-core'; const onEvent = (event: IntentEvent) => { switch (event.type) { case 'quote': setSteps(event.quote.plan.steps.map((s) => ({ id: s.id, type: s.type, state: 'pending' }))); break; case 'step': updateStep(event.step.id, event.state, event.errorDetails?.message); break; case 'status': setStatus(event.status, event.legs.map((leg) => leg.txExplorerUrl)); break; } }; await client.swapWithExactOut(params, { onEvent }); ``` Errors thrown in `onEvent` do not interrupt the swap. A failed step with `committed: true` may still settle, so check its status before retrying. ## Handle quote failures ```typescript title="TypeScript" import { ERROR_CODES, NexusError, UserActionError, getIntentQuoteFailure } from '@avail-project/nexus-core'; try { await client.swapWithExactOut(params); } catch (error) { if (error instanceof UserActionError) return; // user cancelled if (!(error instanceof NexusError)) throw error; const failure = getIntentQuoteFailure(error); switch (error.code) { case ERROR_CODES.BACKEND_VALUE_ABOVE_CEILING: showError(`Maximum is $${String(failure?.details.maxValueUsd)}`); break; case ERROR_CODES.BACKEND_INPUT_BELOW_DEPOSIT_FEE: showError('Increase the amount to cover the deposit fee.'); break; case ERROR_CODES.BACKEND_NO_ROUTABLE_SOURCE: showSourceIssues(failure?.sourceVerdicts.filter((v) => v.state === 'unroutable')); break; default: showError(error.message); } } ``` `getIntentQuoteFailure` returns `null` for errors that are not quote failures. These failures have `retryable: false`; change the request before retrying. ## Paginate a token picker ```typescript title="TypeScript" import type { IntentToken } from '@avail-project/nexus-core'; let tokens: IntentToken[] = []; let offset = 0; let total = Infinity; async function loadMore(chainId: number, symbol?: string) { if (offset >= total) return; const page = await client.getTokensByChain(chainId, { symbol, offset, limit: 50 }); tokens = [...tokens, ...page.tokens]; offset = page.offset + page.limit; total = page.total; } ``` Reset `tokens`, `offset`, and `total` when the chain or search changes. Pages list verified tokens only; pass `includeUnverified: true` to include the rest. ## Show intent history ```typescript title="TypeScript" const { intents, total } = await client.listIntents({ page: 1, status: 'fulfilled' }); for (const intent of intents) { console.log(intent.id, intent.status, intent.provider, intent.explorerUrl); } ``` Pages hold 20 intents for the connected wallet. `provider` is present only when the middleware reports it. ## Widgets Reference Widgets are shadcn-based components built on top of the Nexus SDK that let you drop working cross-chain UI into your project. The standalone per-feature widgets (unified balance, fast bridge, fast transfer, deposit, swaps, view history) have been consolidated into a single **widget**. One component covers swap, send, and deposit modes. ## Install Install the widget with the shadcn CLI: ```bash title="npm" npx shadcn@latest add availproject/widgets/nexus ``` The single `NexusWidget` component brings in its own dependencies and source files, and exposes `swap`, `send`, and `deposit` modes. ## Read the full docs The Widgets documentation now lives on-site. Head there for installation, configuration, and per-mode usage. - [**Get Started**](/docs/nexus/widgets) ## SDK Reference Create a client with `createNexusClient` from `@avail-project/nexus-core`, then pick the method for your task. | Task | Method | | ----------------------------------------------------- | ----------------------------------------------------------------------------------------- | | Receive an exact amount on the destination | [`swapWithExactOut`](/docs/nexus/nexus-sdk/reference/swap-methods/swap-exact-out) | | Spend exact source amounts | [`swapWithExactIn`](/docs/nexus/nexus-sdk/reference/swap-methods/swap-exact-in) | | Move the same asset between chains | Either swap method — see [Bridge tokens](/docs/nexus/nexus-sdk/get-started/bridge-tokens) | | Fund and run a destination contract call | [`swapAndExecute`](/docs/nexus/nexus-sdk/reference/swap-methods/swap-and-execute) | | Call a contract with funds already on the destination | [`execute`](/docs/nexus/nexus-sdk/reference/execute-methods/execute) | | Estimate a contract call | [`simulateExecute`](/docs/nexus/nexus-sdk/reference/execute-methods/simulate-execute) | | Show wallet balances | [`getBalances`](/docs/nexus/nexus-sdk/reference/balances-and-tokens/get-balances) | | Find chains and tokens | [Token catalog](/docs/nexus/nexus-sdk/reference/balances-and-tokens/token-catalog) | | Show past intents | [`listIntents`](/docs/nexus/nexus-sdk/reference/list-intents) | - [**Balances & Tokens**](/docs/nexus/nexus-sdk/reference/balances-and-tokens/get-balances) — Wallet balances, the token catalog, and route compatibility. - [**Swap Methods**](/docs/nexus/nexus-sdk/reference/swap-methods/swap-exact-in) — Exact-in, exact-out, swap and execute, quote review, and events. - [**Execute Methods**](/docs/nexus/nexus-sdk/reference/execute-methods/execute) — Standalone contract calls and gas estimates. - [**List Intents**](/docs/nexus/nexus-sdk/reference/list-intents) — A wallet's intent history. - [**Hooks and Errors**](/docs/nexus/nexus-sdk/reference/hooks-and-errors) — Review hooks and the NexusError types. - [**Utility Reference**](/docs/nexus/nexus-sdk/reference/utility-reference) — Client config, lifecycle, and helper functions. ## Links - [**GitHub**](https://github.com/availproject/nexus-sdk/) — Source code for @avail-project/nexus-core. - [**npm**](https://www.npmjs.com/package/@avail-project/nexus-core) — @avail-project/nexus-core on npm. - [**Migrate from v2**](/docs/nexus/migration/sdk-v2-to-v3) — Map v2 calls to their v3 equivalents. ## MCP & Agent Skills This page covers two ways AI assistants can work with Avail Widgets: **Agent Skills** provide integration guidance for each workflow, while the **MCP server** lets assistants browse and install registry components with natural language. ## Agent Skills [skills.sh](https://skills.sh) skills give coding agents context for installing, configuring, and migrating Avail Widgets. ### Install Skills ```bash # Install all Avail Widgets skills npx skills add availproject/widgets # Install one skill npx skills add https://github.com/availproject/widgets --skill nexus-widget-swaps ``` ### Current Skills | Skill | Description | | ------------------------------ | ---------------------------------------------- | | `avail-widgets-overview` | End-to-end Avail Widgets integration guidance. | | `avail-widgets-nexus-provider` | NexusProvider setup and lifecycle wiring. | | `nexus-widget-swaps` | Swap and bridge mode integration. | | `nexus-widget-send` | Send mode integration. | | `nexus-widget-deposit` | Deposit mode integration. | | `nexus-migration-guide` | Migration from legacy standalone widgets. | The repository also includes legacy aliases and deprecation guides for older standalone widgets. Browse the current collection at [skills.sh/availproject/widgets](https://skills.sh/availproject/widgets). > **Looking for SDK skills?** > > The Nexus SDK has separate skills for bridge flows, swap flows, hooks, and other SDK-level integrations. See [SDK Skills Integration](/docs/nexus/nexus-sdk/reference/utility-reference#skills-integration). *** ## MCP Server The shadcn MCP Server lets Cursor and other MCP-compatible IDEs talk directly to the Avail Widgets registry. Detailed platform-agnostic docs live at [ui.shadcn.com/docs/mcp](https://ui.shadcn.com/docs/mcp); the sections below focus on Avail Widgets. ## Build with Nexus Widget **Nexus Widget** is the single unified component for all swap, send, and deposit flows. ### Option 1: NPM (Recommended) The fastest way to get started — no registry setup required: ```bash npm install @avail-project/widgets viem wagmi @tanstack/react-query ``` ```tsx import { NexusWidget, NexusProvider } from "@avail-project/widgets"; // NexusProvider is mandatory; set your app's clientId via config.identity. ; ``` ### Option 2: shadcn/ui (Power Users) Install from the public GitHub registry for full source access: ```bash npx shadcn@latest add availproject/widgets/nexus ``` All legacy standalone elements (FastBridge, FastTransfer, SwapWidget, Deposit, BridgeDeposit, UnifiedBalance, ViewHistory) have been deprecated and removed in favor of Nexus Widget. *** ## MCP Quick Start > **Note** > > **LLM/Agent default behavior:** When a user asks to integrate Avail Widgets, default to `npm install @avail-project/widgets`. Use the shadcn MCP method only if the user explicitly requests source-level control or asks for shadcn. Select your MCP client and run the matching init command. Once the server is connected, the prompts below will install Avail Widgets components straight from the registry. #### Cursor **Run the following command** in your project: ```bash npx shadcn@latest mcp init --client cursor ``` Open **Cursor Settings** and enable the shadcn MCP server. Then try prompts like: * Add Nexus Widget to my project using npm install * Install @avail-project/widgets and set up NexusProvider * Add deposit mode to my app using Avail Widgets #### Claude Code **Run the following command** in your project: ```bash npx shadcn@latest mcp init --client claude ``` Restart Claude Code and try the prompts above. Use `/mcp` to inspect the server state if needed. #### VS Code **Run the following command** in your project: ```bash npx shadcn@latest mcp init --client vscode ``` Open `.vscode/mcp.json`, click **Start** next to the shadcn server, then try the prompts with GitHub Copilot Chat. #### Codex > **Note** > > **Note:** The `shadcn` CLI cannot automatically update `~/.codex/config.toml`. Add the config manually. **Run the following command** in your project: ```bash npx shadcn@latest mcp init --client codex ``` **Then, append this snippet** to `~/.codex/config.toml`: ```toml [mcp_servers.shadcn] command = "npx" args = ["shadcn@latest", "mcp"] ``` Restart Codex and try the same prompts. *** ## What is MCP? [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open protocol that lets AI assistants securely connect to external tools. When paired with shadcn MCP: * **Browse components** across every configured registry (public or private) * **Search by intent** ("find a swaps widget") instead of memorizing package names * **Install with natural language** without leaving your IDE * **Work across registries** via `@namespace/component` syntax and per-registry auth *** ## How It Works 1. **Registry connection** – MCP reads your `components.json` and surfaces every registry entry. 2. **Natural language** – You describe what you want in Cursor/Claude/etc. 3. **AI processing** – The assistant maps that intent to registry items. 4. **Component delivery** – The shadcn CLI installs files directly into your repo. *** ## Supported Registries * **shadcn/ui registry** – ships with every MCP install for the canonical set of components. * **Avail Widgets registry** – `@avail-widgets` namespace for the Avail-specific UI kit. * **Third-party registries** – any endpoint that follows the shadcn spec. * **Private registries** – internal libraries with optional auth headers or tokens. *** ## Configuration Each client needs a small JSON/TOML stub pointing to the shadcn MCP binary. ### Cursor ```json title=".cursor/mcp.json" { "mcpServers": { "shadcn": { "command": "npx", "args": ["shadcn@latest", "mcp"] } } } ``` After saving, enable the server in Cursor Settings → MCP. ### Claude Code ```json title=".mcp.json" { "mcpServers": { "shadcn": { "command": "npx", "args": ["shadcn@latest", "mcp"] } } } ``` Restart Claude Code, run `/mcp`, and confirm the server shows as `Connected`. ### VS Code ```json title=".vscode/mcp.json" { "servers": { "shadcn": { "command": "npx", "args": ["shadcn@latest", "mcp"] } } } ``` Open the file in VS Code, click **Start** next to the server, and Copilot Chat will gain the new tools. ### Codex ```toml title="~/.codex/config.toml" [mcp_servers.shadcn] command = "npx" args = ["shadcn@latest", "mcp"] ``` Restart Codex so it reloads the config. *** ## Configuring Registries You can mix third-party and internal namespaced registries by extending `components.json`. This is only needed if you want the shadcn install path. ```json title="components.json" { "registries": { "@acme": "https://registry.acme.com/{name}.json", "@internal": { "url": "https://internal.company.com/{name}.json", "headers": { "Authorization": "Bearer ${REGISTRY_TOKEN}" } } } } ``` > **Note** > > **Tip:** No extra config is required for the default shadcn/ui registry—only > customize it when you need additional namespaces. *** ## Example Prompts ### NPM Install * Install @avail-project/widgets and set up NexusProvider in my Next.js app * Add Nexus Widget in deposit mode for my DeFi protocol using npm ### Browse & Search (shadcn registry) * Show me all available components in the shadcn registry * Find me a login form from the shadcn registry ### Install via shadcn MCP * Add the Nexus Widget from the Avail registry using shadcn * Install @avail-widgets/nexus *** ## Troubleshooting ### MCP Not Responding 1. Check your MCP config file (`.cursor/mcp.json`, `.mcp.json`, etc.). 2. Restart the IDE after any config edits. 3. Ensure `npx shadcn@latest mcp` runs without errors. 4. Confirm your network can reach the registry URLs. ### Registry Access Issues 1. Double-check registry URLs inside `components.json`. 2. Verify required env vars are loaded (restart `pnpm dev`/IDE if needed). 3. Confirm the registry endpoint is online. 4. Ensure namespace syntax matches (`@namespace/component`). ### Installation Failures 1. Validate that `components.json` exists at the repo root. 2. Check that CLI has write access to `components/` and `lib/` directories. 3. Make sure underlying dependencies (React, Tailwind, etc.) are installed. 4. Inspect CLI output for missing peer deps or TypeScript errors. ### No Tools or Prompts Listed 1. Run `npx clear-npx-cache`. 2. Disable/re-enable the MCP server in your client. 3. Check the MCP log output (`View → Output → MCP: project-*` in Cursor). *** ## Learn More * [Registry documentation](https://ui.shadcn.com/docs/registry) – overview of shadcn-compatible registries. * [Namespaces](https://ui.shadcn.com/docs/registry/namespace) – configuring multiple sources. * [Authentication](https://ui.shadcn.com/docs/registry/authentication) – securing private registries. * [MCP specification](https://modelcontextprotocol.io/) – full protocol details. ## Bridge Tokens A bridge is a swap whose source and destination are the same token, so you pass that token's address on each chain. This guide receives 10 USDC on Base, funded from USDC on Arbitrum and Optimism. It needs a client with a connected wallet; see [Nexus SDK Setup](/docs/nexus/nexus-sdk/get-started/nexus-sdk-setup). ## Example ```typescript title="TypeScript" import { UserActionError, type IntentEvent, type IntentHookData, type NexusClient, } from "@avail-project/nexus-core"; import { formatUnits, parseUnits } from "@avail-project/nexus-core/utils"; declare const client: NexusClient; declare function confirmWithUser(summary: string): Promise; const USDC = { arbitrum: { chainId: 42161, tokenAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831" }, optimism: { chainId: 10, tokenAddress: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85" }, base: { chainId: 8453, tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" }, } as const; const baseUsdc = await client.getToken(USDC.base); async function review({ quote, execution, allow, deny }: IntentHookData) { if (!execution.possible) { console.warn("Cannot execute:", execution.cause, quote.executionWarnings); deny(); return; } const spend = quote.input .map((input) => `$${input.totalRequiredUsd} ${input.tokenSymbol} on chain ${input.chainId}`) .join(", "); const minimum = formatUnits(quote.output.minAmountRaw, baseUsdc.decimals); const ok = await confirmWithUser(`Spend ${spend}. Receive at least ${minimum} USDC on Base.`); if (ok) allow(); else deny(); } function onEvent(event: IntentEvent) { if (event.type === "step") { console.log(event.step.type, event.state, event.errorDetails?.code); } else if (event.type === "status") { console.log(event.status, event.legs.map((leg) => leg.txExplorerUrl)); } } try { const result = await client.swapWithExactOut( { toChainId: USDC.base.chainId, toTokenAddress: USDC.base.tokenAddress, toAmountRaw: parseUnits("10", baseUsdc.decimals), sources: [USDC.arbitrum, USDC.optimism], }, { hooks: { onIntent: review }, onEvent }, ); console.log("Bridged", result.intentId, result.intentExplorerUrl); } catch (error) { if (error instanceof UserActionError) console.info("User declined"); else throw error; } ``` ## Choose sources `sources` restricts which chains and tokens can fund the transfer. List the same token on each source chain to keep it a pure bridge. If you omit `sources`, the SDK can spend any token in the wallet and swap it into USDC. Amounts are raw `bigint` values. Read `decimals` from `getToken()` on each chain instead of assuming a value. ## Review the quote `hooks.onIntent` receives `quote`, `execution`, `allow`, `deny`, and `refresh`. The operation waits until you call `allow()` or `deny()`; `deny()` rejects the call with a `UserActionError`. Without the hook, the quote is accepted automatically. | Field | Meaning | | --------------------------- | ----------------------------------------------------------------------------------- | | `execution.possible` | `false` with `cause: 'insufficient-balance'` when the wallet cannot cover the quote | | `quote.input[]` | Per-source `amountRaw`, `totalRequiredRaw` (including deposit fee), and USD values | | `quote.output.minAmountRaw` | Minimum received after slippage | | `quote.fees` | Fees in raw units and USD | | `quote.provider` | Route provider: `nexus-v2`, `mayan`, or `relay` | Call `await refresh()` for a new quote before allowing a stale one, and render its return value. See [Review quotes](/docs/nexus/nexus-sdk/reference/swap-methods/review-quotes) for details. ## Track progress `onEvent` receives `quote`, `step`, and `status` events. `step` events report each wallet prompt and submission as `started`, `completed`, or `failed`. `status` events report the intent's lifecycle (`created`, `deposited`, `fulfilled`, or `expired`) and per-source transaction links in `legs`. The promise resolves after fulfillment with `intentId`, `intentExplorerUrl`, and the transactions it sent. > **Note** > > A rejection after a `step` event with `committed: true` does not mean the transfer failed. Funds may already be deposited and still settle. Check the intent in [`listIntents`](/docs/nexus/nexus-sdk/reference/list-intents) or the explorer before retrying. ## Options Pass these alongside `hooks` and `onEvent`: | Option | Default | Description | | -------------------- | ------- | ------------------------------------ | | `slippageBps` | `50` | Integer from 0 to 10000, or `'auto'` | | `fillTimeoutMinutes` | `2` | How long to wait for fulfillment | | `pollingIntervalMs` | `2000` | Status polling interval | See [`swapWithExactOut`](/docs/nexus/nexus-sdk/reference/swap-methods/swap-exact-out) for all parameters and [Swap events](/docs/nexus/nexus-sdk/reference/swap-methods/swap-events) for every event field. ## Deposit into Aave `swapAndExecute` funds a token on the destination chain, then calls a contract with it. This guide spends 10 USDC on Arbitrum and supplies everything it produces to the Aave V3 Pool on Base. It needs a client with a connected wallet; see [Nexus SDK Setup](/docs/nexus/nexus-sdk/get-started/nexus-sdk-setup). Calldata is encoded with [viem](https://viem.sh). Confirm the Pool address in [Aave's address book](https://github.com/bgd-labs/aave-address-book) before use. ## Example ```typescript title="TypeScript" import { UserActionError, type NexusClient } from "@avail-project/nexus-core"; import { parseUnits } from "@avail-project/nexus-core/utils"; import { encodeFunctionData, type Hex } from "viem"; declare const client: NexusClient; declare const user: Hex; // Connected wallet address declare function confirmWithUser(summary: string): Promise; // Aave V3 Pool on Base. Confirm in Aave's address book before use. const AAVE_POOL = "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5"; const BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; const ARBITRUM_USDC = { chainId: 42161, tokenAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831" } as const; const poolAbi = [ { type: "function", name: "supply", stateMutability: "nonpayable", inputs: [ { name: "asset", type: "address" }, { name: "amount", type: "uint256" }, { name: "onBehalfOf", type: "address" }, { name: "referralCode", type: "uint16" }, ], outputs: [], }, ] as const; const supplyData = (amount: bigint) => encodeFunctionData({ abi: poolAbi, functionName: "supply", args: [BASE_USDC, amount, user, 0] }); const sourceToken = await client.getToken(ARBITRUM_USDC); try { const result = await client.swapAndExecute( { tradeType: "exactInput", sources: [{ ...ARBITRUM_USDC, amountRaw: parseUnits("10", sourceToken.decimals) }], toChainId: 8453, toTokenAddress: BASE_USDC, execute: { to: AAVE_POOL, gas: 350_000n, tokenApproval: { toTokenAddress: BASE_USDC, amount: 0n, spender: AAVE_POOL }, }, }, { hooks: { async onIntent({ intent, allow, deny }) { const amount = intent.executeRequirement.token.amount; if (await confirmWithUser(`Supply about ${amount} USDC to Aave on Base`)) allow(); else deny(); }, }, beforeExecute: async ({ executeAmountRaw }) => ({ data: supplyData(executeAmountRaw) }), }, ); console.log("Supplied", result.executeAmountRaw); if (result.executionMode === "wallet") console.log(result.execute.txExplorerUrl); if (!result.swapSkipped) console.log(result.swapResult.intentExplorerUrl); } catch (error) { if (error instanceof UserActionError) console.info("User declined"); else throw error; } ``` ## How it works 1. The review hook receives `intent`: `executeRequirement` holds the estimated deposit, gas, and approval, and `quote` holds the funding quote when `swapRequired` is `true`. 2. After you call `allow()`, the SDK swaps the sources into Base USDC and waits for fulfillment. 3. The SDK measures the USDC that arrived and calls `beforeExecute` with `executeAmountRaw`, the amount available for the call. 4. Your wallet approves the pool for `executeAmountRaw` if needed, then sends the `supply` transaction on Base. | Field | Description | | ----------------------- | --------------------------------------------------------------------------------------- | | `execute.gas` | Required. Raw gas estimate for the call; the SDK adds chain-specific buffers. | | `execute.tokenApproval` | Token and spender to approve. Pass `amount: 0n`; the SDK sets it to `executeAmountRaw`. | | `beforeExecute` | Returns `{ data?, value?, gas? }` to override the call after funding | > **Note** > > Exact-input proceeds are known only after fulfillment. Build amount-dependent calldata in `beforeExecute` from `executeAmountRaw`. Calldata encoded from the quote or the review estimate can supply the wrong amount and revert. `result.executeAmountRaw` is always present. Narrow on `executionMode === "wallet"` before reading `execute` or `approval`, and on `swapSkipped === false` before reading `swapResult`. ## Let Relay execute the deposit Set `useProviderDestinationExecution: true` to have Relay approve the pool and call `supply` as part of fulfillment. Your wallet sends no transaction on Base and needs no gas there. ```typescript title="TypeScript" const result = await client.swapAndExecute( { tradeType: "exactInput", sources: [{ ...ARBITRUM_USDC, amountRaw: parseUnits("10", sourceToken.decimals) }], toChainId: 8453, toTokenAddress: BASE_USDC, execute: { to: AAVE_POOL, gas: 350_000n, tokenApproval: { toTokenAddress: BASE_USDC, amount: 0n, spender: AAVE_POOL }, }, }, { useProviderDestinationExecution: true, beforeExecute: async ({ executeAmountRaw }) => ({ data: supplyData(executeAmountRaw) }), }, ); if (result.executionMode === "provider") { console.log("Supplied", result.executeAmountRaw, result.swapResult.intentExplorerUrl); } ``` * `onBehalfOf` must be the user's address. Relay's router is the caller, so the contract must support acting on behalf of the recipient. * `beforeExecute` runs before review and again on every refresh. `executeAmountRaw` is a fixed budget 1% below the quote's guaranteed minimum, with the reserve capped at $1. Keep the callback free of side effects. * Output above the budget goes to the user's wallet. * If the selected route does not support destination calls, the call rejects before signing. It never falls back to wallet execution. * The result has no `execute` or `approval`. See [Provider destination execution](/docs/nexus/nexus-sdk/reference/swap-methods/swap-and-execute#provider-destination-execution) for exact-output behavior and route requirements. ## Installation #### npm ```bash npm install @avail-project/nexus-core ``` #### pnpm ```bash pnpm add @avail-project/nexus-core ``` #### yarn ```bash yarn add @avail-project/nexus-core ``` #### bun ```bash bun add @avail-project/nexus-core ``` ## Requirements * Node.js 18 or later. * An [EIP-1193 provider](https://eips.ethereum.org/EIPS/eip-1193) from your wallet connector, such as wagmi, RainbowKit, or `window.ethereum`, for any operation that signs or reads the wallet. ## Entry points | Import | Contents | | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `@avail-project/nexus-core` | `createNexusClient`, errors, `ERROR_CODES`, and all types | | `@avail-project/nexus-core/utils` | Formatting and address helpers: `parseUnits`, `formatUnits`, `formatTokenBalance`, `isValidAddress`, `truncateAddress`, and more | Next, [set up the client](/docs/nexus/nexus-sdk/get-started/nexus-sdk-setup). ## Initializing Nexus with RainbowKit This guide passes the connected wagmi connector's EIP-1193 provider to the client and recreates the client whenever the account changes. It assumes you have read [Nexus SDK Setup](/docs/nexus/nexus-sdk/get-started/nexus-sdk-setup). ### Install dependencies ```bash npm install @avail-project/nexus-core @rainbow-me/rainbowkit wagmi viem @tanstack/react-query ``` ### Configure wagmi ```tsx title="src/lib/wagmi.ts" import { getDefaultConfig } from "@rainbow-me/rainbowkit"; import { arbitrum, base, mainnet, optimism, polygon } from "wagmi/chains"; export const wagmiConfig = getDefaultConfig({ appName: "My App", projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID!, chains: [mainnet, arbitrum, base, optimism, polygon], }); ``` Get a project ID from [WalletConnect Cloud](https://cloud.walletconnect.com). The SDK switches the wallet to each source chain as needed. ### Create the Nexus provider ```tsx title="src/components/nexus-provider.tsx" "use client"; import { createContext, useContext, useEffect, useState, type ReactNode } from "react"; import { useAccount } from "wagmi"; import { createNexusClient, type EthereumProvider, type NexusClient, } from "@avail-project/nexus-core"; const NexusContext = createContext(null); export function NexusProvider({ children }: { children: ReactNode }) { const { address, connector, status } = useAccount(); const [client, setClient] = useState(null); useEffect(() => { if (status === "connecting" || status === "reconnecting") return; let cancelled = false; const next = createNexusClient({ clientId: "my-app", network: "mainnet" }); (async () => { await next.initialize(); if (cancelled) return; if (status === "connected" && connector) { const provider = (await connector.getProvider()) as EthereumProvider; if (cancelled) return; await next.setEVMProvider(provider); } if (!cancelled) setClient(next); })().catch((error) => console.error("Nexus setup failed", error)); return () => { cancelled = true; setClient(null); next.destroy(); }; }, [address, connector, status]); return {children}; } export const useNexus = () => useContext(NexusContext); ``` The effect depends on `address` and `connector`, so switching accounts or wallets destroys the old client and creates a new one. While disconnected, the client still initializes, so catalog methods and `swapWithExactIn` quote previews work. `useNexus()` returns `null` until setup finishes; check `client.hasEvmProvider` before calling wallet methods. ### Wrap your app ```tsx title="src/components/providers.tsx" "use client"; import "@rainbow-me/rainbowkit/styles.css"; import { RainbowKitProvider } from "@rainbow-me/rainbowkit"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import type { ReactNode } from "react"; import { WagmiProvider } from "wagmi"; import { wagmiConfig } from "@/lib/wagmi"; import { NexusProvider } from "./nexus-provider"; const queryClient = new QueryClient(); export function Providers({ children }: { children: ReactNode }) { return ( {children} ); } ``` Render `Providers` in your root layout, and add RainbowKit's `ConnectButton` anywhere inside it. ### Use the client ```tsx title="src/components/balances.tsx" "use client"; import { formatUnits } from "@avail-project/nexus-core/utils"; import { useNexus } from "./nexus-provider"; export function Balances() { const client = useNexus(); async function load() { if (!client?.hasEvmProvider) return; const balances = await client.getBalances(); for (const b of balances) { console.log(b.symbol, b.chainId, formatUnits(b.usableBalanceRaw, b.decimals)); } } return ; } ``` ## Next steps * [Bridge tokens](/docs/nexus/nexus-sdk/get-started/bridge-tokens) between chains. * [Deposit into Aave](/docs/nexus/nexus-sdk/get-started/deposit-into-aave) from any chain. ## Nexus SDK Setup ## Create and connect a client ```typescript title="TypeScript" import { createNexusClient, type EthereumProvider } from "@avail-project/nexus-core"; const client = createNexusClient({ clientId: "my-app", network: "mainnet" }); await client.initialize(); const provider = (window as { ethereum?: EthereumProvider }).ethereum; if (provider) { await provider.request({ method: "eth_requestAccounts" }); await client.setEVMProvider(provider); } ``` `initialize()` loads chain metadata. Call it once, before any other method; otherwise calls fail with `SDK_NOT_INITIALIZED`. `setEVMProvider(provider)` reads the connected account from any EIP-1193 provider. Connect the wallet first, or the call rejects. `client.hasEvmProvider` reports whether a provider is set. ## Client options | Option | Type | Description | | ----------- | ------------------------------------ | --------------------------------------------------------------------------- | | `clientId` | `string` | Required. Stable, non-empty identifier for your app. An empty value throws. | | `network` | `'mainnet' \| 'canary' \| 'testnet'` | Default `'mainnet'`. | | `debug` | `boolean` | Enables verbose SDK logging. | | `analytics` | `{ enabled?: boolean }` | Set `enabled: false` to disable product analytics. | ## Networks | Network | Chains | Supports | | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- | | `mainnet` | Mainnet | Everything | | `canary` | Mainnet | Everything, against pre-release Nexus services | | `testnet` | Testnets | `execute` and `simulateExecute` only. Swaps, `swapAndExecute`, and async catalog methods reject with `ENVIRONMENT_NOT_SUPPORTED`. | ## What needs a wallet | Works after `initialize()` | Also needs `setEVMProvider()` | | ---------------------------------------------------------------------------- | ------------------------------------------------------------ | | `getSupportedChains`, `getTokens`, `getToken`, and the other catalog methods | `getBalances`, `listIntents` | | `swapWithExactIn` quote previews (review only, no execution) | `swapWithExactOut`, `swapAndExecute`, and executing any swap | | | `execute`, `simulateExecute` | Wallet methods called without a provider reject with `WALLET_NOT_CONNECTED`. ## Account changes and cleanup ```typescript title="TypeScript" import { createNexusClient, type EthereumProvider, type NexusClient } from "@avail-project/nexus-core"; async function connect(provider: EthereumProvider): Promise { const next = createNexusClient({ clientId: "my-app", network: "mainnet" }); await next.initialize(); await next.setEVMProvider(provider); return next; } declare const provider: EthereumProvider; let client = await connect(provider); provider.on("accountsChanged", async () => { client.destroy(); client = await connect(provider); }); ``` > **Note** > > The client keeps the account it read in `setEVMProvider`. Calling `setEVMProvider` again with the same provider object does nothing, so after an account or provider change, create a new client. Call `destroy()` on the old one and on unmount. ## Next steps * [Connect with RainbowKit](/docs/nexus/nexus-sdk/get-started/initializing-nexus-with-rainbowkit) in a React app. * [Bridge tokens](/docs/nexus/nexus-sdk/get-started/bridge-tokens) between chains. ## Hooks and Errors Pass hooks in the `options` argument of each swap method. `execute` and `simulateExecute` take no hooks. ## Hooks | Option | Methods | Receives | Details | | ---------------- | ------------------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------- | | `hooks.onIntent` | `swapWithExactIn`, `swapWithExactOut` | `IntentHookData`: `quote`, `execution`, `allow`, `deny`, `refresh` | [Review quotes](/docs/nexus/nexus-sdk/reference/swap-methods/review-quotes) | | `hooks.onIntent` | `swapAndExecute` | `SwapAndExecuteHookData`: `intent`, `allow`, `deny`, `refresh` | [Swap and execute](/docs/nexus/nexus-sdk/reference/swap-methods/swap-and-execute) | | `beforeExecute` | `swapAndExecute` | `BeforeExecuteContext`; returns `{ data?, value?, gas? }` | [Swap and execute](/docs/nexus/nexus-sdk/reference/swap-methods/swap-and-execute) | | `onEvent` | All three swap methods | `IntentEvent`: `quote`, `step`, or `status` | [Swap events](/docs/nexus/nexus-sdk/reference/swap-methods/swap-events) | * Without `hooks.onIntent`, the SDK accepts the quote automatically. * With it, the operation waits until you call `allow()` or `deny()`. Returning from the callback does not accept the quote. * `deny()` rejects the operation with `USER_INTENT_HOOK_DENIED`. A hook that throws rejects the operation with that error. * Errors thrown inside `onEvent` are caught and do not interrupt the operation. ## Errors Every SDK error extends `NexusError`. Branch on the subclass or `code`, not on `message`. | Field | Type | Description | | ---------- | -------------------------------------- | ---------------------------------------------------------------------------------- | | `category` | `ErrorCategory` | Matches the subclass, e.g. `'backend'` | | `code` | `ErrorCode` | Stable string such as `'backend/no_routable_source'`; compare with `ERROR_CODES.*` | | `message` | `string` | Displayable text, including the underlying wallet or RPC message | | `context` | `ErrorContext` | `operation`, `service`, `stepId`, `stepType`, and `chainId`, where known | | `details` | `Record \| undefined` | Failure data such as `txHash`, `requestHash`, `approvals`, or `httpStatus` | | Subclass | `context.service` | Thrown when | | ---------------------- | ----------------- | ----------------------------------------------------------------------------------- | | `ValidationError` | none | Input is invalid, the wallet is not connected, or balances cannot cover the request | | `UserActionError` | `wallet`, `hook` | The user rejects a wallet prompt or your hook calls `deny()` | | `ExecutionError` | `wallet`, `rpc` | A transaction fails to send, confirm, or execute | | `BackendError` | `middleware` | A quote, submission, status, balance, or catalog request fails | | `ExternalServiceError` | `coinbase` | `getCoinbaseRates` fails | | `InternalError` | none | An SDK invariant breaks; report it with `code` and `details` | ### Codes to handle Constants are on `ERROR_CODES`. Treat any `UserActionError` as a cancellation, not a failure. | Constant | Recovery | | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | `WALLET_NOT_CONNECTED` | Call `setEVMProvider`, then start a new operation | | `SDK_NOT_INITIALIZED` | Call `client.initialize()` first | | `ENVIRONMENT_NOT_SUPPORTED` | Use `mainnet` or `canary`; `testnet` supports standalone execution only | | `INVALID_INPUT` | Fix the parameter named in `message` | | `INSUFFICIENT_BALANCE` | Destination or native funds cannot cover execution; add funds or change sources | | `BACKEND_INSUFFICIENT_BALANCE` | Reduce the amount or choose another source | | `BACKEND_INSUFFICIENT_APPROVAL_GAS` | Add native gas on the source chain or choose another source | | `BACKEND_VALUE_ABOVE_CEILING` | Reduce the value; `getIntentQuoteFailure(error)?.details.maxValueUsd` is the limit in USD | | `BACKEND_INPUT_BELOW_DEPOSIT_FEE` | Increase the source amount | | `BACKEND_NO_ROUTE_TO_DESTINATION` | Choose a destination served by the allowed providers | | `BACKEND_NO_ROUTABLE_SOURCE` | Read `sourceVerdicts` and change sources or amounts | | `BACKEND_QUOTE_UNAVAILABLE`, `BACKEND_INTENT_REFUSED`, `BACKEND_QUOTE_PRICE_OUTLIER` | Change sources or the amount, then request a new quote | | `BACKEND_SAME_CHAIN_GAS_DROP_UNSUPPORTED`, `BACKEND_GAS_DROP_TOO_SMALL` | Remove or increase `toNativeAmountRaw` | | `BACKEND_REQUEST_EXPIRED`, `BACKEND_INVALID_INTENT_SIGNATURE`, `BACKEND_INVALID_PERMIT_SIGNATURE` | Start a new operation to get a fresh quote and signatures | | `BACKEND_ERROR` with `details.reasonBucket === 'expired'` | The quote or intent expired; start a new operation | | `BACKEND_PROVIDER_UNAVAILABLE`, `BACKEND_RATE_LIMITED`, `BACKEND_UPSTREAM_ERROR` | Retry later | | `BACKEND_UNAUTHORIZED` | Check the `clientId` passed to `createNexusClient` | | `BACKEND_FULFILMENT_WAIT_TIMEOUT` | Not filled within `fillTimeoutMinutes`; check status for `details.requestHash` | | `BACKEND_UPSTREAM_TIMEOUT`, `BACKEND_NETWORK_ERROR` | After commitment, check status before retrying | | `EXEC_TX_ONCHAIN_REVERTED` | Inspect `details.txHash`; fix calldata, value, or gas | | `EXEC_TX_CONFIRM_FAILED`, `EXEC_APPROVAL_TX_CONFIRM_FAILED` | The transaction in `details.txHash` may still confirm; check it before resending | ## getIntentQuoteFailure `getIntentQuoteFailure(error: unknown): IntentQuoteFailure | null` returns routing diagnostics when the quote service rejects a request. It returns `null` for other errors. | Field | Type | Description | | ----------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `subcode` | `IntentQuoteFailureSubcode` | Reason, e.g. `'NO_ROUTABLE_SOURCE'`, `'VALUE_ABOVE_CEILING'` | | `retryable` | `boolean` | `true` only for `PROVIDER_UNAVAILABLE`; otherwise change the request first | | `sourceVerdicts` | `IntentSourceVerdict[]` | Per source: `chainId`, `tokenAddress`, `tokenSymbol`, `state` (`selected`, `unused`, or `unroutable`), optional `reason` and `detail` | | `providerReasons` | `string[]` | Provider-specific reasons | | `details` | `Record` | Endpoint data, e.g. `maxValueUsd` | | `errorId`, `code` | `string \| undefined` | Service identifiers for support | ## Example ```typescript title="TypeScript" import { ERROR_CODES, NexusError, UserActionError, getIntentQuoteFailure, } from '@avail-project/nexus-core'; try { const result = await client.swapWithExactOut({ toChainId: 8453, toTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', toAmountRaw: amountRaw, }); showSuccess(result.intentExplorerUrl); } catch (error) { if (error instanceof UserActionError) return; // User cancelled. if (!(error instanceof NexusError)) throw error; switch (error.code) { case ERROR_CODES.BACKEND_VALUE_ABOVE_CEILING: { const maxUsd = getIntentQuoteFailure(error)?.details.maxValueUsd; showError(`Reduce the amount. Limit: $${String(maxUsd ?? 'unknown')}`); break; } case ERROR_CODES.BACKEND_INSUFFICIENT_BALANCE: showError('Reduce the amount or choose another source.'); break; case ERROR_CODES.BACKEND_FULFILMENT_WAIT_TIMEOUT: showPending(String(error.details?.requestHash)); // Do not retry yet. break; default: showError(error.message); } reportError({ code: error.code, context: error.context, details: error.details }); } ``` ## After a timeout A `step` event with `committed: true` means the user signed the intent or broadcast a native source deposit. After that point, a rejected promise does not mean the intent failed; it can still fulfill. > **Note** > > Do not start a new swap after a timeout or connection failure that follows commitment. Retrying can spend the funds twice. 1. Find the intent ID in `details.requestHash` or in the last `status` event's `intentId`. 2. Check its status with [`listIntents`](/docs/nexus/nexus-sdk/reference/list-intents) or the intent explorer. 3. Check `details.approvals`, if present. Each entry has `txHash` and a `state` of `confirmed`, `reverted`, or `unconfirmed`. An unconfirmed approval may still confirm. 4. Start a new operation with a fresh quote only after the intent shows `expired`. In `swapAndExecute` wallet mode, an error after fulfillment leaves the funds in the destination wallet. Retry the call with [`execute`](/docs/nexus/nexus-sdk/reference/execute-methods/execute) instead of swapping again. ## List Intents `listIntents()` returns the connected wallet's intents across all providers. It requires a [connected wallet](/docs/nexus/nexus-sdk/get-started/nexus-sdk-setup). ## Signature ```typescript title="TypeScript" listIntents(params?: ListIntentsParams): Promise ``` ## Parameters | Name | Type | Description | | -------- | -------------- | ------------------------------------------------------------------- | | `page` | `number` | Optional, default `1`. Integer of 1 or more | | `status` | `IntentStatus` | Optional. `'created'`, `'deposited'`, `'fulfilled'`, or `'expired'` | `IntentStatus` is also exported as a value map, e.g. `IntentStatus.Fulfilled`. ## Example ```typescript title="TypeScript" import { IntentStatus } from '@avail-project/nexus-core'; let page = 1; while (true) { const { intents } = await client.listIntents({ page, status: IntentStatus.Fulfilled }); if (intents.length === 0) break; for (const intent of intents) { console.log(intent.id, intent.provider, intent.status, intent.explorerUrl); } page += 1; } ``` ## Returns `ListIntentsResult`: | Field | Type | Description | | --------- | ---------------- | ------------------------------------------------- | | `intents` | `IntentRecord[]` | Records on this page, newest `createdAt` first | | `total` | `number` | Total matching records across all history sources | `IntentRecord`: | Field | Type | Description | | ------------------------ | ----------------------------------------------- | ----------------------------------------------------- | | `id` | `Hex` | Intent ID; matches `IntentResult.intentId` | | `provider` | `'nexus-v2' \| 'mayan' \| 'relay' \| undefined` | Present only when the service identifies the provider | | `status` | `IntentStatus` | Lifecycle status | | `explorerUrl` | `string` | Intent explorer link | | `createdAt`, `updatedAt` | `number \| undefined` | Timestamps from the history service | Each page merges up to 20 records from Nexus history and up to 20 from external-provider history, so a page can hold up to 40 records. Do not derive the page count from `total`; page until `intents` is empty. ## Errors * `WALLET_NOT_CONNECTED`: no provider is set. * `INVALID_INPUT`: `page` is not an integer of 1 or more. * `BACKEND_RFF_LIST_FAILED` or another `BackendError`: the history request failed. See [Hooks and Errors](/docs/nexus/nexus-sdk/reference/hooks-and-errors). ## Utility Reference This page covers `createNexusClient`, the client lifecycle, and the helpers exported from `@avail-project/nexus-core/utils`. ## createNexusClient ```typescript title="TypeScript" import { createNexusClient } from '@avail-project/nexus-core'; const client = createNexusClient({ clientId: 'my-app', network: 'mainnet' }); await client.initialize(); await client.setEVMProvider(provider); // EIP-1193 provider from your wallet connector ``` | Option | Type | Description | | ----------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `clientId` | `string` | Required. Stable, non-empty identifier for your app. An empty value throws `INVALID_INPUT`. | | `network` | `'mainnet' \| 'canary' \| 'testnet' \| NetworkConfig` | Default `'mainnet'`. `mainnet` and `canary` use mainnet chains. `testnet` supports standalone `execute` only; intent and async catalog methods reject with `ENVIRONMENT_NOT_SUPPORTED`. | | `debug` | `boolean` | Verbose SDK logging. Default `false`. | | `analytics` | `AnalyticsConfig` | Product analytics settings. See [Analytics](#analytics). | | `devTiming` | `DevTimingConfig` | Timing spans for development. Default disabled. | A custom `NetworkConfig` is `{ MIDDLEWARE_HTTP_URL, INTENT_EXPLORER_URL, NETWORK_HINT }`, where `NETWORK_HINT` is `'mainnet'`, `'canary'`, or `'testnet'`. ## Lifecycle | Member | Description | | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `initialize(): Promise` | Loads the chain and token catalog. Call once per client before any other method. Methods that need it throw `SDK_NOT_INITIALIZED`. | | `setEVMProvider(provider): Promise` | Connects an EIP-1193 provider and captures its first account. Throws `ExecutionError` if the wallet returns no account. | | `hasEvmProvider: boolean` | `true` after `setEVMProvider()` succeeds. | | `destroy(): void` | Ends the analytics session and closes middleware connections. Call on unmount. | | `isSupportedChain(chainId): boolean` | Whether `chainId` is in `getSupportedChains()`. | | `chainList: ChainListType` | Synchronous chain lookups for execution metadata. | `initialize()` does not need a wallet. Catalog methods and exact-input quote previews work before `setEVMProvider()`; balances, swaps, and execution need a wallet. > **Note** > > The client keeps the account it captured. When the user switches account or wallet, call `destroy()` and create a new client. Chain switches the SDK makes during execution do not require a new client. ### chainList `client.chainList` exposes `chains` and these lookups. Its token lists are not the full catalog; use the [token catalog](/docs/nexus/nexus-sdk/reference/balances-and-tokens/token-catalog) to find tokens. | Method | Returns | | --------------------------------------------- | --------------------------------- | | `getChainByID(id)` | `Chain` | | `getNativeToken(chainID)` | `TokenInfo` | | `getTokenByAddress(chainID, address)` | `TokenInfo` | | `getChainAndTokenByAddress(chainID, address)` | `{ chain, token, isNativeToken }` | | `getVaultContractAddress(chainID)` | `Hex` | ## Utilities Import stateless helpers from the `/utils` subpath. `client.utils` exposes the same functions. | Function | Description | | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `parseUnits(value, decimals)` | Decimal string to raw `bigint`. | | `formatUnits(value, decimals)` | Raw `bigint` to decimal string. | | `formatTokenBalance(value, options?)` | Display string, such as `"1.2346 USDC"` or `"~0.0₄8509"`. `options.decimals` is required for `bigint` input. | | `formatTokenBalanceParts(value, options?)` | Same, split into `text`, `integer`, `fraction`, `symbol`, and `approx`. | | `isValidAddress(address)` | Whether the string is a valid EVM address. | | `truncateAddress(address)` | Shortened address for display. | | `getCoinbaseRates()` | `Promise>` of Coinbase exchange rates. | | `getSupportedChains(network, { clientId })` | Fetches `{ id, name, logo, swapSupported, asSource, asDestination }[]` for a network without a client. `clientId` is required. | ```typescript title="TypeScript" import { formatTokenBalance, parseUnits } from '@avail-project/nexus-core/utils'; const token = await client.getToken({ chainId: 8453, tokenAddress: usdcOnBase }); const amountRaw = parseUnits('25.5', token.decimals); const label = formatTokenBalance(amountRaw, { decimals: token.decimals, symbol: token.symbol }); const chains = await client.utils.getSupportedChains('mainnet'); // uses the client's clientId ``` Use `token.decimals` from the catalog. The same symbol can have different decimals on different chains. `getFallbackTokenLogoDataUri(symbol, size = 128)` is exported from `@avail-project/nexus-core`, not `/utils`. It returns an SVG data URI to use when a token logo fails to load. ## Analytics The SDK sends product analytics to Avail's PostHog instance. In the default `mode: 'auto'`, it stays off outside browsers, on localhost, and when `NODE_ENV` is `test`, `ci`, or `development`. | Option | Description | | --------------------------------- | ---------------------------------------------------------------------------- | | `enabled` | `false` disables analytics. Overrides `mode`. | | `mode` | `'auto'` (default), `'on'` to always send, or `'off'`. | | `privacy` | `{ anonymizeWallets?, anonymizeAmounts? }`: hash addresses and drop amounts. | | `posthogApiKey`, `posthogApiHost` | Send events to your own PostHog project. | | `appMetadata` | `{ appName?, appVersion?, appUrl? }` attached to events. | ```typescript title="TypeScript" const client = createNexusClient({ clientId: 'my-app', analytics: { enabled: false } }); ``` `analytics: { enabled: false }` stops product analytics only. Diagnostic error logging is separate. ## Skills integration The `nexus-core` agent skill gives AI coding agents, such as Claude Code, Codex, and Cursor, the current SDK integration guide. ```bash npx skills add availproject/nexus-sdk ``` Browse it at [skills.sh/availproject/nexus-sdk](https://skills.sh/availproject/nexus-sdk). For Widgets skills, see [MCP and agent skills](/docs/nexus/widgets/mcp-and-agent-skills). ## Cookbook Recipes > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). 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. ```typescript import { createNexusClient } from '@avail-project/nexus-core'; const client = createNexusClient({ network: 'testnet' }); await client.initialize(); // load deployment data await 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. ```typescript // 1. Simulate a bridge const bridgeSimulation = await client.simulateBridge({ toTokenSymbol: 'USDC', toAmountRaw: 100_000_000n, // 100 USDC (6 decimals) toChainId: 421614, }); // 2. Simulate a bridgeAndExecute (bridge + contract call) const bridgeAndExecuteSimulation = await client.simulateBridgeAndExecute({ toTokenSymbol: 'USDC', toAmountRaw: 1_000_000n, toChainId: 421614, execute: { to: '0xContractAddress', data: encodedCalldata, tokenApproval: { toTokenSymbol: 'USDC', amount: 1_000_000n, spender: '0xContractAddress', }, }, }); // 3. Simulate a bridgeAndTransfer (cross-chain send) const bridgeAndTransferSimulation = await client.simulateBridgeAndTransfer({ toTokenSymbol: 'USDC', toAmountRaw: 1_000_000n, toChainId: 421614, recipient: '0xRecipientAddress', }); ``` `simulateBridge()` returns `{ intent, token }`. `simulateBridgeAndExecute()` and `simulateBridgeAndTransfer()` return `{ bridgeSimulation, executeSimulation }`, where `bridgeSimulation` is `null` when no bridge is needed. ```json { "intent": { "provider": "nexus", "availableSources": [ { "amount": "6245.786635", "amountRaw": "6245786635", "chain": { "id": 11155111, "name": "Ethereum Sepolia", "logo": "https://assets.coingecko.com/asset_platforms/images/279/large/ethereum.png" }, "token": { "decimals": 6, "symbol": "USDC", "logo": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png", "contractAddress": "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238" }, "value": "6245.79" }, { "amount": "754.958732", "amountRaw": "754958732", "chain": { "id": 84532, "name": "Base Sepolia", "logo": "https://assets.coingecko.com/asset_platforms/images/131/large/base-network.png" }, "token": { "decimals": 6, "symbol": "USDC", "logo": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png", "contractAddress": "0x036cbd53842c5426634e7929541ec2318f3dcf7e" }, "value": "754.96" } ], "selectedSources": [ { "amount": "100.000102", "amountRaw": "100000102", "chain": { "id": 11155111, "name": "Ethereum Sepolia", "logo": "https://assets.coingecko.com/asset_platforms/images/279/large/ethereum.png" }, "token": { "decimals": 6, "symbol": "USDC", "logo": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png", "contractAddress": "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238" }, "value": "100.00" } ], "destination": { "amount": "100", "amountRaw": "100000000", "chain": { "id": 421614, "name": "Arbitrum Sepolia", "logo": "https://assets.coingecko.com/coins/images/16547/large/arb.jpg" }, "token": { "decimals": 6, "symbol": "USDC", "logo": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png", "contractAddress": "0x75faf114eafb1bdbe2f0316df893fd58ce46aa4d" }, "value": "100.00", "nativeAmount": "0", "nativeAmountRaw": "0", "nativeAmountValue": "0", "nativeAmountInToken": "0", "nativeToken": { "decimals": 18, "symbol": "ETH", "logo": "https://assets.coingecko.com/coins/images/279/large/ethereum.png", "contractAddress": "0x0000000000000000000000000000000000000000" } }, "fees": { "caGas": "0.000000", "protocol": "0.0000005", "solver": "0.000001", "total": "0.000002", "totalValue": "0.000002" }, "sourcesTotal": "100.000102", "sourcesTotalValue": "100.00" }, "token": { "contractAddress": "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", "decimals": 6, "logo": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png", "name": "USD Coin", "symbol": "USDC" } } ``` ### 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. ```typescript const sim = await client.simulateBridge({ toTokenSymbol: 'USDC', toAmountRaw: 100_000_000n, toChainId: 421614, }); // Sources chosen by the solver sim.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 info console.log(sim.intent.destination.chain.name); console.log(sim.intent.destination.chain.id); // Total amount pulled across all sources console.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 }, }, } ); ``` ```json { "selectedSources": [ { "amount": "100.000102", "amountRaw": "100000102", "chain": { "id": 11155111, "name": "Ethereum Sepolia", "logo": "https://assets.coingecko.com/asset_platforms/images/279/large/ethereum.png" }, "token": { "decimals": 6, "symbol": "USDC", "logo": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png", "contractAddress": "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238" }, "value": "100.00" } ], "availableSources": [ { "amount": "6245.786635", "amountRaw": "6245786635", "chain": { "id": 11155111, "name": "Ethereum Sepolia", "logo": "https://assets.coingecko.com/asset_platforms/images/279/large/ethereum.png" }, "token": { "decimals": 6, "symbol": "USDC", "logo": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png", "contractAddress": "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238" }, "value": "6245.79" } ], "destination": { "amount": "100", "amountRaw": "100000000", "chain": { "id": 421614, "name": "Arbitrum Sepolia", "logo": "https://assets.coingecko.com/coins/images/16547/large/arb.jpg" }, "token": { "decimals": 6, "symbol": "USDC", "logo": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png", "contractAddress": "0x75faf114eafb1bdbe2f0316df893fd58ce46aa4d" }, "value": "100.00" }, "sourcesTotal": "100.000102" } ``` ### 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()`. ```typescript // ─── 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; // 6 sim.token.logo; // "https://..." // Source chain logos and token info sim.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. ```typescript 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 fee console.log(fees.solver); // Solver / fulfillment fee console.log(fees.caGas); // Chain-abstraction gas fee console.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. ```typescript import { IntentStatus } from '@avail-project/nexus-core'; // Fetch the first page of intents const { intents, total } = await client.listIntents(); // Paginate and/or filter by status const page2 = await client.listIntents({ page: 2 }); const fulfilled = await client.listIntents({ status: IntentStatus.Fulfilled }); console.log(`Total intents: ${total}`); // Check status ('created' | 'deposited' | 'fulfilled' | 'expired') intents.forEach((intent) => { if (intent.status === 'fulfilled') console.log(`${intent.requestHash}: fulfilled`); else if (intent.status === 'expired') console.log(`${intent.requestHash}: expired`); else console.log(`${intent.requestHash}: ${intent.status} (expires ${new Date(intent.expiry * 1000)})`); console.log(intent.explorerUrl); }); // Each IntentRecord has this shape: // { // requestHash: "0x...", // explorerUrl: "https://...", // status: "fulfilled", // solver: "0x..." | null, // expiry: 1706000000, // recipientAddress: "0x...", // destinationChain: { id: 421614, name: "Arbitrum Sepolia", logo: "...", universe: 0 }, // destinations: [{ // token: { contractAddress: "0x...", symbol: "USDC", name: "USD Coin", logo: "...", decimals: 6 }, // amount: "1.0", // amountRaw: 1000000n, // }], // sources: [{ // chain: { id: 84532, name: "Base Sepolia", logo: "...", universe: 0 }, // token: { contractAddress: "0x...", symbol: "USDC", name: "USD Coin", logo: "...", decimals: 6 }, // amount: "1.05", // amountRaw: 1050000n, // fee: "0.05", // feeRaw: 50000n, // }], // } ``` ## Chain & Token Utilities > **Note** > > 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. ```typescript // Full Chain object — name, native currency, block explorers const chain = client.chainList.getChainByID(421614); console.log(chain.name); // "Arbitrum Sepolia" console.log(chain.custom.icon); // chain logo URL console.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://..." ``` ```json { "id": 421614, "name": "Arbitrum Sepolia", "logo": "https://assets.coingecko.com/coins/images/16547/large/arb.jpg", "swapSupported": true, "tokens": [ { "symbol": "USDC", "name": "USD Coin", "decimals": 6, "logo": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png", "contractAddress": "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d" } ] } ``` ### 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. ```typescript // Get all supported chains with their tokens const 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 use const 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. ```json [ { "id": 421614, "name": "Arbitrum Sepolia", "logo": "https://assets.coingecko.com/coins/images/16547/large/arb.jpg", "swapSupported": true, "tokens": [ { "symbol": "USDC", "name": "USD Coin", "decimals": 6, "logo": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png", "contractAddress": "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d" } ] } ] ``` ### 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()`. ```typescript // Find chain ID by name const chains = client.getSupportedChains(); const baseSepolia = chains.find((c) => c.name === 'Base Sepolia'); console.log(baseSepolia?.id); // 84532 // Validate if a chain ID is supported const 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. ```typescript // Option A: Token info for a SPECIFIC chain const 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 address const byAddress = client.chainList.getTokenByAddress(421614, '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d'); console.log(byAddress.symbol); // "USDC" // Option C: Get all addresses for a token across chains const 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. ```typescript const vault = client.chainList.getVaultContractAddress(421614); console.log(vault); // "0x..." ``` ## Balances ### How to fetch a wallet's unified balances? 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`. ```typescript const assets = await client.getBalancesForBridge(); assets.forEach((asset) => { console.log(`${asset.symbol} (${asset.name})`); console.log(` Total: ${asset.balance} (~$${asset.value})`); console.log(` Decimals: ${asset.decimals}, Logo: ${asset.logo}`); asset.chainBalances.forEach((cb) => { console.log(` ${cb.chain.name}: ${cb.balance} @ ${cb.contractAddress}`); }); }); ``` ```json { "symbol": "USDC", "name": "USD Coin", "balance": "1250.50", "value": "1250.50", "decimals": 6, "logo": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png", "currencyId": 1, "chainBalances": [ { "balance": "500.00", "value": "500.00", "symbol": "USDC", "chain": { "id": 1, "name": "Ethereum", "logo": "https://..." }, "contractAddress": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "decimals": 6, "universe": 0 } ] } ``` ## Events ### How and when to listen to Nexus events? 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:** ```typescript 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. ``` allowance_approval Token allowance approval on a source chain request_signing User signs the intent request request_submission Intent submitted to the network vault_deposit Deposit into vault on a source chain bridge_fill Fill received on destination chain ``` **Swap Events:** Swaps emit the same typed event union. Swap plans carry `hasBridge` / `hasDestinationSwap` flags and swap-specific step types. ```typescript const result = await client.swapWithExactIn( { sources: [{ chainId: 10, amountRaw: 100_000_000n, tokenAddress: '0xUSDC...' }], toChainId: 8453, toTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base }, { onEvent: (event) => { if (event.type === 'plan_preview') { console.log('Has bridge:', event.plan.hasBridge); console.log('Has destination swap:', event.plan.hasDestinationSwap); } if (event.type === 'plan_progress') { console.log(`Swap step ${event.stepType}: ${event.state}`); // Grab explorer URLs from on-chain swap steps if ( (event.stepType === 'source_swap' || event.stepType === 'destination_swap') && 'txHash' in event ) { console.log(` Swap tx: ${event.explorerUrl}`); } } }, hooks: { onIntent: ({ allow }) => allow(), }, } ); ``` ``` source_swap Execute a swap on a source chain eoa_to_ephemeral_transfer Transfer funds from EOA to ephemeral wallet on a source chain bridge_deposit Deposit into vault for the cross-chain bridge bridge_intent_submission Submit the bridge intent to the network bridge_fill Wait for bridge fill on the destination chain destination_swap Execute a swap on the destination chain ``` ### How to review and approve intents and allowances? 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. ```typescript 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** ```bash filename="Terminal" npm install vite-plugin-node-polyfills ``` **Step 2: Update vite.config.ts** ```typescript filename="vite.config.ts" import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { nodePolyfills } from 'vite-plugin-node-polyfills'; export default defineConfig({ plugins: [ react(), nodePolyfills({ include: ['buffer', 'crypto', 'stream', 'util'], globals: { Buffer: true, global: true, process: true, }, }), ], define: { global: 'globalThis', }, }); ``` **Quick start alternative:** Clone the [Nexus Vite Template](https://github.com/availproject/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: ```json filename="package.json" { "scripts": { "dev": "next dev" } } ``` **Option B: Configure Webpack fallbacks** ```javascript filename="next.config.js" /** @type {import('next').NextConfig} */ const nextConfig = { webpack: (config, { isServer }) => { if (!isServer) { config.resolve.fallback = { ...config.resolve.fallback, crypto: require.resolve('crypto-browserify'), buffer: require.resolve('buffer/'), stream: require.resolve('stream-browserify'), }; } return config; }, }; module.exports = nextConfig; ``` Install required packages: ```bash filename="Terminal" npm install crypto-browserify buffer stream-browserify ``` **Quick start alternative:** Clone the [Nexus Next.js Template](https://github.com/availproject/nexus-nextjs-template) which has proper configuration. ### How to fix "require is not defined" in browser? This error occurs when code meant for Node.js runs in the browser. Solutions: **1. Use ES module imports instead of require():** ```typescript // Bad const { createNexusClient } = require('@avail-project/nexus-core'); // Good import { createNexusClient } from '@avail-project/nexus-core'; ``` **2. Move Node.js-specific code to API routes or Server Components:** ```typescript filename="app/api/data/route.ts" export async function GET() { // Node.js modules work here return Response.json({ data: 'ok' }); } ``` **3. Use dynamic imports for server-only code:** ```typescript if (typeof window === 'undefined') { const serverModule = await import('server-only-module'); } ``` ## Understanding Balance Types > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). Nexus allows you to query 2 types of balances for different use cases. These balances determine the sourcing of liquidity when doing certain operations. * Bridge Balance * Swap Balance Both queries return the same `TokenBalance[]` shape — a unified per-token total with a per-chain breakdown: ```typescript type TokenBalance = { balance: string; value: string; // USD value chainBalances: ChainBalance[]; // per-chain breakdown decimals: number; logo: string; name: string; symbol: string; }; ``` ## Bridge Balance Use [Fetch Bridge Balance](/docs/nexus/nexus-sdk/v2/reference/bridge-methods/fetch-bridge-balances) to get all token balances available for bridge operations across supported chains in a single call. Any Bridge Method you call will source liquidity from these balances. ```typescript client.getBalancesForBridge(): Promise ``` ## Swap Balance Use [Fetch Swap Balance](/docs/nexus/nexus-sdk/v2/reference/swap-methods/fetch-swap-balances) to get all token balances available for swap operations across supported chains in a single call. Any Swap Method you call will source liquidity from these balances. ```typescript client.getBalancesForSwap(): Promise ``` ## Why two different balances? Nexus solvers only manage liquidity for canonical bridge tokens — currently ETH, USDC, and USDT (see [Supported Chains & Tokens](/docs/nexus/supported-chains-and-tokens)). Bridge balances reflect what solvers can move across chains directly. A bridge route may be served natively by Nexus or via Mayan — the SDK selects the provider automatically per route. For crosschain swaps, Nexus orchestrates the intent but the actual swaps are executed by partner DEX aggregators (such as LiFi, Bebop, and 0x). This means swap operations can access a much wider range of tokens, since they aren't limited by solver liquidity. Because the available tokens differ between bridging and swapping, each operation needs its own balance query — your "swappable" balances are not the same as your "bridgeable" assets. > **Note** > > [Learn more about Bridge methods.](/docs/nexus/nexus-sdk/v2/reference/bridge-methods/fetch-bridge-balances) ## SDK Reference > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). This section contains dedicated pages for the main functions exposed by the Avail Nexus SDK (`@avail-project/nexus-core`). - [**Bridge Methods**](/docs/nexus/nexus-sdk/v2/reference/bridge-methods/fetch-bridge-balances) — bridge(), bridgeAndTransfer(), bridgeAndExecute(), calculateMaxForBridge(), and getBalancesForBridge(). - [**Swap Methods**](/docs/nexus/nexus-sdk/v2/reference/swap-methods/fetch-swap-balances) — swapWithExactIn(), swapWithExactOut(), swapAndExecute(), calculateMaxForSwap(), and getBalancesForSwap(). - [**List Intents**](/docs/nexus/nexus-sdk/v2/reference/list-intents) — listIntents() — fetch a user's historical intents with pagination and status filters. - [**Hooks and Errors**](/docs/nexus/nexus-sdk/v2/reference/hooks-and-errors) — Per-operation hooks and the typed NexusError hierarchy. - [**Utility Reference**](/docs/nexus/nexus-sdk/v2/reference/utility-reference) — Client creation, lifecycle, chain/token lookups, and stateless helpers. ## External links - [**GitHub Repository**](https://github.com/availproject/nexus-sdk/) — View the Nexus SDK source code and contribute. - [**npm Package**](https://www.npmjs.com/package/@avail-project/nexus-core) — @avail-project/nexus-core on npm. - [**v1 → v2 Migration Guide**](/docs/nexus/migration/sdk-v1-to-v2) — Every breaking change between nexus-core v1 and v2. ## Agent Skills Install [skills.sh](https://skills.sh) skills to give AI coding agents deep context about the Nexus SDK and Widgets. - [**Nexus SDK Skill**](https://skills.sh/availproject/nexus-sdk) — Client lifecycle, operations, hooks, events, errors, and v1 → v2 migration. - [**Widgets Skills**](/docs/nexus/widgets/mcp-and-agent-skills#agent-skills) — Provider setup plus swap, send, and deposit mode integration. See the [Skills Integration section](/docs/nexus/nexus-sdk/v2/reference/utility-reference#skills-integration) for install commands and the full list of available skills. ## SDK helper functions and constants The Nexus SDK exposes a suite of helper functions and constants used throughout the SDK. - [**Utils**](https://github.com/availproject/nexus-sdk/blob/main/src/core/utils.ts) — Formatting and utility functions on GitHub. - [**Types & Errors**](https://github.com/availproject/nexus-sdk/blob/main/src/domain/errors.ts) — Public types and the NexusError hierarchy on GitHub. ## Get Balances `getBalances()` returns the connected wallet's balances on every supported chain. Connect a wallet with `setEVMProvider()` first; see [SDK setup](/docs/nexus/nexus-sdk/get-started/nexus-sdk-setup). ## Signature ```typescript title="TypeScript" getBalances(): Promise ``` ## Parameters None. ## Example ```typescript title="TypeScript" import { formatUnits } from '@avail-project/nexus-core/utils'; const balances = await client.getBalances(); const spendable = balances.filter((b) => b.usable && b.verified && b.usableBalanceRaw > 0n); for (const b of spendable) { const amount = formatUnits(b.usableBalanceRaw, b.decimals); const usd = b.valueUsd === null ? 'no price' : `$${b.valueUsd.toFixed(2)}`; console.log(`${amount} ${b.symbol} on chain ${b.chainId} (${usd})`); } ``` ## Returns An array of `IntentBalance`. It includes verified and unverified tokens. | Field | Type | Description | | --------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | `chainId` | `number` | Chain the balance is on. | | `tokenAddress` | `Hex` | Token contract address. Use it with `chainId` as a `TokenRef`. | | `symbol`, `name` | `string` | Token metadata. Do not use `symbol` as an identifier. | | `decimals` | `number` | Token decimals on this chain. | | `isNative` | `boolean` | `true` for the chain's native gas token. | | `actualBalanceRaw` | `bigint` | Full wallet balance in base units. | | `usableBalanceRaw` | `bigint` | Balance available for routing after gas reserves, in base units. | | `valueUsd` | `number \| null` | USD value of `usableBalanceRaw`. `null` when no price is available. | | `priceSource` | `'oracle' \| 'indexer' \| 'coingecko' \| 'relay' \| null` | Where `valueUsd` came from. | | `verified` | `boolean` | `true` when at least one provider marks the token as verified. | | `usable` | `boolean` | Whether the balance can fund an intent. | | `providers` | `IntentProviderSupport[]` | Providers that support the token, each `{ id, currencyId? }`. `id` is `'nexus-v2'`, `'mayan'`, or `'relay'`. | | `logo`, `coingeckoId` | `string` (optional) | Display and pricing metadata. | Show `usableBalanceRaw` as the spendable amount and `actualBalanceRaw` as the wallet total. They differ when part of the balance is reserved for gas. ## Filter by provider `getBalances()` takes no filter. Filter the array on `providers[].id`: ```typescript title="TypeScript" const relayBalances = balances.filter((b) => b.providers.some((p) => p.id === 'relay')); ``` Provider support for a token does not guarantee that a route can execute a destination call through that provider. Each quote's `supportsDestinationCalls` flag decides that; see [provider destination execution](/docs/nexus/nexus-sdk/reference/swap-methods/swap-and-execute#provider-destination-execution). ## Errors | Code | Cause | | ---------------------------------- | ------------------------------------------------------ | | `ERROR_CODES.WALLET_NOT_CONNECTED` | No wallet is connected. Call `setEVMProvider()` first. | ## Token Catalog The catalog methods list supported chains and tokens and check which tokens can route together. They need `initialize()` but no wallet; see [SDK setup](/docs/nexus/nexus-sdk/get-started/nexus-sdk-setup). ## Methods | Method | Returns | | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | `getSupportedChains()` | `IntentChainMetadata[]`, synchronously. No token lists. Filter by `capabilities.intent` or `capabilities.execute`. | | `getTokens(query?)` | `IntentTokenPage`: `{ tokens, offset, limit, total }` across chains. | | `getTokensByChain(chainId, query?)` | `IntentTokenPage` for one chain. `query` omits `chainId`. | | `getToken({ chainId, tokenAddress })` | One `IntentToken`, verified or not. Rejects with `TOKEN_NOT_SUPPORTED` or `CHAIN_NOT_FOUND` if unknown. | | `getAvailableSourceTokens(destination, selectedSources?, query?)` | `{ groups, offset, limit, total }`. Each group has `provider` and `chains[].tokens`. | | `getAvailableDestinationTokens(sources, query?)` | `{ chains, offset, limit, total }`. Each chain has `tokens`. | | `confirmRouteExists(sources, destination)` | `boolean`. `true` when one provider supports every source and the destination. | | `getSupportedChainsForRoute(constraints)` | `IntentChainMetadata[]` matching `IntentRouteConstraints`. | `IntentToken` exposes `chainId`, `address`, `symbol`, `name`, `decimals`, `isNative`, `verified`, `providers`, and optional `logo`. Selections use `TokenRef` (`{ chainId, tokenAddress }`), so map `address` to `tokenAddress`. ## IntentTokenQuery | Field | Type | Description | | ---------------------------- | ------------------ | ------------------------------------------------ | | `chainId` | `number` | Restrict to one chain. | | `providers` | `IntentProvider[]` | Restrict to tokens supported by these providers. | | `name`, `symbol`, `contract` | `string` | Case-insensitive substring match. | | `includeUnverified` | `boolean` | Include unverified tokens. Default `false`. | | `offset` | `number` | Default `0`. | | `limit` | `number` | 1–1000. Default `50`. | Filters combine with AND. ## Pagination * **Next page:** request `offset + limit` while it is less than `total`. * **Empty pages:** source and destination pages count candidates before filtering, so an empty page can precede more matches. Keep paging. * **Reset:** set `offset` to `0` when filters or selections change. * **Duplicates:** deduplicate tokens by `chainId` plus `address` across provider groups. ## Example A source picker for an exact-input swap into USDC on Base: ```typescript title="TypeScript" import type { IntentToken, TokenRef } from '@avail-project/nexus-core'; const destination: TokenRef = { chainId: 8453, tokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', }; const selected: TokenRef[] = []; // sources the user already picked async function loadSources(symbol: string): Promise { const seen = new Map(); let offset = 0; let total = Infinity; while (offset < total) { const page = await client.getAvailableSourceTokens(destination, selected, { symbol, offset, limit: 100, }); for (const group of page.groups) for (const chain of group.chains) for (const t of chain.tokens) seen.set(`${t.chainId}:${t.address.toLowerCase()}`, t); total = page.total; offset = page.offset + page.limit; } return [...seen.values()]; } const options = await loadSources('USDC'); const pick = options[0]; if (pick) selected.push({ chainId: pick.chainId, tokenAddress: pick.address }); ``` ## Notes * **Exact input:** pass the selected sources so every source shares one provider with the destination. * **Exact output:** sources are alternatives. Call `getAvailableSourceTokens(destination, [], query)`, and do not reject a set because `confirmRouteExists` returns `false`. * **Route checks:** `confirmRouteExists` checks catalog compatibility, not quote availability. It returns `false` for empty or unknown sources and rejects on network failure. * **Route constraints:** `sources` and `destinations` legs take `chainId`, `tokenAddress`, and `amountRaw`. `amountRaw` needs both identity fields. Size by source amounts, destination amounts, or `valueUsd`, not more than one. Token queries do not inherit constraints. * **Testnet:** the async catalog methods reject with `ENVIRONMENT_NOT_SUPPORTED`. ## Execute `execute()` sends a contract call from the connected wallet on the chain you choose. It does not move funds across chains; use [`swapAndExecute()`](/docs/nexus/nexus-sdk/reference/swap-methods/swap-and-execute) for that. ## Signature ```typescript title="TypeScript" execute(params: ExecuteParams): Promise ``` ## Parameters | Name | Type | Description | | ----------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `toChainId` | `number` | Required. Chain to send the call on. The SDK switches the wallet to it. | | `to` | `Hex` | Required. Contract to call. | | `data` | `Hex` | Calldata. Default `'0x'`. | | `value` | `bigint` | Native value in raw units. Default `0n`. | | `gas` | `bigint` | Gas limit. The wallet estimates it when omitted. | | `tokenApproval` | `{ toTokenAddress, amount, spender }` | ERC-20 approval. The SDK reads the allowance and sends `approve(spender, amount)` first only when it is below `amount`. | | `waitForReceipt` | `boolean` | Default `true`. `false` returns after broadcast. | | `receiptTimeout` | `number` | Milliseconds to wait for the receipt. Default `300000`. | | `requiredConfirmations` | `number` | Confirmations to wait for. Default `1`. | Omit `tokenApproval` for native-token calls. When the wallet supports atomic batches (EIP-5792), the approval and call are sent as one batch. ## Example Deposit 100 USDC into an ERC-4626 vault on Base. ```typescript title="TypeScript" import { encodeFunctionData, parseAbi, parseUnits, type Hex } from 'viem'; const USDC_BASE: Hex = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; const VAULT: Hex = '0xYourVault'; const user: Hex = '0xUserAddress'; const vaultAbi = parseAbi([ 'function deposit(uint256 assets, address receiver) returns (uint256)', ]); const usdc = await client.getToken({ chainId: 8453, tokenAddress: USDC_BASE }); const amountRaw = parseUnits('100', usdc.decimals); const result = await client.execute({ toChainId: 8453, to: VAULT, data: encodeFunctionData({ abi: vaultAbi, functionName: 'deposit', args: [amountRaw, user] }), tokenApproval: { toTokenAddress: USDC_BASE, amount: amountRaw, spender: VAULT }, }); console.log(result.approval?.txExplorerUrl, result.execute.txExplorerUrl); ``` ## Returns | Field | Type | Description | | ------------------- | ----------------------- | ----------------------------------------------------------------------------------- | | `execute` | `TxResult` | `txHash`, `txExplorerUrl`, and `receipt` (absent when `waitForReceipt` is `false`). | | `approval` | `TxResult \| undefined` | Present only when an approval was sent. | | `chainId` | `number` | Chain the call ran on. | | `gasUsed` | `string` | From the receipt; `'0'` without one. | | `effectiveGasPrice` | `string` | From the receipt; `'0'` without one. | > **Note** > > With `waitForReceipt: false`, a returned result means only that the transaction was broadcast. It can still revert. ## Errors | Code (`ERROR_CODES.*`) | Cause | | -------------------------------- | ------------------------------------------------------ | | `WALLET_NOT_CONNECTED` | No wallet is connected. | | `USER_TX_SEND_DENIED` | The user rejected the call. | | `USER_ALLOWANCE_APPROVAL_DENIED` | The user rejected the approval. | | `EXEC_TX_SEND_FAILED` | The wallet failed to send the call. | | `EXEC_TX_CONFIRM_FAILED` | The receipt wait failed, timed out, or found a revert. | | `EXEC_TX_ONCHAIN_REVERTED` | The atomic approval-and-call batch failed. | See [hooks and errors](/docs/nexus/nexus-sdk/reference/hooks-and-errors) for the full list. ## Simulate Execute `simulateExecute()` estimates the gas and maximum native cost of an [`execute()`](/docs/nexus/nexus-sdk/reference/execute-methods/execute) call without sending anything. It requires a connected wallet, because the estimate runs from the user's address. ## Signature ```typescript title="TypeScript" simulateExecute(params: ExecuteParams): Promise ``` ## Parameters Takes the same `ExecuteParams` as `execute()`. These fields affect the estimate: | Name | Type | Description | | --------------- | ------------------------------------- | ----------------------------------------------------------------------------- | | `toChainId` | `number` | Required. Chain to estimate on. | | `to` | `Hex` | Required. Contract to call. | | `data` | `Hex` | Calldata. | | `value` | `bigint` | Native value in raw units. | | `tokenApproval` | `{ toTokenAddress, amount, spender }` | When the current allowance is below `amount`, the approval's gas is included. | ## Example ```typescript title="TypeScript" import { formatUnits, type Hex } from 'viem'; import type { ExecuteParams } from '@avail-project/nexus-core'; declare const VAULT: Hex; declare const USDC_BASE: Hex; declare const depositData: Hex; declare const amountRaw: bigint; const params: ExecuteParams = { toChainId: 8453, to: VAULT, data: depositData, tokenApproval: { toTokenAddress: USDC_BASE, amount: amountRaw, spender: VAULT }, }; const sim = await client.simulateExecute(params); const native = client.chainList.getChainByID(8453).nativeCurrency; console.log(`Up to ${formatUnits(sim.estimatedTotalCost, native.decimals)} ${native.symbol}`); await client.execute(params); ``` ## Returns | Field | Type | Description | | -------------------- | ------------------ | -------------------------------------------------------------------------------------------- | | `estimatedGasUnits` | `bigint` | Gas for the call plus the approval, when one is needed. | | `estimatedTotalCost` | `bigint` | `estimatedGasUnits` times the max fee per gas, in raw native units. An upper bound. | | `feeParams` | `ExecuteFeeParams` | `{ type: 'eip1559', maxFeePerGas, maxPriorityFeePerGas }` or `{ type: 'legacy', gasPrice }`. | The estimate excludes `value` and any L1 data fee on rollups. Each transaction is estimated against current chain state, so a call that only succeeds after its approval can fail to estimate. ## Errors | Code (`ERROR_CODES.*`) | Cause | | ----------------------------- | ----------------------------------------------------------------------------------------------- | | `WALLET_NOT_CONNECTED` | No wallet is connected. | | `EXECUTION_ERROR` | Gas or fee estimation failed. The call would likely revert, for example from a missing balance. | | `EXEC_GAS_PRICE_FETCH_FAILED` | The chain returned a zero gas price. | ## Swap and Execute `swapAndExecute()` funds a contract call on the destination chain, swapping only what the call still needs, then runs the call from the wallet or through Relay. ## Signature ```typescript title="TypeScript" swapAndExecute( input: SwapAndExecuteParams, options?: SwapAndExecuteOptions, ): Promise ``` ## Parameters ### `input` | Name | Type | Description | | ---------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `tradeType` | `'exactOutput' \| 'exactInput'` | Default `'exactOutput'`. | | `toChainId` | `number` | Required. Destination chain. | | `toTokenAddress` | `Hex` | Required. Token the call spends. For native, use the native token address from the [token catalog](/docs/nexus/nexus-sdk/reference/balances-and-tokens/token-catalog) (`isNative: true`). | | `toAmountRaw` | `bigint` | Exact output: required, the amount the call needs. Exact input: omit. | | `sources` | `Array<{ chainId, tokenAddress?, amountRaw? }>` | Exact output: optional, limits which balances fund the call. Exact input: required, each with `tokenAddress` and a positive `amountRaw`. | | `execute` | `SwapExecuteParams` | Required. The destination call. | `execute` fields: | Name | Type | Description | | --------------- | ------------------------------------- | ----------------------------------------------------------------------------------- | | `to` | `Hex` | Required. Contract to call. | | `data` | `Hex` | Calldata. Return it from `beforeExecute` when it depends on the final amount. | | `value` | `bigint` | Native value sent with the call. | | `gas` | `bigint` | Required, positive. Gas estimate for the call. The SDK adds chain-specific buffers. | | `gasPrice` | `'low' \| 'medium' \| 'high'` | Fee tier. Default `'medium'`. | | `tokenApproval` | `{ toTokenAddress, amount, spender }` | ERC-20 approval sent before the call when allowance is short. | Wallet-mode calls also accept the receipt controls from [`execute()`](/docs/nexus/nexus-sdk/reference/execute-methods/execute). Exact input swaps fixed source amounts and executes whatever arrives. Sources that already are the destination token on the destination chain stay in the wallet and are added to the execution amount. The remaining sources must share one provider with the destination. If every source is local, the swap is skipped and the wallet must already hold native gas for the call. When `tokenApproval.toTokenAddress` is the destination token, pass `amount: 0n`; the SDK sets it to the final execution amount. ### `options` | Name | Type | Description | | --------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `hooks.onIntent` | `(data: SwapAndExecuteHookData) => void` | Review gate. Receives `intent`, `allow`, `deny`, `refresh(sources?)`, and `attemptId`. Without it, the operation proceeds. | | `beforeExecute` | `(ctx: BeforeExecuteContext) => Promise<{ data?, value?, gas? }>` | Returns overrides for the call. | | `useProviderDestinationExecution` | `boolean` | Default `false`. `true` requires Relay to run the call. See [provider destination execution](#provider-destination-execution). | | `onEvent` | `(event: IntentEvent) => void` | Funding progress. See [swap events](/docs/nexus/nexus-sdk/reference/swap-methods/swap-events). | | `slippageBps` | `number \| 'auto'` | 0 to 10000. Default `50`. | | `fillTimeoutMinutes` | `number` | Default `2`. | | `pollingIntervalMs` | `number` | Default `2000`. | `onIntent` runs even when no swap is needed. `intent` (`SwapAndExecuteIntent`) contains: * **`executionMode`**: `'wallet'` or `'provider'`. * **`executeRequirement`**: the call's chain, `to`, token amount, gas cost, `nativeValue`, and `tokenApproval`. * **`available`** and **`shortfall`**: destination token and gas, as `FundingAmount` values. * **`swapRequired`**: when `true`, `intent.quote` holds the funding quote. `refresh(sources?)` returns a new intent with current fees and a new quote. It reuses the balances read at the start; start a new call to reread them. Omitted `sources` keep the current selection. For exact output, `[]` clears it. `BeforeExecuteContext`: | Field | Wallet mode | Provider mode | | ------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | `receivedAmountRaw` | Destination balance increase across funding; `0n` when the swap is skipped. | The quote's guaranteed minimum output (`toAmountRaw` for exact output). | | `executeAmountRaw` | Exact input: proceeds plus selected local tokens, less native reserves. Exact output: `toAmountRaw`. | Fixed execution budget. | Build amount-sensitive calldata from `executeAmountRaw`. ## Wallet mode * Reads balances, prices the call's gas and value, and quotes only the shortfall. When balances already cover the call, it skips the swap. * For non-native output, adds a native gas drop when the wallet lacks gas on the destination. * After `allow()`, reads the destination balance, runs the intent, and reads it again. * Calls `beforeExecute`, then sends the approval (if needed) and the call from the connected wallet. * Overrides from `beforeExecute` do not change funding. The balance increase includes any unrelated transfer that lands during funding. ## Provider destination execution With `useProviderDestinationExecution: true`, Relay runs the approval and call on the destination chain as part of the fill. The wallet sends no destination transaction. > **Note** > > The call's `msg.sender` is Relay's router, not the user. Use a contract function that credits an explicit recipient, such as ERC-4626 `deposit(assets, receiver)`. A function that credits `msg.sender` sends the position to the router. * **Route support**: the quote must have `quote.supportsDestinationCalls: true`, and exactly one chain/token leg may fund it. Otherwise the call rejects with `INVALID_INPUT` before signing. It never falls back to wallet mode. * **Funding**: the quote funds the whole call. Destination wallet balances are not used, and exact-input sources that match the destination enter the quote. * **Exact-input budget**: the SDK previews a Relay quote and sets `executeAmountRaw` to its minimum output less 1%, with the reserve capped at $1. The rest goes to the recipient. Unpriced or too-small output rejects with `BACKEND_ERROR`. * **Exact-output budget**: `executeAmountRaw` is `toAmountRaw`. * **Approvals**: `tokenApproval` may only approve the output token. * **Native value**: allowed only for native output, and at most `executeAmountRaw`. ERC-20 output with a nonzero `value` rejects. * **Unused fields**: `execute.gas` is still required but unused; the quote includes execution costs. > **Note** > > In provider mode, `beforeExecute` runs before review and again on every `refresh`. It must only build the call, with no side effects. If the final quote's minimum falls below the budget, the call rejects before signing. Source approvals and the intent signature may still prompt the wallet. To list tokens Relay can route, filter [`getBalances()`](/docs/nexus/nexus-sdk/reference/balances-and-tokens/get-balances) by `balance.providers[].id === 'relay'`. Token support does not guarantee destination-call support, which is decided per quote. | | Wallet mode | Provider mode | | -------------------- | ---------------------------- | --------------------------------- | | Runs the call | Connected wallet | Relay's router | | `msg.sender` | User | Relay's router | | Destination balances | Used first | Ignored | | Swap skipped | When balances cover the call | Never | | `beforeExecute` | Once, after funding | Before review and on each refresh | | Destination gas | Paid by the wallet | Included in the quote | | `result.execute` | Present | `undefined` | ## Example Swap 25 USDC from Arbitrum and deposit it into an ERC-4626 vault on Base. Remove `useProviderDestinationExecution` to run the same deposit from the wallet. ```typescript title="TypeScript" import { encodeFunctionData, parseAbi, parseUnits, type Hex } from 'viem'; const USDC_ARBITRUM: Hex = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831'; const USDC_BASE: Hex = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; const VAULT: Hex = '0xYourVault'; const user: Hex = '0xUserAddress'; const vaultAbi = parseAbi([ 'function deposit(uint256 assets, address receiver) returns (uint256)', ]); const source = await client.getToken({ chainId: 42161, tokenAddress: USDC_ARBITRUM }); const result = await client.swapAndExecute( { tradeType: 'exactInput', sources: [ { chainId: 42161, tokenAddress: USDC_ARBITRUM, amountRaw: parseUnits('25', source.decimals) }, ], toChainId: 8453, toTokenAddress: USDC_BASE, execute: { to: VAULT, gas: 200_000n, tokenApproval: { toTokenAddress: USDC_BASE, amount: 0n, spender: VAULT }, }, }, { useProviderDestinationExecution: true, beforeExecute: async ({ executeAmountRaw }) => ({ data: encodeFunctionData({ abi: vaultAbi, functionName: 'deposit', args: [executeAmountRaw, user], }), }), hooks: { onIntent: ({ intent, allow }) => { console.log(intent.executionMode, intent.executeRequirement.token.amount); allow(); // Call after the user confirms. }, }, }, ); if (result.executionMode === 'wallet') { console.log('Deposit tx', result.execute.txExplorerUrl); } else { console.log('Filled by Relay', result.swapResult.intentExplorerUrl); } console.log('Deposited', result.executeAmountRaw); ``` ## Returns ```typescript title="TypeScript" type SwapAndExecuteIntentResult = { executeAmountRaw: bigint } & ( | { executionMode: 'provider'; swapSkipped: false; swapResult: IntentResult; approval?: undefined; execute?: undefined; } | ({ executionMode: 'wallet'; approval?: TxResult; execute: TxResult } & ( | { swapSkipped: true; swapResult?: undefined } | { swapSkipped: false; swapResult: IntentResult } )) ); ``` Narrow on `executionMode` before reading `execute` or `approval`, and on `swapSkipped` before reading `swapResult` in wallet mode. `executeAmountRaw` is the amount the call used. Intent events cover funding only; the promise resolves after the call. For error codes, see [hooks and errors](/docs/nexus/nexus-sdk/reference/hooks-and-errors). ## Review Quotes Pass `options.hooks.onIntent` as the second argument to `swapWithExactIn()` or `swapWithExactOut()` to show the quote before any wallet prompt. ## Operation options `SwapOperationOptions` fields, all optional: | Name | Type | Description | | -------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------- | | `slippageBps` | `number \| 'auto'` | Default `50`. Integer from `0` to `10000`, or `'auto'`. | | `fillTimeoutMinutes` | `number` | Default `2`. How long to wait for the destination fill. | | `pollingIntervalMs` | `number` | Default `2000`. Interval between fill status checks. | | `onEvent` | `(event: IntentEvent) => void` | Progress events. See [swap events](/docs/nexus/nexus-sdk/reference/swap-methods/swap-events). | | `hooks.onIntent` | `(hook: IntentHookData) => void \| Promise` | Quote review. See below. | ## The onIntent hook | Field | Type | Description | | ------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `quote` | `IntentQuote` | The first quote. | | `execution` | `{ possible: true } \| { possible: false; cause: 'not-connected' \| 'insufficient-balance' }` | Whether the latest quote can execute. Read it again after each refresh. On `'insufficient-balance'`, fund the wallet and refresh. | | `allow()` | `() => void` | Accepts the latest quote and starts execution. | | `deny()` | `() => void` | Rejects the operation with `UserActionError` code `USER_INTENT_HOOK_DENIED`. | | `refresh(sources?)` | `(sources?: IntentSource[]) => Promise` | Requests a replacement quote, optionally for new sources, and emits a `quote` event. | | `attemptId` | `string` | Optional. Correlation ID shared across refreshes. | The operation waits until you call `allow()` or `deny()`. Returning from the hook does not accept the quote. If the hook throws, or an awaited `refresh()` rejects inside it, the operation rejects. Render the quote that `refresh()` returns, and await it before calling `allow()`. For `swapWithExactIn()`, replacement sources need `tokenAddress` and a positive `amountRaw`. Once accepted, the quote is fixed. A quote past `expiresAt` (Unix seconds) rejects when accepted, so refresh stale quotes before calling `allow()`. Without a hook, the SDK accepts the first quote automatically. ## Example ```typescript title="TypeScript" import type { IntentHookData, SwapExactOutParams } from '@avail-project/nexus-core'; import { formatUnits } from '@avail-project/nexus-core/utils'; declare const params: SwapExactOutParams; declare function reviewDialog(view: { receive: string; minimum: string; provider: string; canExecute: boolean; }): Promise<'confirm' | 'refresh' | 'cancel'>; const onIntent = async (hook: IntentHookData) => { let quote = hook.quote; for (;;) { const token = await client.getToken({ chainId: quote.output.chainId, tokenAddress: quote.output.tokenAddress, }); const choice = await reviewDialog({ receive: formatUnits(quote.output.amountRaw, token.decimals), minimum: formatUnits(quote.output.minAmountRaw, token.decimals), provider: quote.provider, canExecute: hook.execution.possible, }); if (choice === 'refresh') { quote = await hook.refresh(); continue; } if (choice === 'confirm' && hook.execution.possible) hook.allow(); else hook.deny(); return; } }; await client.swapWithExactOut(params, { hooks: { onIntent }, slippageBps: 100 }); ``` ## Key quote fields | Field | Description | | ------------------- | -------------------------------------------------------------------------------------------------------------- | | `provider` | Provider that fills this quote: `'nexus-v2'`, `'mayan'`, or `'relay'`. | | `input` | Per-source `amountRaw`, `depositFeeRaw`, and `totalRequiredRaw`, with USD strings. | | `output` | `amountRaw` (expected) and `minAmountRaw` (after slippage), with USD strings. | | `fees` | `depositRaw`, `fulfillmentRaw`, `protocolRaw`, and `solverRaw`, each with a `*Usd` string. | | `allowances` | ERC-20 approvals the wallet still needs. `deficitRaw > 0n` means a prompt. | | `plan.steps` | Steps the SDK will run. See [plan steps](/docs/nexus/nexus-sdk/reference/swap-methods/swap-events#plan-steps). | | `sourceVerdicts` | Each candidate source's `state`: `'selected'`, `'unused'`, or `'unroutable'` with a `reason`. | | `isExecutable` | Source-balance check only. It does not mean a wallet is connected. | | `executionWarnings` | Balance shortfalls per source, as `requiredRaw` and `actualRaw`. | | `expiresAt` | Expiry in Unix seconds. | `Raw` fields are `bigint` in the token's base units. USD fields are decimal strings. ## Disconnected previews `swapWithExactIn()` runs before a wallet connects, so you can show rates early. `swapWithExactOut()` requires a wallet. * `hook.execution` stays `{ possible: false, cause: 'not-connected' }` for that call, even after the user connects. * `allow()` rejects with `WALLET_NOT_CONNECTED`. `deny()` rejects with `USER_INTENT_HOOK_DENIED`. * Without a hook, the SDK emits the `quote` event, then rejects with `WALLET_NOT_CONNECTED`. * The preview can fail with `BACKEND_INSUFFICIENT_APPROVAL_GAS` if a source chain lacks gas for approvals. A preview never resolves successfully. After the wallet connects, start a new swap call and review its quote. ## Swap Exact Input `swapWithExactIn()` spends the exact raw amount you set on each source and delivers the quoted output to the destination token. It needs an [initialized client](/docs/nexus/nexus-sdk/get-started/nexus-sdk-setup). ## Signature ```typescript title="TypeScript" swapWithExactIn( input: SwapExactInParams, options?: SwapOperationOptions, ): Promise ``` ## Parameters | Name | Type | Description | | ---------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | | `sources` | `Array<{ chainId: number; tokenAddress: Hex; amountRaw: bigint }>` | Required, non-empty. Each `amountRaw` must be greater than `0n`. | | `toChainId` | `number` | Required. Destination chain ID. | | `toTokenAddress` | `Hex` | Required. Destination token address. | | `options` | `SwapOperationOptions` | Optional. Slippage, timeouts, quote review, and events. See [review quotes](/docs/nexus/nexus-sdk/reference/swap-methods/review-quotes). | All sources and the destination must share at least one provider. To build a compatible picker, pass the already selected sources to `getAvailableSourceTokens(destination, selectedSources)`. To bridge the same asset, use that asset's address on each chain. ## Example ```typescript title="TypeScript" import { parseUnits } from '@avail-project/nexus-core/utils'; const source = { chainId: 1, tokenAddress: '0xA0b86991c6218b36c1d19d4a2e9eb0cE3606eB48', // USDC on Ethereum } as const; const sourceToken = await client.getToken(source); const result = await client.swapWithExactIn({ sources: [{ ...source, amountRaw: parseUnits('25', sourceToken.decimals) }], toChainId: 8453, toTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base }); console.log(result.intentId, result.intentExplorerUrl); ``` Without an `onIntent` hook, the SDK accepts the first quote automatically. ## Returns A promise that resolves with an [`IntentResult`](/docs/nexus/nexus-sdk/reference/swap-methods/swap-events#result) after the destination fill. Before a wallet connects, the call works as a [rate preview](/docs/nexus/nexus-sdk/reference/swap-methods/review-quotes#disconnected-previews): it returns a quote for display, then rejects instead of executing. ## Errors | Code (`ERROR_CODES.*`) | Cause | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `INVALID_INPUT` | Empty `sources`, a missing `tokenAddress`, a non-positive `amountRaw`, no shared provider, or `slippageBps` out of range. | | `WALLET_NOT_CONNECTED` | The quote was accepted without a connected wallet. | | `USER_INTENT_HOOK_DENIED` | Your hook called `deny()`. | | `BACKEND_INSUFFICIENT_BALANCE` | The wallet cannot cover the quoted inputs. | | `BACKEND_FULFILMENT_WAIT_TIMEOUT` | No fill within `fillTimeoutMinutes`. The intent can still fill. | | `ENVIRONMENT_NOT_SUPPORTED` | The client uses `network: 'testnet'`. | For quote diagnostics and recovery, see [hooks and errors](/docs/nexus/nexus-sdk/reference/hooks-and-errors). ## Swap Events Pass `options.onEvent` to `swapWithExactIn()` or `swapWithExactOut()` to receive `IntentEvent` updates. The SDK catches errors thrown by your callback, so a failing handler never interrupts the swap. ## Events `IntentEvent` is a union discriminated on `type`. | `type` | Fields | When | | -------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | `quote` | `quote: IntentQuote` | For the first quote and after every refresh. | | `step` | `step: IntentPlanStep`, `state`, `committed`, `errorDetails?` | When a plan step starts, completes, or fails. `state` is `'started'`, `'completed'`, or `'failed'`. | | `status` | `intentId`, `status`, `substatus`, `legs` | On each fill status poll. `status` is `'created'`, `'deposited'`, `'fulfilled'`, or `'expired'`. | Each entry in `legs` describes one source: `sourceIndex`, `status`, and optional `txHash`, `txExplorerUrl`, `protocolExplorerUrl`, and `error`. ## Example ```typescript title="TypeScript" import type { IntentEvent, IntentQuote, SwapExactInParams } from '@avail-project/nexus-core'; declare const params: SwapExactInParams; declare function showQuote(quote: IntentQuote): void; declare function setStep(type: string, state: string): void; declare function showError(message?: string, code?: string, committed?: boolean): void; declare function setFillStatus(intentId: string, status: string): void; declare function setLegLink(sourceIndex: number, url?: string): void; const onEvent = (event: IntentEvent) => { switch (event.type) { case 'quote': showQuote(event.quote); break; case 'step': setStep(event.step.type, event.state); if (event.state === 'failed') { showError(event.errorDetails?.message, event.errorDetails?.code, event.committed); } break; case 'status': setFillStatus(event.intentId, event.status); for (const leg of event.legs) setLegLink(leg.sourceIndex, leg.txExplorerUrl); break; } }; await client.swapWithExactIn(params, { onEvent }); ``` Events report progress only. Catch the promise to handle the final outcome. ## Plan steps `quote.plan.steps` lists the steps for that quote, and `step` events reference them. | `step.type` | Extra fields | Wallet action | | --------------------------- | ------------------------------------------------- | ----------------------------------------------------- | | `erc20_approval` | `chainId`, `tokenAddress`, `spender`, `amountRaw` | Approval transaction for the minimum required amount | | `source_approval_signature` | `chainId`, `tokenAddress`, `spender`, `amountRaw` | Typed-data signature that authorizes the source token | | `intent_signature` | none | Signature over the intent | | `native_transaction` | `chainId`, `sourceIndex`, `to`, `valueRaw` | Native-token deposit transaction | | `intent_submission` | none | None. The SDK submits the intent. | | `intent_fulfillment` | none | None. The SDK waits for the fill. | ## Committed attempts `committed` becomes `true` once the attempt can settle even if a later step fails: after the intent signature when an ERC-20 source is used, or after a native deposit is broadcast. > **Note** > > If a step fails with `committed: true`, the intent may still fill. Check `listIntents()` or the explorer before retrying, or the user may pay twice. ## Step errors A failed `step` event carries `errorDetails` (`IntentStepError`): `name`, `message`, and optional `code`, `category`, `service`, `stepId`, `stepType`, `chainId`, and `details`. Match on `code`, not `message`. ## Result Both swap methods resolve with `IntentResult` after the fill. | Field | Type | Description | | -------------------- | ---------------------- | -------------------------------------------------------------------------------- | | `intentId` | `Hex` | Intent ID, also the quote ID. | | `intentExplorerUrl` | `string` | Explorer link for the intent. | | `quote` | `IntentQuote` | The accepted quote. | | `status` | `IntentStatusResponse` | Final `id`, `provider`, `status`, `substatus`, and `legs`. | | `approvals` | `IntentTransaction[]` | Approval transactions: `chainId`, `txHash`, `txExplorerUrl`, optional `receipt`. | | `nativeTransactions` | `IntentTransaction[]` | Native deposit transactions, same shape. | | `attemptId` | `string` | Optional. Store it with `intentId` for support requests. | A rejected promise can follow transactions that already landed. See [hooks and errors](/docs/nexus/nexus-sdk/reference/hooks-and-errors). ## Swap Exact Output `swapWithExactOut()` delivers the exact raw amount you request and lets the quote decide which balances fund it. It needs an [initialized client](/docs/nexus/nexus-sdk/get-started/nexus-sdk-setup) with a connected wallet. ## Signature ```typescript title="TypeScript" swapWithExactOut( input: SwapExactOutParams, options?: SwapOperationOptions, ): Promise ``` ## Parameters | Name | Type | Description | | ------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | | `toChainId` | `number` | Required. Destination chain ID. | | `toTokenAddress` | `Hex` | Required. Destination token address. | | `toAmountRaw` | `bigint` | Required, greater than `0n`. Amount to receive, in the destination token's base units. | | `sources` | `Array<{ chainId: number; tokenAddress?: Hex }>` | Optional. Omit or pass `[]` to fund from any wallet balance. Otherwise, only the listed chains and tokens are used. | | `toNativeAmountRaw` | `bigint` | Optional, default `0n`. Extra destination native token for gas, in native base units. | | `options` | `SwapOperationOptions` | Optional. Slippage, timeouts, quote review, and events. See [review quotes](/docs/nexus/nexus-sdk/reference/swap-methods/review-quotes). | A source with only `chainId` allows any token on that chain. Sources are alternatives, not a required mix: the quote may use some or all of them. To list compatible sources, call `getAvailableSourceTokens(destination, [], query)`. Resolve `toAmountRaw` with the destination token's `decimals` and `toNativeAmountRaw` with the chain's `nativeCurrency.decimals`. ## Example ```typescript title="TypeScript" import { parseUnits } from '@avail-project/nexus-core/utils'; const destination = { chainId: 8453, tokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base } as const; const token = await client.getToken(destination); const base = client.getSupportedChains().find((chain) => chain.id === destination.chainId)!; const result = await client.swapWithExactOut({ toChainId: destination.chainId, toTokenAddress: destination.tokenAddress, toAmountRaw: parseUnits('100', token.decimals), toNativeAmountRaw: parseUnits('0.0005', base.nativeCurrency.decimals), sources: [ { chainId: 42161 }, // any token on Arbitrum { chainId: 1, tokenAddress: '0xA0b86991c6218b36c1d19d4a2e9eb0cE3606eB48' }, // USDC on Ethereum ], }); console.log(result.intentId, result.intentExplorerUrl); ``` Without an `onIntent` hook, the SDK accepts the first quote automatically. ## Returns A promise that resolves with an [`IntentResult`](/docs/nexus/nexus-sdk/reference/swap-methods/swap-events#result) after the destination fill. ## Errors | Code (`ERROR_CODES.*`) | Cause | | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `WALLET_NOT_CONNECTED` | No wallet is connected. | | `INVALID_INPUT` | `toAmountRaw` is not positive, `toNativeAmountRaw` is negative, no provider serves the destination, no listed source shares a provider with it, or `slippageBps` is out of range. | | `USER_INTENT_HOOK_DENIED` | Your hook called `deny()`. | | `BACKEND_INSUFFICIENT_BALANCE` | The eligible balances cannot cover the quote. | | `BACKEND_SAME_CHAIN_GAS_DROP_UNSUPPORTED` | `toNativeAmountRaw` is set on a same-chain route. | | `BACKEND_FULFILMENT_WAIT_TIMEOUT` | No fill within `fillTimeoutMinutes`. The intent can still fill. | | `ENVIRONMENT_NOT_SUPPORTED` | The client uses `network: 'testnet'`. | For quote diagnostics and recovery, see [hooks and errors](/docs/nexus/nexus-sdk/reference/hooks-and-errors). ## Deposit into Aave > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). In this guide you will implement a **Bridge & Execute** flow that bridges USDC from any chain and immediately supplies it into Aave V3 on Arbitrum Sepolia in a single user-signed intent. You will also build a step-by-step progress UI that tracks each phase of the transaction. > **Prerequisite** > > This guide builds on the [Bridge Tokens](/docs/nexus/nexus-sdk/v2/get-started/bridge-tokens) tutorial. Complete that first. ## What you will build A deposit button that: 1. Bridges USDC from one or more source chains to Arbitrum Sepolia 2. Automatically calls Aave V3's `supply()` function on the destination chain 3. Displays a real-time progress checklist with explorer links ## How `bridgeAndExecute` works The `client.bridgeAndExecute()` method extends regular bridging by accepting an `execute` object that defines a contract call to run after the bridge completes. The user signs a single intent that covers both operations. | Param | Type | Required | Description | | ----------------------- | ------------------------------------------------------------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `toTokenSymbol` | `string` | Yes | Token to bridge (e.g. "USDC"). | | `toAmountRaw` | `bigint` | Yes | Amount in smallest unit (e.g. BigInt(1\_000\_000) = 1 USDC). | | `toChainId` | `number` | Yes | Destination chain ID. The `execute` object inherits it, so it omits `toChainId`. | | `execute.to` | `string` | Yes | Contract address to call on the destination chain. | | `execute.data` | `string` | Yes | ABI-encoded function call data. | | `execute.tokenApproval` | `{ toTokenSymbol: string, amount: bigint, spender: string }` | For this flow | Optional in general; required whenever the target contract pulls ERC-20 funds via `transferFrom`, as Aave's `supply()` does. | ## Step 1 - Add the bridgeAndExecute helper Update your SDK helper file with a `bridgeAndExecute` function. This encodes an Aave V3 `supply()` call and passes it to the SDK. ```tsx title="src/lib/nexus.ts" import type { BridgeAndExecuteEvent } from "@avail-project/nexus-core"; import { encodeFunctionData } from "viem"; // ... existing code (client instance, initialize, destroy, getBalancesForBridge, bridge) ... const AAVE_V3_POOL = "0xBfC91D59fdAA134A4ED45f7B584cAf96D7792Eff"; // Aave V3 Pool on Arbitrum Sepolia export async function bridgeAndExecute( userAddress: `0x${string}`, onEvent: (event: BridgeAndExecuteEvent) => void ) { if (!client.hasEvmProvider) { throw new Error("Connect a wallet and initialize the client first."); } if (!userAddress) { throw new Error("User address is required"); } // Look up the USDC contract address on Arbitrum Sepolia from the client's chain list. const usdc = client.chainList.getTokenInfoBySymbol(421614, "USDC"); // Encode the Aave V3 supply() function call const data = encodeFunctionData({ abi: [ { inputs: [ { internalType: "address", name: "asset", type: "address" }, { internalType: "uint256", name: "amount", type: "uint256" }, { internalType: "address", name: "onBehalfOf", type: "address", }, { internalType: "uint16", name: "referralCode", type: "uint16", }, ], name: "supply", outputs: [], stateMutability: "nonpayable", type: "function", }, ], functionName: "supply", args: [ usdc.contractAddress, // asset: USDC token address BigInt(1_000_000), // amount: 1 USDC (6 decimals) userAddress, // onBehalfOf: user's address 0, // referralCode: 0 ], }); const result = await client.bridgeAndExecute( { toTokenSymbol: "USDC", toAmountRaw: BigInt(1_000_000), // 1 USDC (6 decimals) toChainId: 421614, // Arbitrum Sepolia execute: { // toChainId is inherited from the top-level toChainId to: AAVE_V3_POOL, data: data, tokenApproval: { toTokenSymbol: "USDC", amount: BigInt(1_000_000), spender: AAVE_V3_POOL, }, }, }, { onEvent } ); return result; } ``` ### Breaking down the `execute` object ```ts execute: { // toChainId is inherited from the top-level toChainId to: AAVE_V3_POOL, // Aave V3 Pool contract data: data, // Encoded supply() call tokenApproval: { toTokenSymbol: "USDC", amount: BigInt(1_000_000), spender: AAVE_V3_POOL, }, } ``` * **`to`** - the contract address to call after bridging completes * **`data`** - ABI-encoded function call, built with viem's `encodeFunctionData` * **`tokenApproval`** - Aave needs approval to pull USDC from the user, so the SDK handles the approval step automatically ## Step 2 - Create the deposit button component This component triggers the transaction and renders a real-time progress checklist. ```tsx title="src/components/deposit-button.tsx" "use client"; import { useState } from "react"; import { useAccount } from "wagmi"; import type { BridgeAndExecutePlanStep, } from "@avail-project/nexus-core"; import { bridgeAndExecute, client } from "../lib/nexus"; type CompletedStep = { state: string; explorerUrl?: string }; export default function DepositButton({ className, onResult, }: { className?: string; onResult?: (r: any) => void; }) { const { address } = useAccount(); const [steps, setSteps] = useState([]); const [completed, setCompleted] = useState>({}); const [error, setError] = useState(""); const onClick = async () => { if (!client.hasEvmProvider) return alert("Initialize first"); if (!address) return alert("Please connect your wallet first"); setSteps([]); setCompleted({}); setError(""); try { const res = await bridgeAndExecute(address, (event) => { // plan_preview / plan_confirmed carry the full list of steps if (event.type === "plan_preview" || event.type === "plan_confirmed") { setSteps(event.plan.steps); } // plan_progress reports per-step state transitions if (event.type === "plan_progress") { // Both 'confirmed' and 'completed' are terminal success states if (event.state === "confirmed" || event.state === "completed") { setCompleted((prev) => ({ ...prev, [event.step.id]: { state: event.state, explorerUrl: "explorerUrl" in event ? event.explorerUrl : undefined, }, })); } } }); onResult?.(res); } catch (e: any) { setError(e.message || "Transaction failed"); } }; return (
{steps.length > 0 && (

Transaction Progress:

{steps.map((step) => { const done = completed[step.id]; const isDone = !!done; return (
{isDone ? "✅" : "○"} ({step.type}) {done?.explorerUrl && ( View on Explorer )}
); })}
)} {error && (
Error: {error}
)}
); } ``` ### How the progress UI works The component derives the checklist from the typed `onEvent` stream: * **`steps`** - populated from `event.plan.steps` when a `plan_preview` (or `plan_confirmed`) event fires, listing every step the transaction will go through (allowance approval, request signing, vault deposit, bridge fill, execute approval, execute transaction, and so on) * **`completed`** - a map keyed by `step.id`, filled in from `plan_progress` events. On-chain steps settle on `confirmed` and off-chain steps on `completed`, so the UI treats both as terminal-success states The UI renders the expected steps as a checklist and marks each one done when its `step.id` appears in `completed`. If the progress event carried an `explorerUrl`, a "View on Explorer" link is shown. ## Step 3 - Add the deposit button to the page Import `DepositButton` into your main page: ```tsx title="src/app/page.tsx" "use client"; import { useState } from "react"; import { useAccount } from "wagmi"; import ConnectWalletButton from "@/components/connect-button"; import InitButton from "@/components/init-button"; import FetchBalancesButton from "@/components/fetch-balances-button"; import DeinitButton from "@/components/de-init-button"; import BridgeButton from "@/components/bridge-button"; import DepositButton from "@/components/deposit-button"; import { client } from "@/lib/nexus"; export default function Page() { const { isConnected } = useAccount(); const [initialized, setInitialized] = useState(client.hasEvmProvider); const [balances, setBalances] = useState(null); const [bridgeResult, setBridgeResult] = useState(null); const [depositResult, setDepositResult] = useState(null); const btn = "px-4 py-2 rounded-md bg-blue-600 text-white hover:bg-blue-700 " + "disabled:opacity-50 disabled:cursor-not-allowed"; return (
setInitialized(true)} /> setBalances(r)} /> setBridgeResult(r)} /> setDepositResult(r)} /> { setInitialized(false); setBalances(null); }} />
Wallet Status:{" "} {isConnected ? "Connected" : "Not connected"}
SDK Status:{" "} {initialized ? "Initialized" : "Not initialized"}
{balances && (
            {JSON.stringify(balances, null, 2)}
          
)} {bridgeResult && (
            {JSON.stringify(bridgeResult, null, 2)}
          
)} {depositResult && (
            {JSON.stringify(depositResult, null, 2)}
          
)}
); } ``` ## Step 4 - Run and test ```bash pnpm dev ``` 1. Connect your wallet and initialize Nexus 2. Click **Bridge USDC & Deposit into Aave** 3. Approve the transaction in your wallet 4. Watch the progress checklist update in real-time as each step completes 5. Click the explorer links to verify on-chain > **What you just built** > > With a single SDK call you bridged tokens across chains **and** executed a DeFi deposit on the destination chain. The `bridgeAndExecute` pattern works with any contract call. You could swap on a DEX, stake in a protocol, mint an NFT, anything you can encode as calldata. ## What's next You have completed the full Nexus SDK "Get Started" series. From here you can explore: - [**SDK Reference**](/docs/nexus/nexus-sdk/v2/reference) — Full API docs for bridge, swap, and balance methods. - [**Bridge Methods**](/docs/nexus/nexus-sdk/v2/reference/bridge-methods/fetch-bridge-balances) — Deep dive into bridge(), bridgeAndTransfer(), and bridgeAndExecute(). - [**Swap Methods**](/docs/nexus/nexus-sdk/v2/reference/swap-methods/fetch-swap-balances) — Cross-chain swaps with swapWithExactIn() and swapWithExactOut(). - [**Cookbook Recipes**](/docs/nexus/nexus-sdk/v2/cookbook-recipes) — Copy-paste patterns for common developer workflows. ## Bridge Tokens > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). In this guide you will add a bridge function to your Nexus SDK project that moves tokens from any supported source chain to a destination chain. By the end you will have a working bridge button that transfers USDC to Arbitrum Sepolia with real-time step tracking. > **Prerequisite** > > This guide builds on the [Initializing Nexus with RainbowKit](/docs/nexus/nexus-sdk/v2/get-started/initializing-nexus-with-rainbowkit) tutorial. Complete that first. ## What you will build A bridge button that: * Calls `client.bridge()` to move USDC from any chain to Arbitrum Sepolia * Uses the typed `onEvent` callback for step-by-step progress updates * Displays the Nexus explorer URL on completion ## Step 1 — Add the bridge helper function Add a `bridge()` function to your existing SDK helper file. This example bridges 0.0001 USDC to Arbitrum Sepolia (chain ID `421614`). ```tsx title="src/lib/nexus.ts" import type { BridgeResult } from "@avail-project/nexus-core"; // ... existing code (client instance, initialize, destroy, getBalancesForBridge) ... export async function bridge() { const bridgeResult: BridgeResult = await client.bridge( { toTokenSymbol: "USDC", toAmountRaw: BigInt(100), toChainId: 421614, // Arbitrum Sepolia }, { onEvent: (event) => { switch (event.type) { case "status": // Lifecycle phase: intent_building → intent_ready → ... → completed console.log("Status:", event.status); break; case "plan_preview": // Full list of steps that will execute console.log("Expected steps:", event.plan.steps); break; case "plan_progress": // Fires as each step transitions state console.log(`Step ${event.step.type}: ${event.state}`); if ( (event.state === "submitted" || event.state === "confirmed") && "txHash" in event ) { console.log("View tx:", event.explorerUrl); } break; } }, } ); return bridgeResult; } ``` ### How `client.bridge()` works | Param | Type | Required | Description | | --------------- | ---------- | -------- | -------------------------------------------------------------------------------------- | | `toTokenSymbol` | `string` | Yes | Token symbol to bridge (e.g. "USDC", "ETH"). | | `toAmountRaw` | `bigint` | Yes | Amount in the token's smallest unit (e.g. BigInt(100) = 0.0001 USDC with 6 decimals). | | `toChainId` | `number` | Yes | Destination chain ID (e.g. 421614 for Arbitrum Sepolia). | | `sources` | `number[]` | No | Restrict the source chains funds may be pulled from. Defaults to all supported chains. | The second argument is an options object with an `onEvent` callback. Progress arrives as a typed discriminated union on `event.type`: * **`status`** — lifecycle phase updates on `event.status` (e.g. `intent_building` → `completed`) * **`plan_preview`** — fired once with the full list of steps on `event.plan.steps` * **`plan_progress`** — fired as each step advances, carrying `event.stepType`, `event.state`, and `event.step`. On-chain steps expose `txHash` / `explorerUrl` in the `submitted` and `confirmed` states ## Step 2 — Create the bridge button component Create a button component that calls the bridge function and displays the result. ```tsx title="src/components/bridge-button.tsx" "use client"; import { bridge, client } from "../lib/nexus"; export default function BridgeButton({ className, onResult, }: { className?: string; onResult?: (r: any) => void; }) { const onClick = async () => { if (!client.hasEvmProvider) return alert("Initialize first"); const res = await bridge(); onResult?.(res); console.log(res); }; return ( ); } ``` ## Step 3 — Add the button to the page Import `BridgeButton` into your main page alongside the existing components: ```tsx title="src/app/page.tsx" "use client"; import { useState } from "react"; import { useAccount } from "wagmi"; import ConnectWalletButton from "@/components/connect-button"; import InitButton from "@/components/init-button"; import FetchBalancesButton from "@/components/fetch-balances-button"; import DeinitButton from "@/components/de-init-button"; import BridgeButton from "@/components/bridge-button"; import { client } from "@/lib/nexus"; export default function Page() { const { isConnected } = useAccount(); const [initialized, setInitialized] = useState(client.hasEvmProvider); const [balances, setBalances] = useState(null); const [bridgeResult, setBridgeResult] = useState(null); const btn = "px-4 py-2 rounded-md bg-blue-600 text-white hover:bg-blue-700 " + "disabled:opacity-50 disabled:cursor-not-allowed"; return (
setInitialized(true)} /> setBalances(r)} /> setBridgeResult(r)} /> { setInitialized(false); setBalances(null); }} />
Wallet Status:{" "} {isConnected ? "Connected" : "Not connected"}
SDK Status:{" "} {initialized ? "Initialized" : "Not initialized"}
{balances && (
            {JSON.stringify(balances, null, 2)}
          
)} {bridgeResult && (
            {JSON.stringify(bridgeResult, null, 2)}
          
)}
); } ``` ## Step 4 — Run and test ```bash pnpm dev ``` 1. Connect your wallet and initialize Nexus 2. Click **Bridge 0.0001 USDC to Arbitrum Sepolia** 3. Approve the transaction in your wallet 4. Watch the console for step progress events 5. On success, you will see a `BridgeResult` with the intent explorer URL, the source transactions, and the resolved intent: ```json { "intentExplorerUrl": "https://nexus-v2.testnet.avail.so/intent/0x37b1...84fe", "sourceTxs": [ { "txHash": "0x…", "txExplorerUrl": "https://…", "chain": { "id": 84532, "name": "Base Sepolia", "logo": "…" } } ], "intent": { "…": "…" } } ``` > **Customize the bridge** > > Replace the hardcoded values in `bridge()` with user inputs to build a dynamic bridge UI. You can change the `toTokenSymbol`, `toAmountRaw`, and `toChainId` parameters to bridge any supported token to any supported chain. ## Next step Take bridging further by combining it with an on-chain execution — bridge tokens and immediately deposit them into a DeFi protocol in a single transaction. - [**Deposit into Aave**](/docs/nexus/nexus-sdk/v2/get-started/deposit-into-aave) ## Installation > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). In this guide you will install `@avail-project/nexus-core` and its peer dependencies into an existing project. By the end, your project will be ready for SDK initialization in the next step. ## Prerequisites Before you begin, make sure you have: * **A JavaScript or TypeScript project** — the SDK works with any framework or runtime (React, Node.js, vanilla JS, etc.) * **Node.js 18+** or **Bun** with a package manager (npm 9+, pnpm, yarn, or bun) * **An [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compatible wallet provider** to sign transactions (e.g. MetaMask via `window.ethereum`, or a library like [wagmi](https://wagmi.sh)) > **Wallet provider** > > The SDK needs an EIP-1193 provider to send transactions. In a browser you can use `window.ethereum` directly. Wallet libraries like wagmi or RainbowKit are optional — they just make it easier to manage wallet connections in React apps. ## Step 1 — Install the core package Run one of the following commands to add the Nexus SDK to your project: #### npm ```bash npm install @avail-project/nexus-core ``` #### pnpm ```bash pnpm add @avail-project/nexus-core ``` #### yarn ```bash yarn add @avail-project/nexus-core ``` #### bun ```bash bun add @avail-project/nexus-core ``` ### What gets installed The `@avail-project/nexus-core` package provides: * **`createNexusClient`** — the factory function for creating a Nexus client and performing cross-chain operations * **Bridge methods** — `bridge()`, `bridgeAndTransfer()`, `bridgeAndExecute()` for moving tokens across chains * **Swap methods** — `swapWithExactIn()`, `swapWithExactOut()` for cross-chain token swaps * **Balance methods** — `getBalancesForBridge()` and `getBalancesForSwap()` for multi-chain token balances * **Typed progress events** — an `onEvent` callback with a discriminated union (`status`, `plan_preview`, `plan_confirmed`, `plan_progress`) for tracking transaction progress * **Type exports** — `NexusClient`, `BridgeResult`, `EthereumProvider`, and more ## Step 2 — Verify the installation Confirm the package was installed by importing and creating a client: ```ts import { createNexusClient } from "@avail-project/nexus-core"; const client = createNexusClient(); console.log("Nexus SDK ready"); ``` If this compiles without errors, the installation is complete. ## Next step The package is installed. Next, you will create an SDK instance, connect it to a wallet, and fetch your first token balances. - [**Nexus SDK Setup**](/docs/nexus/nexus-sdk/v2/get-started/nexus-sdk-setup) ## Nexus SDK Setup > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). In this guide you will create a Nexus SDK instance, connect it to a wallet provider, and fetch unified balances across all supported chains. By the end you will have a minimal Next.js app with four buttons that demonstrate the full SDK lifecycle. ## Prerequisites * [Node.js](https://nodejs.org) version 18 or higher, with npm 9 or higher * A package manager — this guide uses `pnpm`, but `npm` and `yarn` work too * An [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compatible wallet (e.g. MetaMask) installed in your browser * `@avail-project/nexus-core` installed in your project (see [Installation](/docs/nexus/nexus-sdk/v2/get-started/installation)) ## What you will build A minimal Next.js page with four buttons: 1. **Connect Wallet** — connects an EIP-1193 wallet 2. **Initialize Nexus** — passes the wallet provider to the SDK 3. **Fetch Balances** — retrieves token balances across all supported chains 4. **De-initialize** — tears down the SDK instance > **Balance methods** > > The SDK provides two balance methods: `getBalancesForBridge()` returns tokens available for bridging, while `getBalancesForSwap()` returns tokens available for cross-chain swaps. Learn more in the [Bridge vs Swap concept page](/docs/nexus/nexus-sdk/v2/bridge-v-swap). ## Step 1 — Create a Next.js project If you don't have an existing project, scaffold one: ```bash pnpm create next-app@latest my-nexus-app --ts --eslint --app --src-dir cd my-nexus-app ``` Install the Nexus SDK: ```bash pnpm add @avail-project/nexus-core ``` ## Step 2 — Create the SDK instance Create a single shared SDK instance that the rest of the app will import. This file lives outside of React so it has no component lifecycle concerns. ```tsx title="src/lib/nexus.ts" import { createNexusClient, type NexusClient } from "@avail-project/nexus-core"; // Create a single client instance. // Pass { network: 'testnet' } to use testnet chains. // Defaults to 'mainnet' if omitted. export const client: NexusClient = createNexusClient({ network: "testnet" }); export async function initializeWithProvider(provider: any) { if (!provider) throw new Error("No EIP-1193 provider (e.g. MetaMask) found"); if (client.hasEvmProvider) return; await client.initialize(); // loads deployment info from the middleware await client.setEVMProvider(provider); // attaches the connected wallet } export function destroy() { client.destroy(); } export async function getBalancesForBridge() { return await client.getBalancesForBridge(); } export async function getBalancesForSwap() { return await client.getBalancesForSwap(); } ``` ### What each part does | Export | Type | Description | | ---------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------- | | `client` | `NexusClient` | The shared Nexus client, created once with `createNexusClient()`. | | `client.hasEvmProvider` | `boolean` | Getter that returns `true` once `setEVMProvider()` has attached a wallet. | | `initializeWithProvider(provider)` | `(provider: any) => Promise` | Runs `client.initialize()` then `client.setEVMProvider(provider)`. Must be called after a wallet is connected. | | `destroy()` | `() => void` | Tears down the client (synchronous). | | `getBalancesForBridge()` | `() => Promise` | Returns bridgeable token balances across all supported chains. | | `getBalancesForSwap()` | `() => Promise` | Returns swappable token balances across all supported chains. | > **Two-step initialization** > > v2 splits startup into two calls: `client.initialize()` loads deployment data from the middleware, and `client.setEVMProvider(provider)` attaches the connected wallet. You can call them in either order, but wallet-dependent methods only work once `setEVMProvider()` has resolved. ## Step 3 — Create the UI components Create four button components in the `src/components` directory. ### connect-button.tsx Connects an EIP-1193 wallet using the browser's injected provider (`window.ethereum`). ```tsx title="src/components/connect-button.tsx" "use client"; export default function ConnectButton({ className, }: { className?: string; }) { const onClick = async () => { const eth = (window as any)?.ethereum; if (!eth) return alert("Install an EIP-1193 wallet (e.g. MetaMask)"); await eth.request?.({ method: "eth_requestAccounts" }); alert("Wallet connected"); }; return ( ); } ``` ### init-button.tsx Initializes the Nexus SDK with the connected wallet's provider. ```tsx title="src/components/init-button.tsx" "use client"; import { initializeWithProvider, client } from "../lib/nexus"; export default function InitButton({ className, onReady, }: { className?: string; onReady?: () => void; }) { const onClick = async () => { const eth = (window as any)?.ethereum; try { await initializeWithProvider(eth); onReady?.(); alert("Nexus initialized"); } catch (e: any) { alert(e?.message ?? "Init failed"); } }; return ( ); } ``` > **Using a wallet library?** > > This tutorial uses `window.ethereum` directly for simplicity. If you use wagmi, RainbowKit, or another wallet library, see [Initializing Nexus with RainbowKit](/docs/nexus/nexus-sdk/v2/get-started/initializing-nexus-with-rainbowkit) for how to retrieve the provider from a connector. ### fetch-balances-button.tsx Fetches the user's balances across all supported chains. ```tsx title="src/components/fetch-balances-button.tsx" "use client"; import { getBalancesForBridge, getBalancesForSwap, client } from "../lib/nexus"; export default function FetchBalancesButton({ className, onResult, }: { className?: string; onResult?: (r: any) => void; }) { const onClick = async () => { if (!client.hasEvmProvider) return alert("Initialize first"); // Use getBalancesForBridge() for bridgeable tokens // Use getBalancesForSwap() for swappable tokens const bridgeBalances = await getBalancesForBridge(); const swapBalances = await getBalancesForSwap(); const res = { bridgeBalances, swapBalances }; onResult?.(res); console.log(res); }; return ( ); } ``` ### de-init-button.tsx Destroys the client and resets state. ```tsx title="src/components/de-init-button.tsx" "use client"; import { destroy, client } from "../lib/nexus"; export default function DeinitButton({ className, onDone, }: { className?: string; onDone?: () => void; }) { const onClick = async () => { destroy(); onDone?.(); alert("Nexus de-initialized"); }; return ( ); } ``` ## Step 4 — Assemble the page Wire all four components together in a single page: ```tsx title="src/app/page.tsx" "use client"; import { useState } from "react"; import ConnectButton from "@/components/connect-button"; import InitButton from "@/components/init-button"; import FetchBalancesButton from "@/components/fetch-balances-button"; import DeinitButton from "@/components/de-init-button"; import { client } from "@/lib/nexus"; export default function Page() { const [initialized, setInitialized] = useState(client.hasEvmProvider); const [balances, setBalances] = useState(null); const btn = "px-4 py-2 rounded-md bg-blue-600 text-white hover:bg-blue-700 " + "disabled:opacity-50 disabled:cursor-not-allowed"; return (
setInitialized(true)} /> setBalances(r)} /> { setInitialized(false); setBalances(null); }} />
SDK Status:{" "} {initialized ? "Initialized" : "Not initialized"}
{balances && (
            {JSON.stringify(balances, null, 2)}
          
)}
); } ``` ## Step 5 — Run and test ```bash pnpm dev ``` Open [http://localhost:3000](http://localhost:3000) and click through the buttons in order: 1. **Connect Wallet** — approve the wallet prompt in MetaMask 2. **Initialize Nexus** — the SDK connects to the wallet provider 3. **Fetch Balances** — a JSON object appears showing your tokens across chains 4. **De-initialize** — the SDK resets > **What you just built** > > In a few lines of code you connected to a wallet, initialized the Nexus SDK, and fetched a cross-chain view of every token the user holds. From here you can bridge tokens, execute swaps, or build any custom UI on top of the headless SDK. ## Next step Upgrade your wallet connection experience by replacing `window.ethereum` with RainbowKit. - [**Initializing Nexus with RainbowKit**](/docs/nexus/nexus-sdk/v2/get-started/initializing-nexus-with-rainbowkit) ## Initializing Nexus with RainbowKit > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). In this guide you will add RainbowKit to an existing Nexus SDK project and retrieve the EIP-1193 provider from wagmi's connector instead of `window.ethereum`. By the end you will have a full wallet connection modal, multi-wallet support, and all existing SDK functionality intact. > **Prerequisite** > > This guide builds on the [Nexus SDK Setup](/docs/nexus/nexus-sdk/v2/get-started/nexus-sdk-setup). Complete that first. ## What changes | Before (basic setup) | After (RainbowKit) | | --------------------------------------- | ------------------------------------------ | | `window.ethereum` for wallet connection | RainbowKit modal with multi-wallet support | | Manual `eth_requestAccounts` call | wagmi handles connection state | | Single injected provider only | WalletConnect, Coinbase, and more | Everything else — the SDK instance, helper functions, balance fetching, and de-initialization — stays **exactly the same**. ## Step 1 — Install RainbowKit dependencies Add the required packages to your project: #### npm ```bash npm install @rainbow-me/rainbowkit wagmi viem @tanstack/react-query ``` #### pnpm ```bash pnpm add @rainbow-me/rainbowkit wagmi viem @tanstack/react-query ``` #### yarn ```bash yarn add @rainbow-me/rainbowkit wagmi viem @tanstack/react-query ``` ## Step 2 — Create the wagmi configuration Create a config file that defines the supported chains and your WalletConnect project ID. ```tsx title="src/lib/wagmi.ts" import { getDefaultConfig } from "@rainbow-me/rainbowkit"; import { mainnet, arbitrum, polygon, optimism, base, avalanche, } from "wagmi/chains"; export const config = getDefaultConfig({ appName: "Nexus SDK with RainbowKit", projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID!, chains: [mainnet, arbitrum, polygon, optimism, base, avalanche], ssr: true, }); ``` > **WalletConnect Project ID required** > > Get a free Project ID at [cloud.walletconnect.com](https://cloud.walletconnect.com). Add it to your `.env.local` as `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID`. ## Step 3 — Create the providers component Wrap your app with wagmi, React Query, and RainbowKit providers. ```tsx title="src/components/providers.tsx" "use client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { WagmiProvider } from "wagmi"; import { RainbowKitProvider } from "@rainbow-me/rainbowkit"; import { config } from "@/lib/wagmi"; import "@rainbow-me/rainbowkit/styles.css"; const queryClient = new QueryClient(); export function Providers({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ## Step 4 — Update the layout Wrap your root layout with the new providers component: ```tsx title="src/app/layout.tsx" import { Providers } from "@/components/providers"; import "./globals.css"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` ## Step 5 — Update the connect button Replace the manual `window.ethereum` approach with RainbowKit's `ConnectButton`: ```tsx title="src/components/connect-button.tsx" "use client"; import { ConnectButton } from "@rainbow-me/rainbowkit"; export default function ConnectWalletButton({ className, }: { className?: string; }) { return ( {({ account, chain, openAccountModal, openChainModal, openConnectModal, authenticationStatus, mounted, }) => { const ready = mounted && authenticationStatus !== "loading"; const connected = ready && account && chain && (!authenticationStatus || authenticationStatus === "authenticated"); return (
{(() => { if (!connected) { return ( ); } if (chain.unsupported) { return ( ); } return (
); })()}
); }}
); } ``` ## Step 6 — Update the init button The key change: instead of reading `window.ethereum`, use wagmi's `useAccount` hook to get the provider from the connected wallet's connector. ```tsx title="src/components/init-button.tsx" "use client"; import { useAccount } from "wagmi"; import { initializeWithProvider, client } from "../lib/nexus"; export default function InitButton({ className, onReady, }: { className?: string; onReady?: () => void; }) { const { connector } = useAccount(); const onClick = async () => { try { const provider = await connector?.getProvider(); if (!provider) throw new Error("No provider found"); await initializeWithProvider(provider); onReady?.(); alert("Nexus initialized"); } catch (e: any) { alert(e?.message ?? "Init failed"); } }; return ( ); } ``` ### How the provider retrieval works ```ts const { connector } = useAccount(); const provider = await connector?.getProvider(); await initializeWithProvider(provider); ``` * **`useAccount()`** — wagmi hook that returns the current connection state and active connector * **`connector.getProvider()`** — returns the EIP-1193 provider for whichever wallet the user connected (MetaMask, WalletConnect, Coinbase, etc.) * **`initializeWithProvider(provider)`** — your existing helper function runs `client.initialize()` and then passes the provider to `client.setEVMProvider()` This means **any wallet RainbowKit supports** will work with the Nexus SDK — no code changes needed per wallet. ## Step 7 — Update the main page Add wallet status display using wagmi's `useAccount` hook: ```tsx title="src/app/page.tsx" "use client"; import { useState } from "react"; import { useAccount } from "wagmi"; import ConnectWalletButton from "@/components/connect-button"; import InitButton from "@/components/init-button"; import FetchBalancesButton from "@/components/fetch-balances-button"; import DeinitButton from "@/components/de-init-button"; import { client } from "@/lib/nexus"; export default function Page() { const { isConnected } = useAccount(); const [initialized, setInitialized] = useState(client.hasEvmProvider); const [balances, setBalances] = useState(null); const btn = "px-4 py-2 rounded-md bg-blue-600 text-white hover:bg-blue-700 " + "disabled:opacity-50 disabled:cursor-not-allowed"; return (
setInitialized(true)} /> setBalances(r)} /> { setInitialized(false); setBalances(null); }} />
Wallet Status:{" "} {isConnected ? "Connected" : "Not connected"}
SDK Status:{" "} {initialized ? "Initialized" : "Not initialized"}
{balances && (
            {JSON.stringify(balances, null, 2)}
          
)}
); } ``` ## Summary of changes | File | What changed | | ----------------------------------- | -------------------------------------------------------------------- | | `src/lib/wagmi.ts` | **New** — wagmi + RainbowKit config | | `src/components/providers.tsx` | **New** — provider wrapper component | | `src/app/layout.tsx` | Wrapped with `Providers` | | `src/components/connect-button.tsx` | Replaced `window.ethereum` with RainbowKit `ConnectButton.Custom` | | `src/components/init-button.tsx` | Replaced `window.ethereum` with `connector.getProvider()` from wagmi | | `src/app/page.tsx` | Added `useAccount` for wallet status display | | `src/lib/nexus.ts` | **No changes** — SDK setup is the same | ## Next step Your project now has a production-quality wallet connection experience. Next, add cross-chain bridging functionality. - [**Bridge Tokens**](/docs/nexus/nexus-sdk/v2/get-started/bridge-tokens) ## Hooks and Errors > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). The Nexus SDK ships with per-operation hooks for gating and observing an operation, and a typed `NexusError` hierarchy for handling failures. ## Hooks In v2 there are **no global hook setters** (`setOnIntentHook`, `setOnAllowanceHook`, `setOnSwapIntentHook` were removed). Hooks are passed **per operation** through the second `options` argument: ```typescript title="TypeScript" await client.bridge(params, { hooks: { onIntent: ({ intent, allow }) => allow(), onAllowance: ({ allow }) => allow(['min']), }, }); ``` > **Note** > > If you don't provide any hooks, the SDK **auto-approves**: the intent is allowed automatically and allowances default to `'min'` for every source. Where each hook lives per method: | Method | `onEvent` | `onIntent` | `onAllowance` | | ------------------------------------------ | --------- | -------------------- | ------------------- | | `bridge()` / `bridgeAndTransfer()` | top-level | `hooks.onIntent` | `hooks.onAllowance` | | `swapWithExactIn()` / `swapWithExactOut()` | top-level | `hooks.onIntent` | — | | `bridgeAndExecute()` | top-level | top-level `onIntent` | automatic (`min`) | | `swapAndExecute()` | top-level | top-level `onIntent` | — | ### onEvent Supported by every operation, `onEvent` streams progress as the operation runs. The v2 event is a typed union discriminated by `type`: ```typescript title="TypeScript" type Event = | { type: 'status'; status: string } | { type: 'plan_preview'; plan: /* plan */ } | { type: 'plan_confirmed'; plan: /* plan */ } | { type: 'plan_progress'; stepType: string; state: string; step: /* step */ }; type OnEventParam = { onEvent?: (event: TEvent) => void; }; ``` ```typescript title="TypeScript" await client.bridge(params, { onEvent: (event) => { if (event.type === 'plan_progress') { console.log('Step:', event.stepType, event.state); } }, }); ``` > **Note** > > Each method emits its own typed event union. See the [Bridge Events](/docs/nexus/nexus-sdk/v2/reference/bridge-methods/bridge-events) and [Swap Events](/docs/nexus/nexus-sdk/v2/reference/swap-methods/swap-events) pages for the full step-by-step definitions. ### onIntent Gate the intent before execution. Render the plan and fees, then call `allow()` to continue or `deny()` to abort. `refresh(selectedSources?)` re-plans (for example after the user picks different source chains) and resolves to the updated intent. #### Signature ```typescript title="TypeScript" type OnIntentHookData = { allow: () => void; deny: () => void; intent: BridgeIntent; refresh: (selectedSources?: number[]) => Promise; }; ``` `BridgeIntent` is the human-readable breakdown of `availableSources`, `selectedSources`, `destination`, and `fees`: ```typescript title="TypeScript" type BridgeIntent = { provider: 'nexus' | 'mayan'; availableSources: Array<{ amount: string; amountRaw: bigint; chain: { id: number; name: string; logo: string }; token: { decimals: number; symbol: string; logo: string; contractAddress: Hex }; value: string; }>; selectedSources: Array<{ amount: string; amountRaw: bigint; chain: { id: number; name: string; logo: string }; token: { decimals: number; symbol: string; logo: string; contractAddress: Hex }; value: string; }>; destination: { amount: string; amountRaw: bigint; chain: { id: number; name: string; logo: string }; token: { decimals: number; symbol: string; logo: string; contractAddress: Hex }; value: string; nativeAmount: string; nativeAmountRaw: bigint; nativeAmountValue: string; nativeAmountInToken: string; nativeToken: { decimals: number; symbol: string; logo: string; contractAddress: Hex }; }; fees: { caGas: string; protocol: string; solver: string; total: string; totalValue: string; }; sourcesTotal: string; sourcesTotalValue: string; }; ``` #### Example ```typescript title="TypeScript" await client.bridge(params, { hooks: { onIntent: async ({ intent, allow, deny, refresh }) => { // Render the plan and fees from `intent` if (userChangedSources) { const updated = await refresh([10, 42161]); // re-plan against Optimism + Arbitrum // re-render with `updated` } if (userApproves) allow(); else deny(); }, }, }); ``` ### onAllowance Fires when one or more sources need an ERC-20 approval before the deposit. Call `allow(values)` with one entry per source, or `deny()` to abort. #### Signature ```typescript title="TypeScript" type OnAllowanceHookData = { allow: (values: Array<'max' | 'min' | bigint | string>) => void; deny: () => void; sources: AllowanceHookSource[]; }; type AllowanceHookSource = { allowance: { current: string; // Current allowance (human-readable) currentRaw: bigint; // Current allowance (raw) minimum: string; // Minimum required (human-readable) minimumRaw: bigint; // Minimum required (raw) }; chain: { id: number; logo: string; name: string; }; holderAddress?: Hex; token: { contractAddress: `0x${string}`; decimals: number; logo: string; name: string; symbol: string; }; }; ``` * `allow(values)` — `values.length` must equal `sources.length`. Each entry is one of: * `'min'` — the minimum required allowance for that source. * `'max'` — an unlimited (`maxUint256`) allowance for that source. * a `bigint` or numeric `string` — a custom raw amount for that source. * `deny()` — stops the flow. #### Example ```typescript title="TypeScript" await client.bridge(params, { hooks: { onAllowance: ({ sources, allow, deny }) => { // `sources` has one entry per approval required if (userApproves) allow(sources.map(() => 'min')); // or 'max' / custom per source else deny(); }, }, }); ``` > **Note** > > If you don't provide `onAllowance`, the SDK approves `'min'` for every source automatically. Approval progress surfaces through `onEvent`. ### Swap onIntent `swapWithExactIn()` and `swapWithExactOut()` take `hooks.onIntent`, but the intent shape is `SwapIntent` (route, destination, and gas), and `refresh` accepts `Source[]`: ```typescript title="TypeScript" type OnIntentHookData = { allow: () => void; deny: () => void; intent: SwapIntent; refresh: (sources?: Source[]) => Promise; }; ``` ```typescript title="TypeScript" await client.swapWithExactIn(input, { hooks: { onIntent: ({ intent, allow }) => { // Render the route and destination amount from `intent` allow(); }, }, }); ``` See the full `SwapIntent` definition in the [SDK source](https://github.com/availproject/nexus-sdk/blob/main/src/swap/types.ts). ### Composite operations `bridgeAndExecute()` and `swapAndExecute()` take a **top-level** `onIntent` (not nested under `hooks`). Its `intent` describes both the requirement and any bridge/swap needed to satisfy it. ```typescript title="TypeScript" type BridgeAndExecuteOnIntentHookData = { allow: () => void; deny: () => void; intent: BridgeAndExecuteIntent; refresh: (selectedSources?: number[]) => Promise; }; type SwapAndExecuteOnIntentHookData = { allow: () => void; deny: () => void; intent: SwapAndExecuteIntent; refresh: (sources?: Source[]) => Promise; }; ``` ```typescript title="TypeScript" await client.bridgeAndExecute(params, { onIntent: ({ intent, allow, deny }) => { // `intent.bridgeRequired` tells you whether a bridge leg is needed if (userApproves) allow(); else deny(); }, }); ``` ## Error Handling Every SDK operation throws a typed `NexusError` subclass on failure rather than returning an error result. ### The NexusError hierarchy `NexusError` is the abstract base. Each concrete subclass pins a `category`: ```typescript title="TypeScript" abstract class NexusError extends Error { readonly category: ErrorCategory; // set by each subclass readonly code: ErrorCode; // namespaced string, e.g. 'validation/insufficient_balance' readonly context: ErrorContext; // { operation?, service?, stepId?, stepType?, chainId? } readonly details?: Record; toJSON(): object; // serializable { name, message, category, code, context, details } } type ErrorCategory = | 'validation' | 'user_action' | 'simulation' | 'execution' | 'backend' | 'external_service' | 'internal'; ``` | Subclass | `category` | Meaning | | ---------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------- | | `ValidationError` | `validation` | Caller input or precondition failure (bad params, unsupported token/chain, insufficient balance, SDK not initialized). | | `UserActionError` | `user_action` | User rejected a prompt (intent hook, signature, allowance approval, tx send). | | `SimulationError` | `simulation` | A pre-execution `eth_call` / simulation failed. | | `ExecutionError` | `execution` | Runtime failure at the wallet/RPC boundary (gas estimate, revert, receipt timeout). | | `BackendError` | `backend` | Avail middleware HTTP/WS failure. | | `ExternalServiceError` | `external_service` | A third-party dependency failed (LiFi, Bebop, Fibrous, 0x, Mystic, Relay, Coinbase). | | `InternalError` | `internal` | A true SDK invariant was violated. | > **Note** > > `code` holds a namespaced string of the shape `category/specific_noun`. The exported `ERROR_CODES` map gives you friendly aliases — e.g. `ERROR_CODES.INSUFFICIENT_BALANCE === 'validation/insufficient_balance'`. Branch on `error.category` (or `instanceof`) for coarse handling and on `error.code === ERROR_CODES.*` for specific cases. There is no `NexusStepError` in v2. Step-scoped failures are thrown as the applicable subclass, carrying `context.stepId`, `context.stepType`, and `context.chainId`. ### Recommended pattern ```typescript title="TypeScript" import { NexusError, UserActionError, ValidationError, ERROR_CODES, } from '@avail-project/nexus-core'; try { await client.bridge({ toTokenSymbol: 'USDC', toAmountRaw: 1_000_000n, toChainId: 137 }); } catch (error) { if (error instanceof NexusError) { console.error(`[${error.category}] ${error.code}: ${error.message}`); // Coarse branching on category if (error instanceof UserActionError) { // User cancelled a prompt — usually nothing to show return; } if (error instanceof ValidationError) { switch (error.code) { case ERROR_CODES.INSUFFICIENT_BALANCE: showInsufficientBalanceUI(); break; case ERROR_CODES.TOKEN_NOT_SUPPORTED: showUnsupportedTokenUI(); break; default: showGenericError(error.message); } } // Log the structured form for telemetry logErrorToService(error.toJSON()); } else { // Non-Nexus errors (network, library, etc.) console.error('Unexpected error:', error); } } ``` ### Error codes reference The full list lives in [`src/domain/errors.ts`](https://github.com/availproject/nexus-sdk/blob/main/src/domain/errors.ts). Representative codes by category: | `ERROR_CODES` key | `code` value | Category | | ----------------------------------- | ------------------------------------------- | ----------------- | | `INVALID_INPUT` | `validation/invalid_input` | validation | | `INSUFFICIENT_BALANCE` | `validation/insufficient_balance` | validation | | `TOKEN_NOT_SUPPORTED` | `validation/token_not_supported` | validation | | `CHAIN_NOT_FOUND` | `validation/chain_not_found` | validation | | `SDK_NOT_INITIALIZED` | `validation/sdk_not_initialized` | validation | | `WALLET_NOT_CONNECTED` | `validation/wallet_not_connected` | validation | | `VAULT_CONTRACT_NOT_FOUND` | `validation/vault_contract_not_found` | validation | | `INVALID_VALUES_ALLOWANCE_HOOK` | `validation/invalid_allowance_hook` | validation | | `USER_INTENT_HOOK_DENIED` | `user_action/intent_hook_denied` | user\_action | | `USER_INTENT_SIGNATURE_DENIED` | `user_action/intent_signature_denied` | user\_action | | `USER_ALLOWANCE_APPROVAL_DENIED` | `user_action/allowance_approval_denied` | user\_action | | `USER_TX_SEND_DENIED` | `user_action/tx_send_denied` | user\_action | | `SIMULATION_ETH_CALL_FAILED` | `simulation/eth_call_failed` | simulation | | `EXEC_GAS_ESTIMATE_FAILED` | `execution/gas_estimate_failed` | execution | | `EXEC_TX_ONCHAIN_REVERTED` | `execution/tx_onchain_reverted` | execution | | `EXEC_TX_RECEIPT_WAIT_TIMEOUT` | `execution/tx_receipt_wait_timeout` | execution | | `EXEC_SLIPPAGE_EXCEEDED` | `execution/slippage_exceeded` | execution | | `BACKEND_RFF_SUBMIT_FAILED` | `backend/rff_submit_failed` | backend | | `BACKEND_FULFILMENT_WAIT_TIMEOUT` | `backend/fulfilment_wait_timeout` | backend | | `BACKEND_FEE_GRANT_REQUESTED` | `backend/fee_grant_requested` | backend | | `EXTERNAL_SOURCE_SWAP_QUOTE_FAILED` | `external_service/source_swap_quote_failed` | external\_service | | `EXTERNAL_RATES_DRIFT_EXCEEDED` | `external_service/rates_drift_exceeded` | external\_service | | `INTERNAL_ERROR` | `internal/error` | internal | > **Note** > > See the [full error definitions](https://github.com/availproject/nexus-sdk/blob/main/src/domain/errors.ts) on GitHub for the exhaustive `ERROR_CODES` map, the named `Errors.*` factories, and every `code`. ## List Intents > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). > **Note** > > **SET UP THE SDK BEFORE YOU START:** > > Install and initialize the client first — see [Installation](/docs/nexus/nexus-sdk/v2/get-started/installation) and [SDK Setup](/docs/nexus/nexus-sdk/v2/get-started/nexus-sdk-setup). Use the `listIntents()` function to fetch a user's historical intents, with pagination and an optional status filter. > **Note** > > `listIntents()` replaces the v1 `getMyIntents(page)` method. It returns a structured `{ intents, total }` result instead of a bare array, and accepts an optional `status` filter. ## Method signature ```typescript title="TypeScript" listIntents( params?: ListIntentsParams, ): Promise ``` ## Parameters ```typescript title="TypeScript" export type ListIntentsParams = { page?: number; status?: IntentStatus; }; export type IntentStatus = 'created' | 'deposited' | 'fulfilled' | 'expired'; ``` * `ListIntentsParams` (optional): Filters for the intent query. * `page` (number, optional): The page number for pagination. Page size is fixed at 20 records — paginate by incrementing `page`. * `status` (`IntentStatus`, optional): Restrict results to a single lifecycle status — `'created'`, `'deposited'`, `'fulfilled'`, or `'expired'`. > **Note** > > `IntentStatus` is exported both as a value map (`IntentStatus.Fulfilled`) and as a type. Use the value map to avoid hardcoding the string literals. ## Example ```typescript title="TypeScript" import { IntentStatus, type ListIntentsResult } from '@avail-project/nexus-core'; // First page of all intents const { intents, total }: ListIntentsResult = await client.listIntents(); console.log(`Showing ${intents.length} of ${total} intents`); // Second page, filtered to fulfilled intents const fulfilled = await client.listIntents({ page: 2, status: IntentStatus.Fulfilled, }); for (const intent of fulfilled.intents) { console.log(intent.requestHash, intent.status, intent.explorerUrl); } ``` ## Return Value The return value is a `ListIntentsResult` object. ```typescript title="TypeScript" export type ListIntentsResult = { intents: IntentRecord[]; total: number; }; export type IntentRecord = { requestHash: Hex; explorerUrl: string; status: IntentStatus; solver: Hex | null; createdAt?: number; updatedAt?: number; expiry: number; recipientAddress: Hex; destinationChain: { id: number; name: string; logo: string; universe: Universe; }; destinations: Array<{ token: { contractAddress: Hex; symbol: string; name: string; logo: string; decimals: number; }; amount: string; amountRaw: bigint; }>; sources: Array<{ chain: { id: number; name: string; logo: string; universe: Universe; }; amountRaw: bigint; amount: string; feeRaw: bigint; fee: string; token: { contractAddress: Hex; symbol: string; name: string; logo: string; decimals: number; }; }>; }; ``` > **Note** > > See the [full type definitions](https://github.com/availproject/nexus-sdk/blob/main/src/domain/types/index.ts) on GitHub. ## Utility Reference > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). The Nexus SDK is created with `createNexusClient` and exposes a headless client plus a set of stateless helpers. This page covers client configuration, lifecycle, chain/token lookups, and the utility exports. ## Creating a client ```typescript title="TypeScript" import { createNexusClient } from '@avail-project/nexus-core'; const client = createNexusClient({ network: 'mainnet', }); await client.initialize(); ``` ### Configuration ```typescript title="TypeScript" createNexusClient(config?: { network?: 'mainnet' | 'testnet' | NetworkConfig; debug?: boolean; analytics?: AnalyticsConfig; devTiming?: DevTimingConfig; domain?: string; forceMayan?: boolean; }): NexusClient ``` | Option | Type | Description | | ------------ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------- | | `network` | `'mainnet' \| 'testnet' \| NetworkConfig` | Target network. Defaults to `'mainnet'`. Pass a `NetworkConfig` object for a fully custom deployment. | | `debug` | `boolean` | Enable verbose debug logging. | | `analytics` | `AnalyticsConfig` | Analytics configuration (see [Analytics](#analytics)). | | `devTiming` | `DevTimingConfig` | Developer timing instrumentation for spans. | | `domain` | `string` | Override the auto-detected domain used in the ephemeral-key sign message. | | `forceMayan` | `boolean` | Force routing through Mayan where available. | ### Custom network config ```typescript title="TypeScript" type NetworkConfig = { MIDDLEWARE_HTTP_URL: string; MIDDLEWARE_WS_URL: string; INTENT_EXPLORER_URL: string; NETWORK_HINT: 'mainnet' | 'testnet'; }; const client = createNexusClient({ network: { MIDDLEWARE_HTTP_URL: 'https://your-deployment.example.com/middleware/', MIDDLEWARE_WS_URL: 'wss://your-deployment.example.com/middleware/', INTENT_EXPLORER_URL: 'https://your-deployment.example.com/', NETWORK_HINT: 'mainnet', }, }); ``` ## Lifecycle #### `initialize()` Initialize the client. Call once before invoking any operation; it can run before or after `setEVMProvider()`. ```typescript title="TypeScript" await client.initialize(); ``` #### `setEVMProvider(provider)` Attach or replace the EIP-1193 EVM provider used to sign and send transactions. ```typescript title="TypeScript" await client.setEVMProvider(window.ethereum); ``` | Parameter | Type | Description | | ---------- | ------------------ | ------------------------------------------------------------- | | `provider` | `EthereumProvider` | EIP-1193 compatible provider (MetaMask, WalletConnect, etc.). | #### `hasEvmProvider` Boolean getter — `true` once an EVM provider has been attached. ```typescript title="TypeScript" if (client.hasEvmProvider) { // Safe to run operations that need a wallet } ``` #### `destroy()` Tear down the client and release resources (analytics, sockets). Call when unmounting your app. ```typescript title="TypeScript" client.destroy(); ``` ## Chain and token helpers #### `getSupportedChains()` Return the chains and tokens supported by the connected deployment. ```typescript title="TypeScript" const supported = client.getSupportedChains(); // SupportedChainsAndTokensResult: Array<{ id, name, logo, swapSupported, tokens }> ``` #### `isSupportedChain(chainId)` ```typescript title="TypeScript" const ok = client.isSupportedChain(137); // boolean ``` #### `convertTokenReadableAmountToBigInt(amount, tokenSymbol, chainId)` Convert a human-readable amount into raw integer units using the token's on-chain decimals. ```typescript title="TypeScript" const amountRaw = client.convertTokenReadableAmountToBigInt('83.5', 'USDC', 137); // 83_500_000n ``` ### `client.chainList` `client.chainList` exposes synchronous lookups over the resolved deployment. ```typescript title="TypeScript" client.chainList.getChainByID(chainID: number): Chain; client.chainList.getTokenInfoBySymbol(chainID: number, symbol: string): TokenInfo; client.chainList.getNativeToken(chainID: number): TokenInfo; client.chainList.getVaultContractAddress(chainID: number): `0x${string}`; client.chainList.getTokenByAddress(chainID: number, address: `0x${string}`): TokenInfo; client.chainList.getTokenByCurrencyId(chainID: number, currencyId: number): TokenInfo; client.chainList.getChainAndTokenFromSymbol( chainID: number, tokenSymbol: string, ): { chain: Chain; token: TokenInfo; isNativeToken: boolean }; client.chainList.getChainAndTokenByAddress( chainID: number, address: `0x${string}`, ): { chain: Chain; token: TokenInfo; isNativeToken: boolean }; ``` ```typescript title="TypeScript" const polygon = client.chainList.getChainByID(137); const usdc = client.chainList.getTokenInfoBySymbol(137, 'USDC'); const vault = client.chainList.getVaultContractAddress(137); ``` ## Max-amount helpers #### `calculateMaxForBridge(params)` Compute the maximum amount that can be bridged for a token to a destination chain. `maxAmountRaw` can be passed straight into `bridge()` as `toAmountRaw`. ```typescript title="TypeScript" const max = await client.calculateMaxForBridge({ toChainId: 137, toTokenSymbol: 'USDC', }); type BridgeMaxResult = { toChainId: number; toTokenSymbol: string; provider: BridgeProvider; maxAmount: string; // human decimal string maxAmountRaw: bigint; // raw integer units symbol: string; decimals: number; sources: Array<{ chainId: number; tokenAddress: Hex; symbol: string; decimals: number; amount: string; // human decimal drawn from this source }>; }; ``` #### `calculateMaxForSwap(params)` Compute the maximum input available for a swap into a destination token. See the [Swap Methods](/docs/nexus/nexus-sdk/v2/reference/swap-methods/fetch-swap-balances) pages for the swap surface. ```typescript title="TypeScript" const max = await client.calculateMaxForSwap({ toChainId: 8453, toTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', }); type SwapMaxResult = { toChainId: number; toTokenAddress: Hex; maxAmount: string; // human decimal string maxAmountRaw: bigint; // raw integer units symbol: string; decimals: number; sources: Array<{ chainId: number; tokenAddress: Hex; symbol: string; decimals: number; amount: string; }>; }; ``` ## Stateless utilities Formatting and address helpers are importable directly from the `/utils` subpath, and are also available on `client.utils`. ```typescript title="TypeScript" import { formatTokenBalance, formatTokenBalanceParts, formatUnits, parseUnits, truncateAddress, isValidAddress, getCoinbaseRates, getSupportedChains, } from '@avail-project/nexus-core/utils'; ``` ### Formatting ```typescript title="TypeScript" // Format a raw balance for display const formatted = formatTokenBalance(1234567890n, { decimals: 6, symbol: 'USDC' }); // Get the parts separately const parts = formatTokenBalanceParts(1234567890n, { decimals: 6 }); // Convert between raw and human units const raw = parseUnits('1.5', 18); // 1500000000000000000n const human = formatUnits(raw, 18); // "1.5" ``` ### Addresses ```typescript title="TypeScript" const valid = isValidAddress('0x742d35Cc6634C0532925a3b8D4C9db96c4b4Db45'); // boolean const short = truncateAddress('0x742d35Cc6634C0532925a3b8D4C9db96c4b4Db45'); // "0x742d...Db45" ``` ### Token logo fallback `getFallbackTokenLogoDataUri` generates a deterministic gradient SVG logo as a data URI — useful as an `onError` fallback for token images. Note it is exported from the main entry point, not `/utils`: ```typescript title="TypeScript" import { getFallbackTokenLogoDataUri } from '@avail-project/nexus-core'; const logo = getFallbackTokenLogoDataUri('USDC'); // "data:image/svg+xml;charset=utf-8,..." const large = getFallbackTokenLogoDataUri('USDC', 256); // optional size, defaults to 128 ``` ### Prices and supported chains ```typescript title="TypeScript" // Current token prices from Coinbase const rates = await getCoinbaseRates(); // Supported chains and tokens for a network (async, standalone helper) const supported = await getSupportedChains('mainnet'); ``` ### Via `client.utils` The same helpers are attached to the client as `NexusUtils`: ```typescript title="TypeScript" const rates = await client.utils.getCoinbaseRates(); const supported = await client.utils.getSupportedChains('mainnet'); const formatted = client.utils.formatTokenBalance(1234567890n, { decimals: 6, symbol: 'USDC' }); ``` > **Note** > > `client.getSupportedChains()` (the client method) is synchronous and returns the currently connected deployment's chains. The standalone `getSupportedChains(network)` helper is asynchronous and fetches the deployment for the given network hint. ## Analytics The Nexus SDK includes built-in analytics powered by PostHog. Analytics are **enabled by default** and send anonymous telemetry to Avail's PostHog instance; they can be customized or disabled through the `analytics` config. ### Disabling analytics ```typescript title="TypeScript" const client = createNexusClient({ network: 'mainnet', analytics: { enabled: false }, }); ``` ### Privacy controls ```typescript title="TypeScript" const client = createNexusClient({ network: 'mainnet', analytics: { enabled: true, privacy: { anonymizeWallets: true, // Hash wallet addresses anonymizeAmounts: true, // Exclude transaction amounts }, }, }); ``` ### Custom analytics (BYO PostHog) ```typescript title="TypeScript" const client = createNexusClient({ network: 'mainnet', analytics: { enabled: true, posthogApiKey: 'your-posthog-key', posthogApiHost: 'https://your-posthog-instance.com', appMetadata: { appName: 'My DApp', appVersion: '1.0.0', appUrl: 'https://mydapp.com', }, }, }); ``` ### Accessing analytics programmatically ```typescript title="TypeScript" if (client.analytics.isEnabled()) { client.analytics.track('custom_event', { foo: 'bar' }); } ``` ## Common pitfalls * Operation amounts are raw integer units (`bigint`), not human-readable decimal strings. Use `convertTokenReadableAmountToBigInt()` or `parseUnits()` to convert. * Bridge methods (`bridge`, `bridgeAndTransfer`, `bridgeAndExecute`) identify tokens by **symbol**; swap methods identify tokens by **contract address**. * Call `initialize()` once before running operations. It fetches deployment data and does not require a provider, so it can run before or after `setEVMProvider()`. * Call `destroy()` on teardown to flush analytics and close sockets. ## Skills Integration [skills.sh](https://skills.sh) skills give AI coding agents (Codex, Claude Code, Cursor, etc.) deep context about the Nexus SDK. ```bash # Install the SDK skill npx skills add availproject/nexus-sdk ``` * `nexus-core` — Full v2 integration guide: client lifecycle, operations, hooks, events, error handling, utils, and v1 → v2 migration Browse it at [skills.sh/availproject/nexus-sdk](https://skills.sh/availproject/nexus-sdk). For Widgets skills, see [MCP & Agent Skills](/docs/nexus/widgets/mcp-and-agent-skills). ## Bridge and Execute > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). > **Note** > > **SET UP THE SDK BEFORE YOU START:** > > Install and initialize the client first — see [Installation](/docs/nexus/nexus-sdk/v2/get-started/installation) and [SDK Setup](/docs/nexus/nexus-sdk/v2/get-started/nexus-sdk-setup). Use the `bridgeAndExecute()` function to bridge a token and execute a contract function on the recipient chain in a single flow. Use the `simulateBridgeAndExecute()` function to simulate `bridgeAndExecute()` before sending an actual transaction. > **Note** > > **Note:** Check out the [API reference](/docs/nexus/supported-chains-and-tokens) for a full list of supported tokens and chains. ## Method signature ```typescript title="TypeScript" bridgeAndExecute( params: BridgeAndExecuteParams, options?: BridgeAndExecuteOptions, ): Promise simulateBridgeAndExecute( params: BridgeAndExecuteParams, ): Promise ``` ## Parameters ```typescript title="TypeScript" export interface BridgeAndExecuteParams { toChainId: number; toTokenSymbol: string; toAmountRaw: bigint; sources?: number[]; execute: Omit; enableTransactionPolling?: boolean; transactionTimeout?: number; waitForReceipt?: boolean; receiptTimeout?: number; requiredConfirmations?: number; recentApprovalTxHash?: string; } export interface ExecuteParams { toChainId: number; to: Hex; value?: bigint; data?: Hex; gas?: bigint; gasPrice?: 'low' | 'medium' | 'high'; enableTransactionPolling?: boolean; transactionTimeout?: number; // Transaction receipt confirmation options waitForReceipt?: boolean; receiptTimeout?: number; requiredConfirmations?: number; tokenApproval?: { toTokenSymbol: string; amount: bigint; spender: Hex; }; } ``` * `BridgeAndExecuteParams`: Parameters for using the `bridgeAndExecute()` function. * `toChainId` (number, required): The chain ID of the destination chain. * `toTokenSymbol` (string, required): The symbol of the token to be bridged in this flow (e.g. `'USDC'`). * `toAmountRaw` (bigint, required): The amount of tokens to be used in this flow, in raw integer units. * `sources` (number\[], optional): The chain IDs of the source chains to be used for the bridge. Useful if you want to maintain your holdings on some chains. * `execute` (`Omit`, required): The contract call on the destination chain. `toChainId` is inherited from the top-level `toChainId`. * `enableTransactionPolling` (boolean, optional): Whether to enable transaction polling. Defaults to `false`. * `transactionTimeout` (number, optional) * `waitForReceipt` (boolean, optional) * `receiptTimeout` (number, optional) * `requiredConfirmations` (number, optional) * `recentApprovalTxHash` (string, optional) * `options`: `BridgeAndExecuteOptions` (optional): Top-level intent hook, a `beforeExecute` hook, event listener, and fill timeout. ```typescript title="TypeScript" export type BridgeAndExecuteOptions = { onIntent?: (data: BridgeAndExecuteOnIntentHookData) => void; beforeExecute?: () => Promise<{ value?: bigint; data?: Hex; gas?: bigint }>; onEvent?: (event: BridgeAndExecuteEvent) => void; fillTimeoutMinutes?: number; }; ``` * `onIntent` (optional): Called at the **top level** (not under `hooks`) with the composite intent before execution. Call `data.allow()` / `data.deny()`, or `data.refresh(sources?)` to re-quote. Read `data.intent.bridgeRequired` to detect whether a bridge is needed. When omitted, the intent is auto-approved. * `beforeExecute` (optional): Called just before the execute transaction is sent; return overrides for `value` / `data` / `gas` (for example, to inject freshly quoted calldata). * `onEvent` (optional): Callback that receives the typed `BridgeAndExecuteEvent` union as the operation progresses. * `fillTimeoutMinutes` (optional): How long to wait for the bridge to be filled before timing out. Defaults to `2`. > **Note** > > The `onEvent` callback receives a typed discriminated union. In addition to the bridge step types, `bridgeAndExecute` emits `execute_approval` and `execute_transaction` progress steps. See the [Bridge Events](/docs/nexus/nexus-sdk/v2/reference/bridge-methods/bridge-events) page for the full event shapes. ## Example ```typescript title="TypeScript" import type { BridgeAndExecuteParams, BridgeAndExecuteResult, } from '@avail-project/nexus-core'; // Bridge and execute const bridgeAndExecuteResult: BridgeAndExecuteResult = await client.bridgeAndExecute( { toTokenSymbol: 'USDC', toAmountRaw: 100_000_000n, // 100 USDC (6 decimals) toChainId: 1, sources: [8453], execute: { to: '0x...', data: '0x...', tokenApproval: { toTokenSymbol: 'USDC', amount: 100_000_000n, // 100 USDC (6 decimals) spender: '0x...', }, }, }, { onIntent: (data) => data.allow(), onEvent: (event) => { if (event.type === 'plan_preview') console.log('Bridge+Execute steps:', event.plan.steps); if (event.type === 'plan_progress') console.log('Step progress:', event.stepType, event.state); }, }, ); console.log('Bridge and execute result:', bridgeAndExecuteResult); const bridgeAndExecuteSimulation = await client.simulateBridgeAndExecute({ toTokenSymbol: 'USDC', toAmountRaw: 100_000_000n, // 100 USDC (6 decimals) toChainId: 1, sources: [8453], execute: { to: '0x...', data: '0x...', // tokenApproval optional for simulation }, }); console.log('Bridge and execute simulation:', bridgeAndExecuteSimulation); ``` > **Warning** > > `bridgeAndExecute()` throws typed [`NexusError`](https://github.com/availproject/nexus-sdk/blob/main/src/domain/errors.ts) subclasses on failure. Branch on `error.category` / `error.code`. ## Return Value The return value is a `BridgeAndExecuteResult` object — a discriminated union on `bridgeSkipped`. When the destination already holds enough funds, the bridge is skipped and `bridgeResult` is absent. ```typescript title="TypeScript" export type TxResult = { txHash: Hex; txExplorerUrl: string; receipt?: TransactionReceipt; }; export type BridgeAndExecuteResult = { approval?: TxResult; // present when an ERC-20 approval was needed for execute execute: TxResult; } & ( | { bridgeSkipped: false; bridgeResult: BridgeResult } | { bridgeSkipped: true; bridgeResult?: undefined } ); export type BridgeAndExecuteSimulationResult = { bridgeSimulation: BridgeSimulationResult | null; // null if bridging is skipped executeSimulation: ExecuteSimulation; }; export type ExecuteFeeParams = | { type: 'eip1559'; maxFeePerGas: bigint; maxPriorityFeePerGas: bigint } | { type: 'legacy'; gasPrice: bigint }; export type ExecuteSimulation = { feeParams: ExecuteFeeParams; estimatedGasUnits: bigint; // combined across approval (if any) and execute estimatedTotalCost: bigint; // combined estimated cost }; ``` > **Note** > > Detect a skipped bridge via `result.bridgeSkipped` (there is no `*_SKIPPED` event). See the [full type definitions](https://github.com/availproject/nexus-sdk/blob/main/src/domain/types/index.ts) on GitHub. ## Bridge Tokens > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). > **Note** > > **SET UP THE SDK BEFORE YOU START:** > > Install and initialize the client first — see [Installation](/docs/nexus/nexus-sdk/v2/get-started/installation) and [SDK Setup](/docs/nexus/nexus-sdk/v2/get-started/nexus-sdk-setup). Use the `bridge()` function to bridge a specific token from one (*or many*) chains to a single chain. Use the `simulateBridge()` function to simulate the bridge transaction to preview the costs and fees, before actually executing the transaction. > **Note** > > **Note:** Check out the [API reference](/docs/nexus/supported-chains-and-tokens) for a full list of supported tokens and chains. ## Method signature ```typescript title="TypeScript" bridge( params: BridgeParams, options?: BridgeOperationOptions, ): Promise simulateBridge( params: BridgeParams, ): Promise ``` ## Parameters ```typescript title="TypeScript" /** * Parameters for bridging tokens. */ export interface BridgeParams { recipient?: Hex; toTokenSymbol: string; toAmountRaw: bigint; toChainId: number; toNativeAmountRaw?: bigint; sources?: number[]; } ``` * `BridgeParams`: Parameters for bridging tokens. * `recipient` (Hex, optional): The recipient address. Defaults to the address of the connected user. * `toTokenSymbol` (string, required): The symbol of the token to be bridged (e.g. `'USDC'`). * `toAmountRaw` (bigint, required): The amount of tokens to receive on the destination chain, in raw integer units. * `toChainId` (number, required): The chain ID of the destination chain. * `toNativeAmountRaw` (bigint, optional): Amount of destination-chain native gas to also deliver to the recipient, in raw integer units. * `sources` (number\[], optional): The chain IDs of the source chains to be used for the bridge. Useful if you want to maintain your holdings on some chains. * `options`: `BridgeOperationOptions` (optional): Per-operation hooks, event listener, and fill timeout. ```typescript title="TypeScript" export type BridgeOperationOptions = { hooks?: { onIntent?: (data: OnIntentHookData) => void; onAllowance?: (data: OnAllowanceHookData) => void; }; onEvent?: (event: BridgeEvent) => void; fillTimeoutMinutes?: number; }; ``` * `hooks.onIntent` (optional): Called with the resolved intent before execution. Call `data.allow()` / `data.deny()`, or `data.refresh(sources?)` to re-quote. When omitted, the intent is auto-approved. * `hooks.onAllowance` (optional): Called when ERC-20 allowances are required. Call `data.allow([...])` with a strategy per source. When omitted, allowances default to `'min'`. * `onEvent` (optional): Callback that receives the typed `BridgeEvent` union as the operation progresses. Use it to render progress to the user. * `fillTimeoutMinutes` (optional): How long to wait for the bridge to be filled before timing out. Defaults to `2`. > **Note** > > The `onEvent` callback receives a typed discriminated union (`status` / `plan_preview` / `plan_confirmed` / `plan_progress`). See the [Bridge Events](/docs/nexus/nexus-sdk/v2/reference/bridge-methods/bridge-events) page for the full event shapes. ## Example ```typescript title="TypeScript" import type { BridgeParams, BridgeResult } from '@avail-project/nexus-core'; const bridgeResult: BridgeResult = await client.bridge( { toTokenSymbol: 'USDC', toAmountRaw: 83_500_000n, // 83.5 USDC (6 decimals) toChainId: 137, // Polygon } satisfies BridgeParams, { onEvent: (event) => { switch (event.type) { case 'plan_preview': // Ordered list of steps about to run — render them if you wish console.log('Steps:', event.plan.steps); break; case 'plan_progress': // Granular per-step progress if (event.stepType === 'request_submission' && event.state === 'completed') { console.log('Explorer:', event.explorerUrl); } break; } }, }, ); console.log('Bridge result:', bridgeResult); // Simulate bridge to preview costs. // You can use the convertTokenReadableAmountToBigInt helper to build the raw amount. const toAmountRaw = client.convertTokenReadableAmountToBigInt('83.5', 'USDC', 137); const bridgeSimulation = await client.simulateBridge({ toTokenSymbol: 'USDC', toAmountRaw, toChainId: 137, }); console.log('Bridge simulation:', bridgeSimulation); ``` > **Warning** > > `bridge()` and `simulateBridge()` throw typed [`NexusError`](https://github.com/availproject/nexus-sdk/blob/main/src/domain/errors.ts) subclasses on failure (e.g. `UserActionError` on a wallet rejection, `ValidationError` on bad input). Branch on `error.category` / `error.code`. ## Return Value ### `bridge()` The return value is a `BridgeResult` object. ```typescript title="TypeScript" /** * Result structure for bridge transactions. */ export type BridgeResult = { intentExplorerUrl: string; sourceTxs: SourceTxs; // array of per-source deposit transactions intent: BridgeIntent; }; export type TxResult = { txHash: Hex; txExplorerUrl: string; receipt?: TransactionReceipt; }; export type SourceTxs = (TxResult & { chain: { id: number; name: string; logo: string }; })[]; ``` ### `simulateBridge()` The return value is a `BridgeSimulationResult` object. ```typescript title="TypeScript" export interface BridgeSimulationResult { intent: BridgeIntent; token: TokenInfo; } export type BridgeIntent = { provider: 'nexus' | 'mayan'; availableSources: { amount: string; amountRaw: bigint; chain: { id: number; name: string; logo: string }; token: { decimals: number; symbol: string; logo: string; contractAddress: Hex }; value: string; }[]; destination: { amount: string; amountRaw: bigint; chain: { id: number; name: string; logo: string }; token: { decimals: number; symbol: string; logo: string; contractAddress: Hex }; value: string; nativeAmount: string; nativeAmountRaw: bigint; nativeAmountValue: string; nativeAmountInToken: string; nativeToken: { decimals: number; symbol: string; logo: string; contractAddress: Hex }; }; fees: { caGas: string; protocol: string; solver: string; total: string; totalValue: string; }; selectedSources: { amount: string; amountRaw: bigint; chain: { id: number; name: string; logo: string }; token: { decimals: number; symbol: string; logo: string; contractAddress: Hex }; value: string; }[]; sourcesTotal: string; sourcesTotalValue: string; }; type TokenInfo = { contractAddress: `0x${string}`; decimals: number; logo: string; name: string; symbol: string; }; ``` > **Note** > > `BridgeIntent` replaces the [v1](https://github.com/availproject/nexus-sdk/blob/main/MIGRATION.md) `ReadableIntent` type: `sources` → `selectedSources`, `allSources` → `availableSources`, and chain fields are nested under `chain: { id, name, logo }`. See the [full type definitions](https://github.com/availproject/nexus-sdk/blob/main/src/domain/types/index.ts) on GitHub. ## Bridge Events > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). This page documents the events emitted through the `onEvent` callback by the following methods: * `bridge()` * `bridgeAndTransfer()` * `bridgeAndExecute()` In v2 the flat `{ name, args }` event union is replaced by a typed discriminated union keyed on `type`. Every event is one of four kinds: | `type` | Meaning | | ---------------- | ---------------------------------------------------------------------------- | | `status` | High-level lifecycle status of the operation. | | `plan_preview` | The ordered list of steps about to run, emitted once the intent is resolved. | | `plan_confirmed` | The same plan after the user approves it. | | `plan_progress` | Granular per-step progress, keyed on `stepType` + `state`. | > **Note** > > * `bridgeAndTransfer()` and `bridgeAndExecute()` emit the bridge steps plus the steps for the destination-chain operation. > * `bridge()` emits only the bridge steps. > * When `bridgeAndTransfer()` or `bridgeAndExecute()` do not need to bridge (the destination already holds enough funds), only the destination-side steps are emitted. Detect a skipped bridge via `result.bridgeSkipped`, not via an event. ## Event union ```typescript title="TypeScript" export type BridgeEvent = | BridgeStatusEvent | BridgePlanPreviewEvent | BridgePlanConfirmedEvent | BridgePlanProgressEvent; export type OnEventParam = { onEvent?: (event: TEvent) => void; }; ``` ## Status events ```typescript title="TypeScript" export type BridgeStatus = | 'intent_building' | 'intent_ready' | 'awaiting_approval' | 'awaiting_allowance_selection' | 'approved' | 'executing' | 'completed'; export type BridgeStatusEvent = { type: 'status'; status: BridgeStatus; }; ``` > **Note** > > `bridgeAndExecute()` (and `bridgeAndTransfer()`) additionally emit a `'preparing'` status at the start — its status type is `BridgeAndExecuteStatus`, which is `BridgeStatus` plus `'preparing'`. ## Plan preview / confirmed events Both carry the `BridgePlan` — an ordered list of the steps that will run. ```typescript title="TypeScript" export type BridgePlanPreviewEvent = { type: 'plan_preview'; plan: BridgePlan }; export type BridgePlanConfirmedEvent = { type: 'plan_confirmed'; plan: BridgePlan }; export type BridgePlan = { steps: BridgePlanStep[]; }; export type BridgePlanStep = | BridgeAllowanceApprovalStep | BridgeRequestSigningStep | BridgeRequestSubmissionStep | BridgeVaultDepositStep | BridgeFillStep; ``` For `bridgeAndExecute()`, the plan is a `BridgeAndExecutePlan`, which adds a `bridgeRequired` flag and the execute step types: ```typescript title="TypeScript" export type BridgeAndExecutePlan = { bridgeRequired: boolean; steps: BridgeAndExecutePlanStep[]; // BridgePlanStep | ExecutePlanStep }; export type ExecutePlanStep = ExecuteApprovalStep | ExecuteTransactionStep; ``` ## Progress events Each `plan_progress` event carries a `stepType` discriminant and a `state`. The tables below list the states per step type, and the extra fields each progress event carries. ### Bridge step types | `stepType` | `state` values | Extra fields | | -------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `allowance_approval` | `wallet_prompted`, `submitted`, `confirmed`, `failed` | `step`, `approvedAmount`, `approvedAmountRaw`; `txHash` + `explorerUrl` on `submitted`/`confirmed`; `error` on `failed` | | `request_signing` | `wallet_prompted`, `completed`, `failed` | `step`; `intentRequestHash` on `completed`; `error` on `failed` | | `request_submission` | `started`, `completed`, `failed` | `step`, `intentRequestHash`; `explorerUrl` on `completed`; `error` on `failed` | | `vault_deposit` | `started`, `wallet_prompted`, `submitted`, `confirmed`, `completed`, `failed` | `step`; `txHash` + `explorerUrl` on `submitted`/`confirmed` (optional on `failed`); `error` on `failed` | | `bridge_fill` | `waiting`, `completed`, `failed` | `step`, `intentRequestHash`; `error` on `failed` | ### Execute step types Emitted by `bridgeAndExecute()` (and `bridgeAndTransfer()`) for the destination-chain call. | `stepType` | `state` values | Extra fields | | --------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `execute_approval` | `wallet_prompted`, `submitted`, `confirmed`, `failed` | `step`; `txHash` + `explorerUrl` on `submitted`/`confirmed` (optional on `failed`); `error` on `failed` | | `execute_transaction` | `wallet_prompted`, `submitted`, `confirmed`, `failed` | `step`, `value`, `hasData`; `txHash` + `explorerUrl` on `submitted`/`confirmed` (optional on `failed`); `error` on `failed` | ### Progress event shapes ```typescript title="TypeScript" export type BridgePlanProgressEvent = | BridgeAllowanceApprovalProgressEvent | BridgeRequestSigningProgressEvent | BridgeRequestSubmissionProgressEvent | BridgeVaultDepositProgressEvent | BridgeFillProgressEvent; // Example: allowance_approval progress export type BridgeAllowanceApprovalProgressEvent = | { type: 'plan_progress'; stepType: 'allowance_approval'; state: 'wallet_prompted'; step: BridgeAllowanceApprovalStep; approvedAmount: string; approvedAmountRaw: string; } | { type: 'plan_progress'; stepType: 'allowance_approval'; state: 'submitted' | 'confirmed'; step: BridgeAllowanceApprovalStep; approvedAmount: string; approvedAmountRaw: string; txHash: Hex; explorerUrl: string; } | { type: 'plan_progress'; stepType: 'allowance_approval'; state: 'failed'; step: BridgeAllowanceApprovalStep; approvedAmount: string; approvedAmountRaw: string; error: string; }; ``` > **Note** > > You can find the full type definitions for every step and progress event on GitHub: } title="Bridge event types" href="https://github.com/availproject/nexus-sdk/blob/main/src/domain/types/bridge-events.ts" target="_blank" rel="noopener noreferrer" arrow /> ## Usage Example ```typescript title="TypeScript" client.bridge(params, { onEvent: (event) => { switch (event.type) { case 'status': // event.status — 'intent_building' | 'intent_ready' | ... | 'completed' break; case 'plan_preview': // event.plan.steps — ordered list of steps about to run break; case 'plan_confirmed': // event.plan.steps — after approval break; case 'plan_progress': // event.stepType + event.state — granular progress if (event.stepType === 'bridge_fill' && event.state === 'completed') { // bridge filled on the destination chain } break; } }, }); ``` ## Calculate Max for Bridge > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). > **Note** > > **SET UP THE SDK BEFORE YOU START:** > > Install and initialize the client first — see [Installation](/docs/nexus/nexus-sdk/v2/get-started/installation) and [SDK Setup](/docs/nexus/nexus-sdk/v2/get-started/nexus-sdk-setup). Use the `calculateMaxForBridge()` function to compute the maximum amount that can be bridged to a destination token across all same-currency holdings on other chains. This is useful for populating a "Max" button before calling [`bridge()`](/docs/nexus/nexus-sdk/v2/reference/bridge-methods/bridge). The max is sized against the provider the bridge will actually use: the summed bridge amount is checked against the Mayan threshold (the same decision the real bridge makes), and the receivable max is computed for that provider. A small safety haircut is applied so the suggested amount survives fee drift before execution. The returned `provider` tells you which path was used. > **Note** > > **Note:** Check out the [API reference](/docs/nexus/supported-chains-and-tokens) for a full list of supported tokens and chains. ## Method signature ```typescript title="TypeScript" calculateMaxForBridge( input: BridgeMaxParams, ): Promise ``` ## Parameters ```typescript title="TypeScript" export type BridgeMaxParams = { toChainId: number; toTokenSymbol: string; /** Restrict which source chains are considered (chain IDs). Empty/omitted = all. */ sources?: number[]; }; ``` * `BridgeMaxParams`: Parameters for computing the max bridgeable amount. * `toChainId` (number, required): The chain ID of the destination chain. * `toTokenSymbol` (string, required): The symbol of the destination token (e.g. `'USDC'`). * `sources` (number\[], optional): Restrict which source chain IDs are considered. When omitted, all same-currency holdings are used. ## Example ```typescript title="TypeScript" import type { BridgeMaxParams, BridgeMaxResult } from '@avail-project/nexus-core'; const max: BridgeMaxResult = await client.calculateMaxForBridge({ toChainId: 8453, toTokenSymbol: 'USDC', } satisfies BridgeMaxParams); console.log(`Max bridgeable: ${max.maxAmount} ${max.symbol} via ${max.provider}`); console.log('Sources used:', max.sources); // Feed the raw amount straight into a bridge: await client.bridge({ toChainId: 8453, toTokenSymbol: 'USDC', toAmountRaw: max.maxAmountRaw, }); ``` You can also restrict which source chains are considered: ```typescript title="TypeScript" const max = await client.calculateMaxForBridge({ toChainId: 8453, toTokenSymbol: 'USDC', sources: [10, 42161], // only Optimism + Arbitrum balances }); ``` > **Warning** > > `calculateMaxForBridge()` throws typed [`NexusError`](https://github.com/availproject/nexus-sdk/blob/main/src/domain/errors.ts) subclasses on failure. Branch on `error.category` / `error.code`. ## Return Value The return value is a `BridgeMaxResult` object. ```typescript title="TypeScript" export type BridgeMaxResult = { toChainId: number; toTokenSymbol: string; /** Provider the max was sized against ('mayan' once the amount clears the threshold). */ provider: 'nexus' | 'mayan'; maxAmount: string; // Human-readable decimal string maxAmountRaw: bigint; // Raw integer units — suitable for toAmountRaw in bridge() symbol: string; decimals: number; sources: { chainId: number; tokenAddress: Hex; symbol: string; decimals: number; amount: string; // Human-readable portion drawn from this source }[]; }; ``` > **Note** > > See the [full type definitions](https://github.com/availproject/nexus-sdk/blob/main/src/bridge/types.ts) on GitHub. ## Calculate Max for Swap > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). > **Note** > > **SET UP THE SDK BEFORE YOU START:** > > Install and initialize the client first — see [Installation](/docs/nexus/nexus-sdk/v2/get-started/installation) and [SDK Setup](/docs/nexus/nexus-sdk/v2/get-started/nexus-sdk-setup). Use the `calculateMaxForSwap()` function to compute the maximum amount that can be swapped to a destination token across all available sources. This is useful for populating a "Max" button before calling `swapWithExactIn()` or `swapWithExactOut()`. > **Note** > > **Note:** Check out the [API reference](/docs/nexus/supported-chains-and-tokens) for a full list of supported tokens and chains. ## Method signature ```typescript title="TypeScript" calculateMaxForSwap( input: SwapMaxParams, ): Promise ``` ## Parameters ```typescript title="TypeScript" export type SwapMaxParams = { toChainId: number; toTokenAddress: Hex; sources?: Source[]; }; export type Source = { tokenAddress: Hex; chainId: number; }; ``` * `SwapMaxParams`: Parameters for the `calculateMaxForSwap()` function. * `toChainId` (number, required): The chain ID of the destination chain. * `toTokenAddress` (Hex, required): The contract address of the token to receive on the destination chain. * `sources` (Source\[], optional): Restrict which source chains and tokens are considered. If omitted, all available holdings are considered. * `tokenAddress` (Hex): The contract address of the source token. * `chainId` (number): The chain ID of the source chain. ## Example ```typescript title="TypeScript" import type { SwapMaxParams, SwapMaxResult } from '@avail-project/nexus-core'; const max = await client.calculateMaxForSwap({ toChainId: 8453, toTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base }); console.log(`Max swappable: ${max.maxAmount} ${max.symbol}`); console.log('Sources used:', max.sources); ``` You can also restrict which source chains and tokens are considered: ```typescript title="TypeScript" const max = await client.calculateMaxForSwap({ toChainId: 8453, toTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', sources: [ { chainId: 10, tokenAddress: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85' }, ], }); // Feed the raw amount straight into an exact-out swap: await client.swapWithExactOut({ toChainId: 8453, toTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', toAmountRaw: max.maxAmountRaw, }); ``` ## Return Value The return value is a `SwapMaxResult` object. ```typescript title="TypeScript" export type SwapMaxResult = { toChainId: number; toTokenAddress: Hex; maxAmount: string; // Human-readable decimal string maxAmountRaw: bigint; // Raw amount suitable for toAmountRaw in swapWithExactOut symbol: string; decimals: number; sources: { chainId: number; tokenAddress: Hex; symbol: string; decimals: number; amount: string; // Human-readable portion sourced from this chain/token }[]; }; ``` * `maxAmount` (string): The maximum receivable amount as a human-readable decimal string. * `maxAmountRaw` (bigint): The same maximum in raw integer units — suitable for `toAmountRaw` in `swapWithExactOut()`. * `symbol` / `decimals`: Metadata for the destination token. * `sources`: The per-source breakdown of holdings that make up the max. > **Note** > > On failure the method throws a typed [`NexusError`](/docs/nexus/nexus-sdk/v2/reference/hooks-and-errors) subclass. See the [full type definitions](https://github.com/availproject/nexus-sdk/blob/main/src/swap/types.ts) on GitHub. ## Fetch Swap Balances > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). > **Note** > > **SET UP THE SDK BEFORE YOU START:** > > Install and initialize the client first — see [Installation](/docs/nexus/nexus-sdk/v2/get-started/installation) and [SDK Setup](/docs/nexus/nexus-sdk/v2/get-started/nexus-sdk-setup). Use the `getBalancesForSwap()` function to fetch token balances that can be used in swap operations across all supported chains in one call. > **Note** > > **Note:** Check out the [API reference](/docs/nexus/supported-chains-and-tokens) for a full list of supported tokens and chains. ## Method signature ```typescript title="TypeScript" getBalancesForSwap(): Promise ``` > **Note** > > `getBalancesForSwap()` returns the same `TokenBalance[]` shape as `getBalancesForBridge()`. If you need the tokens that can be used for bridging instead, use [`getBalancesForBridge()`](/docs/nexus/nexus-sdk/v2/reference/bridge-methods/fetch-bridge-balances). ## Parameters * None ## Example Here is a minimal example of how to use the `getBalancesForSwap()` function to fetch balances that are eligible to be used as swap inputs: ```typescript title="TypeScript" // Get all balances that are eligible for swap operations const swapBalances = await client.getBalancesForSwap(); console.log('Swap balances:', swapBalances); ``` ## Return Value `getBalancesForSwap()` returns `Promise` — an array of `TokenBalance` objects. Each `TokenBalance` aggregates a token's holdings across every chain it lives on, with the per-chain split in `chainBalances`. ```typescript title="TypeScript" export type TokenBalance = { balance: string; // Aggregate balance across all chains (human-readable) value: string; // Aggregate fiat value (string for precision) chainBalances: ChainBalance[];// Per-chain breakdown currencyId?: number; // Currency identifier (when applicable) decimals: number; logo: string; name: string; // Display label (e.g. "USDC/USDM") symbol: string; // Majority symbol by chain count }; export type ChainBalance = { balance: string; // Balance on this chain (human-readable) value: string; // Fiat value on this chain (string for precision) symbol: string; chain: { id: number; logo: string; name: string; }; contractAddress: `0x${string}`; decimals: number; universe: Universe; // VM family of the chain (e.g. 'EVM') }; ``` > **Note** > > Field renames from [v1](https://github.com/availproject/nexus-sdk/blob/main/MIGRATION.md): `balanceInFiat` → `value`, `icon` → `logo`, and `breakdown` → `chainBalances`. See the [full type definitions](https://github.com/availproject/nexus-sdk/blob/main/src/domain/types/index.ts) on GitHub. ## Swap and Execute > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). > **Note** > > **SET UP THE SDK BEFORE YOU START:** > > Install and initialize the client first — see [Installation](/docs/nexus/nexus-sdk/v2/get-started/installation) and [SDK Setup](/docs/nexus/nexus-sdk/v2/get-started/nexus-sdk-setup). Use the `swapAndExecute()` function to swap tokens to a destination chain and then execute a contract call, in a single flow. If sufficient funds already exist on the destination chain, the swap is skipped and only the execution runs. Swaps are routed through multiple DEX aggregators (such as LiFi, Bebop, and 0x), quoted in parallel. Execution runs through a per-chain smart account chosen automatically — an ephemeral key delegated via EIP-7702 on 7702-enabled chains, or a deterministic Safe owned by the ephemeral key elsewhere. > **Note** > > **Note:** Check out the [API reference](/docs/nexus/supported-chains-and-tokens) for a full list of supported tokens and chains. ## Method signature ```typescript title="TypeScript" swapAndExecute( input: SwapAndExecuteParams, options?: SwapAndExecuteOptions, ): Promise ``` ## Parameters ```typescript title="TypeScript" export interface SwapAndExecuteParams { toChainId: number; toTokenAddress: Hex; toAmountRaw: bigint; sources?: Source[]; execute: SwapExecuteParams; } export interface SwapExecuteParams { to: Hex; value?: bigint; data?: Hex; gas: bigint; gasPrice?: 'low' | 'medium' | 'high'; tokenApproval?: { toTokenAddress: Hex; amount: bigint; spender: Hex }; } export type Source = { tokenAddress: Hex; chainId: number; }; ``` * `SwapAndExecuteParams`: Parameters for the `swapAndExecute()` function. * `toChainId` (number, required): The chain ID of the destination chain. * `toTokenAddress` (Hex, required): The contract address of the token needed on the destination chain. * `toAmountRaw` (bigint, required): The amount of the destination token needed, in raw integer units. * `sources` (Source\[], optional): Restrict which chains and tokens can be used as the swap source. If omitted, the SDK selects the best sources from available balances. * `execute` (`SwapExecuteParams`, required): The contract call to run on the destination chain. * `to` (Hex, required): The contract address to call. * `value` (bigint, optional): Native token value to send, in raw integer units (wei). * `data` (Hex, optional): The encoded function call data. * `gas` (bigint, required): The gas limit. Chain-specific adjustments and buffering are applied by the SDK. * `gasPrice` (`'low' | 'medium' | 'high'`, optional): Gas price strategy. * `tokenApproval` (optional): A token approval to submit before the execution call. * `toTokenAddress` (Hex): The token to approve. * `amount` (bigint): The approval amount, in raw integer units. * `spender` (Hex): The spender address. * `options`: `SwapAndExecuteOptions` (optional): Callbacks to track and gate the operation. ```typescript title="TypeScript" export type SwapAndExecuteOptions = { onEvent?: (event: SwapAndExecuteEvent) => void; onIntent?: (data: SwapAndExecuteOnIntentHookData) => void; slippageTolerance?: number; }; ``` * `onEvent` (optional): Receives status, plan preview/confirmed, and per-step progress updates. `swapAndExecute()` emits a superset of the [Swap Events](/docs/nexus/nexus-sdk/v2/reference/swap-methods/swap-events) — the swap events plus a `preparing` status and the execute approval/transaction progress events. * `onIntent` (optional, top-level): Called with the resolved intent before execution. The intent carries `swapRequired`. Call `allow()` to proceed, `deny()` to cancel, or `refresh(sources?)` to re-quote. * `slippageTolerance` (number, optional): Slippage override as a fraction (default `0.005`, i.e. 0.5%). ## Example ```typescript title="TypeScript" import type { SwapAndExecuteParams, SwapAndExecuteOptions, SwapAndExecuteResult, } from '@avail-project/nexus-core'; const result = await client.swapAndExecute( { toChainId: 42161, toTokenAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', toAmountRaw: 100_000_000n, execute: { to: '0x3333333333333333333333333333333333333333', data: '0xdeadbeef', gas: 100_000n, value: 0n, tokenApproval: { toTokenAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', amount: 100_000_000n, spender: '0x3333333333333333333333333333333333333333', }, }, }, { onEvent: (event) => { if (event.type === 'status') { // preparing | route_building | route_ready | awaiting_approval | approved | executing | completed console.log('Status:', event.status); } }, onIntent: ({ allow, deny, refresh, intent }) => { console.log('Swap required:', intent.swapRequired); allow(); }, }, ); console.log('Swap skipped:', result.swapSkipped); console.log('Swap result:', result.swapResult); console.log('Execute tx hash:', result.execute.txHash); ``` ## Return Value The return value is a `SwapAndExecuteResult` object. It always carries the `execute` transaction result (and an optional `approval`), plus a discriminated `swapSkipped` flag that tells you whether a swap ran. ```typescript title="TypeScript" export type SwapAndExecuteResult = { approval?: TxResult; execute: TxResult; } & ( | { swapSkipped: false; swapResult: SwapResult } | { swapSkipped: true; swapResult?: undefined } ); export type TxResult = { txHash: Hex; txExplorerUrl: string; receipt?: TransactionReceipt; }; ``` * `approval` (`TxResult`, optional): The approval transaction, when one was required. * `execute` (`TxResult`): The contract execution transaction on the destination chain. * `swapSkipped` (boolean): `true` when destination funds were already sufficient and no swap ran. * `swapResult` (`SwapResult`): Present only when `swapSkipped` is `false`. See [Swap Exact In](/docs/nexus/nexus-sdk/v2/reference/swap-methods/swap-exact-in#return-value) for the `SwapResult` shape. > **Note** > > On failure the method throws a typed [`NexusError`](/docs/nexus/nexus-sdk/v2/reference/hooks-and-errors) subclass rather than returning an error result. See the [full type definitions](https://github.com/availproject/nexus-sdk/blob/main/src/swap/types.ts) on GitHub. ## Swap Events > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). Both `swapWithExactIn()` and `swapWithExactOut()` accept an `onEvent` callback (via `SwapOperationOptions`) that receives a typed `SwapEvent`. Every event carries a `type` discriminant, so you can narrow on `event.type` and then, for progress events, on `event.stepType` and `event.state`. ## Event union ```typescript title="TypeScript" export type SwapEvent = | SwapStatusEvent // type: 'status' | SwapPlanPreviewEvent // type: 'plan_preview' | SwapPlanConfirmedEvent // type: 'plan_confirmed' | SwapPlanProgressEvent; // type: 'plan_progress' ``` ### Status events Emitted as the operation moves through its lifecycle. ```typescript title="TypeScript" export type SwapStatus = | 'route_building' | 'route_ready' | 'awaiting_approval' | 'approved' | 'executing' | 'completed'; export type SwapStatusEvent = { type: 'status'; status: SwapStatus; }; ``` ### Plan preview & confirmed events `plan_preview` is emitted once the route is planned; `plan_confirmed` is emitted after the intent is approved. Both carry the same `SwapPlan`. ```typescript title="TypeScript" export type SwapPlanPreviewEvent = { type: 'plan_preview'; plan: SwapPlan; }; export type SwapPlanConfirmedEvent = { type: 'plan_confirmed'; plan: SwapPlan; }; export type SwapPlan = { hasBridge: boolean; hasDestinationSwap: boolean; steps: SwapPlanStep[]; }; export type SwapPlanStep = | SwapSourceSwapStep // type: 'source_swap' | SwapEoaToEphemeralTransferStep // type: 'eoa_to_ephemeral_transfer' | SwapBridgeDepositStep // type: 'bridge_deposit' | SwapBridgeIntentSubmissionStep // type: 'bridge_intent_submission' | BridgeFillStep // type: 'bridge_fill' | SwapDestinationSwapStep; // type: 'destination_swap' ``` ## Progress events `plan_progress` events are emitted as each step advances. Narrow first on `stepType`, then on `state`. Every progress event carries its `step` object. ```typescript title="TypeScript" export type SwapPlanProgressEvent = | SwapSourceSwapProgressEvent | SwapEoaToEphemeralTransferProgressEvent | SwapBridgeDepositProgressEvent | SwapBridgeIntentSubmissionProgressEvent | SwapBridgeFillProgressEvent | SwapDestinationSwapProgressEvent; ``` The table below lists the `state` values each `stepType` can emit: | `stepType` | States | | --------------------------- | -------------------------------------------------------------------- | | `source_swap` | `wallet_prompted` · `started` · `submitted` · `confirmed` · `failed` | | `eoa_to_ephemeral_transfer` | `wallet_prompted` · `submitted` · `confirmed` · `failed` | | `bridge_deposit` | `started` · `submitted` · `confirmed` · `failed` | | `bridge_intent_submission` | `started` · `completed` · `failed` | | `bridge_fill` | `waiting` · `completed` · `failed` | | `destination_swap` | `wallet_prompted` · `started` · `submitted` · `confirmed` · `failed` | ### Field shapes `submitted` and `confirmed` states carry `txHash` and `explorerUrl`. `failed` states carry an `error` string (and, for tx-based steps, optional `txHash`/`explorerUrl`). The bridge steps carry an `intentRequestHash` instead. ```typescript title="TypeScript" export type SwapSourceSwapProgressEvent = | { type: 'plan_progress'; stepType: 'source_swap'; state: 'wallet_prompted'; step: SwapSourceSwapStep } | { type: 'plan_progress'; stepType: 'source_swap'; state: 'started'; step: SwapSourceSwapStep } | { type: 'plan_progress'; stepType: 'source_swap'; state: 'submitted'; step: SwapSourceSwapStep; txHash: Hex; explorerUrl: string } | { type: 'plan_progress'; stepType: 'source_swap'; state: 'confirmed'; step: SwapSourceSwapStep; txHash: Hex; explorerUrl: string } | { type: 'plan_progress'; stepType: 'source_swap'; state: 'failed'; step: SwapSourceSwapStep; txHash?: Hex; explorerUrl?: string; error: string }; export type SwapEoaToEphemeralTransferProgressEvent = | { type: 'plan_progress'; stepType: 'eoa_to_ephemeral_transfer'; state: 'wallet_prompted'; step: SwapEoaToEphemeralTransferStep } | { type: 'plan_progress'; stepType: 'eoa_to_ephemeral_transfer'; state: 'submitted'; step: SwapEoaToEphemeralTransferStep; txHash: Hex; explorerUrl: string } | { type: 'plan_progress'; stepType: 'eoa_to_ephemeral_transfer'; state: 'confirmed'; step: SwapEoaToEphemeralTransferStep; txHash: Hex; explorerUrl: string } | { type: 'plan_progress'; stepType: 'eoa_to_ephemeral_transfer'; state: 'failed'; step: SwapEoaToEphemeralTransferStep; txHash?: Hex; explorerUrl?: string; error: string }; export type SwapBridgeDepositProgressEvent = | { type: 'plan_progress'; stepType: 'bridge_deposit'; state: 'started'; step: SwapBridgeDepositStep } | { type: 'plan_progress'; stepType: 'bridge_deposit'; state: 'submitted'; step: SwapBridgeDepositStep; txHash: Hex; explorerUrl: string } | { type: 'plan_progress'; stepType: 'bridge_deposit'; state: 'confirmed'; step: SwapBridgeDepositStep; txHash: Hex; explorerUrl: string } | { type: 'plan_progress'; stepType: 'bridge_deposit'; state: 'failed'; step: SwapBridgeDepositStep; txHash?: Hex; explorerUrl?: string; error: string }; export type SwapBridgeIntentSubmissionProgressEvent = | { type: 'plan_progress'; stepType: 'bridge_intent_submission'; state: 'started'; step: SwapBridgeIntentSubmissionStep } | { type: 'plan_progress'; stepType: 'bridge_intent_submission'; state: 'completed'; step: SwapBridgeIntentSubmissionStep; intentRequestHash: Hex } | { type: 'plan_progress'; stepType: 'bridge_intent_submission'; state: 'failed'; step: SwapBridgeIntentSubmissionStep; intentRequestHash?: Hex; error: string }; export type SwapBridgeFillProgressEvent = | { type: 'plan_progress'; stepType: 'bridge_fill'; state: 'waiting'; step: BridgeFillStep; intentRequestHash: Hex } | { type: 'plan_progress'; stepType: 'bridge_fill'; state: 'completed'; step: BridgeFillStep; intentRequestHash: Hex } | { type: 'plan_progress'; stepType: 'bridge_fill'; state: 'failed'; step: BridgeFillStep; intentRequestHash: Hex; error: string }; export type SwapDestinationSwapProgressEvent = | { type: 'plan_progress'; stepType: 'destination_swap'; state: 'wallet_prompted'; step: SwapDestinationSwapStep } | { type: 'plan_progress'; stepType: 'destination_swap'; state: 'started'; step: SwapDestinationSwapStep } | { type: 'plan_progress'; stepType: 'destination_swap'; state: 'submitted'; step: SwapDestinationSwapStep; txHash: Hex; explorerUrl: string } | { type: 'plan_progress'; stepType: 'destination_swap'; state: 'confirmed'; step: SwapDestinationSwapStep; txHash: Hex; explorerUrl: string } | { type: 'plan_progress'; stepType: 'destination_swap'; state: 'failed'; step: SwapDestinationSwapStep; txHash?: Hex; explorerUrl?: string; error: string }; ``` ### Step shapes ```typescript title="TypeScript" export type PlanTokenAmount = { symbol: string; contractAddress: Hex; decimals: number; logo?: string; amount: string; // human-readable amountRaw: bigint; // raw integer units }; export type SwapSourceSwapStep = { type: 'source_swap'; id: string; chain: { id: number; name: string; logo: string }; walletPath: 'ephemeral' | 'safe'; swaps: { input: PlanTokenAmount; output: PlanTokenAmount }[]; }; export type SwapEoaToEphemeralTransferStep = { type: 'eoa_to_ephemeral_transfer'; id: string; chain: { id: number; name: string; logo: string }; asset: PlanTokenAmount; }; export type SwapBridgeDepositStep = { type: 'bridge_deposit'; id: string; chain: { id: number; name: string; logo: string }; asset: PlanTokenAmount; }; export type SwapBridgeIntentSubmissionStep = { type: 'bridge_intent_submission'; id: string; }; export type BridgeFillStep = { type: 'bridge_fill'; id: string; chain: { id: number; name: string; logo: string }; asset: PlanTokenAmount; }; export type SwapDestinationSwapStep = { type: 'destination_swap'; id: string; chain: { id: number; name: string; logo: string }; walletPath: 'ephemeral' | 'safe'; swaps: { input: PlanTokenAmount; output: PlanTokenAmount }[]; }; ``` ## Usage example ```typescript title="TypeScript" const result = await client.swapWithExactIn(swapInput, { onEvent: (event) => { switch (event.type) { case 'status': console.log('Status:', event.status); break; case 'plan_preview': console.log('Planned steps:', event.plan.steps); console.log('Has bridge:', event.plan.hasBridge); break; case 'plan_progress': if (event.stepType === 'source_swap' && event.state === 'confirmed') { console.log('Source swap confirmed:', event.txHash, event.explorerUrl); } if (event.stepType === 'destination_swap' && event.state === 'confirmed') { console.log('Destination swap confirmed:', event.txHash, event.explorerUrl); } break; } }, }); ``` } title="Swap event types" href="https://github.com/availproject/nexus-sdk/blob/main/src/domain/types/swap-events.ts" target="_blank" rel="noopener noreferrer" arrow /> > **Note** > > `swapAndExecute()` emits a superset of these events (`SwapAndExecuteEvent`), adding a `preparing` status plus the execute approval and transaction progress events. See the [Swap and Execute](/docs/nexus/nexus-sdk/v2/reference/swap-methods/swap-and-execute) page. ## Swap Exact Input > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). > **Note** > > **SET UP THE SDK BEFORE YOU START:** > > Install and initialize the client first — see [Installation](/docs/nexus/nexus-sdk/v2/get-started/installation) and [SDK Setup](/docs/nexus/nexus-sdk/v2/get-started/nexus-sdk-setup). Use the `swapWithExactIn()` function to swap tokens with an exactly defined input amount. For example, if you want to swap `1 USDC` into whatever amount of the destination token you can get on the destination chain. Swaps are routed through multiple DEX aggregators (such as LiFi, Bebop, and 0x), quoted in parallel. Execution runs through a per-chain smart account chosen automatically — an ephemeral key delegated via EIP-7702 on 7702-enabled chains, or a deterministic Safe owned by the ephemeral key elsewhere. The connected EOA is never used to dispatch a swap directly. ## Method signature ```typescript title="TypeScript" swapWithExactIn( input: SwapExactInParams, options?: SwapOperationOptions, ): Promise ``` ## Parameters ```typescript title="TypeScript" export interface SwapExactInParams { sources?: (Source & { amountRaw?: bigint })[]; toChainId: number; toTokenAddress: Hex; } export type Source = { tokenAddress: Hex; chainId: number; }; ``` * `SwapExactInParams`: Parameters for using the `swapWithExactIn()` function. * `sources` (`Array`, optional): The source tokens and amounts to swap from. Omit to let the SDK use all available holdings. * `chainId` (number, required): The chain ID of the source chain. * `tokenAddress` (Hex, required): The contract address of the source token. * `amountRaw` (bigint, optional): The amount of the source token to swap, in raw integer units (e.g. `1_000_000n` for 1 USDC). * `toChainId` (number, required): The chain ID of the destination chain. * `toTokenAddress` (Hex, required): The contract address of the token to receive on the destination chain. * `options`: `SwapOperationOptions` (optional): Callbacks to track and gate the swap operation. ```typescript title="TypeScript" export type SwapOperationOptions = { onEvent?: (event: SwapEvent) => void; hooks?: { onIntent?: (data: OnIntentHookData) => void; }; slippageTolerance?: number; }; ``` * `onEvent` (optional): Receives `status`, `plan_preview`, `plan_confirmed`, and `plan_progress` updates as the swap progresses. See the [Swap Events](/docs/nexus/nexus-sdk/v2/reference/swap-methods/swap-events) page for the full typed union. * `hooks.onIntent` (optional): Called with the resolved swap intent before execution. Call `allow()` to proceed, `deny()` to cancel, or `refresh(sources?)` to re-quote. * `slippageTolerance` (number, optional): Slippage override as a fraction (default `0.005`, i.e. 0.5%). ## Example ```typescript title="TypeScript" import type { SwapExactInParams, SwapOperationOptions, SwapResult, } from '@avail-project/nexus-core'; const result = await client.swapWithExactIn( { sources: [ { chainId: 10, amountRaw: 1_000_000n, tokenAddress: '0x...' }, { chainId: 42161, amountRaw: 500_000n, tokenAddress: '0x...' }, ], toChainId: 8453, toTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', }, { onEvent: (event) => { if (event.type === 'plan_progress') { console.log('Swap progress:', event.step.type, event.state); } }, hooks: { onIntent: ({ intent, allow }) => { console.log('Swap intent:', intent); allow(); }, }, slippageTolerance: 0.005, }, ); console.log('Swap with exact in result:', result); ``` ## Return Value The return value is a `SwapResult` object. ```typescript title="TypeScript" export type SwapResult = { sourceSwaps: ChainSwap[]; intentExplorerUrl: string; destinationSwap: ChainSwap | null; intent: SwapIntent; }; export type ChainSwap = { chainId: number; swaps: Swap[]; txHash: Hex; }; export type Swap = { inputAmount: bigint; inputContract: Hex; inputDecimals: number; outputAmount: bigint; outputContract: Hex; outputDecimals: number; }; ``` > **Note** > > On failure the method throws a typed [`NexusError`](/docs/nexus/nexus-sdk/v2/reference/hooks-and-errors) subclass rather than returning an error result. See the [full type definitions](https://github.com/availproject/nexus-sdk/blob/main/src/swap/types.ts) on GitHub. ## Swap Exact Out > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). > **Note** > > **SET UP THE SDK BEFORE YOU START:** > > Install and initialize the client first — see [Installation](/docs/nexus/nexus-sdk/v2/get-started/installation) and [SDK Setup](/docs/nexus/nexus-sdk/v2/get-started/nexus-sdk-setup). Use the `swapWithExactOut()` function to swap tokens with an exactly defined output amount. For example, if you want to get exactly `100 USDC` on the destination but don't care which amount of source funds are used to source the swap. You can however limit your source chains and tokens to a subset of the total supported holdings. Swaps are routed through multiple DEX aggregators (such as LiFi, Bebop, and 0x), quoted in parallel. Execution runs through a per-chain smart account chosen automatically — an ephemeral key delegated via EIP-7702 on 7702-enabled chains, or a deterministic Safe owned by the ephemeral key elsewhere. The connected EOA is never used to dispatch a swap directly. ## Method signature ```typescript title="TypeScript" swapWithExactOut( input: SwapExactOutParams, options?: SwapOperationOptions, ): Promise ``` ## Parameters ```typescript title="TypeScript" export interface SwapExactOutParams { sources?: Source[]; toChainId: number; toTokenAddress: Hex; toAmountRaw: bigint; toNativeAmountRaw?: bigint; } export type Source = { tokenAddress: Hex; chainId: number; }; ``` * `SwapExactOutParams`: Parameters for the `swapWithExactOut()` function. * `sources` (Source\[], optional): Restrict which chains and tokens can be used as the swap source. If omitted, the SDK automatically selects the best sources from available balances. * `tokenAddress` (Hex): The contract address of the source token. * `chainId` (number): The chain ID of the source chain. * `toChainId` (number, required): The chain ID of the destination chain where you want to receive tokens. * `toTokenAddress` (Hex, required): The contract address of the token you want to receive. * `toAmountRaw` (bigint, required): The exact amount of tokens you want to receive on the destination chain, in raw integer units (e.g. `1_000_000n` for 1 USDC). * `toNativeAmountRaw` (bigint, optional): Amount of native gas tokens to receive on the destination chain alongside the swap, in raw integer units (wei). Useful for ensuring the user has gas for subsequent transactions. * `options`: `SwapOperationOptions` (optional): Callbacks to track and gate the swap operation. ```typescript title="TypeScript" export type SwapOperationOptions = { onEvent?: (event: SwapEvent) => void; hooks?: { onIntent?: (data: OnIntentHookData) => void; }; slippageTolerance?: number; }; ``` * `onEvent` (optional): Receives `status`, `plan_preview`, `plan_confirmed`, and `plan_progress` updates as the swap progresses. See the [Swap Events](/docs/nexus/nexus-sdk/v2/reference/swap-methods/swap-events) page for the full typed union. * `hooks.onIntent` (optional): Called with the resolved swap intent before execution. Call `allow()` to proceed, `deny()` to cancel, or `refresh(sources?)` to re-quote. * `slippageTolerance` (number, optional): Slippage override as a fraction (default `0.005`, i.e. 0.5%). ## Example ```typescript title="TypeScript" import type { SwapExactOutParams, SwapOperationOptions, SwapResult, } from '@avail-project/nexus-core'; const result = await client.swapWithExactOut( { toChainId: 42161, toTokenAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', // USDC on Arbitrum toAmountRaw: 100_000_000n, // 100 USDC (6 decimals) // Optional: also fund destination native gas toNativeAmountRaw: 100_000_000_000_000n, // Optional: restrict route planning to specific source tokens/chains sources: [{ chainId: 8453, tokenAddress: '0x...' }], }, { onEvent: (event) => { if (event.type === 'status') { console.log('Swap status:', event.status); } if (event.type === 'plan_preview') { console.log('Swap plan:', event.plan.steps); console.log('Has bridge:', event.plan.hasBridge); console.log('Has destination swap:', event.plan.hasDestinationSwap); } }, hooks: { onIntent: ({ allow, deny, refresh, intent }) => { console.log('Swap intent:', intent); allow(); }, }, }, ); console.log('Swap with exact out result:', result); ``` ## Return Value The return value is a `SwapResult` object. ```typescript title="TypeScript" export type SwapResult = { sourceSwaps: ChainSwap[]; intentExplorerUrl: string; destinationSwap: ChainSwap | null; intent: SwapIntent; }; export type ChainSwap = { chainId: number; swaps: Swap[]; txHash: Hex; }; export type Swap = { inputAmount: bigint; inputContract: Hex; inputDecimals: number; outputAmount: bigint; outputContract: Hex; outputDecimals: number; }; ``` > **Note** > > On failure the method throws a typed [`NexusError`](/docs/nexus/nexus-sdk/v2/reference/hooks-and-errors) subclass rather than returning an error result. See the [full type definitions](https://github.com/availproject/nexus-sdk/blob/main/src/swap/types.ts) on GitHub. ## Fetch Bridge Balances > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). > **Note** > > **SET UP THE SDK BEFORE YOU START:** > > Install and initialize the client first — see [Installation](/docs/nexus/nexus-sdk/v2/get-started/installation) and [SDK Setup](/docs/nexus/nexus-sdk/v2/get-started/nexus-sdk-setup). Use the `getBalancesForBridge()` function to fetch token balances that can be used in bridge operations across all supported chains in one call. > **Note** > > **Note:** Check out the [API reference](/docs/nexus/supported-chains-and-tokens) for a full list of supported tokens and chains. ## Method signature ```typescript title="TypeScript" getBalancesForBridge(): Promise ``` > **Note** > > If you need the tokens that can be used for swaps instead, use `getBalancesForSwap()`, which returns the same `TokenBalance[]` shape. ## Parameters * None ## Example Here is a minimal example of how to use the `getBalancesForBridge()` function to fetch the balances accessible by the bridge operations of a user: ```typescript title="TypeScript" // Get all balances accessible by the bridge operations of a user const bridgeBalances = await client.getBalancesForBridge(); console.log('Bridge balances:', bridgeBalances); ``` ## Return Value `getBalancesForBridge()` returns `Promise` — an array of `TokenBalance` objects. Each `TokenBalance` aggregates a token's holdings across every chain it lives on, with the per-chain split in `chainBalances`. ```typescript title="TypeScript" export type TokenBalance = { balance: string; // Aggregate balance across all chains (human-readable) value: string; // Aggregate fiat value (string for precision) chainBalances: ChainBalance[];// Per-chain breakdown currencyId?: number; // Bridge currency identifier (present on bridge balances) decimals: number; logo: string; name: string; // Display label (e.g. "USDC/USDM") symbol: string; // Majority symbol by chain count }; export type ChainBalance = { balance: string; // Balance on this chain (human-readable) value: string; // Fiat value on this chain (string for precision) symbol: string; chain: { id: number; logo: string; name: string; }; contractAddress: `0x${string}`; decimals: number; universe: Universe; // VM family as a numeric enum (0 = Ethereum/EVM) }; ``` > **Note** > > Field renames from [v1](https://github.com/availproject/nexus-sdk/blob/main/MIGRATION.md): `balanceInFiat` → `value`, `icon` → `logo`, and `breakdown` → `chainBalances`. See the [full type definitions](https://github.com/availproject/nexus-sdk/blob/main/src/domain/types/index.ts) on GitHub. ## Bridge and Transfer > **Note** > > **You are viewing the Nexus SDK v2 docs.** For the current SDK, see the [v3 docs](/docs/nexus/nexus-sdk/get-started/installation) and the [v2 → v3 migration guide](/docs/nexus/migration/sdk-v2-to-v3). > **Note** > > **SET UP THE SDK BEFORE YOU START:** > > Install and initialize the client first — see [Installation](/docs/nexus/nexus-sdk/v2/get-started/installation) and [SDK Setup](/docs/nexus/nexus-sdk/v2/get-started/nexus-sdk-setup). Use the `bridgeAndTransfer()` function to source a token from multiple source chains to a single destination chain. Use the `simulateBridgeAndTransfer()` function to simulate the bridge and transfer transaction to preview the costs and fees, before actually executing the transaction. > **Note** > > **Note:** Check out the [API reference](/docs/nexus/supported-chains-and-tokens) for a full list of supported tokens and chains. ## Method signature ```typescript title="TypeScript" bridgeAndTransfer( params: TransferParams, options?: BridgeOperationOptions, ): Promise simulateBridgeAndTransfer( params: TransferParams, ): Promise ``` ## Parameters ```typescript title="TypeScript" /** * Parameters for bridging and transferring tokens. */ export interface TransferParams { toTokenSymbol: string; toAmountRaw: bigint; toChainId: number; recipient: `0x${string}`; sources?: number[]; } ``` * `TransferParams`: Parameters for transferring tokens. * `toTokenSymbol` (string, required): The symbol of the token to be bridged and transferred (e.g. `'USDC'`). * `toAmountRaw` (bigint, required): The amount of tokens to transfer to the recipient, in raw integer units. * `toChainId` (number, required): The chain ID of the destination chain. * `recipient` (`0x${string}`, required): The recipient address. * `sources` (number\[], optional): The chain IDs of the source chains to be used for the transfer. Useful if you want to maintain your holdings on some chains. * `options`: `BridgeOperationOptions` (optional): Per-operation hooks, event listener, and fill timeout — the same options object accepted by [`bridge()`](/docs/nexus/nexus-sdk/v2/reference/bridge-methods/bridge). ```typescript title="TypeScript" export type BridgeOperationOptions = { hooks?: { onIntent?: (data: OnIntentHookData) => void; onAllowance?: (data: OnAllowanceHookData) => void; }; onEvent?: (event: BridgeEvent) => void; fillTimeoutMinutes?: number; }; ``` > **Note** > > The `onEvent` callback receives a typed discriminated union (`status` / `plan_preview` / `plan_confirmed` / `plan_progress`). See the [Bridge Events](/docs/nexus/nexus-sdk/v2/reference/bridge-methods/bridge-events) page for the full event shapes. ## Example ```typescript title="TypeScript" import type { TransferParams, TransferResult } from '@avail-project/nexus-core'; // Smart transfer with automatic optimization const bridgeAndTransferResult: TransferResult = await client.bridgeAndTransfer( { toTokenSymbol: 'USDC', toAmountRaw: 1_530_000n, // 1.53 USDC (6 decimals) toChainId: 1, // Ethereum recipient: '0x742d35Cc6634C0532925a3b8D4C9db96c4b4Db45', }, { onEvent: (event) => { if (event.type === 'plan_preview') console.log('Transfer steps:', event.plan.steps); if (event.type === 'plan_progress') console.log('Step progress:', event.stepType, event.state); }, }, ); console.log('Bridge and transfer result:', bridgeAndTransferResult); const bridgeAndTransferSimulation = await client.simulateBridgeAndTransfer({ toTokenSymbol: 'USDC', toAmountRaw: 1_530_000n, // 1.53 USDC (6 decimals) toChainId: 1, recipient: '0x742d35Cc6634C0532925a3b8D4C9db96c4b4Db45', }); console.log('Bridge and transfer simulation:', bridgeAndTransferSimulation); ``` > **Warning** > > `bridgeAndTransfer()` throws typed [`NexusError`](https://github.com/availproject/nexus-sdk/blob/main/src/domain/errors.ts) subclasses on failure. Branch on `error.category` / `error.code`. ## Return Value ### `bridgeAndTransfer()` The return value is a `TransferResult` object, which is an alias for [`BridgeAndExecuteResult`](/docs/nexus/nexus-sdk/v2/reference/bridge-methods/bridge-and-execute). ```typescript title="TypeScript" /** * Result structure for transfer transactions. */ export type TransferResult = BridgeAndExecuteResult; export type BridgeAndExecuteResult = { approval?: TxResult; execute: TxResult; } & ( | { bridgeSkipped: false; bridgeResult: BridgeResult } | { bridgeSkipped: true; bridgeResult?: undefined } ); ``` ### `simulateBridgeAndTransfer()` The return value is a `BridgeAndExecuteSimulationResult` object. ```typescript title="TypeScript" export type BridgeAndExecuteSimulationResult = { bridgeSimulation: BridgeSimulationResult | null; // null when bridging is skipped executeSimulation: ExecuteSimulation; }; ```