Skip to Content
docs

Supply Assets on Aave V4

Supplying assets to Aave V4 allows you to:

  • Receive reserve shares representing your deposit
  • Earn variable supply APY on your reserve shares
  • Use reserve shares as collateral for borrowing
  • Withdraw assets and earnings anytime

Supplying collateral while you have open debt can raise your health factor.

Supply Assets

Supplying assets to Aave can be broken down into the following steps:

  1. Identify the Reserve
  2. Preview the impact of the supply operation (optional)
  3. Supply the assets

Identify the Reserve

The first step is to filter the supply reserves to those marked with canSupply: true.

Reserves List
const reserves: Reserve[] = [
  {
    id: "SGVsbG8h",
    onChainId: "42",
    canSupply: true,
    settings: {
      supplyCap: {
        amount: { value: BigDecimal(2000000000.0) }, // 2B USDC
        // …
      },
      // …
    },
    // …
  },
  {
    id: "V29ybGQh",
    onChainId: "43",
    canSupply: false, // cannot supply to this reserve
    settings: {
      supplyCap: {
        amount: { value: BigDecimal(1000000000.0) }, // 1B USDC
        // …
      },
      // …
    },
    // …
  },
  // …
];

canSupply means the reserve is active: not frozen, not paused, and below its supply cap.

From the remaining reserves, choose one that meets these criteria:

  • Collateral Enabled: The reserve can be used as collateral (canUseAsCollateral) if you plan to borrow against it.
  • Suppliable Amount: The userState.suppliable value is sufficient for the amount you want to supply.
Example Reserve Data
const reserve: Reserve = {
  id: "SGVsbG8h",
  onChainId: "42",
  canSupply: true,
  canUseAsCollateral: true,
  chain: {
    chainId: 1,
    name: "Ethereum",
  },
  spoke: {
    address: "0x123…",
    // …
  },
  status: {
    frozen: false,
    paused: false,
  },
  settings: {
    supplyCap: {
      amount: { value: BigDecimal(2000000000.0) }, // 2B USDC
      // …
    },
    // …
  },
  userState: {
    suppliable: {
      amount: {
        onChainValue: "1000000000",
        decimals: 6,
        value: BigDecimal(1000.0), // Can supply up to 1,000 USDC
      },
      // …
    },
    // …
  },
  // …
};

Include a user address when fetching reserve data, or userState will be null.

Preview Supply with AaveKit

Preview the impact of a supply operation before committing to it.

Use the usePreview hook (or the imperative usePreviewAction variant) in React, the preview action in TypeScript, or the preview query in GraphQL to preview the impact of the supply operation on the user’s position.

import { type SupplyRequest, usePreview } from "@aave/react";

