Skip to content

Commit d84ae5a

Browse files
authored
Merge pull request #2177 from louissaadgo/feat/add-zealous-swap-pools
add zealous swap pools
2 parents 6f01283 + ec7b832 commit d84ae5a

File tree

1 file changed

+186
-0
lines changed

1 file changed

+186
-0
lines changed

src/adaptors/zealousswap/index.js

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Zealous Swap (Kasplex) — Pools adapter for DefiLlama yield-server
2+
// Fetches pool data from our API and reserves on-chain, calculating TVL using DefiLlama's price feeds.
3+
//
4+
// Endpoint used: https://kasplex.zealousswap.com/v1/pools
5+
// Notes:
6+
// - Reserves are fetched on-chain via getReserves(), with API fallback if call fails
7+
// - TVL is calculated from token reserves using DefiLlama's price API
8+
// - Falls back to API's TVL value if DefiLlama prices are unavailable
9+
// - We use `apr` from our API as `apyBase` (already annualized %)
10+
// - If `apr` is missing, we fall back to fee APR from volume * feeRate
11+
// - We include `apyReward` only if our API reports a positive `farmApr`
12+
13+
const axios = require("axios");
14+
const sdk = require("@defillama/sdk");
15+
const BigNumber = require("bignumber.js");
16+
17+
const CHAIN = "kasplex";
18+
const API = "https://kasplex.zealousswap.com/v1/pools";
19+
20+
function toNumber(x) {
21+
if (x === null || x === undefined) return 0;
22+
const n = Number(x);
23+
return Number.isFinite(n) ? n : 0;
24+
}
25+
26+
function poolSymbol(p) {
27+
const s0 = p.token0?.symbol || "T0";
28+
const s1 = p.token1?.symbol || "T1";
29+
return `${s0}-${s1}`;
30+
}
31+
32+
function calcFeeAPR(volumeUSD, tvlUsd, feeRate) {
33+
const vol = toNumber(volumeUSD);
34+
const tvl = toNumber(tvlUsd);
35+
const fee = toNumber(feeRate);
36+
if (tvl <= 0 || vol <= 0 || fee <= 0) return 0;
37+
return (vol * fee / tvl) * 365 * 100;
38+
}
39+
40+
const getPrices = async (addresses, chain) => {
41+
const prices = (
42+
await axios.get(
43+
`https://coins.llama.fi/prices/current/${addresses
44+
.map((address) => `${chain}:${address}`)
45+
.join(',')
46+
.toLowerCase()}`
47+
)
48+
).data.coins;
49+
50+
const pricesObj = Object.entries(prices).reduce(
51+
(acc, [address, price]) => ({
52+
...acc,
53+
[address.split(':')[1].toLowerCase()]: price.price,
54+
}),
55+
{}
56+
);
57+
58+
return pricesObj;
59+
};
60+
61+
const calculateReservesUSD = (
62+
reserve0,
63+
reserve1,
64+
token0Address,
65+
token1Address,
66+
decimals0,
67+
decimals1,
68+
tokenPrices
69+
) => {
70+
const token0Price = tokenPrices[token0Address.toLowerCase()];
71+
const token1Price = tokenPrices[token1Address.toLowerCase()];
72+
73+
const reserve0Adjusted = new BigNumber(reserve0).div(10 ** decimals0);
74+
const reserve1Adjusted = new BigNumber(reserve1).div(10 ** decimals1);
75+
76+
if (token0Price) return reserve0Adjusted.times(token0Price).times(2);
77+
if (token1Price) return reserve1Adjusted.times(token1Price).times(2);
78+
79+
return null;
80+
};
81+
82+
async function apy() {
83+
const { data } = await axios.get(API);
84+
const poolsObj = data?.pools || {};
85+
86+
const poolAddresses = Object.keys(poolsObj);
87+
if (poolAddresses.length === 0) return [];
88+
89+
const reservesResults = await sdk.api.abi.multiCall({
90+
abi: {
91+
inputs: [],
92+
name: "getReserves",
93+
outputs: [
94+
{ internalType: "uint112", name: "_reserve0", type: "uint112" },
95+
{ internalType: "uint112", name: "_reserve1", type: "uint112" },
96+
{ internalType: "uint32", name: "_blockTimestampLast", type: "uint32" }
97+
],
98+
stateMutability: "view",
99+
type: "function"
100+
},
101+
calls: poolAddresses.map((address) => ({
102+
target: address,
103+
})),
104+
chain: CHAIN,
105+
permitFailure: true,
106+
});
107+
108+
const tokenAddresses = new Set();
109+
for (const p of Object.values(poolsObj)) {
110+
if (p.token0?.address) tokenAddresses.add(p.token0.address.toLowerCase());
111+
if (p.token1?.address) tokenAddresses.add(p.token1.address.toLowerCase());
112+
}
113+
114+
const tokenPrices = await getPrices(Array.from(tokenAddresses), CHAIN);
115+
116+
const results = [];
117+
118+
for (let i = 0; i < poolAddresses.length; i++) {
119+
const address = poolAddresses[i];
120+
const p = poolsObj[address];
121+
122+
if (!p.token0?.address || !p.token1?.address) continue;
123+
124+
const reserveData = reservesResults.output[i];
125+
let reserve0, reserve1;
126+
127+
if (reserveData && reserveData.success && reserveData.output) {
128+
reserve0 = reserveData.output._reserve0 || reserveData.output[0];
129+
reserve1 = reserveData.output._reserve1 || reserveData.output[1];
130+
} else {
131+
reserve0 = p.token0Reserves;
132+
reserve1 = p.token1Reserves;
133+
}
134+
135+
if (!reserve0 || !reserve1) continue;
136+
137+
const tvlFromReserves = calculateReservesUSD(
138+
reserve0,
139+
reserve1,
140+
p.token0.address,
141+
p.token1.address,
142+
p.token0.decimals,
143+
p.token1.decimals,
144+
tokenPrices
145+
);
146+
147+
const tvlUsd = tvlFromReserves
148+
? Number(tvlFromReserves.toString())
149+
: toNumber(p.tvl);
150+
151+
if (!(tvlUsd > 0)) continue;
152+
153+
let apyBase = toNumber(p.apr);
154+
155+
if (!(apyBase > 0)) {
156+
const feeRate = p.regularFeeRate ?? p.discountedFeeRate ?? 0.003;
157+
apyBase = calcFeeAPR(p.volumeUSD, tvlUsd, feeRate);
158+
}
159+
160+
const apyReward = toNumber(p.farmApr) > 0 ? toNumber(p.farmApr) : null;
161+
162+
results.push({
163+
pool: `${address}-${CHAIN}`,
164+
chain: CHAIN,
165+
project: "zealousswap",
166+
symbol: poolSymbol(p),
167+
tvlUsd,
168+
apyBase,
169+
apyReward,
170+
rewardTokens: p.hasActiveFarm
171+
? ["0xb7a95035618354D9ADFC49Eca49F38586B624040"]
172+
: [],
173+
underlyingTokens: [p.token0.address, p.token1.address],
174+
url: "https://app.zealousswap.com/liquidity",
175+
volumeUsd1d: toNumber(p.volumeUSD),
176+
});
177+
}
178+
179+
return results.filter(x => Number.isFinite(x.tvlUsd) && x.tvlUsd > 0);
180+
}
181+
182+
module.exports = {
183+
timetravel: false,
184+
apy,
185+
url: API,
186+
};

0 commit comments

Comments
 (0)