Swap Features

Learn how to perform and monitor swap operations with AaveKit.


Alongside the Position Swaps feature, AaveKit provides the following tools for swapping:

Swaps are implemented using a modular provider architecture. Currently, CoW Protocol is the swap provider, offering MEV protection and optimal execution through batch auctions.

Review the fees section to understand the swap costs.

Swap Tokens

Token swaps can be executed in two ways:

  • Intent-based: The swap is signed off-chain and submitted as an intent, which is then executed by a solver. This is the preferred method as it is gasless for users.

  • Transaction-based: The swap is executed directly on-chain via a transaction. This method is used when intent-based execution is not available.

Native token swaps are always transaction-based since users must send funds directly.

Swapping tokens involves the following steps:

  1. Select the token to swap from

  2. Select the token to swap to

  3. Choose between market order and limit order

  4. Execute the swap operation

Source Token

Fetch user token balances that can be swapped. This allows you to display only tokens the user owns that are swappable, making source token selection easier.

Use the useUserBalances hook with the swappable filter to list user tokens that can be swapped.

import type { UserBalancesRequest, TokenAmount } from "@aave/react";import { useUserBalances } from "@aave/react";
function SwapFromToken({  request,  onSelect,}: {  request: UserBalancesRequest;  onSelect: (amount: TokenAmount) => void;}) {  const { data, loading, error } = useUserBalances(request);
  return (    <select      disabled={loading || error}      onChange={(e) => {        const balance = data.find((b) => b.id === e.target.value);        if (balance) onSelect(balance.balances[0]);      }}    >      {data.map((balance) => (        <option key={balance.id} value={balance.id}>          {balance.info.symbol} ({balance.totalAmount.value.toDisplayString(2)})        </option>      ))}    </select>  );}

You can query for swappable token balances as follows:

import { evmAddress, chainId, OrderDirection } from "@aave/react";
const request: UserBalancesRequest = {  user: evmAddress("0x456…"),  filter: {    swappable: {      chainIds: [chainId(1)],    },  },  orderBy: { balance: OrderDirection.Desc },};

For more details on filtering and sorting user balances, see the User Balances documentation.

Destination Token

Once you've selected a source token, fetch the tokens you can swap TO. This ensures you only display valid swap destinations for the selected source token.

Use useSwappableTokens to list destination tokens based on your selected source token.

import type { SwappableTokensRequest, Token } from "@aave/react";import { useSwappableTokens } from "@aave/react";
function SwapToToken({  request,  onSelect,}: {  request: SwappableTokensRequest;  onSelect: (token: Token) => void;}) {  const { data, loading, error } = useSwappableTokens(request);
  return (    <>      <select        disabled={loading}        onChange={(e) => {          const token = data.find((t) => t.info.address === e.target.value);          if (token) onSelect(token);        }}      >        {data.map((token) => (          <option key={token.info.address} value={token.info.address}>            {token.info.symbol} ({token.info.name})          </option>        ))}      </select>      {error && <p>{error.message}</p>}    </>  );}

You can query for a list of swappable tokens as follows:

const request: SwappableTokensRequest = {  query: {    chainIds: [chainId(1)],  },};

Market or Limit Order

Once you've identified the tokens to swap, the next step is choosing an order type. Market orders execute immediately at the current rate, while limit orders wait until your specified price conditions are met.

To place a market order, specify the token pair, amount, and swap direction.

