Skip to Content
docs

Fetch Open Positions on Aave V4

Each user position captures a user’s activity within a Spoke, simplifying health and performance tracking.

User Positions

User Position Data Structure

User position data describes a user’s lending and borrowing activity on a specific spoke:

  • Identification: id, user, spoke, creation timestamp
  • Position balances: supplied amounts, collateral amounts, debt amounts, net balances
  • Yield information: supply APY, borrow APY, net APY, net interests
  • Risk metrics: health factor, user risk premium breakdown, liquidation price, maximum and remaining borrowing power
  • Performance data: net balance percentage changes over time

The following TypeScript interfaces illustrate the core UserPosition type:

interface UserPosition {
  __typename: "UserPosition";
  id: UserPositionId;
  user: EvmAddress;
  createdAt: Date;
  spoke: Spoke;
  totalSupplied: ExchangeAmountWithChange;
  totalCollateral: ExchangeAmountWithChange;
  totalDebt: ExchangeAmountWithChange;
  netBalance: ExchangeAmountWithChange;
  netCollateral: ExchangeAmountWithChange;
  netApy: PercentNumber;
  netSupplyApy: PercentNumberWithChange;
  netBorrowApy: PercentNumberWithChange;
  netAccruedInterest: ExchangeAmount;
  healthFactor: HealthFactorWithChange;
  riskPremium: UserPositionRiskPremium | null;
  liquidationPrice: ExchangeAmount | null;
  maxBorrowingPower: ExchangeAmount;
  remainingBorrowingPower: ExchangeAmount;
  canUpdateDynamicConfig: boolean;
  netBalancePercentChange: PercentNumber;
  averageCollateralFactor: PercentNumber;
}

See the Spoke Data Structure for more details.

Listing User Positions

Fetch all user positions with filtering and ordering options.

Use the useUserPositions hook in React, the userPositions action in TypeScript, or the userPositions query in GraphQL to fetch positions for a given user address. In TypeScript, the examples also show how to filter by chains or tokens.

import { type UserPositionsRequest, useUserPositions } from "@aave/react";

function UserPositionsList({ request }: { request: UserPositionsRequest }) {
  const { data, loading, error } = useUserPositions(request);

  if (loading) return <div>Loading…</div>;

  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      {data.map((position) => (
        <div key={position.id}>
          <h3>Position on {position.spoke.name}</h3>
          <p>Chain: {position.spoke.chain.name}</p>
        </div>
      ))}
    </div>
  );
}

In React and GraphQL, you can filter positions by the following criteria:

import { evmAddress, chainId } from "@aave/react";

const request: UserPositionsRequest = {
  user: evmAddress("0x456…"),
  filter: {
    chainIds: [chainId(1)],
  },
};

And you can sort positions as follows:

import { evmAddress, OrderDirection, type UserPositionsRequest } from "@aave/react";

const request: UserPositionsRequest = {
  user: evmAddress("0x456…"),
  filter: {
    // your filter criteria
  },
  orderBy: { balance: OrderDirection.Asc },
};

Finally, you can specify a different time window or currency for the data.

import { evmAddress, TimeWindow } from "@aave/react";

const { data, error, loading } = useUserPositions({
  user: evmAddress("0x456…"),
  filter: {
    // your filter criteria
  },
  timeWindow: TimeWindow.LastWeek,
});

In Solidity, to fetch user positions onchain, iterate through each reserve listed on the spoke and fetch the record for the target user.

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

function getUserPositions(
  address spokeAddress,
  address user
) external view returns (ISpoke.UserPosition[] memory) {
  ISpoke spoke = ISpoke(spokeAddress);
  uint256 reserveCount = spoke.getReserveCount();

  ISpoke.UserPosition[] memory positions = new ISpoke.UserPosition[](reserveCount);

  for (uint256 reserveId = 0; reserveId < reserveCount; reserveId++) {
    positions[reserveId] = spoke.getUserPosition(reserveId, user);
  }

  return positions;
}

In Solidity, you can also pull aggregate risk metrics directly:

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

