Repay Loans

Learn how to repay borrowed assets to Aave v4 reserves.


Repaying borrowed assets allows you to:

  • Reduce or eliminate your debt position

  • Improve your position's health factor and reduce liquidation risk

  • Free up collateral for withdrawal or additional borrowing

  • Close your borrow position entirely when repaying the full amount

Repaying borrowed assets improves the position's health factor and reduces liquidation risk. You can repay partial amounts or the full debt amount.

Repaying

Repaying a loan can be broken down into the following steps:

  1. Identify the borrow position to repay

  2. Preview the impact of the repay operation

  3. Repay the borrowed assets

Identify the Borrow Position

Given a list of the user's borrow positions, identify the position (token) you want to reduce and choose the repayment amount (partial or full).

The borrowed position reserve should not be paused in order to repay.

For example, let’s say you have identified the following UserBorrowItem object.

Example UserBorrowItem
const borrowPosition: UserBorrowItem = {  reserve: {    id: "SGVsbG8h",    onChainId: "42",    chain: {      chainId: 1,      name: "Ethereum",    },    spoke: {      address: "0x123…",      // …    },    asset: {      underlying: {        address: "0xa0b86a33e6e2ad05ad6c9ac3b6e5e5f6e7b6c1b2", // USDC      },      // …    },    status: {      paused: false,    },    // … other reserve properties  },  debt: {    amount: {      value: BigDecimal(512.023456), // ~512 USDC debt      // …    },    // …  },  // …};

Keep in mind that repaying:

  • Improves your health factor, reducing liquidation risk.

  • Frees up collateral, making it available for withdrawal or additional borrowing.

  • Can be partial (reducing debt but keeping the position open) or full (closing the borrow position entirely).

Preview Repay

Preview the impact of a repay operation before committing to it.

Use the usePreview hook (or the imperative usePreviewAction variant) to preview the impact of the repay operation on the user's position.

import { type RepayRequest, usePreview } from "@aave/react";
function RepayPreview({ request }: { request: RepayRequest }) {  const { data, error, loading } = usePreview({    action: {      repay: request,    },  });
  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 ?? "N/A"}</p>      <p>To: {data.healthFactor?.after ?? "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>    </div>  );}

Where the RepayRequest can be as follows:

const request: RepayRequest = {  sender: evmAddress("0x789…"), // User's address  reserve: borrowPosition.reserve.id,  amount: {    erc20: {      value: {        exact: bigDecimal(250), // 250 USDC      },    },  },};

The PreviewUserPosition shows the impact of the repay 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
borrowingPower.[current → after]: ExchangeAmountNew borrowing power

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

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

Step-by-Step

Now that we know how to identify a borrow position to repay, and we know how to preview the impact of a repay operation, let's see how to repay assets for this position.

Repaying ERC-20 tokens requires token approval. You can choose between two approaches:

  • Transaction-based approval — Sends a separate ERC-20 approve() transaction before the repay transaction (2 transactions total).

  • Permit-based approval — Signs an EIP-2612 permit to approve and repay in a single transaction. More gas-efficient, but requires the token to support permits.

To repay a loan with AaveKit React, follow these steps.

1

Configure Wallet Integration

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

  • useSendTransaction — used to send ERC-20 approval and repay transactions

  • useSignTypedData — used to sign ERC-20 permits when available

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

2

Define the Repay Flow

Then, use the useRepay hook to prepare the repay operation.

import { useRepay } from "@aave/react";
const [repay, { loading, error }] = useRepay((plan) => {  switch (plan.__typename) {    case "TransactionRequest":      return sendTransaction(plan);
    case "Erc20Approval":      // If token supports EIP-2612 permits, sign permit (recommended)      if (plan.bySignature) {        return signTypedData(plan.bySignature);      }      // Otherwise use traditional approval transaction      return sendTransaction(plan.byTransaction);
    case "PreContractActionRequired":      return sendTransaction(plan.transaction);  }});

In the Erc20Approval case, the bySignature field is only available if the token supports EIP-2612 permits.

For tokens like USDT on Ethereum Mainnet that require an allowance reset, the hook calls your callback twice: once to reset the allowance to 0, then again to set the new value. bySignature will be null for these approvals.

3

Execute the Repay Operation

Then, execute the desired repay operation.

import { bigDecimal, evmAddress } from "@aave/react";
const execute = async () => {  const result = await repay({    sender: evmAddress(wallet.account.address), // User's address    reserve: borrowPosition.reserve.id,    amount: {      erc20: {        value: {          exact: bigDecimal(250), // Repay 250 USDC        },      },    },  });
  // …};

4

Handle the Result

Finally, handle the result.

Example
const execute = async () => {  const result = await repay(/* … */);
  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 "ValidationError":        console.error(          "Insufficient balance:",          `required: ${result.error.cause.required.value.toDisplayString(2)}`,          `available: ${result.error.cause.available.value.toDisplayString(2)}`,        );        break;
      case "UnexpectedError":        console.error(result.error.message);        break;    }    return;  }
  console.log("Repay successful with hash:", result.value);};

Advanced Usage

Network Fee

This experimental AaveKit React hook currently works only with Viem or Wagmi integrations. Support for additional wallet libraries may be added later.

Estimate the network cost of any action using the same PreviewAction you pass to the usePreview hook.

Let's consider the following example:

PreviewAction
import { type PreviewAction } from "@aave/react";
const action: PreviewAction = {  repay: {    sender: evmAddress("0x123…"), // User's address    reserve: borrowPosition.reserve.id,    amount: {      erc20: {        value: bigDecimal(42), // USDC      },    },  },};

Use the useNetworkFee hook to estimate both the network fee for the provided action and its fiat equivalent.

Viem
import { type PreviewAction, Currency } from "@aave/react";import { useNetworkFee } from "@aave/react/viem";
function NetworkFee({ action }: { action: PreviewAction }) {  const {    data: fee,    loading,    error,  } = useNetworkFee({    query: { estimate: action },    currency: Currency.Eur,  });
  if (loading) return <p>Loading fee…</p>;  if (error) return <p>Error: {error.message}</p>;
  return (    <p>      Network Fee: {fee.amount.value.toDisplayString(2)} {fee.token.info.symbol}      <span>{fee.exchange.symbol}        {fee.exchange.value.toDisplayString(2)}      </span>    </p>  );}

Native Tokens

When the Reserve's underlying token is the wrapped version of the chain's native token (e.g., WETH on Ethereum), you can repay the debt using the chain's native token with the Native Token Gateway.

Use the reserve.asset.underlying.isWrappedNativeToken flag to determine if the underlying token is a wrapped native token. The Native Gateway address is available from the chain details.

WETH Borrow Position
const borrowPosition: UserBorrowItem = {  reserve: {    id: "SGVsbG8h",    onChainId: "42",    asset: {      underlying: {        address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",        info: {          name: "Wrapped Ether",          symbol: "WETH",          decimals: 18,          // …        },        isWrappedNativeToken: true,        // …      },      // …    },    spoke: {      address: "0x123…",      // …    },    chain: {      chainId: 1,      name: "Ethereum",      nativeGateway: "0xabc…",    },    // …  },  // …};

Specify the amount in the amount field as a native value.

const execute = async () => {  const result = await repay({    sender: evmAddress(wallet.account.address), // User's address    reserve: borrowPosition.reserve.id,    amount: {      native: {        value: {          exact: bigDecimal(1), // Repay 1 ETH        },      },    },  });
  // …};