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: "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>Risk Premium:</h3>      <p>From: {data.riskPremium.current.value}</p>      <p>To: {data.riskPremium.after.value}</p>
      <h3>Net APY:</h3>      <p>From: {data.netApy.current.value}</p>      <p>To: {data.netApy.after.value}</p>
      <h3>Net Collateral:</h3>      <p>From: {data.netCollateral.current.value}</p>      <p>To: {data.netCollateral.after.value}</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      },    },  },};

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

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.

Current → AfterImpact
healthFactor?.current → healthFactor?.after: BigDecimalHigher is better
riskPremium.current → riskPremium.after: PercentNumberLower is better
netApy.current → netApy.after: PercentNumberHigher is better
netCollateral.current → netCollateral.after: FiatAmountHigher is better
netBalance.current → netBalance.after: FiatAmountUpdated balance

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.

To repay borrowed assets to an Aave reserve with AaveKit React, follow these steps.

1

Configure Wallet Integration

First, instantiate the useSendTransaction hook for the wallet library of your choice.

Viem
import { useWalletClient } from "wagmi";import { useSendTransaction } from "@aave/react/viem";
// …
const { data: wallet } = useWalletClient();const [sendTransaction] = useSendTransaction(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 "Erc20ApprovalRequired":    case "PreContractActionRequired":      return sendTransaction(plan.transaction);  }});

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: ${result.error.cause.available.value} available.`        );        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.fiatAmount.symbol}        {fee.fiatAmount.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        },      },    },  });
  // …};

ERC-20 Permits

Repay operations support ERC-20 permit functionality using EIP-2612 signatures to authorize ERC-20 token transfers within the same transaction. The permit approach has a few benefits:

  • Single Transaction: Execute operations without separate approval transactions

  • Exact Amounts: Sign permissions for specific amounts instead of infinite approvals

  • Time Limited: Permits have deadlines for enhanced security

Use the reserve.asset.underlying.permitSupported flag to determine if the underlying token supports EIP-2612 permits.

USDC Borrow Position
const borrowPosition: UserBorrowItem = {  reserve: {    id: "SGVsbG8h",    onChainId: "42",    asset: {      underlying: {        address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",        info: {          name: "USD Coin",          symbol: "USDC",          decimals: 6,          // …        },        permitSupported: true,        // …      },      // …    },    chain: {      chainId: 1,      name: "Ethereum",      signatureGateway: "0xabc…",    },    // …  },  // …};

If the underlying token supports EIP-2612 permits, you can use this alternate approach to repay ERC-20 tokens.

The process outlined here assumes you have familiarity with the standard repay operation flow.

1

Configure Wallet Integration

First, instantiate the useERC20Permit hook for the wallet library of your choice.

Viem
import { useWalletClient } from "wagmi";import { useERC20Permit } from "@aave/react/viem";
// …
const { data: wallet } = useWalletClient();const [signERC20Permit] = useERC20Permit(wallet);

2

Generate an ERC-20 Permit Signature

Then, generate the ERC-20 Permit signature for the desired repay operation.

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

3

Execute the Repay Operation

Finally, execute the repay operation providing the permit signature as part of the ERC-20 amount input.

const [repay, { loading, error }] = useRepay((plan) => {  switch (plan.__typename) {    case "TransactionRequest":      return sendTransaction(plan);    case "Erc20ApprovalRequired":    case "PreContractActionRequired":      return sendTransaction(plan.transaction);  }});
const execute = async () => {  const result = await signERC20Permit({    repay: {      sender: evmAddress(wallet.account.address), // User's address      reserve: borrowPosition.reserve.id,      amount: {        exact: bigDecimal(250), // 250 USDC      },    },  }).andThen((permitSig) =>    repay({      sender: evmAddress(wallet.account.address), // User's address      reserve: borrowPosition.reserve.id,      amount: {        erc20: {          permitSig,          value: {            exact: bigDecimal(250), // 250 USDC          },        },      },    })  );
  // …};

Handle the result object as described in the standard repay operation flow.

While the useRepay handler supports both TransactionRequest and ApprovalRequired cases, this particular repay operation will always execute only the TransactionRequest path.