function getUserAccountSummary(
  address spokeAddress,
  address user
) external view returns (ISpoke.UserAccountData memory) {
  ISpoke spoke = ISpoke(spokeAddress);
  return spoke.getUserAccountData(user);
}

Fetching a Single Position

Fetch a specific user position.

Use the useUserPosition hook in React, the userPosition action in TypeScript, or the userPosition query in GraphQL to fetch a specific user position. In TypeScript, the examples also show how to query by user and spoke or by position ID, and how to specify a different time window or currency.

import { type UserPositionRequest, useUserPosition } from "@aave/react";

function UserPositionDetail({ request }: { request: UserPositionRequest }) {
  const { data, loading, error } = useUserPosition(request);

  if (loading) return <div>Loading…</div>;

  if (error) return <div>Error: {error.message}</div>;

  if (!data) return <div>Position not found</div>;

  return (
    <div>
      <h2>Position {data.id}</h2>
      <p>Net Balance: {data.netBalance.current.value.toDisplayString(2)}</p>
      <p>Health Factor: {data.healthFactor.current?.toFixed(2) ?? "N/A"}</p>
    </div>
  );
}

Query by user and spoke or by position ID.

  • React: See below for examples of UserPositionRequest objects.
  • GraphQL: Use the request field; the examples also show how to specify a different currency or time window.
import { userPositionId } from "@aave/react";

const request: UserPositionRequest = {
  id: userPositionId(params.id), // UserPositionId - typically from URL params
};

In React, you can also specify a different time window or currency for the data.

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

const { data, error, loading } = useUserPosition({
  // your query criteria
  timeWindow: TimeWindow.LastWeek,
});

In Solidity, positions are identified by the spoke’s reserveId and the user address onchain. To inspect a single position, call getUserPosition on the spoke with the reserve ID and user address.

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

function getUserPosition(
  address spokeAddress,
  uint256 reserveId,
  address user
) external view returns (
  ISpoke.UserPosition memory position,
  bool isBorrowing,
  bool usingAsCollateral
) {
  ISpoke spoke = ISpoke(spokeAddress);

  position = spoke.getUserPosition(reserveId, user);
  (usingAsCollateral, isBorrowing) = spoke.getUserReserveStatus(reserveId, user);

  return (position, isBorrowing, usingAsCollateral);
}

User Supplies

User Supply Data Structure

User supply data provides detailed information about individual user supplies within a spoke, including:

  • Reserve details: token information, supply APY, lending limits
  • Position amounts: current balance, principal, earned interest, and withdrawable amounts
  • Collateral status: whether the supply is used as collateral
  • Performance metrics: earned interest over time

The following TypeScript interfaces illustrate the UserSupplyItem type:

UserSupplyItem
interface UserSupplyItem {
  __typename: "UserSupplyItem";
  id: UserSupplyItemId;
  reserve: Reserve;
  principal: Erc20Amount;
  balance: Erc20Amount;
  withdrawable: Erc20Amount;
  interest: Erc20Amount;
  isCollateral: boolean;
}

See the Reserve Data Structure for more details.

Fetching User Supplies

Fetch user supplies within a specific spoke.

Use the useUserSupplies hook in React, the userSupplies action in TypeScript, or the userSupplies query in GraphQL to fetch user supplies for a user. In TypeScript, the examples also show the supported fetch criteria.

import { type UserSuppliesRequest, useUserSupplies } from "@aave/react";

function UserSuppliesList({ request }: { request: UserSuppliesRequest }) {
  const { data, loading, error } = useUserSupplies(request);

  if (loading) return <div>Loading…</div>;

  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      {data.map((supply) => (
        <div key={supply.id}>
          <h3>{supply.reserve.asset.underlying.info.name}</h3>
          <p>Amount: {supply.withdrawable.amount.value.toDisplayString(2)}</p>
        </div>
      ))}
    </div>
  );
}

See below for examples of fetch criteria.

  • React: These are UserSuppliesRequest objects.
  • GraphQL: Use the query field to specify the fetch criteria.
