Skip to content

Commit ec7b832

Browse files
committed
calculate reserves on-chain & TVL using Defillama's Price feeds
1 parent f3aa799 commit ec7b832

File tree

1 file changed

+116
-10
lines changed

1 file changed

+116
-10
lines changed

src/adaptors/zealousswap/index.js

Lines changed: 116 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
// Zealous Swap (Kasplex) — Pools adapter for DefiLlama yield-server
2-
// Pulls per-pool TVL (USD) and APR directly from our public API.
2+
// Fetches pool data from our API and reserves on-chain, calculating TVL using DefiLlama's price feeds.
33
//
44
// Endpoint used: https://kasplex.zealousswap.com/v1/pools
55
// Notes:
6-
// - We use `apr` from our API as `apyBase` (already annualized %).
7-
// - If `apr` is missing, we fall back to fee APR from volume * feeRate.
8-
// - We include `apyReward` only if our API reports a positive `farmApr`.
9-
// - Only emit pools with `hasUSDValues === true`.
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`
1012

1113
const axios = require("axios");
14+
const sdk = require("@defillama/sdk");
15+
const BigNumber = require("bignumber.js");
1216

1317
const CHAIN = "kasplex";
1418
const API = "https://kasplex.zealousswap.com/v1/pools";
@@ -33,16 +37,117 @@ function calcFeeAPR(volumeUSD, tvlUsd, feeRate) {
3337
return (vol * fee / tvl) * 365 * 100;
3438
}
3539

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+
3682
async function apy() {
3783
const { data } = await axios.get(API);
3884
const poolsObj = data?.pools || {};
3985

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+
40116
const results = [];
41117

42-
for (const [address, p] of Object.entries(poolsObj)) {
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);
43150

44-
if (!p?.hasUSDValues) continue;
45-
const tvlUsd = toNumber(p.tvl);
46151
if (!(tvlUsd > 0)) continue;
47152

48153
let apyBase = toNumber(p.apr);
@@ -65,16 +170,17 @@ async function apy() {
65170
rewardTokens: p.hasActiveFarm
66171
? ["0xb7a95035618354D9ADFC49Eca49F38586B624040"]
67172
: [],
68-
underlyingTokens: [p.token0?.address, p.token1?.address].filter(Boolean),
173+
underlyingTokens: [p.token0.address, p.token1.address],
69174
url: "https://app.zealousswap.com/liquidity",
70175
volumeUsd1d: toNumber(p.volumeUSD),
71176
});
72177
}
73178

74-
return results.filter(x => Number.isFinite(x.tvlUsd));
179+
return results.filter(x => Number.isFinite(x.tvlUsd) && x.tvlUsd > 0);
75180
}
76181

77182
module.exports = {
78183
timetravel: false,
79184
apy,
185+
url: API,
80186
};

0 commit comments

Comments
 (0)