Skip to Content
docs

Borrow Assets on Aave V4

Borrowing assets from Aave V4 allows you to:

  • Access liquidity without selling your assets
  • Leverage your positions with borrowed capital
  • Repay borrowed assets at any time to close your position

Borrowing lowers your health factor. Keep enough collateral and watch the health factor, especially in volatile markets, to avoid liquidation.

Borrow Assets

Borrowing on Aave V4 takes place at the spoke level, and users can only borrow against collateral supplied to that same spoke.

Borrowing can be broken down into the following steps:

  1. Identify the Reserve to borrow from
  2. Preview the impact of the borrow operation
  3. Borrow the assets

Identify the Reserve

The first step is to filter the borrow reserves on the same spoke as the collateral to those marked with canBorrow: true.

Reserves List
const reserves: Reserve[] = [
  {
    id: "SGVsbG8h",
    onChainId: "42",
    canBorrow: true,
    settings: {
      borrowCap: {
        amount: { value: BigDecimal(1000000000.0) }, // 1B USDC
        // …
      },
      // …
    },
    status: {
      frozen: false,
      paused: false,
    },
    spoke: {
      address: "0x123…",
      // …
    },
    // …
  },
  {
    id: "V29ybGQh",
    onChainId: "43",
    canBorrow: false, // cannot borrow from this reserve
    settings: {
      borrowCap: {
        amount: { value: BigDecimal(500000000.0) }, // 500M DAI
        // …
      },
      // …
    },
    status: {
      frozen: true, // reserve is frozen
      paused: false,
    },
    spoke: {
      address: "0x123…",
      // …
    },
    // …
  },
  // …
];

canBorrow means the reserve is active: not frozen, not paused, and below its borrow cap.

From the remaining borrowable reserves, select the one that:

  • Best matches the desired token with a sufficient borrowable amount. This already accounts for the combined collateral factors (LTV ratios) of the position’s collateral
  • Offers the lowest borrow APY: the effective borrow APY combines the reserve’s borrow APY with the Risk Premium tied to the position’s collateral
Reserve
const reserve: Reserve = {
  id: "SGVsbG8h",
  onChainId: "42",
  canBorrow: true,
  chain: {
    chainId: 1,
    name: "Ethereum",
  },
  spoke: {
    address: "0x123…",
    // …
  },
  asset: {
    underlying: {
      address: "0xa0b86a33e6e2ad05ad6c9ac3b6e5e5f6e7b6c1b2", // USDC
    },
    // …
  },
  userState: {
    borrowApy: {
      normalized: BigDecimal(3.73), // 3.73% APY
      // …
    },
    borrowable: {
      amount: {
        value: BigDecimal(10000.0), // 10,000 USDC
      },
      // …
    },
    // …
  },
};

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

Preview Borrow with AaveKit

Preview the impact of a borrow 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 borrow operation on the user’s position.

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

function BorrowPreview({ request }: { request: BorrowRequest }) {
  const { data, error, loading } = usePreview({
    action: {
      borrow: 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>
  );
}
  • React: The BorrowRequest can be as follows.
  • GraphQL: The PreviewRequest can be as follows.
Borrow ERC-20
const request: BorrowRequest = {
  sender: evmAddress("0x789…"), // User's address
  reserve: reserve.id,
  amount: {
    erc20: {
      value: bigDecimal(1000), // 1000 USDC
    },
  },
};

The PreviewUserPosition shows the impact of the borrow 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)
riskPremium.[current → after]: PercentNumberLower is better
netApy.[current → after]: PercentNumberHigher is better
netCollateral.[current → after]: ExchangeAmountHigher is better
netBalance.[current → after]: ExchangeAmountUpdated balance
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

Borrowing assets updates the Dynamic Config and User Risk Premium of the 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: {
    borrow: request,
  },
  currency: Currency.Eur,
});

Preview Borrow 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 borrow:

Amount to Borrow
uint256 amountToBorrow = 42e6; // 42 USDC

Follow the steps below to preview the impact of a borrow 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 Debt Value

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

New Debt Value
address oracleAddress = spoke.ORACLE();
ISpoke.Reserve memory reserve = spoke.getReserve(reserveId);
uint256 assetPrice = IAaveOracle(oracleAddress).getReservePrice(reserveId);
uint256 borrowValue = amountToBorrow * assetPrice * 10 ** (18 - reserve.decimals);
uint256 newTotalDebtValue = currentData.totalDebtValueRay / 1e27 + borrowValue;

3. Compute New Health Factor

Finally, calculate the new health factor by incorporating the new debt value.

Compute New Health Factor
uint256 newHealthFactor = (currentData.totalCollateralValue * currentData.avgCollateralFactor) / newTotalDebtValue; // WAD, 1e18 = 1.00

Complete Preview Example

Here’s a complete Solidity example that calculates the health factor before a borrow 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";

