Deposit (exact-output-mode)
Route funds into a configured protocol or app action with Nexus intents using exact output mode.
How exact-output mode works
Note
Looking for exact-input mode? See Deposit (exact-input-mode) to fund deposits by specifying input tokens and amounts to spend.
Installation
pnpm add @avail-project/widgets viem wagmi @tanstack/react-query
Usage
NexusWidget must be rendered inside NexusProvider (mandatory — it initializes the SDK), and the provider should receive your identity.clientId. See Get Started for provider setup and Client ID for details.
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;
<NexusProvider config={{ identity: { clientId: "YOUR_NEXUS_CLIENT_ID" } }}>
<NexusWidget config={depositConfig} connectedAddress={address} />
</NexusProvider>;Configuration
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.
Configure Deposit widget
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: trueto offer local currency funding alongside wallet funding. Defaults tofalse, 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
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
embed={false} to invoke Deposit from a button and show the element as a modal. The widget still needs to sit under NexusProvider.
<NexusWidget
config={depositConfig}
connectedAddress={address}
embed={false}
defaultOpen={false}
/>const [open, setOpen] = useState(false);
<NexusWidget
config={depositConfig}
connectedAddress={address}
embed={false}
open={open}
onOpenChange={setOpen}
/>;Supporting Multiple Opportunities / Selector UI
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:
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<keyof typeof OPPORTUNITIES>("aave");
return (
<div className="space-y-4">
<div className="flex flex-col gap-1.5 max-w-xs mx-auto">
<label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
Select Opportunity
</label>
<select
value={selectedOpt}
onChange={(e) =>
setSelectedOpt(e.target.value as keyof typeof OPPORTUNITIES)
}
className="w-full h-10 px-3 rounded-lg border border-border bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent cursor-pointer"
>
<option value="aave">Aave (USDT on Arbitrum)</option>
<option value="compound">Compound (USDC on Base)</option>
</select>
</div>
<NexusProvider config={{ identity: { clientId: "YOUR_NEXUS_CLIENT_ID" } }}>
<NexusWidget
config={{
mode: "deposit",
enableOnRamp: true, // Optional; defaults to false (wallet funding only).
destination: {
chain: OPPORTUNITIES[selectedOpt].chain,
tokens: [OPPORTUNITIES[selectedOpt].token],
},
depositAddress: OPPORTUNITIES[selectedOpt].depositAddress,
executeDeposit: OPPORTUNITIES[selectedOpt].executeDeposit,
appearance: {
appName: OPPORTUNITIES[selectedOpt].appName,
mode: "system",
},
}}
connectedAddress={address}
/>
</NexusProvider>
</div>
);
}How is this guide?