Skip to Content
docs

Aave V4 Swap Features

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

The swap API is deprecated. Move to the Order API:

  • prepareTokenSwap → prepareOrder
  • swap → submitOrder
  • swapStatus → orderStatus
  • userSwaps → pendingOrders
  • prepareSwapCancel → prepareCancelOrder
  • cancelSwap → cancelOrder
  • React: useTokenSwap → useTokenSwapOrder
  • React: useSwapStatus → useOrderStatus
  • React: useUserSwaps → usePendingOrders
  • React: useCancelSwap → useCancelOrder

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.
User TransactionDirect SwapOrder BookSolverSettlementUser Intent
Intent-Based Swap and Transaction-Based Fallback

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 in React, the userBalances action in TypeScript, or the userBalances 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>
  );
}
  • React: You can query for swappable token balances as follows.
  • GraphQL: Use the filter.swappable field to filter by swappable tokens.
import { evmAddress, chainId, OrderDirection } from "@aave/react";

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

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 useSwappableTokens in React, swappableTokens in TypeScript, or the swappableTokens 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>
  );
}
  • React: You can query for a list of swappable tokens as follows.
  • GraphQL: Use the query field to select destination tokens by different criteria.
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.

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%
  },
};

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: TokenSwapKind.Sell or TokenSwapKind.Buy, required; 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("0xA0b86991…") },
    sell: { erc20: evmAddress("0x6B175474E…") },
    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
  },
};

The request takes the following parameters:

  • chainId: The target chain
  • sell: The token and amount to sell
  • buy: The token and amount to receive
  • user: The sender address
  • receiver: The recipient address (defaults to user)
  • kind: Sell (default) or Buy, determines which amount is honored
  • previousQuoteId: Optional quote ID from a prior market order
  • deadline: Optional deadline as DateTime; if not provided, uses default expiry

With both amounts specified, the swap kind determines which amount takes priority:

Swap KindUser specifies…What happens with the order
SellThe exact sell amount and a minimum buy amountThe order is fulfilled when the buy price is large enough to accommodate the user’s ask and the swap costs
BuyThe exact buy amount and a maximum sell amountThe 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…"),
  },
};

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 useTokenSwapQuote hook (or the imperative useTokenSwapQuoteAction variant) in React, the tokenSwapQuote action in TypeScript, or the tokenSwapQuote query in GraphQL to get a swap quote. The quote’s orderQuoteId 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>
  );
}

useTokenSwapQuoteAction 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,
});

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;
  orderQuoteId: OrderQuoteId;
  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: token amounts before fees and slippage
  • finalBuy and finalSell: amounts you’ll receive/pay after fees and slippage
  • orderQuoteId: same value as quoteId, typed for order operations
  • costs: a breakdown of all fees (partner fee, provider fee, flash loan fee, network costs)
  • suggestedSlippage: recommended slippage tolerance based on asset volatility and order size
  • selectedSlippage: the slippage tolerance selected when requesting a market quote (if provided)

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…"
    }
  }
}

2. Configure Wallet Integration

Then, in React, 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, in React, use the useTokenSwapOrder 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);
  }
});

4. Handle Approvals

Then, in TypeScript, using helpers from the wallet library of your choice, use signTypedDataWith and sendWith to handle ERC-20 approvals, and prepareOrder with signTypedDataWith to sign intent-based swap orders. Each branch resolves to a SubmitOrderRequest 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);

For tokens that need an allowance reset, like USDT on Ethereum Mainnet, sendWith sends both approvals before the swap: reset to 0, then the new allowance.

5. Handle Swap Quote Response

In GraphQL, depending on the TokenSwapQuoteResult type, you’ll need to follow different actions:

Response TypeAction
SwapByIntentPass the quote.orderQuoteId to the prepareOrder query.
SwapByIntentWithApprovalRequiredSend each transaction in approvals from the user account, then pass the quote.orderQuoteId to prepareOrder.
SwapByTransactionKeep a note of the quote.orderQuoteId.
SwapByTransactionWithApprovalRequiredSend each transaction from approvals in sequence, then keep a note of the quote.orderQuoteId.
InsufficientLiquidityErrorNot 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…"
        // …
      }
    }
  }
}

6. Prepare the Swap

Then, in GraphQL, use the prepareOrder query to prepare the swap order.

query ($request: PrepareOrderRequest!) {
  prepareOrder(request: $request) {
    __typename
    ... on PreparedOrder {
      ...PreparedOrder
    }
    ... on InsufficientBalanceError {
      ...InsufficientBalanceError
    }
  }
}

Define PrepareOrderRequest taking the quote.orderQuoteId from the previous step and the permit signature when needed.

{
  "request": {
    "quoteId": "<quote.orderQuoteId>"
  }
}

7. Handle Prepare Swap Response

In GraphQL, depending on the PrepareOrderResult type, you’ll need to follow different actions:

