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:
| Method | Description | Typical Scenario |
|---|---|---|
| Direct Repay | Repay ERC-20 or native tokens directly from the sender’s wallet. | User repays borrowed assets from their own wallet balance. |
| On Behalf of Another | Repay debt for another address. | User borrowed with one wallet and wants to repay from a different wallet. |
| With Permit | Use 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 Another | Same 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 Market object:
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.
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. flag indicates whether the underlying token supports EIP-2612 permits.
Additionally, if Reserve. 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 use 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,
});
// …
};
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: {
native: bigDecimal(1), // 1 ETH
},
sender: evmAddress(walletClient!.account.address),
chainId: position.market.chain.chainId,
});
// …
};
import { useWalletClient } from "wagmi";
import { useRepay, 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: {
max: true,
},
},
},
sender: evmAddress(walletClient!.account.address),
chainId: position.market.chain.chainId,
});
// …
};
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,
onBehalfOf: evmAddress("0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"),
});
// …
};
import { repay } from "@aave/client/actions";
import { bigDecimal, evmAddress } from "@aave/client";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await repay(client, {
market: position.market.address,
amount: {
erc20: {
currency: position.currency.address,
value: {
exact: bigDecimal(1), // 1 WETH
},
},
},
sender: evmAddress(wallet.account.address),
chainId: position.market.chain.chainId,
});
// …
import { repay } from "@aave/client/actions";
import { bigDecimal, evmAddress } from "@aave/client";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await repay(client, {
market: position.market.address,
amount: {
native: bigDecimal(1), // 1 ETH
},
sender: evmAddress(wallet.account.address),
chainId: position.market.chain.chainId,
});
// …
import { repay } from "@aave/client/actions";
import { evmAddress } from "@aave/client";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await repay(client, {
market: position.market.address,
amount: {
erc20: {
currency: position.currency.address,
value: {
max: true,
},
},
},
sender: evmAddress(wallet.account.address),
chainId: position.market.chain.chainId,
});
// …
import { repay } from "@aave/client/actions";
import { bigDecimal, evmAddress } from "@aave/client";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await repay(client, {
market: position.market.address,
amount: {
erc20: {
currency: position.currency.address,
value: {
exact: bigDecimal(1), // 1 WETH
},
},
},
sender: evmAddress(wallet.account.address),
chainId: position.market.chain.chainId,
onBehalfOf: evmAddress("0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"),
});
// …
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
import { createWalletClient, http, type Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
export const wallet = createWalletClient({
account: privateKeyToAccount(process.env.PRIVATE_KEY as Hex),
chain: mainnet,
transport: http(),
});
query {
repay(
request: {
market: "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2"
amount: {
erc20: {
currency: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
value: {
exact: "1"
# OR
# max: true
}
}
# OR if the reserve accepts native currency
# native: "1"
}
sender: "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"
chainId: 1
# optional
# onBehalfOf: "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"
}
) {
__typename
... on TransactionRequest {
to
from
data
value
chainId
operation
}
... on ApprovalRequired {
reason
requiredAmount {
value
decimals
}
currentAllowance {
value
decimals
}
approval {
to
from
data
value
chainId
operation
}
originalTransaction {
to
from
data
value
chainId
operation
}
}
... on InsufficientBalanceError {
required {
value
decimals
}
available {
value
decimals
}
}
}
}
If Reserve. is true and your borrow position is an ERC-20 token, you can sign permits and prepare repay transactions.
- React: Use the
useandRepay usehooks, with theERC20 Permit useimplementation for the wallet library of choice.ERC20 Permit - TypeScript: Use the
repayandpermitactions together with theTyped Data signhelper, with theERC20 Permit With signimplementation for the wallet library of choice.ERC20 Permit With
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,
})
);
// …
};
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,
onBehalfOf: evmAddress("0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"),
})
);
// …
};
import { repay, permitTypedData } from "@aave/client/actions";
import { signERC20PermitWith } from "@aave/client/viem";
import { bigDecimal, evmAddress } from "@aave/client";
import { client } from "./client";
import { wallet } from "./wallet";
const amount = bigDecimal(1); // 1 WETH
const result = await permitTypedData(client, {
amount,
chainId: position.market.chain.chainId,
currency: position.currency.address,
owner: evmAddress(wallet.account.address),
spender: position.market.address,
})
.andThen(signERC20PermitWith(wallet))
.andThen((signature) =>
repay(client, {
market: position.market.address,
amount: {
erc20: {
currency: position.currency.address,
value: {
exact: amount,
},
permitSig: signature,
},
},
sender: evmAddress(wallet.account.address),
chainId: position.market.chain.chainId,
})
);
// …
import { repay, permitTypedData } from "@aave/client/actions";
import { signERC20PermitWith } from "@aave/client/viem";
import { bigDecimal, evmAddress } from "@aave/client";
import { client } from "./client";
import { wallet } from "./wallet";
const amount = bigDecimal(1); // 1 WETH
const result = await permitTypedData(client, {
amount,
chainId: position.market.chain.chainId,
currency: position.currency.address,
owner: evmAddress(wallet.account.address),
spender: position.market.address,
})
.andThen(signERC20PermitWith(wallet))
.andThen((signature) =>
repay(client, {
market: position.market.address,
amount: {
erc20: {
currency: position.currency.address,
value: {
exact: amount,
},
permitSig: signature,
},
},
sender: evmAddress(wallet.account.address),
chainId: position.market.chain.chainId,
onBehalfOf: evmAddress("0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"),
})
);
// …
3. Process the Repay Execution Plan
Finally, handle the execution plan.
- React: Use the
usehook for the wallet library of your choice to send the transactions in the execution plan.Send Transaction - TypeScript: Use
sendfor your chosen wallet library to send the transactions in the execution plan, and await indexing withWith client..wait For Transaction - GraphQL: If the response is a
Transaction, you can send it directly using the wallet associated with the from address.Request
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);
}
};
import { repay } from "@aave/client/actions";
import { sendWith } from "@aave/client/viem";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await repay(client, {
// …
})
.andThen(sendWith(wallet))
.andThen(client.waitForTransaction);
if (result.isErr()) {
console.error("Repay failed:", result.error);
} else {
console.log("Repay successful:", result.value);
}
{
"data": {
"repay": {
"__typename": "TransactionRequest",
"to": "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
"from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"data": "0x573ade81000000000000000000000000c02…",
"value": "0",
"chainId": 1,
"operation": "REPAY"
}
}
}
In GraphQL, if the response is an Approval, you must first send the approval transaction. Once it succeeds, you can then send the original.
{
"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 Insufficient, inform the user that they don’t have enough balance to repay the requested amount.
{
"data": {
"repay": {
"__typename": "InsufficientBalanceError",
"required": {
"value": "1.0",
"decimals": 18
},
"available": {
"value": "0.5",
"decimals": 18
}
}
}
}