function previewBorrowHealthFactor(
  address spokeAddress,
  uint256 reserveId,
  uint256 amountToBorrow,
  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 Debt Value
  ISpoke.Reserve memory reserve = spoke.getReserve(reserveId);
  uint256 assetPrice = oracle.getReservePrice(reserveId);
  uint256 borrowValue = amountToBorrow * assetPrice * 10 ** (18 - reserve.decimals);
  uint256 newTotalDebtValue = currentData.totalDebtValueRay / 1e27 + borrowValue;

  // Step 3: Compute New Health Factor
  return (currentData.totalCollateralValue * currentData.avgCollateralFactor) / newTotalDebtValue; // WAD, 1e18 = 1.00
}

Step-by-Step with AaveKit

Borrow assets from the selected reserve.

Borrowing increases risk and updates the Dynamic Config, which updates the position’s User Risk Premium. See User Position Conditions.

To borrow assets from an Aave reserve 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 Borrow Flow

Then, in React, use the useBorrow hook to prepare the borrow operation.

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

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

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

3. Execute the Borrow Operation

Then, execute the desired borrow operation.

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

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

  // …
};

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

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

4. Process the 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 { borrow } from "@aave/client/actions";
import { sendWith } from "@aave/client/viem";

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

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

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

TransactionRequest
{
  "data": {
    "borrow": {
      "__typename": "TransactionRequest",
      "to": "0x94e7a5dcbe816e498b89ab752661904e2f56c485",
      "from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
      "data": "0xd6bda0c0…",
      "value": "0",
      "chainId": 1,
      "operations": ["SPOKE_BORROW"]
    }
  }
}

If the response is a PreContractActionRequired, you must first send the transaction request. Once it succeeds, you can then send the originalTransaction.

PreContractActionRequired
{
  "data": {
    "borrow": {
      "__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": "0xd6bda0c0…",
        "value": "0",
        "chainId": 1,
        "operations": ["SPOKE_BORROW"]
      }
    }
  }
}

5. Handle the Result

Finally, in React and TypeScript, handle the result.

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

  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("Borrow successful with hash:", result.value.txHash);
};

Step-by-Step with Solidity

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

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

uint256 amountToBorrow = 42e6; // 42 USDC

Complete the borrow 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. Borrow Assets

Then, call the borrow function on the Spoke contract, specifying the reserve ID, amount to borrow, and the onBehalfOf address (the position owner). The borrowed tokens are sent to the caller (msg.sender).

Borrow
spoke.borrow(
  reserveId,
  amountToBorrow,
  msg.sender
);

Complete Borrow Example

Here’s a complete Solidity example that executes the borrow operation:

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

function borrowFromReserve(
  address spokeAddress,
  uint256 reserveId,
  uint256 amountToBorrow,
  address onBehalfOf
) external {
  // Borrow assets from reserve
  ISpoke(spokeAddress).borrow(reserveId, amountToBorrow, 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 PreviewAction you pass to the usePreview hook.

For example:

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

const action: PreviewAction = {
  borrow: {
    sender: evmAddress("0x789…"), // User's address
    reserve: reserve.id,
    amount: {
      erc20: {
        value: bigDecimal(1000), // 1000 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 borrow the asset as the chain’s native token using the Native Token Gateway.

Use the 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",
  canBorrow: true,
  canSupply: true,
  canUseAsCollateral: true,
  asset: {
    underlying: {
      address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
      info: {
        name: "Wrapped Ether",
        symbol: "WETH",
        decimals: 18,
        // …
      },
      isWrappedNativeToken: true,
      // …
    },
    // …
  },
  settings: {
    borrowCap: {
      amount: { value: BigDecimal(1000000000.0) }, // 1B WETH
      // …
    },
    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.

Borrow Native
const execute = async () => {
  const result = await borrow({
    sender: evmAddress(wallet.account.address), // User's address
    reserve: reserve.id,
    amount: {
      native: bigDecimal(1), // 1 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 borrow native tokens (e.g., ETH) from 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. Borrow Native Tokens

Then, call borrowNative on the gateway to borrow and unwrap the tokens.

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

INativeTokenGateway gateway = INativeTokenGateway(nativeGateway);
gateway.borrowNative(spokeAddress, reserveId, amountToBorrow);

The gateway calls spoke.borrow on your behalf, unwraps the token (WETH → ETH), and sends the native token to the caller (msg.sender).

Complete Native Token Example

Here’s a complete Solidity example for borrowing native tokens. 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 borrow for a user’s wallet, the user calls setUserPositionManager and the gateway directly.

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

function borrowNativeToken(
  address spokeAddress,
  address nativeGateway,
  uint256 reserveId,
  uint256 amountToBorrow
) 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: Borrow native tokens via gateway
  INativeTokenGateway(nativeGateway).borrowNative(spokeAddress, reserveId, amountToBorrow);
}

receive() external payable {}