Response TypeAction
PreparedOrderSign the typed data with the account performing the swap.
InsufficientBalanceErrorNotify 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": {
        // …
      }
    }
  }
}

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 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: OrderReceipt
  console.log("Swap order placed:", result.value);
};

For limit orders, call swap with a limit request that includes the previousQuoteId from the last swap quote.

Execute Limit Order Request
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 submitOrder action to submit the swap order. For transaction-based swaps, use the sendWith helper to send the transaction.
  • GraphQL: Submit the swap order using the submitOrder mutation.
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);
    }
  });

// …

For intent swaps, pass the newQuoteId from prepareOrder and the signature in bySignature. For transaction swaps, pass the quote.orderQuoteId from tokenSwapQuote in byTransaction.

{
  "request": {
    "bySignature": {
      "quoteId": "<prepareOrder.newQuoteId>",
      "signature": "0x456…"
    }
  }
}

9. 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: OrderReceipt
console.log("Swap order placed:", result.value.orderId);

In GraphQL, depending on the OrderExecutionPlan type, you’ll need to follow different actions:

Response TypeAction
OrderTransactionRequestSend the transaction with the account performing the swap.
OrderReceiptThe swap order has been placed.
InsufficientBalanceErrorNotify 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…"
      }
    }
  }
}

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 OrderReceipt object is returned with an orderId field. Use this orderId to track the order status. For more details, see the Execute Swaps section.

  • React: Use the useOrderStatus hook to monitor the status of an order in real-time.
  • TypeScript: Use the orderStatus action to check the current status of an order, or waitForOrderOutcome to poll until it reaches a terminal state.
  • GraphQL: Use the orderStatus query to track the order 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>;
  }
}

In React, the hook automatically polls for status updates until the order reaches a terminal state: fulfilled, cancelled, or expired.

In GraphQL, the OrderStatus object can be one of the following:

fragment OrderOpen on OrderOpen {
  __typename
  orderId
  createdAt
  deadline
  explorerUrl
  operation {
    ...OrderOperation
  }
}

User Swaps

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

Use the usePendingOrders hook in React, the pendingOrders action in TypeScript, or the pendingOrders query in GraphQL to list user orders. Use filterBy 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>
  );
}

In React, the hook automatically polls for status updates until all listed orders reach a terminal state: fulfilled, cancelled, or expired.

The OrderStatus fragments are listed under Monitor Swap Status.

  • React: You can filter orders as follows.
  • GraphQL: The PendingOrdersRequest object should be defined as follows.
import { evmAddress, chainId } from "@aave/react";

const request: PendingOrdersRequest = {
  user: evmAddress("0x742d35cc…"),
  chainId: chainId(1),
};

Cancel a Swap

Before cancelling, identify the swap order by its status.

Only orders with status OrderOpen or OrderPendingSignature can be cancelled.

Pick the OrderOpen item (order) to cancel.

User Orders
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 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, in React, use the useCancelOrder 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);
  }
});

3. Prepare Cancel Swap

  • TypeScript: Prepare the cancel typed data with prepareCancelOrder.
  • GraphQL: Use the prepareCancelOrder query to prepare the cancel.
import { prepareCancelOrder } from "@aave/client/actions";

import { client } from "./client";

const result = await prepareCancelOrder(client, {
  id: order.orderId,
});
  • TypeScript: Use signTypedDataWith for the wallet library of your choice to sign the typed data in result.data.
  • GraphQL: Sign the data of the PrepareCancelOrderResult response with the account that placed the order.
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);
}

4. Execute Cancel Swap

Then, execute the cancel order operation.

  • TypeScript: Execute the cancel with the cancelOrder action.
  • GraphQL: Use the cancelOrder mutation to execute the cancel.
Cancel Swap
const execute = async () => {
  const result = await cancelOrder({
    id: order.orderId,
  });
};

In TypeScript, use sendWith for the wallet library of your choice to send transactions in the plan.

Send Transaction
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 CancelOrderRequest object takes the signed typed data from the prepareCancelOrder query in bySignature, or the order ID in byTransaction.

{
  "request": {
    "bySignature": {
      "id": "<order.orderId>",
      "signature": "0x456…"
    }
  }
}

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 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);

In TypeScript, when the cancellation is sent as a transaction, waitForOrderOutcome resolves to the final order status. Check for OrderCancelled, since the order may be fulfilled or expire before the cancellation takes effect.

In GraphQL, depending on the CancelOrderExecutionPlan type, you’ll need to follow different actions:

Response TypeAction
TransactionRequestSend the transaction with the wallet associated with the account that placed the order.
OrderCancelledResultOrder was cancelled successfully.

Below are examples illustrating the response for each response type.

{
  "data": {
    "cancelOrder": {
      "__typename": "TransactionRequest"
      // …
    }
  }
}

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.networkCosts.

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.