function SupplyPreview({ request }: { request: SupplyRequest }) {
  const { data, error, loading } = usePreview({
    action: {
      supply: 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?.toFixed(2) ?? "N/A"}</p>
      <p>To: {data.healthFactor.after?.toFixed(2) ?? "N/A"}</p>

      <h3>User Risk Premium:</h3>
      <p>From: {data.riskPremium.current.normalized.toFixed(2)}%</p>
      <p>To: {data.riskPremium.after.normalized.toFixed(2)}%</p>

      <h3>Net APY:</h3>
      <p>From: {data.netApy.current.normalized.toFixed(2)}%</p>
      <p>To: {data.netApy.after.normalized.toFixed(2)}%</p>

      <h3>Net Collateral:</h3>
      <p>From: {data.netCollateral.current.value.toDisplayString(2)}</p>
      <p>To: {data.netCollateral.after.value.toDisplayString(2)}</p>

      <h3>Max Borrowing Power:</h3>
      <p>From: {data.maxBorrowingPower.current.value.toDisplayString(2)}</p>
      <p>To: {data.maxBorrowingPower.after.value.toDisplayString(2)}</p>
    </div>
  );
}
  • React: See below for examples of SupplyRequest objects.
  • GraphQL: You can preview different supply operations using the action field.
const request: SupplyRequest = {
  sender: evmAddress("0x123…"), // User's address
  reserve: reserve.id,
  amount: {
    erc20: {
      value: bigDecimal(42), // USDC
    },
  },
};

The PreviewUserPosition shows the impact of the supply operation by comparing current and after states, with the table below outlining key fields and how to interpret them.

FieldImpact
healthFactor.[current → after]: BigDecimal|nullHigher is better
(null if not applicable)
netApy.[current → after]: PercentNumberHigher is better
netCollateral.[current → after]: ExchangeAmountHigher is better
netBalance.[current → after]: ExchangeAmountUpdated balance
projectedEarnings.[current → after]: ExchangeAmountProjected earnings
maxBorrowingPower.[current → after]: ExchangeAmountMaximum borrowing power
remainingBorrowingPower.[current → after]: ExchangeAmountRemaining borrowing power
reserveRates.supplyApy.[current → after]: PercentNumberSupply APY impact on the reserve
reserveRates.borrowApy.[current → after]: PercentNumberBorrow APY impact on the reserve
otherConditions: UserPositionConditionVariation[]Dynamic config changes

Supplying assets as collateral updates the Dynamic Config just for the reserve being supplied as collateral.

The otherConditions field is an array of objects describing the resulting dynamic config changes.

  • CollateralFactorVariation – Collateral factor change
  • LiquidationFeeVariation – Liquidation fee change
  • MaxLiquidationBonusVariation – Maximum liquidation bonus change

You can also specify a different currency to return fiat amounts in.

import { Currency } from "@aave/react";

const { data, error, loading } = usePreview({
  action: {
    supply: request,
  },
  currency: Currency.Eur,
});

Preview Supply with Solidity

Start with the spoke address and on-chain reserve ID identified:

Target Reserve
address spokeAddress = 0x123…;
uint256 reserveId = 42;

And an amount to supply:

Amount to Supply
uint256 amountToSupply = 42e6; // 42 USDC

Follow these steps to preview the impact of a supply operation on the user’s position.

1. Get Current Account Data

First, retrieve the user’s current account data from the Spoke contract.

Get Account Data
import { ISpoke } from "aave-v4/src/spoke/interfaces/ISpoke.sol";

ISpoke spoke = ISpoke(spokeAddress);
ISpoke.UserAccountData memory currentData = spoke.getUserAccountData(user);

2. Calculate New Collateral Value

Then, retrieve the reserve data and asset price from the oracle to calculate the USD value of the new supply, and add it to the current total collateral.

New Collateral Value
address oracleAddress = spoke.ORACLE();
ISpoke.Reserve memory reserve = spoke.getReserve(reserveId);
uint256 assetPrice = IAaveOracle(oracleAddress).getReservePrice(reserveId);

uint256 supplyValue = amountToSupply * assetPrice * 10 ** (18 - reserve.decimals);
uint256 newTotalCollateralValue = currentData.totalCollateralValue + supplyValue;

3. Get Collateral Factor

Then, retrieve the user’s position configuration to get the collateral factor for the reserve.

Get Collateral Factor
uint32 dynamicConfigKey = spoke.getUserPosition(reserveId, user).dynamicConfigKey;
ISpoke.DynamicReserveConfig memory config = spoke.getDynamicReserveConfig(reserveId, dynamicConfigKey);
uint256 collateralFactor = config.collateralFactor;

4. Compute New Health Factor

Finally, calculate the new health factor by incorporating the new supply’s collateral value and factor into the weighted sum. This assumes the supply is enabled as collateral.

Compute New Health Factor
import { Math } from "aave-v4/src/dependencies/openzeppelin/Math.sol";

uint256 currentWeightedSum = (currentData.totalCollateralValue * currentData.avgCollateralFactor) / 1e18;
uint256 newSupplyWeightedValue = (supplyValue * collateralFactor) / 10000;
uint256 newWeightedSum = currentWeightedSum + newSupplyWeightedValue;

uint256 newHealthFactor = currentData.totalDebtValueRay == 0 ? type(uint256).max : Math.mulDiv(newWeightedSum * 1e18, 1e27, currentData.totalDebtValueRay);

Complete Preview Example

Here’s a complete example that calculates the health factor before a supply operation:

Complete Health Factor Preview
import { ISpoke } from "aave-v4/src/spoke/interfaces/ISpoke.sol";
import { IAaveOracle } from "aave-v4/src/spoke/interfaces/IAaveOracle.sol";
import { Math } from "aave-v4/src/dependencies/openzeppelin/Math.sol";

function previewSupplyHealthFactor(
  address spokeAddress,
  uint256 reserveId,
  uint256 amountToSupply,
  address user
) external view returns (uint256) {
  ISpoke spoke = ISpoke(spokeAddress);
  IAaveOracle oracle = IAaveOracle(spoke.ORACLE());

  // Step 1: Get Current Account Data
  ISpoke.UserAccountData memory currentData = spoke.getUserAccountData(user);

  // Step 2: Calculate New Collateral Value
  ISpoke.Reserve memory reserve = spoke.getReserve(reserveId);
  uint256 assetPrice = oracle.getReservePrice(reserveId);
  uint256 supplyValue = amountToSupply * assetPrice * 10 ** (18 - reserve.decimals);

  // Step 3: Get Collateral Factor
  uint32 dynamicConfigKey = spoke.getUserPosition(reserveId, user).dynamicConfigKey;
  uint256 collateralFactor = spoke.getDynamicReserveConfig(reserveId, dynamicConfigKey).collateralFactor;

  // Step 4: Compute New Health Factor
  uint256 currentWeightedSum = (currentData.totalCollateralValue * currentData.avgCollateralFactor) / 1e18;
  uint256 newWeightedSum = currentWeightedSum + (supplyValue * collateralFactor) / 10000;

  return currentData.totalDebtValueRay == 0 ? type(uint256).max : Math.mulDiv(newWeightedSum * 1e18, 1e27, currentData.totalDebtValueRay);
}

Step-by-Step with AaveKit

Supply assets to the selected reserve.

Supplying ERC-20 tokens requires token approval. You can choose between two approaches:

  • Transaction-based approval: Sends a separate ERC-20 approve() transaction before the supply transaction (2 transactions total).
  • Permit-based approval: Signs an EIP-2612 permit to approve and supply in a single transaction. More gas-efficient, but requires the token to support permits.

To supply assets to an Aave reserve with AaveKit, follow these steps.

1. Configure Wallet Integration

In React, first instantiate the hooks for the wallet library of your choice:

  • useSendTransaction: used to send ERC-20 approval and supply transactions
  • useSignTypedData: used to sign ERC-20 permits when available
Viem
import { useWalletClient } from "wagmi";
import { useSendTransaction, useSignTypedData } from "@aave/react/viem";

// …

const { data: wallet } = useWalletClient();
const [sendTransaction] = useSendTransaction(wallet);
const [signTypedData] = useSignTypedData(wallet);

2. Define the Supply Flow

Then, in React, use the useSupply hook to prepare the supply operation.

import { useSupply } from "@aave/react";

const [supply, { loading, error }] = useSupply((plan) => {
  switch (plan.__typename) {
    case "TransactionRequest":
      return sendTransaction(plan);

    case "Erc20Approval":
      // If token supports EIP-2612 permits, sign permit (recommended)
      if (plan.bySignature) {
        return signTypedData(plan.bySignature);
      }
      // use traditional approval transaction
      return sendTransaction(plan.byTransaction);

    case "PreContractActionRequired":
      return sendTransaction(plan.transaction);
  }
});

In the Erc20Approval case, the bySignature field is only available if the token supports EIP-2612 permits.

For tokens that need an allowance reset, like USDT on Ethereum Mainnet, the hook calls your callback twice: reset to 0, then set the new value. bySignature is null for these.

3. Execute the Supply Operation

Then, execute the supply operation.

  • TypeScript: Use the supply action to prepare the supply execution plan.
  • GraphQL: Use the supply query to prepare the supply execution plan.
import { bigDecimal, evmAddress } from "@aave/react";

const execute = async () => {
  const result = await supply({
    sender: evmAddress(wallet.account.address), // User's address
    reserve: reserve.id,
    amount: {
      erc20: {
        value: bigDecimal(42), // 42 USDC
      },
    },
  });

  // …
};

In TypeScript, to handle ERC-20 permit-based approval, wrap the supply call with permitWith for the wallet library of your choice.

import { supply } from "@aave/client/actions";
import { permitWith } from "@aave/client/viem";
import { bigDecimal, evmAddress } from "@aave/client";

import { client } from "./client";
import { wallet } from "./wallet";

const result = await permitWith(wallet, (permitSig) =>
  supply(client, {
    sender: evmAddress(wallet.account.address),
    reserve: reserve.id,
    amount: {
      erc20: {
        value: bigDecimal(42), // 42 USDC
        permitSig,
      },
    },
  }),
);

// …

In TypeScript, the permitWith helper:

  1. Calls the supply action without a permit signature
  2. If the response is Erc20ApprovalRequired with permit signature available, signs the permit and calls supply again with the signature to get an updated execution plan
  3. Otherwise, returns the execution plan unchanged (no permit needed or token doesn’t support permits)

For tokens that need an allowance reset, like USDT on Ethereum Mainnet, permitWith skips the permit and the next step handles both approvals.

In GraphQL, below are some examples of SupplyRequest objects.

{
  "request": {
    "sender": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
    "reserve": "SGVsbG8h",
    "amount": {
      "erc20": {
        "value": "42"
      }
    }
  }
}

4. Process the Supply Execution Plan

In TypeScript, use sendWith for the wallet library of your choice to send the transactions in the execution plan, and await indexing with client.waitForTransaction.

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

sendWith handles all required approvals, including the allowance reset for tokens like USDT.

In GraphQL, if the response is a TransactionRequest, you can send it directly using the wallet associated with the from address.

TransactionRequest
{
  "data": {
    "supply": {
      "__typename": "TransactionRequest",
      "to": "0x94e7a5dcbe816e498b89ab752661904e2f56c485",
      "from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
      "data": "0x852a56a5…",
      "value": "0",
      "chainId": 1,
      "operations": ["SPOKE_SUPPLY"]
    }
  }
}

If the response is Erc20ApprovalRequired, you have two options:

Transaction-based approval

  1. Send each transaction in the approvals array in sequence
  2. Then send originalTransaction

Permit-based approval

This is available only when approvals contains a single Erc20Approval item.

  1. Sign the approvals[0].bySignature typed data with the sender’s account.
  2. Call the supply query again with the permit signature (see the Permit-based Approval example above)
  3. The response will be a TransactionRequest you can then send directly
{
  "data": {
    "supply": {
      "__typename": "Erc20ApprovalRequired",
      "reason": "Insufficient allowance for ERC-20 token transfer",
      "requiredAmount": {
        "value": "42.000000",
        "decimals": 6
      },
      "currentAllowance": {
        "value": "0.000000",
        "decimals": 6
      },
      "approvals": [
        {
          "byTransaction": {
            "to": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
            "from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
            "data": "0x095ea7b3000000000000000000000000878…",
            "value": "0",
            "chainId": 1,
            "operations": null
          },
          "bySignature": {
            "types": { /* … */ },
            "primaryType": "Permit",
            "domain": { /* … */ },
            "message": { /* … */ }
          }
        }
      ],
      "originalTransaction": {
        "to": "0x94e7a5dcbe816e498b89ab752661904e2f56c485",
        "from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
        "data": "0x852a56a5…",
        "value": "0",
        "chainId": 1,
        "operations": ["SPOKE_SUPPLY"]
      }
    }
  }
}

With multiple approvals in Erc20ApprovalRequired, send each byTransaction in order, then originalTransaction. For USDT on Ethereum Mainnet, the first resets the allowance to 0 and the second sets the required amount.

If the response is PreContractActionRequired, you must first send the transaction request, then send the originalTransaction.

PreContractActionRequired
{
  "data": {
    "supply": {
      "__typename": "PreContractActionRequired",
      "reason": "Native gateway needs approval",
      "transaction": {
        "to": "0x94e7a5dcbe816e498b89ab752661904e2f56c485",
        "from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
        "data": "0x095ea7b3000000000000000000000000878…",
        "value": "0",
        "chainId": 1,
        "operations": null
      },
      "originalTransaction": {
        "to": "0x94e7a5dcbe816e498b89ab752661904e2f56c485",
        "from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
        "data": "0x852a56a5…",
        "value": "0",
        "chainId": 1,
        "operations": ["SPOKE_SUPPLY"]
      }
    }
  }
}

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.000000",
        "decimals": 6
      },
      "available": {
        "value": "15.500000",
        "decimals": 6
      }
    }
  }
}

