Skip to content

Commit e789167

Browse files
amrrobbbheluga
andauthored
feat(napier): add dexs volume adapter + rewrite fees adapter to API-based (#6065)
* feat(napier): rewrite fees adapter to use API-based daily fee data Replace on-chain event tracking with pre-computed dailyFeeInUsd from napier-api market list. Removes all ABI definitions, BigNumber deps, and complex on-chain fee parsing in favor of a single API call per chain. - Sum dailyFeeInUsd across all markets for daily fees - Split into curator (supply side) and protocol revenue - Keep addTokensReceived for reward token protocol revenue - Support all 11 chains (ETH, Base, Sonic, ARB, OP, Frax, Mantle, BSC, Polygon, Avax, HyperEVM) * feat(napier): add dexs volume adapter + fix fees error handling - Add production dexs/napier/index.ts for on-chain volume tracking (Curve TokenExchange + TokiHook HookSwap events, all 11 chains) - Fix fees adapter: throw on API failure instead of silently returning zeros - Fix fees adapter: skip addTokensReceived when no reward tokens exist * update methodology to align with NAP-2375 * refactor(napier/dexs): derive TokiHook address from API instead of hardcoding * feat(napier): add Plume chain support for dexs and fees * fix: address CodeRabbit review - validate API payload, explicit pool type check, fix methodology * chore: remove staging files from PR * add pullhourly * add pullhourly to fees * add runAtCurrTime for fees , as api doesnt support historic data --------- Co-authored-by: bheluga <bheluga@defillama.com>
1 parent 42ab37b commit e789167

File tree

3 files changed

+338
-295
lines changed

3 files changed

+338
-295
lines changed

dexs/napier/index.ts

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { Chain } from "../../adapters/types";
2+
import axios from "axios";
3+
import { FetchOptions, FetchResultVolume, SimpleAdapter } from "../../adapters/types";
4+
import { CHAIN } from "../../helpers/chains";
5+
6+
/**
7+
* Napier Finance Volume Adapter
8+
*
9+
* Tracks swap volume from Napier AMM pools by monitoring on-chain events.
10+
* Supports both Curve TwoCrypto pools (TokenExchange events) and
11+
* TokiHook/Uniswap V4 pools (HookSwap events).
12+
*
13+
* Pool addresses and types are fetched from napier-api to dynamically
14+
* detect which pools exist on each chain.
15+
*/
16+
17+
const CURVE_POOL_ABI = {
18+
tokenExchangeEvent: "event TokenExchange(address indexed buyer, uint256 sold_id, uint256 tokens_sold, uint256 bought_id, uint256 tokens_bought, uint256 fee, uint256 packed_price_scale)",
19+
};
20+
21+
const TOKI_HOOK_ABI = {
22+
hookSwapEvent: "event HookSwap(bytes32 indexed poolId, address indexed sender, int128 amount0, int128 amount1, uint128 hookLPfeeAmount0, uint128 hookLPfeeAmount1)",
23+
};
24+
25+
interface Market {
26+
chainId: number;
27+
metadata: { address: string };
28+
pool: {
29+
address: string;
30+
poolId?: string;
31+
poolType?: "CURVE_TWO_CRYPTO" | "TOKI_HOOK";
32+
} | null;
33+
tokens: {
34+
targetToken: { id: string; decimals: number; address: string };
35+
assetToken: { id: string; address: string; decimals: number };
36+
};
37+
}
38+
39+
const API_BASE_URL = process.env.NAPIER_API_URL ?? "https://api-v2.napier.finance";
40+
41+
async function fetchMarkets(api: any) {
42+
const url = `${API_BASE_URL}/v1/market?chainIds=${api.chainId!}`;
43+
const res = await axios.get<Market[]>(url);
44+
45+
46+
if (!Array.isArray(res.data)) {
47+
throw new Error(`Napier API returned non-array payload for chainId ${api.chainId}`);
48+
}
49+
50+
const curvePools: string[] = [];
51+
const poolToMarket = new Map<string, Market>();
52+
let tokiHookAddress: string | null = null;
53+
const poolIdToMarket = new Map<string, Market>();
54+
55+
for (const market of res.data) {
56+
if (!market.pool?.address) continue;
57+
const poolAddress = market.pool.address;
58+
poolToMarket.set(poolAddress.toLowerCase(), market);
59+
60+
if (market.pool.poolType === "TOKI_HOOK" && market.pool.poolId) {
61+
tokiHookAddress = poolAddress;
62+
poolIdToMarket.set(market.pool.poolId.toLowerCase(), market);
63+
} else if (market.pool.poolType === "CURVE_TWO_CRYPTO") {
64+
curvePools.push(poolAddress);
65+
}
66+
}
67+
68+
return { curvePools, tokiHookAddress, poolIdToMarket, poolToMarket };
69+
}
70+
71+
const fetch = async (options: FetchOptions) => {
72+
const { getLogs, createBalances } = options;
73+
const { curvePools, tokiHookAddress, poolIdToMarket, poolToMarket } = await fetchMarkets(options.api);
74+
const dailyVolume = createBalances();
75+
76+
// Track Curve TwoCrypto pool volume via TokenExchange events
77+
if (curvePools.length > 0) {
78+
const curveExchangeEvents = await getLogs({
79+
targets: curvePools,
80+
eventAbi: CURVE_POOL_ABI.tokenExchangeEvent,
81+
flatten: false,
82+
});
83+
84+
for (let i = 0; i < curvePools.length; i++) {
85+
const poolAddress = curvePools[i].toLowerCase();
86+
const market = poolToMarket.get(poolAddress);
87+
if (!market) continue;
88+
89+
const events = curveExchangeEvents[i];
90+
if (!events || events.length === 0) continue;
91+
92+
const assetToken = market.tokens.assetToken.address;
93+
const assetDecimals = market.tokens.assetToken.decimals;
94+
const targetDecimals = market.tokens.targetToken.decimals;
95+
96+
for (const event of events) {
97+
const soldId = event.sold_id;
98+
const boughtId = event.bought_id;
99+
const tokensSold = event.tokens_sold;
100+
const tokensBought = event.tokens_bought;
101+
102+
// Target token is always token index 1 in the Curve pool
103+
let targetAmount: bigint;
104+
if (soldId === 1n) {
105+
targetAmount = tokensSold;
106+
} else if (boughtId === 1n) {
107+
targetAmount = tokensBought;
108+
} else {
109+
continue;
110+
}
111+
112+
// Convert target token amount to asset token terms for pricing
113+
const assetAmount = targetAmount * BigInt(10 ** assetDecimals) / BigInt(10 ** targetDecimals);
114+
dailyVolume.add(assetToken, assetAmount);
115+
}
116+
}
117+
}
118+
119+
// Track TokiHook (Uniswap V4) pool volume via HookSwap events
120+
// tokiHookAddress is the singleton contract derived from the API response (pool.address for TOKI_HOOK markets)
121+
if (tokiHookAddress && poolIdToMarket.size > 0) {
122+
const hookSwapEvents = await getLogs({
123+
target: tokiHookAddress,
124+
eventAbi: TOKI_HOOK_ABI.hookSwapEvent,
125+
});
126+
127+
for (const event of hookSwapEvents) {
128+
const market = poolIdToMarket.get(event.poolId.toLowerCase());
129+
if (!market) continue;
130+
131+
const assetToken = market.tokens.assetToken.address;
132+
const assetDecimals = market.tokens.assetToken.decimals;
133+
const targetDecimals = market.tokens.targetToken.decimals;
134+
const absAmount0 = event.amount0 < 0n ? -event.amount0 : event.amount0;
135+
const assetAmount = absAmount0 * BigInt(10 ** assetDecimals) / BigInt(10 ** targetDecimals);
136+
dailyVolume.add(assetToken, assetAmount);
137+
}
138+
}
139+
140+
return { dailyVolume };
141+
};
142+
143+
const methodology = {
144+
Volume: "Aggregates trading volume from Napier AMM pools by tracking on-chain swap events. Supports Curve AMM (TwoCrypto) pools via TokenExchange events and Napier AMM (TokiHook/Uniswap V4) pools via HookSwap events.",
145+
};
146+
147+
const chainConfig: Record<Chain, { start: string }> = {
148+
[CHAIN.ETHEREUM]: { start: "2024-02-28" },
149+
[CHAIN.BASE]: { start: "2024-02-27" },
150+
[CHAIN.SONIC]: { start: "2024-03-07" },
151+
[CHAIN.ARBITRUM]: { start: "2024-03-11" },
152+
[CHAIN.OPTIMISM]: { start: "2024-03-11" },
153+
[CHAIN.FRAXTAL]: { start: "2024-03-11" },
154+
[CHAIN.MANTLE]: { start: "2024-03-11" },
155+
[CHAIN.BSC]: { start: "2024-03-11" },
156+
[CHAIN.POLYGON]: { start: "2024-03-12" },
157+
[CHAIN.AVAX]: { start: "2024-03-12" },
158+
[CHAIN.HYPERLIQUID]: { start: "2024-03-13" },
159+
[CHAIN.PLUME]: { start: "2024-03-13" },
160+
};
161+
162+
const adapter: SimpleAdapter = {
163+
version: 2,
164+
pullHourly: true,
165+
fetch,
166+
adapter: chainConfig,
167+
methodology,
168+
};
169+
170+
export default adapter;

0 commit comments

Comments
 (0)