const request: UserSuppliesRequest = {
  query: {
    userPositionId: position.id, // position: UserPosition
  },
};

Sort user supplies by asset name, amount, or supply APY.

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

const request: UserSuppliesRequest = {
  query: {
    // your query criteria
  },
  orderBy: { assetName: OrderDirection.Asc },
};

Include zero-value entries for all other supply reserves available on the same spoke, typically ordered by descending amount.

Include Zero Balances
import { OrderDirection } from "@aave/react";

const request: UserSuppliesRequest = {
  query: {
    // your query criteria
  },
  includeZeroBalances: true,
  orderBy: { amount: OrderDirection.Desc },
};

Finally, you can specify a different currency for displaying fiat amounts. In React, you can also specify a different time window.

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

const { data, error, loading } = useUserSupplies({
  query: {
    // your query criteria
  },
  timeWindow: TimeWindow.LastWeek,
});

In Solidity, supply balances are exposed per reserve on the spoke. Loop through all reserves and inspect suppliedShares for the target user, converting them to asset amounts using the hub’s exchange rate. Here’s a complete example:

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

struct UserSupply {
  uint256 reserveId;
  address underlying;
  uint256 suppliedAmount;
  bool isCollateral;
}

function getUserSupplies(
  address spokeAddress,
  address user
) external view returns (UserSupply[] memory) {
  ISpoke spoke = ISpoke(spokeAddress);
  uint256 reserveCount = spoke.getReserveCount();

  UserSupply[] memory supplies = new UserSupply[](reserveCount);
  uint256 supplyCount = 0;

  for (uint256 reserveId = 0; reserveId < reserveCount; reserveId++) {
    ISpoke.UserPosition memory position = spoke.getUserPosition(reserveId, user);

    if (position.suppliedShares > 0) {
      ISpoke.Reserve memory reserve = spoke.getReserve(reserveId);
      (bool isCollateral, ) = spoke.getUserReserveStatus(reserveId, user);

      supplies[supplyCount] = UserSupply({
        reserveId: reserveId,
        underlying: reserve.underlying,
        suppliedAmount: reserve.hub.previewRemoveByShares(reserve.assetId, position.suppliedShares),
        isCollateral: isCollateral
      });

      supplyCount++;
    }
  }

  UserSupply[] memory result = new UserSupply[](supplyCount);
  for (uint256 i = 0; i < supplyCount; i++) {
    result[i] = supplies[i];
  }

  return result;
}

User positions store balances as shares. Use the hub’s previewRemoveByShares to convert shares to the underlying asset amount, accounting for accrued interest.

User Borrows

User Borrow Data Structure

User borrow data provides detailed information about individual borrow positions within a spoke, including:

  • Reserve details: token information, borrow APY, borrowing limits
  • Position amounts: principal and current debt, with token details
  • Performance metrics: accrued interest

The following TypeScript interfaces illustrate the UserBorrowItem type:

UserBorrowItem
interface UserBorrowItem {
  __typename: "UserBorrowItem";
  id: UserBorrowItemId;
  reserve: Reserve;
  principal: Erc20Amount;
  debt: Erc20Amount;
  interest: Erc20Amount;
}

See the Reserve Data Structure for more details.

Fetching User Borrows

Fetch user borrows within a specific spoke.

Use the useUserBorrows hook in React, the userBorrows action in TypeScript, or the userBorrows query in GraphQL to fetch borrow positions for a user. In TypeScript, the examples also show the supported fetch criteria.

import { type UserBorrowsRequest, useUserBorrows } from "@aave/react";

function UserBorrowsList({ request }: { request: UserBorrowsRequest }) {
  const { data, loading, error } = useUserBorrows(request);

  if (loading) return <div>Loading…</div>;

  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      {data.map((borrow) => (
        <div key={borrow.id}>
          <h3>{borrow.reserve.asset.underlying.info.name}</h3>
          <p>Amount: {borrow.debt.amount.value.toDisplayString(2)}</p>
        </div>
      ))}
    </div>
  );
}