5. Handle the Supply Result

Finally, in React and TypeScript, handle the result.

Example
const execute = async () => {
  const result = await supply(/* … */);

  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:",
          `required: ${result.error.cause.required.value.toDisplayString(2)}`,
          `available: ${result.error.cause.available.value.toDisplayString(2)}`,
        );
        break;

      case "UnexpectedError":
        console.error(result.error.message);
        break;
    }
    return;
  }

  console.log("Supply successful with hash:", result.value.txHash);
};

Step-by-Step with Solidity

Use the same identifiers and amount to supply used to preview the supply operation:

Target Reserve
address spokeAddress = 0x123…;
uint256 reserveId = 42;

uint256 amountToSupply = 42e6; // 42 USDC

Complete the supply process with the following steps:

1. Import the Spoke Interface

First, create an instance of the ISpoke interface.

Import the Spoke Interface
import { ISpoke } from "aave-v4/src/spoke/interfaces/ISpoke.sol";

ISpoke spoke = ISpoke(spokeAddress);

2. Approve Token Transfer

Then, retrieve the reserve from the spoke and approve the Spoke to transfer the specified amount of ERC-20 tokens.

Approve Token Transfer
import { IERC20 } from "aave-v4/src/dependencies/openzeppelin/IERC20.sol";

ISpoke.Reserve memory reserve = spoke.getReserve(reserveId);
IERC20 token = IERC20(reserve.underlying);
token.approve(spokeAddress, amountToSupply);

