Skip to content

Commit 6f24ceb

Browse files
authored
Merge pull request #2200 from giladm11/adding-kaspacom-dex-yields
adding kaspacom-dex yields
2 parents 01d1963 + 6b9205d commit 6f24ceb

File tree

1 file changed

+198
-0
lines changed

1 file changed

+198
-0
lines changed

src/adaptors/kaspacom-dex/index.js

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
const axios = require('axios');
2+
const { request, gql } = require('graphql-request');
3+
const { chunk } = require('lodash');
4+
5+
const utils = require('../utils');
6+
7+
const SUBGRAPH_URL =
8+
'https://graph-kasplex.kaspa.com/subgraphs/name/kasplex-kas-v2-core';
9+
const CHAIN = 'kasplex';
10+
const FEE_RATE = 0.01;
11+
const PAGE_SIZE = 1000;
12+
const DAY_IN_SECONDS = 86400;
13+
14+
const PAIRS_QUERY = gql`
15+
query getPairs($first: Int!, $skip: Int!) {
16+
pairs(
17+
first: $first
18+
skip: $skip
19+
orderBy: reserveKAS
20+
orderDirection: desc
21+
) {
22+
id
23+
token0 {
24+
id
25+
symbol
26+
}
27+
token1 {
28+
id
29+
symbol
30+
}
31+
reserveKAS
32+
trackedReserveKAS
33+
reserve0
34+
reserve1
35+
volumeKAS
36+
}
37+
}
38+
`;
39+
40+
const PAIR_DAY_DATA_QUERY = gql`
41+
query getPairDayData($pairAddresses: [Bytes!]!, $startTime: Int!) {
42+
pairDayDatas(
43+
first: 1000
44+
orderBy: date
45+
orderDirection: desc
46+
where: { pairAddress_in: $pairAddresses, date_gt: $startTime }
47+
) {
48+
date
49+
pairAddress
50+
dailyVolumeKAS
51+
}
52+
}
53+
`;
54+
55+
const fetchKasPrice = async () => {
56+
try {
57+
const priceKey = 'coingecko:kaspa';
58+
const kaspa = (
59+
await utils.getData(`https://coins.llama.fi/prices/current/${priceKey}`)
60+
).coins[priceKey].price;
61+
if (Number.isFinite(kaspa) && kaspa > 0) return kaspa;
62+
} catch (error) {
63+
console.log('Kas price from CoinGecko failed, falling back to API', error);
64+
try {
65+
const { data } = await axios.get('https://api.kaspa.org/info/price');
66+
const price = Number(data.price);
67+
if (Number.isFinite(price) && price > 0) return price;
68+
} catch (fallbackError) {
69+
console.log('Kas price from API also failed', fallbackError);
70+
}
71+
}
72+
73+
return 0;
74+
};
75+
76+
const fetchPairs = async () => {
77+
const pairs = [];
78+
let skip = 0;
79+
80+
while (true) {
81+
const { pairs: page } = await request(SUBGRAPH_URL, PAIRS_QUERY, {
82+
first: PAGE_SIZE,
83+
skip,
84+
});
85+
pairs.push(...page);
86+
if (page.length < PAGE_SIZE) break;
87+
skip += PAGE_SIZE;
88+
}
89+
90+
return pairs;
91+
};
92+
93+
const fetchPairDayData = async (pairIds) => {
94+
if (pairIds.length === 0) return { daily: {}, weekly: {} };
95+
96+
const startWeek = Math.floor(Date.now() / 1000) - 7 * DAY_IN_SECONDS;
97+
const volumes = {};
98+
const latestDaily = {};
99+
100+
for (const batch of chunk(pairIds, 75)) {
101+
const { pairDayDatas } = await request(SUBGRAPH_URL, PAIR_DAY_DATA_QUERY, {
102+
pairAddresses: batch,
103+
startTime: startWeek,
104+
});
105+
106+
pairDayDatas.forEach((entry) => {
107+
const pairAddress = entry.pairAddress?.toLowerCase();
108+
const volumeKas = Number(entry.dailyVolumeKAS);
109+
if (!pairAddress || !Number.isFinite(volumeKas)) return;
110+
111+
if (!volumes[pairAddress]) {
112+
volumes[pairAddress] = 0;
113+
}
114+
volumes[pairAddress] += volumeKas;
115+
116+
if (
117+
!latestDaily[pairAddress] ||
118+
entry.date > latestDaily[pairAddress].date
119+
) {
120+
latestDaily[pairAddress] = {
121+
date: entry.date,
122+
volumeKas,
123+
};
124+
}
125+
});
126+
}
127+
128+
return {
129+
daily: Object.fromEntries(
130+
Object.entries(latestDaily).map(([pair, { volumeKas }]) => [
131+
pair,
132+
volumeKas,
133+
])
134+
),
135+
weekly: volumes,
136+
};
137+
};
138+
139+
const buildPools = (pairs, volumeData, kasPrice) => {
140+
const { daily, weekly } = volumeData;
141+
142+
return pairs
143+
.map((pair) => {
144+
const pairId = pair.id.toLowerCase();
145+
const kasReserve =
146+
Number(pair.trackedReserveKAS ?? pair.reserveKAS ?? 0) || 0;
147+
if (!(Number.isFinite(kasReserve) && kasReserve > 10000)) return null;
148+
const tvlUsd = kasReserve * kasPrice;
149+
150+
const volumeKas1d = daily[pairId] ?? 0;
151+
const volumeKas7d = weekly[pairId] ?? 0;
152+
153+
const volumeUsd1d = volumeKas1d * kasPrice;
154+
const volumeUsd7d = volumeKas7d * kasPrice;
155+
156+
const apyBase =
157+
tvlUsd > 0 ? ((volumeUsd1d * FEE_RATE * 365) / tvlUsd) * 100 : 0;
158+
const apyBase7d =
159+
tvlUsd > 0 ? ((volumeUsd7d * FEE_RATE * 52) / tvlUsd) * 100 : 0;
160+
161+
const symbol = utils.formatSymbol(
162+
`${pair.token0.symbol}-${pair.token1.symbol}`
163+
);
164+
165+
return {
166+
pool: `${pair.id}-${CHAIN}`,
167+
chain: CHAIN,
168+
project: 'kaspacom-dex',
169+
symbol,
170+
tvlUsd,
171+
apyBase,
172+
apyBase7d,
173+
volumeUsd1d,
174+
volumeUsd7d,
175+
underlyingTokens: [pair.token0.id, pair.token1.id],
176+
url: 'https://defi.kaspa.com',
177+
};
178+
})
179+
.filter((pool) => pool && utils.keepFinite(pool));
180+
};
181+
182+
const apy = async () => {
183+
const [pairs, kasPrice] = await Promise.all([fetchPairs(), fetchKasPrice()]);
184+
if (!pairs.length || !Number.isFinite(kasPrice) || kasPrice <= 0) {
185+
return [];
186+
}
187+
188+
const pairIds = pairs.map((pair) => pair.id.toLowerCase());
189+
const volumeData = await fetchPairDayData(pairIds);
190+
191+
return buildPools(pairs, volumeData, kasPrice);
192+
};
193+
194+
module.exports = {
195+
timetravel: false,
196+
apy,
197+
url: 'https://defi.kaspa.com',
198+
};

0 commit comments

Comments
 (0)