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:
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 user field: the Collateral Factor applies to reserves linked by User when used as collateral, while the Max Liquidation Bonus and Liquidation Fee apply to reserves linked by User.
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",
},
// …
},
// …
};
const supplyItem: UserSupplyItem = {
__typename: "UserSupplyItem",
id: "aGVsbG8=",
isCollateral: true,
reserve: {
__typename: "Reserve",
id: "SGVsbG8h",
userState: {
__typename: "ReserveUserState",
collateralFactor: {
normalized: BigDecimal(80), // 80%
// …
},
// …
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 risk field in the User Position response.
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 risk 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.
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:
currentrepresents the current contribution of this collateral asset to the overall User Risk Premium.Risk Premium Weight latestrepresents the contribution if the User Risk Premium were to be updated.Risk Premium Weight collateralrepresents this asset’s share of the position’s total collateral value.
As mentioned above, the breakdown is available in the risk field of user positions, or you can use the standalone query explained below.
Use the use hook in React, the user action in TypeScript, or the user 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>
);
}
import {
type UserRiskPremiumBreakdownRequest,
useUserRiskPremiumBreakdown,
} from "@aave/react";
function UserRiskPremiumBreakdown({
request,
}: {
request: UserRiskPremiumBreakdownRequest;
}) {
const { data } = useUserRiskPremiumBreakdown({
...request,
suspense: true,
});
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>
);
}
import { userRiskPremiumBreakdown } from "@aave/client/actions";
import { evmAddress, userPositionId } from "@aave/client";
import { client } from "./client";
const result = await userRiskPremiumBreakdown(client, {
user: evmAddress("0x742d35cc…"),
query: {
userPositionId: userPositionId(params.id), // UserPositionId - typically from route params
},
});
if (result.isOk()) {
console.log(result.value); // UserRiskPremiumBreakdownItem[]
} else {
console.error(result.error);
}
import { userRiskPremiumBreakdown } from "@aave/client/actions";
import { evmAddress } from "@aave/client";
import { client } from "./client";
const result = await userRiskPremiumBreakdown(client, {
user: evmAddress("0x742d35cc…"),
query: {
userSpoke: {
user: evmAddress("0x742d35cc…"),
spoke: spoke.id, // spoke: Spoke
},
},
});
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
query ($request: UserRiskPremiumBreakdownRequest!) {
userRiskPremiumBreakdown(request: $request) {
token {
...Erc20Token
}
currentRiskPremiumWeight {
...PercentNumber
}
latestRiskPremiumWeight {
...PercentNumber
}
collateral {
...PercentNumber
}
}
}
fragment Erc20Token on Erc20Token {
address
info {
...TokenInfo
}
chain {
...Chain
}
}
fragment TokenInfo on TokenInfo {
id
name
symbol
icon
decimals
}
fragment Chain on Chain {
name
chainId
icon
}
fragment PercentNumber on PercentNumber {
onChainValue
decimals
value
normalized
}
- 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
},
};
const request: UserRiskPremiumBreakdownRequest = {
user: evmAddress("0x742d35cc…"),
query: {
userSpoke: {
user: evmAddress("0x742d35cc…"),
spoke: spoke.id, // spoke: Spoke
},
},
};
{
"request": {
"user": "0x742d35cc…",
"query": {
"userPositionId": "aGVsbG8"
}
}
}
{
"request": {
"user": "0x742d35cc…",
"query": {
"userSpoke": {
"user": "0x742d35cc…",
"spoke": "SGVsbG8h"
}
}
}
}
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:
- Identify eligible positions
- Preview the impact of the update (optional)
- Update the position conditions
Identify Eligible Positions
You can identify positions that qualify for updates by checking if the risk is lower than risk (indicating a better risk premium is available) or the can field (true when dynamic config can be updated) in the user’s positions.
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 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 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>
);
}
import {
type UpdateUserPositionConditionsRequest,
usePreview,
} from "@aave/react";
function UserPositionConditionsPreview({
request,
}: {
request: UpdateUserPositionConditionsRequest;
}) {
const { data, error, loading } = usePreview({
action: {
updateUserPositionConditions: 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>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: UpdateUserPositionConditionsRequest) => {
const result = await preview({
action: {
updateUserPositionConditions: request,
},
});
if (result.isOk()) {
console.log(result.value); // PreviewUserPosition
} else {
console.error(result.error);
}
};
import { preview } from "@aave/client/actions";
import { UserPositionConditionsUpdate } from "@aave/client";
import { client } from "./client";
const result = await preview(client, {
action: {
updateUserPositionConditions: {
userPositionId: userPosition.id,
update: UserPositionConditionsUpdate.JustRiskPremium,
},
},
});
if (result.isOk()) {
console.log(result.value); // PreviewUserPosition
} else {
console.error(result.error);
}
const result = await preview(client, {
action: {
updateUserPositionConditions: {
userPositionId: userPosition.id,
update: UserPositionConditionsUpdate.AllDynamicConfig,
},
},
});
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
}
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
Updateobjects.User Position Conditions Request - GraphQL: You can preview different update operations using the
updatefield.
const request: UpdateUserPositionConditionsRequest = {
userPositionId: userPosition.id,
update: UserPositionConditionsUpdate.JustRiskPremium,
};
const request: UpdateUserPositionConditionsRequest = {
userPositionId: userPosition.id,
update: UserPositionConditionsUpdate.AllDynamicConfig,
};
{
"request": {
"action": {
"updateUserPositionConditions": {
"userPositionId": "aGVsbG8",
"update": "JUST_RISK_PREMIUM"
}
}
}
}
{
"request": {
"action": {
"updateUserPositionConditions": {
"userPositionId": "aGVsbG8",
"update": "ALL_DYNAMIC_CONFIG"
}
}
}
}
Updating Dynamic Config also updates the User Risk Premium.
The Preview 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.
| Field | Impact |
|---|---|
health | Higher is better ( null if not applicable) |
risk | Lower is better |
net | Higher is better |
projected | Projected earnings |
max | Maximum borrowing power |
remaining | Remaining borrowing power |
other | Dynamic config changes |
The other field is an array of objects describing dynamic configuration changes per reserve the user has a position in.
Collateral– Collateral factor changeFactor Variation Liquidation– Liquidation fee changeFee Variation Max– Maximum liquidation bonus changeLiquidation Bonus Variation
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 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 Update Flow
Then, in React, use the use hook to prepare the transaction request.
import { useUpdateUserPositionConditions } from "@aave/react";
const [updateConditions, { loading, error }] = useUpdateUserPositionConditions(
(transaction) => sendTransaction(transaction),
);
import { useUpdateUserPositionConditions } from "@aave/react";
const [status, setStatus] = useState("");
const [updateConditions, { loading, error }] = useUpdateUserPositionConditions(
(transaction) => {
setStatus("Please sign the transaction in your wallet");
return sendTransaction(transaction).andTee(() =>
setStatus("Updating position conditions…"),
);
},
);
import { useUpdateUserPositionConditions } from "@aave/react";
const [updateConditions, { loading, error }] = useUpdateUserPositionConditions(
(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, execute the transaction.
- TypeScript: Use the
updateaction to prepare the transaction request.User Position Conditions - GraphQL: Use the
updatequery to prepare the transaction request.User Position Conditions
import { UserPositionConditionsUpdate, type UserPosition } from "@aave/react";
const execute = async (userPosition: UserPosition) => {
const result = await updateConditions({
userPositionId: userPosition.id,
update: UserPositionConditionsUpdate.JustRiskPremium,
});
};
import { UserPositionConditionsUpdate, type UserPosition } from "@aave/react";
const execute = async (userPosition: UserPosition) => {
const result = await updateConditions({
userPositionId: userPosition.id,
update: UserPositionConditionsUpdate.AllDynamicConfig,
});
};
import { updateUserPositionConditions } from "@aave/client/actions";
import { UserPositionConditionsUpdate } from "@aave/client";
// const userPosition: UserPosition = …
const result = await updateUserPositionConditions(client, {
userPositionId: userPosition.id,
update: UserPositionConditionsUpdate.JustRiskPremium,
});
import { updateUserPositionConditions } from "@aave/client/actions";
import { UserPositionConditionsUpdate } from "@aave/client";
const result = await updateUserPositionConditions(client, {
userPositionId: userPosition.id,
update: UserPositionConditionsUpdate.AllDynamicConfig,
});
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
query ($request: UpdateUserPositionConditionsRequest!) {
updateUserPositionConditions(request: $request) {
__typename
... on TransactionRequest {
...TransactionRequest
}
}
}
fragment TransactionRequest on TransactionRequest {
to
from
data
value
chainId
operations
}
In GraphQL, use the following variables:
{
"request": {
"userPositionId": "aGVsbG8",
"update": "JUST_RISK_PREMIUM"
}
}
{
"request": {
"userPositionId": "aGVsbG8",
"update": "ALL_DYNAMIC_CONFIG"
}
}
4. Process the Transaction Request
In TypeScript, use send for the wallet library of your choice to send the transaction, and await indexing with client..
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);
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": {
"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.
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,
);
};
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(
"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 spoke:
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. Update User Position Conditions
Then, call update to update only the User Risk Premium, or update to update all Dynamic Config and the User Risk Premium.
spoke.updateUserRiskPremium(msg.sender);
spoke.updateUserDynamicConfig(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, UserPositionConditionsUpdate } from "@aave/react";
const action: PreviewAction = {
updateUserPositionConditions: {
userPositionId: userPosition.id, // from UserPosition
update: UserPositionConditionsUpdate.AllDynamicConfig,
},
};
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>
);
}