Skip to content

Commit 6be8ac3

Browse files
Hybra-v4 fee integrated (#4504)
* feat hybra integration * update 24 fee & Revenue * refactor and add fees * commit hybra-v4 adapte * update graph fee * remvoe fees files * update hybra-v4 adapter --------- Co-authored-by: Eden <[email protected]>
1 parent 9e7de5c commit 6be8ac3

File tree

1 file changed

+208
-0
lines changed

1 file changed

+208
-0
lines changed

dexs/hybra-v4.ts

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import { FetchOptions, SimpleAdapter } from "../adapters/types";
2+
import { CHAIN } from "../helpers/chains";
3+
import { addOneToken } from "../helpers/prices";
4+
import { request } from "graphql-request";
5+
6+
const swapEvent = 'event Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick)'
7+
8+
const FACTORY = '0x32b9dA73215255d50D84FeB51540B75acC1324c2';
9+
10+
const FEE_SUBGRAPH_URL = 'https://api.goldsky.com/api/public/project_cmbj707z4cd9901sib1f6cu0c/subgraphs/fee-setting/fee-setting/gn';
11+
12+
// Default fee mapping based on tickSpacing (from Hybra V4 factory initialization)
13+
// enableTickSpacing(tickSpacing, fee) where fee is in basis points (1e6 = 100%)
14+
const TICK_SPACING_TO_FEE: {[key: number]: number} = {
15+
1: 100 / 1e6, // 0.01%
16+
10: 500 / 1e6, // 0.05%
17+
50: 2500 / 1e6, // 0.25%
18+
100: 5000 / 1e6, // 0.5%
19+
200: 10000 / 1e6, // 1%
20+
2000: 10000 / 1e6, // 1%
21+
};
22+
23+
interface FeeEvent {
24+
blockNumber: number;
25+
timestamp: number;
26+
fee: number;
27+
}
28+
29+
interface SetCustomFee {
30+
pool: string;
31+
fee: string;
32+
timestamp_: string;
33+
block_number: string;
34+
}
35+
36+
async function fetch(options: FetchOptions) {
37+
const allPoolsLength = await options.api.call({
38+
target: FACTORY,
39+
abi: 'uint256:allPoolsLength',
40+
});
41+
const allPoolsCalls: any = [];
42+
for (let i = 0; i < Number(allPoolsLength); i++) {
43+
allPoolsCalls.push({ params: [i] })
44+
}
45+
const allPools = await options.api.multiCall({
46+
target: FACTORY,
47+
abi: 'function allPools(uint) view returns (address)',
48+
calls: allPoolsCalls,
49+
});
50+
51+
const token0Addresses = await options.api.multiCall({ abi: 'address:token0', calls: allPools });
52+
const token1Addresses = await options.api.multiCall({ abi: 'address:token1', calls: allPools });
53+
54+
const pairObject: Record<string, Array<string>> = {}
55+
for (let i = 0; i < Number(allPoolsLength); i++) {
56+
pairObject[allPools[i]] = [
57+
token0Addresses[i],
58+
token1Addresses[i],
59+
]
60+
}
61+
62+
const allLogs = await options.getLogs({
63+
targets: allPools,
64+
eventAbi: swapEvent,
65+
flatten: false
66+
});
67+
68+
const dailyVolume = options.createBalances();
69+
allLogs.forEach((logs, index) => {
70+
if (!logs.length) return;
71+
const pool = allPools[index];
72+
const [token0, token1] = pairObject[pool];
73+
74+
for (const log of logs) {
75+
addOneToken({
76+
chain: options.chain,
77+
balances: dailyVolume,
78+
token0,
79+
token1,
80+
amount0: log.amount0.toString(),
81+
amount1: log.amount1.toString()
82+
});
83+
}
84+
})
85+
86+
return await customLogic({ pairObject, dailyVolume, allLogs, fetchOptions: options })
87+
}
88+
89+
const customLogic = async ({ pairObject, dailyVolume, allLogs, fetchOptions }: any) => {
90+
const { api, chain, createBalances } = fetchOptions;
91+
const poolAddresses = Object.keys(pairObject);
92+
93+
// 1. Get all SetCustomFee events from GraphQL to track fee history
94+
const query = `
95+
query {
96+
setCustomFees(orderBy: timestamp_, orderDirection: asc) {
97+
fee
98+
pool
99+
timestamp_
100+
block_number
101+
}
102+
}
103+
`;
104+
105+
const data = await request(FEE_SUBGRAPH_URL, query);
106+
const customFees: SetCustomFee[] = data?.setCustomFees || [];
107+
108+
// 2. Build fee history mapping: pool -> [(blockNumber, timestamp, fee)]
109+
const feeHistory: {[pool: string]: FeeEvent[]} = {};
110+
customFees.forEach((event: SetCustomFee) => {
111+
const pool = event.pool.toLowerCase();
112+
if (!feeHistory[pool]) {
113+
feeHistory[pool] = [];
114+
}
115+
feeHistory[pool].push({
116+
blockNumber: parseInt(event.block_number),
117+
timestamp: parseInt(event.timestamp_),
118+
fee: parseFloat(event.fee) / 1e6
119+
});
120+
});
121+
122+
// Sort fee events by blockNumber and timestamp (already sorted by query, but ensure consistency)
123+
Object.keys(feeHistory).forEach(pool => {
124+
feeHistory[pool].sort((a, b) => {
125+
if (a.blockNumber !== b.blockNumber) {
126+
return a.blockNumber - b.blockNumber;
127+
}
128+
return a.timestamp - b.timestamp;
129+
});
130+
});
131+
132+
// 3. Get default fees based on tickSpacing for pools without custom fees
133+
const tickSpacings = await api.multiCall({
134+
abi: 'function tickSpacing() view returns (int24)',
135+
calls: poolAddresses,
136+
permitFailure: true
137+
});
138+
139+
const defaultFees: {[pool: string]: number} = {};
140+
tickSpacings.forEach((tickSpacing: any, i: number) => {
141+
if (tickSpacing !== null) {
142+
const pool
143+
= poolAddresses[i];
144+
const absTickSpacing = Math.abs(tickSpacing);
145+
defaultFees[pool] = TICK_SPACING_TO_FEE[absTickSpacing] || 0.003; // Default to 0.3%
146+
}
147+
});
148+
149+
// 4. Process each swap with the correct historical fee
150+
const dailyFees = createBalances();
151+
allLogs.forEach((logs: any, index: number) => {
152+
if (!logs.length) return;
153+
const pool = poolAddresses[index];
154+
const [token0, token1] = pairObject[pool];
155+
156+
logs.forEach((log: any) => {
157+
// Find the applicable fee at the time of this swap
158+
let fee = defaultFees[pool]
159+
160+
const poolFeeHistory = feeHistory[pool];
161+
if (poolFeeHistory && poolFeeHistory.length > 0) {
162+
// Find the most recent fee change before or at this swap's block
163+
for (let i = poolFeeHistory.length - 1; i >= 0; i--) {
164+
const feeEvent = poolFeeHistory[i];
165+
if (feeEvent.blockNumber <= log.blockNumber) {
166+
fee = feeEvent.fee;
167+
break;
168+
}
169+
}
170+
}
171+
172+
addOneToken({
173+
chain,
174+
balances: dailyFees,
175+
token0,
176+
token1,
177+
amount0: log.amount0.toString() * fee,
178+
amount1: log.amount1.toString() * fee
179+
});
180+
});
181+
});
182+
183+
return {
184+
dailyVolume,
185+
dailyFees,
186+
dailyUserFees: dailyFees,
187+
dailyRevenue: dailyFees.clone(0.25),
188+
dailyProtocolRevenue: dailyFees.clone(0.25),
189+
dailySupplySideRevenue: dailyFees.clone(0.75),
190+
};
191+
};
192+
193+
const adapter: SimpleAdapter = {
194+
version: 2,
195+
methodology: {
196+
Volume: `Total swap volume collected from factory ${FACTORY}`,
197+
Fees: 'Users paid dynamic fees per swap.',
198+
UserFees: 'Users paid dynamic fees per swap.',
199+
Revenue: '25% swap fees collected by protocol Treasury.',
200+
ProtocolRevenue: '25% swap fees collected by protocol Treasury.',
201+
SupplySideRevenue: '75% swap fees distributed to LPs.',
202+
},
203+
start: '2025-10-17',
204+
chains: [CHAIN.HYPERLIQUID],
205+
fetch,
206+
}
207+
208+
export default adapter

0 commit comments

Comments
 (0)