@kamino-finance/kliquidity-sdk 8.3.1 → 8.4.0

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.
Files changed (91) hide show
  1. package/dist/Kamino.d.ts +16 -14
  2. package/dist/Kamino.d.ts.map +1 -1
  3. package/dist/Kamino.js +114 -125
  4. package/dist/Kamino.js.map +1 -1
  5. package/dist/constants/numericalValues.d.ts +3 -0
  6. package/dist/constants/numericalValues.d.ts.map +1 -1
  7. package/dist/constants/numericalValues.js +4 -1
  8. package/dist/constants/numericalValues.js.map +1 -1
  9. package/dist/rebalance_methods/autodriftRebalance.d.ts.map +1 -1
  10. package/dist/rebalance_methods/autodriftRebalance.js +10 -10
  11. package/dist/rebalance_methods/autodriftRebalance.js.map +1 -1
  12. package/dist/rebalance_methods/driftRebalance.d.ts.map +1 -1
  13. package/dist/rebalance_methods/driftRebalance.js +10 -10
  14. package/dist/rebalance_methods/driftRebalance.js.map +1 -1
  15. package/dist/rebalance_methods/expanderRebalance.d.ts.map +1 -1
  16. package/dist/rebalance_methods/expanderRebalance.js +3 -4
  17. package/dist/rebalance_methods/expanderRebalance.js.map +1 -1
  18. package/dist/rebalance_methods/pricePercentageRebalance.d.ts.map +1 -1
  19. package/dist/rebalance_methods/pricePercentageRebalance.js +5 -6
  20. package/dist/rebalance_methods/pricePercentageRebalance.js.map +1 -1
  21. package/dist/rebalance_methods/pricePercentageWithResetRebalance.d.ts.map +1 -1
  22. package/dist/rebalance_methods/pricePercentageWithResetRebalance.js +5 -6
  23. package/dist/rebalance_methods/pricePercentageWithResetRebalance.js.map +1 -1
  24. package/dist/rebalance_methods/takeProfitRebalance.d.ts.map +1 -1
  25. package/dist/rebalance_methods/takeProfitRebalance.js +5 -5
  26. package/dist/rebalance_methods/takeProfitRebalance.js.map +1 -1
  27. package/dist/services/JupService.d.ts.map +1 -1
  28. package/dist/services/MeteoraService.d.ts.map +1 -1
  29. package/dist/services/MeteoraService.js +4 -1
  30. package/dist/services/MeteoraService.js.map +1 -1
  31. package/dist/services/OrcaService.d.ts +7 -11
  32. package/dist/services/OrcaService.d.ts.map +1 -1
  33. package/dist/services/OrcaService.js +133 -101
  34. package/dist/services/OrcaService.js.map +1 -1
  35. package/dist/services/OrcaWhirlpoolsResponse.d.ts +63 -92
  36. package/dist/services/OrcaWhirlpoolsResponse.d.ts.map +1 -1
  37. package/dist/services/RaydiumService.d.ts.map +1 -1
  38. package/dist/services/RaydiumService.js +7 -1
  39. package/dist/services/RaydiumService.js.map +1 -1
  40. package/dist/utils/farms.d.ts +1 -0
  41. package/dist/utils/farms.d.ts.map +1 -0
  42. package/dist/utils/farms.js +2 -0
  43. package/dist/utils/farms.js.map +1 -0
  44. package/dist/utils/index.d.ts +1 -0
  45. package/dist/utils/index.d.ts.map +1 -1
  46. package/dist/utils/index.js +1 -0
  47. package/dist/utils/index.js.map +1 -1
  48. package/dist/utils/lookupTable.d.ts.map +1 -1
  49. package/dist/utils/lookupTable.js +1 -1
  50. package/dist/utils/lookupTable.js.map +1 -1
  51. package/dist/utils/orca.d.ts +37 -1
  52. package/dist/utils/orca.d.ts.map +1 -1
  53. package/dist/utils/orca.js +242 -0
  54. package/dist/utils/orca.js.map +1 -1
  55. package/dist/utils/tokenUtils.d.ts +5 -5
  56. package/dist/utils/tokenUtils.d.ts.map +1 -1
  57. package/dist/utils/tokenUtils.js +11 -7
  58. package/dist/utils/tokenUtils.js.map +1 -1
  59. package/dist/utils/transactions.d.ts.map +1 -1
  60. package/dist/utils/types.d.ts +5 -0
  61. package/dist/utils/types.d.ts.map +1 -1
  62. package/dist/utils/types.js.map +1 -1
  63. package/dist/utils/utils.d.ts +0 -1
  64. package/dist/utils/utils.d.ts.map +1 -1
  65. package/dist/utils/utils.js +3 -4
  66. package/dist/utils/utils.js.map +1 -1
  67. package/dist/utils/whirlpools.d.ts +12 -1
  68. package/dist/utils/whirlpools.d.ts.map +1 -1
  69. package/dist/utils/whirlpools.js +30 -29
  70. package/dist/utils/whirlpools.js.map +1 -1
  71. package/package.json +10 -9
  72. package/src/Kamino.ts +271 -215
  73. package/src/constants/numericalValues.ts +5 -0
  74. package/src/rebalance_methods/autodriftRebalance.ts +30 -22
  75. package/src/rebalance_methods/driftRebalance.ts +30 -22
  76. package/src/rebalance_methods/expanderRebalance.ts +7 -4
  77. package/src/rebalance_methods/pricePercentageRebalance.ts +13 -6
  78. package/src/rebalance_methods/pricePercentageWithResetRebalance.ts +13 -6
  79. package/src/rebalance_methods/takeProfitRebalance.ts +13 -5
  80. package/src/services/MeteoraService.ts +5 -2
  81. package/src/services/OrcaService.ts +163 -126
  82. package/src/services/OrcaWhirlpoolsResponse.ts +69 -101
  83. package/src/services/RaydiumService.ts +8 -2
  84. package/src/utils/farms.ts +0 -0
  85. package/src/utils/index.ts +1 -0
  86. package/src/utils/lookupTable.ts +2 -2
  87. package/src/utils/orca.ts +377 -1
  88. package/src/utils/tokenUtils.ts +5 -5
  89. package/src/utils/types.ts +7 -0
  90. package/src/utils/utils.ts +2 -4
  91. package/src/utils/whirlpools.ts +50 -31
