Skip to Content
docs

Swap Supply Asset

Swapping supply assets combines withdraw, swap, and re-supply into one step.

Position swaps now run on the Order API. Update your code:

  • preparePositionSwap → prepareOrder
  • swap → submitOrder
  • React: useSupplySwap → useSupplySwapOrder
  • React: useBorrowSwap → useBorrowSwapOrder
  • React: useRepayWithSupply → useRepayWithSupplyOrder

This feature leverages CoW Protocol for swap execution. Users sign an intent and solvers execute it through batch auctions with MEV protection. Most flows need no transaction; if an approval or the submitOrder result returns a transaction request, the user sends it and pays gas.

See Fees for swap costs.

Common scenarios:

ScenarioDescription
Liquidation Risk ReductionUser wants to swap volatile collateral to stable assets to reduce liquidation risk
Better RatesUser wants to switch supply position to a reserve with better rates (regardless of being used as collateral or not)
Replenish CollateralUser wants to use a non-collateral supply to replenish a collateral position to gain more borrowing power at the same risk level
Better Risk PremiumUser wants to consolidate collateral to a less risky asset (lower Collateral Risk) to reduce the risk premium on borrows

Swapping supply assets can be broken down into the following steps:

  1. Identify the supply position to swap from
  2. Choose the target reserve to swap onto
  3. Choose between market order and limit order
  4. Execute the swap operation

Identify the Supply Position

Identify the supply position among the user’s supply positions where reserve.canSwapFrom: true.

UserSupplyItem
const supplyPosition: UserSupplyItem = {
  id: "aGVsbG8",
  reserve: {
    id: "SGVsbG8h",
    canSwapFrom: true,
    chain: {
      chainId: 1,
      name: "Ethereum",
    },
    spoke: {
      id: "aGVsbG8",
      // …
    },
    // …
  },
  isCollateral: true,
  balance: {
    amount: {
      value: BigDecimal(1.5),
      exchange: {
        value: BigDecimal(5000.0),
        // …
      },
      // …
    },
    // …
  },
  withdrawable: {
    amount: {
      value: BigDecimal(0.03),
      exchange: {
        value: BigDecimal(100.0),
        // …
      },
      // …
    },
    // …
  },
  // …
};

reserve.canSwapFrom means the reserve isn’t paused and has flash loans enabled, which the swap needs.

Identify the Target Reserve

Choose the target reserve where canSupply: true. The target reserve must be on the same spoke as the source supply position.

As in a regular supply, canSupply means the reserve isn’t frozen, isn’t paused and is below its supply cap.

Target Reserve
const targetReserve: Reserve = {
  id: "V29ybGQh",
  canSupply: true,
  canUseAsCollateral: true,
  spoke: {
    id: "aGVsbG8",
    // …
  },
  asset: {
    underlying: {
      address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
      info: {
        name: "USD Coin",
        symbol: "USDC",
        decimals: 6,
        // …
      },
      // …
    },
    // …
  },
  summary: {
    supplyApy: {
      normalized: BigDecimal(4.5), // 4.5% APY
      // …
    },
    // …
  },
  userState: {
    suppliable: {
      exchange: {
        value: BigDecimal(1245.67),
        name: "USD",
        symbol: "$",
        // …
      },
      // …
    },
  },
  // …
};

Consider the following factors when selecting a target reserve:

  • Token: Check asset.underlying for the token of the new supply position
  • Collateral: Verify canUseAsCollateral if collateral is needed
  • Capacity: Review userState.suppliable for sufficient supply capacity
  • Rate: Compare summary.supplyApy for favorable APY

Market or Limit Order

You decide whether to use a market order or a limit order.

Market Orders

Market orders execute at current rates.

const request: SupplySwapQuoteRequest = {
  market: {
    sellPosition: supplyPosition.id,
    buyReserve: targetReserve.id,
    enableCollateral:
      supplyPosition.isCollateral && targetReserve.canUseAsCollateral,
    amount: bigDecimal(0.03),
    user: evmAddress("0x123…"),
    // selectedSlippage: bigDecimal(1), // Optional: override suggested slippage
  },
};

The request takes the following parameters:

  • sellPosition: The current supply position to swap from
  • buyReserve: The target reserve to swap to
  • enableCollateral: Whether to enable the new position as collateral
  • amount: The amount of the current position to swap
  • user: The user’s address
  • selectedSlippage: Optional slippage tolerance to use instead of the suggested slippage

Limit Orders

Limit orders execute when your price condition is met.