3. Supply Tokens

Finally, call the supply function on the Spoke contract, specifying the reserve ID, amount, and the address to credit the supply shares to.

Supply
spoke.supply(
  reserveId,
  amountToSupply,
  msg.sender
);

Complete Supply Example

Here’s a complete example that executes the full supply flow:

Complete Supply Flow
import { ISpoke } from "aave-v4/src/spoke/interfaces/ISpoke.sol";
import { IERC20 } from "aave-v4/src/dependencies/openzeppelin/IERC20.sol";

function supplyToReserve(
  address spokeAddress,
  uint256 reserveId,
  uint256 amountToSupply,
  address onBehalfOf
) external {
  ISpoke spoke = ISpoke(spokeAddress);

  // Step 1: Pull tokens from the caller and approve the Spoke
  IERC20 token = IERC20(spoke.getReserve(reserveId).underlying);
  token.transferFrom(msg.sender, address(this), amountToSupply);
  token.approve(spokeAddress, amountToSupply);

  // Step 2: Supply tokens
  spoke.supply(reserveId, amountToSupply, onBehalfOf);
}

The contract must be an active Position Manager approved by onBehalfOf, otherwise supply reverts.

Collateral Management

Manage how user supplies are used as collateral for borrowing. The process can be broken down into three steps:

  1. Identify the supply position
  2. Preview the impact of changing collateral status
  3. Toggle the collateral status

