Skip to content

Commit ee8b607

Browse files
committed
adding kaspacom-dex yields
1 parent f4ae12b commit ee8b607

File tree

1 file changed

+189
-0
lines changed

1 file changed

+189
-0
lines changed

src/adaptors/kaspacom-dex/index.js

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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 { data } = await axios.get('https://api.kaspa.org/info/price');
58+
const price = Number(data.price);
59+
if (Number.isFinite(price) && price > 0) return price;
60+
} catch (error) {
61+
console.log('Kas price from API failed, falling back to DefiLlama', error);
62+
}
63+
64+
return 0;
65+
};
66+
67+
const fetchPairs = async () => {
68+
const pairs = [];
69+
let skip = 0;
70+
71+
while (true) {
72+
const { pairs: page } = await request(SUBGRAPH_URL, PAIRS_QUERY, {
73+
first: PAGE_SIZE,
74+
skip,
75+
});
76+
pairs.push(...page);
77+
if (page.length < PAGE_SIZE) break;
78+
skip += PAGE_SIZE;
79+
}
80+
81+
return pairs;
82+
};
83+
84+
const fetchPairDayData = async (pairIds) => {
85+
if (pairIds.length === 0) return { daily: {}, weekly: {} };
86+
87+
const startWeek = Math.floor(Date.now() / 1000) - 7 * DAY_IN_SECONDS;
88+
const volumes = {};
89+
const latestDaily = {};
90+
91+
for (const batch of chunk(pairIds, 75)) {
92+
const { pairDayDatas } = await request(SUBGRAPH_URL, PAIR_DAY_DATA_QUERY, {
93+
pairAddresses: batch,
94+
startTime: startWeek,
95+
});
96+
97+
pairDayDatas.forEach((entry) => {
98+
const pairAddress = entry.pairAddress?.toLowerCase();
99+
const volumeKas = Number(entry.dailyVolumeKAS);
100+
if (!pairAddress || !Number.isFinite(volumeKas)) return;
101+
102+
if (!volumes[pairAddress]) {
103+
volumes[pairAddress] = 0;
104+
}
105+
volumes[pairAddress] += volumeKas;
106+
107+
if (
108+
!latestDaily[pairAddress] ||
109+
entry.date > latestDaily[pairAddress].date
110+
) {
111+
latestDaily[pairAddress] = {
112+
date: entry.date,
113+
volumeKas,
114+
};
115+
}
116+
});
117+
}
118+
119+
return {
120+
daily: Object.fromEntries(
121+
Object.entries(latestDaily).map(([pair, { volumeKas }]) => [
122+
pair,
123+
volumeKas,
124+
])
125+
),
126+
weekly: volumes,
127+
};
128+
};
129+
130+
const buildPools = (pairs, volumeData, kasPrice) => {
131+
const { daily, weekly } = volumeData;
132+
133+
return pairs
134+
.map((pair) => {
135+
const pairId = pair.id.toLowerCase();
136+
const kasReserve =
137+
Number(pair.trackedReserveKAS ?? pair.reserveKAS ?? 0) || 0;
138+
if (!(Number.isFinite(kasReserve) && kasReserve > 10000)) return null;
139+
const tvlUsd = kasReserve * kasPrice;
140+
141+
const volumeKas1d = daily[pairId] ?? 0;
142+
const volumeKas7d = weekly[pairId] ?? 0;
143+
144+
const volumeUsd1d = volumeKas1d * kasPrice;
145+
const volumeUsd7d = volumeKas7d * kasPrice;
146+
147+
const apyBase =
148+
tvlUsd > 0 ? ((volumeUsd1d * FEE_RATE * 365) / tvlUsd) * 100 : 0;
149+
const apyBase7d =
150+
tvlUsd > 0 ? ((volumeUsd7d * FEE_RATE * 52) / tvlUsd) * 100 : 0;
151+
152+
const symbol = utils.formatSymbol(
153+
`${pair.token0.symbol}-${pair.token1.symbol}`
154+
);
155+
156+
return {
157+
pool: `${pair.id}-${CHAIN}`,
158+
chain: CHAIN,
159+
project: 'kaspacom-dex',
160+
symbol,
161+
tvlUsd,
162+
apyBase,
163+
apyBase7d,
164+
volumeUsd1d,
165+
volumeUsd7d,
166+
underlyingTokens: [pair.token0.id, pair.token1.id],
167+
url: 'https://defi.kaspacom.com',
168+
};
169+
})
170+
.filter((pool) => pool && utils.keepFinite(pool));
171+
};
172+
173+
const apy = async () => {
174+
const [pairs, kasPrice] = await Promise.all([fetchPairs(), fetchKasPrice()]);
175+
if (!pairs.length || !Number.isFinite(kasPrice) || kasPrice <= 0) {
176+
return [];
177+
}
178+
179+
const pairIds = pairs.map((pair) => pair.id.toLowerCase());
180+
const volumeData = await fetchPairDayData(pairIds);
181+
182+
return buildPools(pairs, volumeData, kasPrice);
183+
};
184+
185+
module.exports = {
186+
timetravel: false,
187+
apy,
188+
url: 'https://defi.kaspacom.com',
189+
};

0 commit comments

Comments
 (0)