Aave V3 Advanced Market Features
Implement advanced Aave features in DeFi applications.
User Transaction History
Track user transaction history across Aave markets with detailed transaction information.
Use the use hook in React, the user action in TypeScript, or the user query in GraphQL to fetch paginated transaction history for a user.
const { data, loading, error } = useUserTransactionHistory({
market: EvmAddress,
user: EvmAddress,
chainId: ChainId,
filter?: UserTransactionType[],
orderBy?: UserTransactionHistoryRequestOrderBy,
pageSize?: PageSize,
cursor?: Cursor,
});
const { data } = useUserTransactionHistory({
market: EvmAddress,
user: EvmAddress,
chainId: ChainId,
filter?: UserTransactionType[],
orderBy?: UserTransactionHistoryRequestOrderBy,
pageSize?: PageSize,
cursor?: Cursor,
suspense: true,
});
Fetch the history ordered by date, or filter it by transaction type.
import {
useUserTransactionHistory,
evmAddress,
chainId,
PageSize,
OrderDirection,
} from "@aave/react";
// …
const { data, loading, error } = useUserTransactionHistory({
market: evmAddress("0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2"),
user: evmAddress("0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"),
chainId: chainId(1),
orderBy: { date: OrderDirection.Desc },
pageSize: PageSize.Fifty,
});
if (loading) {
return <p>Loading transaction history…</p>;
}
if (error) {
return <p>Error: {error.message}</p>;
}
// data.items: UserTransactionItem[]
// data.pageInfo.next: Cursor | null
// data.pageInfo.prev: Cursor | null
import {
useUserTransactionHistory,
evmAddress,
chainId,
PageSize,
OrderDirection,
} from "@aave/react";
// …
const { data, loading, error } = useUserTransactionHistory({
market: evmAddress("0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2"),
user: evmAddress("0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"),
chainId: chainId(1),
filter: ["SUPPLY", "WITHDRAW"],
orderBy: { date: OrderDirection.Desc },
pageSize: PageSize.Ten,
});
import { userTransactionHistory } from "@aave/client/actions";
import { evmAddress, chainId, PageSize, OrderDirection } from "@aave/client";
import { client } from "./client";
const result = await userTransactionHistory(client, {
market: evmAddress("0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2"),
user: evmAddress("0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"),
chainId: chainId(1),
orderBy: { date: OrderDirection.Desc },
pageSize: PageSize.Fifty,
});
if (result.isErr()) {
console.error("User transaction history error:", result.error);
} else {
// result.value.items: UserTransactionItem[]
// result.value.pageInfo.next: Cursor | null
// result.value.pageInfo.prev: Cursor | null
console.log(result.value);
}
import { userTransactionHistory } from "@aave/client/actions";
import { evmAddress, chainId, PageSize, OrderDirection } from "@aave/client";
import { client } from "./client";
const result = await userTransactionHistory(client, {
market: evmAddress("0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2"),
user: evmAddress("0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"),
chainId: chainId(1),
filter: ["SUPPLY", "WITHDRAW"],
orderBy: { date: OrderDirection.Desc },
pageSize: PageSize.Ten,
});
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
query {
userTransactionHistory(
request: {
market: "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2"
user: "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"
chainId: 1
orderBy: { date: DESC }
pageSize: FIFTY
}
) {
items {
... on UserSupplyTransaction {
...UserSupplyTransaction
}
... on UserWithdrawTransaction {
...UserWithdrawTransaction
}
... on UserBorrowTransaction {
...UserBorrowTransaction
}
... on UserRepayTransaction {
...UserRepayTransaction
}
... on UserUsageAsCollateralTransaction {
...UserUsageAsCollateralTransaction
}
... on UserLiquidationCallTransaction {
...UserLiquidationCallTransaction
}
}
pageInfo {
next
prev
}
}
}
query {
userTransactionHistory(
request: {
market: "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2"
user: "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"
chainId: 1
filter: [SUPPLY, WITHDRAW]
orderBy: { date: DESC }
pageSize: TEN
}
) {
items {
__typename
...UserSupplyTransaction
...UserWithdrawTransaction
}
pageInfo {
next
prev
}
}
}
fragment UserSupplyTransaction on UserSupplyTransaction {
blockExplorerUrl
txHash
timestamp
amount {
amount {
value
raw
decimals
}
usd
}
reserve {
underlyingToken {
symbol
name
address
}
}
}
fragment UserWithdrawTransaction on UserWithdrawTransaction {
blockExplorerUrl
txHash
timestamp
amount {
amount {
value
raw
decimals
}
usd
}
reserve {
underlyingToken {
symbol
name
address
}
}
}
fragment UserBorrowTransaction on UserBorrowTransaction {
blockExplorerUrl
txHash
timestamp
amount {
amount {
value
raw
decimals
}
usd
}
reserve {
underlyingToken {
symbol
name
address
}
}
}
fragment UserRepayTransaction on UserRepayTransaction {
blockExplorerUrl
txHash
timestamp
amount {
amount {
value
raw
decimals
}
usd
}
reserve {
underlyingToken {
symbol
name
address
}
}
}
fragment UserUsageAsCollateralTransaction on UserUsageAsCollateralTransaction {
blockExplorerUrl
txHash
timestamp
enabled
reserve {
underlyingToken {
symbol
name
address
}
}
}
fragment UserLiquidationCallTransaction on UserLiquidationCallTransaction {
blockExplorerUrl
txHash
timestamp
collateral {
reserve {
underlyingToken {
symbol
name
address
}
}
amount {
amount {
value
}
usd
}
}
debtRepaid {
reserve {
underlyingToken {
symbol
name
address
}
}
amount {
amount {
value
}
usd
}
}
}
{
"data": {
"userTransactionHistory": {
"items": [
{
"__typename": "UserSupplyTransaction",
"amount": {
"amount": {
"value": "1000.0",
"raw": "1000000000",
"decimals": 6
},
"usd": "1000.0"
},
"reserve": {
"underlyingToken": {
"symbol": "USDC",
"name": "USD Coin",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
},
"blockExplorerUrl": "https://etherscan.io/tx/0xeef2ffc57b99ce887edd8362c6d57e5c7a4d8c8a9b2c3d4e5f6789abc123def45",
"txHash": "0xeef2ffc57b99ce887edd8362c6d57e5c7a4d8c8a9b2c3d4e5f6789abc123def45",
"timestamp": "2024-03-15T14:30:00Z"
},
{
"__typename": "UserBorrowTransaction",
"amount": {
"amount": {
"value": "0.5",
"raw": "500000000000000000",
"decimals": 18
},
"usd": "1250.0"
},
"reserve": {
"underlyingToken": {
"symbol": "WETH",
"name": "Wrapped Ether",
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
}
},
"blockExplorerUrl": "https://etherscan.io/tx/0xabc123def456789012345678901234567890abcdef456789012345678901234",
"txHash": "0xabc123def456789012345678901234567890abcdef456789012345678901234",
"timestamp": "2024-03-14T10:15:00Z"
}
],
"pageInfo": {
"next": "eyJpZCI6IjB4MTIzNDU2Nzg5MGFiY2RlZjEyMzQ1Njc4OTBhYmNkZWYxMjM0NTY3OC00MSJ9",
"prev": null
}
}
}
}
Transaction Types
The user transaction history includes the following transaction types with their specific data:
SUPPLY- Asset supply transactions with amount and reserve informationWITHDRAW- Asset withdrawal transactions with amount and reserve informationBORROW- Asset borrowing transactions with amount and reserve informationREPAY- Debt repayment transactions with amount and reserve informationUSAGE_- Collateral enable/disable transactions withAS_ COLLATERAL enabledflagLIQUIDATION_- Liquidation transactions with collateral and debt repaid detailsCALL
Filtering Options
Use the filter parameter to narrow down transaction results by transaction types:
filter: ["SUPPLY", "WITHDRAW"];
Available filter values: SUPPLY, WITHDRAW, BORROW, REPAY, USAGE_, LIQUIDATION_
Ordering Options
Order transactions using the order parameter:
date- Order by transaction date (ASC or DESC)
Efficiency Mode (E-Mode)
Efficiency Mode (E-Mode) in Aave optimizes borrowing power for price-correlated assets. This feature is particularly useful when both supplied and borrowed assets share similar economic price movements, such as stablecoins or derivatives that track the same underlying asset. By using E-Mode, users benefit from lower collateral requirements, effectively increasing their available borrowing capacity for correlated assets.
To enable efficiency mode and maximize capital efficiency for correlated assets, follow these steps.
1. Check Available E-Mode Categories
First, check the available E-Mode categories on the market to understand which category to enable.
Given the following market object with available E-Mode categories:
const market: Market = {
__typename: "Market",
name: "Aave V3 Ethereum",
address: "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
chain: {
__typename: "Chain",
chainId: 1,
name: "Ethereum",
// …
},
eModeCategories: [
{
__typename: "EmodeMarketCategory",
id: 1,
label: "Stablecoins",
maxLTV: {
__typename: "PercentValue",
value: "0.97",
// …
},
liquidationThreshold: {
__typename: "PercentValue",
value: "0.975",
// …
},
liquidationPenalty: {
__typename: "PercentValue",
value: "0.01",
// …
},
reserves: [
{
__typename: "EmodeMarketReserveInfo",
underlyingToken: {
__typename: "Currency",
symbol: "USDC",
name: "USD Coin",
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
// …
},
canBeCollateral: true,
canBeBorrowed: true,
},
// … more reserves
],
// …
},
// … more categories
],
// …
};
Where each Emode object has the following properties:
max- Maximum borrowing power (% of supplied collateral you can borrow)LTV. value liquidation- The debt-to-collateral ratio at which a position becomes liquidatableThreshold. value liquidation- How much you lose if liquidation happens (lower = better)Penalty. value reserves[]- an array ofEmodeobjects, each representing a reserve that indicates if the asset can be used as collateral and/or borrowed in this specific E-Mode.Market Reserve Info
2. Prepare the Transaction Request
Next, we can proceed with creating the transaction request for the desired E-Mode operation. Use the use hook in React, the user action in TypeScript, or the user query in GraphQL to create the transaction request for enabling or disabling efficiency mode.
import { useWalletClient } from "wagmi";
import { useUserEMode, evmAddress } from "@aave/react";
// …
const { data: walletClient } = useWalletClient();
const [setEmode, settingEmode] = useUserEMode();
const execute = async () => {
const result = await setEmode({
market: market.address,
user: evmAddress(walletClient!.account.address),
categoryId: 1, // Enable category 1 (Stablecoins)
chainId: market.chain.chainId,
});
// …
};
import { useWalletClient } from "wagmi";
import { useUserEMode, evmAddress } from "@aave/react";
// …
const { data: walletClient } = useWalletClient();
const [setEmode, settingEmode] = useUserEMode();
const execute = async () => {
const result = await setEmode({
market: market.address,
user: evmAddress(walletClient!.account.address),
categoryId: null, // Disable eMode
chainId: market.chain.chainId,
});
// …
};
import { userSetEmode } from "@aave/client/actions";
import { evmAddress } from "@aave/client";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await userSetEmode(client, {
market: market.address,
user: evmAddress(wallet.account.address),
categoryId: 1, // Enable category 1 (Stablecoins)
chainId: market.chain.chainId,
});
// …
import { userSetEmode } from "@aave/client/actions";
import { evmAddress } from "@aave/client";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await userSetEmode(client, {
market: market.address,
user: evmAddress(wallet.account.address),
categoryId: null, // Disable eMode
chainId: market.chain.chainId,
});
// …
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
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(),
});
query {
userSetEmode(
request: {
market: "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2"
user: "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234"
categoryId: 1 # Enable category 1, or null to disable
chainId: 1
}
) {
to
from
data
value
chainId
}
}
3. Send the Transaction
Finally, send the transaction.
- React: Use the
usehook for the wallet library of your choice to send the transaction.Send Transaction - TypeScript: Use
sendfor your chosen wallet library to initiate the transaction, and await indexing withWith client..wait For Transaction - GraphQL: The response is a
Transactionthat can be sent directly using the wallet associated with theRequest fromaddress.
import { useWalletClient } from "wagmi";
import { useUserEMode } from "@aave/react";
import { useSendTransaction } from "@aave/react/viem";
// …
const { data: walletClient } = useWalletClient();
const [setEmode, settingEmode] = useUserEMode();
const [sendTransaction, sending] = useSendTransaction(walletClient);
// …
// Optional: combine loading states
const loading = settingEmode.loading || sending.loading;
const error = settingEmode.error || sending.error;
// …
const execute = async () => {
const result = await setEmode({
// …
}).andThen(sendTransaction);
if (result.isErr()) {
console.error("eMode operation failed:", result.error);
} else {
console.log("eMode operation successful with hash:", result.value);
}
};
import { userSetEmode } from "@aave/client/actions";
import { sendWith } from "@aave/client/viem";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await userSetEmode(client, {
// …
})
.andThen(sendWith(wallet))
.andThen(client.waitForTransaction);
if (result.isErr()) {
console.error("eMode operation failed:", result.error);
} else {
console.log("eMode operation successful:", result.value);
}
{
"data": {
"userSetEmode": {
"__typename": "TransactionRequest",
"to": "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
"from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"data": "0x28530a47000000000000000000000000000000000000000000000000000000000000000001",
"value": "0",
"chainId": 1,
"operation": "USER_EMODE_SET"
}
}
}
Liquidations
Liquidate undercollateralized positions to earn liquidation bonuses and help maintain protocol stability.
1. Identify an Undercollateralized Position
First, identify a target user with a health factor below 1.0, and one of their borrow positions that is eligible for liquidation.
const targetUser = evmAddress("0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234");
Given the following user borrow position:
const position: MarketUserReserveBorrowPosition = {
__typename: "MarketUserReserveBorrowPosition",
market: {
__typename: "MarketInfo",
address: "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
chain: { chainId: 1 },
// …
},
currency: {
__typename: "Currency",
symbol: "WETH",
name: "Wrapped Ether",
address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
// …
},
debt: {
__typename: "TokenAmount",
amount: {
__typename: "DecimalValue",
value: "1.5",
raw: "1500000000000000000",
decimals: 18,
// …
},
// …
},
// …
};
Finding users with a health factor below 1.0 is out of scope for this guide.
2. Choose the Collateral to Receive
Next, choose which of the target user’s collateral assets to receive, including the liquidation bonus.
Given the following collateral position:
const collateral: MarketUserReserveSupplyPosition = {
__typename: "MarketUserReserveSupplyPosition",
market: {
__typename: "MarketInfo",
address: "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
chain: { chainId: 1 },
// …
},
currency: {
__typename: "Currency",
symbol: "USDC",
name: "USD Coin",
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
// …
},
isCollateral: true,
canBeCollateral: true,
// …
};
3. Prepare the Transaction Request
Then, create the transaction request for the liquidation operation. Use the use hook in React, the liquidate action in TypeScript, or the liquidate query in GraphQL to create the transaction request for liquidating the undercollateralized position.
import { useLiquidate } from "@aave/react";
// …
const [liquidate, liquidating] = useLiquidate();
const execute = async () => {
const result = await liquidate({
chainId: position.market.chain.chainId,
market: position.market.address,
user: targetUser,
underlyingToken: position.currency.address,
debtToCover: { max: true }, // Liquidate maximum debt
collateralToken: collateral.currency.address,
receiveAToken: false, // Receive the underlying collateral token
});
// …
};
import { liquidate } from "@aave/client/actions";
import { client } from "./client";
const result = await liquidate(client, {
chainId: position.market.chain.chainId,
market: position.market.address,
user: targetUser,
underlyingToken: position.currency.address,
debtToCover: { max: true }, // Liquidate maximum debt
collateralToken: collateral.currency.address,
receiveAToken: false, // Receive the underlying collateral token
});
// …
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
query {
liquidate(
request: {
chainId: 1
market: "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2"
user: "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234" # targetUser
underlyingToken: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
debtToCover: { max: true }
collateralToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
receiveAToken: false
}
) {
to
from
data
value
chainId
}
}
4. Send the Transaction
Finally, send the transaction to execute the liquidation.
- React: Use the
usehook for the wallet library of your choice to send the transaction.Send Transaction - TypeScript: Use
sendfor your chosen wallet library to initiate the transaction, and await indexing withWith client..wait For Transaction - GraphQL: The response is a
Transactionthat can be sent directly using the wallet associated with theRequest fromaddress.
import { useWalletClient } from "wagmi";
import { useLiquidate } from "@aave/react";
import { useSendTransaction } from "@aave/react/viem";
// …
const { data: walletClient } = useWalletClient();
const [liquidate, liquidating] = useLiquidate();
const [sendTransaction, sending] = useSendTransaction(walletClient);
// …
// Optional: combine loading states
const loading = liquidating.loading || sending.loading;
const error = liquidating.error || sending.error;
// …
const execute = async () => {
const result = await liquidate({
// …
}).andThen(sendTransaction);
if (result.isErr()) {
console.error("Liquidation failed:", result.error);
} else {
console.log("Liquidation successful with hash:", result.value);
}
};
import { liquidate } from "@aave/client/actions";
import { sendWith } from "@aave/client/viem";
import { client } from "./client";
import { wallet } from "./wallet";
const result = await liquidate(client, {
// …
})
.andThen(sendWith(wallet))
.andThen(client.waitForTransaction);
if (result.isErr()) {
console.error("Liquidation failed:", result.error);
} else {
console.log("Liquidation successful:", result.value);
}
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(),
});
{
"data": {
"liquidate": {
"__typename": "TransactionRequest",
"to": "0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2",
"from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
"data": "0x00a718a9000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2…",
"value": "0",
"chainId": 1,
"operation": "LIQUIDATION"
}
}
}