Aave V4 Spokes
Spokes are the modules users interact with; each Spoke draws liquidity from one or more Liquidity Hubs.
Spoke Data Structure
Spoke data provide information about individual borrowing modules, including:
- Identification: id, name, address, chain
- Liquidation parameters: target health factor, max-bonus threshold, and bonus factor (
liquidation)Config - Summary metrics: supplied, borrowed, supply and borrow caps, connected Hubs, and unique assets
The following TypeScript interfaces and GraphQL fragments illustrate the core Spoke type. In Solidity, Spokes are deployed contracts implementing the ISpoke interface, managed by governance, and the following structs illustrate the Spoke data.
interface Spoke {
__typename: "Spoke";
id: SpokeId;
name: string;
address: EvmAddress;
chain: Chain;
liquidationConfig: SpokeLiquidationConfig | null;
summary: SpokeSummary;
connectedHubs: SpokeConnectedHub[];
}
interface SpokeLiquidationConfig {
__typename: "SpokeLiquidationConfig";
targetHealthFactor: BigDecimal;
healthFactorForMaxBonus: BigDecimal;
liquidationBonusFactor: PercentNumber;
}
interface SpokeSummary {
__typename: "SpokeSummary";
totalBorrowed: ExchangeAmount;
totalBorrowCap: ExchangeAmount;
totalSupplied: ExchangeAmount;
totalSupplyCap: ExchangeAmount;
uniqueAssets: number;
connectedHubs: number;
}
interface SpokeConnectedHub {
__typename: "SpokeConnectedHub";
hub: Hub;
summary: SpokeConnectedHubSummary;
}
interface SpokeConnectedHubSummary {
__typename: "SpokeConnectedHubSummary";
totalBorrowed: ExchangeAmount;
totalSupplied: ExchangeAmount;
creditLine: ExchangeAmount;
creditUsed: PercentNumber;
}
interface Chain {
__typename: "Chain";
chainId: ChainId;
name: string;
icon: string;
rpcUrl: string;
explorerUrl: string;
isTestnet: boolean;
nativeWrappedToken: EvmAddress;
nativeInfo: TokenInfo;
nativeGateway: EvmAddress;
signatureGateway: EvmAddress;
}
interface TokenInfo {
__typename: "TokenInfo";
id: TokenInfoId;
name: string;
symbol: string;
canonicalSymbol: string;
icon: string;
decimals: number;
categories: TokenCategory[];
}
fragment Spoke on Spoke {
__typename
id
name
address
chain {
...Chain
}
liquidationConfig {
...SpokeLiquidationConfig
}
summary(currency: $currency) {
...SpokeSummary
}
connectedHubs(currency: $currency) {
...SpokeConnectedHub
}
}
fragment SpokeLiquidationConfig on SpokeLiquidationConfig {
__typename
targetHealthFactor
healthFactorForMaxBonus
liquidationBonusFactor {
...PercentNumber
}
}
fragment SpokeSummary on SpokeSummary {
__typename
totalBorrowed {
...ExchangeAmount
}
totalBorrowCap {
...ExchangeAmount
}
totalSupplied {
...ExchangeAmount
}
totalSupplyCap {
...ExchangeAmount
}
uniqueAssets
connectedHubs
}
fragment SpokeConnectedHub on SpokeConnectedHub {
__typename
hub {
...Hub
}
summary {
...SpokeConnectedHubSummary
}
}
fragment SpokeConnectedHubSummary on SpokeConnectedHubSummary {
__typename
totalBorrowed {
...ExchangeAmount
}
totalSupplied {
...ExchangeAmount
}
creditLine {
...ExchangeAmount
}
creditUsed {
...PercentNumber
}
}
fragment Chain on Chain {
__typename
chainId
name
icon
rpcUrl
explorerUrl
isTestnet
nativeWrappedToken
nativeInfo {
...TokenInfo
}
nativeGateway
signatureGateway
}
fragment TokenInfo on TokenInfo {
__typename
id
name
symbol
icon
decimals
categories
}
struct SpokeData {
uint120 drawnShares;
uint120 premiumShares;
int200 premiumOffsetRay;
uint120 addedShares;
uint40 addCap;
uint40 drawCap;
uint24 riskPremiumThreshold;
bool active;
bool halted;
uint200 deficitRay;
}
struct SpokeConfig {
uint40 addCap;
uint40 drawCap;
uint24 riskPremiumThreshold;
bool active;
bool halted;
}
In Solidity, the relationship between Spokes and Hubs is tracked onchain within the Hub contract. Each Spoke can connect to multiple Hub Assets, maintaining separate Spoke state for each asset. Spoke connections and config updates emit these events:
event AddSpoke(uint256 indexed assetId, address indexed spoke);
event UpdateSpokeConfig(uint256 indexed assetId, address indexed spoke, SpokeConfig config);
Listing Available Spokes
Discover all available Aave Spokes across supported networks.
Use the use hook in React, the spokes action in TypeScript, or the spokes query in GraphQL to fetch a list of available spokes.
import { type SpokesRequest, useSpokes } from "@aave/react";
function SpokesList({ request }: { request: SpokesRequest }) {
const { data, loading, error } = useSpokes(request);
if (loading) return <div>Loading…</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{data.map((spoke) => (
<div key={spoke.id}>
<h3>{spoke.name}</h3>
<p>Chain: {spoke.chain.name}</p>
</div>
))}
</div>
);
}
import { type SpokesRequest, useSpokes } from "@aave/react";
function SpokesList({ request }: { request: SpokesRequest }) {
const { data } = useSpokes({
...request,
suspense: true,
});
return (
<div>
{data.map((spoke) => (
<div key={spoke.id}>
<h3>{spoke.name}</h3>
<p>Chain: {spoke.chain.name}</p>
</div>
))}
</div>
);
}
import { chainId } from "@aave/client";
import { spokes } from "@aave/client/actions";
import { client } from "./client";
const result = await spokes(client, {
query: {
chainIds: [chainId(1)],
},
});
if (result.isErr()) {
console.error(result.error);
} else {
// result.value: Spoke[]
console.log(result.value);
}
import { spokes } from "@aave/client/actions";
import { client } from "./client";
// const hubId: HubId = "SGVsbG8h";
const result = await spokes(client, {
query: {
hubId,
},
});
import { chainId, evmAddress } from "@aave/client";
import { spokes } from "@aave/client/actions";
import { client } from "./client";
const result = await spokes(client, {
query: {
hub: {
address: evmAddress("0x123…"),
chainId: chainId(1),
},
},
});
query {
spokes(request: { query: { chainIds: [1] } }) {
id
name
address
chain {
...Chain
}
}
}
query {
spokes(request: { query: { hubId: "SGVsbG8h" } }) {
id
name
address
chain {
...Chain
}
}
}
query {
spokes(request: { query: { hub: { address: "0x123…", chainId: 1 } } }) {
id
name
address
chain {
...Chain
}
}
}
In React, see below for examples of Spokes objects.
import { chainId } from "@aave/react";
const request: SpokesRequest = {
query: {
chainIds: [chainId(1)],
},
};
const request: SpokesRequest = {
query: {
hubId: hub.id,
},
};
import { chainId, evmAddress } from "@aave/react";
const request: SpokesRequest = {
query: {
hub: {
address: evmAddress("0x123…"),
chainId: chainId(1),
},
},
};
Deployed Spokes
In Solidity, Spokes are deployed and managed by governance. The following Spokes are deployed on Ethereum Mainnet:
| Name | Address |
|---|---|
| Main Spoke | 0x94e7 |
| Bluechip Spoke | 0x973a023 |
| Ethena Correlated Spoke | 0x58131 |
| Ethena Ecosystem Spoke | 0xba1 |
| EtherFi eSpoke | 0xb |
| Forex Spoke | 0x |
| Gold Spoke | 0x65407b940966954b23df |
| Kelp eSpoke | 0x3131 |
| Lido eSpoke | 0xe1900480ac69f0 |
| Lombard BTC Spoke | 0x7 |
| Treasury Spoke | 0x |
Spokes connected to a specific Hub can be discovered through the Hub’s Spoke events and methods. See the Hub Data Structure section for details on querying spoke relationships.
Fetching a Single Spoke
Fetch a specific Spoke by ID or by address and chain ID.
Use the use hook in React, the spoke action in TypeScript, or the spoke query in GraphQL to fetch a specific spoke.
import { type SpokeRequest, useSpoke } from "@aave/react";
function SpokeDetails({ request }: { request: SpokeRequest }) {
const { data, loading, error } = useSpoke(request);
if (loading) return <div>Loading…</div>;
if (error) return <div>Error: {error.message}</div>;
if (!data) return <div>Spoke not found</div>;
return (
<div>
<h3>{data.name}</h3>
<p>Chain: {data.chain.name}</p>
</div>
);
}
import { type SpokeRequest, useSpoke } from "@aave/react";
function SpokeDetails({ request }: { request: SpokeRequest }) {
const { data } = useSpoke({
...request,
suspense: true,
});
if (!data) return <div>Spoke not found</div>;
return (
<div>
<h3>{data.name}</h3>
<p>Chain: {data.chain.name}</p>
</div>
);
}
import { spoke } from "@aave/client/actions";
import { client } from "./client";
// const spokeId: SpokeId = "SGVsbG8h";
const result = await spoke(client, {
query: {
spokeId,
},
});
if (result.isOk()) {
console.log(result.value); // Spoke | null - null if not found
} else {
console.error(result.error);
}
import { chainId, evmAddress } from "@aave/client";
import { spoke } from "@aave/client/actions";
import { client } from "./client";
const result = await spoke(client, {
query: {
spoke: {
address: evmAddress("0x123…"),
chainId: chainId(1),
},
},
});
query {
spoke(request: { query: { spokeId: "SGVsbG8h" } }) {
id
name
address
chain {
...Chain
}
}
}
query {
spoke(request: { query: { spoke: { address: "0x123…", chainId: 1 } } }) {
id
name
address
chain {
...Chain
}
}
}
In React, see below for examples of Spoke objects.
import { spokeId } from "@aave/react";
const request: SpokeRequest = {
query: {
spokeId: spokeId(params.id), // SpokeId - typically from URL params
},
};
import { chainId, evmAddress } from "@aave/react";
const request: SpokeRequest = {
query: {
spoke: {
address: evmAddress("0x123…"),
chainId: chainId(1),
},
},
};
Spoke Summary History
Fetch historical deposits and borrows for a specific spoke over a chosen time window.
Use the use hook in React, the spoke action in TypeScript, or the spoke query in GraphQL to fetch historical data for a Spoke.
import {
type SpokeSummaryHistoryRequest,
useSpokeSummaryHistory,
} from "@aave/react";
function SpokeHistory({ request }: { request: SpokeSummaryHistoryRequest }) {
const { data, loading, error } = useSpokeSummaryHistory(request);
if (loading) return <div>Loading…</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h2>Spoke History</h2>
{data.map((item) => (
<div key={item.date.toString()}>
<p>Date: {item.date.toLocaleDateString()}</p>
<p>
Deposits: {item.deposits.symbol}
{item.deposits.value.toDisplayString(2)}
</p>
<p>
Borrows: {item.borrows.symbol}
{item.borrows.value.toDisplayString(2)}
</p>
</div>
))}
</div>
);
}
import {
type SpokeSummaryHistoryRequest,
useSpokeSummaryHistory,
} from "@aave/react";
function SpokeHistory({ request }: { request: SpokeSummaryHistoryRequest }) {
const { data } = useSpokeSummaryHistory({
...request,
suspense: true,
});
return (
<div>
<h2>Spoke History</h2>
{data.map((item) => (
<div key={item.date.toString()}>
<p>Date: {item.date.toLocaleDateString()}</p>
<p>
Deposits: {item.deposits.symbol}
{item.deposits.value.toDisplayString(2)}
</p>
<p>
Borrows: {item.borrows.symbol}
{item.borrows.value.toDisplayString(2)}
</p>
</div>
))}
</div>
);
}
import { Currency, spokeId, TimeWindow } from "@aave/client";
import { spokeSummaryHistory } from "@aave/client/actions";
import { client } from "./client";
const result = await spokeSummaryHistory(client, {
query: { spokeId: spokeId("SGVsbG8h") },
currency: Currency.Usd,
window: TimeWindow.LastWeek,
});
if (result.isOk()) {
console.log(result.value); // SpokeSummarySample[]
} else {
console.error(result.error);
}
import { AaveClient } from "@aave/client";
export const client = AaveClient.create();
query ($request: SpokeSummaryHistoryRequest!) {
spokeSummaryHistory(request: $request) {
date
deposits {
...ExchangeAmount
}
borrows {
...ExchangeAmount
}
}
}
- React: See below for examples of
Spokeobjects.Summary History Request - GraphQL: Use the
query,currencyandwindowfields to specify the Spoke and time period.
import { spokeId, TimeWindow } from "@aave/react";
const request: SpokeSummaryHistoryRequest = {
query: { spokeId: spokeId(params.id) },
window: TimeWindow.LastWeek,
};
import { chainId, evmAddress, TimeWindow } from "@aave/react";
const request: SpokeSummaryHistoryRequest = {
query: {
spokeInput: {
address: evmAddress("0x123…"),
chainId: chainId(1),
},
},
window: TimeWindow.LastWeek,
};
{
"request": {
"query": { "spokeId": "SGVsbG8h" },
"currency": "USD",
"window": "LAST_WEEK"
}
}
{
"request": {
"query": {
"spokeInput": { "address": "0x123…", "chainId": 1 }
},
"currency": "USD",
"window": "LAST_WEEK"
}
}