Aave V4 Swap Features
Alongside the Position Swaps feature, AaveKit provides the following tools for swapping:
- Swap Tokens: exchange tokens instantly at market price or set conditions with a limit order.
- Monitor Swap Status: track the status of a swap order.
- User Swaps: list and filter user orders.
- Cancel a Swap: cancel a swap order in progress.
The swap API is deprecated. Move to the Order API:
prepare→Token Swap prepareOrder swap→submitOrder swap→Status orderStatus user→Swaps pendingOrders prepare→Swap Cancel prepareCancel Order cancel→Swap cancelOrder - React:
use→Token Swap useToken Swap Order - React:
use→Swap Status useOrder Status - React:
use→User Swaps usePending Orders - React:
use→Cancel Swap useCancel Order
Swaps are implemented using a modular provider architecture. Currently, CoW Protocol is the swap provider, offering MEV protection and optimal execution through batch auctions.
See fees for 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:
- Select the token to swap from
- Select the token to swap to
- Choose between market order and limit order
- 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 use hook in React, the user action in TypeScript, or the user query in GraphQL 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);
if (loading) return <p>Loading…</p>;
if (error) return <p>{error.message}</p>;
return (
<select
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>
);
}
import {
type UserBalancesRequest,
type TokenAmount,
useUserBalances,
} from "@aave/react";
function SwapFromToken({
request,
onSelect,
}: {
request: UserBalancesRequest;
onSelect: (amount: TokenAmount) => void;
}) {
const { data } = useUserBalances({
...request,
suspense: true,
});
if (data.length === 0) return <p>No swappable tokens found</p>;
return (
<select
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>
);
}
import { userBalances } from "@aave/client/actions";
import { evmAddress, chainId, OrderDirection } from "@aave/client";
import { client } from "./client";
const result = await userBalances(client, {
user: evmAddress("0x456…"),
filter: {
swappable: {
chainIds: [chainId(1)],
},
},
orderBy: { balance: OrderDirection.Desc },
});
if (result.isErr()) {
console.error(result.error);
} else {
// result.value: UserBalance[]
result.value.forEach((balance) => {
console.log(
`${balance.info.symbol}: ${balance.totalAmount.value.toDisplayString(2)}`,
);
});
}
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
query ($request: UserBalancesRequest!) {
userBalances(request: $request) {
info {
__typename
... on Erc20Token {
symbol
name
address
chainId
}
... on NativeToken {
symbol
name
chainId
}
}
balances {
...TokenAmount
}
totalAmount {
value
}
}
}
fragment TokenAmount on TokenAmount {
__typename
... on Erc20Amount {
amount {
value
}
token {
address
chainId
}
}
... on NativeAmount {
amount {
value
}
token {
chainId
}
}
}
- React: You can query for swappable token balances as follows.
- GraphQL: Use the
filter.field to filter by swappable tokens.swappable
import { evmAddress, chainId, OrderDirection } from "@aave/react";
const request: UserBalancesRequest = {
user: evmAddress("0x456…"),
filter: {
swappable: {
chainIds: [chainId(1)],
},
},
orderBy: { balance: OrderDirection.Desc },
};
import { evmAddress, chainId, OrderDirection } from "@aave/react";
const request: UserBalancesRequest = {
user: evmAddress("0x456…"),
filter: {
swappable: {
chainIds: [chainId(1)],
},
},
orderBy: { balance: OrderDirection.Desc },
includeZeroBalances: true,
};
{
"request": {
"user": "0x456…",
"filter": {
"swappable": {
"chainIds": [1]
}
},
"orderBy": {
"balance": "DESC"
}
}
}
{
"request": {
"user": "0x456…",
"filter": {
"swappable": {
"chainIds": [1]
}
},
"orderBy": {
"balance": "DESC"
},
"includeZeroBalances": true
}
}
See User Balances for filtering and sorting.
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 use in React, swappable in TypeScript, or the swappable query in GraphQL 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);
if (loading) return <p>Loading…</p>;
if (error) return <p>{error.message}</p>;
return (
<select
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>
);
}
import type { SwappableTokensRequest, Token } from "@aave/react";
import { useSwappableTokens } from "@aave/react";
function SwapToToken({
request,
onSelect,
}: {
request: SwappableTokensRequest;
onSelect: (token: Token) => void;
}) {
const { data } = useSwappableTokens({
...request,
suspense: true,
});
return (
<select
disabled={data.length === 0}
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>
);
}
import { chainId } from "@aave/client";
import { swappableTokens } from "@aave/client/actions";
import { client } from "./client";
const result = await swappableTokens(client, {
query: { chainIds: [chainId(1)] },
});
if (result.isErr()) {
console.error(result.error);
} else {
// result.value: Token[]
result.value.forEach((token) => {
console.log(`Token: ${token.info.symbol} (${token.info.name})`);
});
}
import { chainId, evmAddress } from "@aave/client";
import { swappableTokens } from "@aave/client/actions";
import { client } from "./client";
const result = await swappableTokens(client, {
query: {
from: {
erc20: { address: evmAddress("0xA0b86991…"), chainId: chainId(1) },
},
},
});
if (result.isErr()) {
console.error(result.error);
} else {
// result.value: Token[]
result.value.forEach((token) => {
console.log(`Token: ${token.info.symbol} (${token.info.name})`);
});
}
import { chainId, evmAddress } from "@aave/client";
import { swappableTokens } from "@aave/client/actions";
import { client } from "./client";
const result = await swappableTokens(client, {
query: {
tokens: [
{ address: evmAddress("0xA0b86991…"), chainId: chainId(1) },
{ address: evmAddress("0x6B175474E…"), chainId: chainId(1) },
],
},
});
if (result.isErr()) {
console.error(result.error);
} else {
// result.value: Token[]
result.value.forEach((token) => {
console.log(`Token: ${token.info.symbol} (${token.info.name})`);
});
}
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
query ($request: SwappableTokensRequest!) {
swappableTokens(request: $request) {
__typename
... on Erc20Token {
...Erc20Token
}
... on NativeToken {
...NativeToken
}
}
}
- React: You can query for a list of swappable tokens as follows.
- GraphQL: Use the
queryfield to select destination tokens by different criteria.
const request: SwappableTokensRequest = {
query: {
chainIds: [chainId(1)],
},
};
const request: SwappableTokensRequest = {
query: {
from: {
erc20: { address: evmAddress("0xA0b86991…"), chainId: chainId(1) },
},
},
};
const request: SwappableTokensRequest = {
query: {
tokens: [
{ address: evmAddress("0xA0b86991…"), chainId: chainId(1) },
{ address: evmAddress("0x6B175474E…"), chainId: chainId(1) },
],
},
};
{
"request": {
"query": {
"chainIds": [1]
}
}
}
{
"request": {
"query": {
"from": {
"erc20": { "address": "0xA0b86991…", "chainId": 1 }
}
}
}
}
{
"request": {
"query": {
"tokens": [
{ "address": "0xA0b86991…", "chainId": 1 },
{ "address": "0x6B175474E…", "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.
Market Orders
To place a market order, specify the token pair, amount, and swap direction.
const request: TokenSwapQuoteRequest = {
market: {
chainId: chainId(1),
buy: { erc20: evmAddress("0xA0b86991…") },
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%
},
};
{
"request": {
"market": {
"chainId": 1,
"buy": { "erc20": "0xA0b86991…" },
"sell": { "erc20": "0x6B175474E…" },
"amount": "1000",
"kind": "BUY",
"user": "0x742d35cc…", // Your address
"receiver": "0x742d35cc…", // In case the receiver is different from the user
"selectedSlippage": "2" // 2%
}
}
}
The request takes the following parameters:
chain: The target chainId sell: The token to sell (nativeorerc20)buy: The token to receive (nativeorerc20)amount: The swap amount, interpreted based onkindkind:TokenorSwap Kind. Sell Token, required; determines howSwap Kind. Buy amountis interpreteduser: The sender addressreceiver: The recipient address (defaults touser)selected: Optional slippage tolerance to use instead of the suggested slippageSlippage
Since only one amount is provided, the swap kind determines what gets calculated:
| Swap Kind | User inputs… | System calculates… |
|---|---|---|
| Sell | How much of the token to sell and which token to buy | The amount of the buy token you’ll receive |
| Buy | How much of the token to buy and which token to sell | The 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("0xA0b86991…") },
sell: { erc20: evmAddress("0x6B175474E…") },
amount: bigDecimal("1000"),
kind: TokenSwapKind.Sell,
user: evmAddress("0x742d35cc…"), // Your address
},
};
const request: TokenSwapQuoteRequest = {
market: {
chainId: chainId(1),
sell: { native: true },
buy: { erc20: evmAddress("0x6B175474E…") },
amount: bigDecimal("1000"),
kind: TokenSwapKind.Sell,
user: evmAddress("0x742d35cc…"), // Your address
},
};
const request: TokenSwapQuoteRequest = {
market: {
chainId: chainId(1),
sell: { erc20: evmAddress("0x6B175474E…") },
buy: { native: true },
amount: bigDecimal("1000"),
kind: TokenSwapKind.Sell,
user: evmAddress("0x742d35cc…"), // Your address
},
};
Limit Orders
To place a limit order, specify exact amounts for both the sell and buy tokens.
const request: TokenSwapQuoteRequest = {
limit: {
chainId: chainId(1),
sell: {
erc20: {
address: evmAddress("0x6B175474E…"),
value: bigDecimal("1000"),
},
},
buy: {
erc20: {
address: evmAddress("0xA0b86991…"),
value: bigDecimal("1000"),
},
},
user: evmAddress("0x742d35cc…"),
receiver: evmAddress("0x742d35cc…"),
// kind: TokenSwapKind.Buy, default is TokenSwapKind.Sell
// previousQuoteId: quote.quoteId, if available
},
};
{
"request": {
"limit": {
"chainId": 1,
"sell": {
"erc20": {
"address": "0x6B175474E…",
"value": "1000"
}
},
"buy": {
"erc20": {
"address": "0xA0b86991…",
"value": "1000"
}
},
"user": "0x742d35cc…",
"receiver": "0x742d35cc…",
"kind": "SELL"
}
}
}
The request takes the following parameters:
chain: The target chainId sell: The token and amount to sellbuy: The token and amount to receiveuser: The sender addressreceiver: The recipient address (defaults touser)kind: Sell (default) or Buy, determines which amount is honoredprevious: Optional quote ID from a prior market orderQuote Id deadline: Optional deadline as DateTime; if not provided, uses default expiry
With both amounts specified, the swap kind determines which amount takes priority:
| Swap Kind | User specifies… | What happens with the order |
|---|---|---|
| Sell | The exact sell amount and a minimum buy amount | The order is fulfilled when the buy price is large enough to accommodate the user’s ask and the swap costs |
| Buy | The exact buy amount and a maximum sell amount | The order is fulfilled when the sell price is small enough to accommodate the user’s desired buy and the swap costs |
Depending on the token pair, limit orders can be configured in different ways:
const request: TokenSwapQuoteRequest = {
limit: {
chainId: chainId(1),
sell: {
erc20: {
address: evmAddress("0x6B175474E…"),
value: bigDecimal("1000"),
},
},
buy: {
erc20: {
address: evmAddress("0xA0b86991…"),
value: bigDecimal("950"),
},
},
user: evmAddress("0x742d35cc…"),
},
};
const request: TokenSwapQuoteRequest = {
limit: {
chainId: chainId(1),
sell: {
erc20: {
address: evmAddress("0xA0b86991…"),
value: bigDecimal("3000"),
},
},
buy: {
native: bigDecimal("1"),
},
user: evmAddress("0x742d35cc…"),
},
};
Limit orders from native tokens are currently not supported via CoW Protocol to avoid holding user funds.
Execute Swaps
To execute the swap operation with AaveKit, follow these steps.
1. Get a Swap Quote
First, use the use hook (or the imperative use variant) in React, the token action in TypeScript, or the token query in GraphQL to get a swap quote. The quote’s order is the ID you pass to the Order API.
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.normalized.toFixed(2)}%</p>
<p>Network costs: {data.costs.networkCosts.amount.value.toDisplayString(2)}</p>
</div>
);
}
import { type TokenSwapQuoteRequest, useTokenSwapQuote } from "@aave/react";
function DisplayQuote({ request }: { request: TokenSwapQuoteRequest }) {
const { data } = useTokenSwapQuote({
...request,
suspense: true,
});
if (!data) return <p>No quote found</p>;
return (
<div>
<h3>Swap Quote</h3>
<h4>{data.quoteId}</h4>
<p>Suggested slippage: {data.suggestedSlippage.normalized.toFixed(2)}%</p>
<p>Network costs: {data.costs.networkCosts.amount.value.toDisplayString(2)}</p>
</div>
);
}
import {
type TokenSwapQuoteRequest,
useTokenSwapQuoteAction,
} from "@aave/react";
// …
const [getQuote, { called, data, error, loading }] = useTokenSwapQuoteAction();
const handler = async (request: TokenSwapQuoteRequest) => {
const result = await getQuote(request);
if (result.isOk()) {
console.log(result.value); // SwapQuote
} else {
console.error(result.error);
}
};
import { tokenSwapQuote } from "@aave/client/actions";
import { client } from "./client";
// …
const result = await tokenSwapQuote(client, request);
if (result.isErr()) {
console.error(result.error);
return;
}
// result.value.quote: SwapQuote
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
query ($request: TokenSwapQuoteRequest!, $currency: Currency = USD) {
tokenSwapQuote(request: $request) {
__typename
... on SwapByIntent {
...SwapByIntent
}
... on SwapByIntentWithApprovalRequired {
...SwapByIntentWithApprovalRequired
}
... on SwapByTransaction {
...SwapByTransaction
}
... on SwapByTransactionWithApprovalRequired {
...SwapByTransactionWithApprovalRequired
}
... on InsufficientLiquidityError {
reason
}
}
}
fragment SwapByIntent on SwapByIntent {
__typename
quote {
...SwapQuote
}
}
fragment SwapByIntentWithApprovalRequired on SwapByIntentWithApprovalRequired {
__typename
approvals {
...Erc20Approval
}
quote {
...SwapQuote
}
}
fragment SwapByTransaction on SwapByTransaction {
__typename
quote {
...SwapQuote
}
}
fragment SwapByTransactionWithApprovalRequired on SwapByTransactionWithApprovalRequired {
__typename
quote {
...SwapQuote
}
approvals {
...Erc20Approval
}
}
fragment SwapQuote on SwapQuote {
__typename
accuracy
quoteId
suggestedSlippage {
...PercentNumber
}
selectedSlippage {
...PercentNumber
}
buy {
...TokenAmount
}
sell {
...TokenAmount
}
costs {
__typename
networkCosts {
...TokenAmount
}
partnerFee {
...TokenAmount
}
flashloanFee {
...TokenAmount
}
providerFee {
...TokenAmount
}
}
finalBuy {
...TokenAmount
}
finalSell {
...TokenAmount
}
orderQuoteId
}
use doesn’t watch for updates. Use it for on-demand
fresh data, such as 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,
});
import { Currency } from "@aave/react";
const [getQuote, { called, data, error, loading }] = useTokenSwapQuoteAction({
currency: Currency.Eur,
});
import { Currency } from "@aave/client";
// …
const result = await tokenSwapQuote(client, request, {
currency: Currency.Eur,
});
{
"request": {
"market": {
"chainId": 1,
"buy": { "erc20": "0xA0b86991…" },
"sell": { "native": true },
"amount": "1000",
"kind": "SELL",
"user": "0x742d35cc…"
}
},
"currency": "EUR"
}
The Swap 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;
orderQuoteId: OrderQuoteId;
costs: SwapQuoteCosts;
}
interface SwapQuoteCosts {
__typename: "SwapQuoteCosts";
networkCosts: TokenAmount;
partnerFee: TokenAmount;
flashloanFee: TokenAmount | null;
providerFee: TokenAmount;
}
type TokenAmount = Erc20Amount | NativeAmount;
interface Erc20Amount {
__typename: "Erc20Amount";
token: Erc20Token;
amount: DecimalNumber;
exchange: ExchangeAmount;
exchangeRate: DecimalNumber;
}
interface NativeAmount {
__typename: "NativeAmount";
token: NativeToken;
amount: DecimalNumber;
exchange: ExchangeAmount;
exchangeRate: DecimalNumber;
}
Where:
accuracy: quote accuracy level (QuoteorAccuracy. Fast Quote)Accuracy. Accurate quote: a unique quote identifier required for executing the swapId buyandsell: token amounts before fees and slippagefinalandBuy final: amounts you’ll receive/pay after fees and slippageSell order: same value asQuote Id quote, typed for order operationsId costs: a breakdown of all fees (partner fee, provider fee, flash loan fee, network costs)suggested: recommended slippage tolerance based on asset volatility and order sizeSlippage selected: the slippage tolerance selected when requesting a market quote (if provided)Slippage
For limit orders, fetch a market quote first and use its buy and sell
values as a baseline.
In GraphQL, kind works as described in Market Orders.
In GraphQL, depending on the token pair, swaps can be performed in different ways:
{
"request": {
"market": {
"chainId": 1,
"buy": { "erc20": "0xA0b86991…" },
"sell": { "erc20": "0x6B175474E…" },
"amount": "1000",
"kind": "SELL", // SELL, BUY (see table above)
"user": "0x742d35cc…"
}
}
}
{
"request": {
"market": {
"chainId": 1,
"sell": { "native": true },
"buy": { "erc20": "0xA0b86991…" },
"amount": "1000",
"kind": "BUY", // SELL, BUY (see table above)
"user": "0x742d35cc…"
}
}
}
{
"request": {
"market": {
"chainId": 1,
"sell": { "erc20": "0xA0b86991…" },
"buy": { "native": true },
"amount": "1000",
"kind": "SELL", // SELL, BUY (see table above)
"user": "0x742d35cc…"
}
}
}
2. Configure Wallet Integration
Then, in React, instantiate the hooks for the wallet library of your choice:
use: used to send ERC-20 approval and swap transactions when neededSend Transaction use: used to sign ERC-20 permits and swap order typed dataSign Typed Data
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, in React, use the use hook to handle the operation. It fetches the quote, handles any ERC-20 approval, and prepares, signs, and submits the order.
import { useTokenSwapOrder } from "@aave/react";
// …
const [swap, { loading, error }] = useTokenSwapOrder((plan) => {
switch (plan.__typename) {
case "Erc20Approval":
if (plan.bySignature) {
return signTypedData(plan.bySignature);
}
return sendTransaction(plan.byTransaction);
case "OrderTransactionRequest":
return sendTransaction(plan.transaction);
case "OrderTypedData":
return signTypedData(plan);
}
});
import { useTokenSwapOrder } from "@aave/react";
// …
const [status, setStatus] = useState("");
const [swap, { loading, error }] = useTokenSwapOrder((plan) => {
switch (plan.__typename) {
case "Erc20Approval":
setStatus("Please approve the token in your wallet");
if (plan.bySignature) {
return signTypedData(plan.bySignature).andTee(() =>
setStatus("Signing permit…"),
);
}
return sendTransaction(plan.byTransaction).andTee(() =>
setStatus("Approving…"),
);
case "OrderTransactionRequest":
setStatus("Please sign the Swap transaction in your wallet");
return sendTransaction(plan.transaction).andTee(() =>
setStatus("Swapping…"),
);
case "OrderTypedData":
setStatus("Please sign the Swap Order in your wallet");
return signTypedData(plan).andTee(() => setStatus("Signing…"));
}
});
import { useTokenSwapOrder } from "@aave/react";
// …
const [swap, { loading, error }] = useTokenSwapOrder((plan, { cancel }) => {
if (window.confirm("Are you sure you want to continue?") === false) {
return cancel("User cancelled the operation");
}
switch (plan.__typename) {
case "Erc20Approval":
if (plan.bySignature) {
return signTypedData(plan.bySignature);
}
return sendTransaction(plan.byTransaction);
case "OrderTransactionRequest":
return sendTransaction(plan.transaction);
case "OrderTypedData":
return signTypedData(plan);
}
});
4. Handle Approvals
Then, in TypeScript, using helpers from the wallet library of your choice, use sign and send to handle ERC-20 approvals, and prepare with sign to sign intent-based swap orders. Each branch resolves to a Submit for the next step.
import { tokenSwapQuote } from "@aave/client/actions";
import { client } from "./client";
import { handleApprovals } from "./handleApprovals";
// …
const result = await tokenSwapQuote(client, request) //
.andThen(handleApprovals);
import { type TokenSwapQuoteResult, okAsync } from "@aave/client";
import { signTypedDataWith, sendWith } from "@aave/client/viem";
import { wallet } from "./wallet";
import { signSwapOrder } from "./signSwapOrder";
// …
export function handleApprovals(result: TokenSwapQuoteResult) {
switch (result.__typename) {
case "SwapByTransaction":
return okAsync({
byTransaction: { quoteId: result.quote.orderQuoteId },
});
case "SwapByTransactionWithApprovalRequired":
return result.approvals
.reduce(
(acc, approval) =>
acc.andThen(() => sendWith(wallet, approval.byTransaction)),
okAsync<unknown, unknown>(undefined),
)
.map(() => ({
byTransaction: { quoteId: result.quote.orderQuoteId },
}));
case "SwapByIntent":
return signSwapOrder({ quoteId: result.quote.orderQuoteId });
case "SwapByIntentWithApprovalRequired":
// Permit-based approval (only available for single approval)
if (result.approvals.length === 1 && result.approvals[0].bySignature) {
const permitTypedData = result.approvals[0].bySignature;
return signTypedDataWith(wallet, permitTypedData)
.map((signature) => ({
deadline: permitTypedData.message.deadline,
value: signature,
}))
.andThen((permitSig) =>
signSwapOrder({ quoteId: result.quote.orderQuoteId, permitSig }),
);
}
// Transaction-based approval (handles multiple approvals for USDT-like tokens)
return result.approvals
.reduce(
(acc, approval) =>
acc.andThen(() => sendWith(wallet, approval.byTransaction)),
okAsync<unknown, unknown>(undefined),
)
.andThen(() => signSwapOrder({ quoteId: result.quote.orderQuoteId }));
case "InsufficientLiquidityError":
throw new Error(`Insufficient liquidity: ${result.reason}`);
}
}
import type { PrepareOrderRequest } from "@aave/client";
import { prepareOrder } from "@aave/client/actions";
import { signTypedDataWith } from "@aave/client/viem";
import { client } from "./client";
import { wallet } from "./wallet";
export function signSwapOrder(request: PrepareOrderRequest) {
return prepareOrder(client, request).andThen(({ newQuoteId, data }) =>
signTypedDataWith(wallet, data).map((signature) => ({
bySignature: { quoteId: newQuoteId, signature },
})),
);
}
import { createWalletClient, http, type Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
export const wallet = createWalletClient({
account: privateKeyToAccount(process.env.PRIVATE_KEY as Hex),
chain: mainnet,
transport: http(),
});
For tokens that need an allowance reset, like USDT on Ethereum
Mainnet,
send sends both approvals before the swap: reset to 0, then the new
allowance.
5. Handle Swap Quote Response
In GraphQL, depending on the Token type, you’ll need to follow different actions:
| Response Type | Action |
|---|---|
Swap | Pass the quote. to the prepare query. |
Swap | Send each transaction in approvals from the user account, then pass the quote. to prepare. |
Swap | Keep a note of the quote.. |
Swap | Send each transaction from approvals in sequence, then keep a note of the quote.. |
Insufficient | Not enough liquidity available for this swap. Display the error to the user or suggest a smaller amount. |
Below are examples illustrating the response for each response type.
{
"data": {
"tokenSwapQuote": {
"__typename": "SwapByIntent",
"quote": {
"orderQuoteId": "0x8f3…"
// …
}
}
}
}
{
"data": {
"tokenSwapQuote": {
"__typename": "SwapByIntentWithApprovalRequired",
"approvals": [
{
// …
}
],
"quote": {
"orderQuoteId": "0x8f3…"
// …
}
}
}
}
{
"data": {
"tokenSwapQuote": {
"__typename": "SwapByTransaction",
"quote": {
"orderQuoteId": "0x8f3…"
// …
}
}
}
}
{
"data": {
"tokenSwapQuote": {
"__typename": "SwapByTransactionWithApprovalRequired",
"approvals": [
{
// …
}
],
"quote": {
"orderQuoteId": "0x8f3…"
// …
}
}
}
}
{
"data": {
"tokenSwapQuote": {
"__typename": "InsufficientLiquidityError",
"reason": "Not enough liquidity available for this swap amount"
}
}
}
6. Prepare the Swap
Then, in GraphQL, use the prepare query to prepare the swap order.
query ($request: PrepareOrderRequest!) {
prepareOrder(request: $request) {
__typename
... on PreparedOrder {
...PreparedOrder
}
... on InsufficientBalanceError {
...InsufficientBalanceError
}
}
}
fragment PreparedOrder on PreparedOrder {
__typename
newQuoteId
data {
...OrderTypedData
}
}
fragment OrderTypedData on OrderTypedData {
__typename
types
primaryType
domain {
...DomainData
}
message
}
Define Prepare taking the quote. from the previous step and the permit signature when needed.
{
"request": {
"quoteId": "<quote.orderQuoteId>"
}
}
{
"request": {
"quoteId": "<quote.orderQuoteId>",
"permitSig": { "value": "<signature>", "deadline": <approval.bySignature.message.deadline> }
}
}
7. Handle Prepare Swap Response
In GraphQL, depending on the Prepare type, you’ll need to follow different actions:
| Response Type | Action |
|---|---|
Prepared | Sign the typed data with the account performing the swap. |
Insufficient | Notify the user that their balance is insufficient to perform the swap. |
Below are examples illustrating the response for each response type.
{
"data": {
"prepareOrder": {
"__typename": "PreparedOrder",
"newQuoteId": "0x8f3…",
"data": {
// …
}
}
}
}
{
"data": {
"prepareOrder": {
"__typename": "InsufficientBalanceError",
"required": {
"value": "150.000000",
"decimals": 6
},
"available": {
"value": "75.500000",
"decimals": 6
}
}
}
}
8. Execute the Swap
Then, execute the swap. In React, 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 quote from the last market order quote.
const execute = async (quote: SwapQuote) => {
const result = await swap({
fromQuote: {
quoteId: quote.quoteId,
},
});
if (result.isErr()) {
console.error(result.error);
return;
}
// result.value: OrderReceipt
console.log("Swap order placed:", result.value);
};
For limit orders, call swap with a limit request that includes the previous from the last swap quote.
const execute = async ({ limit }: TokenSwapQuoteRequest) => {
const result = await swap({
limit: {
...limit,
previousQuoteId: quote.quoteId, // from prior market order quote
},
});
if (result.isErr()) {
console.error(result.error);
return;
}
// result.value: OrderReceipt
console.log("Swap order placed:", result.value);
};
- TypeScript: Use the
submitaction to submit the swap order. For transaction-based swaps, use theOrder sendhelper to send the transaction.With - GraphQL: Submit the swap order using the
submitmutation.Order
import { okAsync } from "@aave/client";
import { submitOrder, tokenSwapQuote } from "@aave/client/actions";
import { sendWith } from "@aave/client/viem";
import { client } from "./client";
import { handleApprovals } from "./handleApprovals";
import { wallet } from "./wallet";
const result = await tokenSwapQuote(client, request)
.andThen(handleApprovals)
.andThen((request) => submitOrder(client, request))
.andThen((plan) => {
switch (plan.__typename) {
case "OrderReceipt":
return okAsync(plan);
case "OrderTransactionRequest":
return sendWith(wallet, plan.transaction).map(() => plan.orderReceipt);
}
});
// …
import { createWalletClient, http, type Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
export const wallet = createWalletClient({
account: privateKeyToAccount(process.env.PRIVATE_KEY as Hex),
chain: mainnet,
transport: http(),
});
mutation ($request: SubmitOrderRequest!) {
submitOrder(request: $request) {
__typename
... on OrderTransactionRequest {
...OrderTransactionRequest
}
... on OrderReceipt {
...OrderReceipt
}
... on InsufficientBalanceError {
...InsufficientBalanceError
}
}
}
fragment OrderTransactionRequest on OrderTransactionRequest {
__typename
transaction {
...TransactionRequest
}
orderReceipt {
...OrderReceipt
}
}
fragment OrderReceipt on OrderReceipt {
__typename
orderId
}
For intent swaps, pass the new from prepare and the signature in by. For transaction swaps, pass the quote. from token in by.
{
"request": {
"bySignature": {
"quoteId": "<prepareOrder.newQuoteId>",
"signature": "0x456…"
}
}
}
{
"request": {
"byTransaction": {
"quoteId": "<tokenSwapQuote.quote.orderQuoteId>"
}
}
}
9. Handle the Result
Finally, handle the 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: OrderReceipt
console.log("Swap order placed:", result.value.orderId);
// …
if (result.isErr()) {
switch (result.error.name) {
case "CancelError":
console.error("Operation cancelled by user wallet");
break;
case "SigningError":
console.error("Signing failed:", 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("Unexpected error:", result.error.message);
break;
}
return;
}
// result.value: OrderReceipt
console.log("Swap order placed:", result.value.orderId);
In GraphQL, depending on the Order type, you’ll need to follow different actions:
| Response Type | Action |
|---|---|
Order | Send the transaction with the account performing the swap. |
Order | The swap order has been placed. |
Insufficient | Notify the user that their balance is insufficient to perform the swap. |
Below are examples illustrating the response for each response type.
{
"data": {
"submitOrder": {
"__typename": "OrderTransactionRequest",
"transaction": {
// …
},
"orderReceipt": {
"__typename": "OrderReceipt",
"orderId": "0x123…"
}
}
}
}
{
"data": {
"submitOrder": {
"__typename": "OrderReceipt",
"orderId": "0x123…"
}
}
}
{
"data": {
"submitOrder": {
"__typename": "InsufficientBalanceError",
"required": {
"value": "150.000000",
"decimals": 6
},
"available": {
"value": "75.500000",
"decimals": 6
}
}
}
}
You placed your first swap order. Keep track of its status in the Monitor Swap Status section.
Monitor Swap Status
Once the swap order is submitted, an Order object is returned with an order field. Use this order to track the order status.
For more details, see the Execute Swaps section.
- React: Use the
usehook to monitor the status of an order in real-time.Order Status - TypeScript: Use the
orderaction to check the current status of an order, orStatus waitto poll until it reaches a terminal state.For Order Outcome - GraphQL: Use the
orderquery to track the order status.Status
import { useOrderStatus, type OrderReceipt } from "@aave/react";
function MonitorSwap({ orderReceipt }: { orderReceipt: OrderReceipt }) {
const { data, loading, error } = useOrderStatus({
id: orderReceipt.orderId,
});
if (loading) return <p>Monitoring swap…</p>;
if (error) return <p>Error: {error.message}</p>;
switch (data.__typename) {
case "OrderOpen":
return <p>Swap is open, waiting for execution…</p>;
case "OrderPendingSignature":
return <p>Waiting for signature…</p>;
case "OrderFulfilled":
return (
<div>
<p>Swap completed successfully!</p>
<a href={data.explorerUrl}>View transaction</a>
</div>
);
case "OrderCancelled":
return <p>Swap was cancelled at {data.cancelledAt}</p>;
case "OrderExpired":
return <p>Swap expired at {data.expiredAt}</p>;
}
}
import { useOrderStatus, type OrderReceipt } from "@aave/react";
function MonitorSwap({ orderReceipt }: { orderReceipt: OrderReceipt }) {
const { data } = useOrderStatus({
id: orderReceipt.orderId,
suspense: true,
});
switch (data.__typename) {
case "OrderOpen":
return <p>Swap is open, waiting for execution…</p>;
case "OrderPendingSignature":
return <p>Waiting for signature…</p>;
case "OrderFulfilled":
return (
<div>
<p>Swap completed successfully!</p>
<a href={data.explorerUrl}>View transaction</a>
</div>
);
case "OrderCancelled":
return <p>Swap was cancelled at {data.cancelledAt}</p>;
case "OrderExpired":
return <p>Swap expired at {data.expiredAt}</p>;
}
}
import { orderStatus } from "@aave/client/actions";
import { client } from "./client";
const result = await orderStatus(client, { id: orderReceipt.orderId });
if (result.isErr()) {
console.error(result.error);
} else {
// result.value: OrderStatus | null
switch (result.value?.__typename) {
case "OrderOpen":
console.log("Swap is open, waiting for execution…");
break;
case "OrderPendingSignature":
console.log("Waiting for signature…");
break;
case "OrderFulfilled":
console.log("Swap completed:", result.value.txHash);
break;
case "OrderCancelled":
console.log("Swap was cancelled:", result.value.cancelledAt);
break;
case "OrderExpired":
console.log("Swap expired:", result.value.expiredAt);
break;
}
}
import { waitForOrderOutcome } from "@aave/client/actions";
import { client } from "./client";
const result = await waitForOrderOutcome(client)(orderReceipt);
// result.value: OrderFulfilled | OrderCancelled | OrderExpired
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
query {
orderStatus(request: { id: "<orderReceipt.orderId>" }) {
__typename
... on OrderOpen {
...OrderOpen
}
... on OrderPendingSignature {
...OrderPendingSignature
}
... on OrderCancelled {
...OrderCancelled
}
... on OrderExpired {
...OrderExpired
}
... on OrderFulfilled {
...OrderFulfilled
}
}
}
In React, the hook automatically polls for status updates until the order reaches a terminal state: fulfilled, cancelled, or expired.
In GraphQL, the Order object can be one of the following:
fragment OrderOpen on OrderOpen {
__typename
orderId
createdAt
deadline
explorerUrl
operation {
...OrderOperation
}
}
fragment OrderPendingSignature on OrderPendingSignature {
__typename
orderId
createdAt
deadline
explorerUrl
operation {
...OrderOperation
}
}
fragment OrderCancelled on OrderCancelled {
__typename
orderId
createdAt
cancelledAt
explorerUrl
operation {
...OrderOperation
}
}
fragment OrderExpired on OrderExpired {
__typename
orderId
createdAt
expiredAt
explorerUrl
operation {
...OrderOperation
}
}
fragment OrderFulfilled on OrderFulfilled {
__typename
orderId
createdAt
fulfilledAt
txHash
explorerUrl
operation {
...OrderOperation
}
}
fragment OrderOperation on OrderOperation {
__typename
... on TokenSwap {
sell {
...TokenAmount
}
buy {
...TokenAmount
}
kind
orderClass
}
... on SupplySwap {
sell {
...PositionAmount
}
buy {
...PositionAmount
}
kind
orderClass
}
... on BorrowSwap {
sell {
...PositionAmount
}
buy {
...PositionAmount
}
kind
orderClass
}
... on RepayWithSupply {
repay {
...PositionAmount
}
supply {
...PositionAmount
}
kind
orderClass
}
... on WithdrawSwap {
withdraw {
...PositionAmount
}
buy {
...TokenAmount
}
kind
orderClass
}
... on Leverage {
collateral {
...PositionAmount
}
debt {
...PositionAmount
}
multiplier
}
}
User Swaps
List and filter user orders with built-in pagination support.
Use the use hook in React, the pending action in TypeScript, or the pending query in GraphQL to list user orders. Use filter to narrow the list by order status.
import { usePendingOrders, type PendingOrdersRequest } from "@aave/react";
function ListUserOrders({ request }: { request: PendingOrdersRequest }) {
const { data, loading, error } = usePendingOrders(request);
if (loading) return <div>Loading…</div>;
if (error) return <div>Error: {error.message}</div>;
// data.items: Array<OrderStatus>
return (
<div>
<h3>User Orders</h3>
{data.items.map((order) => (
<div key={order.orderId}>
<p>
Status - {order.__typename} - {order.explorerUrl}
</p>
</div>
))}
</div>
);
}
import { usePendingOrders, type PendingOrdersRequest } from "@aave/react";
function ListUserOrders({ request }: { request: PendingOrdersRequest }) {
const { data } = usePendingOrders({
...request,
suspense: true,
});
if (data.items.length === 0) return <div>No orders found</div>;
return (
<div>
<h3>User Orders</h3>
{data.items.map((order) => (
<div key={order.orderId}>
<p>
Status - {order.__typename} - {order.explorerUrl}
</p>
</div>
))}
</div>
);
}
import { chainId, evmAddress } from "@aave/client";
import { pendingOrders } from "@aave/client/actions";
import { client } from "./client";
const result = await pendingOrders(client, {
chainId: chainId(1),
user: evmAddress("0x742d35cc…"),
});
if (result.isErr()) {
console.error(result.error);
}
import { chainId, evmAddress, OrderStatusFilter } from "@aave/client";
import { pendingOrders } from "@aave/client/actions";
import { client } from "./client";
const result = await pendingOrders(client, {
chainId: chainId(1),
user: evmAddress("0x742d35cc…"),
filterBy: [OrderStatusFilter.Open, OrderStatusFilter.PendingSignature],
});
if (result.isErr()) {
console.error(result.error);
}
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
query ($request: PendingOrdersRequest!) {
pendingOrders(request: $request) {
__typename
items {
...OrderStatus
}
pageInfo {
next
prev
}
}
}
fragment OrderStatus on OrderStatus {
__typename
... on OrderOpen {
...OrderOpen
}
... on OrderPendingSignature {
...OrderPendingSignature
}
... on OrderCancelled {
...OrderCancelled
}
... on OrderExpired {
...OrderExpired
}
... on OrderFulfilled {
...OrderFulfilled
}
}
In React, the hook automatically polls for status updates until all listed orders reach a terminal state: fulfilled, cancelled, or expired.
The Order fragments are listed under Monitor Swap Status.
- React: You can filter orders as follows.
- GraphQL: The
Pendingobject should be defined as follows.Orders Request
import { evmAddress, chainId } from "@aave/react";
const request: PendingOrdersRequest = {
user: evmAddress("0x742d35cc…"),
chainId: chainId(1),
};
import { evmAddress, chainId, OrderStatusFilter } from "@aave/react";
const request: PendingOrdersRequest = {
user: evmAddress("0x742d35cc…"),
chainId: chainId(1),
filterBy: [OrderStatusFilter.Fulfilled, OrderStatusFilter.Cancelled],
};
{
"request": {
"chainId": 1,
"user": "0x742d35cc…"
}
}
{
"request": {
"chainId": 1,
"user": "0x742d35cc…",
"filterBy": ["FULFILLED"]
}
}
Cancel a Swap
Before cancelling, identify the swap order by its status.
Only orders with status Order or Order can be
cancelled.
Pick the Order item (order) to cancel.
const orderItems: OrderStatus[] = [
{
__typename: "OrderOpen",
orderId: "0x123…",
// …
},
{
__typename: "OrderPendingSignature",
// …
},
{
__typename: "OrderCancelled",
// …
},
{
__typename: "OrderExpired",
// …
},
{
__typename: "OrderFulfilled",
// …
},
];
After identifying the order, follow the steps below to cancel it with AaveKit.
1. Configure Wallet Integration
First, in React, instantiate the use and use hooks for the wallet library of your choice.
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, in React, use the use hook to prepare the cancel order operation. The hook checks the order status first, then prepares, signs, and submits the cancellation.
import { useCancelOrder } from "@aave/react";
const [cancelOrder, { loading, error }] = useCancelOrder((plan) => {
switch (plan.__typename) {
case "TransactionRequest":
return sendTransaction(plan);
case "OrderTypedData":
return signTypedData(plan);
}
});
import { useCancelOrder } from "@aave/react";
const [status, setStatus] = useState("");
const [cancelOrder, { loading, error }] = useCancelOrder((plan) => {
switch (plan.__typename) {
case "TransactionRequest":
setStatus("Please send the Cancel Order transaction in your wallet");
return sendTransaction(plan).andTee(() => setStatus("Cancelling…"));
case "OrderTypedData":
setStatus("Please sign the Cancel Order data in your wallet");
return signTypedData(plan).andTee(() => setStatus("Cancelling…"));
}
});
import { useCancelOrder } from "@aave/react";
const [cancelOrder, { loading, error }] = useCancelOrder((plan, { cancel }) => {
if (window.confirm("Are you sure you want to cancel the swap?") === false) {
return cancel("User cancelled the operation");
}
switch (plan.__typename) {
case "TransactionRequest":
return sendTransaction(plan);
case "OrderTypedData":
return signTypedData(plan);
}
});
3. Prepare Cancel Swap
- TypeScript: Prepare the cancel typed data with
prepare.Cancel Order - GraphQL: Use the
preparequery to prepare the cancel.Cancel Order
import { prepareCancelOrder } from "@aave/client/actions";
import { client } from "./client";
const result = await prepareCancelOrder(client, {
id: order.orderId,
});
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
query {
prepareCancelOrder(request: { id: "<order.orderId>" }) {
__typename
data {
...OrderTypedData
}
}
}
fragment OrderTypedData on OrderTypedData {
__typename
types
primaryType
domain {
...DomainData
}
message
}
- TypeScript: Use
signfor the wallet library of your choice to sign the typed data inTyped Data With result..data - GraphQL: Sign the
dataof thePrepareresponse with the account that placed the order.Cancel Order Result
import { prepareCancelOrder } from "@aave/client/actions";
import { signTypedDataWith } from "@aave/client/viem";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await prepareCancelOrder(client, {
id: order.orderId,
}).andThen((prepared) => signTypedDataWith(wallet, prepared.data));
if (result.isErr()) {
console.error(result.error);
}
import { createWalletClient, http, type Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
export const wallet = createWalletClient({
account: privateKeyToAccount(process.env.PRIVATE_KEY as Hex),
chain: mainnet,
transport: http(),
});
{
"data": {
"prepareCancelOrder": {
"__typename": "PrepareCancelOrderResult",
"data": {
// …
}
}
}
}
4. Execute Cancel Swap
Then, execute the cancel order operation.
- TypeScript: Execute the cancel with the
cancelaction.Order - GraphQL: Use the
cancelmutation to execute the cancel.Order
const execute = async () => {
const result = await cancelOrder({
id: order.orderId,
});
};
import { prepareCancelOrder, cancelOrder } from "@aave/client/actions";
import { signTypedDataWith } from "@aave/client/viem";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await prepareCancelOrder(client, {
id: order.orderId,
})
.andThen((prepared) => signTypedDataWith(wallet, prepared.data))
.andThen((signature) =>
cancelOrder(client, {
bySignature: {
id: order.orderId,
signature,
},
}),
);
mutation ($request: CancelOrderRequest!) {
cancelOrder(request: $request) {
__typename
... on TransactionRequest {
...TransactionRequest
}
... on OrderCancelledResult {
...OrderCancelledResult
}
}
}
fragment OrderCancelledResult on OrderCancelledResult {
__typename
orderId
}
In TypeScript, use send for the wallet library of your choice to send transactions in the plan.
import { okAsync } from "@aave/client";
import {
prepareCancelOrder,
cancelOrder,
waitForOrderOutcome,
} from "@aave/client/actions";
import { signTypedDataWith, sendWith } from "@aave/client/viem";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await prepareCancelOrder(client, {
id: order.orderId,
})
.andThen((prepared) => signTypedDataWith(wallet, prepared.data))
.andThen((signature) =>
cancelOrder(client, {
bySignature: {
id: order.orderId,
signature,
},
}),
)
.andThen((plan) => {
switch (plan.__typename) {
case "TransactionRequest":
return sendWith(wallet, plan).andThen(() =>
waitForOrderOutcome(client)({
__typename: "OrderReceipt",
orderId: order.orderId,
}),
);
case "OrderCancelledResult":
return okAsync(plan);
}
});
In GraphQL, the Cancel object takes the signed typed data from the prepare query in by, or the order ID in by.
{
"request": {
"bySignature": {
"id": "<order.orderId>",
"signature": "0x456…"
}
}
}
{
"request": {
"byTransaction": "<order.orderId>"
}
}
5. Handle the Result
Finally, handle the 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 "CannotCancelOrderError":
console.error(`Cannot cancel: ${result.error.message}`);
break;
case "UnexpectedError":
console.error(result.error.message);
break;
}
return;
}
// result.value: OrderCancelledResult
console.log("Order cancelled:", result.value.orderId);
if (result.isErr()) {
switch (result.error.name) {
case "CancelError":
console.error("Operation cancelled by user wallet");
break;
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: OrderCancelledResult | OrderCancelled | OrderExpired | OrderFulfilled
console.log("Order status:", result.value.__typename);
In TypeScript, when the cancellation is sent as a transaction, wait resolves to the final order status. Check for Order, since the order may be fulfilled or expire before the cancellation takes effect.
In GraphQL, depending on the Cancel type, you’ll need to follow different actions:
| Response Type | Action |
|---|---|
Transaction | Send the transaction with the wallet associated with the account that placed the order. |
Order | Order was cancelled successfully. |
Below are examples illustrating the response for each response type.
{
"data": {
"cancelOrder": {
"__typename": "TransactionRequest"
// …
}
}
}
{
"data": {
"cancelOrder": {
"__typename": "OrderCancelledResult",
"orderId": "0x123…"
}
}
}
You cancelled your first swap order. 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 for executing the swap. Transaction-based swaps pay it in the chain’s native token (e.g., ETH on Ethereum). Intent-based swaps are gasless for the user: the solver’s cost is included in the quote under costs..
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:
- Swap Borrow Asset: 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 keep a stable price relationship, usually pegged to the same asset or index. Their lower price risk earns the discounted fee.
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.