@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
package/src/utils/orca.ts
CHANGED
|
@@ -1,8 +1,63 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BN } from '@coral-xyz/anchor';
|
|
2
|
+
import {
|
|
3
|
+
address,
|
|
4
|
+
Address,
|
|
5
|
+
GetAccountInfoApi,
|
|
6
|
+
getAddressEncoder,
|
|
7
|
+
GetMultipleAccountsApi,
|
|
8
|
+
getProgramDerivedAddress,
|
|
9
|
+
Rpc,
|
|
10
|
+
} from '@solana/kit';
|
|
2
11
|
import { ProgramDerivedAddress } from '@solana/addresses/dist/types/program-derived-address';
|
|
12
|
+
import { ONE_BN, U64_MAX, ZERO_BN } from '../constants';
|
|
13
|
+
import { Percentage } from './types';
|
|
14
|
+
import { DEFAULT_ADDRESS, IncreaseLiquidityQuoteParam } from '@orca-so/whirlpools';
|
|
15
|
+
import { TickArray, Whirlpool } from '../@codegen/whirlpools/accounts';
|
|
16
|
+
import {
|
|
17
|
+
_MAX_TICK_INDEX,
|
|
18
|
+
_MIN_TICK_INDEX,
|
|
19
|
+
getTickArrayStartTickIndex,
|
|
20
|
+
IncreaseLiquidityQuote,
|
|
21
|
+
tickIndexToPrice,
|
|
22
|
+
TransferFee,
|
|
23
|
+
} from '@orca-so/whirlpools-core';
|
|
24
|
+
import { increaseLiquidityQuoteA, increaseLiquidityQuoteB, increaseLiquidityQuote } from '@orca-so/whirlpools-core';
|
|
25
|
+
import { Whirlpool as WhirlpoolAPIResponse, WhirlpoolReward } from '../services/OrcaWhirlpoolsResponse';
|
|
26
|
+
import Decimal from 'decimal.js/decimal';
|
|
27
|
+
import { getRemoveLiquidityQuote } from './whirlpools';
|
|
28
|
+
import { FullBPS } from './CreationParameters';
|
|
29
|
+
import { fetchMaybeTickArray, getTickArrayAddress } from '@orca-so/whirlpools-client';
|
|
30
|
+
import { sleep } from './utils';
|
|
31
|
+
import { ZERO } from './math';
|
|
3
32
|
|
|
33
|
+
export const defaultSlippagePercentageBPS = 10;
|
|
4
34
|
const addressEncoder = getAddressEncoder();
|
|
5
35
|
|
|
36
|
+
const TICK_ARRAY_SIZE = 88;
|
|
37
|
+
|
|
38
|
+
const SECONDS_PER_YEAR =
|
|
39
|
+
60 * // SECONDS
|
|
40
|
+
60 * // MINUTES
|
|
41
|
+
24 * // HOURS
|
|
42
|
+
365; // DAYS
|
|
43
|
+
|
|
44
|
+
function estimateRewardApr(
|
|
45
|
+
reward: WhirlpoolReward,
|
|
46
|
+
rewardTokenDecimals: number,
|
|
47
|
+
concentratedValue: Decimal,
|
|
48
|
+
tokenPrices: Map<Address, Decimal>
|
|
49
|
+
) {
|
|
50
|
+
const { mint } = reward;
|
|
51
|
+
const rewardTokenPrice = tokenPrices.get(address(mint));
|
|
52
|
+
const emissionsPerSecond = new Decimal(reward.emissionsPerSecond).div(new Decimal(10).pow(rewardTokenDecimals));
|
|
53
|
+
if (emissionsPerSecond.eq(ZERO) || !rewardTokenPrice) {
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const res = emissionsPerSecond.mul(SECONDS_PER_YEAR).mul(rewardTokenPrice).div(concentratedValue).toNumber();
|
|
58
|
+
return res;
|
|
59
|
+
}
|
|
60
|
+
|
|
6
61
|
export async function getTickArray(
|
|
7
62
|
programId: Address,
|
|
8
63
|
whirlpoolAddress: Address,
|
|
@@ -13,3 +68,324 @@ export async function getTickArray(
|
|
|
13
68
|
programAddress: programId,
|
|
14
69
|
});
|
|
15
70
|
}
|
|
71
|
+
|
|
72
|
+
export function getTokenAFromLiquidity(liquidity: BN, sqrtPrice0X64: BN, sqrtPrice1X64: BN, roundUp: boolean) {
|
|
73
|
+
const [sqrtPriceLowerX64, sqrtPriceUpperX64] = orderSqrtPrice(sqrtPrice0X64, sqrtPrice1X64);
|
|
74
|
+
|
|
75
|
+
const numerator = liquidity.mul(sqrtPriceUpperX64.sub(sqrtPriceLowerX64)).shln(64);
|
|
76
|
+
const denominator = sqrtPriceUpperX64.mul(sqrtPriceLowerX64);
|
|
77
|
+
if (roundUp) {
|
|
78
|
+
return divRoundUp(numerator, denominator);
|
|
79
|
+
} else {
|
|
80
|
+
return numerator.div(denominator);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function getTokenBFromLiquidity(liquidity: BN, sqrtPrice0X64: BN, sqrtPrice1X64: BN, roundUp: boolean) {
|
|
85
|
+
const [sqrtPriceLowerX64, sqrtPriceUpperX64] = orderSqrtPrice(sqrtPrice0X64, sqrtPrice1X64);
|
|
86
|
+
|
|
87
|
+
const result = liquidity.mul(sqrtPriceUpperX64.sub(sqrtPriceLowerX64));
|
|
88
|
+
if (roundUp) {
|
|
89
|
+
return shiftRightRoundUp(result);
|
|
90
|
+
} else {
|
|
91
|
+
return result.shrn(64);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function getNearestValidTickIndexFromTickIndex(tickIndex: number, tickSpacing: number): number {
|
|
96
|
+
return tickIndex - (tickIndex % tickSpacing);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getIncreaseLiquidityQuote(
|
|
100
|
+
param: IncreaseLiquidityQuoteParam,
|
|
101
|
+
pool: Whirlpool,
|
|
102
|
+
tickLowerIndex: number,
|
|
103
|
+
tickUpperIndex: number,
|
|
104
|
+
slippageToleranceBps: number,
|
|
105
|
+
transferFeeA: TransferFee | undefined,
|
|
106
|
+
transferFeeB: TransferFee | undefined
|
|
107
|
+
): IncreaseLiquidityQuote {
|
|
108
|
+
if ('liquidity' in param) {
|
|
109
|
+
return increaseLiquidityQuote(
|
|
110
|
+
param.liquidity,
|
|
111
|
+
slippageToleranceBps,
|
|
112
|
+
BigInt(pool.sqrtPrice.toString()),
|
|
113
|
+
tickLowerIndex,
|
|
114
|
+
tickUpperIndex,
|
|
115
|
+
transferFeeA,
|
|
116
|
+
transferFeeB
|
|
117
|
+
);
|
|
118
|
+
} else if ('tokenA' in param) {
|
|
119
|
+
return increaseLiquidityQuoteA(
|
|
120
|
+
param.tokenA,
|
|
121
|
+
slippageToleranceBps,
|
|
122
|
+
BigInt(pool.sqrtPrice.toString()),
|
|
123
|
+
tickLowerIndex,
|
|
124
|
+
tickUpperIndex,
|
|
125
|
+
transferFeeA,
|
|
126
|
+
transferFeeB
|
|
127
|
+
);
|
|
128
|
+
} else {
|
|
129
|
+
return increaseLiquidityQuoteB(
|
|
130
|
+
param.tokenB,
|
|
131
|
+
slippageToleranceBps,
|
|
132
|
+
BigInt(pool.sqrtPrice.toString()),
|
|
133
|
+
tickLowerIndex,
|
|
134
|
+
tickUpperIndex,
|
|
135
|
+
transferFeeA,
|
|
136
|
+
transferFeeB
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function orderSqrtPrice(sqrtPrice0X64: BN, sqrtPrice1X64: BN): [BN, BN] {
|
|
142
|
+
if (sqrtPrice0X64.lt(sqrtPrice1X64)) {
|
|
143
|
+
return [sqrtPrice0X64, sqrtPrice1X64];
|
|
144
|
+
} else {
|
|
145
|
+
return [sqrtPrice1X64, sqrtPrice0X64];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function shiftRightRoundUp(n: BN): BN {
|
|
150
|
+
let result = n.shrn(64);
|
|
151
|
+
|
|
152
|
+
if (n.mod(new BN(U64_MAX)).gt(ZERO_BN)) {
|
|
153
|
+
result = result.add(ONE_BN);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function divRoundUp(n0: BN, n1: BN): BN {
|
|
160
|
+
const hasRemainder = !n0.mod(n1).eq(ZERO_BN);
|
|
161
|
+
if (hasRemainder) {
|
|
162
|
+
return n0.div(n1).add(ONE_BN);
|
|
163
|
+
} else {
|
|
164
|
+
return n0.div(n1);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function adjustForSlippage(n: BN, { numerator, denominator }: Percentage, adjustUp: boolean): BN {
|
|
169
|
+
if (adjustUp) {
|
|
170
|
+
return n.mul(denominator.add(numerator)).div(denominator);
|
|
171
|
+
} else {
|
|
172
|
+
return n.mul(denominator).div(denominator.add(numerator));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export type EstimatedAprs = {
|
|
177
|
+
fee: number;
|
|
178
|
+
rewards: number[];
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const ZERO_APR = {
|
|
182
|
+
fee: 0,
|
|
183
|
+
rewards: [0, 0, 0],
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export function estimateAprsForPriceRange(
|
|
187
|
+
pool: WhirlpoolAPIResponse,
|
|
188
|
+
tokenPrices: Map<Address, Decimal>,
|
|
189
|
+
fees24h: number,
|
|
190
|
+
tickLowerIndex: number,
|
|
191
|
+
tickUpperIndex: number,
|
|
192
|
+
rewardsDecimals: Map<Address, number>
|
|
193
|
+
): EstimatedAprs {
|
|
194
|
+
const tokenPriceA = tokenPrices.get(address(pool.tokenMintA));
|
|
195
|
+
const tokenPriceB = tokenPrices.get(address(pool.tokenMintB));
|
|
196
|
+
|
|
197
|
+
if (!fees24h || !tokenPriceA || !tokenPriceB || tickLowerIndex >= tickUpperIndex) {
|
|
198
|
+
return ZERO_APR;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Value of liquidity if the entire liquidity were concentrated between tickLower/Upper
|
|
202
|
+
// Since this is virtual liquidity, concentratedValue should actually be less than totalValue
|
|
203
|
+
const { minTokenA, minTokenB } = getRemoveLiquidityQuote({
|
|
204
|
+
positionAddress: DEFAULT_ADDRESS,
|
|
205
|
+
tickCurrentIndex: pool.tickCurrentIndex,
|
|
206
|
+
sqrtPrice: new BN(pool.sqrtPrice.toString()),
|
|
207
|
+
tickLowerIndex,
|
|
208
|
+
tickUpperIndex,
|
|
209
|
+
liquidity: new BN(pool.liquidity.toString()),
|
|
210
|
+
slippageTolerance: { numerator: ZERO_BN, denominator: new BN(FullBPS) },
|
|
211
|
+
});
|
|
212
|
+
const tokenValueA = getTokenValue(minTokenA, pool.tokenA.decimals, tokenPriceA);
|
|
213
|
+
const tokenValueB = getTokenValue(minTokenB, pool.tokenB.decimals, tokenPriceB);
|
|
214
|
+
const concentratedValue = tokenValueA.add(tokenValueB);
|
|
215
|
+
|
|
216
|
+
const feesPerYear = new Decimal(fees24h).mul(365).div(new Decimal(10).pow(6)); // scale from lamports of USDC to tokens
|
|
217
|
+
const feeApr = feesPerYear.div(concentratedValue).toNumber();
|
|
218
|
+
|
|
219
|
+
const rewards = pool.rewards.map((reward) => {
|
|
220
|
+
if (rewardsDecimals.has(address(reward.mint))) {
|
|
221
|
+
return estimateRewardApr(reward, rewardsDecimals.get(address(reward.mint))!, concentratedValue, tokenPrices);
|
|
222
|
+
} else {
|
|
223
|
+
return 0;
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
return { fee: feeApr, rewards };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function getTokenValue(tokenAmount: BN, decimals: number, tokenPrice: Decimal): Decimal {
|
|
231
|
+
return tokenPrice.mul(new Decimal(tokenAmount.toString()).div(new Decimal(10).pow(decimals)));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function getLowestInitializedTickArrayTickIndex(
|
|
235
|
+
rpc: Rpc<GetAccountInfoApi>,
|
|
236
|
+
whirlpoolAddress: Address,
|
|
237
|
+
tickSpacing: number
|
|
238
|
+
): Promise<number> {
|
|
239
|
+
const minTick = _MIN_TICK_INDEX();
|
|
240
|
+
let startTickIndex = getTickArrayStartTickIndex(minTick, tickSpacing);
|
|
241
|
+
|
|
242
|
+
// eslint-disable-next-line
|
|
243
|
+
while (true) {
|
|
244
|
+
const [tickArrayAddress] = await getTickArrayAddress(whirlpoolAddress, startTickIndex);
|
|
245
|
+
const tickArray = await fetchMaybeTickArray(rpc, tickArrayAddress);
|
|
246
|
+
|
|
247
|
+
if (tickArray.exists) {
|
|
248
|
+
return startTickIndex;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
startTickIndex += TICK_ARRAY_SIZE * tickSpacing;
|
|
252
|
+
await sleep(500);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export async function getHighestInitializedTickArrayTickIndex(
|
|
257
|
+
rpc: Rpc<GetAccountInfoApi>,
|
|
258
|
+
whirlpoolAddress: Address,
|
|
259
|
+
tickSpacing: number
|
|
260
|
+
): Promise<number> {
|
|
261
|
+
const maxTick = _MAX_TICK_INDEX();
|
|
262
|
+
let startTickIndex = getTickArrayStartTickIndex(maxTick, tickSpacing);
|
|
263
|
+
|
|
264
|
+
// eslint-disable-next-line
|
|
265
|
+
while (true) {
|
|
266
|
+
const [tickArrayAddress] = await getTickArrayAddress(whirlpoolAddress, startTickIndex);
|
|
267
|
+
const tickArray = await fetchMaybeTickArray(rpc, tickArrayAddress);
|
|
268
|
+
|
|
269
|
+
if (tickArray.exists) {
|
|
270
|
+
return startTickIndex;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
startTickIndex -= TICK_ARRAY_SIZE * tickSpacing;
|
|
274
|
+
await sleep(500);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export type LiquidityDataPoint = {
|
|
279
|
+
liquidity: Decimal;
|
|
280
|
+
price: Decimal;
|
|
281
|
+
tickIndex: number;
|
|
282
|
+
};
|
|
283
|
+
export type WhirlpoolLiquidityDistribution = {
|
|
284
|
+
currentPrice: Decimal;
|
|
285
|
+
currentTickIndex: number;
|
|
286
|
+
datapoints: LiquidityDataPoint[];
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
export async function getLiquidityDistribution(
|
|
290
|
+
rpc: Rpc<GetMultipleAccountsApi>,
|
|
291
|
+
poolAddress: Address,
|
|
292
|
+
poolData: WhirlpoolAPIResponse,
|
|
293
|
+
tickLower: number,
|
|
294
|
+
tickUpper: number,
|
|
295
|
+
whirlpoolProgramId: Address
|
|
296
|
+
): Promise<WhirlpoolLiquidityDistribution> {
|
|
297
|
+
const datapoints: LiquidityDataPoint[] = [];
|
|
298
|
+
|
|
299
|
+
const tokenDecimalsA = poolData.tokenA.decimals;
|
|
300
|
+
const tokenDecimalsB = poolData.tokenB.decimals;
|
|
301
|
+
|
|
302
|
+
const tickArrayAddresses = await getSurroundingTickArrayAddresses(
|
|
303
|
+
poolAddress,
|
|
304
|
+
poolData,
|
|
305
|
+
tickLower,
|
|
306
|
+
tickUpper,
|
|
307
|
+
whirlpoolProgramId
|
|
308
|
+
);
|
|
309
|
+
const tickArrays = await TickArray.fetchMultiple(rpc, tickArrayAddresses, whirlpoolProgramId);
|
|
310
|
+
|
|
311
|
+
const currentLiquidity = new Decimal(poolData.liquidity.toString());
|
|
312
|
+
let relativeLiquidity = currentLiquidity;
|
|
313
|
+
let minLiquidity = new Decimal(0);
|
|
314
|
+
let liquidity = new Decimal(0);
|
|
315
|
+
|
|
316
|
+
tickArrays.forEach((tickArray) => {
|
|
317
|
+
if (!tickArray) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const startIndex = tickArray.startTickIndex;
|
|
322
|
+
tickArray.ticks.forEach((tick, index) => {
|
|
323
|
+
const tickIndex = startIndex + index * poolData.tickSpacing;
|
|
324
|
+
const price = tickIndexToPrice(tickIndex, tokenDecimalsA, tokenDecimalsB);
|
|
325
|
+
const liquidityNet = new Decimal(tick.liquidityNet.toString());
|
|
326
|
+
liquidity = liquidity.add(liquidityNet);
|
|
327
|
+
datapoints.push({ liquidity: new Decimal(liquidity), price: new Decimal(price), tickIndex });
|
|
328
|
+
|
|
329
|
+
minLiquidity = liquidity.lt(minLiquidity) ? liquidity : minLiquidity;
|
|
330
|
+
|
|
331
|
+
if (tickIndex === poolData.tickCurrentIndex) {
|
|
332
|
+
relativeLiquidity = relativeLiquidity.sub(liquidityNet);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
if (!relativeLiquidity.eq(currentLiquidity)) {
|
|
338
|
+
minLiquidity = minLiquidity.add(relativeLiquidity);
|
|
339
|
+
datapoints.forEach((datapoint) => {
|
|
340
|
+
datapoint.liquidity = datapoint.liquidity.add(relativeLiquidity);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (minLiquidity.lt(0)) {
|
|
345
|
+
datapoints.forEach((datapoint) => {
|
|
346
|
+
datapoint.liquidity = datapoint.liquidity.add(minLiquidity.neg());
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
currentPrice: new Decimal(poolData.price),
|
|
352
|
+
currentTickIndex: poolData.tickCurrentIndex,
|
|
353
|
+
datapoints,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function getSurroundingTickArrayAddresses(
|
|
358
|
+
poolAddress: Address,
|
|
359
|
+
pool: WhirlpoolAPIResponse,
|
|
360
|
+
tickLower: number,
|
|
361
|
+
tickUpper: number,
|
|
362
|
+
programId: Address
|
|
363
|
+
): Promise<Address[]> {
|
|
364
|
+
const tickArrayAddresses: Address[] = [];
|
|
365
|
+
|
|
366
|
+
let startIndex = getTickArrayStartTickIndex(tickLower, pool.tickSpacing);
|
|
367
|
+
while (startIndex <= tickUpper) {
|
|
368
|
+
const [address, _bump] = await getTickArrayPda(programId, poolAddress, startIndex);
|
|
369
|
+
tickArrayAddresses.push(address);
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
startIndex = getTickArrayStartTickIndex(startIndex, pool.tickSpacing);
|
|
373
|
+
} catch (_e) {
|
|
374
|
+
return tickArrayAddresses;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return tickArrayAddresses;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export async function getTickArrayPda(
|
|
381
|
+
programId: Address,
|
|
382
|
+
poolAddress: Address,
|
|
383
|
+
startIndex: number
|
|
384
|
+
): Promise<[Address, number]> {
|
|
385
|
+
const pdaWithBump = await getProgramDerivedAddress({
|
|
386
|
+
seeds: [Buffer.from('tick_array'), addressEncoder.encode(poolAddress), Buffer.from(startIndex.toString())],
|
|
387
|
+
programAddress: programId,
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
return [pdaWithBump[0], pdaWithBump[1]];
|
|
391
|
+
}
|
package/src/utils/tokenUtils.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Address, IInstruction, address, TransactionSigner, Rpc, GetAccountInfoApi, Account } from '@solana/kit';
|
|
2
2
|
import { WhirlpoolStrategy } from '../@codegen/kliquidity/accounts';
|
|
3
|
-
import { tickIndexToPrice } from '@orca-so/whirlpool-sdk';
|
|
4
3
|
import Decimal from 'decimal.js';
|
|
5
4
|
import { CollateralInfo } from '../@codegen/kliquidity/types';
|
|
6
5
|
import { getPriceOfBinByBinIdWithDecimals } from './meteora';
|
|
@@ -15,6 +14,7 @@ import {
|
|
|
15
14
|
} from '@solana-program/token-2022';
|
|
16
15
|
import { SYSTEM_PROGRAM_ADDRESS } from '@solana-program/system';
|
|
17
16
|
import { getSetComputeUnitLimitInstruction } from '@solana-program/compute-budget';
|
|
17
|
+
import { tickIndexToPrice } from '@orca-so/whirlpools-core';
|
|
18
18
|
|
|
19
19
|
export const SOL_MINTS = [
|
|
20
20
|
address('So11111111111111111111111111111111111111111'),
|
|
@@ -52,7 +52,7 @@ export async function getAssociatedTokenAddress(
|
|
|
52
52
|
|
|
53
53
|
export function createAssociatedTokenAccountInstruction(
|
|
54
54
|
payer: TransactionSigner,
|
|
55
|
-
|
|
55
|
+
associatedTokenAddress: Address,
|
|
56
56
|
owner: Address,
|
|
57
57
|
mint: Address,
|
|
58
58
|
programId: Address = TOKEN_PROGRAM_ADDRESS,
|
|
@@ -62,7 +62,7 @@ export function createAssociatedTokenAccountInstruction(
|
|
|
62
62
|
{
|
|
63
63
|
mint,
|
|
64
64
|
owner,
|
|
65
|
-
ata:
|
|
65
|
+
ata: associatedTokenAddress,
|
|
66
66
|
payer: payer,
|
|
67
67
|
tokenProgram: programId,
|
|
68
68
|
systemProgram: SYSTEM_PROGRAM_ADDRESS,
|
|
@@ -99,9 +99,9 @@ export function getStrategyPriceRangeRaydium(
|
|
|
99
99
|
tokenBDecimals: number
|
|
100
100
|
) {
|
|
101
101
|
const { priceLower, priceUpper } = getPriceLowerUpper(tickLowerIndex, tickUpperIndex, tokenADecimals, tokenBDecimals);
|
|
102
|
-
const poolPrice = tickIndexToPrice(tickCurrent, tokenADecimals, tokenBDecimals);
|
|
102
|
+
const poolPrice = new Decimal(tickIndexToPrice(tickCurrent, tokenADecimals, tokenBDecimals));
|
|
103
103
|
const strategyOutOfRange = poolPrice.lt(priceLower) || poolPrice.gt(priceUpper);
|
|
104
|
-
return { priceLower, poolPrice, priceUpper, strategyOutOfRange };
|
|
104
|
+
return { priceLower: new Decimal(priceLower), poolPrice, priceUpper: new Decimal(priceUpper), strategyOutOfRange };
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
export function getStrategyPriceRangeMeteora(
|
package/src/utils/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { BN } from '@coral-xyz/anchor';
|
|
2
|
+
|
|
1
3
|
import { address, Address, IInstruction, TransactionMessage, TransactionSigner } from '@solana/kit';
|
|
2
4
|
import { WhirlpoolStrategy } from '../@codegen/kliquidity/accounts';
|
|
3
5
|
import { Dex, collToLamportsDecimal } from './utils';
|
|
@@ -310,3 +312,8 @@ export interface InitPoolTickIfNeeded {
|
|
|
310
312
|
tick: Address;
|
|
311
313
|
initTickIx: IInstruction | undefined;
|
|
312
314
|
}
|
|
315
|
+
|
|
316
|
+
export type Percentage = {
|
|
317
|
+
numerator: BN;
|
|
318
|
+
denominator: BN;
|
|
319
|
+
};
|
package/src/utils/utils.ts
CHANGED
|
@@ -19,19 +19,17 @@ import {
|
|
|
19
19
|
import { RebalanceFieldInfo, RebalanceFieldsDict } from './types';
|
|
20
20
|
import BN from 'bn.js';
|
|
21
21
|
import { PoolPriceReferenceType, TwapPriceReferenceType } from './priceReferenceTypes';
|
|
22
|
-
import { sqrtPriceX64ToPrice } from '@orca-so/whirlpool-sdk';
|
|
23
22
|
import { U64_MAX } from '../constants/numericalValues';
|
|
24
23
|
import { SqrtPriceMath } from '@raydium-io/raydium-sdk-v2/lib/raydium/clmm/utils/math';
|
|
25
24
|
import { DEFAULT_PUBLIC_KEY } from '../constants/pubkeys';
|
|
26
25
|
import { SYSTEM_PROGRAM_ADDRESS } from '@solana-program/system';
|
|
26
|
+
import { sqrtPriceToPrice as orcaSqrtPriceToPrice } from '@orca-so/whirlpools-core';
|
|
27
27
|
|
|
28
28
|
export const DollarBasedMintingMethod = new Decimal(0);
|
|
29
29
|
export const ProportionalMintingMethod = new Decimal(1);
|
|
30
30
|
|
|
31
31
|
export const RebalanceParamOffset = new Decimal(256);
|
|
32
32
|
|
|
33
|
-
export const ZERO_BN = new BN(0);
|
|
34
|
-
|
|
35
33
|
export function sleep(ms: number) {
|
|
36
34
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
37
35
|
}
|
|
@@ -272,7 +270,7 @@ export function isVaultInitialized(vault: Address, decimals: BN): boolean {
|
|
|
272
270
|
export function sqrtPriceToPrice(sqrtPrice: BN, dexNo: number, decimalsA: number, decimalsB: number): Decimal {
|
|
273
271
|
const dex = numberToDex(dexNo);
|
|
274
272
|
if (dex == 'ORCA') {
|
|
275
|
-
return
|
|
273
|
+
return new Decimal(orcaSqrtPriceToPrice(BigInt(sqrtPrice.toString()), decimalsA, decimalsB));
|
|
276
274
|
}
|
|
277
275
|
if (dex == 'RAYDIUM') {
|
|
278
276
|
return SqrtPriceMath.sqrtPriceX64ToPrice(sqrtPrice, decimalsA, decimalsB);
|
package/src/utils/whirlpools.ts
CHANGED
|
@@ -2,19 +2,13 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Added roundUp flag to accurately estimate token holdings for deposits
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
import { BN } from '@coral-xyz/anchor';
|
|
7
7
|
import { Address } from '@solana/kit';
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Percentage,
|
|
13
|
-
PositionStatus,
|
|
14
|
-
PositionUtil,
|
|
15
|
-
RemoveLiquidityQuote,
|
|
16
|
-
} from '@orca-so/whirlpool-sdk';
|
|
17
|
-
import { ZERO_BN } from './utils';
|
|
8
|
+
import { positionStatus, tickIndexToSqrtPrice } from '@orca-so/whirlpools-core';
|
|
9
|
+
import { ZERO_BN } from '../constants/numericalValues';
|
|
10
|
+
import { adjustForSlippage, getTokenAFromLiquidity, getTokenBFromLiquidity } from './orca';
|
|
11
|
+
import { Percentage } from './types';
|
|
18
12
|
|
|
19
13
|
export type InternalRemoveLiquidityQuoteParam = {
|
|
20
14
|
positionAddress: Address;
|
|
@@ -30,21 +24,17 @@ export function getRemoveLiquidityQuote(
|
|
|
30
24
|
param: InternalRemoveLiquidityQuoteParam,
|
|
31
25
|
roundUp: boolean = false
|
|
32
26
|
): RemoveLiquidityQuote {
|
|
33
|
-
const
|
|
34
|
-
param.tickCurrentIndex,
|
|
35
|
-
param.tickLowerIndex,
|
|
36
|
-
param.tickUpperIndex
|
|
37
|
-
);
|
|
27
|
+
const posStatus = positionStatus(BigInt(param.sqrtPrice.toString()), param.tickLowerIndex, param.tickUpperIndex);
|
|
38
28
|
|
|
39
|
-
switch (
|
|
40
|
-
case
|
|
29
|
+
switch (posStatus) {
|
|
30
|
+
case 'priceBelowRange':
|
|
41
31
|
return getRemoveLiquidityQuoteWhenPositionIsBelowRange(param, roundUp);
|
|
42
|
-
case
|
|
32
|
+
case 'priceInRange':
|
|
43
33
|
return getRemoveLiquidityQuoteWhenPositionIsInRange(param, roundUp);
|
|
44
|
-
case
|
|
34
|
+
case 'priceAboveRange':
|
|
45
35
|
return getRemoveLiquidityQuoteWhenPositionIsAboveRange(param, roundUp);
|
|
46
36
|
default:
|
|
47
|
-
throw new Error(`type ${
|
|
37
|
+
throw new Error(`type ${posStatus} is an unknown PositionStatus`);
|
|
48
38
|
}
|
|
49
39
|
}
|
|
50
40
|
|
|
@@ -54,10 +44,15 @@ function getRemoveLiquidityQuoteWhenPositionIsBelowRange(
|
|
|
54
44
|
): RemoveLiquidityQuote {
|
|
55
45
|
const { positionAddress, tickLowerIndex, tickUpperIndex, liquidity, slippageTolerance } = param;
|
|
56
46
|
|
|
57
|
-
const sqrtPriceLowerX64 =
|
|
58
|
-
const sqrtPriceUpperX64 =
|
|
47
|
+
const sqrtPriceLowerX64 = tickIndexToSqrtPrice(tickLowerIndex);
|
|
48
|
+
const sqrtPriceUpperX64 = tickIndexToSqrtPrice(tickUpperIndex);
|
|
59
49
|
|
|
60
|
-
const estTokenA = getTokenAFromLiquidity(
|
|
50
|
+
const estTokenA = getTokenAFromLiquidity(
|
|
51
|
+
liquidity,
|
|
52
|
+
new BN(sqrtPriceLowerX64.toString()),
|
|
53
|
+
new BN(sqrtPriceUpperX64.toString()),
|
|
54
|
+
roundUp
|
|
55
|
+
);
|
|
61
56
|
const minTokenA = adjustForSlippage(estTokenA, slippageTolerance, roundUp);
|
|
62
57
|
|
|
63
58
|
return {
|
|
@@ -77,13 +72,23 @@ function getRemoveLiquidityQuoteWhenPositionIsInRange(
|
|
|
77
72
|
const { positionAddress, sqrtPrice, tickLowerIndex, tickUpperIndex, liquidity, slippageTolerance } = param;
|
|
78
73
|
|
|
79
74
|
const sqrtPriceX64 = sqrtPrice;
|
|
80
|
-
const sqrtPriceLowerX64 =
|
|
81
|
-
const sqrtPriceUpperX64 =
|
|
75
|
+
const sqrtPriceLowerX64 = tickIndexToSqrtPrice(tickLowerIndex);
|
|
76
|
+
const sqrtPriceUpperX64 = tickIndexToSqrtPrice(tickUpperIndex);
|
|
82
77
|
|
|
83
|
-
const estTokenA = getTokenAFromLiquidity(
|
|
78
|
+
const estTokenA = getTokenAFromLiquidity(
|
|
79
|
+
liquidity,
|
|
80
|
+
new BN(sqrtPriceX64.toString()),
|
|
81
|
+
new BN(sqrtPriceUpperX64.toString()),
|
|
82
|
+
roundUp
|
|
83
|
+
);
|
|
84
84
|
const minTokenA = adjustForSlippage(estTokenA, slippageTolerance, roundUp);
|
|
85
85
|
|
|
86
|
-
const estTokenB = getTokenBFromLiquidity(
|
|
86
|
+
const estTokenB = getTokenBFromLiquidity(
|
|
87
|
+
liquidity,
|
|
88
|
+
new BN(sqrtPriceLowerX64.toString()),
|
|
89
|
+
new BN(sqrtPriceX64.toString()),
|
|
90
|
+
roundUp
|
|
91
|
+
);
|
|
87
92
|
const minTokenB = adjustForSlippage(estTokenB, slippageTolerance, roundUp);
|
|
88
93
|
|
|
89
94
|
return {
|
|
@@ -102,10 +107,15 @@ function getRemoveLiquidityQuoteWhenPositionIsAboveRange(
|
|
|
102
107
|
): RemoveLiquidityQuote {
|
|
103
108
|
const { positionAddress, tickLowerIndex, tickUpperIndex, liquidity, slippageTolerance: slippageTolerance } = param;
|
|
104
109
|
|
|
105
|
-
const sqrtPriceLowerX64 =
|
|
106
|
-
const sqrtPriceUpperX64 =
|
|
110
|
+
const sqrtPriceLowerX64 = tickIndexToSqrtPrice(tickLowerIndex);
|
|
111
|
+
const sqrtPriceUpperX64 = tickIndexToSqrtPrice(tickUpperIndex);
|
|
107
112
|
|
|
108
|
-
const estTokenB = getTokenBFromLiquidity(
|
|
113
|
+
const estTokenB = getTokenBFromLiquidity(
|
|
114
|
+
liquidity,
|
|
115
|
+
new BN(sqrtPriceLowerX64.toString()),
|
|
116
|
+
new BN(sqrtPriceUpperX64.toString()),
|
|
117
|
+
roundUp
|
|
118
|
+
);
|
|
109
119
|
const minTokenB = adjustForSlippage(estTokenB, slippageTolerance, roundUp);
|
|
110
120
|
|
|
111
121
|
return {
|
|
@@ -117,3 +127,12 @@ function getRemoveLiquidityQuoteWhenPositionIsAboveRange(
|
|
|
117
127
|
liquidity,
|
|
118
128
|
};
|
|
119
129
|
}
|
|
130
|
+
|
|
131
|
+
export type RemoveLiquidityQuote = {
|
|
132
|
+
positionAddress: Address;
|
|
133
|
+
minTokenA: BN;
|
|
134
|
+
minTokenB: BN;
|
|
135
|
+
estTokenA: BN;
|
|
136
|
+
estTokenB: BN;
|
|
137
|
+
liquidity: BN;
|
|
138
|
+
};
|