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:
- Identify the Reserve
- Preview the impact of the supply operation (optional)
- Supply the assets
Identify the Reserve
The first step is to filter the supply reserves to those marked with can.
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
// …
},
// …
},
// …
},
// …
];
can 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 (
can) if you plan to borrow against it.Use As Collateral - Suppliable Amount: The
uservalue is sufficient for the amount you want to supply.State. suppliable
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 user will be
null.
Preview Supply with AaveKit
Preview the impact of a supply operation before committing to it.
Use the use hook (or the imperative use 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>
);
}
import { type SupplyRequest, usePreview } from "@aave/react";
function SupplyPreview({ request }: { request: SupplyRequest }) {
const { data, error, loading } = usePreview({
action: {
supply: request,
},
suspense: true,
});
// 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>
);
}
import { usePreviewAction } from "@aave/react";
// …
const [preview, { called, data, error, loading }] = usePreviewAction();
const handler = async (request: SupplyRequest) => {
const result = await preview({
action: {
supply: request,
},
});
if (result.isOk()) {
console.log(result.value); // PreviewUserPosition
} else {
console.error(result.error);
}
};
import { preview } from "@aave/client/actions";
import { bigDecimal, evmAddress } from "@aave/client";
import { client } from "./client";
const result = await preview(client, {
action: {
supply: {
sender: evmAddress("0x123…"),
reserve: reserve.id,
amount: {
erc20: {
value: bigDecimal(100), // 100 tokens
},
},
},
},
});
if (result.isOk()) {
console.log(result.value); // PreviewUserPosition
} else {
console.error(result.error);
}
const result = await preview(client, {
action: {
supply: {
sender: evmAddress("0x123…"),
reserve: reserve.id,
amount: {
erc20: {
value: bigDecimal(100),
},
},
enableCollateral: true,
},
},
});
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
query ($request: PreviewRequest!, $currency: Currency = USD) {
preview(request: $request) {
__typename
id
healthFactor {
...HealthFactorResult
}
netApy {
...PercentNumberVariation
}
riskPremium {
...PercentNumberVariation
}
netCollateral(currency: $currency) {
...ExchangeAmountVariation
}
netBalance(currency: $currency) {
...ExchangeAmountVariation
}
projectedEarnings {
...ExchangeAmountVariation
}
maxBorrowingPower {
...ExchangeAmountVariation
}
remainingBorrowingPower {
...ExchangeAmountVariation
}
reserveRates {
supplyApy {
...PercentNumberVariation
}
borrowApy {
...PercentNumberVariation
}
}
otherConditions {
...UserPositionConditionVariation
}
}
}
fragment HealthFactorResult on HealthFactorResult {
__typename
... on HealthFactorVariation {
...HealthFactorVariation
}
... on HealthFactorError {
...HealthFactorError
}
}
fragment HealthFactorVariation on HealthFactorVariation {
__typename
current
after
}
fragment HealthFactorError on HealthFactorError {
__typename
reason
current
after
}
fragment UserPositionConditionVariation on UserPositionConditionVariation {
__typename
... on CollateralFactorVariation {
reserveId
token {
...Erc20Token
}
current {
...PercentNumber
}
after {
...PercentNumber
}
}
... on LiquidationFeeVariation {
reserveId
token {
...Erc20Token
}
current {
...PercentNumber
}
after {
...PercentNumber
}
}
... on MaxLiquidationBonusVariation {
reserveId
token {
...Erc20Token
}
current {
...PercentNumber
}
after {
...PercentNumber
}
}
}
fragment PercentNumberVariation on PercentNumberVariation {
__typename
current {
...PercentNumber
}
after {
...PercentNumber
}
}
fragment ExchangeAmountVariation on ExchangeAmountVariation {
__typename
current {
...ExchangeAmount
}
after {
...ExchangeAmount
}
}
- React: See below for examples of
Supplyobjects.Request - GraphQL: You can preview different supply operations using the
actionfield.
const request: SupplyRequest = {
sender: evmAddress("0x123…"), // User's address
reserve: reserve.id,
amount: {
erc20: {
value: bigDecimal(42), // USDC
},
},
};
const request: SupplyRequest = {
sender: evmAddress("0x123…"), // User's address
reserve: reserve.id,
amount: {
erc20: {
value: bigDecimal(42), // USDC
},
},
enableCollateral: true,
};
{
"request": {
"action": {
"supply": {
"sender": "0x123…",
"reserve": "SGVsbG8h",
"amount": {
"erc20": {
"value": "100"
}
}
}
}
}
}
{
"request": {
"action": {
"supply": {
"sender": "0x123…",
"reserve": "SGVsbG8h",
"amount": {
"erc20": {
"value": "100"
}
},
"enableCollateral": true
}
}
}
}
The Preview 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.
| Field | Impact |
|---|---|
health | Higher is better ( null if not applicable) |
net | Higher is better |
net | Higher is better |
net | Updated balance |
projected | Projected earnings |
max | Maximum borrowing power |
remaining | Remaining borrowing power |
reserve | Supply APY impact on the reserve |
reserve | Borrow APY impact on the reserve |
other | Dynamic config changes |
Supplying assets as collateral updates the Dynamic Config just for the reserve being supplied as collateral.
The other field is an array of objects describing the resulting dynamic config changes.
Collateral– Collateral factor changeFactor Variation Liquidation– Liquidation fee changeFee Variation Max– Maximum liquidation bonus changeLiquidation Bonus Variation
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,
});
import { Currency } from "@aave/react";
const [preview, { called, data, error, loading }] = usePreviewAction({
currency: Currency.Eur,
});
import { Currency } from "@aave/client";
const result = await preview(
client,
{
action: {
supply: {
// your supply action
},
},
},
{
currency: Currency.Eur,
},
);
{
"request": {
"action": {
"supply": {
// …
}
}
},
"currency": "EUR"
}
Preview Supply with Solidity
Start with the spoke address and on-chain reserve ID identified:
address spokeAddress = 0x123…;
uint256 reserveId = 42;
And an 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.
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.
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.
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.
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:
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:
use: used to send ERC-20 approval and supply transactionsSend Transaction use: used to sign ERC-20 permits when availableSign Typed Data
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 use 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);
}
});
import { useSupply } from "@aave/react";
const [status, setStatus] = useState("");
const [supply, { loading, error }] = useSupply((plan) => {
switch (plan.__typename) {
case "TransactionRequest":
setStatus("Please sign the Supply transaction in your wallet");
return sendTransaction(plan).andTee(() => setStatus("Supplying…"));
case "Erc20Approval":
if (plan.bySignature) {
setStatus("Please sign the Permit approval in your wallet");
return signTypedData(plan.bySignature);
}
setStatus("Please sign the Approval transaction in your wallet");
return sendTransaction(plan.byTransaction).andTee(() =>
setStatus("Approving…"),
);
case "PreContractActionRequired":
setStatus("Please sign the Pre-Contract Action in your wallet");
return sendTransaction(plan.transaction).andTee(() =>
setStatus("Processing…"),
);
}
});
import { useSupply } from "@aave/react";
const [supply, { loading, error }] = useSupply((plan, { cancel }) => {
if (window.confirm("Are you sure you want to continue?") === false) {
return cancel("User cancelled the operation");
}
switch (plan.__typename) {
case "TransactionRequest":
return sendTransaction(plan);
case "Erc20Approval":
return sendTransaction(plan.byTransaction);
case "PreContractActionRequired":
return sendTransaction(plan.transaction);
}
});
In the Erc20 case, the by 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.
by is null for these.
3. Execute the Supply Operation
Then, execute the supply operation.
- TypeScript: Use the
supplyaction to prepare the supply execution plan. - GraphQL: Use the
supplyquery 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
},
},
});
// …
};
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
},
},
enableCollateral: true,
});
// …
};
import { supply } from "@aave/client/actions";
import { bigDecimal, evmAddress } from "@aave/client";
import { client } from "./client";
const result = await supply(client, {
sender: evmAddress(wallet.account.address), // User's address
reserve: reserve.id,
amount: {
erc20: {
value: bigDecimal(42), // 42 USDC
},
},
});
// …
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
query ($request: SupplyRequest!) {
supply(request: $request) {
__typename
... on TransactionRequest {
...TransactionRequest
}
... on Erc20ApprovalRequired {
reason
requiredAmount {
...DecimalNumber
}
currentAllowance {
...DecimalNumber
}
approvals {
byTransaction {
...TransactionRequest
}
bySignature {
...PermitTypedData
}
}
originalTransaction {
...TransactionRequest
}
}
... on PreContractActionRequired {
reason
transaction {
...TransactionRequest
}
originalTransaction {
...TransactionRequest
}
}
... on InsufficientBalanceError {
required {
...DecimalNumber
}
available {
...DecimalNumber
}
}
}
}
fragment TransactionRequest on TransactionRequest {
to
from
data
value
chainId
operations
}
fragment PermitTypedData on PermitTypedData {
types
primaryType
domain {
name
version
chainId
verifyingContract
}
message
}
In TypeScript, to handle ERC-20 permit-based approval, wrap the supply call with permit 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,
},
},
}),
);
// …
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(),
});
In TypeScript, the permit helper:
- Calls the
supplyaction without a permit signature - If the response is
Erc20with permit signature available, signs the permit and callsApproval Required supplyagain with the signature to get an updated execution plan - 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,
permit skips the permit and the next step handles both approvals.
In GraphQL, below are some examples of Supply objects.
{
"request": {
"sender": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"reserve": "SGVsbG8h",
"amount": {
"erc20": {
"value": "42"
}
}
}
}
{
"request": {
"sender": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"reserve": "SGVsbG8h",
"amount": {
"erc20": {
"value": "42"
}
},
"enableCollateral": true
}
}
{
"request": {
"sender": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"reserve": "SGVsbG8h",
"amount": {
"erc20": {
"value": "42",
"permitSig": {
"deadline": 1700000000, // use approvals[0].bySignature.message.deadline
"value": "0xabcdef…"
}
}
}
}
}
4. Process the Supply Execution Plan
In TypeScript, use send for the wallet library of your choice to send the transactions in the execution plan, and await indexing with client..
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);
import { supply } from "@aave/client/actions";
import { permitWith, sendWith } from "@aave/client/viem";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await permitWith(wallet, (permitSig) =>
supply(client, {
// …
}),
)
.andThen(sendWith(wallet))
.andThen(client.waitForTransaction);
send handles all required approvals, including the allowance reset for
tokens like USDT.
In GraphQL, if the response is a Transaction, you can send it directly using the wallet associated with the from address.
{
"data": {
"supply": {
"__typename": "TransactionRequest",
"to": "0x94e7a5dcbe816e498b89ab752661904e2f56c485",
"from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"data": "0x852a56a5…",
"value": "0",
"chainId": 1,
"operations": ["SPOKE_SUPPLY"]
}
}
}
If the response is Erc20, you have two options:
Transaction-based approval
- Send each transaction in the
approvalsarray in sequence - Then send
originalTransaction
Permit-based approval
This is available only when approvals contains a single Erc20
item.
- Sign the
approvals[0].typed data with the sender’s account.by Signature - Call the
supplyquery again with the permit signature (see thePermit-based Approvalexample above) - The response will be a
Transactionyou can then send directlyRequest
{
"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"]
}
}
}
}
{
"data": {
"supply": {
"__typename": "Erc20ApprovalRequired",
"reason": "Token requires allowance reset before new approval",
"requiredAmount": {
"value": "42.000000",
"decimals": 6
},
"currentAllowance": {
"value": "10.000000",
"decimals": 6
},
"approvals": [
{
"byTransaction": {
"to": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"data": "0x095ea7b3000000000000000000000000878…0",
"value": "0",
"chainId": 1,
"operations": null
},
"bySignature": null
},
{
"byTransaction": {
"to": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"data": "0x095ea7b3000000000000000000000000878…42",
"value": "0",
"chainId": 1,
"operations": null
},
"bySignature": null
}
],
"originalTransaction": {
"to": "0x94e7a5dcbe816e498b89ab752661904e2f56c485",
"from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"data": "0x852a56a5…",
"value": "0",
"chainId": 1,
"operations": ["SPOKE_SUPPLY"]
}
}
}
}
With multiple approvals in Erc20, send each by
in order, then original. For USDT on Ethereum
Mainnet,
the first resets the allowance to 0 and the second sets the required amount.
If the response is Pre, you must first send the transaction request, then send the original.
{
"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 Insufficient, inform the user that they don’t have enough balance to supply the requested amount.
{
"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.
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);
};
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;
}
} else {
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:
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 { 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.
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.
spoke.supply(
reserveId,
amountToSupply,
msg.sender
);
Complete Supply Example
Here’s a complete example that executes the full 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 on, otherwise supply reverts.
Collateral Management
Manage how user supplies are used as collateral for borrowing. The process can be broken down into three steps:
- Identify the supply position
- Preview the impact of changing collateral status
- Toggle the collateral status
Identify the Supply Position
First, identify the supply position you want to modify from the user’s supply positions.
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 can 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 use hook (or the imperative use 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>
);
}
import {
type SetUserSuppliesAsCollateralRequest,
usePreview,
} from "@aave/react";
function CollateralPreview({
request,
}: {
request: SetUserSuppliesAsCollateralRequest;
}) {
const { data, error, loading } = usePreview({
action: {
setUserSuppliesAsCollateral: request,
},
suspense: true,
});
// 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>
);
}
import { usePreviewAction } from "@aave/react";
// …
const [preview, { called, data, error, loading }] = usePreviewAction();
const handler = async (request: SetUserSuppliesAsCollateralRequest) => {
const result = await preview({
action: {
setUserSuppliesAsCollateral: request,
},
});
if (result.isOk()) {
console.log(result.value); // PreviewUserPosition
} else {
console.error(result.error);
}
};
import { preview } from "@aave/client/actions";
import { evmAddress } from "@aave/client";
const result = await preview(client, {
action: {
setUserSuppliesAsCollateral: {
sender: evmAddress("0x123…"), // User's address
changes: [
{
reserve: supplyPosition.reserve.id,
enableCollateral: true, // false to disable collateral
},
],
},
},
});
if (result.isOk()) {
console.log(result.value); // PreviewUserPosition
} else {
console.error(result.error);
}
query ($request: PreviewRequest!, $currency: Currency = USD) {
preview(request: $request) {
__typename
id
healthFactor {
...HealthFactorResult
}
netApy {
...PercentNumberVariation
}
riskPremium {
...PercentNumberVariation
}
netCollateral(currency: $currency) {
...ExchangeAmountVariation
}
netBalance(currency: $currency) {
...ExchangeAmountVariation
}
projectedEarnings {
...ExchangeAmountVariation
}
maxBorrowingPower {
...ExchangeAmountVariation
}
remainingBorrowingPower {
...ExchangeAmountVariation
}
otherConditions {
...UserPositionConditionVariation
}
}
}
fragment HealthFactorResult on HealthFactorResult {
__typename
... on HealthFactorVariation {
...HealthFactorVariation
}
... on HealthFactorError {
...HealthFactorError
}
}
fragment HealthFactorVariation on HealthFactorVariation {
__typename
current
after
}
fragment HealthFactorError on HealthFactorError {
__typename
reason
current
after
}
fragment UserPositionConditionVariation on UserPositionConditionVariation {
__typename
... on CollateralFactorVariation {
reserveId
token {
...Erc20Token
}
current {
...PercentNumber
}
after {
...PercentNumber
}
}
... on LiquidationFeeVariation {
reserveId
token {
...Erc20Token
}
current {
...PercentNumber
}
after {
...PercentNumber
}
}
... on MaxLiquidationBonusVariation {
reserveId
token {
...Erc20Token
}
current {
...PercentNumber
}
after {
...PercentNumber
}
}
}
fragment PercentNumberVariation on PercentNumberVariation {
__typename
current {
...PercentNumber
}
after {
...PercentNumber
}
}
fragment ExchangeAmountVariation on ExchangeAmountVariation {
__typename
current {
...ExchangeAmount
}
after {
...ExchangeAmount
}
}
- React: The
Setcan be as follows.User Supplies As Collateral Request - GraphQL: The
Previewcan be as follows.Request
const request: SetUserSuppliesAsCollateralRequest = {
sender: evmAddress("0x123…"), // User's address
changes: [
{
reserve: supplyPosition.reserve.id,
enableCollateral: true, // false to disable collateral
},
],
};
const request: SetUserSuppliesAsCollateralRequest = {
sender: evmAddress("0x123…"), // User's address
changes: [
{
reserve: supplyPositions[0].reserve.id,
enableCollateral: true,
},
{
reserve: supplyPositions[1].reserve.id,
enableCollateral: true,
},
],
};
{
"request": {
"action": {
"setUserSuppliesAsCollateral": {
"sender": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"changes": [
{
"reserve": "SGVsbG8h",
"enableCollateral": true
}
]
}
}
}
}
The Preview 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.
| Field | Impact |
|---|---|
health | Higher is better ( null if not applicable) |
risk | Lower is better |
net | Higher is better |
net | Higher is better |
max | Maximum borrowing power |
remaining | Remaining borrowing power |
other | 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 other field is an array of objects describing the resulting dynamic config changes.
Collateral– Collateral factor changeFactor Variation Liquidation– Liquidation fee changeFee Variation Max– Maximum liquidation bonus changeLiquidation Bonus Variation
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,
});
import { Currency } from "@aave/react";
const [preview, { called, data, error, loading }] = usePreviewAction({
currency: Currency.Eur,
});
import { Currency } from "@aave/client";
const result = await preview(
client,
{
action: {
setUserSuppliesAsCollateral: {
/* … */
},
},
},
{
currency: Currency.Eur,
},
);
{
"request": {
"action": {
"setUserSuppliesAsCollateral": {
// …
}
}
},
"currency": "EUR"
}
Preview Changes with Solidity
Start with the spoke address and on-chain reserve ID identified:
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.
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.
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);
uint32 dynamicConfigKey = userPosition.dynamicConfigKey;
ISpoke.DynamicReserveConfig memory config = spoke.getDynamicReserveConfig(reserveId, dynamicConfigKey);
uint256 collateralFactor = config.collateralFactor;
uint256 currentWeightedSum = (currentData.totalCollateralValue * currentData.avgCollateralFactor) / 1e18;
uint256 removedWeightedValue = (supplyValue * collateralFactor) / 10000;
uint256 newWeightedSum = currentWeightedSum - removedWeightedValue;
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 use hook for the wallet library of your choice.
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 use hook to prepare the transaction request.
import { useSetUserSuppliesAsCollateral } from "@aave/react";
const [setAsCollateral, { loading, error }] = useSetUserSuppliesAsCollateral(
(transaction) => sendTransaction(transaction),
);
import { useSetUserSuppliesAsCollateral } from "@aave/react";
const [status, setStatus] = useState("");
const [setAsCollateral, { loading, error }] = useSetUserSuppliesAsCollateral(
(transaction) => {
setStatus("Please sign the transaction in your wallet");
return sendTransaction(transaction).andTee(() =>
setStatus("Setting collateral…"),
);
},
);
import { useSetUserSuppliesAsCollateral } from "@aave/react";
const [setAsCollateral, { loading, error }] = useSetUserSuppliesAsCollateral(
(transaction, { cancel }) => {
if (window.confirm("Are you sure you want to continue?") === false) {
return cancel("User cancelled the operation");
}
return sendTransaction(transaction);
},
);
3. Execute the Transaction
Then, update the collateral status.
- TypeScript: Use the
setaction to prepare the transaction request.User Supplies As Collateral - GraphQL: Use the
setquery to prepare the transaction request.User Supplies As 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,
},
],
});
};
// …
import { setUserSuppliesAsCollateral } from "@aave/client/actions";
import { evmAddress } from "@aave/client";
import { client } from "./client";
const result = await setUserSuppliesAsCollateral(client, {
sender: evmAddress(wallet.account.address), // User's address
changes: [
{
reserve: supplyPosition.reserve.id,
enableCollateral: true,
},
],
});
// …
query ($request: SetUserSuppliesAsCollateralRequest!) {
setUserSuppliesAsCollateral(request: $request) {
__typename
... on TransactionRequest {
...TransactionRequest
}
}
}
fragment TransactionRequest on TransactionRequest {
to
from
data
value
chainId
operations
}
In GraphQL, below is an example of a Set object.
{
"request": {
"sender": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"changes": [
{
"reserve": "SGVsbG8h",
"enableCollateral": true
}
]
}
}
4. Process the Collateral Execution Plan
In TypeScript, use send for the wallet library of your choice to send the transaction, and await indexing with client..
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);
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(),
});
In GraphQL, the API returns a Transaction, which you can send directly with the wallet tied to the from address.
{
"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.
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);
};
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;
}
} else {
console.log("Collateral updated successfully with hash:", result.value.txHash);
}
Toggle Collateral with Solidity
To enable or disable a supplied asset as collateral, call set on the Spoke contract.
import { ISpoke } from "aave-v4/src/spoke/interfaces/ISpoke.sol";
ISpoke spoke = ISpoke(spokeAddress);
spoke.setUsingAsCollateral(
reserveId,
true,
msg.sender
);
import { ISpoke } from "aave-v4/src/spoke/interfaces/ISpoke.sol";
ISpoke spoke = ISpoke(spokeAddress);
spoke.setUsingAsCollateral(
reserveId,
false,
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 Preview you pass to the use hook.
For example:
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 use hook to estimate both the network fee for the provided action and its fiat equivalent.
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. flag to determine if the underlying token is a wrapped native token. The Native Token Gateway address is available from the chain details.
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.
const execute = async () => {
const result = await supply({
sender: evmAddress(wallet.account.address), // User's address
reserve: reserve.id,
amount: {
native: bigDecimal(42), // 42 ETH
},
});
// …
};
const result = await supply(client, {
sender: evmAddress(wallet.account.address), // User's address
reserve: reserve.id,
amount: {
native: bigDecimal(42), // 42 ETH
},
});
// …
{
"request": {
"sender": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"reserve": "SGVsbG8h",
"amount": {
"native": "42" // 42 ETH
}
}
}
Native Tokens with Solidity
Start with the spoke address, reserve ID, and native gateway address identified:
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.
import { ISpoke } from "aave-v4/src/spoke/interfaces/ISpoke.sol";
ISpoke spoke = ISpoke(spokeAddress);
spoke.setUserPositionManager(nativeGateway, true);
2. Supply Native Tokens
Then, call supply on the gateway, sending the native token amount via msg..
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. 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 set and the gateway directly.
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);
}