Withdraw Assets on Aave V4
Withdrawing assets from Aave V4 allows you to:
- Access your supplied assets and earned interest
- Reduce or fully exit a supply position
Withdrawing collateral while you have open debt lowers your health factor and can put the position at risk of liquidation.
Withdraw Assets
Withdrawing can be broken down into the following steps:
- Identify the supply position to withdraw from
- Preview the impact of the withdraw operation
- Withdraw the assets
Identify the Supply Position
Given a list of the user’s supply positions, identify the supply position matching the token you want to withdraw and with a withdrawable amount that covers your needs.
You can’t withdraw while the reserve is paused.
For example, let’s say you have identified the following User object.
const supplyPosition: UserSupplyItem = {
reserve: {
id: "SGVsbG8h",
onChainId: "42",
chain: {
chainId: 1,
name: "Ethereum",
},
spoke: {
address: "0x123…",
// …
},
asset: {
underlying: {
address: "0xa0b86a33e6e2ad05ad6c9ac3b6e5e5f6e7b6c1b2", // USDC
},
// …
},
status: {
paused: false,
},
// … other reserve properties
},
isCollateral: true,
balance: {
amount: {
value: BigDecimal(1042.5), // principal + accrued interest
// …
},
// …
},
withdrawable: {
amount: {
value: BigDecimal(1000.0), // withdrawable now, capped by available liquidity
// …
},
// …
},
// …
};
Keep in mind that if the asset is used as collateral (is), withdrawing may:
- Reduce your borrowing capacity and lower the position’s health factor, increasing the risk of liquidation.
- Raise the User Risk Premium, and with it the borrow APY of open borrow positions on the same Spoke, if the remaining collateral has a higher
reserve..settings. collateral Risk
Preview Withdraw with AaveKit
Preview the impact of a withdraw 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 withdraw operation on the user’s position.
import { type WithdrawRequest, usePreview } from "@aave/react";
function WithdrawPreview({ request }: { request: WithdrawRequest }) {
const { data, error, loading } = usePreview({
action: {
withdraw: 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>
</div>
);
}
import { type WithdrawRequest, usePreview } from "@aave/react";
function WithdrawPreview({ request }: { request: WithdrawRequest }) {
const { data, error, loading } = usePreview({
action: {
withdraw: 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>
</div>
);
}
import { type WithdrawRequest, usePreviewAction } from "@aave/react";
const [preview, { called, data, error, loading }] = usePreviewAction();
const handler = async (request: WithdrawRequest) => {
const result = await preview({
action: {
withdraw: 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";
const result = await preview(client, {
action: {
withdraw: {
sender: evmAddress("0x789…"), // User's address
reserve: supplyPosition.reserve.id,
amount: {
erc20: {
exact: bigDecimal(500), // 500 USDC
},
},
},
},
});
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: {
withdraw: {
sender: evmAddress("0x789…"), // User's address
reserve: supplyPosition.reserve.id,
amount: {
erc20: {
max: true, // Withdraw everything (principal + interest)
},
},
},
},
});
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
Withdrawcan be as follows.Request - GraphQL: The
Previewcan be as follows.Request
const request: WithdrawRequest = {
sender: evmAddress("0x789…"), // User's address
reserve: supplyPosition.reserve.id,
amount: {
erc20: {
exact: bigDecimal(500), // 500 USDC
},
},
};
const request: WithdrawRequest = {
sender: evmAddress("0x789…"), // User's address
reserve: supplyPosition.reserve.id,
amount: {
erc20: {
max: true, // Withdraw everything (principal + interest)
},
},
};
{
"request": {
"action": {
"withdraw": {
"sender": "0x789…",
"reserve": "SGVsbG8h",
"amount": {
"erc20": {
"exact": "500"
}
}
}
}
}
}
{
"request": {
"action": {
"withdraw": {
"sender": "0x789…",
"reserve": "SGVsbG8h",
"amount": {
"erc20": {
"max": true
}
}
}
}
}
}
The Preview shows the impact of the withdraw 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) |
risk | Lower is better |
net | Higher is better |
net | Higher is better |
net | Updated balance |
max | Maximum borrowing power |
remaining | Remaining borrowing power |
other | Dynamic config changes |
Withdrawing collateral updates the Dynamic Config 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: {
withdraw: 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: {
withdraw: {
/* … */
},
},
},
{
currency: Currency.Eur,
},
);
{
"request": {
"action": {
"withdraw": {
// …
}
}
},
"currency": "EUR"
}
Preview Withdraw with Solidity
Start with the spoke address and on-chain reserve ID identified:
address spokeAddress = 0x123…;
uint256 reserveId = 42;
And an amount to withdraw:
uint256 amountToWithdraw = 42e6; // 42 USDC
Follow the steps below to preview the impact of a withdraw 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";
import { Math } from "aave-v4/src/dependencies/openzeppelin/Math.sol";
ISpoke spoke = ISpoke(spokeAddress);
ISpoke.UserAccountData memory currentData = spoke.getUserAccountData(user);
2. Calculate Withdraw Value
Then, retrieve the reserve data and asset price from the oracle to calculate the USD value of the withdrawal amount.
address oracleAddress = spoke.ORACLE();
ISpoke.Reserve memory reserve = spoke.getReserve(reserveId);
uint256 assetPrice = IAaveOracle(oracleAddress).getReservePrice(reserveId);
uint256 withdrawValue = amountToWithdraw * assetPrice * 10 ** (18 - reserve.decimals);
3. Compute New Health Factor
Finally, calculate the new health factor by removing the withdrawn collateral value and factor.
ISpoke.UserPosition memory userPosition = spoke.getUserPosition(reserveId, user);
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 = (withdrawValue * collateralFactor) / 10000;
uint256 newWeightedSum = currentWeightedSum - removedWeightedValue;
uint256 newHealthFactor = currentData.totalDebtValueRay == 0 ? type(uint256).max : Math.mulDiv(newWeightedSum * 1e18, 1e27, currentData.totalDebtValueRay);
Complete Preview Example
Here’s a complete Solidity example that calculates the health factor before a withdraw 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 previewWithdrawHealthFactor(
address spokeAddress,
uint256 reserveId,
uint256 amountToWithdraw,
address user
) external view returns (uint256) {
ISpoke spoke = ISpoke(spokeAddress);
// Step 1: Get current account data
ISpoke.UserAccountData memory currentData = spoke.getUserAccountData(user);
// Step 2: Calculate withdraw value
ISpoke.Reserve memory reserve = spoke.getReserve(reserveId);
uint256 assetPrice = IAaveOracle(spoke.ORACLE()).getReservePrice(reserveId);
uint256 withdrawValue = amountToWithdraw * assetPrice * 10 ** (18 - reserve.decimals);
// Step 3: Compute new health factor
uint32 dynamicConfigKey = spoke.getUserPosition(reserveId, user).dynamicConfigKey;
uint256 collateralFactor = spoke.getDynamicReserveConfig(reserveId, dynamicConfigKey).collateralFactor;
uint256 currentWeightedSum = (currentData.totalCollateralValue * currentData.avgCollateralFactor) / 1e18;
uint256 newWeightedSum = currentWeightedSum - (withdrawValue * collateralFactor) / 10000;
return currentData.totalDebtValueRay == 0 ? type(uint256).max : Math.mulDiv(newWeightedSum * 1e18, 1e27, currentData.totalDebtValueRay);
}
Step-by-Step with AaveKit
Withdraw assets from the selected supply position.
Withdrawing collateral with open debt increases risk and updates the Dynamic Config, which updates the position’s User Risk Premium.
To withdraw assets from an Aave supply position 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 Withdraw Flow
Then, in React, use the use hook to prepare the withdraw operation.
import { useWithdraw } from "@aave/react";
const [withdraw, { loading, error }] = useWithdraw((plan) => {
switch (plan.__typename) {
case "TransactionRequest":
return sendTransaction(plan);
case "PreContractActionRequired":
return sendTransaction(plan.transaction);
}
});
import { useWithdraw } from "@aave/react";
const [status, setStatus] = useState("");
const [withdraw, { loading, error }] = useWithdraw((plan) => {
switch (plan.__typename) {
case "TransactionRequest":
setStatus("Please sign the Withdraw transaction in your wallet");
return sendTransaction(plan).andTee(() => setStatus("Withdrawing…"));
case "PreContractActionRequired":
setStatus("Please sign the Pre-Contract Action in your wallet");
return sendTransaction(plan.transaction).andTee(() =>
setStatus("Processing…"),
);
}
});
import { useWithdraw } from "@aave/react";
const [withdraw, { loading, error }] = useWithdraw((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 "PreContractActionRequired":
return sendTransaction(plan.transaction);
}
});
3. Execute the Withdraw Operation
Then, execute the desired withdraw operation.
- TypeScript: Use the
withdrawaction to prepare the withdraw execution plan. - GraphQL: Use the
withdrawquery to prepare the withdraw execution plan.
import { bigDecimal, evmAddress } from "@aave/react";
const execute = async () => {
const result = await withdraw({
sender: evmAddress(wallet.account.address), // User's address
reserve: supplyPosition.reserve.id,
amount: {
erc20: {
exact: bigDecimal(500), // Withdraw 500 USDC
},
},
});
// …
};
import { evmAddress } from "@aave/react";
const execute = async () => {
const result = await withdraw({
sender: evmAddress(wallet.account.address), // User's address
reserve: supplyPosition.reserve.id,
amount: {
erc20: {
max: true, // Withdraw everything (principal + interest)
},
},
});
// …
};
import { withdraw } from "@aave/client/actions";
import { bigDecimal, evmAddress } from "@aave/client";
import { client } from "./client";
const result = await withdraw(client, {
sender: evmAddress(wallet.account.address), // User's address
reserve: supplyPosition.reserve.id,
amount: {
erc20: {
exact: bigDecimal(500), // Withdraw 500 USDC
},
},
});
// …
import { withdraw } from "@aave/client/actions";
import { evmAddress } from "@aave/client";
import { client } from "./client";
const result = await withdraw(client, {
sender: evmAddress(wallet.account.address), // User's address
reserve: supplyPosition.reserve.id,
amount: {
erc20: {
max: true, // Withdraw everything (principal + interest)
},
},
});
// …
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
query ($request: WithdrawRequest!) {
withdraw(request: $request) {
__typename
... on TransactionRequest {
...TransactionRequest
}
... on PreContractActionRequired {
reason
transaction {
...TransactionRequest
}
originalTransaction {
...TransactionRequest
}
}
... on InsufficientBalanceError {
required {
...DecimalNumber
}
available {
...DecimalNumber
}
}
}
}
fragment TransactionRequest on TransactionRequest {
to
from
data
value
chainId
operations
}
In GraphQL, below are some examples of Withdraw objects.
{
"request": {
"sender": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"reserve": "SGVsbG8h",
"amount": {
"erc20": {
"exact": "500"
}
}
}
}
{
"request": {
"sender": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"reserve": "SGVsbG8h",
"amount": {
"erc20": {
"max": true
}
}
}
}
4. Process the 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 { withdraw } from "@aave/client/actions";
import { sendWith } from "@aave/client/viem";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await withdraw(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, if the response is a Transaction, you can send it directly using the wallet associated with the from address.
{
"data": {
"withdraw": {
"__typename": "TransactionRequest",
"to": "0x94e7a5dcbe816e498b89ab752661904e2f56c485",
"from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"data": "0x0ad58d2f…",
"value": "0",
"chainId": 1,
"operations": ["SPOKE_WITHDRAW"]
}
}
}
If the response is a Pre, you must first send the transaction request. Once it succeeds, you can then send the original.
{
"data": {
"withdraw": {
"__typename": "PreContractActionRequired",
"reason": "Authorize the native wrapping gateway to operate on behalf of the user",
"transaction": {
"to": "0x94e7a5dcbe816e498b89ab752661904e2f56c485",
"from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"data": "0x095ea7b3000000000000000000000000878…",
"value": "0",
"chainId": 1,
"operations": null
},
"originalTransaction": {
"to": "0x94e7a5dcbe816e498b89ab752661904e2f56c485",
"from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"data": "0x0ad58d2f…",
"value": "0",
"chainId": 1,
"operations": ["SPOKE_WITHDRAW"]
}
}
}
}
If the response is an Insufficient, inform the user that they don’t have enough balance to withdraw the requested amount.
{
"data": {
"withdraw": {
"__typename": "InsufficientBalanceError",
"required": {
"value": "500.000000",
"decimals": 6
},
"available": {
"value": "250.500000",
"decimals": 6
}
}
}
}
5. Handle the Result
Finally, in React and TypeScript, handle the result.
const execute = async () => {
const result = await withdraw(/* … */);
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(`Invalid withdrawal amount: ${result.error.message}`);
break;
case "UnexpectedError":
console.error(result.error.message);
break;
}
return;
}
console.log("Withdraw 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(`Invalid withdrawal amount: ${result.error.message}`);
break;
case "UnexpectedError":
console.error(result.error.message);
break;
}
} else {
console.log("Withdraw successful with hash:", result.value.txHash);
}
Step-by-Step with Solidity
Use the same identifiers and amount to withdraw used to preview the withdraw operation:
address spokeAddress = 0x123…;
uint256 reserveId = 42;
uint256 amountToWithdraw = 42e6; // 42 USDC
Complete the withdraw 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. Withdraw Assets
Finally, call the withdraw function on the Spoke contract, specifying the reserve ID, amount, and the on address (the position owner). The withdrawn assets are sent to msg..
spoke.withdraw(
reserveId,
amountToWithdraw,
msg.sender
);
Complete Withdraw Example
Here’s a complete Solidity example that executes the full withdraw flow:
import { ISpoke } from "aave-v4/src/spoke/interfaces/ISpoke.sol";
function withdrawFromReserve(
address spokeAddress,
uint256 reserveId,
uint256 amountToWithdraw,
address onBehalfOf
) external {
ISpoke(spokeAddress).withdraw(reserveId, amountToWithdraw, onBehalfOf);
}
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 = {
withdraw: {
sender: evmAddress("0x123…"), // User's address
reserve: supplyPosition.reserve.id,
amount: {
erc20: {
exact: 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 withdraw 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 supplyPosition: UserSupplyItem = {
reserve: {
id: "SGVsbG8h",
onChainId: "42",
asset: {
underlying: {
address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
info: {
name: "Wrapped Ether",
symbol: "WETH",
decimals: 18,
// …
},
isWrappedNativeToken: true,
// …
},
// …
},
spoke: {
address: "0x123…",
// …
},
chain: {
chainId: 1,
name: "Ethereum",
nativeGateway: "0xabc…",
},
// …
},
balance: {
amount: {
value: BigDecimal(1.25),
// …
},
// …
},
// …
};
Specify the amount in the amount field as a native value.
const execute = async () => {
const result = await withdraw({
sender: evmAddress(wallet.account.address), // User's address
reserve: supplyPosition.reserve.id,
amount: {
native: {
exact: bigDecimal(1), // Withdraw 1 ETH
},
},
});
// …
};
const execute = async () => {
const result = await withdraw({
sender: evmAddress(wallet.account.address), // User's address
reserve: supplyPosition.reserve.id,
amount: {
native: {
max: true, // Withdraw everything
},
},
});
// …
};
const result = await withdraw(client, {
sender: evmAddress(wallet.account.address), // User's address
reserve: supplyPosition.reserve.id,
amount: {
native: {
exact: bigDecimal(1), // Withdraw 1 ETH
},
},
});
// …
const result = await withdraw(client, {
sender: evmAddress(wallet.account.address), // User's address
reserve: supplyPosition.reserve.id,
amount: {
native: {
max: true, // Withdraw everything
},
},
});
// …
{
"request": {
"sender": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"reserve": "SGVsbG8h",
"amount": {
"native": {
"exact": "1"
}
}
}
}
{
"request": {
"sender": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"reserve": "SGVsbG8h",
"amount": {
"native": {
"max": true
}
}
}
}
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 withdraw native tokens (e.g., ETH) from an existing supply position in 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. Withdraw Native Tokens
Then, call withdraw on the gateway to withdraw and unwrap the tokens.
import { INativeTokenGateway } from "aave-v4/src/position-manager/interfaces/INativeTokenGateway.sol";
INativeTokenGateway gateway = INativeTokenGateway(nativeGateway);
gateway.withdrawNative(spokeAddress, reserveId, amountToWithdraw);
import { INativeTokenGateway } from "aave-v4/src/position-manager/interfaces/INativeTokenGateway.sol";
INativeTokenGateway gateway = INativeTokenGateway(nativeGateway);
gateway.withdrawNative(spokeAddress, reserveId, type(uint256).max);
The gateway calls spoke. on your behalf, unwraps the token (WETH → ETH), and sends the native token to the caller.
Complete Native Token Example
Here’s a complete Solidity example that executes the native token withdraw flow. This helper acts on the contract’s own position. The gateway sends the native tokens to the contract, so it needs a receive() function. To withdraw 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 withdrawNativeToken(
address spokeAddress,
address nativeGateway,
uint256 reserveId,
uint256 amountToWithdraw
) external {
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: Withdraw native tokens via gateway
INativeTokenGateway(nativeGateway).withdrawNative(spokeAddress, reserveId, amountToWithdraw);
}
receive() external payable {}