@cetusprotocol/dlmm-sdk 0.0.1

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 (44) hide show
  1. package/.turbo/turbo-build.log +10423 -0
  2. package/README.md +646 -0
  3. package/dist/index.d.mts +1015 -0
  4. package/dist/index.d.ts +1015 -0
  5. package/dist/index.js +13 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +13 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +35 -0
  10. package/src/config/index.ts +2 -0
  11. package/src/config/mainnet.ts +25 -0
  12. package/src/config/testnet.ts +30 -0
  13. package/src/errors/errors.ts +40 -0
  14. package/src/index.ts +8 -0
  15. package/src/modules/configModule.ts +184 -0
  16. package/src/modules/index.ts +1 -0
  17. package/src/modules/partnerModule.ts +302 -0
  18. package/src/modules/poolModule.ts +578 -0
  19. package/src/modules/positionModule.ts +888 -0
  20. package/src/modules/rewardModule.ts +175 -0
  21. package/src/modules/swapModule.ts +129 -0
  22. package/src/sdk.ts +88 -0
  23. package/src/types/constants.ts +23 -0
  24. package/src/types/dlmm.ts +445 -0
  25. package/src/types/index.ts +2 -0
  26. package/src/utils/binUtils.ts +552 -0
  27. package/src/utils/feeUtils.ts +92 -0
  28. package/src/utils/index.ts +5 -0
  29. package/src/utils/parseData.ts +519 -0
  30. package/src/utils/strategyUtils.ts +121 -0
  31. package/src/utils/weightUtils.ts +510 -0
  32. package/tests/add_liquidity_bidask.test.ts +180 -0
  33. package/tests/add_liquidity_curve.test.ts +244 -0
  34. package/tests/add_liquidity_spot.test.ts +262 -0
  35. package/tests/bin.test.ts +80 -0
  36. package/tests/config.test.ts +51 -0
  37. package/tests/partner.test.ts +74 -0
  38. package/tests/pool.test.ts +174 -0
  39. package/tests/position.test.ts +76 -0
  40. package/tests/remove_liquidity.test.ts +137 -0
  41. package/tests/swap.test.ts +96 -0
  42. package/tests/tsconfig.json +26 -0
  43. package/tsconfig.json +5 -0
  44. package/tsup.config.ts +9 -0
