Skip to Content
docs

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:

MethodDescriptionTypical Scenario
Direct SupplySupply 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 AnotherSupply 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 PermitUse 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 AnotherSame 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:

Reserve
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.isFrozen is false - the reserve is not frozen
  • Reserve.isPaused is false - the reserve is not paused
  • Reserve.userState.suppliable.amount.value is greater than 0 - the amount the user can supply to the reserve given their unique circumstances

Include a user address when fetching market and reserve data, or Reserve.userState will be empty.

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

Additionally, if Reserve.acceptsNative 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 useSupply 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,
  });

  // …
};

If Reserve.permitSupported is true and you supply ERC-20 tokens, you can sign permits and prepare supply transactions.

  • React: Use the useSupply and useERC20Permit hooks, with the useERC20Permit implementation for the wallet library of choice.
  • TypeScript: Use the supply and permitTypedData actions together with the signERC20PermitWith helper, with the signERC20PermitWith implementation for the wallet library of choice.
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,
    })
  );

  // …
};

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

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": {
    "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 InsufficientBalanceError, inform the user that they don’t have enough balance to supply the requested amount.

InsufficientBalanceError
{
  "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 MarketUserReserveSupplyPosition object:

MarketUserReserveSupplyPosition
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.canBeCollateral is true - the asset can be used as collateral
  • position.isCollateral is false - the asset is not currently used as collateral

To disable a position as collateral, you only need to check if position.isCollateral 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 useCollateralToggle hook in React, the collateralToggle action in TypeScript, or the collateralToggle query in GraphQL to create the transaction request for enabling or disabling collateral.

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

  // …
};

3. Send the Collateral Transaction

Finally, send the transaction.

  • React: Use the useSendTransaction hook for the wallet library of your choice to send the transaction.
  • TypeScript: Use sendWith for your chosen wallet library to initiate the transaction, and await indexing with client.waitForTransaction.
  • GraphQL: The response is a TransactionRequest that can be sent directly using the wallet associated with the from address.
Viem
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);
  }
};