Skip to content

Commit 6dccbc1

Browse files
authored
Merge pull request #2204 from calintje/fix/orca-dex
Update Orca adapter to v2 pools API
2 parents 8e33a7b + f7d1c46 commit 6dccbc1

File tree

2 files changed

+143
-36
lines changed

2 files changed

+143
-36
lines changed

src/adaptors/orca-dex/index.js

Lines changed: 0 additions & 36 deletions
This file was deleted.

src/adaptors/orca-dex/index.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import * as utils from '../utils';
2+
3+
declare const require: any;
4+
const axios = require('axios');
5+
6+
const ORCA_API_BASE_URL = 'https://api.orca.so/v2/solana/pools';
7+
8+
interface PublicWhirlpoolStatsWindow {
9+
fees?: string | null;
10+
rewards?: string | null;
11+
volume?: string | null;
12+
}
13+
14+
interface PublicWhirlpoolStats {
15+
'24h'?: PublicWhirlpoolStatsWindow;
16+
'7d'?: PublicWhirlpoolStatsWindow;
17+
[period: string]: PublicWhirlpoolStatsWindow | undefined;
18+
}
19+
20+
interface PublicToken {
21+
address: string;
22+
symbol?: string | null;
23+
}
24+
25+
interface WhirlpoolReward {
26+
mint: string;
27+
active: boolean;
28+
emissionsPerSecond: string; // BigDecimal serialized as decimal string
29+
}
30+
31+
interface PublicWhirlpool {
32+
address: string;
33+
tvlUsdc: string | number | null;
34+
stats?: PublicWhirlpoolStats;
35+
tokenA: PublicToken;
36+
tokenB: PublicToken;
37+
rewards?: WhirlpoolReward[];
38+
}
39+
40+
interface CursorMeta {
41+
previous?: string | null;
42+
next?: string | null;
43+
}
44+
45+
interface OrcaPoolsResponse {
46+
data: PublicWhirlpool[];
47+
meta?: {
48+
cursor?: CursorMeta | null;
49+
} | null;
50+
}
51+
52+
const toNumber = (value: string | number | null | undefined): number =>
53+
value == null ? NaN : Number(value);
54+
55+
const ratioOrNaN = (num: number, denom: number): number =>
56+
Number.isFinite(num) && Number.isFinite(denom) && denom > 0 ? num / denom : NaN;
57+
58+
async function fetchAllPools(): Promise<PublicWhirlpool[]> {
59+
const pools: PublicWhirlpool[] = [];
60+
let next: string | undefined;
61+
62+
for (; ;) {
63+
const params = next
64+
? { next }
65+
: {
66+
sortBy: 'tvl',
67+
sortDirection: 'desc',
68+
minTvl: 10000,
69+
size: 1000,
70+
stats: '24h,7d',
71+
};
72+
73+
const response = await axios.get(ORCA_API_BASE_URL, { params });
74+
const { data, meta } = (response.data || {}) as OrcaPoolsResponse;
75+
76+
if (!Array.isArray(data) || data.length === 0) break;
77+
78+
pools.push(...data);
79+
80+
const cursor = meta?.cursor?.next ?? null;
81+
if (!cursor) break;
82+
next = cursor;
83+
}
84+
85+
return pools;
86+
}
87+
88+
const getApy = async () => {
89+
const pools = await fetchAllPools();
90+
91+
const mapped = pools.map((p) => {
92+
const tvlUsd = toNumber(p.tvlUsdc);
93+
94+
const stats = p.stats;
95+
const stats24h = stats?.['24h'];
96+
const stats7d = stats?.['7d'];
97+
98+
const fees24h = toNumber(stats24h?.fees);
99+
const rewards24h = toNumber(stats24h?.rewards);
100+
const fees7d = toNumber(stats7d?.fees);
101+
102+
const volumeUsd1d = toNumber(stats24h?.volume);
103+
const volumeUsd7d = toNumber(stats7d?.volume);
104+
105+
// Only compute APY when both numerator and denominator are finite and denom > 0.
106+
// Otherwise APY becomes NaN and the pool will be filtered out by keepFinite.
107+
const apyBase = ratioOrNaN(fees24h, tvlUsd) * 365 * 100;
108+
const apyReward = ratioOrNaN(rewards24h, tvlUsd) * 365 * 100;
109+
const apyBase7d = ratioOrNaN(fees7d, tvlUsd) * (365 / 7) * 100;
110+
111+
const rewardTokens =
112+
p.rewards
113+
?.filter((r) => {
114+
if (!r || !r.active || !r.mint) return false;
115+
const emissions = toNumber(r.emissionsPerSecond);
116+
return Number.isFinite(emissions) && emissions > 0;
117+
})
118+
.map((r) => r.mint) ?? [];
119+
120+
const symbolA = p.tokenA.symbol ?? '';
121+
const symbolB = p.tokenB.symbol ?? '';
122+
123+
return {
124+
pool: p.address,
125+
chain: 'Solana',
126+
project: 'orca-dex',
127+
symbol: utils.formatSymbol(`${symbolA}-${symbolB}`),
128+
underlyingTokens: [p.tokenA.address, p.tokenB.address],
129+
rewardTokens,
130+
tvlUsd,
131+
apyBase,
132+
apyReward,
133+
apyBase7d,
134+
volumeUsd1d,
135+
volumeUsd7d,
136+
url: `https://www.orca.so/pools/${p.address}`,
137+
};
138+
});
139+
140+
return mapped.filter((p) => utils.keepFinite(p));
141+
};
142+
143+
export const apy = getApy;

0 commit comments

Comments
 (0)