const request: TokenSwapQuoteRequest = {  market: {    chainId: chainId(1),    buy: { erc20: evmAddress("0xA0b86a33E6…") },    sell: { erc20: evmAddress("0x6B175474E…") },    amount: bigDecimal("1000"),    kind: TokenSwapKind.Buy,    user: evmAddress("0x742d35cc…"), // Your address    receiver: evmAddress("0x742d35cc…"), // In case the receiver is different from the user    selectedSlippage: bigDecimal("2"), // 2%  },};

The request takes the following parameters:

  • chainId — The target chain

  • sell — The token to sell (native or erc20)

  • buy — The token to receive (native or erc20)

  • amount — The swap amount, interpreted based on kind

  • kind — Sell (default) or Buy, determines how amount is interpreted

  • user — The sender address

  • receiver — The recipient address (defaults to user)

  • selectedSlippage — Optional slippage tolerance to use instead of the suggested slippage

Since only one amount is provided, the swap kind determines what gets calculated:

Swap KindUser inputs…System calculates…
SellHow much of the token to sell and which token to buyThe amount of the buy token you'll receive
BuyHow much of the token to buy and which token to sellThe amount of the sell token you need to provide

Depending on the token pair, swaps can be performed in different ways:

const request: TokenSwapQuoteRequest = {  market: {    chainId: chainId(1),    buy: { erc20: evmAddress("0xA0b86a33E6…") },    sell: { erc20: evmAddress("0x6B175474E…") },    amount: bigDecimal("1000"),    user: evmAddress("0x742d35cc…"), // Your address  },};

Execute Swaps

To execute the swap operation with AaveKit React, follow these steps.

1

Get Swap Quote

First, use the useTokenSwapQuote hook (or the imperative useTokenSwapQuoteAction variant) to get a swap quote.

import { type TokenSwapQuoteRequest, useTokenSwapQuote } from "@aave/react";
function DisplayQuote({ request }: { request: TokenSwapQuoteRequest }) {  const { data, loading, error } = useTokenSwapQuote(request);
  if (loading) return <p>Loading…</p>;
  if (error) {    if (error.name === "ValidationError") {      switch (error.cause.__typename) {        case "InsufficientLiquidityError":          return <p>Not enough liquidity for this swap</p>;      }    }    return <p>{error.message}</p>;  }
  return (    <div>      <h3>Swap Quote</h3>      <h4>{data.quoteId}</h4>      <p>Suggested slippage: {data.suggestedSlippage}</p>      <p>Costs: {data.costs}</p>    </div>  );}

The useTokenSwapQuoteAction hook does not watch for updates. Use it when you need on-demand, fresh data (e.g., in an event handler).

You can specify a different currency to return fiat amounts in.

import { Currency } from "@aave/react";
const { data, error, loading } = useTokenSwapQuote({  ...request,  currency: Currency.Eur,});

The SwapQuote object provides detailed pricing information for swap operations.

interface SwapQuote {  __typename: "SwapQuote";  accuracy: QuoteAccuracy;  quoteId: SwapId;  suggestedSlippage: PercentNumber;  selectedSlippage: PercentNumber | null;  buy: TokenAmount;  sell: TokenAmount;  finalBuy: TokenAmount;  finalSell: TokenAmount;  costs: SwapQuoteCosts;}

Where:

  • accuracy — quote accuracy level (QuoteAccuracy.Fast or QuoteAccuracy.Accurate)

  • quoteId — a unique quote identifier required for executing the swap

  • buy and sell — current prices before fees

  • finalBuy and finalSell — amounts you'll receive/pay after fees and slippage

  • costs — a breakdown of all fees (partner fee, provider fee, flash loan fee, network costs)

  • suggestedSlippage — recommended slippage tolerance

  • selectedSlippage — the slippage tolerance selected when requesting a market quote (if provided)

For limit orders, consider fetching a market quote first to get current buy and sell values. Use these as a baseline to repeat the swap quote with a limit order request.

2

Configure Wallet Integration

Then, instantiate the hooks for the wallet library of your choice:

  • useSendTransaction — used to send ERC-20 approval and swap transactions when needed

  • useSignTypedData — used to sign ERC-20 permits and swap order typed data

Viem
import { useWalletClient } from "wagmi";import { useSendTransaction, useSignTypedData } from "@aave/react/viem";
// …
const { data: wallet } = useWalletClient();const [sendTransaction] = useSendTransaction(wallet);const [signTypedData] = useSignTypedData(wallet);

3

Define the Swap Flow

Then, use the useTokenSwap hook to handle the operation.

import { useTokenSwap } from "@aave/react";
// …
const [swap, { loading, error }] = useTokenSwap((plan) => {  switch (plan.__typename) {    case "Erc20Approval":      if (plan.bySignature) {        return signTypedData(plan.bySignature);      }      return sendTransaction(plan.byTransaction);
    case "SwapTransactionRequest":      return sendTransaction(plan.transaction);
    case "SwapTypedData":      return signTypedData(plan);  }});

4

Execute the Swap Operation

Then, execute the swap. The approach differs based on order type: market orders execute immediately at current prices, while limit orders let you set specific price conditions.

For market orders, call swap with the quoteId from the last market order quote.

Basic Usage
const execute = async (quote: SwapQuote) => {  const result = await swap({    fromQuote: {      quoteId: quote.quoteId,    },  });
  if (result.isErr()) {    console.error(result.error);    return;  }
  // result.value: SwapReceipt  console.log("Swap order placed:", result.value);};

5

Handle the Result

Finally, handle the result.

Handle Result
if (result.isErr()) {  switch (result.error.name) {    case "CancelError":      // The user cancelled the operation      return;
    case "SigningError":      console.error(`Failed to sign: ${result.error.message}`);      break;
    case "TimeoutError":      console.error(`Transaction timed out: ${result.error.message}`);      break;
    case "TransactionError":      console.error(`Transaction failed: ${result.error.message}`);      break;
    case "ValidationError":      switch (result.error.cause.__typename) {        case "InsufficientBalanceError":          console.error(            `Insufficient balance: ${result.error.cause.required.value.toDisplayString(2)} required.`,          );          break;        case "InsufficientLiquidityError":          console.error(`Insufficient liquidity: ${result.error.cause.reason}`);          break;      }      break;
    case "UnexpectedError":      console.error(result.error.message);      break;  }  return;}// result.value: SwapReceiptconsole.log("Swap order placed:", result.value);

That's it—you placed your first swap order. Keep track of its status in the Monitor Swap Status section.

Monitor Swap Status

Once the swap is executed, a SwapReceipt object is returned with an id field. Use this id to track the swap status. For more details, see the Execute Swaps section.

Use the useSwapStatus hook to monitor the status of a swap in real-time.

import { useSwapStatus } from "@aave/react";
function MonitorSwap({ swapReceipt }: { swapReceipt: SwapReceipt }) {  const { data, loading, error } = useSwapStatus({    id: swapReceipt.id,  });
  if (loading) return <p>Monitoring swap…</p>;
  if (error) return <p>Error: {error.message}</p>;
  switch (data.__typename) {    case "SwapOpen":      return <p>Swap is open, waiting for execution…</p>;
    case "SwapPendingSignature":      return <p>Waiting for signature…</p>;
    case "SwapFulfilled":      return (        <div>          <p>Swap completed successfully!</p>          <a href={data.explorerUrl}>View transaction</a>        </div>      );
    case "SwapCancelled":      return <p>Swap was cancelled at {data.cancelledAt}</p>;
    case "SwapExpired":      return <p>Swap expired at {data.expiredAt}</p>;  }}

The hook automatically polls for status updates until the swap reaches a terminal state: fulfilled, cancelled, or expired.

User Swaps

List and filter user swaps with built-in pagination support.

Use the useUserSwaps hook to list user swaps.

import { useUserSwaps, evmAddress, chainId } from "@aave/react";
function ListUserSwaps({ request }: { request: UserSwapsRequest }) {  const { data, loading, error } = useUserSwaps(request);
  if (loading) return <div>Loading…</div>;
  if (error) return <div>Error: {error.message}</div>;
  // data.items: Array<SwapStatus>  return (    <div>      <h3>User Swaps</h3>      {data.items.map((swapStatus) => (        <div key={swapStatus.createdAt}>          <p>            Status - {swapStatus.__typename} - {swapStatus.explorerUrl}          </p>        </div>      ))}    </div>  );}

The hook automatically polls for status updates until all listed swaps reach a terminal state: fulfilled, cancelled, or expired.

You can filter swaps as follows:

import { evmAddress, chainId } from "@aave/react";
const request: UserSwapsRequest = {  user: evmAddress("0x742d35cc…"),  chainId: chainId(1),};

Cancel a Swap

Before cancelling, identify the swap by its status.

Only swaps with status Open can be cancelled.

User Swaps
const swapsItems: SwapStatus[] = [  {    __typename: "SwapOpen",    swapId: "adb123…",    // …  },  {    __typename: "SwapPendingSignature",    // …  },  {    __typename: "SwapCancelled",    // …  },  {    __typename: "SwapExpired",    // …  },  {    __typename: "SwapFulfilled",    // …  },];

After identifying the swap, follow the steps below to cancel it with AaveKit.

To cancel a swap with AaveKit React:

1

Configure Wallet Integration

First, instantiate the useSendTransaction and useSignTypedData hooks for the wallet library of your choice.

Viem
import { useWalletClient } from "wagmi";import { useSendTransaction, useSignTypedData } from "@aave/react/viem";
// …
const { data: wallet } = useWalletClient();const [sendTransaction] = useSendTransaction(wallet);const [signTypedData, signing] = useSignTypedData(wallet);

2

Define the Cancel Flow

Then, use the useCancelSwap hook to prepare the cancel swap operation.

import { useCancelSwap } from "@aave/react";
const [cancelSwap, { loading, error }] = useCancelSwap(  (plan: CancelSwapTypedData | TransactionRequest) => {    switch (plan.__typename) {      case "TransactionRequest":        return sendTransaction(plan);
      case "CancelSwapTypedData":        return signTypedData(plan);    }  },);

3

Execute the Cancel Swap Operation

Then, execute the cancel swap operation.

Cancel Swap
const execute = async () => {  const result = await cancelSwap({    id: swapReceipt.id,  });};

4

Handle the Result

Finally, handle the result.

Handle Result
if (result.isErr()) {  switch (result.error.name) {    case "CancelError":      // The user cancelled the operation      return;
    case "SigningError":      console.error(`Failed to sign the transaction: ${result.error.message}`);      break;
    case "TimeoutError":      console.error(`Transaction timed out: ${result.error.message}`);      break;
    case "TransactionError":      console.error(`Transaction failed: ${result.error.message}`);      break;
    case "UnexpectedError":      console.error(result.error.message);      break;  }  return;}
// result.value: SwapCancelledResultconsole.log("Swap cancelled:", result.value.cancelledAt);

That's it—you cancelled your first swap. Keep track of its status in the Monitor Swap Status section.

Fees

Swaps include a network fee, partner fee, provider fee, and in the case of Position Swaps a flash loan fee.

Network Fee

The network fee covers gas costs for executing the swap transaction on-chain. This fee varies based on current network congestion and is paid in the chain's native token (e.g., ETH on Ethereum).

Partner Fee

Aave Labs applies a fee to swaps done through the Aave Labs API. The fee depends on the swap operation and token pair:

  • Borrow Swap: 0% fee across all pairs.

  • All other swap operations: a discounted fee of 0.15% is applied to swaps between correlated assets (e.g., ETH/wstETH), while 0.25% is applied to all other pairs.

Correlated assets are tokens that maintain a stable price relationship, typically pegged to the same underlying asset or index. Swaps between correlated assets receive a discounted fee because they carry lower price risk.

The following asset groups are considered correlated (list reviewed and updated periodically):

  • Stablecoins: USDC, USDT, DAI, GHO, EURC, USDbC, USDe, USDS, sUSDe, RLUSD, PYUSD, LUSD, sDAI, crvUSD, USD₮0, USDC.e, EURe, xDAI, wxDAI

  • ETH correlated: weETH, ETH, WETH, wstETH, cbETH, ezETH, wrsETH, osETH, rETH, ETHx

  • BTC correlated: cbBTC, WBTC, LBTC, tBTC, eBTC

Provider Fee

CoW Protocol charges a fee for swap execution: 0.02% for most pairs, reduced to 0.003% for tokens in their Reduced Fee Tokens list (a dynamic registry of major stablecoins and RWAs based on price volatility). The fee is included in the swap quote.

Flash Loan Fee

Some of the Position Swaps require flash loans to execute atomically. When applicable, a flash loan fee of 0.05% of the flash-loaned amount is charged. This fee is collected by the protocol treasury and controlled by the DAO through governance.

Flash loan fees apply to operations that require borrowing assets temporarily (e.g., Swap Borrow Asset). Operations that use existing positions (e.g., Withdraw and Swap) typically do not incur flash loan fees.