Identify the Supply Position

First, identify the supply position you want to modify from the user’s supply positions.

UserSupplyItem
const supplyPosition: UserSupplyItem = {
  reserve: {
    id: "SGVsbG8h",
    onChainId: "42",
    chain: {
      chainId: 1,
      name: "Ethereum",
    },
    spoke: {
      address: "0x123…",
      // …
    },
    canUseAsCollateral: true,
    // …
  },
  isCollateral: false,
  balance: {
    amount: {
      value: BigDecimal(46.2),
    },
    // …
  },
  principal: {
    amount: {
      value: BigDecimal(42.0),
    },
    // …
  },
  interest: {
    amount: {
      value: BigDecimal(4.2),
    },
    // …
  },
};

The reserve must have canUseAsCollateral: true to be used as collateral.

Preview Changes with AaveKit

Preview the impact of changing collateral status before executing the transaction.

Disabling collateral can lower your health factor and put the position at risk of liquidation.

Use the usePreview hook (or the imperative usePreviewAction variant) in React, the preview action in TypeScript, or the preview query in GraphQL to preview the impact of collateral status changes on the user’s position.

import {
  type SetUserSuppliesAsCollateralRequest,
  usePreview,
} from "@aave/react";

function CollateralPreview({
  request,
}: {
  request: SetUserSuppliesAsCollateralRequest;
}) {
  const { data, error, loading } = usePreview({
    action: {
      setUserSuppliesAsCollateral: 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?.toFixed(2) ?? "N/A"}</p>
      <p>To: {data.healthFactor.after?.toFixed(2) ?? "N/A"}</p>

      <h3>User Risk Premium:</h3>
      <p>From: {data.riskPremium.current.normalized.toFixed(2)}%</p>
      <p>To: {data.riskPremium.after.normalized.toFixed(2)}%</p>

      <h3>Net Collateral:</h3>
      <p>From: {data.netCollateral.current.value.toDisplayString(2)}</p>
      <p>To: {data.netCollateral.after.value.toDisplayString(2)}</p>

      <h3>Max Borrowing Power:</h3>
      <p>From: {data.maxBorrowingPower.current.value.toDisplayString(2)}</p>
      <p>To: {data.maxBorrowingPower.after.value.toDisplayString(2)}</p>
    </div>
  );
}
  • React: The SetUserSuppliesAsCollateralRequest can be as follows.
  • GraphQL: The PreviewRequest can be as follows.
const request: SetUserSuppliesAsCollateralRequest = {
  sender: evmAddress("0x123…"), // User's address
  changes: [
    {
      reserve: supplyPosition.reserve.id,
      enableCollateral: true, // false to disable collateral
    },
  ],
};

The PreviewUserPosition shows the impact of the collateral status change by comparing current and after states, with the table below outlining key fields and how to interpret them.

FieldImpact
healthFactor.[current → after]: BigDecimal|nullHigher is better
(null if not applicable)
riskPremium.[current → after]: PercentNumberLower is better
netApy.[current → after]: PercentNumberHigher is better
netCollateral.[current → after]: ExchangeAmountHigher is better
maxBorrowingPower.[current → after]: ExchangeAmountMaximum borrowing power
remainingBorrowingPower.[current → after]: ExchangeAmountRemaining borrowing power
otherConditions: UserPositionConditionVariation[]Dynamic config changes

Enabling collateral updates the Dynamic Config just for the reserve being enabled, while disabling collateral updates the Dynamic Config for all reserves in which the user has supplies or borrows within the same user position.

The otherConditions field is an array of objects describing the resulting dynamic config changes.

  • CollateralFactorVariation – Collateral factor change
  • LiquidationFeeVariation – Liquidation fee change
  • MaxLiquidationBonusVariation – Maximum liquidation bonus change

You can also specify a different currency to return fiat amounts in.

import { Currency } from "@aave/react";

const { data, error, loading } = usePreview({
  action: {
    setUserSuppliesAsCollateral: request,
  },
  currency: Currency.Eur,
});

Preview Changes with Solidity

Start with the spoke address and on-chain reserve ID identified:

Target Reserve
address spokeAddress = 0x123…;
uint256 reserveId = 42;

Follow the steps below to preview the impact of enabling or disabling collateral on the user’s position.

1. Get Current Account Data

First, retrieve the user’s current account data from the Spoke contract.

Get Account Data
import { ISpoke } from "aave-v4/src/spoke/interfaces/ISpoke.sol";
import { IAaveOracle } from "aave-v4/src/spoke/interfaces/IAaveOracle.sol";
import { Math } from "aave-v4/src/dependencies/openzeppelin/Math.sol";

ISpoke spoke = ISpoke(spokeAddress);
ISpoke.UserAccountData memory currentData = spoke.getUserAccountData(user);

2. Calculate Supply Position Value

Then, retrieve the user’s supply position and calculate its USD value using the oracle.

Supply Position Value
ISpoke.UserPosition memory userPosition = spoke.getUserPosition(reserveId, user);
ISpoke.Reserve memory reserve = spoke.getReserve(reserveId);
address oracleAddress = spoke.ORACLE();
uint256 assetPrice = IAaveOracle(oracleAddress).getReservePrice(reserveId);

uint256 suppliedAssets = spoke.getUserSuppliedAssets(reserveId, user);
uint256 supplyValue = suppliedAssets * assetPrice * 10 ** (18 - reserve.decimals);

3. Compute New Health Factor

Finally, calculate the new health factor based on whether collateral is being enabled or disabled.

uint32 dynamicConfigKey = reserve.dynamicConfigKey;
ISpoke.DynamicReserveConfig memory config = spoke.getDynamicReserveConfig(reserveId, dynamicConfigKey);
uint256 collateralFactor = config.collateralFactor;

uint256 currentWeightedSum = (currentData.totalCollateralValue * currentData.avgCollateralFactor) / 1e18;
uint256 newSupplyWeightedValue = (supplyValue * collateralFactor) / 10000;
uint256 newWeightedSum = currentWeightedSum + newSupplyWeightedValue;

uint256 newHealthFactor = currentData.totalDebtValueRay == 0 ? type(uint256).max : Math.mulDiv(newWeightedSum * 1e18, 1e27, currentData.totalDebtValueRay);

Toggle Collateral with AaveKit

Toggle the collateral status of any supplied asset using the following steps.

Enabling collateral updates the asset’s Dynamic Config (Collateral Factor, Liquidation Fee, Max Liquidation Bonus). Disabling it increases risk and also updates the User Risk Premium of the whole position (see User Position Conditions).

To enable or disable a supplied asset as collateral with AaveKit, follow these steps.

1. Configure Wallet Integration

In React, 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 Collateral Management Flow

Then, in React, use the useSetUserSuppliesAsCollateral hook to prepare the transaction request.

import { useSetUserSuppliesAsCollateral } from "@aave/react";

const [setAsCollateral, { loading, error }] = useSetUserSuppliesAsCollateral(
  (transaction) => sendTransaction(transaction),
);

3. Execute the Transaction

Then, update the collateral status.

  • TypeScript: Use the setUserSuppliesAsCollateral action to prepare the transaction request.
  • GraphQL: Use the setUserSuppliesAsCollateral query to prepare the transaction request.
Set Collateral
import { evmAddress } from "@aave/react";

const execute = async () => {
  const result = await setAsCollateral({
    sender: evmAddress(wallet.account.address), // User's address
    changes: [
      {
        reserve: supplyPosition.reserve.id,
        enableCollateral: true,
      },
    ],
  });
};

// …

In GraphQL, below is an example of a SetUserSuppliesAsCollateralRequest object.

Basic Usage
{
  "request": {
    "sender": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
    "changes": [
      {
        "reserve": "SGVsbG8h",
        "enableCollateral": true
      }
    ]
  }
}

4. Process the Collateral Execution Plan

In TypeScript, use sendWith for the wallet library of your choice to send the transaction, and await indexing with client.waitForTransaction.

import { setUserSuppliesAsCollateral } from "@aave/client/actions";
import { sendWith } from "@aave/client/viem";

import { client } from "./client";
import { wallet } from "./wallet";

const result = await setUserSuppliesAsCollateral(client, {
  // …
})
  .andThen(sendWith(wallet))
  .andThen(client.waitForTransaction);

In GraphQL, the API returns a TransactionRequest, which you can send directly with the wallet tied to the from address.

TransactionRequest
{
  "data": {
    "setUserSuppliesAsCollateral": {
      "__typename": "TransactionRequest",
      "to": "0x94e7a5dcbe816e498b89ab752661904e2f56c485",
      "from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
      "data": "0x9e35c533…",
      "value": "0",
      "chainId": 1,
      "operations": ["SPOKE_SET_USER_USING_AS_COLLATERAL"]
    }
  }
}

5. Handle the Collateral Result

Finally, in React and TypeScript, handle the result.

Example
const execute = async () => {
  const result = await setAsCollateral(/* … */);

  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 "UnexpectedError":
        console.error(result.error.message);
        break;
    }
    return;
  }

  console.log("Collateral updated successfully with hash:", result.value.txHash);
};