@@ -1,22 +1,18 @@
1
- import { Address, Rpc, SolanaRpcApi } from '@solana/kit';
1
+ import { address, Address, Base58EncodedBytes, 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._orcaNetwork = cluster === 'mainnet-beta' ? OrcaNetwork.MAINNET : OrcaNetwork.DEVNET;
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
- async getOrcaWhirlpools() {
56
- return (await axios.get<OrcaWhirlpoolsResponse>(`${this._orcaApiUrl}/v1/whirlpool/list`)).data;
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
- ): Record<string, Decimal> {
72
- const tokensPrices: Record<string, Decimal> = {};
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[mintA] = aPrice.price;
92
- tokensPrices[mintB] = bPrice.price;
143
+ tokensPrices.set(mintA, aPrice.price);
144
+ tokensPrices.set(mintB, bPrice.price);
93
145
  if (reward0Price !== null) {
94
- tokensPrices[reward0] = reward0Price.price;
146
+ tokensPrices.set(reward0, reward0Price.price);
95
147
  }
96
148
  if (reward1Price !== null) {
97
- tokensPrices[reward1] = reward1Price.price;
149
+ tokensPrices.set(reward1, reward1Price.price);
98
150
  }
99
151
  if (reward2Price !== null) {
100
- tokensPrices[reward2] = reward2Price.price;
152
+ tokensPrices.set(reward2, reward2Price.price);
101
153
  }
102
154
 
103
155
  return tokensPrices;
104
156
  }
105
157
 
106
- private getPoolTokensPrices(pool: PoolData, prices: KaminoPrices) {
107
- const tokensPrices: Record<string, Decimal> = {};
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].mint.toString(),
112
- pool.rewards[1].mint.toString(),
113
- pool.rewards[2].mint.toString(),
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[mint] = price;
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 orca.getPool(strategy.pool);
152
- if (!whirlpools) {
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
- ...priceRange,
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.feePercentage;
180
- const volume24hUsd = whirlpool?.volume?.day ?? new Decimal(0);
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
- const totalApr = new Decimal(apr.fee).add(apr.rewards[0]).add(apr.rewards[1]).add(apr.rewards[2]);
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
- ...priceRange,
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 orca = new OrcaWhirlpoolClient({
214
- connection: this._legacyConnection,
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 orca.pool.getLowestInitializedTickArrayTickIndex(pool, poolData.tickSpacing);
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 orca.pool.getHighestInitializedTickArrayTickIndex(pool, poolData.tickSpacing);
283
+ highestInitializedTick = await getHighestInitializedTickArrayTickIndex(this._rpc, pool, whirlpool.tickSpacing);
234
284
  }
235
285
 
236
- const orcaLiqDistribution = await orca.pool.getLiquidityDistribution(
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: poolData.price,
244
- currentTickIndex: poolData.tickCurrentIndex,
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
- whirlpools?: Whirlpool[]
323
+ rewardsDecimals: Map<Address, number>
271
324
  ): Promise<WhirlpoolAprApy> {
272
- const orca = new OrcaWhirlpoolClient({
273
- connection: this._legacyConnection,
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.feePercentage;
308
- const volume24hUsd = whirlpool?.volume?.day ?? new Decimal(0);
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.tokenDecimalsA, pool.tokenDecimalsB),
314
- whirlpool.tickSpacing
355
+ priceToTickIndex(priceLower.toNumber(), pool.tokenA.decimals, pool.tokenB.decimals),
356
+ pool.tickSpacing
315
357
  );
316
358
  const tickUpperIndex = getNearestValidTickIndexFromTickIndex(
317
- priceToTickIndex(priceUpper, pool.tokenDecimalsA, pool.tokenDecimalsB),
318
- whirlpool.tickSpacing
359
+ priceToTickIndex(priceUpper.toNumber(), pool.tokenA.decimals, pool.tokenB.decimals),
360
+ pool.tickSpacing
319
361
  );
320
362
 
321
- const apr = estimateAprsForPriceRange(pool, tokensPrices, fee24Usd, tickLowerIndex, tickUpperIndex);
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, whirlpools?: Whirlpool[]) {
341
- const orca = new OrcaWhirlpoolClient({
342
- connection: this._legacyConnection,
343
- network: this._orcaNetwork,
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: fromLegacyPublicKey(pool.tokenMintA),
362
- tokenMintB: fromLegacyPublicKey(pool.tokenMintB),
363
- price: pool.price,
364
- feeRate: pool.feePercentage,
365
- volumeOnLast7d: whirlpool.volume ? new Decimal(whirlpool.volume?.week) : undefined,
366
- tvl: whirlpool.tvl ? new Decimal(whirlpool.tvl) : undefined,
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),
@@ -378,7 +415,7 @@ export class OrcaService {
378
415
  filters: [
379
416
  // account LAYOUT: https://github.com/orca-so/whirlpools/blob/main/programs/whirlpool/src/state/position.rs#L20
380
417
  { dataSize: 216n },
381
- { memcmp: { bytes: pool, offset: 8n, encoding: 'base58' } },
418
+ { memcmp: { bytes: pool.toString() as Base58EncodedBytes, offset: 8n, encoding: 'base58' } },
382
419
  ],
383
420
  })
384
421
  .send();
@@ -1,120 +1,88 @@
1
+ //
2
+ // meta: {
3
+ // cursor: {
4
+ // previous: null,
5
+ // next: '5arazDvfFnLxLWEUdX9orQaeYvq6QRehJtAuna9Vu3zHTET'
6
+ // }
7
+ // }
1
8
  export interface OrcaWhirlpoolsResponse {
2
- whirlpools: Whirlpool[];
3
- hasMore: boolean;
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
- tokenA: TokenA;
9
- tokenB: TokenB;
10
- whitelisted: boolean;
20
+ whirlpoolsConfig: string;
21
+ whirlpoolBump: number[];
11
22
  tickSpacing: number;
12
- price: number;
13
- lpFeeRate: number;
23
+ tickSpacingSeed: number[];
24
+ feeRate: number;
14
25
  protocolFeeRate: number;
15
- whirlpoolsConfig: string;
16
- modifiedTimeMs?: number;
17
- tvl?: number;
18
- volume?: Volume;
19
- volumeDenominatedA?: VolumeDenominatedA;
20
- volumeDenominatedB?: VolumeDenominatedB;
21
- priceRange?: PriceRange;
22
- feeApr?: FeeApr;
23
- reward0Apr?: Reward0Apr;
24
- reward1Apr?: Reward1Apr;
25
- reward2Apr?: Reward2Apr;
26
- totalApr?: TotalApr;
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 TokenA {
30
- mint: string;
31
- symbol: string;
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
- logoURI: string;
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 VolumeDenominatedA {
60
- day: number;
61
- week: number;
62
- month: number;
67
+ export interface WhirlpoolStat {
68
+ volume: string;
69
+ fees: string;
70
+ rewards: string;
71
+ yieldOverTvl: string;
63
72
  }
64
73
 
65
- export interface VolumeDenominatedB {
66
- day: number;
67
- week: number;
68
- month: number;
74
+ export interface Stats {
75
+ '24h': WhirlpoolStat;
76
+ '7d': WhirlpoolStat;
77
+ '30d': WhirlpoolStat;
69
78
  }
70
79
 
71
- export interface PriceRange {
72
- day: Day;
73
- week: Week;
74
- month: Month;
75
- }
76
-
77
- export interface Day {
78
- min: number;
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
  }