@@ -0,0 +1,552 @@
1
+ import { d, DETAILS_KEYS, MathUtil } from '@cetusprotocol/common-sdk'
2
+ import { BIN_BOUND, MAX_BIN_PER_POSITION } from '../types/constants'
3
+ import Decimal from 'decimal.js'
4
+ import { BASIS_POINT_MAX } from '../types/constants'
5
+ import BN from 'bn.js'
6
+ import { DlmmErrorCode, handleError } from '../errors/errors'
7
+ import { BinAmount, BinLiquidityInfo } from '../types/dlmm'
8
+
9
+ const MAX_EXPONENTIAL = new BN(0x80000)
10
+ export const SCALE_OFFSET = 64
11
+ export const ONE = new BN(1).shln(SCALE_OFFSET)
12
+ const MAX = new BN(2).pow(new BN(128)).sub(new BN(1))
13
+
14
+ export class BinUtils {
15
+ /**
16
+ * Split bins into multiple smaller positions based on MAX_BIN_PER_POSITION
17
+ * @param bins - The bins to split
18
+ * @param lower_bin_id - The lower bin id
19
+ * @param upper_bin_id - The upper bin id
20
+ * @returns Array of bin info objects for each position
21
+ */
22
+ static splitBinLiquidityInfo(liquidity_bins: BinLiquidityInfo, lower_bin_id: number, upper_bin_id: number): BinLiquidityInfo[] {
23
+ const position_count = BinUtils.getPositionCount(lower_bin_id, upper_bin_id)
24
+ if (position_count <= 1) {
25
+ return [liquidity_bins]
26
+ }
27
+
28
+ const positions: BinLiquidityInfo[] = []
29
+ let current_lower = lower_bin_id
30
+
31
+ for (let i = 0; i < position_count; i++) {
32
+ const current_upper = Math.min(current_lower + MAX_BIN_PER_POSITION - 2, upper_bin_id)
33
+ const position_bins = liquidity_bins.bins.filter((bin) => bin.bin_id >= current_lower && bin.bin_id <= current_upper)
34
+
35
+ positions.push({
36
+ bins: position_bins,
37
+ amount_a: position_bins.reduce((acc, bin) => d(acc).plus(bin.amount_a), d(0)).toFixed(0),
38
+ amount_b: position_bins.reduce((acc, bin) => d(acc).plus(bin.amount_b), d(0)).toFixed(0),
39
+ })
40
+
41
+ current_lower = current_upper + 1
42
+ }
43
+
44
+ return positions
45
+ }
46
+
47
+ /**
48
+ * Process bins by rate
49
+ * @param bins - The bins to be processed
50
+ * @param rate - The rate to be applied
51
+ * @returns The processed bins
52
+ */
53
+ static processBinsByRate(bins: BinAmount[], rate: string): { bins: BinLiquidityInfo; has_invalid_amount: boolean } {
54
+ const used_bins: BinAmount[] = []
55
+ let used_total_amount_a = d(0)
56
+ let used_total_amount_b = d(0)
57
+
58
+ let has_invalid_amount = false
59
+
60
+ bins.forEach((bin) => {
61
+ const { amount_a, amount_b, liquidity = '0' } = bin
62
+ const used_liquidity = d(rate).mul(liquidity).toFixed(0)
63
+ const used_amount_a = d(amount_a).mul(rate)
64
+ const used_amount_b = d(amount_b).mul(rate)
65
+
66
+ used_total_amount_a = d(used_total_amount_a).plus(used_amount_a)
67
+ used_total_amount_b = d(used_total_amount_b).plus(used_amount_b)
68
+
69
+ if ((d(used_amount_a).lt(1) && d(used_amount_a).gt(0)) || (d(used_amount_b).lt(1) && d(used_amount_b).gt(0))) {
70
+ has_invalid_amount = true
71
+ }
72
+
73
+ used_bins.push({
74
+ bin_id: bin.bin_id,
75
+ amount_a: used_amount_a.toFixed(0),
76
+ amount_b: used_amount_b.toFixed(0),
77
+ price_per_lamport: bin.price_per_lamport,
78
+ liquidity: used_liquidity,
79
+ })
80
+ })
81
+
82
+ return {
83
+ bins: {
84
+ bins: used_bins,
85
+ amount_a: used_total_amount_a.toFixed(0),
86
+ amount_b: used_total_amount_b.toFixed(0),
87
+ },
88
+ has_invalid_amount,
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Calculate the amount of token A and token B to be removed from a bin
94
+ * @param bin - The bin information
95
+ * @param remove_liquidity - The amount of liquidity to be removed
96
+ * @returns The amount of token A and token B to be removed
97
+ */
98
+ static calculateOutByShare(bin: BinAmount, remove_liquidity: string) {
99
+ const { amount_a, amount_b, liquidity = '0' } = bin
100
+
101
+ if (liquidity === '0') {
102
+ return {
103
+ amount_a: '0',
104
+ amount_b: '0',
105
+ }
106
+ }
107
+
108
+ if (d(remove_liquidity).gte(d(liquidity))) {
109
+ return {
110
+ amount_a: amount_a,
111
+ amount_b: amount_b,
112
+ }
113
+ }
114
+
115
+ const amount_a_out = d(remove_liquidity).div(liquidity).mul(amount_a).toFixed(0, Decimal.ROUND_FLOOR)
116
+ const amount_b_out = d(remove_liquidity).div(liquidity).mul(amount_b).toFixed(0, Decimal.ROUND_FLOOR)
117
+
118
+ return {
119
+ amount_a: amount_a_out,
120
+ amount_b: amount_b_out,
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Get the number of positions in a range of bin ids
126
+ * @param lower_bin_id - The lower bin id
127
+ * @param upper_bin_id - The upper bin id
128
+ * @returns The number of positions
129
+ */
130
+ static getPositionCount(lower_bin_id: number, upper_bin_id: number) {
131
+ const binDelta = d(upper_bin_id).sub(lower_bin_id).add(1)
132
+ const positionCount = binDelta.div(MAX_BIN_PER_POSITION)
133
+ return Number(positionCount.toFixed(0, Decimal.ROUND_UP))
134
+ }
135
+
136
+ /**
137
+ * Calculate the amount of liquidity following the constant sum formula `L = price * x + y`
138
+ * @param amount_a
139
+ * @param amount_b
140
+ * @param qPrice Price is in Q64x64
141
+ * @returns
142
+ */
143
+ static getLiquidity(amount_a: string, amount_b: string, qPrice: string): string {
144
+ const px = d(qPrice).mul(amount_a)
145
+ const liquidity = px.add(d(amount_b).mul(d(2).pow(SCALE_OFFSET)))
146
+ return liquidity.toFixed(0)
147
+ }
148
+
149
+ /**
150
+ * Calculate amount_a from liquidity when all liquidity is in token A
151
+ * @param liquidity - The liquidity amount
152
+ * @param qPrice - Price in Q64x64 format
153
+ * @returns The amount of token A
154
+ */
155
+ static getAmountAFromLiquidity(liquidity: string, qPrice: string): string {
156
+ return d(liquidity).div(d(qPrice)).toFixed(0)
157
+ }
158
+
159
+ /**
160
+ * Calculate amount_b from liquidity when all liquidity is in token B
161
+ * @param liquidity - The liquidity amount
162
+ * @returns The amount of token B
163
+ */
164
+ static getAmountBFromLiquidity(liquidity: string): string {
165
+ return d(liquidity).div(d(2).pow(SCALE_OFFSET)).toFixed(0)
166
+ }
167
+
168
+ /**
169
+ * Calculate amounts from liquidity using the same logic as Move code
170
+ * @param amount_a - Current amount of token A in the bin
171
+ * @param amount_b - Current amount of token B in the bin
172
+ * @param delta_liquidity - The liquidity delta to calculate amounts for
173
+ * @param liquidity_supply - Total liquidity supply in the bin
174
+ * @returns [amount_a_out, amount_b_out]
175
+ */
176
+ static getAmountsFromLiquidity(amount_a: string, amount_b: string, delta_liquidity: string, liquidity_supply: string): [string, string] {
177
+ if (d(liquidity_supply).isZero()) {
178
+ handleError(DlmmErrorCode.LiquiditySupplyIsZero, 'Liquidity supply is zero')
179
+ }
180
+
181
+ if (d(delta_liquidity).gt(d(liquidity_supply))) {
182
+ handleError(DlmmErrorCode.InvalidDeltaLiquidity, 'Invalid delta liquidity')
183
+ }
184
+
185
+ if (d(delta_liquidity).isZero()) {
186
+ return ['0', '0']
187
+ }
188
+
189
+ let out_amount_a: string
190
+ if (d(amount_a).isZero()) {
191
+ out_amount_a = '0'
192
+ } else {
193
+ out_amount_a = d(amount_a).mul(d(delta_liquidity)).div(d(liquidity_supply)).toFixed(0, Decimal.ROUND_FLOOR)
194
+ }
195
+
196
+ let out_amount_b: string
197
+ if (d(amount_b).isZero()) {
198
+ out_amount_b = '0'
199
+ } else {
200
+ out_amount_b = d(amount_b).mul(d(delta_liquidity)).div(d(liquidity_supply)).toFixed(0, Decimal.ROUND_FLOOR)
201
+ }
202
+
203
+ return [out_amount_a, out_amount_b]
204
+ }
205
+
206
+ /**
207
+ * Get the price of a bin by bin id
208
+ * @param bin_id - The bin id
209
+ * @param bin_step - The bin step
210
+ * @param decimal_a - The decimal of the token a
211
+ * @param decimal_b - The decimal of the token b
212
+ * @returns The price of the bin
213
+ */
214
+ static getPriceFromBinId(bin_id: number, bin_step: number, decimal_a: number, decimal_b: number): string {
215
+ const pricePerLamport = BinUtils.getPricePerLamportFromBinId(bin_id, bin_step)
216
+ return BinUtils.getPriceFromLamport(decimal_a, decimal_b, pricePerLamport).toString()
217
+ }
218
+
219
+ /**
220
+ * Get the price per lamport of a bin by bin id
221
+ * @param bin_id - The bin id
222
+ * @param bin_step - The bin step
223
+ * @returns The price per lamport of the bin
224
+ */
225
+ static getPricePerLamportFromBinId(bin_id: number, bin_step: number): string {
226
+ const binStepNum = new Decimal(bin_step).div(new Decimal(BASIS_POINT_MAX))
227
+ return new Decimal(1).add(new Decimal(binStepNum)).pow(new Decimal(bin_id)).toString()
228
+ }
229
+
230
+ /**
231
+ * Get the bin id from a price
232
+ * @param price - The price
233
+ * @param binStep - The bin step
234
+ * @param min - Whether to use the minimum or maximum bin id
235
+ * @param decimal_a - The decimal of the token a
236
+ * @param decimal_b - The decimal of the token b
237
+ * @returns The bin id
238
+ */
239
+ public static getBinIdFromPrice(price: string, binStep: number, min: boolean, decimal_a: number, decimal_b: number): number {
240
+ const pricePerLamport = BinUtils.getPricePerLamport(decimal_a, decimal_b, price)
241
+ return BinUtils.getBinIdFromLamportPrice(pricePerLamport, binStep, min)
242
+ }
243
+
244
+ /**
245
+ * Get the bin id from a price per lamport
246
+ * @param pricePerLamport - The price per lamport
247
+ * @param binStep - The bin step
248
+ * @param min - Whether to use the minimum or maximum bin id
249
+ * @returns The bin id
250
+ */
251
+ public static getBinIdFromLamportPrice(pricePerLamport: string, binStep: number, min: boolean): number {
252
+ const binStepNum = new Decimal(binStep).div(new Decimal(BASIS_POINT_MAX))
253
+ const binId = new Decimal(pricePerLamport).log().dividedBy(new Decimal(1).add(binStepNum).log())
254
+ return (min ? binId.floor() : binId.ceil()).toNumber()
255
+ }
256
+
257
+ /**
258
+ * Get the price per lamport
259
+ * @param decimal_a - The decimal of the token a
260
+ * @param decimal_b - The decimal of the token b
261
+ * @param price - The price
262
+ * @returns The price per lamport
263
+ */
264
+ public static getPricePerLamport(decimal_a: number, decimal_b: number, price: string): string {
265
+ return new Decimal(price).mul(new Decimal(10 ** (decimal_b - decimal_a))).toString()
266
+ }
267
+
268
+ /**
269
+ * Convert price per lamport back to original price
270
+ * @param decimal_a - The decimal of the token a
271
+ * @param decimal_b - The decimal of the token b
272
+ * @param pricePerLamport - The price per lamport
273
+ * @returns The original price
274
+ */
275
+ public static getPriceFromLamport(decimal_a: number, decimal_b: number, pricePerLamport: string): string {
276
+ return new Decimal(pricePerLamport).div(new Decimal(10 ** (decimal_b - decimal_a))).toString()
277
+ }
278
+
279
+ /**
280
+ * Get the reverse price
281
+ * @param price - The price
282
+ * @returns The reverse price
283
+ */
284
+ public static getReversePrice(price: string): string {
285
+ return new Decimal(1).div(price).toString()
286
+ }
287
+
288
+ /**
289
+ * Get the price of a bin by bin id
290
+ * @param binId - The bin id
291
+ * @param binStep - The bin step
292
+ * @returns The price of the bin
293
+ */
294
+ static getQPriceFromId(binId: number, binStep: number): string {
295
+ const bps = new BN(binStep).shln(SCALE_OFFSET).div(new BN(BASIS_POINT_MAX))
296
+ const base = ONE.add(bps)
297
+ return BinUtils.pow(base, new BN(binId)).toString()
298
+ }
299
+
300
+ /**
301
+ * Convert QPrice (Q64x64 format) to actual price
302
+ * @param qPrice - The price in Q64x64 format
303
+ * @returns The actual price
304
+ */
305
+ static getPricePerLamportFromQPrice(qPrice: string): string {
306
+ return MathUtil.fromX64(new BN(qPrice)).toString()
307
+ }
308
+
309
+ static pow(base: BN, exp: BN): BN {
310
+ let invert = exp.isNeg()
311
+
312
+ if (exp.isZero()) {
313
+ return ONE
314
+ }
315
+
316
+ exp = invert ? exp.abs() : exp
317
+
318
+ if (exp.gt(MAX_EXPONENTIAL)) {
319
+ return new BN(0)
320
+ }
321
+
322
+ let squaredBase = base
323
+ let result = ONE
324
+
325
+ if (squaredBase.gte(result)) {
326
+ squaredBase = MAX.div(squaredBase)
327
+ invert = !invert
328
+ }
329
+
330
+ if (!exp.and(new BN(0x1)).isZero()) {
331
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
332
+ }
333
+
334
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
335
+
336
+ if (!exp.and(new BN(0x2)).isZero()) {
337
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
338
+ }
339
+
340
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
341
+
342
+ if (!exp.and(new BN(0x4)).isZero()) {
343
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
344
+ }
345
+
346
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
347
+
348
+ if (!exp.and(new BN(0x8)).isZero()) {
349
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
350
+ }
351
+
352
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
353
+
354
+ if (!exp.and(new BN(0x10)).isZero()) {
355
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
356
+ }
357
+
358
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
359
+
360
+ if (!exp.and(new BN(0x20)).isZero()) {
361
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
362
+ }
363
+
364
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
365
+
366
+ if (!exp.and(new BN(0x40)).isZero()) {
367
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
368
+ }
369
+
370
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
371
+
372
+ if (!exp.and(new BN(0x80)).isZero()) {
373
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
374
+ }
375
+
376
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
377
+
378
+ if (!exp.and(new BN(0x100)).isZero()) {
379
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
380
+ }
381
+
382
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
383
+
384
+ if (!exp.and(new BN(0x200)).isZero()) {
385
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
386
+ }
387
+
388
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
389
+
390
+ if (!exp.and(new BN(0x400)).isZero()) {
391
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
392
+ }
393
+
394
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
395
+
396
+ if (!exp.and(new BN(0x800)).isZero()) {
397
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
398
+ }
399
+
400
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
401
+
402
+ if (!exp.and(new BN(0x1000)).isZero()) {
403
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
404
+ }
405
+
406
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
407
+
408
+ if (!exp.and(new BN(0x2000)).isZero()) {
409
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
410
+ }
411
+
412
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
413
+
414
+ if (!exp.and(new BN(0x4000)).isZero()) {
415
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
416
+ }
417
+
418
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
419
+
420
+ if (!exp.and(new BN(0x8000)).isZero()) {
421
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
422
+ }
423
+
424
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
425
+
426
+ if (!exp.and(new BN(0x10000)).isZero()) {
427
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
428
+ }
429
+
430
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
431
+
432
+ if (!exp.and(new BN(0x20000)).isZero()) {
433
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
434
+ }
435
+
436
+ squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET)
437
+
438
+ if (!exp.and(new BN(0x40000)).isZero()) {
439
+ result = result.mul(squaredBase).shrn(SCALE_OFFSET)
440
+ }
441
+
442
+ if (result.isZero()) {
443
+ return new BN(0)
444
+ }
445
+
446
+ if (invert) {
447
+ result = MAX.div(result)
448
+ }
449
+
450
+ return result
451
+ }
452
+
453
+ /**
454
+ * Converts a bin ID to a score by adding the bin bound and validating the range
455
+ * @param binId - The bin ID to convert
456
+ * @returns The calculated bin score
457
+ * @throws Error if the bin ID is invalid
458
+ */
459
+ static binScore(binId: number): string {
460
+ const score = BigInt(binId) + BIN_BOUND
461
+ if (score < 0n || score > BIN_BOUND * 2n) {
462
+ handleError(DlmmErrorCode.InvalidBinId, new Error('Invalid bin ID'), {
463
+ [DETAILS_KEYS.METHOD_NAME]: 'binScore',
464
+ [DETAILS_KEYS.REQUEST_PARAMS]: { binId },
465
+ })
466
+ }
467
+
468
+ return score.toString()
469
+ }
470
+
471
+ /**
472
+ * Converts a score back to bin ID by subtracting the bin bound
473
+ * @param score - The score to convert
474
+ * @returns The calculated bin ID
475
+ * @throws Error if the score is invalid
476
+ */
477
+ static scoreToBinId(score: string): number {
478
+ const binId = BigInt(score) - BIN_BOUND
479
+ if (binId < -BIN_BOUND || binId > BIN_BOUND) {
480
+ handleError(DlmmErrorCode.InvalidBinId, new Error('Invalid score'), {
481
+ [DETAILS_KEYS.METHOD_NAME]: 'scoreToBinId',
482
+ [DETAILS_KEYS.REQUEST_PARAMS]: { score },
483
+ })
484
+ }
485
+
486
+ return Number(binId)
487
+ }
488
+
489
+ /**
490
+ * Resolves the bin position from a score.
491
+ *
492
+ * @param score - The score to resolve
493
+ * @returns Tuple of [group index, offset in group]
494
+ */
495
+ static resolveBinPosition(score: string): [string, number] {
496
+ const scoreBigInt = BigInt(score)
497
+ const groupIndex = scoreBigInt >> 4n
498
+ const offsetInGroup = Number(scoreBigInt & 0xfn)
499
+
500
+ return [groupIndex.toString(), offsetInGroup]
501
+ }
502
+
503
+ static findMinMaxBinId(binStep: number) {
504
+ const base = 1 + binStep / BASIS_POINT_MAX
505
+ const maxQPriceSupported = new Decimal('18446744073709551615')
506
+ const n = maxQPriceSupported.log(10).div(new Decimal(base).log(10)).floor()
507
+
508
+ let minBinId = n.neg()
509
+ let maxBinId = n
510
+
511
+ let minQPrice = d(1)
512
+ let maxQPrice = d('340282366920938463463374607431768211455')
513
+
514
+ while (true) {
515
+ const qPrice = d(BinUtils.getQPriceFromId(minBinId.toNumber(), binStep))
516
+ if (qPrice.gt(minQPrice) && !qPrice.isZero()) {
517
+ break
518
+ } else {
519
+ minBinId = minBinId.add(1)
520
+ }
521
+ }
522
+
523
+ while (true) {
524
+ const qPrice = d(BinUtils.getQPriceFromId(maxBinId.toNumber(), binStep))
525
+ if (qPrice.lt(maxQPrice) && !qPrice.isZero()) {
526
+ break
527
+ } else {
528
+ maxBinId = maxBinId.sub(1)
529
+ }
530
+ }
531
+
532
+ return {
533
+ minBinId: minBinId.toNumber(),
534
+ maxBinId: maxBinId.toNumber(),
535
+ }
536
+ }
537
+
538
+ static getBinShift(active_id: number, bin_step: number, max_price_slippage: number): number {
539
+ const price = BinUtils.getPricePerLamportFromBinId(active_id, bin_step)
540
+ const price_limit = d(price)
541
+ .mul(1 + max_price_slippage)
542
+ .toString()
543
+ const slippage_active_id = BinUtils.getBinIdFromLamportPrice(price_limit, bin_step, true)
544
+ const bin_shift = d(slippage_active_id).sub(active_id).abs().toFixed(0, Decimal.ROUND_UP)
545
+
546
+ console.log('getBinShift Options:', {
547
+ active_id,
548
+ bin_shift,
549
+ })
550
+ return Number(bin_shift)
551
+ }
552
+ }
@@ -0,0 +1,92 @@
1
+ import { d } from '@cetusprotocol/common-sdk'
2
+ import { BinAmount, BinStepConfig, VariableParameters } from '../types/dlmm'
3
+ import { BinUtils } from './binUtils'
4
+ import { BASIS_POINT, FEE_PRECISION, MAX_FEE_RATE } from '../types/constants'
5
+
6
+ export class FeeUtils {
7
+ static getVariableFee(variableParameters: VariableParameters): string {
8
+ const { volatility_accumulator, bin_step_config } = variableParameters
9
+ const { variable_fee_control, bin_step } = bin_step_config
10
+
11
+ if (d(variable_fee_control).gt(0)) {
12
+ const square_vfa_bin = d(volatility_accumulator).mul(bin_step).pow(2)
13
+ const v_fee = square_vfa_bin.mul(variable_fee_control)
14
+ const scaled_v_fee = v_fee.add(99_999_999_999).div(100_000_000_000)
15
+ return scaled_v_fee.toFixed(0)
16
+ }
17
+ return '0'
18
+ }
19
+
20
+ static calculateCompositionFee(amount: string, total_fee_rate: string) {
21
+ const fee_amount = d(amount).mul(total_fee_rate)
22
+ const composition_fee = d(fee_amount).mul(d(FEE_PRECISION).add(total_fee_rate))
23
+ return composition_fee.div(d(FEE_PRECISION).pow(2)).toFixed(0)
24
+ }
25
+
26
+ static calculateProtocolFee(fee_amount: string, protocol_fee_rate: string) {
27
+ const protocol_fee = d(fee_amount).mul(protocol_fee_rate).div(BASIS_POINT).ceil().toFixed(0)
28
+ return protocol_fee
29
+ }
30
+
31
+ static getProtocolFees(fee_a: string, fee_b: string, protocol_fee_rate: string) {
32
+ const protocol_fee_a = FeeUtils.calculateProtocolFee(fee_a, protocol_fee_rate)
33
+ const protocol_fee_b = FeeUtils.calculateProtocolFee(fee_b, protocol_fee_rate)
34
+ return {
35
+ protocol_fee_a,
36
+ protocol_fee_b,
37
+ }
38
+ }
39
+
40
+ static getCompositionFees(
41
+ active_bin: BinAmount,
42
+ used_bin: BinAmount,
43
+ binStepConfig: BinStepConfig,
44
+ variableParameters: VariableParameters
45
+ ): { fees_a: string; fees_b: string } {
46
+ if (d(active_bin.liquidity || '0').eq(d(0))) {
47
+ return {
48
+ fees_a: '0',
49
+ fees_b: '0',
50
+ }
51
+ }
52
+ const { bin_step, base_factor } = binStepConfig
53
+ const qPrice = BinUtils.getQPriceFromId(active_bin.bin_id, bin_step)
54
+ const used_liquidity = BinUtils.getLiquidity(used_bin.amount_a, used_bin.amount_b, qPrice)
55
+
56
+ const { amount_a: amount_a_out, amount_b: amount_b_out } = BinUtils.calculateOutByShare(
57
+ {
58
+ bin_id: active_bin.bin_id,
59
+ liquidity: d(active_bin.liquidity).add(used_liquidity).toFixed(0),
60
+ amount_a: d(active_bin.amount_a).add(used_bin.amount_a).toFixed(0),
61
+ amount_b: d(active_bin.amount_b).add(used_bin.amount_b).toFixed(0),
62
+ price_per_lamport: active_bin.price_per_lamport,
63
+ },
64
+ used_liquidity
65
+ )
66
+
67
+ const base_fee = d(bin_step).mul(base_factor)
68
+ const variable_fee = FeeUtils.getVariableFee(variableParameters)
69
+
70
+ let total_fee_rate = d(base_fee).add(variable_fee).toFixed(0)
71
+
72
+ if (d(total_fee_rate).gt(MAX_FEE_RATE)) {
73
+ total_fee_rate = MAX_FEE_RATE.toString()
74
+ }
75
+
76
+ let fees_a = '0'
77
+ let fees_b = '0'
78
+
79
+ if (d(amount_a_out).gt(used_bin.amount_a)) {
80
+ fees_a = FeeUtils.calculateCompositionFee(amount_a_out, total_fee_rate)
81
+ }
82
+
83
+ if (d(amount_b_out).gt(used_bin.amount_b)) {
84
+ fees_b = FeeUtils.calculateCompositionFee(amount_b_out, total_fee_rate)
85
+ }
86
+
87
+ return {
88
+ fees_a,
89
+ fees_b,
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,5 @@
1
+ export * from './parseData'
2
+ export * from './binUtils'
3
+ export * from './weightUtils'
4
+ export * from './strategyUtils'
5
+ export * from './feeUtils'