const request: SupplySwapQuoteRequest = {
  limit: {
    currentPosition: supplyPosition.id,
    currentAmount: bigDecimal(0.03),
    newReserve: targetReserve.id,
    newAmount: bigDecimal(105), // Desired amount to receive
    enableCollateral:
      supplyPosition.isCollateral && targetReserve.canUseAsCollateral,
    user: evmAddress("0x123…"),
    kind: SupplySwapKind.Current,
    // deadline: new Date(Date.now() + 24 * 60 * 60 * 1000), // Optional: custom expiry
  },
};

The request takes the following parameters:

  • currentPosition: The current supply position to swap from
  • currentAmount: The amount of the current position to swap
  • newReserve: The target reserve to swap to
  • newAmount: The desired amount to receive in the new position
  • enableCollateral: Whether to enable the new position as collateral
  • user: The user’s address
  • kind: Current (default) or New, determines which amount is honored
  • deadline: Optional deadline as DateTime; if not provided, uses default expiry

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

KindUser specifies…What happens with the order
CurrentThe exact current amount and a minimum new amountOrder fulfilled when new position price allows desired exchange within current limit
NewThe exact new amount and a maximum current usageOrder fulfilled when current position price allows achieving the desired new amount

The enableCollateral flag controls the destination supply’s collateral status: true enables it as collateral; false keeps the existing status (or non-collateral for a new position). The examples assume the target reserve allows collateral and mirror the source’s collateral state; adjust to fit your scenario.

Step-by-Step with AaveKit

To execute a supply swap with AaveKit, follow these steps.

1. Get a Quote

First, use the useSupplySwapQuote hook (or the imperative useSupplySwapQuoteAction variant) in React, the supplySwapQuote action in TypeScript, or the supplySwapQuote query in GraphQL to get pricing information for the swap.

import { type SupplySwapQuoteRequest, useSupplySwapQuote } from "@aave/react";

function SwapQuote({ request }: { request: SupplySwapQuoteRequest }) {
  const { data, loading, error } = useSupplySwapQuote(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>;
  }

  // data: SwapQuote
  return (
    <div>
      <p>You will receive: {data.finalBuy.amount.value.toDisplayString()}</p>
      <p>Partner fee: {data.costs.partnerFee.amount.value.toDisplayString()}</p>
      <p>Slippage: {data.suggestedSlippage.normalized}%</p>
    </div>
  );
}

In React and TypeScript, you can specify a different currency to return fiat amounts in.

import { Currency } from "@aave/react";

const { data, error, loading } = useSupplySwapQuote({
  ...request,
  currency: Currency.Eur,
});

In React and TypeScript, the quote is returned as a SwapQuote.

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)

In GraphQL, the response can be either:

  • PositionSwapByIntentApprovalsRequired: The quote was successful, but the user needs to sign approvals before executing the swap.
  • InsufficientLiquidityError: There’s not enough liquidity for the requested swap amount.

The PositionSwapByIntentApprovalsRequired contains the SwapQuote with pricing details and the PositionSwapApproval list of required approvals to sign before executing the swap.

The SwapQuote object provides detailed pricing information for swap operations.

fragment SwapQuote on SwapQuote {
  __typename
  accuracy
  quoteId
  suggestedSlippage {
    ...PercentNumber
  }
  selectedSlippage {
    ...PercentNumber
  }
  buy {
    ...TokenAmount
  }
  sell {
    ...TokenAmount
  }
  finalBuy {
    ...TokenAmount
  }
  finalSell {
    ...TokenAmount
  }
  orderQuoteId
  costs {
    ...SwapQuoteCosts
  }
}

Where:

  • accuracy: quote accuracy level (FAST or 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)

2. Preview the Impact

Then, use the usePreview hook (or the imperative usePreviewAction variant) in React, the preview action in TypeScript, or the preview query with the quoteId from the previous step in GraphQL to preview the impact of the swap operation on the user’s position. In TypeScript and GraphQL, this step is optional.

import { type SwapQuote, usePreview } from "@aave/react";

