Supply Assets on Aave V3
Supply assets to Aave V3 markets to earn interest, and enable or disable them as collateral.
Permit Overview
Some Aave operations support permit functionality using EIP-2612 signatures to authorize ERC-20 token transfers within the same transaction.
Benefits of Permit
- 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
Permit is available for ERC-20 tokens that implement EIP-2612.
Supply Assets
By supplying assets to an Aave market, users earn interest on their deposits according to the reserve’s supply APY rate.
When supplying assets to an Aave market, you can use the following methods:
| Method | Description | Typical Scenario |
|---|---|---|
| Direct Supply | Supply ERC-20 or native tokens directly from the sender’s wallet. aTokens are sent to the sender’s address. | User supplies assets from their own wallet. |
| On Behalf of Another | Supply from the sender’s wallet, but send aTokens to another address. | User supplies from one wallet and the supply position (aTokens) belongs to another. |
| With Permit | Use a permit signature to skip ERC-20 approval and supply 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 supply, but aTokens are sent to another address. | No ERC-20 allowance needed and the supply position belongs to another wallet. |
To supply assets to an Aave market, follow these steps.
1. Identify the Supply Reserve
First, determine which reserve you want to supply assets to.
Given this Reserve object for WETH on an Ethereum market:
const reserve: Reserve = {
__typename: "Reserve",
market: {
__typename: "MarketInfo",
address: "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
chain: { chainId: 1 },
// …
},
underlyingToken: {
__typename: "Currency",
symbol: "WETH",
name: "Wrapped Ether",
address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
// …
},
acceptsNative: {
__typename: "NativeCurrency",
symbol: "ETH",
name: "Ethereum",
// …
},
isFrozen: false,
isPaused: false,
permitSupported: true,
userState: {
__typename: "ReserveUserState",
suppliable: {
__typename: "TokenAmount",
amount: {
__typename: "DecimalValue",
value: "1245.67",
raw: "1245670000000000000000",
decimals: 18,
// …
},
// …
},
},
// …
};
To supply to the reserve, you need to verify these conditions:
Reserve.isis Frozen false- the reserve is not frozenReserve.isis Paused false- the reserve is not pausedReserve.is greater than 0 - the amount the user can supply to the reserve given their unique circumstancesuser State. suppliable. amount. value
Include a user address when fetching market and reserve data, or
Reserve. will be empty.
The Reserve. flag indicates whether the underlying token supports EIP-2612 permits.
Additionally, if Reserve. is not null, users can choose to supply the asset 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 Supply Execution Plan
Next, if all of these conditions are met, we can proceed with creating the execution plan for the supply operation.
Use the use hook in React, the supply action in TypeScript, or the supply query in GraphQL to create the execution plan for supplying assets to a market reserve. In React and TypeScript, you can optionally supply on behalf of another address.
import { useWalletClient } from "wagmi";
import { useSupply, bigDecimal, evmAddress } from "@aave/react";
// …
const { data: walletClient } = useWalletClient();
const [supply, supplying] = useSupply();
const execute = async () => {
const result = await supply({
market: reserve.market.address,
amount: {
erc20: {
currency: reserve.underlyingToken.address,
value: bigDecimal(42), // 42 WETH
},
},
sender: evmAddress(walletClient!.account.address),
chainId: reserve.market.chain.chainId,
});
// …
};
import { useWalletClient } from "wagmi";
import { useSupply, bigDecimal, evmAddress } from "@aave/react";
// …
const { data: walletClient } = useWalletClient();
const [supply, supplying] = useSupply();
const execute = async () => {
const result = await supply({
market: reserve.market.address,
amount: {
native: bigDecimal(42), // 42 ETH
},
sender: evmAddress(walletClient!.account.address),
chainId: reserve.market.chain.chainId,
});
// …
};
import { useWalletClient } from "wagmi";
import { useSupply, bigDecimal, evmAddress } from "@aave/react";
// …
const { data: walletClient } = useWalletClient();
const [supply, supplying] = useSupply();
const execute = async () => {
const result = await supply({
market: reserve.market.address,
amount: {
erc20: {
currency: reserve.underlyingToken.address,
value: bigDecimal(42), // 42 WETH
},
// OR
// native: bigDecimal(42), // 42 ETH
},
sender: evmAddress(walletClient!.account.address),
chainId: reserve.market.chain.chainId,
onBehalfOf: evmAddress("0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"),
});
// …
};
import { supply } from "@aave/client/actions";
import { bigDecimal, evmAddress } from "@aave/client";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await supply(client, {
market: reserve.market.address,
amount: {
erc20: {
currency: reserve.underlyingToken.address,
value: bigDecimal(42), // 42 WETH
},
},
sender: evmAddress(wallet.account.address),
chainId: reserve.market.chain.chainId,
});
// …
import { supply } from "@aave/client/actions";
import { bigDecimal, evmAddress } from "@aave/client";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await supply(client, {
market: reserve.market.address,
amount: {
native: bigDecimal(42), // 42 ETH
},
sender: evmAddress(wallet.account.address),
chainId: reserve.market.chain.chainId,
});
// …
import { supply } from "@aave/client/actions";
import { bigDecimal, evmAddress } from "@aave/client";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await supply(client, {
market: reserve.market.address,
amount: {
erc20: {
currency: reserve.underlyingToken.address,
value: bigDecimal(42), // 42 WETH
},
// OR
// native: bigDecimal(42), // 42 ETH
},
sender: evmAddress(wallet.account.address),
chainId: reserve.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 {
supply(
request: {
market: "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2"
amount: {
erc20: {
currency: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
value: "42"
}
# OR
# native: "42" # if the reserve accepts native currency
}
sender: "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"
chainId: 1
}
) {
__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 you supply ERC-20 tokens, you can sign permits and prepare supply transactions.
- React: Use the
useandSupply usehooks, with theERC20 Permit useimplementation for the wallet library of choice.ERC20 Permit - TypeScript: Use the
supplyandpermitactions together with theTyped Data signhelper, with theERC20 Permit With signimplementation for the wallet library of choice.ERC20 Permit With
import { useSupply, 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 [supply, supplying] = useSupply();
const execute = async () => {
const amount = bigDecimal(42); // 42 WETH
const permit = await signPermit({
amount,
chainId: reserve.market.chain.chainId,
currency: reserve.underlyingToken.address,
owner: evmAddress(walletClient!.account.address),
spender: reserve.market.address,
}).andThen((signature) =>
supply({
market: reserve.market.address,
amount: {
erc20: {
currency: reserve.underlyingToken.address,
value: amount, // 42 WETH
permitSig: signature,
},
},
sender: evmAddress(walletClient!.account.address),
chainId: reserve.market.chain.chainId,
})
);
// …
};
import { useSupply, 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 [supply, supplying] = useSupply();
const execute = async () => {
const amount = bigDecimal(42); // 42 WETH
const permit = await signPermit({
amount,
chainId: reserve.market.chain.chainId,
currency: reserve.underlyingToken.address,
owner: evmAddress(walletClient!.account.address),
spender: reserve.market.address,
}).andThen((signature) =>
supply({
market: reserve.market.address,
amount: {
erc20: {
currency: reserve.underlyingToken.address,
value: amount, // 42 WETH
permitSig: signature,
},
},
sender: evmAddress(walletClient!.account.address),
chainId: reserve.market.chain.chainId,
onBehalfOf: evmAddress("0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"),
})
);
// …
};
import { supply, 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(42); // 42 WETH
const result = await permitTypedData(client, {
amount,
chainId: reserve.market.chain.chainId,
currency: reserve.underlyingToken.address,
owner: evmAddress(wallet.account.address),
spender: reserve.market.address,
})
.andThen(signERC20PermitWith(wallet))
.andThen((signature) =>
supply(client, {
market: reserve.market.address,
amount: {
erc20: {
currency: reserve.underlyingToken.address,
value: amount,
permitSig: signature,
},
},
sender: evmAddress(wallet.account.address),
chainId: reserve.market.chain.chainId,
})
);
// …
import { supply, 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(42); // 42 WETH
const result = await permitTypedData(client, {
amount,
chainId: reserve.market.chain.chainId,
currency: reserve.underlyingToken.address,
owner: evmAddress(wallet.account.address),
spender: reserve.market.address,
})
.andThen(signERC20PermitWith(wallet))
.andThen((signature) =>
supply(client, {
market: reserve.market.address,
amount: {
erc20: {
currency: reserve.underlyingToken.address,
value: amount,
permitSig: signature,
},
},
sender: evmAddress(wallet.account.address),
chainId: reserve.market.chain.chainId,
onBehalfOf: evmAddress("0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"),
})
);
// …
3. Process the Supply Execution Plan
Finally, you need to handle the execution plan, and the process is the same whether or not you used a permit in the previous step.
With a permit signature, the execution plan runs as a single transaction.
- 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, useSupply } from "@aave/react";
import { useSendTransaction } from "@aave/react/viem";
// …
const { data: walletClient } = useWalletClient();
const [supply, supplying] = useSupply();
const [sendTransaction, sending] = useSendTransaction(walletClient);
// …
// Optional: combine loading states
const loading = supplying.loading || sending.loading;
const error = supplying.error || sending.error;
// …
const execute = async () => {
const result = await supply({
// …
}).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("Supply failed:", result.error);
} else {
console.log("Supply successful with hash:", result.value);
}
};
import { supply } from "@aave/client/actions";
import { sendWith } from "@aave/client/viem";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await supply(client, {
// …
})
.andThen(sendWith(wallet))
.andThen(client.waitForTransaction);
if (result.isErr()) {
console.error("Supply failed:", result.error);
} else {
console.log("Supply successful:", result.value);
}
{
"data": {
"supply": {
"__typename": "TransactionRequest",
"to": "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
"from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"data": "0x617ba037000000000000000000000000c0…",
"value": "0",
"chainId": 1,
"operation": "SUPPLY"
}
}
}
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": {
"supply": {
"__typename": "ApprovalRequired",
"reason": "Insufficient allowance for ERC-20 token transfer",
"requiredAmount": {
"value": "42.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": "0x617ba037000000000000000000000000c0…",
"value": "0",
"chainId": 1,
"operation": "SUPPLY"
}
}
}
}
In GraphQL, if the response is an Insufficient, inform the user that they don’t have enough balance to supply the requested amount.
{
"data": {
"supply": {
"__typename": "InsufficientBalanceError",
"required": {
"value": "42.0",
"decimals": 18
},
"available": {
"value": "15.5",
"decimals": 18
}
}
}
}
Collateral Management
To manage which supplied assets are used as collateral for borrowing, follow these steps.
1. Identify the Collateral Position
First, determine which user supply position you want to enable or disable as collateral.
Given this Market object:
const position: MarketUserReserveSupplyPosition = {
__typename: "MarketUserReserveSupplyPosition",
market: {
__typename: "MarketInfo",
address: "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
chain: { chainId: 1 },
// …
},
currency: {
__typename: "Currency",
symbol: "WETH",
name: "Wrapped Ether",
address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
// …
},
balance: {
__typename: "TokenAmount",
amount: {
__typename: "DecimalValue",
value: "5.0",
raw: "5000000000000000000",
decimals: 18,
// …
},
// …
},
isCollateral: false, // Currently not used as collateral
canBeCollateral: true, // Can be enabled as collateral
// …
};
To enable a position as collateral, you need to verify these conditions:
position.iscan Be Collateral true- the asset can be used as collateralposition.isis Collateral false- the asset is not currently used as collateral
To disable a position as collateral, you only need to check if position. is true.
An Aave DAO vote can disable a reserve as collateral. This blocks new collateral use but doesn’t affect existing positions.
2. Prepare the Collateral Transaction Request
Next, we can proceed with creating the transaction request for the collateral toggle operation.
Use the use hook in React, the collateral action in TypeScript, or the collateral query in GraphQL to create the transaction request for enabling or disabling collateral.
import { useWalletClient } from "wagmi";
import { useCollateralToggle, evmAddress } from "@aave/react";
// …
const { data: walletClient } = useWalletClient();
const [toggleCollateral, toggling] = useCollateralToggle();
const execute = async () => {
const result = await toggleCollateral({
market: position.market.address,
underlyingToken: position.currency.address,
user: evmAddress(walletClient!.account.address),
chainId: position.market.chain.chainId,
});
// …
};
import { collateralToggle } from "@aave/client/actions";
import { evmAddress } from "@aave/client";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await collateralToggle(client, {
market: position.market.address,
underlyingToken: position.currency.address,
user: evmAddress(wallet.account.address),
chainId: position.market.chain.chainId,
});
// …
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 {
collateralToggle(
request: {
market: "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2"
underlyingToken: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
user: "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"
chainId: 1
}
) {
to
from
data
value
chainId
}
}
3. Send the Collateral Transaction
Finally, send the transaction.
- React: Use the
usehook for the wallet library of your choice to send the transaction.Send Transaction - TypeScript: Use
sendfor your chosen wallet library to initiate the transaction, and await indexing withWith client..wait For Transaction - GraphQL: The response is a
Transactionthat can be sent directly using the wallet associated with theRequest fromaddress.
import { useWalletClient } from "wagmi";
import { useCollateralToggle } from "@aave/react";
import { useSendTransaction } from "@aave/react/viem";
// …
const { data: walletClient } = useWalletClient();
const [toggleCollateral, toggling] = useCollateralToggle();
const [sendTransaction, sending] = useSendTransaction(walletClient);
// …
// Optional: combine loading states
const loading = toggling.loading || sending.loading;
const error = toggling.error || sending.error;
// …
const execute = async () => {
const result = await toggleCollateral({
// …
}).andThen(sendTransaction);
if (result.isErr()) {
console.error("Collateral toggle failed:", result.error);
} else {
console.log("Collateral toggle successful with hash:", result.value);
}
};
import { collateralToggle } from "@aave/client/actions";
import { sendWith } from "@aave/client/viem";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await collateralToggle(client, {
// …
})
.andThen(sendWith(wallet))
.andThen(client.waitForTransaction);
if (result.isErr()) {
console.error("Collateral toggle failed:", result.error);
} else {
console.log("Collateral toggle successful:", result.value);
}
{
"data": {
"collateralToggle": {
"__typename": "TransactionRequest",
"to": "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
"from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"data": "0x5a3b74b9000000000000000000000000c02…",
"value": "0",
"chainId": 1,
"operation": "RESERVE_USED_AS_COLLATERAL_ENABLED"
}
}
}