Skip to Content
docs

Repay Loans on Aave V3

Repay borrowed assets to Aave V3 markets.

Repay Loans

By repaying borrowed assets to an Aave market, users reduce or clear their debt position.

When repaying borrowed assets to an Aave market, you can use the following methods:

MethodDescriptionTypical Scenario
Direct RepayRepay ERC-20 or native tokens directly from the sender’s wallet.User repays borrowed assets from their own wallet balance.
On Behalf of AnotherRepay debt for another address.User borrowed with one wallet and wants to repay from a different wallet.
With PermitUse a permit signature to skip ERC-20 approval and repay in a single transaction.User signs a permit and sends a single transaction. No ERC-20 allowance left behind.
With Permit, On Behalf of AnotherSame as permit repay, but the debt position is owned by another wallet.No ERC-20 allowance needed when repaying debt for another wallet.

To repay borrowed assets from an Aave market, follow these steps.

1. Identify the Borrow Position

First, determine which user borrow position you want to repay debt for.

Given this MarketUserReserveBorrowPosition object:

MarketUserReserveBorrowPosition
const position: MarketUserReserveBorrowPosition = {
  __typename: "MarketUserReserveBorrowPosition",
  market: {
    __typename: "MarketInfo",
    address: "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
    chain: { chainId: 1 },
    // …
  },
  currency: {
    __typename: "Currency",
    symbol: "WETH",
    name: "Wrapped Ether",
    address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
    // …
  },
  debt: {
    __typename: "TokenAmount",
    amount: {
      __typename: "DecimalValue",
      value: "1.5",
      raw: "1500000000000000000",
      decimals: 18,
      // …
    },
    // …
  },
  // …
};

That is tied to the following Reserve object.

Reserve
const reserve: Reserve = {
  __typename: "Reserve",
  market: {
    __typename: "MarketInfo",
    address: "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
    chain: { chainId: 1 },
    // …
  },
  underlyingToken: {
    __typename: "Currency",
    symbol: "WETH",
    name: "Wrapped Ether",
    address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
    // …
  },
  permitSupported: true,
  acceptsNative: {
    __typename: "NativeCurrency",
    // …
  },
};

The Reserve.permitSupported flag indicates whether the underlying token supports EIP-2612 permits.

Additionally, if Reserve.acceptsNative is not null, users can choose to repay the borrow position as the chain’s native token and it will be automatically wrapped before being sent to the reserve. This is typical for reserves of the wrapped version of the chain’s native token (e.g., WETH on Ethereum).

2. Prepare the Repay Execution Plan

Next, we can proceed with creating the execution plan for the repay operation.

Use the useRepay hook in React, the repay action in TypeScript, or the repay query in GraphQL to create the execution plan for repaying debt from a market reserve, optionally on behalf of another address.

import { useWalletClient } from "wagmi";
import { useRepay, bigDecimal, evmAddress } from "@aave/react";

// …

const { data: walletClient } = useWalletClient();

const [repay, repaying] = useRepay();

const execute = async () => {
  const result = await repay({
    market: position.market.address,
    amount: {
      erc20: {
        currency: position.currency.address,
        value: {
          exact: bigDecimal(1), // 1 WETH
        },
      },
    },
    sender: evmAddress(walletClient!.account.address),
    chainId: position.market.chain.chainId,
  });

  // …
};

If Reserve.permitSupported is true and your borrow position is an ERC-20 token, you can sign permits and prepare repay transactions.

  • React: Use the useRepay and useERC20Permit hooks, with the useERC20Permit implementation for the wallet library of choice.
  • TypeScript: Use the repay and permitTypedData actions together with the signERC20PermitWith helper, with the signERC20PermitWith implementation for the wallet library of choice.
import { useRepay, bigDecimal, evmAddress } from "@aave/react";
import { useERC20Permit } from "@aave/react/viem";
import { useWalletClient } from "wagmi";

// …

const { data: walletClient } = useWalletClient();

const [signPermit, signing] = useERC20Permit(walletClient);
const [repay, repaying] = useRepay();

const execute = async () => {
  const amount = bigDecimal(1); // 1 WETH

  const permit = await signPermit({
    amount,
    chainId: position.market.chain.chainId,
    currency: position.currency.address,
    owner: evmAddress(walletClient!.account.address),
    spender: position.market.address,
  }).andThen((signature) =>
    repay({
      market: position.market.address,
      amount: {
        erc20: {
          currency: position.currency.address,
          value: {
            exact: amount, // 1 WETH
          },
          permitSig: signature,
        },
      },
      sender: evmAddress(walletClient!.account.address),
      chainId: position.market.chain.chainId,
    })
  );

  // …
};

3. Process the Repay Execution Plan

Finally, handle the execution plan.

  • React: Use the useSendTransaction hook for the wallet library of your choice to send the transactions in the execution plan.
  • TypeScript: Use sendWith for your chosen wallet library to send the transactions in the execution plan, and await indexing with client.waitForTransaction.
  • GraphQL: If the response is a TransactionRequest, you can send it directly using the wallet associated with the from address.
Viem
import { useWalletClient } from "wagmi";
import { errAsync, useRepay } from "@aave/react";
import { useSendTransaction } from "@aave/react/viem";

// …

const { data: walletClient } = useWalletClient();

const [repay, repaying] = useRepay();
const [sendTransaction, sending] = useSendTransaction(walletClient);

// …

// Optional: combine loading states
const loading = repaying.loading || sending.loading;
const error = repaying.error || sending.error;

// …

const execute = async () => {
  const result = await repay({
    // …
  }).andThen((plan) => {
    switch (plan.__typename) {
      case "TransactionRequest":
        // Single transaction execution
        return sendTransaction(plan);

      case "ApprovalRequired":
        // Approval + transaction sequence
        return sendTransaction(plan.approval).andThen(() =>
          sendTransaction(plan.originalTransaction)
        );

      case "InsufficientBalanceError":
        return errAsync(
          new Error(`Insufficient balance: ${plan.required.value} required.`)
        );
    }
  });

  if (result.isErr()) {
    console.error("Repay failed:", result.error);
  } else {
    console.log("Repay successful with hash:", result.value);
  }
};

In GraphQL, if the response is an ApprovalRequired, you must first send the approval transaction. Once it succeeds, you can then send the originalTransaction.

ApprovalRequired
{
  "data": {
    "repay": {
      "__typename": "ApprovalRequired",
      "reason": "Insufficient allowance for ERC-20 token transfer",
      "requiredAmount": {
        "value": "1.0",
        "decimals": 18
      },
      "currentAllowance": {
        "value": "0.0",
        "decimals": 18
      },
      "approval": {
        "to": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
        "from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
        "data": "0x095ea7b3000000000000000000000000878…",
        "value": "0",
        "chainId": 1,
        "operation": null
      },
      "originalTransaction": {
        "to": "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
        "from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
        "data": "0x573ade81000000000000000000000000c02…",
        "value": "0",
        "chainId": 1,
        "operation": "REPAY"
      }
    }
  }
}

In GraphQL, if the response is an InsufficientBalanceError, inform the user that they don’t have enough balance to repay the requested amount.

InsufficientBalanceError
{
  "data": {
    "repay": {
      "__typename": "InsufficientBalanceError",
      "required": {
        "value": "1.0",
        "decimals": 18
      },
      "available": {
        "value": "0.5",
        "decimals": 18
      }
    }
  }
}