Skip to content

Commit 66759f9

Browse files
kyo-agbheluga
andauthored
Add KYO AG volume indexing adapter (#6272)
* kyoag * fix * add pullhourly --------- Co-authored-by: bheluga <bheluga@defillama.com>
1 parent 8ddbc2b commit 66759f9

File tree

1 file changed

+193
-0
lines changed

1 file changed

+193
-0
lines changed

aggregators/kyoag/index.ts

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import { ethers } from "ethers";
2+
import { FetchOptions, SimpleAdapter } from "../../adapters/types";
3+
import { CHAIN } from "../../helpers/chains";
4+
import ADDRESSES from "../../helpers/coreAssets.json";
5+
6+
const LEGACY_ROUTERS: Record<string, string[]> = {
7+
[CHAIN.HYPERLIQUID]: [
8+
"0x5d78854510a2d008E2E21c2fAB4Cc78582B0F2Ce",
9+
"0x91B81ACd2Ee2D8F26518139Af632c56Fad28Dcb4",
10+
],
11+
[CHAIN.SONEIUM]: [
12+
"0xD2dFd922fd6bBAe0399F68F99CD444adbd80d255",
13+
"0xf4087AFfE358c1f267Cca84293abB89C4BD10712",
14+
],
15+
};
16+
17+
// Router delegates execution to logic contracts passed in calldata, so keep a small
18+
// manual allowlist of known logic contracts per chain and index inflows to both.
19+
const LOGICS: Record<string, string[]> = {
20+
[CHAIN.HYPERLIQUID]: [
21+
"0x775f533c082a466156bd0e771957853375c96265",
22+
"0xE34E1A6b31D90ED63E1e8EB0640495978b4eB172",
23+
"0xD3027e4869da32d3c295271E4c8d4c4f6C170464",
24+
"0xcb7a9dF8074c2a2c06496b8Bee28372051f3abd7",
25+
"0x69E896668A0dDe9450C8EdD8c1D2Cee2bF99A9a9",
26+
"0xC049cA5Fa95CdE6Fd4ADCeaFaaf331A6bb33C435",
27+
"0xF8A773940Dee10144D5204c44749220e8FFa7b49",
28+
"0x7A34F2C757589825aa795aa98B72F301A8980be0",
29+
"0x0cB398DE0616c8E7b2b737fF715ACcDc888e99ce",
30+
"0x484408554626d420DE940f91786Ffb4913A78000",
31+
"0x734b73B13594638a11f59B35257390738D08543d",
32+
],
33+
[CHAIN.SONEIUM]: [
34+
"0xB3f8F67230CbAcD5Adf297BE3F1884A845c9c3C0",
35+
"0xd118e1c57d347D13BF2b14Bd665B74b7B56AF563",
36+
"0xC049cA5Fa95CdE6Fd4ADCeaFaaf331A6bb33C435",
37+
"0x36af20bc2d7F1B3cBd9cADC61395a3c5c56A75D0",
38+
"0x26Dd7F2672C96761280DC2f7Ad9D431e518002e9",
39+
"0xf582CBB0788323FAaCB00a6677caF4890ed19aCF",
40+
"0xaec97346c8d562EccfBa46325d00Af3fFB7246d0",
41+
"0x23120352144E920dbAC60bcDa2d78dE4845A084f",
42+
],
43+
};
44+
45+
const EVENT_ROUTERS: Record<string, string[]> = {
46+
[CHAIN.HYPERLIQUID]: ["0x463E176246c4fF727153a8b98381531df1B66b80"],
47+
[CHAIN.SONEIUM]: ["0x206D7FBBD740780D7eFf488D40744276e8dAf077"],
48+
[CHAIN.MONAD]: ["0x852a57ae203fec9c96c7ac9a774db048cbe4e34e"],
49+
};
50+
51+
const TRANSFER_TOPIC =
52+
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
53+
const DEPOSIT_TOPIC =
54+
"0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c";
55+
const NATIVE_TOKEN = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
56+
const SWAP_EXECUTED_EVENT =
57+
"event SwapExecuted(address indexed sender, address indexed srcToken, address indexed dstToken, address logic, uint256 amountIn, uint256 amountOut)";
58+
59+
const WRAPPED_NATIVE_TOKENS: Record<string, string[]> = {
60+
[CHAIN.HYPERLIQUID]: [ADDRESSES.hyperliquid.WHYPE],
61+
[CHAIN.SONEIUM]: [ADDRESSES.soneium.WETH],
62+
[CHAIN.MONAD]: [ADDRESSES.monad.WMON, ADDRESSES.monad.WETH],
63+
};
64+
65+
const isPositiveAmount = (amount?: string) => {
66+
if (!amount || amount === "0x") return false;
67+
try {
68+
return BigInt(amount) > 0n;
69+
} catch {
70+
return false;
71+
}
72+
};
73+
74+
const addVolume = (
75+
dailyVolume: ReturnType<FetchOptions["createBalances"]>,
76+
token?: string,
77+
amount?: string,
78+
) => {
79+
if (!token || !isPositiveAmount(amount)) return;
80+
if (token.toLowerCase() === NATIVE_TOKEN.toLowerCase()) dailyVolume.addGasToken(amount);
81+
else dailyVolume.add(token, amount);
82+
};
83+
84+
const fetch = async ({ createBalances, getLogs, chain }: FetchOptions) => {
85+
const dailyVolume = createBalances();
86+
const eventRouters = EVENT_ROUTERS[chain] ?? [];
87+
const trackedTargets = [...eventRouters, ...(LEGACY_ROUTERS[chain] ?? []), ...(LOGICS[chain] ?? [])];
88+
const trackedSet = new Set(trackedTargets.map((r) => r.toLowerCase()));
89+
const wrappedNativeSet = new Set((WRAPPED_NATIVE_TOKENS[chain] ?? []).map((a) => a.toLowerCase()));
90+
const allLogs: any[] = [];
91+
const eventTxs = new Set<string>();
92+
93+
if (eventRouters.length) {
94+
const eventLogs = await getLogs({
95+
targets: eventRouters,
96+
eventAbi: SWAP_EXECUTED_EVENT,
97+
onlyArgs: false,
98+
});
99+
100+
for (const log of eventLogs) {
101+
const txHash = (log.transactionHash as string | undefined)?.toLowerCase();
102+
if (txHash) eventTxs.add(txHash);
103+
addVolume(
104+
dailyVolume,
105+
log.srcToken ?? log.args?.srcToken,
106+
log.amountIn ?? log.args?.amountIn,
107+
);
108+
}
109+
}
110+
111+
for (const target of trackedTargets) {
112+
const padded = ethers.zeroPadValue(target, 32);
113+
114+
const [transferLogs, depositLogs] = await Promise.all([
115+
getLogs({
116+
topics: [TRANSFER_TOPIC, null as any, padded],
117+
noTarget: true,
118+
eventAbi:
119+
"event Transfer(address indexed from, address indexed to, uint256 value)",
120+
entireLog: true,
121+
}),
122+
getLogs({
123+
topics: [DEPOSIT_TOPIC, padded],
124+
noTarget: true,
125+
eventAbi: "event Deposit(address indexed dst, uint256 wad)",
126+
entireLog: true,
127+
}),
128+
]);
129+
130+
for (const log of transferLogs) {
131+
if (!isPositiveAmount(log.data)) continue;
132+
const txHash = (log.transactionHash as string | undefined)?.toLowerCase();
133+
if (!txHash) continue;
134+
if (eventTxs.has(txHash)) continue;
135+
// Exclude transfers from other tracked contracts (router/logic internal routing)
136+
if (!log.topics?.[1]) continue;
137+
const from = "0x" + log.topics[1].slice(26).toLowerCase();
138+
if (trackedSet.has(from)) continue;
139+
allLogs.push(log);
140+
}
141+
142+
for (const log of depositLogs) {
143+
if (!isPositiveAmount(log.data)) continue;
144+
const txHash = (log.transactionHash as string | undefined)?.toLowerCase();
145+
if (!txHash) continue;
146+
if (eventTxs.has(txHash)) continue;
147+
const emitter = (log.address as string | undefined)?.toLowerCase();
148+
if (!emitter || !wrappedNativeSet.has(emitter)) continue;
149+
allLogs.push(log);
150+
}
151+
}
152+
153+
// Per transaction: keep only the first inbound transfer/deposit (lowest log index).
154+
// This is the sell-token inflow before DEX routing, internal hops, or fee payouts.
155+
const firstByTx: Record<string, any> = {};
156+
for (const log of allLogs) {
157+
const txHash = (log.transactionHash as string | undefined)?.toLowerCase();
158+
if (!txHash) continue;
159+
const idx = log.logIndex ?? log.index ?? 0;
160+
const prev = firstByTx[txHash];
161+
if (!prev || idx < (prev.logIndex ?? prev.index ?? 0)) {
162+
firstByTx[txHash] = log;
163+
}
164+
}
165+
166+
for (const log of Object.values(firstByTx)) {
167+
addVolume(dailyVolume, log.address, log.data);
168+
}
169+
170+
return { dailyVolume };
171+
};
172+
173+
const methodology = {
174+
Volume:
175+
"New KYO AG routers are indexed from SwapExecuted events using the srcToken " +
176+
"amountIn side when present. Routers or methods that do not emit a swap event " +
177+
"(including legacy routers and swapMulti flows) are indexed via the earliest " +
178+
"inbound ERC-20 Transfer or wrapped-native Deposit to the router or allowlisted " +
179+
"logic contract; internal hops and fee payouts are excluded.",
180+
};
181+
182+
const adapter: SimpleAdapter = {
183+
version: 2,
184+
pullHourly: true,
185+
adapter: {
186+
[CHAIN.HYPERLIQUID]: { fetch, start: "2026-02-03" },
187+
[CHAIN.MONAD]: { fetch, start: "2026-03-23" },
188+
[CHAIN.SONEIUM]: { fetch, start: "2025-12-30" },
189+
},
190+
methodology,
191+
};
192+
193+
export default adapter;

0 commit comments

Comments
 (0)