Toggle Collateral with Solidity

To enable or disable a supplied asset as collateral, call setUsingAsCollateral on the Spoke contract.

import { ISpoke } from "aave-v4/src/spoke/interfaces/ISpoke.sol";

ISpoke spoke = ISpoke(spokeAddress);
spoke.setUsingAsCollateral(
  reserveId,
  true,
  msg.sender
);

Advanced Usage

Network Fee

This experimental React hook only works with Viem or Wagmi integrations.

Estimate the network cost of any action using the same PreviewAction you pass to the usePreview hook.

For example:

PreviewAction
import { type PreviewAction } from "@aave/react";

const action: PreviewAction = {
  supply: {
    sender: evmAddress("0x123…"), // User's address
    reserve: supplyPosition.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.exchange.symbol}
        {fee.exchange.value.toDisplayString(2)}
      </span>
    </p>
  );
}

Native Tokens with AaveKit

When the Reserve’s underlying token is the wrapped version of the chain’s native token (e.g., WETH on Ethereum), you can supply the asset as the chain’s native token using the Native Token Gateway.

Use the reserve.asset.underlying.isWrappedNativeToken flag to determine if the underlying token is a wrapped native token. The Native Token Gateway address is available from the chain details.

WETH Reserve
const reserve: Reserve = {
  id: "SGVsbG8h",
  onChainId: "42",
  canSupply: true,
  canUseAsCollateral: true,
  asset: {
    underlying: {
      address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
      info: {
        name: "Wrapped Ether",
        symbol: "WETH",
        decimals: 18,
        // …
      },
      isWrappedNativeToken: true,
      // …
    },
    // …
  },
  settings: {
    supplyCap: {
      amount: { value: BigDecimal(2000000000.0) }, // 2B WETH
      // …
    },
    // …
  },
  spoke: {
    address: "0x123…",
    // …
  },
  chain: {
    chainId: 1,
    name: "Ethereum",
    nativeGateway: "0xabc…",
  },
  // …
};

