Deposit into Aave
Swap tokens from any chain and supply the proceeds to Aave V3 in one operation with swapAndExecute.
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. Calldata is encoded with viem. Confirm the Pool address in Aave's address book before use.
Example
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<boolean>;
// 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
- The review hook receives
intent:executeRequirementholds the estimated deposit, gas, and approval, andquoteholds the funding quote whenswapRequiredistrue. - After you call
allow(), the SDK swaps the sources into Base USDC and waits for fulfillment. - The SDK measures the USDC that arrived and calls
beforeExecutewithexecuteAmountRaw, the amount available for the call. - Your wallet approves the pool for
executeAmountRawif needed, then sends thesupplytransaction 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 |
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
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.
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);
}onBehalfOfmust be the user's address. Relay's router is the caller, so the contract must support acting on behalf of the recipient.beforeExecuteruns before review and again on every refresh.executeAmountRawis 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
executeorapproval.
How is this guide?