function SwapPreview({ quote }: { quote: SwapQuote }) {
  const { data, error, loading } = usePreview({
    action: {
      supplySwap: {
        fromQuote: {
          quoteId: quote.quoteId,
        },
      },
    },
  });

  if (loading) return <div>Loading…</div>;
  if (error) return <div>Error: {error.message}</div>;

  // data: PreviewUserPosition
  return (
    <div>
      <h3>Health Factor:</h3>
      <p>From: {data.healthFactor.current?.toFixed(2) ?? "N/A"}</p>
      <p>To: {data.healthFactor.after?.toFixed(2) ?? "N/A"}</p>

      <h3>User Risk Premium:</h3>
      <p>From: {data.riskPremium.current.normalized.toFixed(2)}%</p>
      <p>To: {data.riskPremium.after.normalized.toFixed(2)}%</p>

      <h3>Net APY:</h3>
      <p>From: {data.netApy.current.normalized.toFixed(2)}%</p>
      <p>To: {data.netApy.after.normalized.toFixed(2)}%</p>

      <h3>Net Collateral:</h3>
      <p>From: {data.netCollateral.current.value.toDisplayString(2)}</p>
      <p>To: {data.netCollateral.after.value.toDisplayString(2)}</p>

      <h3>Max Borrowing Power:</h3>
      <p>From: {data.maxBorrowingPower.current.value.toDisplayString(2)}</p>
      <p>To: {data.maxBorrowingPower.after.value.toDisplayString(2)}</p>
    </div>
  );
}

In GraphQL, pass the quoteId in the supplySwap.fromQuote field:

Variables
{
  "request": {
    "action": {
      "supplySwap": {
        "fromQuote": {
          "quoteId": "<quote-id-from-step-1>"
        }
      }
    }
  },
  "currency": "USD"
}

The PreviewUserPosition shows the impact of the swap operation by comparing current and after states, with the table below outlining key fields and how to interpret them.

FieldImpact
healthFactor.[current → after]: BigDecimal|nullHigher is better
(null if not applicable)
riskPremium.[current → after]: PercentNumberLower is better
netApy.[current → after]: PercentNumberHigher is better
netCollateral.[current → after]: ExchangeAmountHigher is better
netBalance.[current → after]: ExchangeAmountUpdated balance
projectedEarnings.[current → after]: ExchangeAmountProjected earnings
maxBorrowingPower.[current → after]: ExchangeAmountMaximum borrowing power
remainingBorrowingPower.[current → after]: ExchangeAmountRemaining borrowing power
otherConditions: UserPositionConditionVariation[]Dynamic config changes

When swapping supply positions, Position Conditions update depending on the collateral status:

  • Swapping collateral: Swapping out of collateral updates the Dynamic Config for all reserves in the position and refreshes the User Risk Premium.
  • Swapping non-collateral into collateral: The reserve being swapped into uses the latest Dynamic Config, with no implicit risk premium changes.

The otherConditions field is an array of objects describing the resulting dynamic config changes.

  • CollateralFactorVariation – Collateral factor change
  • LiquidationFeeVariation – Liquidation fee change
  • MaxLiquidationBonusVariation – Maximum liquidation bonus change

3. Configure Wallet Integration

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

  • useSignTypedData: used to sign the approvals and the order typed data
  • useSendTransaction: used to send the order transaction when the Order API returns one
Viem
import { useWalletClient } from "wagmi";
import { useSendTransaction, useSignTypedData } from "@aave/react/viem";

// …

const { data: wallet } = useWalletClient();
const [sendTransaction] = useSendTransaction(wallet);
const [signTypedData] = useSignTypedData(wallet);

4. Define the Swap Flow

Then, in React, use the useSupplySwapOrder hook to handle the operation. It fetches the quote, collects the approval signatures, and prepares, signs, and submits the order.

import { useSupplySwapOrder } from "@aave/react";

const [swapSupply, { loading, error }] = useSupplySwapOrder((plan) => {
  switch (plan.__typename) {
    case "PositionSwapAdapterContractApproval":
    case "PositionSwapPositionManagerApproval":
    case "PositionSwapSetCollateralApproval":
      return signTypedData(plan.bySignature);

    case "OrderTypedData":
      return signTypedData(plan);

    case "OrderTransactionRequest":
      return sendTransaction(plan.transaction);
  }
});

The operation involves up to 4 signatures:

  • Swap adapter contract approval: A one-time-use contract that encapsulates the swap logic. This signature ensures the contract is used only once for this specific swap operation.
  • Swap position manager approval: Authorizes a dedicated Position Manager to operate on the user’s position.
  • Set collateral approval: Enables the destination asset as collateral, needed only when it is not yet enabled as collateral.
  • Swap order: The order typed data returned by prepareOrder, signed after the approvals.

5. Sign Approvals

In TypeScript, use signTypedDataWith for the wallet library of your choice to sign the swap approvals.