Specify the amount in the amount field as a native value.

Supply Native
const execute = async () => {
  const result = await supply({
    sender: evmAddress(wallet.account.address), // User's address
    reserve: reserve.id,
    amount: {
      native: bigDecimal(42), // 42 ETH
    },
  });

  // …
};

Native Tokens with Solidity

Start with the spoke address, reserve ID, and native gateway address identified:

Native Token Gateway
address spokeAddress = 0x123…;
uint256 reserveId = 42;
address nativeGateway = 0xabc…;

Follow the steps below to supply native tokens (e.g., ETH) to a wrapped native token reserve (e.g., WETH).

1. Approve the Gateway

First, approve the Native Token Gateway as your position manager. This is a one-time setup step.

Approve Gateway
import { ISpoke } from "aave-v4/src/spoke/interfaces/ISpoke.sol";

ISpoke spoke = ISpoke(spokeAddress);
spoke.setUserPositionManager(nativeGateway, true);

2. Supply Native Tokens

Then, call supplyNative on the gateway, sending the native token amount via msg.value.

Supply Native
import { INativeTokenGateway } from "aave-v4/src/position-manager/interfaces/INativeTokenGateway.sol";

INativeTokenGateway gateway = INativeTokenGateway(nativeGateway);
gateway.supplyNative{value: amountToSupply}(spokeAddress, reserveId, amountToSupply);

The gateway wraps the native token (ETH → WETH), approves the spoke, and calls spoke.supply on your behalf.

Complete Native Token Example

Here’s a complete example for supplying native tokens. This helper acts on the contract’s own position. To supply for a user’s wallet, the user calls setUserPositionManager and the gateway directly.

Complete Native Supply Flow
import { ISpoke } from "aave-v4/src/spoke/interfaces/ISpoke.sol";
import { INativeTokenGateway } from "aave-v4/src/position-manager/interfaces/INativeTokenGateway.sol";

function supplyNativeToken(
  address spokeAddress,
  address nativeGateway,
  uint256 reserveId,
  uint256 amountToSupply
) external payable {
  require(msg.value == amountToSupply, "Incorrect ETH amount");

  ISpoke spoke = ISpoke(spokeAddress);

  // Step 1: Approve gateway as position manager (one-time setup)
  if (!spoke.isPositionManager(address(this), nativeGateway)) {
    spoke.setUserPositionManager(nativeGateway, true);
  }

  // Step 2: Supply native tokens via gateway
  INativeTokenGateway(nativeGateway).supplyNative{value: amountToSupply}(spokeAddress, reserveId, amountToSupply);
}