@kamino-finance/kliquidity-sdk 8.3.1 → 8.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Kamino.d.ts +12 -12
- package/dist/Kamino.d.ts.map +1 -1
- package/dist/Kamino.js +101 -120
- package/dist/Kamino.js.map +1 -1
- package/dist/constants/numericalValues.d.ts +3 -0
- package/dist/constants/numericalValues.d.ts.map +1 -1
- package/dist/constants/numericalValues.js +4 -1
- package/dist/constants/numericalValues.js.map +1 -1
- package/dist/rebalance_methods/autodriftRebalance.d.ts.map +1 -1
- package/dist/rebalance_methods/autodriftRebalance.js +10 -10
- package/dist/rebalance_methods/autodriftRebalance.js.map +1 -1
- package/dist/rebalance_methods/driftRebalance.d.ts.map +1 -1
- package/dist/rebalance_methods/driftRebalance.js +10 -10
- package/dist/rebalance_methods/driftRebalance.js.map +1 -1
- package/dist/rebalance_methods/expanderRebalance.d.ts.map +1 -1
- package/dist/rebalance_methods/expanderRebalance.js +3 -4
- package/dist/rebalance_methods/expanderRebalance.js.map +1 -1
- package/dist/rebalance_methods/pricePercentageRebalance.d.ts.map +1 -1
- package/dist/rebalance_methods/pricePercentageRebalance.js +5 -6
- package/dist/rebalance_methods/pricePercentageRebalance.js.map +1 -1
- package/dist/rebalance_methods/pricePercentageWithResetRebalance.d.ts.map +1 -1
- package/dist/rebalance_methods/pricePercentageWithResetRebalance.js +5 -6
- package/dist/rebalance_methods/pricePercentageWithResetRebalance.js.map +1 -1
- package/dist/rebalance_methods/takeProfitRebalance.d.ts.map +1 -1
- package/dist/rebalance_methods/takeProfitRebalance.js +5 -5
- package/dist/rebalance_methods/takeProfitRebalance.js.map +1 -1
- package/dist/services/OrcaService.d.ts +7 -11
- package/dist/services/OrcaService.d.ts.map +1 -1
- package/dist/services/OrcaService.js +132 -100
- package/dist/services/OrcaService.js.map +1 -1
- package/dist/services/OrcaWhirlpoolsResponse.d.ts +63 -92
- package/dist/services/OrcaWhirlpoolsResponse.d.ts.map +1 -1
- package/dist/utils/farms.d.ts +1 -0
- package/dist/utils/farms.d.ts.map +1 -0
- package/dist/utils/farms.js +2 -0
- package/dist/utils/farms.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/orca.d.ts +37 -1
- package/dist/utils/orca.d.ts.map +1 -1
- package/dist/utils/orca.js +242 -0
- package/dist/utils/orca.js.map +1 -1
- package/dist/utils/tokenUtils.d.ts +5 -5
- package/dist/utils/tokenUtils.d.ts.map +1 -1
- package/dist/utils/tokenUtils.js +11 -7
- package/dist/utils/tokenUtils.js.map +1 -1
- package/dist/utils/types.d.ts +5 -0
- package/dist/utils/types.d.ts.map +1 -1
- package/dist/utils/types.js.map +1 -1
- package/dist/utils/utils.d.ts +0 -1
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +3 -4
- package/dist/utils/utils.js.map +1 -1
- package/dist/utils/whirlpools.d.ts +12 -1
- package/dist/utils/whirlpools.d.ts.map +1 -1
- package/dist/utils/whirlpools.js +30 -29
- package/dist/utils/whirlpools.js.map +1 -1
- package/package.json +4 -3
- package/src/Kamino.ts +252 -205
- package/src/constants/numericalValues.ts +5 -0
- package/src/rebalance_methods/autodriftRebalance.ts +30 -22
- package/src/rebalance_methods/driftRebalance.ts +30 -22
- package/src/rebalance_methods/expanderRebalance.ts +7 -4
- package/src/rebalance_methods/pricePercentageRebalance.ts +13 -6
- package/src/rebalance_methods/pricePercentageWithResetRebalance.ts +13 -6
- package/src/rebalance_methods/takeProfitRebalance.ts +13 -5
- package/src/services/OrcaService.ts +162 -125
- package/src/services/OrcaWhirlpoolsResponse.ts +69 -101
- package/src/utils/farms.ts +0 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/orca.ts +377 -1
- package/src/utils/tokenUtils.ts +5 -5
- package/src/utils/types.ts +7 -0
- package/src/utils/utils.ts +2 -4
- package/src/utils/whirlpools.ts +50 -31
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import { Address, Rpc, SolanaRpcApi } from '@solana/kit';
|
|
1
|
+
import { address, Address, Rpc, SolanaRpcApi } from '@solana/kit';
|
|
2
2
|
import Decimal from 'decimal.js';
|
|
3
|
-
import {
|
|
4
|
-
estimateAprsForPriceRange,
|
|
5
|
-
OrcaNetwork,
|
|
6
|
-
OrcaWhirlpoolClient,
|
|
7
|
-
getNearestValidTickIndexFromTickIndex,
|
|
8
|
-
priceToTickIndex,
|
|
9
|
-
PoolData,
|
|
10
|
-
} from '@orca-so/whirlpool-sdk';
|
|
11
3
|
import axios from 'axios';
|
|
12
|
-
import { OrcaWhirlpoolsResponse, Whirlpool } from './OrcaWhirlpoolsResponse';
|
|
13
|
-
import { SolanaCluster } from '@hubbleprotocol/hubble-config';
|
|
4
|
+
import { OrcaWhirlpoolsResponse, Whirlpool as WhirlpoolAPIResponse } from './OrcaWhirlpoolsResponse';
|
|
14
5
|
import { WhirlpoolStrategy } from '../@codegen/kliquidity/accounts';
|
|
15
6
|
import { Position } from '../@codegen/whirlpools/accounts';
|
|
16
7
|
import { WhirlpoolAprApy } from './WhirlpoolAprApy';
|
|
17
8
|
import {
|
|
18
9
|
aprToApy,
|
|
10
|
+
estimateAprsForPriceRange,
|
|
19
11
|
GenericPoolInfo,
|
|
12
|
+
getHighestInitializedTickArrayTickIndex,
|
|
13
|
+
getLiquidityDistribution,
|
|
14
|
+
getLowestInitializedTickArrayTickIndex,
|
|
15
|
+
getNearestValidTickIndexFromTickIndex,
|
|
20
16
|
getStrategyPriceRangeOrca,
|
|
21
17
|
LiquidityDistribution,
|
|
22
18
|
LiquidityForPrice,
|
|
@@ -25,35 +21,91 @@ import {
|
|
|
25
21
|
import { PROGRAM_ID as WHIRLPOOLS_PROGRAM_ID } from '../@codegen/whirlpools/programId';
|
|
26
22
|
import { CollateralInfo } from '../@codegen/kliquidity/types';
|
|
27
23
|
import { KaminoPrices } from '../models';
|
|
28
|
-
import { fromLegacyPublicKey } from '@solana/compat';
|
|
29
24
|
import { Connection } from '@solana/web3.js';
|
|
25
|
+
import { priceToTickIndex } from '@orca-so/whirlpools-core';
|
|
30
26
|
|
|
31
27
|
export class OrcaService {
|
|
32
28
|
private readonly _rpc: Rpc<SolanaRpcApi>;
|
|
33
|
-
private readonly _legacyConnection: Connection;
|
|
34
29
|
private readonly _whirlpoolProgramId: Address;
|
|
35
|
-
private readonly _orcaNetwork: OrcaNetwork;
|
|
36
30
|
private readonly _orcaApiUrl: string;
|
|
37
31
|
|
|
38
32
|
constructor(
|
|
39
33
|
rpc: Rpc<SolanaRpcApi>,
|
|
40
34
|
legacyConnection: Connection,
|
|
41
|
-
cluster: SolanaCluster,
|
|
42
35
|
whirlpoolProgramId: Address = WHIRLPOOLS_PROGRAM_ID
|
|
43
36
|
) {
|
|
44
37
|
this._rpc = rpc;
|
|
45
|
-
this._legacyConnection = legacyConnection;
|
|
46
38
|
this._whirlpoolProgramId = whirlpoolProgramId;
|
|
47
|
-
this.
|
|
48
|
-
this._orcaApiUrl = `https://api.${cluster === 'mainnet-beta' ? 'mainnet' : 'devnet'}.orca.so`;
|
|
39
|
+
this._orcaApiUrl = `https://api.orca.so/v2/solana`;
|
|
49
40
|
}
|
|
50
41
|
|
|
51
42
|
getWhirlpoolProgramId(): Address {
|
|
52
43
|
return this._whirlpoolProgramId;
|
|
53
44
|
}
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
// Fetch all Orca whirlpools with pagination support (note there are over 20 pages so it may take a while)
|
|
47
|
+
async getOrcaWhirlpools(tokens: Address[] = []): Promise<WhirlpoolAPIResponse[]> {
|
|
48
|
+
const maxPageSize = 1000;
|
|
49
|
+
const maxPages = 100; // Safety limit to prevent infinite loops
|
|
50
|
+
const allWhirlpools: WhirlpoolAPIResponse[] = [];
|
|
51
|
+
let after: string | undefined = undefined;
|
|
52
|
+
let hasMore = true;
|
|
53
|
+
let pageCount = 0;
|
|
54
|
+
|
|
55
|
+
while (hasMore && pageCount < maxPages) {
|
|
56
|
+
pageCount++;
|
|
57
|
+
const url = new URL(`${this._orcaApiUrl}/pools`);
|
|
58
|
+
url.searchParams.set('size', maxPageSize.toString());
|
|
59
|
+
|
|
60
|
+
if (after) {
|
|
61
|
+
url.searchParams.set('after', after);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Add token filtering parameters based on the number of tokens provided
|
|
65
|
+
if (tokens.length === 1) {
|
|
66
|
+
url.searchParams.set('token', tokens[0]);
|
|
67
|
+
} else if (tokens.length === 2) {
|
|
68
|
+
url.searchParams.set('tokensBothOf', tokens.join(','));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const response = await axios.get<OrcaWhirlpoolsResponse>(url.toString());
|
|
73
|
+
const data = response.data;
|
|
74
|
+
|
|
75
|
+
// Add whirlpools from this page to our collection
|
|
76
|
+
if (data.data && data.data.length > 0) {
|
|
77
|
+
allWhirlpools.push(...data.data);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check if there are more pages using the meta.cursor.next field
|
|
81
|
+
if (data.meta?.cursor?.next) {
|
|
82
|
+
after = data.meta.cursor.next;
|
|
83
|
+
hasMore = true;
|
|
84
|
+
} else {
|
|
85
|
+
hasMore = false;
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('Error fetching Orca whirlpools page:', error);
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (pageCount >= maxPages) {
|
|
94
|
+
console.warn(`Reached maximum page limit (${maxPages}). There might be more whirlpools available.`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return allWhirlpools;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async getOrcaWhirlpool(poolAddress: Address): Promise<WhirlpoolAPIResponse> {
|
|
101
|
+
const response = await axios.get(`${this._orcaApiUrl}/pools/${poolAddress}`);
|
|
102
|
+
|
|
103
|
+
// If the API response has a nested data field that contains the actual pool data
|
|
104
|
+
if (response.data.data && typeof response.data.data === 'object') {
|
|
105
|
+
return response.data.data;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return response.data;
|
|
57
109
|
}
|
|
58
110
|
|
|
59
111
|
/**
|
|
@@ -68,8 +120,8 @@ export class OrcaService {
|
|
|
68
120
|
strategy: WhirlpoolStrategy,
|
|
69
121
|
prices: KaminoPrices,
|
|
70
122
|
collateralInfos: CollateralInfo[]
|
|
71
|
-
):
|
|
72
|
-
const tokensPrices:
|
|
123
|
+
): Map<Address, Decimal> {
|
|
124
|
+
const tokensPrices: Map<Address, Decimal> = new Map();
|
|
73
125
|
|
|
74
126
|
const tokenA = collateralInfos[strategy.tokenACollateralId.toNumber()];
|
|
75
127
|
const tokenB = collateralInfos[strategy.tokenBCollateralId.toNumber()];
|
|
@@ -83,34 +135,34 @@ export class OrcaService {
|
|
|
83
135
|
const reward1Price = strategy.reward1Decimals.toNumber() !== 0 ? prices.spot[rewardToken1.mint.toString()] : null;
|
|
84
136
|
const reward2Price = strategy.reward2Decimals.toNumber() !== 0 ? prices.spot[rewardToken2.mint.toString()] : null;
|
|
85
137
|
|
|
86
|
-
const [mintA, mintB] = [strategy.tokenAMint.toString(), strategy.tokenBMint.toString()];
|
|
87
|
-
const reward0 = collateralInfos[strategy.reward0CollateralId.toNumber()]?.mint?.toString();
|
|
88
|
-
const reward1 = collateralInfos[strategy.reward1CollateralId.toNumber()]?.mint?.toString();
|
|
89
|
-
const reward2 = collateralInfos[strategy.reward2CollateralId.toNumber()]?.mint?.toString();
|
|
138
|
+
const [mintA, mintB] = [address(strategy.tokenAMint.toString()), address(strategy.tokenBMint.toString())];
|
|
139
|
+
const reward0 = address(collateralInfos[strategy.reward0CollateralId.toNumber()]?.mint?.toString());
|
|
140
|
+
const reward1 = address(collateralInfos[strategy.reward1CollateralId.toNumber()]?.mint?.toString());
|
|
141
|
+
const reward2 = address(collateralInfos[strategy.reward2CollateralId.toNumber()]?.mint?.toString());
|
|
90
142
|
|
|
91
|
-
tokensPrices
|
|
92
|
-
tokensPrices
|
|
143
|
+
tokensPrices.set(mintA, aPrice.price);
|
|
144
|
+
tokensPrices.set(mintB, bPrice.price);
|
|
93
145
|
if (reward0Price !== null) {
|
|
94
|
-
tokensPrices
|
|
146
|
+
tokensPrices.set(reward0, reward0Price.price);
|
|
95
147
|
}
|
|
96
148
|
if (reward1Price !== null) {
|
|
97
|
-
tokensPrices
|
|
149
|
+
tokensPrices.set(reward1, reward1Price.price);
|
|
98
150
|
}
|
|
99
151
|
if (reward2Price !== null) {
|
|
100
|
-
tokensPrices
|
|
152
|
+
tokensPrices.set(reward2, reward2Price.price);
|
|
101
153
|
}
|
|
102
154
|
|
|
103
155
|
return tokensPrices;
|
|
104
156
|
}
|
|
105
157
|
|
|
106
|
-
private getPoolTokensPrices(pool:
|
|
107
|
-
const tokensPrices:
|
|
158
|
+
private getPoolTokensPrices(pool: WhirlpoolAPIResponse, prices: KaminoPrices): Map<Address, Decimal> {
|
|
159
|
+
const tokensPrices: Map<Address, Decimal> = new Map();
|
|
108
160
|
const tokens = [
|
|
109
|
-
pool.tokenMintA.toString(),
|
|
110
|
-
pool.tokenMintB.toString(),
|
|
111
|
-
pool.rewards[0]
|
|
112
|
-
pool.rewards[1]
|
|
113
|
-
pool.rewards[2]
|
|
161
|
+
address(pool.tokenMintA.toString()),
|
|
162
|
+
address(pool.tokenMintB.toString()),
|
|
163
|
+
address(pool.rewards[0]?.mint.toString()),
|
|
164
|
+
address(pool.rewards[1]?.mint.toString()),
|
|
165
|
+
address(pool.rewards[2]?.mint.toString()),
|
|
114
166
|
];
|
|
115
167
|
for (const mint of tokens) {
|
|
116
168
|
if (mint) {
|
|
@@ -118,46 +170,28 @@ export class OrcaService {
|
|
|
118
170
|
if (!price) {
|
|
119
171
|
throw new Error(`Could not get token ${mint} price`);
|
|
120
172
|
}
|
|
121
|
-
tokensPrices
|
|
173
|
+
tokensPrices.set(mint, price);
|
|
122
174
|
}
|
|
123
175
|
}
|
|
124
176
|
|
|
125
177
|
return tokensPrices;
|
|
126
178
|
}
|
|
127
179
|
|
|
128
|
-
async getPool(poolAddress: Address) {
|
|
129
|
-
const orca = new OrcaWhirlpoolClient({
|
|
130
|
-
connection: this._legacyConnection,
|
|
131
|
-
network: this._orcaNetwork,
|
|
132
|
-
});
|
|
133
|
-
return orca.getPool(poolAddress);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
180
|
async getStrategyWhirlpoolPoolAprApy(
|
|
137
181
|
strategy: WhirlpoolStrategy,
|
|
138
182
|
collateralInfos: CollateralInfo[],
|
|
139
|
-
prices: KaminoPrices
|
|
140
|
-
whirlpools?: Whirlpool[]
|
|
183
|
+
prices: KaminoPrices
|
|
141
184
|
): Promise<WhirlpoolAprApy> {
|
|
142
|
-
const orca = new OrcaWhirlpoolClient({
|
|
143
|
-
connection: this._legacyConnection,
|
|
144
|
-
network: this._orcaNetwork,
|
|
145
|
-
});
|
|
146
185
|
const position = await Position.fetch(this._rpc, strategy.position);
|
|
147
186
|
if (!position) {
|
|
148
187
|
throw new Error(`Position ${strategy.position.toString()} does not exist`);
|
|
149
188
|
}
|
|
150
189
|
|
|
151
|
-
const pool = await
|
|
152
|
-
if (!
|
|
153
|
-
({ whirlpools } = await this.getOrcaWhirlpools());
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const whirlpool = whirlpools?.find((x) => x.address === strategy.pool);
|
|
157
|
-
|
|
158
|
-
if (!pool || !whirlpool) {
|
|
190
|
+
const pool = await this.getOrcaWhirlpool(strategy.pool);
|
|
191
|
+
if (!pool) {
|
|
159
192
|
throw Error(`Could not get orca pool data for ${strategy.pool.toString()}`);
|
|
160
193
|
}
|
|
194
|
+
|
|
161
195
|
const priceRange = getStrategyPriceRangeOrca(
|
|
162
196
|
position.tickLowerIndex,
|
|
163
197
|
position.tickUpperIndex,
|
|
@@ -166,7 +200,10 @@ export class OrcaService {
|
|
|
166
200
|
);
|
|
167
201
|
if (priceRange.strategyOutOfRange) {
|
|
168
202
|
return {
|
|
169
|
-
|
|
203
|
+
priceLower: new Decimal(priceRange.priceLower),
|
|
204
|
+
priceUpper: new Decimal(priceRange.priceUpper),
|
|
205
|
+
poolPrice: new Decimal(pool.price),
|
|
206
|
+
strategyOutOfRange: priceRange.strategyOutOfRange,
|
|
170
207
|
rewardsApy: [],
|
|
171
208
|
rewardsApr: [],
|
|
172
209
|
feeApy: ZERO,
|
|
@@ -176,20 +213,34 @@ export class OrcaService {
|
|
|
176
213
|
};
|
|
177
214
|
}
|
|
178
215
|
|
|
179
|
-
const lpFeeRate = pool.
|
|
180
|
-
const volume24hUsd =
|
|
216
|
+
const lpFeeRate = new Decimal(pool.feeRate);
|
|
217
|
+
const volume24hUsd = pool.stats['24h']?.volume ?? new Decimal(0);
|
|
181
218
|
const fee24Usd = new Decimal(volume24hUsd).mul(lpFeeRate).toNumber();
|
|
182
219
|
const tokensPrices = this.getTokenPrices(strategy, prices, collateralInfos);
|
|
183
220
|
|
|
221
|
+
const rewardsDecimals = new Map<Address, number>();
|
|
222
|
+
if (strategy.reward0Decimals.toNumber() !== 0) {
|
|
223
|
+
rewardsDecimals.set(address(pool.rewards[0]?.mint), strategy.reward0Decimals.toNumber());
|
|
224
|
+
}
|
|
225
|
+
if (strategy.reward1Decimals.toNumber() !== 0) {
|
|
226
|
+
rewardsDecimals.set(address(pool.rewards[1]?.mint), strategy.reward1Decimals.toNumber());
|
|
227
|
+
}
|
|
228
|
+
if (strategy.reward2Decimals.toNumber() !== 0) {
|
|
229
|
+
rewardsDecimals.set(address(pool.rewards[2]?.mint), strategy.reward2Decimals.toNumber());
|
|
230
|
+
}
|
|
184
231
|
const apr = estimateAprsForPriceRange(
|
|
185
232
|
pool,
|
|
186
233
|
tokensPrices,
|
|
187
234
|
fee24Usd,
|
|
188
235
|
position.tickLowerIndex,
|
|
189
|
-
position.tickUpperIndex
|
|
236
|
+
position.tickUpperIndex,
|
|
237
|
+
rewardsDecimals
|
|
190
238
|
);
|
|
191
239
|
|
|
192
|
-
|
|
240
|
+
let totalApr = new Decimal(apr.fee);
|
|
241
|
+
for (const reward of apr.rewards) {
|
|
242
|
+
totalApr = totalApr.add(reward);
|
|
243
|
+
}
|
|
193
244
|
const feeApr = new Decimal(apr.fee);
|
|
194
245
|
const rewardsApr = apr.rewards.map((r) => new Decimal(r));
|
|
195
246
|
return {
|
|
@@ -199,7 +250,10 @@ export class OrcaService {
|
|
|
199
250
|
feeApy: aprToApy(feeApr, 365),
|
|
200
251
|
rewardsApr,
|
|
201
252
|
rewardsApy: rewardsApr.map((x) => aprToApy(x, 365)),
|
|
202
|
-
|
|
253
|
+
priceLower: new Decimal(priceRange.priceLower),
|
|
254
|
+
priceUpper: new Decimal(priceRange.priceUpper),
|
|
255
|
+
poolPrice: new Decimal(pool.price),
|
|
256
|
+
strategyOutOfRange: priceRange.strategyOutOfRange,
|
|
203
257
|
};
|
|
204
258
|
}
|
|
205
259
|
|
|
@@ -210,12 +264,8 @@ export class OrcaService {
|
|
|
210
264
|
lowestTick?: number,
|
|
211
265
|
highestTick?: number
|
|
212
266
|
): Promise<LiquidityDistribution> {
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
network: this._orcaNetwork,
|
|
216
|
-
});
|
|
217
|
-
const poolData = await orca.getPool(pool);
|
|
218
|
-
if (!poolData) {
|
|
267
|
+
const whirlpool = await this.getOrcaWhirlpool(pool);
|
|
268
|
+
if (!whirlpool) {
|
|
219
269
|
throw new Error(`Could not get pool data for Whirlpool ${pool}`);
|
|
220
270
|
}
|
|
221
271
|
|
|
@@ -223,25 +273,28 @@ export class OrcaService {
|
|
|
223
273
|
if (lowestTick) {
|
|
224
274
|
lowestInitializedTick = lowestTick;
|
|
225
275
|
} else {
|
|
226
|
-
lowestInitializedTick = await
|
|
276
|
+
lowestInitializedTick = await getLowestInitializedTickArrayTickIndex(this._rpc, pool, whirlpool.tickSpacing);
|
|
227
277
|
}
|
|
228
278
|
|
|
229
279
|
let highestInitializedTick: number;
|
|
230
280
|
if (highestTick) {
|
|
231
281
|
highestInitializedTick = highestTick;
|
|
232
282
|
} else {
|
|
233
|
-
highestInitializedTick = await
|
|
283
|
+
highestInitializedTick = await getHighestInitializedTickArrayTickIndex(this._rpc, pool, whirlpool.tickSpacing);
|
|
234
284
|
}
|
|
235
285
|
|
|
236
|
-
const orcaLiqDistribution = await
|
|
286
|
+
const orcaLiqDistribution = await getLiquidityDistribution(
|
|
287
|
+
this._rpc,
|
|
237
288
|
pool,
|
|
289
|
+
whirlpool,
|
|
238
290
|
lowestInitializedTick,
|
|
239
|
-
highestInitializedTick
|
|
291
|
+
highestInitializedTick,
|
|
292
|
+
this._whirlpoolProgramId
|
|
240
293
|
);
|
|
241
294
|
|
|
242
295
|
const liqDistribution: LiquidityDistribution = {
|
|
243
|
-
currentPrice:
|
|
244
|
-
currentTickIndex:
|
|
296
|
+
currentPrice: new Decimal(whirlpool.price),
|
|
297
|
+
currentTickIndex: whirlpool.tickCurrentIndex,
|
|
245
298
|
distribution: [],
|
|
246
299
|
};
|
|
247
300
|
|
|
@@ -267,26 +320,15 @@ export class OrcaService {
|
|
|
267
320
|
priceLower: Decimal,
|
|
268
321
|
priceUpper: Decimal,
|
|
269
322
|
prices: KaminoPrices,
|
|
270
|
-
|
|
323
|
+
rewardsDecimals: Map<Address, number>
|
|
271
324
|
): Promise<WhirlpoolAprApy> {
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
network: this._orcaNetwork,
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
const pool = await orca.getPool(poolPubkey);
|
|
278
|
-
if (!whirlpools) {
|
|
279
|
-
({ whirlpools } = await this.getOrcaWhirlpools());
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const whirlpool = whirlpools?.find((x) => x.address === poolPubkey.toString());
|
|
283
|
-
|
|
284
|
-
if (!pool || !whirlpool) {
|
|
325
|
+
const pool = await this.getOrcaWhirlpool(poolPubkey);
|
|
326
|
+
if (!pool) {
|
|
285
327
|
throw Error(`Could not get orca pool data for ${poolPubkey}`);
|
|
286
328
|
}
|
|
287
329
|
|
|
288
330
|
let strategyOutOfRange = false;
|
|
289
|
-
if (priceLower.gt(pool.price) || priceUpper.lt(pool.price)) {
|
|
331
|
+
if (priceLower.gt(new Decimal(pool.price)) || priceUpper.lt(new Decimal(pool.price))) {
|
|
290
332
|
strategyOutOfRange = true;
|
|
291
333
|
}
|
|
292
334
|
if (strategyOutOfRange) {
|
|
@@ -294,7 +336,7 @@ export class OrcaService {
|
|
|
294
336
|
priceLower,
|
|
295
337
|
priceUpper,
|
|
296
338
|
strategyOutOfRange,
|
|
297
|
-
poolPrice: pool.price,
|
|
339
|
+
poolPrice: new Decimal(pool.price),
|
|
298
340
|
rewardsApy: [],
|
|
299
341
|
rewardsApr: [],
|
|
300
342
|
feeApy: ZERO,
|
|
@@ -304,21 +346,28 @@ export class OrcaService {
|
|
|
304
346
|
};
|
|
305
347
|
}
|
|
306
348
|
|
|
307
|
-
const lpFeeRate = pool.
|
|
308
|
-
const volume24hUsd =
|
|
349
|
+
const lpFeeRate = pool.feeRate;
|
|
350
|
+
const volume24hUsd = pool?.stats?.['24h']?.volume ?? new Decimal(0);
|
|
309
351
|
const fee24Usd = new Decimal(volume24hUsd).mul(lpFeeRate).toNumber();
|
|
310
352
|
const tokensPrices = this.getPoolTokensPrices(pool, prices);
|
|
311
353
|
|
|
312
354
|
const tickLowerIndex = getNearestValidTickIndexFromTickIndex(
|
|
313
|
-
priceToTickIndex(priceLower, pool.
|
|
314
|
-
|
|
355
|
+
priceToTickIndex(priceLower.toNumber(), pool.tokenA.decimals, pool.tokenB.decimals),
|
|
356
|
+
pool.tickSpacing
|
|
315
357
|
);
|
|
316
358
|
const tickUpperIndex = getNearestValidTickIndexFromTickIndex(
|
|
317
|
-
priceToTickIndex(priceUpper, pool.
|
|
318
|
-
|
|
359
|
+
priceToTickIndex(priceUpper.toNumber(), pool.tokenA.decimals, pool.tokenB.decimals),
|
|
360
|
+
pool.tickSpacing
|
|
319
361
|
);
|
|
320
362
|
|
|
321
|
-
const apr = estimateAprsForPriceRange(
|
|
363
|
+
const apr = estimateAprsForPriceRange(
|
|
364
|
+
pool,
|
|
365
|
+
tokensPrices,
|
|
366
|
+
fee24Usd,
|
|
367
|
+
tickLowerIndex,
|
|
368
|
+
tickUpperIndex,
|
|
369
|
+
rewardsDecimals
|
|
370
|
+
);
|
|
322
371
|
|
|
323
372
|
const totalApr = new Decimal(apr.fee).add(apr.rewards[0]).add(apr.rewards[1]).add(apr.rewards[2]);
|
|
324
373
|
const feeApr = new Decimal(apr.fee);
|
|
@@ -332,38 +381,26 @@ export class OrcaService {
|
|
|
332
381
|
rewardsApy: rewardsApr.map((x) => aprToApy(x, 365)),
|
|
333
382
|
priceLower,
|
|
334
383
|
priceUpper,
|
|
335
|
-
poolPrice: pool.price,
|
|
384
|
+
poolPrice: new Decimal(pool.price),
|
|
336
385
|
strategyOutOfRange,
|
|
337
386
|
};
|
|
338
387
|
}
|
|
339
388
|
|
|
340
|
-
async getGenericPoolInfo(poolPubkey: Address
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
const poolString = poolPubkey.toString();
|
|
347
|
-
const pool = await orca.getPool(poolPubkey);
|
|
348
|
-
if (!whirlpools) {
|
|
349
|
-
({ whirlpools } = await this.getOrcaWhirlpools());
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const whirlpool = whirlpools?.find((x) => x.address === poolString);
|
|
353
|
-
|
|
354
|
-
if (!pool || !whirlpool) {
|
|
355
|
-
throw Error(`Could not get orca pool data for ${poolString}`);
|
|
389
|
+
async getGenericPoolInfo(poolPubkey: Address) {
|
|
390
|
+
const pool = await this.getOrcaWhirlpool(poolPubkey);
|
|
391
|
+
if (!pool) {
|
|
392
|
+
throw Error(`Could not get orca pool data for ${poolPubkey.toString()}`);
|
|
356
393
|
}
|
|
357
394
|
|
|
358
395
|
const poolInfo: GenericPoolInfo = {
|
|
359
396
|
dex: 'ORCA',
|
|
360
397
|
address: poolPubkey,
|
|
361
|
-
tokenMintA:
|
|
362
|
-
tokenMintB:
|
|
363
|
-
price: pool.price,
|
|
364
|
-
feeRate: pool.
|
|
365
|
-
volumeOnLast7d:
|
|
366
|
-
tvl:
|
|
398
|
+
tokenMintA: address(pool.tokenMintA),
|
|
399
|
+
tokenMintB: address(pool.tokenMintB),
|
|
400
|
+
price: new Decimal(pool.price),
|
|
401
|
+
feeRate: new Decimal(pool.feeRate),
|
|
402
|
+
volumeOnLast7d: pool.stats['7d'] ? new Decimal(pool.stats['7d'].volume) : undefined,
|
|
403
|
+
tvl: pool.tvlUsdc ? new Decimal(pool.tvlUsdc) : undefined,
|
|
367
404
|
tickSpacing: new Decimal(pool.tickSpacing),
|
|
368
405
|
// todo(Silviu): get real amount of positions
|
|
369
406
|
positions: new Decimal(0),
|
|
@@ -1,120 +1,88 @@
|
|
|
1
|
+
//
|
|
2
|
+
// meta: {
|
|
3
|
+
// cursor: {
|
|
4
|
+
// previous: null,
|
|
5
|
+
// next: '5arazDvfFnLxLWEUdX9orQaeYvq6QRehJtAuna9Vu3zHTET'
|
|
6
|
+
// }
|
|
7
|
+
// }
|
|
1
8
|
export interface OrcaWhirlpoolsResponse {
|
|
2
|
-
|
|
3
|
-
|
|
9
|
+
data: Whirlpool[];
|
|
10
|
+
meta: {
|
|
11
|
+
cursor: {
|
|
12
|
+
previous: string | null;
|
|
13
|
+
next: string | null;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
4
16
|
}
|
|
5
17
|
|
|
6
18
|
export interface Whirlpool {
|
|
7
19
|
address: string;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
whitelisted: boolean;
|
|
20
|
+
whirlpoolsConfig: string;
|
|
21
|
+
whirlpoolBump: number[];
|
|
11
22
|
tickSpacing: number;
|
|
12
|
-
|
|
13
|
-
|
|
23
|
+
tickSpacingSeed: number[];
|
|
24
|
+
feeRate: number;
|
|
14
25
|
protocolFeeRate: number;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
liquidity: string;
|
|
27
|
+
sqrtPrice: string;
|
|
28
|
+
tickCurrentIndex: number;
|
|
29
|
+
protocolFeeOwedA: string;
|
|
30
|
+
protocolFeeOwedB: string;
|
|
31
|
+
tokenMintA: string;
|
|
32
|
+
tokenVaultA: string;
|
|
33
|
+
feeGrowthGlobalA: string;
|
|
34
|
+
tokenMintB: string;
|
|
35
|
+
tokenVaultB: string;
|
|
36
|
+
feeGrowthGlobalB: string;
|
|
37
|
+
rewardLastUpdatedTimestamp: string;
|
|
38
|
+
updatedAt: string;
|
|
39
|
+
updatedSlot: number;
|
|
40
|
+
writeVersion: number;
|
|
41
|
+
hasWarning: boolean;
|
|
42
|
+
poolType: string;
|
|
43
|
+
tokenA: WhirlpoolToken;
|
|
44
|
+
tokenB: WhirlpoolToken;
|
|
45
|
+
price: string;
|
|
46
|
+
tvlUsdc: string;
|
|
47
|
+
yieldOverTvl: string;
|
|
48
|
+
tokenBalanceA: string;
|
|
49
|
+
tokenBalanceB: string;
|
|
50
|
+
stats: Stats;
|
|
51
|
+
rewards: WhirlpoolReward[];
|
|
52
|
+
feeTierIndex: number;
|
|
53
|
+
adaptiveFeeEnabled: boolean;
|
|
54
|
+
tradeEnableTimestamp: string;
|
|
27
55
|
}
|
|
28
56
|
|
|
29
|
-
export interface
|
|
30
|
-
|
|
31
|
-
|
|
57
|
+
export interface WhirlpoolToken {
|
|
58
|
+
address: string;
|
|
59
|
+
programId: string;
|
|
60
|
+
imageURL: string;
|
|
32
61
|
name: string;
|
|
33
|
-
decimals: number;
|
|
34
|
-
logoURI?: string;
|
|
35
|
-
coingeckoId?: string;
|
|
36
|
-
whitelisted: boolean;
|
|
37
|
-
poolToken: boolean;
|
|
38
|
-
wrapper?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface TokenB {
|
|
42
|
-
mint: string;
|
|
43
62
|
symbol: string;
|
|
44
|
-
name: string;
|
|
45
63
|
decimals: number;
|
|
46
|
-
|
|
47
|
-
coingeckoId?: string;
|
|
48
|
-
whitelisted: boolean;
|
|
49
|
-
poolToken: boolean;
|
|
50
|
-
wrapper?: string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface Volume {
|
|
54
|
-
day: number;
|
|
55
|
-
week: number;
|
|
56
|
-
month: number;
|
|
64
|
+
tags: string[];
|
|
57
65
|
}
|
|
58
66
|
|
|
59
|
-
export interface
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
export interface WhirlpoolStat {
|
|
68
|
+
volume: string;
|
|
69
|
+
fees: string;
|
|
70
|
+
rewards: string;
|
|
71
|
+
yieldOverTvl: string;
|
|
63
72
|
}
|
|
64
73
|
|
|
65
|
-
export interface
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
export interface Stats {
|
|
75
|
+
'24h': WhirlpoolStat;
|
|
76
|
+
'7d': WhirlpoolStat;
|
|
77
|
+
'30d': WhirlpoolStat;
|
|
69
78
|
}
|
|
70
79
|
|
|
71
|
-
export interface
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
max: number;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface Week {
|
|
83
|
-
min: number;
|
|
84
|
-
max: number;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export interface Month {
|
|
88
|
-
min: number;
|
|
89
|
-
max: number;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export interface FeeApr {
|
|
93
|
-
day: number;
|
|
94
|
-
week: number;
|
|
95
|
-
month: number;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export interface Reward0Apr {
|
|
99
|
-
day: number;
|
|
100
|
-
week?: number;
|
|
101
|
-
month?: number;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export interface Reward1Apr {
|
|
105
|
-
day: number;
|
|
106
|
-
week: number;
|
|
107
|
-
month: number;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export interface Reward2Apr {
|
|
111
|
-
day: number;
|
|
112
|
-
week: number;
|
|
113
|
-
month: number;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export interface TotalApr {
|
|
117
|
-
day: number;
|
|
118
|
-
week?: number;
|
|
119
|
-
month?: number;
|
|
80
|
+
export interface WhirlpoolReward {
|
|
81
|
+
mint: string;
|
|
82
|
+
vault: string;
|
|
83
|
+
authority: string;
|
|
84
|
+
emissions_per_second_x64: string;
|
|
85
|
+
growth_global_x64: string;
|
|
86
|
+
active: boolean;
|
|
87
|
+
emissionsPerSecond: string;
|
|
120
88
|
}
|
|
File without changes
|
package/src/utils/index.ts
CHANGED