Skip to Content
docs

Repay with Supply

Repay with supply combines withdraw, swap, and repay 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
Avoid LiquidationUser wants to improve Health Factor by repaying debt using their supply
Rebalance PositionsUser wants to consolidate stablecoin exposure by using idle supply to pay off debt in a different stablecoin
Reduce Debt ExposureUser wants to reduce debt load by converting non-collateral supply into debt repayment

Repaying with supply lowers your supplied balance. If that asset is collateral, check that the position stays healthy.

Repay with supply can be broken down into the following steps:

  1. Identify the supply position to use for repayment
  2. Identify the borrow position to repay
  3. Choose between market order and limit order
  4. Execute the swap operation

Identify the Supply Position to Repay With

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

UserSupplyItem
const supplyPosition: UserSupplyItem = {
  id: "c3VwcGx5",
  reserve: {
    id: "SGVsbG8h",
    canSwapFrom: true,
    chain: {
      chainId: 1,
      name: "Ethereum",
    },
    spoke: {
      id: "aGVsbG8",
      // …
    },
    // …
  },
  isCollateral: true,
  balance: {
    amount: {
      value: BigDecimal(5000.0),
      exchange: {
        value: BigDecimal(5000.0),
        // …
      },
      // …
    },
    // …
  },
  principal: {
    amount: {
      value: BigDecimal(4800.0),
      // …
    },
    // …
  },
  interest: {
    amount: {
      value: BigDecimal(200.0),
      // …
    },
    // …
  },
  // …
};

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

Identify the Borrow Position to Repay

Identify the borrow position among the user’s borrow positions to repay.

UserBorrowItem
const borrowPosition: UserBorrowItem = {
  id: "Ym9ycm93",
  reserve: {
    id: "V29ybGQh",
    spoke: {
      id: "aGVsbG8",
      // …
    },
    // …
  },
  debt: {
    amount: {
      value: BigDecimal(1000.0),
      exchange: {
        value: BigDecimal(1000.0),
        // …
      },
      // …
    },
    // …
  },
  principal: {
    amount: {
      value: BigDecimal(950.0),
      // …
    },
    // …
  },
  interest: {
    amount: {
      value: BigDecimal(50.0),
      // …
    },
    // …
  },
  // …
};

The supply and borrow positions must be on the same spoke. The supply position’s asset is swapped to repay the debt.

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: RepayWithSupplyQuoteRequest = {
  market: {
    debtPosition: borrowPosition.id,
    repayWithReserve: supplyPosition.reserve.id,
    amount: bigDecimal(1000),
    user: evmAddress("0x123…"),
    // kind: RepayWithSupplyKind.Repay, // Optional: specify repay or supply priority
    // selectedSlippage: bigDecimal(1), // Optional: override suggested slippage
  },
};

The request takes the following parameters:

  • debtPosition: The borrow position with debt to repay
  • repayWithReserve: The reserve of the supply position to use for repayment
  • amount: The swap amount, interpreted based on kind
  • user: The user’s address
  • kind: Repay (default) or Supply, determines how amount is interpreted
  • selectedSlippage: Optional slippage tolerance to use instead of the suggested slippage

Since only one amount is provided, the kind parameter determines how it’s interpreted:

KindUser inputs…System calculates…
Repay (default)The debt amount to repayThe supply amount needed
SupplyThe supply amount to useThe debt amount that will be repaid

Limit Orders

Limit orders execute when your price condition is met.

const request: RepayWithSupplyQuoteRequest = {
  limit: {
    repayPosition: borrowPosition.id,
    repayAmount: bigDecimal(1000),
    supplyPosition: supplyPosition.id,
    supplyAmount: bigDecimal(1050), // Amount of supply to use
    user: evmAddress("0x123…"),
    kind: RepayWithSupplyKind.Repay,
    // deadline: new Date(Date.now() + 24 * 60 * 60 * 1000), // Optional: custom expiry
  },
};

The request takes the following parameters:

  • repayPosition: The borrow position with debt to repay
  • repayAmount: The amount of debt to repay
  • supplyPosition: The supply position to use for repayment
  • supplyAmount: The amount of supply to use
  • user: The user’s address
  • kind: Repay (default) or Supply, 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
RepayThe exact repay amount and a maximum supply usageOrder fulfilled when supply price allows full debt repayment within supply limit
SupplyThe exact supply to use and a minimum repay amountOrder fulfilled when enough debt can be repaid using the specified supply amount

Step-by-Step with AaveKit

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

1. Get a Quote

First, use the useRepayWithSupplyQuote hook (or the imperative useRepayWithSupplyQuoteAction variant) in React, the repayWithSupplyQuote action in TypeScript, or the repayWithSupplyQuote query in GraphQL to get pricing information for the swap.

import {
  type RepayWithSupplyQuoteRequest,
  useRepayWithSupplyQuote,
} from "@aave/react";

function SwapQuote({ request }: { request: RepayWithSupplyQuoteRequest }) {
  const { data, loading, error } = useRepayWithSupplyQuote(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>Repay amount: {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 } = useRepayWithSupplyQuote({
  ...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: {
      repayWithSupply: {
        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 repayWithSupply.fromQuote field:

Variables
{
  "request": {
    "action": {
      "repayWithSupply": {
        "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 (less negative for net borrowers)
netCollateral.[current → after]: ExchangeAmountLower after (collateral used to repay)
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

If the supply used for repayment was enabled as collateral, the Dynamic Config and User Risk Premium of the user position will be updated.

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 useRepayWithSupplyOrder hook to handle the operation. It fetches the quote, collects the approval signatures, and prepares, signs, and submits the order.

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

const [repayWithSupply, { loading, error }] = useRepayWithSupplyOrder((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 3 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.
  • 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.

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

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 repayWithSupply({
    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 repay with supply order. Keep track of its status in the Monitor Swap Status section.