import {
  type PositionSwapByIntentApprovalsRequired,
  type PrepareOrderRequest,
  type ResultAsync,
  type SignTypedDataError,
  okAsync,
} from "@aave/client";
import { signTypedDataWith } from "@aave/client/viem";

import { wallet } from "./wallet";

export function signApprovals({
  approvals,
  quote,
}: PositionSwapByIntentApprovalsRequired): ResultAsync<
  PrepareOrderRequest,
  SignTypedDataError
> {
  return approvals.reduce(
    (acc, approval) =>
      acc.andThen((request) =>
        signTypedDataWith(wallet, approval.bySignature).map((signature) => {
          switch (approval.__typename) {
            case "PositionSwapAdapterContractApproval":
              return { ...request, adapterContractSignature: signature };
            case "PositionSwapPositionManagerApproval":
              return { ...request, positionManagerSignature: signature };
            case "PositionSwapSetCollateralApproval":
              return { ...request, setCollateralSignature: signature };
          }
        }),
      ),
    okAsync<PrepareOrderRequest, SignTypedDataError>({
      quoteId: quote.orderQuoteId,
      adapterContractSignature: null,
      positionManagerSignature: null,
      setCollateralSignature: null,
    }),
  );
}

In GraphQL, handle the required approvals from the quote response before proceeding:

  • PositionSwapAdapterContractApproval: Sign the bySignature typed data to authorize the swap adapter contract.
  • PositionSwapPositionManagerApproval: Either send the byTransaction request or sign the bySignature typed data to authorize the position manager.
  • PositionSwapSetCollateralApproval: Sign the bySignature typed data to enable the destination asset as collateral. Pass it as setCollateralSignature when preparing the order.

6. Prepare the Order

  • TypeScript: Use the prepareOrder action to prepare the order.
  • GraphQL: Use the prepareOrder query with the quote’s orderQuoteId and the signed approvals.
Prepare Order
import { prepareOrder } from "@aave/client/actions";

import { signApprovals } from "./signApprovals";

// …

const result = await supplySwapQuote(client, request)
  .andThen(signApprovals)
  .andThen((request) => prepareOrder(client, request));

if (result.isErr()) {
  console.error(result.error);
  return;
}

// result.value: PreparedOrder

With the orderQuoteId from the last quote response and the signed approvals, construct the request as follows:

{
  "request": {
    "quoteId": "<quote.orderQuoteId>",
    "adapterContractSignature": "0x…",
    "positionManagerSignature": "0x…",
    "setCollateralSignature": "0x…"
  }
}

If the response is a PreparedOrder, sign the data typed data and submit the order. If it’s an InsufficientBalanceError, inform the user about the insufficient balance.

7. Execute the Swap

Then, execute the swap for the given quote.

  • TypeScript: Sign the order typed data and submit it with the submitOrder action. If the response is an OrderTransactionRequest, send the transaction with sendWith.
  • GraphQL: Use the submitOrder mutation with the new quote ID and the signed order from the previous step.
Execute
const execute = async (quote: SwapQuote) => {
  const result = await swapSupply({
    fromQuote: {
      quoteId: quote.quoteId,
    },
  });

  if (result.isErr()) {
    console.error(result.error);
    return;
  }

  // result.value: OrderReceipt
  console.log("Swap order placed:", result.value.orderId);
};

In GraphQL, pass the newQuoteId from the prepareOrder response and the signed order as variables:

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

8. Handle the Result

Finally, in React and TypeScript, handle the result.

Handle Result
if (result.isErr()) {
  switch (result.error.name) {
    case "CancelError":
      return;

    case "SigningError":
      console.error(`Failed to sign: ${result.error.message}`);
      break;

    case "TimeoutError":
      console.error(`Timed out: ${result.error.message}`);
      break;

    case "TransactionError":
      console.error(`Failed: ${result.error.message}`);
      break;

    case "ValidationError":
      switch (result.error.cause.__typename) {
        case "InsufficientBalanceError":
          console.error(
            "Insufficient balance:",
            `required: ${result.error.cause.required.value.toDisplayString(2)}`,
            `available: ${result.error.cause.available.value.toDisplayString(2)}`,
          );
          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, process the response based on the __typename:

  • OrderReceipt: The order was placed. Use the orderId to track its status.
  • OrderTransactionRequest: Send the transaction request, then track the order using orderReceipt.orderId.
  • InsufficientBalanceError: Inform the user about the insufficient balance.

You placed your first supply swap order. Keep track of its status in the Monitor Swap Status section.