See below for examples of fetch criteria.

  • React: These are UserBorrowsRequest objects.
  • GraphQL: Use the query field to specify the fetch criteria.
const request: UserBorrowsRequest = {
  query: {
    userPositionId: position.id, // position: UserPosition
  },
};

Sort user borrows by asset name, amount, or borrow APY.

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

const request: UserBorrowsRequest = {
  query: {
    // your query criteria
  },
  orderBy: { assetName: OrderDirection.Asc },
};

Include zero-value entries for all other borrow reserves available on the same spoke, typically ordered by descending amount.

Include Zero Balances
import { OrderDirection } from "@aave/react";

const request: UserBorrowsRequest = {
  query: {
    // your query criteria
  },
  includeZeroBalances: true,
  orderBy: { amount: OrderDirection.Desc },
};

Finally, you can specify a different currency for displaying fiat amounts. In React, you can also specify a different time window.

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

const { data, error, loading } = useUserBorrows({
  query: {
    // your query criteria
  },
  timeWindow: TimeWindow.LastWeek,
});

In Solidity, borrow balances are exposed per reserve on the spoke. Loop through all reserves and read each borrowed reserve’s total debt for the target user with getUserTotalDebt. Here’s a complete example:

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

struct UserBorrow {
  uint256 reserveId;
  address underlying;
  uint256 borrowedAmount;
}

function getUserBorrows(
  address spokeAddress,
  address user
) external view returns (UserBorrow[] memory) {
  ISpoke spoke = ISpoke(spokeAddress);
  uint256 reserveCount = spoke.getReserveCount();

  UserBorrow[] memory borrows = new UserBorrow[](reserveCount);
  uint256 borrowCount = 0;

  for (uint256 reserveId = 0; reserveId < reserveCount; reserveId++) {
    ISpoke.UserPosition memory position = spoke.getUserPosition(reserveId, user);

    if (position.drawnShares > 0) {
      borrows[borrowCount] = UserBorrow({
        reserveId: reserveId,
        underlying: spoke.getReserve(reserveId).underlying,
        borrowedAmount: spoke.getUserTotalDebt(reserveId, user)
      });

      borrowCount++;
    }
  }

  UserBorrow[] memory result = new UserBorrow[](borrowCount);
  for (uint256 i = 0; i < borrowCount; i++) {
    result[i] = borrows[i];
  }

  return result;
}

Use the Spoke’s getUserTotalDebt to get the drawn plus premium debt in asset units, including accrued interest.

User Summary

User Summary Data Structure

User summary data describes a user’s aggregated activity across all positions:

  • Position count: total number of positions held by the user
  • Portfolio totals: supplied amounts, collateral amounts, debt amounts, net balances
  • Yield information: calculated net APY across all positions
  • Risk metrics: lowest health factor across all positions
  • Performance data: net accrued interest across all positions

The following TypeScript interfaces illustrate the UserSummary type:

interface UserSummary {
  __typename: "UserSummary";
  totalPositions: number;
  netBalance: ExchangeAmountWithChange;
  totalCollateral: ExchangeAmount;
  totalSupplied: ExchangeAmount;
  totalDebt: ExchangeAmount;
  netApy: PercentNumber;
  netAccruedInterest: ExchangeAmount;
  lowestHealthFactor: BigDecimal | null;
}

Fetching User Summary

Fetch a financial summary for a user across all their positions.

Use the useUserSummary hook in React, the userSummary action in TypeScript, or the userSummary query in GraphQL to fetch the financial summary for a user. In TypeScript, the examples also show how to filter the summary.

import { type UserSummaryRequest, useUserSummary } from "@aave/react";

