Skip to Content
docs

Aave V4 Position Conditions

User positions track two risk conditions: Dynamic Risk Configuration and User Risk Premium.

Dynamic Risk Configuration

Dynamic Risk Configuration, or Dynamic Config for short, snapshots the per-reserve collateral parameters (Collateral Factor, Max Liquidation Bonus, and Liquidation Fee) active when the position first became risk-bearing, i.e. when the user initially borrowed against collateral.

It updates when the user performs risk-increasing actions: borrowing, withdrawing collateral, or disabling collateral (updates all reserves' snapshots), or enabling collateral (updates only that reserve’s snapshot). It can also be updated manually, and always refreshes the User Risk Premium.

The Dynamic Config values used on first borrow or after an update (implicit or explicit) are exposed on the Reserve object under the settings field:

Live Dynamic Config
const reserve: Reserve = {
  __typename: "Reserve",
  id: "SGVsbG8h",
  settings: {
    __typename: "ReserveSettings",
    collateralFactor: {
      normalized: BigDecimal(80), // 80%
      // …
    },
    maxLiquidationBonus: {
      normalized: BigDecimal(10), // 10%
      // …
    },
    // protocol fee
    liquidationFee: {
      normalized: BigDecimal(1), // 1%
      // …
    },
    // …
    latestDynamicConfigKey: "1234567890",
  },
  // …
};

The snapshotted values are exposed under the Reserve’s userState field: the Collateral Factor applies to reserves linked by UserSupplyItem when used as collateral, while the Max Liquidation Bonus and Liquidation Fee apply to reserves linked by UserBorrowItem.

const borrowItem: UserBorrowItem = {
  __typename: "UserBorrowItem",
  id: "aGVsbG8=",
  reserve: {
    __typename: "Reserve",
    id: "SGVsbG8h",
    userState: {
      __typename: "ReserveUserState",
      maxLiquidationBonus: {
        normalized: BigDecimal(10), // 10%
        // …
      },
      // protocol fee
      liquidationFee: {
        normalized: BigDecimal(1), // 1%
        // …
      },
      // …
      dynamicConfigKey: "1234567889",
    },
    // …
  },
  // …
};

User Risk Premium

User Risk Premium reflects the current risk profile of the collateral assets. In practice, it acts as a multiplier: a percentage applied on top of each reserve’s base borrow rate. Lower is better.

Riskier collateral raises the User Risk Premium and the borrow rate. Higher-quality collateral lowers both.

It updates when the user performs actions that affect collateralization (borrowing, withdrawing collateral, enabling or disabling collateral), when the Dynamic Risk Configuration changes, or when explicitly triggered.

You can keep track of the User Risk Premium for a position by checking the riskPremium.current field in the User Position response.

User Risk Premium
const userPosition: UserPosition = {
  __typename: "UserPosition",
  id: "aGVsbG8",
  user: "0x456…",
  riskPremium: {
    current: {
      normalized: BigDecimal(50), // 50%
      // …
    },
    latest: {
      normalized: BigDecimal(50), // 50% - same as current
      // …
    },
    breakdown: [
      // …
    ],
  },
  spoke: {
    id: "SGVsbG8h",
    address: "0x123…",
    chain: {
      chainId: 1,
      name: "Ethereum",
      // …
    },
    // …
  },
  // …
};

In this example, a 50% User Risk Premium on a 5% base borrow rate means the user pays 7.5% (5% × 1.5).

The riskPremium.breakdown field contains the User Risk Premium Breakdown for the user position collaterals.

User Risk Premium Breakdown

The User Risk Premium Breakdown shows each collateral asset in a user position, including what percentage of total collateral it represents and its weight in the overall User Risk Premium.

User Risk Premium Breakdown
const breakdown: UserRiskPremiumBreakdownItem[] = [
  {
    __typename: "UserRiskPremiumBreakdownItem",
    token: {
      __typename: "Erc20Token",
      address: "0x456…",
      info: {
        name: "USD Coin",
        symbol: "USDC",
        decimals: 6,
      },
    },
    currentRiskPremiumWeight: {
      __typename: "PercentNumber",
      normalized: BigDecimal(50), // 50%
      // …
    },
    latestRiskPremiumWeight: {
      __typename: "PercentNumber",
      normalized: BigDecimal(45), // 45%
      // …
    },
    collateral: {
      __typename: "PercentNumber",
      normalized: BigDecimal(30), // 30%
      // …
    },
  },
  // …
];

Where:

  • currentRiskPremiumWeight represents the current contribution of this collateral asset to the overall User Risk Premium.
  • latestRiskPremiumWeight represents the contribution if the User Risk Premium were to be updated.
  • collateral represents this asset’s share of the position’s total collateral value.

As mentioned above, the breakdown is available in the riskPremium.breakdown field of user positions, or you can use the standalone query explained below.

Use the useUserRiskPremiumBreakdown hook in React, the userRiskPremiumBreakdown action in TypeScript, or the userRiskPremiumBreakdown query in GraphQL to fetch the breakdown for a specific user position.

import {
  type UserRiskPremiumBreakdownRequest,
  useUserRiskPremiumBreakdown,
} from "@aave/react";

function UserRiskPremiumBreakdown({
  request,
}: {
  request: UserRiskPremiumBreakdownRequest;
}) {
  const { data, loading, error } = useUserRiskPremiumBreakdown(request);

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

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

  return (
    <div>
      {data.map((item) => (
        <div key={item.token.address}>
          <p>{item.token.info.symbol}</p>
          <p>
            Current Weight:{" "}
            {item.currentRiskPremiumWeight.normalized.toFixed(2)}%
          </p>
          <p>
            Latest Weight: {item.latestRiskPremiumWeight.normalized.toFixed(2)}%
          </p>
          <p>Collateral: {item.collateral.normalized.toFixed(2)}%</p>
        </div>
      ))}
    </div>
  );
}
  • React: You can query by user position ID or by user spoke.
  • GraphQL: Use the following variables:
const request: UserRiskPremiumBreakdownRequest = {
  user: evmAddress("0x742d35cc…"),
  query: {
    userPositionId: userPositionId(params.id), // UserPositionId - typically from URL params
  },
};

Update Conditions

As protocol configurations evolve through governance votes, a user position’s conditions may improve.

Updating user position conditions can be broken down into the following steps:

  1. Identify eligible positions
  2. Preview the impact of the update (optional)
  3. Update the position conditions

Identify Eligible Positions

You can identify positions that qualify for updates by checking if the riskPremium.latest is lower than riskPremium.current (indicating a better risk premium is available) or the canUpdateDynamicConfig field (true when dynamic config can be updated) in the user’s positions.

Eligible User Position
const userPosition: UserPosition = {
  __typename: "UserPosition",
  id: "aGVsbG8",
  user: "0x456…",
  riskPremium: {
    current: {
      normalized: BigDecimal(50), // 50%
      // …
    },
    latest: {
      normalized: BigDecimal(25), // 25% - lower is better
      // …
    },
    breakdown: [
      // …
    ],
  },
  spoke: {
    id: "SGVsbG8h",
    address: "0x123…",
    chain: {
      chainId: 1,
      name: "Ethereum",
      // …
    },
    // …
  },
  canUpdateDynamicConfig: true,
  // …
};

Preview the Impact

Preview the impact of updating the user position conditions 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 updating the user position conditions.

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

function UserPositionConditionsPreview({
  request,
}: {
  request: UpdateUserPositionConditionsRequest;
}) {
  const { data, error, loading } = usePreview({
    action: {
      updateUserPositionConditions: 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>Max Borrowing Power:</h3>
      <p>From: {data.maxBorrowingPower.current.value.toDisplayString(2)}</p>
      <p>To: {data.maxBorrowingPower.after.value.toDisplayString(2)}</p>
    </div>
  );
}
  • React: See below for examples of UpdateUserPositionConditionsRequest objects.
  • GraphQL: You can preview different update operations using the update field.
const request: UpdateUserPositionConditionsRequest = {
  userPositionId: userPosition.id,
  update: UserPositionConditionsUpdate.JustRiskPremium,
};

Updating Dynamic Config also updates the User Risk Premium.

The PreviewUserPosition shows the impact of updating the position conditions 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
projectedEarnings.[current → after]: ExchangeAmountProjected earnings
maxBorrowingPower.[current → after]: ExchangeAmountMaximum borrowing power
remainingBorrowingPower.[current → after]: ExchangeAmountRemaining borrowing power
otherConditions: UserPositionConditionVariation[]Dynamic config changes

The otherConditions field is an array of objects describing dynamic configuration changes per reserve the user has a position in.

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

Step-by-Step with AaveKit

Update the selected user position conditions.

To update user position conditions 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 Update Flow

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

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

const [updateConditions, { loading, error }] = useUpdateUserPositionConditions(
  (transaction) => sendTransaction(transaction),
);

3. Execute the Transaction

Then, execute the transaction.

  • TypeScript: Use the updateUserPositionConditions action to prepare the transaction request.
  • GraphQL: Use the updateUserPositionConditions query to prepare the transaction request.
import { UserPositionConditionsUpdate, type UserPosition } from "@aave/react";

const execute = async (userPosition: UserPosition) => {
  const result = await updateConditions({
    userPositionId: userPosition.id,
    update: UserPositionConditionsUpdate.JustRiskPremium,
  });
};

In GraphQL, use the following variables:

{
  "request": {
    "userPositionId": "aGVsbG8",
    "update": "JUST_RISK_PREMIUM"
  }
}

4. Process the Transaction Request

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

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

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

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

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

TransactionRequest
{
  "data": {
    "updateUserPositionConditions": {
      "__typename": "TransactionRequest",
      "to": "0x94e7a5dcbe816e498b89ab752661904e2f56c485",
      "from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
      "data": "0x91c46d09…",
      "value": "0",
      "chainId": 1,
      "operations": ["SPOKE_UPDATE_USER_RISK_PREMIUM"]
    }
  }
}

5. Handle the Result

Finally, in React and TypeScript, handle the result.

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

  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(
    "Position conditions updated successfully with hash:",
    result.value.txHash,
  );
};

Step-by-Step with Solidity

To update user position conditions onchain, follow these steps after identifying the spokeAddress:

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. Update User Position Conditions

Then, call updateUserRiskPremium to update only the User Risk Premium, or updateUserDynamicConfig to update all Dynamic Config and the User Risk Premium.

spoke.updateUserRiskPremium(msg.sender);

Advanced Usage

Network Fee

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

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

For example:

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

const action: PreviewAction = {
  updateUserPositionConditions: {
    userPositionId: userPosition.id, // from UserPosition
    update: UserPositionConditionsUpdate.AllDynamicConfig,
  },
};

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