function UserSummaryDisplay({ request }: { request: UserSummaryRequest }) {
  const { data, loading, error } = useUserSummary(request);

  if (loading) return <div>Loading…</div>;

  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h2>Financial Summary</h2>
      <p>Total Positions: {data.totalPositions}</p>
      <p>
        Net Balance: {data.netBalance.current.symbol}
        {data.netBalance.current.value.toDisplayString(2)}
      </p>
      <p>
        Total Supplied: {data.totalSupplied.symbol}
        {data.totalSupplied.value.toDisplayString(2)}
      </p>
      <p>
        Total Debt: {data.totalDebt.symbol}
        {data.totalDebt.value.toDisplayString(2)}
      </p>
      <p>Net APY: {data.netApy.normalized.toFixed(2)}%</p>
    </div>
  );
}
  • React: See below for examples of UserSummaryRequest objects.
  • GraphQL: Use the user field to specify the user.
import { evmAddress } from "@aave/react";

const request: UserSummaryRequest = {
  user: evmAddress("0x456…"),
};

In GraphQL, filter the summary by specific spokes or chains using the filter field.

{
  "request": {
    "user": "0x456…",
    "filter": {
      "spokeId": "SGVsbG8h"
    }
  }
}

Finally, you can specify a different time window or currency for the data.

import { evmAddress, TimeWindow } from "@aave/react";

const { data, error, loading } = useUserSummary({
  user: evmAddress("0x456…"),
  timeWindow: TimeWindow.LastWeek,
});

User Summary History

User Summary History Data Structure

User summary history data provides detailed information about a user’s activity changes over time, including:

  • Historical snapshots: portfolio state at different points in time
  • Portfolio metrics: net balance, borrows, supplies at each snapshot
  • Risk metrics: health factor changes over time

The following TypeScript interfaces illustrate the UserSummaryHistoryItem type:

UserSummaryHistoryItem
interface UserSummaryHistoryItem {
  __typename: "UserSummaryHistoryItem";
  netBalance: ExchangeAmount;
  borrows: ExchangeAmount;
  supplies: ExchangeAmount;
  healthFactor: BigDecimal | null;
  date: Date;
}

Fetching User Summary History

Fetch historical financial data for a user over a specified time window.

Use the useUserSummaryHistory hook in React, the userSummaryHistory action in TypeScript, or the userSummaryHistory query in GraphQL to fetch historical financial data for a user. In TypeScript, the examples also show how to set the time window.

import {
  type UserSummaryHistoryRequest,
  useUserSummaryHistory,
} from "@aave/react";

function UserSummaryHistory({
  request,
}: {
  request: UserSummaryHistoryRequest;
}) {
  const { data, loading, error } = useUserSummaryHistory(request);

  if (loading) return <div>Loading…</div>;

  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h2>Financial History</h2>
      {data.map((item) => (
        <div key={item.date.toString()}>
          <p>Date: {item.date.toLocaleDateString()}</p>
          <p>
            Net Balance: {item.netBalance.symbol}
            {item.netBalance.value.toDisplayString(2)}
          </p>
          <p>
            Supplies: {item.supplies.symbol}
            {item.supplies.value.toDisplayString(2)}
          </p>
          <p>
            Borrows: {item.borrows.symbol}
            {item.borrows.value.toDisplayString(2)}
          </p>
        </div>
      ))}
    </div>
  );
}
  • React: See below for examples of UserSummaryHistoryRequest objects.
  • GraphQL: Use the user and window fields to specify the user and time period.
import { evmAddress, TimeWindow } from "@aave/react";

const request: UserSummaryHistoryRequest = {
  user: evmAddress("0x456…"),
  window: TimeWindow.LastDay,
};

Filter the history by specific spokes or chains. In GraphQL, use the filter field.

import { evmAddress, TimeWindow } from "@aave/react";

const request: UserSummaryHistoryRequest = {
  user: evmAddress("0x456…"),
  window: TimeWindow.LastWeek,
  filter: {
    spokeId: spoke.id,
  },
};

Finally, you can specify a different currency for displaying fiat amounts.

Custom Currency
import { Currency, evmAddress, TimeWindow } from "@aave/react";

const { data, error, loading } = useUserSummaryHistory({
  user: evmAddress("0x456…"),
  window: TimeWindow.LastWeek,
  currency: Currency.Eur,
});