100x-sdk 1.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.
@@ -0,0 +1,1215 @@
1
+ const Decimal = require('decimal.js');
2
+
3
+ // Configure Decimal.js to use 50-bit precision for better accuracy with tiny prices
4
+ // Increased from 28 to 50 to handle extremely small prices (e.g., 0.000000041571)
5
+ // ROUND_HALF_UP ensures consistent rounding behavior
6
+ Decimal.set({ precision: 50, rounding: Decimal.ROUND_HALF_UP });
7
+
8
+
9
+ /**
10
+ * Denominator used for fee calculations (10^5)
11
+ * @type {bigint}
12
+ */
13
+ const FEE_DENOMINATOR = 100_000n;
14
+
15
+ /**
16
+ * Maximum fee rate (10%)
17
+ * @type {bigint}
18
+ */
19
+ const MAX_FEE_RATE = 10_000n;
20
+
21
+
22
+
23
+
24
+ /**
25
+ * Traditional AMM trading model class
26
+ * Implements constant product (xy=k) algorithm for automated market maker functionality
27
+ * Usage when importing: const CurveAMM = require("../tools/curve_amm");
28
+ */
29
+ class CurveAMM {
30
+ /**
31
+ * Initial SOL reserve amount, represented as Decimal
32
+ * @type {Decimal}
33
+ */
34
+ static INITIAL_SOL_RESERVE_DECIMAL = new Decimal('30');
35
+
36
+ /**
37
+ * Initial Token reserve amount, represented as Decimal
38
+ * @type {Decimal}
39
+ */
40
+ static INITIAL_TOKEN_RESERVE_DECIMAL = new Decimal('1073000000');
41
+
42
+ /**
43
+ * Initial constant K value, represented as Decimal
44
+ * @type {Decimal}
45
+ */
46
+ static INITIAL_K_DECIMAL = new Decimal('32190000000');
47
+
48
+ /**
49
+ * Minimum price that can appear, below this price may cause overflow
50
+ * Note: With dynamic liquidity pools, prices can be much smaller (e.g., 3e-10)
51
+ * Reduced from 1e-9 to 1e-12 to support wider range of pool configurations
52
+ * @type {Decimal}
53
+ */
54
+ static INITIAL_MIN_PRICE_DECIMAL = new Decimal('0.000000000001'); // 1e-12
55
+
56
+ /**
57
+ * Decimal representation of precision factor = 10^23
58
+ * @type {Decimal}
59
+ */
60
+ //static PRICE_PRECISION_FACTOR_DECIMAL = new Decimal('10000000000000000000000000000');
61
+ static PRICE_PRECISION_FACTOR_DECIMAL = new Decimal('100000000000000000000000');
62
+
63
+ /**
64
+ * Decimal representation of Token precision factor = 1000000000 (10^9)
65
+ * @type {Decimal}
66
+ */
67
+ static TOKEN_PRECISION_FACTOR_DECIMAL = new Decimal('1000000000');
68
+
69
+ /**
70
+ * Decimal representation of SOL precision factor = 1000000000
71
+ * @type {Decimal}
72
+ */
73
+ static SOL_PRECISION_FACTOR_DECIMAL = new Decimal('1000000000');
74
+
75
+
76
+ /**
77
+ * Maximum price for u128 (matches PRICE_CALCULATION_LIMIT in Rust)
78
+ * @type {bigint}
79
+ */
80
+ static MAX_U128_PRICE = 70000000000000000000000000000n;
81
+
82
+
83
+ /**
84
+ * Minimum price for u128
85
+ * @type {bigint}
86
+ */
87
+ static MIN_U128_PRICE = 11958993476234855n;
88
+
89
+
90
+
91
+
92
+ /**
93
+ * Convert u128 price to Decimal
94
+ *
95
+ * @param {bigint|string|number} price - u128 price to be converted
96
+ * @returns {Decimal} Converted Decimal price
97
+ */
98
+ static u128ToDecimal(price) {
99
+ if (typeof price === 'bigint') {
100
+ price = price.toString();
101
+ }
102
+ const priceDecimal = new Decimal(price);
103
+ return priceDecimal.div(this.PRICE_PRECISION_FACTOR_DECIMAL);
104
+ }
105
+
106
+ /**
107
+ * Convert Decimal price to u128, rounded down
108
+ *
109
+ * @param {Decimal} price - Decimal price to be converted
110
+ * @returns {bigint|null} Converted u128 price, returns null if overflow
111
+ */
112
+ static decimalToU128(price) {
113
+ const scaled = price.mul(this.PRICE_PRECISION_FACTOR_DECIMAL);
114
+ const floored = scaled.floor();
115
+ if (floored.isNaN() || floored.isNegative() || floored.gt(this.MAX_U128_PRICE.toString())) {
116
+ return null;
117
+ }
118
+ // Use toFixed() to avoid scientific notation
119
+ const flooredStr = floored.toFixed(0);
120
+ return BigInt(flooredStr);
121
+ }
122
+
123
+ /**
124
+ * Convert Decimal price to u128, rounded up
125
+ *
126
+ * @param {Decimal} price - Decimal price to be converted
127
+ * @returns {bigint|null} Converted u128 price, returns null if overflow
128
+ */
129
+ static decimalToU128Ceil(price) {
130
+ const scaled = price.mul(this.PRICE_PRECISION_FACTOR_DECIMAL);
131
+ const ceiled = scaled.ceil();
132
+ if (ceiled.isNaN() || ceiled.isNegative() || ceiled.gt(this.MAX_U128_PRICE.toString())) {
133
+ return null;
134
+ }
135
+ // Use toFixed() to avoid scientific notation
136
+ const ceiledStr = ceiled.toFixed(0);
137
+ return BigInt(ceiledStr);
138
+ }
139
+
140
+ /**
141
+ * Convert u64 price to Decimal (legacy compatibility)
142
+ *
143
+ * @param {bigint|string|number} price - u64 price to be converted
144
+ * @returns {Decimal} Converted Decimal price
145
+ * @deprecated Please use u128ToDecimal instead
146
+ */
147
+ static u64ToDecimal(price) {
148
+ // Directly use old precision factor for conversion
149
+ if (typeof price === 'bigint') {
150
+ price = price.toString();
151
+ }
152
+ const priceDecimal = new Decimal(price);
153
+ // Use old precision factor 10^15
154
+ const oldPrecisionFactor = new Decimal('1000000000000000');
155
+ return priceDecimal.div(oldPrecisionFactor);
156
+ }
157
+
158
+ /**
159
+ * Convert Decimal price to u64, rounded down (legacy compatibility)
160
+ *
161
+ * @param {Decimal} price - Decimal price to be converted
162
+ * @returns {bigint|null} Converted u64 price, returns null if overflow
163
+ * @deprecated Please use decimalToU128 instead
164
+ */
165
+ static decimalToU64(price) {
166
+ // Directly use old precision factor for conversion
167
+ const oldPrecisionFactor = new Decimal('1000000000000000');
168
+ const scaled = price.mul(oldPrecisionFactor);
169
+ const floored = scaled.floor();
170
+ if (floored.isNaN() || floored.isNegative() || floored.gt('18446744073709551615')) {
171
+ return null;
172
+ }
173
+ const flooredStr = floored.toFixed(0);
174
+ return BigInt(flooredStr);
175
+ }
176
+
177
+ /**
178
+ * Convert Decimal price to u64, rounded up (legacy compatibility)
179
+ *
180
+ * @param {Decimal} price - Decimal price to be converted
181
+ * @returns {bigint|null} Converted u64 price, returns null if overflow
182
+ * @deprecated Please use decimalToU128Ceil instead
183
+ */
184
+ static decimalToU64Ceil(price) {
185
+ // Directly use old precision factor for conversion
186
+ const oldPrecisionFactor = new Decimal('1000000000000000');
187
+ const scaled = price.mul(oldPrecisionFactor);
188
+ const ceiled = scaled.ceil();
189
+ if (ceiled.isNaN() || ceiled.isNegative() || ceiled.gt('18446744073709551615')) {
190
+ return null;
191
+ }
192
+ const ceiledStr = ceiled.toFixed(0);
193
+ return BigInt(ceiledStr);
194
+ }
195
+
196
+ /**
197
+ * Convert Decimal token amount to u64, using 9-digit precision, rounded down
198
+ *
199
+ * @param {Decimal} amount - Decimal token amount to be converted
200
+ * @returns {bigint|null} Converted u64 token amount, returns null if overflow
201
+ */
202
+ static tokenDecimalToU64(amount) {
203
+ const scaled = amount.mul(this.TOKEN_PRECISION_FACTOR_DECIMAL);
204
+ const floored = scaled.floor();
205
+ if (floored.isNaN() || floored.isNegative() || floored.gt('18446744073709551615')) {
206
+ return null;
207
+ }
208
+ const flooredStr = floored.toFixed(0);
209
+ return BigInt(flooredStr);
210
+ }
211
+
212
+ /**
213
+ * Convert Decimal token amount to u64, using 9-digit precision, rounded up
214
+ *
215
+ * @param {Decimal} amount - Decimal token amount to be converted
216
+ * @returns {bigint|null} Converted u64 token amount, returns null if overflow
217
+ */
218
+ static tokenDecimalToU64Ceil(amount) {
219
+ const scaled = amount.mul(this.TOKEN_PRECISION_FACTOR_DECIMAL);
220
+ const ceiled = scaled.ceil();
221
+ if (ceiled.isNaN() || ceiled.isNegative() || ceiled.gt('18446744073709551615')) {
222
+ return null;
223
+ }
224
+ const ceiledStr = ceiled.toFixed(0);
225
+ return BigInt(ceiledStr);
226
+ }
227
+
228
+ /**
229
+ * Convert Decimal SOL amount to u64, using 9-digit precision, rounded down
230
+ *
231
+ * @param {Decimal} amount - Decimal SOL amount to be converted
232
+ * @returns {bigint|null} Converted u64 SOL amount, returns null if overflow
233
+ */
234
+ static solDecimalToU64(amount) {
235
+ const scaled = amount.mul(this.SOL_PRECISION_FACTOR_DECIMAL);
236
+ const floored = scaled.floor();
237
+ if (floored.isNaN() || floored.isNegative() || floored.gt('18446744073709551615')) {
238
+ return null;
239
+ }
240
+ const flooredStr = floored.toFixed(0);
241
+ return BigInt(flooredStr);
242
+ }
243
+
244
+ /**
245
+ * Convert Decimal SOL amount to u64, using 9-digit precision, rounded up
246
+ *
247
+ * @param {Decimal} amount - Decimal SOL amount to be converted
248
+ * @returns {bigint|null} Converted u64 SOL amount, returns null if overflow
249
+ */
250
+ static solDecimalToU64Ceil(amount) {
251
+ const scaled = amount.mul(this.SOL_PRECISION_FACTOR_DECIMAL);
252
+ const ceiled = scaled.ceil();
253
+ if (ceiled.isNaN() || ceiled.isNegative() || ceiled.gt('18446744073709551615')) {
254
+ return null;
255
+ }
256
+ const ceiledStr = ceiled.toFixed(0);
257
+ return BigInt(ceiledStr);
258
+ }
259
+
260
+ /**
261
+ * Convert u64 token amount to Decimal, using 9-digit precision
262
+ *
263
+ * @param {bigint|string|number} amount - u64 token amount to be converted
264
+ * @returns {Decimal} Converted Decimal token amount
265
+ */
266
+ static u64ToTokenDecimal(amount) {
267
+ if (typeof amount === 'bigint') {
268
+ amount = amount.toString();
269
+ }
270
+ const amountDecimal = new Decimal(amount);
271
+ return amountDecimal.div(this.TOKEN_PRECISION_FACTOR_DECIMAL);
272
+ }
273
+
274
+ /**
275
+ * Convert u64 SOL amount to Decimal, using 9-digit precision
276
+ *
277
+ * @param {bigint|string|number} amount - u64 SOL amount to be converted
278
+ * @returns {Decimal} Converted Decimal SOL amount
279
+ */
280
+ static u64ToSolDecimal(amount) {
281
+ if (typeof amount === 'bigint') {
282
+ amount = amount.toString();
283
+ }
284
+ const amountDecimal = new Decimal(amount);
285
+ return amountDecimal.div(this.SOL_PRECISION_FACTOR_DECIMAL);
286
+ }
287
+
288
+ /**
289
+ * 根据自定义初始储备量计算 k 值
290
+ * @param {string|number|Decimal} initialVirtualSol - 初始虚拟SOL储备量(实际值,非lamports)
291
+ * @param {string|number|Decimal} initialVirtualToken - 初始虚拟Token储备量(实际值,非最小单位)
292
+ * @returns {Decimal} k值
293
+ *
294
+ * @example
295
+ * // 使用默认参数(30 SOL, 10.73亿 Token)
296
+ * const k1 = CurveAMM.calculateK(30, 1073000000);
297
+ *
298
+ * // 使用自定义参数(60 SOL, 10.73亿 Token)
299
+ * const k2 = CurveAMM.calculateK(60, 1073000000);
300
+ */
301
+ static calculateK(initialVirtualSol, initialVirtualToken) {
302
+ const sol = new Decimal(initialVirtualSol);
303
+ const token = new Decimal(initialVirtualToken);
304
+ return sol.mul(token);
305
+ }
306
+
307
+ /**
308
+ * 使用默认参数计算初始k值(向后兼容)
309
+ * @returns {Decimal} Product k value of initial reserves
310
+ */
311
+ static calculateInitialK() {
312
+ return this.calculateK(
313
+ this.INITIAL_SOL_RESERVE_DECIMAL,
314
+ this.INITIAL_TOKEN_RESERVE_DECIMAL
315
+ );
316
+ }
317
+
318
+ /**
319
+ * 根据自定义初始储备量获取初始价格
320
+ * @param {string|number|Decimal} initialVirtualSol - 初始虚拟SOL储备量
321
+ * @param {string|number|Decimal} initialVirtualToken - 初始虚拟Token储备量
322
+ * @returns {bigint|null} u128格式的初始价格
323
+ */
324
+ static getInitialPriceWithParams(initialVirtualSol, initialVirtualToken) {
325
+ const sol = new Decimal(initialVirtualSol);
326
+ const token = new Decimal(initialVirtualToken);
327
+ const initialPrice = sol.div(token);
328
+ return this.decimalToU128(initialPrice);
329
+ }
330
+
331
+ /**
332
+ * 使用默认参数获取初始价格(向后兼容)
333
+ * Get initial price (SOL amount for 1 token)
334
+ *
335
+ * @returns {bigint|null} Initial price in u128 format, returns null if calculation fails
336
+ */
337
+ static getInitialPrice() {
338
+ return this.getInitialPriceWithParams(
339
+ this.INITIAL_SOL_RESERVE_DECIMAL,
340
+ this.INITIAL_TOKEN_RESERVE_DECIMAL
341
+ );
342
+ }
343
+
344
+ /**
345
+ * 根据自定义初始储备量计算从低价买入到高价所需的SOL和获得的Token
346
+ * @param {bigint|string|number} startLowPrice - 起始价格(较低)
347
+ * @param {bigint|string|number} endHighPrice - 目标价格(较高)
348
+ * @param {string|number|Decimal} initialVirtualSol - 初始虚拟SOL储备量
349
+ * @param {string|number|Decimal} initialVirtualToken - 初始虚拟Token储备量
350
+ * @returns {[bigint, bigint]|null} 成功返回 [需投入SOL数量, 获得token数量], 失败返回null
351
+ */
352
+ static buyFromPriceToPriceWithParams(startLowPrice, endHighPrice, initialVirtualSol, initialVirtualToken) {
353
+ // Convert to Decimal for calculation
354
+ const startPriceDec = this.u128ToDecimal(startLowPrice);
355
+ const endPriceDec = this.u128ToDecimal(endHighPrice);
356
+
357
+ // Ensure starting price is lower than ending price
358
+ if (startPriceDec.gte(endPriceDec)) {
359
+ return null;
360
+ }
361
+
362
+ // Use custom k value
363
+ const k = this.calculateK(initialVirtualSol, initialVirtualToken);
364
+
365
+ // Calculate reserves for starting and ending states
366
+ const startReserves = this.calculateReservesByPrice(startPriceDec, k);
367
+ const endReserves = this.calculateReservesByPrice(endPriceDec, k);
368
+
369
+ if (!startReserves || !endReserves) {
370
+ return null;
371
+ }
372
+
373
+ const [startSolReserve, startTokenReserve] = startReserves;
374
+ const [endSolReserve, endTokenReserve] = endReserves;
375
+
376
+ // Calculate SOL amount to invest (increase in SOL reserves)
377
+ const solInputAmount = endSolReserve.sub(startSolReserve);
378
+
379
+ // Calculate token amount to obtain (decrease in token reserves)
380
+ const tokenOutputAmount = startTokenReserve.sub(endTokenReserve);
381
+
382
+ // Check if calculation results are valid
383
+ if (solInputAmount.lte(0) || tokenOutputAmount.lte(0)) {
384
+ return null;
385
+ }
386
+
387
+ // Convert back to u64
388
+ // SOL uses 9-digit precision rounded up, token uses 9-digit precision rounded down
389
+ const solAmountU64 = this.solDecimalToU64Ceil(solInputAmount);
390
+ const tokenAmountU64 = this.tokenDecimalToU64(tokenOutputAmount);
391
+
392
+ if (solAmountU64 === null || tokenAmountU64 === null) {
393
+ return null;
394
+ }
395
+
396
+ return [solAmountU64, tokenAmountU64];
397
+ }
398
+
399
+ /**
400
+ * 使用默认参数计算从低价买入到高价(向后兼容)
401
+ * Calculate SOL required and token amount obtained when buying tokens from low to high price
402
+ *
403
+ * @param {bigint|string|number} startLowPrice - Starting price (lower)
404
+ * @param {bigint|string|number} endHighPrice - Target price (higher)
405
+ * @returns {[bigint, bigint]|null} Returns [SOL amount to invest, token amount to obtain] on success, null on failure
406
+ * SOL amount in 9-digit precision rounded up; token amount in 9-digit precision rounded down
407
+ */
408
+ static buyFromPriceToPrice(startLowPrice, endHighPrice) {
409
+ return this.buyFromPriceToPriceWithParams(
410
+ startLowPrice,
411
+ endHighPrice,
412
+ this.INITIAL_SOL_RESERVE_DECIMAL,
413
+ this.INITIAL_TOKEN_RESERVE_DECIMAL
414
+ );
415
+ }
416
+
417
+ /**
418
+ * 根据自定义初始储备量计算从高价卖出到低价所需的Token和获得的SOL
419
+ * @param {bigint|string|number} startHighPrice - 起始价格(较高)
420
+ * @param {bigint|string|number} endLowPrice - 目标价格(较低)
421
+ * @param {string|number|Decimal} initialVirtualSol - 初始虚拟SOL储备量
422
+ * @param {string|number|Decimal} initialVirtualToken - 初始虚拟Token储备量
423
+ * @returns {[bigint, bigint]|null} 成功返回 [需卖出token数量, 获得SOL数量], 失败返回null
424
+ */
425
+ static sellFromPriceToPriceWithParams(startHighPrice, endLowPrice, initialVirtualSol, initialVirtualToken) {
426
+ // Convert to Decimal for calculation
427
+ const startPriceDec = this.u128ToDecimal(startHighPrice);
428
+ const endPriceDec = this.u128ToDecimal(endLowPrice);
429
+
430
+ // Ensure starting price is higher than ending price
431
+ if (startPriceDec.lte(endPriceDec)) {
432
+ return null;
433
+ }
434
+
435
+ // Use custom k value
436
+ const k = this.calculateK(initialVirtualSol, initialVirtualToken);
437
+
438
+ // Calculate reserves for starting and ending states
439
+ const startReserves = this.calculateReservesByPrice(startPriceDec, k);
440
+ const endReserves = this.calculateReservesByPrice(endPriceDec, k);
441
+
442
+ if (!startReserves || !endReserves) {
443
+ return null;
444
+ }
445
+
446
+ const [startSolReserve, startTokenReserve] = startReserves;
447
+ const [endSolReserve, endTokenReserve] = endReserves;
448
+
449
+ // Calculate token amount to sell (increase in token reserves)
450
+ const tokenInputAmount = endTokenReserve.sub(startTokenReserve);
451
+
452
+ // Calculate SOL amount to obtain (decrease in SOL reserves)
453
+ const solOutputAmount = startSolReserve.sub(endSolReserve);
454
+
455
+ // Check if calculation results are valid
456
+ if (tokenInputAmount.lte(0) || solOutputAmount.lte(0)) {
457
+ return null;
458
+ }
459
+
460
+ // Convert back to u64
461
+ // token uses 9-digit precision rounded up, SOL uses 9-digit precision rounded down
462
+ const tokenAmountU64 = this.tokenDecimalToU64Ceil(tokenInputAmount);
463
+ const solAmountU64 = this.solDecimalToU64(solOutputAmount);
464
+
465
+ if (tokenAmountU64 === null || solAmountU64 === null) {
466
+ return null;
467
+ }
468
+
469
+ return [tokenAmountU64, solAmountU64];
470
+ }
471
+
472
+ /**
473
+ * 使用默认参数计算从高价卖出到低价(向后兼容)
474
+ * Calculate SOL amount obtained when selling tokens from high to low price
475
+ *
476
+ * @param {bigint|string|number} startHighPrice - Starting price (higher)
477
+ * @param {bigint|string|number} endLowPrice - Target price (lower)
478
+ * @returns {[bigint, bigint]|null} Returns [token amount to sell, SOL amount to obtain] on success, null on failure
479
+ * token amount in 9-digit precision rounded up; SOL amount in 9-digit precision rounded down
480
+ */
481
+ static sellFromPriceToPrice(startHighPrice, endLowPrice) {
482
+ return this.sellFromPriceToPriceWithParams(
483
+ startHighPrice,
484
+ endLowPrice,
485
+ this.INITIAL_SOL_RESERVE_DECIMAL,
486
+ this.INITIAL_TOKEN_RESERVE_DECIMAL
487
+ );
488
+ }
489
+
490
+ /**
491
+ * 根据价格和自定义k值计算储备量
492
+ * @param {bigint|string} price - u128格式价格
493
+ * @param {Decimal} k - 自定义k值
494
+ * @returns {{solReserve: Decimal, tokenReserve: Decimal}|null}
495
+ */
496
+ static priceToReservesWithK(price, k) {
497
+ const priceDecimal = this.u128ToDecimal(price);
498
+
499
+ // Check if input parameters are valid
500
+ if (priceDecimal.lte(0) || k.lte(0)) {
501
+ return null;
502
+ }
503
+
504
+ // Minimum price check to prevent overflow
505
+ if (priceDecimal.lt(this.INITIAL_MIN_PRICE_DECIMAL)) {
506
+ return null;
507
+ }
508
+
509
+ // token_reserve = sqrt(k / price)
510
+ const tokenReserve = k.div(priceDecimal).sqrt();
511
+
512
+ // sol_reserve = k / token_reserve
513
+ const solReserve = k.div(tokenReserve);
514
+
515
+ if (tokenReserve.isNaN() || solReserve.isNaN()) {
516
+ return null;
517
+ }
518
+
519
+ return {
520
+ solReserve,
521
+ tokenReserve
522
+ };
523
+ }
524
+
525
+ /**
526
+ * 根据价格和自定义初始储备量计算储备量
527
+ * @param {bigint|string} price - u128格式价格
528
+ * @param {string|number|Decimal} initialVirtualSol - 初始虚拟SOL储备量
529
+ * @param {string|number|Decimal} initialVirtualToken - 初始虚拟Token储备量
530
+ * @returns {{solReserve: Decimal, tokenReserve: Decimal}|null}
531
+ */
532
+ static priceToReservesWithParams(price, initialVirtualSol, initialVirtualToken) {
533
+ const k = this.calculateK(initialVirtualSol, initialVirtualToken);
534
+ return this.priceToReservesWithK(price, k);
535
+ }
536
+
537
+ /**
538
+ * 使用默认参数根据价格计算储备量(向后兼容)
539
+ * @param {bigint|string} price - u128格式价格
540
+ * @returns {{solReserve: Decimal, tokenReserve: Decimal}|null}
541
+ */
542
+ static priceToReserves(price) {
543
+ return this.priceToReservesWithParams(
544
+ price,
545
+ this.INITIAL_SOL_RESERVE_DECIMAL,
546
+ this.INITIAL_TOKEN_RESERVE_DECIMAL
547
+ );
548
+ }
549
+
550
+ /**
551
+ * Calculate reserves given a price
552
+ *
553
+ * @param {Decimal} price - Price, representing SOL amount for 1 token
554
+ * @param {Decimal} k - Constant product
555
+ * @returns {[Decimal, Decimal]|null} Returns [SOL reserve, token reserve] on success, null on failure
556
+ */
557
+ static calculateReservesByPrice(price, k) {
558
+ // Check if input parameters are valid
559
+ if (price.lte(0) || k.lte(0)) {
560
+ return null;
561
+ }
562
+
563
+ // Minimum price check to prevent overflow
564
+ if (price.lt(this.INITIAL_MIN_PRICE_DECIMAL)) {
565
+ return null;
566
+ }
567
+
568
+ // Warning for extremely small prices that may have precision issues
569
+ const warningPrice = new Decimal('0.0000001'); // 10^-7
570
+ if (price.lt(warningPrice)) {
571
+ // Only log once per execution to avoid spam (use a static flag)
572
+ if (!this._tinyPriceWarningShown) {
573
+ console.warn(
574
+ `[CurveAMM] 警告: 价格非常小 (${price.toString()}),` +
575
+ `计算精度可能受影响。建议验证结果。`
576
+ );
577
+ this._tinyPriceWarningShown = true;
578
+ }
579
+ }
580
+
581
+ // According to AMM formula: k = sol_reserve * token_reserve
582
+ // and price = sol_reserve / token_reserve
583
+ // We get: sol_reserve = price * token_reserve
584
+ // Substituting into k formula: k = price * token_reserve^2
585
+ // Therefore: token_reserve = sqrt(k / price)
586
+ // sol_reserve = sqrt(k * price)
587
+
588
+ // Calculate k / price
589
+ const kDivPrice = k.div(price);
590
+
591
+ // Calculate token_reserve = sqrt(k / price)
592
+ const tokenReserve = kDivPrice.sqrt();
593
+
594
+ // Calculate sol_reserve = price * token_reserve
595
+ const solReserve = price.mul(tokenReserve);
596
+
597
+ if (tokenReserve.isNaN() || solReserve.isNaN()) {
598
+ return null;
599
+ }
600
+
601
+ // Verify calculation accuracy by checking if k is preserved (optional but recommended)
602
+ // Only check for tiny prices to minimize performance impact
603
+ if (price.lt(warningPrice)) {
604
+ const verifyK = solReserve.mul(tokenReserve);
605
+ const kDiff = verifyK.sub(k).abs().div(k);
606
+
607
+ // If k value deviation exceeds 0.1%, log a warning
608
+ if (kDiff.gt('0.001')) {
609
+ console.warn(
610
+ `[CurveAMM] 储备量计算精度偏差: ${kDiff.mul(100).toFixed(4)}%,` +
611
+ `价格=${price.toString()}`
612
+ );
613
+ }
614
+ }
615
+
616
+ return [solReserve, tokenReserve];
617
+ }
618
+
619
+ /**
620
+ * 根据自定义初始储备量,基于起始价格和SOL输入量计算token输出量和结束价格
621
+ * @param {bigint|string|number} startLowPrice - 起始价格
622
+ * @param {bigint|string|number} solInputAmount - 用于买入的SOL数量
623
+ * @param {string|number|Decimal} initialVirtualSol - 初始虚拟SOL储备量
624
+ * @param {string|number|Decimal} initialVirtualToken - 初始虚拟Token储备量
625
+ * @returns {[bigint, bigint]|null} 成功返回 [交易后价格, 获得的token数量], 失败返回null
626
+ */
627
+ static buyFromPriceWithSolInputWithParams(startLowPrice, solInputAmount, initialVirtualSol, initialVirtualToken) {
628
+ // Convert to Decimal for calculation
629
+ const startPriceDec = this.u128ToDecimal(startLowPrice);
630
+ const solInputDec = this.u64ToSolDecimal(solInputAmount);
631
+
632
+ // Check if input parameters are valid
633
+ if (startPriceDec.lte(0)) {
634
+ return null;
635
+ }
636
+
637
+ // If SOL input amount is 0, return unchanged price and token output of 0
638
+ if (solInputDec.eq(0)) {
639
+ const endPriceU128 = this.decimalToU128(startPriceDec);
640
+ if (endPriceU128 === null) return null;
641
+ return [endPriceU128, 0n];
642
+ }
643
+
644
+ if (solInputDec.lt(0)) {
645
+ return null;
646
+ }
647
+
648
+ // Use custom k value
649
+ const k = this.calculateK(initialVirtualSol, initialVirtualToken);
650
+
651
+ // Calculate reserves for starting state
652
+ const startReserves = this.calculateReservesByPrice(startPriceDec, k);
653
+ if (!startReserves) return null;
654
+
655
+ const [startSolReserve, startTokenReserve] = startReserves;
656
+
657
+ // Calculate SOL reserves for ending state
658
+ const endSolReserve = startSolReserve.add(solInputDec);
659
+
660
+ // Calculate token reserves for ending state according to AMM formula
661
+ const endTokenReserve = k.div(endSolReserve);
662
+
663
+ // Calculate token output amount
664
+ const tokenOutputAmount = startTokenReserve.sub(endTokenReserve);
665
+
666
+ // Calculate ending price
667
+ const endPrice = endSolReserve.div(endTokenReserve);
668
+
669
+ // Check if calculation results are valid
670
+ if (tokenOutputAmount.lte(0) || endPrice.lte(0)) {
671
+ return null;
672
+ }
673
+
674
+ // Convert back to appropriate types with required rounding
675
+ const endPriceU128 = this.decimalToU128(endPrice); // Price rounded down
676
+ const tokenAmountU64 = this.tokenDecimalToU64(tokenOutputAmount); // Token rounded down
677
+
678
+ if (endPriceU128 === null || tokenAmountU64 === null) {
679
+ return null;
680
+ }
681
+
682
+ return [endPriceU128, tokenAmountU64];
683
+ }
684
+
685
+ /**
686
+ * 使用默认参数计算token输出量和结束价格(向后兼容)
687
+ * Calculate token output amount and ending price based on starting price and SOL input amount
688
+ *
689
+ * @param {bigint|string|number} startLowPrice - Starting price
690
+ * @param {bigint|string|number} solInputAmount - SOL amount for buying
691
+ * @returns {[bigint, bigint]|null} Returns [price after transaction, token amount obtained] on success, null on failure
692
+ * Price rounded down, token amount rounded down
693
+ */
694
+ static buyFromPriceWithSolInput(startLowPrice, solInputAmount) {
695
+ return this.buyFromPriceWithSolInputWithParams(
696
+ startLowPrice,
697
+ solInputAmount,
698
+ this.INITIAL_SOL_RESERVE_DECIMAL,
699
+ this.INITIAL_TOKEN_RESERVE_DECIMAL
700
+ );
701
+ }
702
+
703
+ /**
704
+ * 根据自定义初始储备量,基于起始价格和token输入量计算SOL输出量和结束价格
705
+ * @param {bigint|string|number} startHighPrice - 起始价格
706
+ * @param {bigint|string|number} tokenInputAmount - 要卖出的token数量
707
+ * @param {string|number|Decimal} initialVirtualSol - 初始虚拟SOL储备量
708
+ * @param {string|number|Decimal} initialVirtualToken - 初始虚拟Token储备量
709
+ * @returns {[bigint, bigint]|null} 成功返回 [交易后价格, 获得的SOL数量], 失败返回null
710
+ */
711
+ static sellFromPriceWithTokenInputWithParams(startHighPrice, tokenInputAmount, initialVirtualSol, initialVirtualToken) {
712
+ // Convert to Decimal for calculation
713
+ const startPriceDec = this.u128ToDecimal(startHighPrice);
714
+ const tokenInputDec = this.u64ToTokenDecimal(tokenInputAmount);
715
+
716
+ // Check if input parameters are valid
717
+ if (startPriceDec.lte(0)) {
718
+ return null;
719
+ }
720
+
721
+ // If token input amount is 0, return unchanged price and SOL output of 0
722
+ if (tokenInputDec.eq(0)) {
723
+ const endPriceU128 = this.decimalToU128(startPriceDec);
724
+ if (endPriceU128 === null) return null;
725
+ return [endPriceU128, 0n];
726
+ }
727
+
728
+ if (tokenInputDec.lt(0)) {
729
+ return null;
730
+ }
731
+
732
+ // Use custom k value
733
+ const k = this.calculateK(initialVirtualSol, initialVirtualToken);
734
+
735
+ // Calculate reserves for starting state
736
+ const startReserves = this.calculateReservesByPrice(startPriceDec, k);
737
+ if (!startReserves) return null;
738
+
739
+ const [startSolReserve, startTokenReserve] = startReserves;
740
+
741
+ // Calculate token reserves for ending state
742
+ const endTokenReserve = startTokenReserve.add(tokenInputDec);
743
+
744
+ // 根据AMM公式计算结束状态的SOL储备量
745
+ const endSolReserve = k.div(endTokenReserve);
746
+
747
+ // Calculate SOL output amount
748
+ const solOutputAmount = startSolReserve.sub(endSolReserve);
749
+
750
+ // Calculate ending price
751
+ const endPrice = endSolReserve.div(endTokenReserve);
752
+
753
+ // Check if calculation results are valid
754
+ if (solOutputAmount.lte(0) || endPrice.lte(0)) {
755
+ return null;
756
+ }
757
+
758
+ // Convert back to appropriate types with required rounding
759
+ const endPriceU128 = this.decimalToU128(endPrice); // Price rounded down
760
+ const solAmountU64 = this.solDecimalToU64(solOutputAmount); // SOL rounded down
761
+
762
+ if (endPriceU128 === null || solAmountU64 === null) {
763
+ return null;
764
+ }
765
+
766
+ return [endPriceU128, solAmountU64];
767
+ }
768
+
769
+ /**
770
+ * 使用默认参数计算SOL输出量和结束价格(向后兼容)
771
+ * Calculate SOL output amount and ending price based on starting price and token input amount
772
+ *
773
+ * @param {bigint|string|number} startHighPrice - Starting price
774
+ * @param {bigint|string|number} tokenInputAmount - Token amount to sell
775
+ * @returns {[bigint, bigint]|null} Returns [price after transaction, SOL amount obtained] on success, null on failure
776
+ * Price rounded down, SOL amount rounded down
777
+ */
778
+ static sellFromPriceWithTokenInput(startHighPrice, tokenInputAmount) {
779
+ return this.sellFromPriceWithTokenInputWithParams(
780
+ startHighPrice,
781
+ tokenInputAmount,
782
+ this.INITIAL_SOL_RESERVE_DECIMAL,
783
+ this.INITIAL_TOKEN_RESERVE_DECIMAL
784
+ );
785
+ }
786
+
787
+ /**
788
+ * Calculate required SOL input amount and ending price based on starting price and expected token output amount
789
+ *
790
+ * @param {bigint|string|number} startLowPrice - Starting price
791
+ * @param {bigint|string|number} tokenOutputAmount - Desired token amount to obtain
792
+ * @returns {[bigint, bigint]|null} Returns [price after transaction, SOL amount to pay] on success, null on failure
793
+ * Price rounded down, SOL amount rounded up
794
+ */
795
+ static buyFromPriceWithTokenOutput(startLowPrice, tokenOutputAmount) {
796
+ // Convert to Decimal for calculation
797
+ const startPriceDec = this.u128ToDecimal(startLowPrice);
798
+ const tokenOutputDec = this.u64ToTokenDecimal(tokenOutputAmount);
799
+
800
+ // Check if input parameters are valid
801
+ if (startPriceDec.lte(0)) {
802
+ return null;
803
+ }
804
+
805
+ // If token output amount is 0, return unchanged price and SOL input of 0
806
+ if (tokenOutputDec.eq(0)) {
807
+ const endPriceU128 = this.decimalToU128(startPriceDec);
808
+ if (endPriceU128 === null) return null;
809
+ return [endPriceU128, 0n];
810
+ }
811
+
812
+ if (tokenOutputDec.lt(0)) {
813
+ return null;
814
+ }
815
+
816
+ // Use initial k value
817
+ const k = this.calculateInitialK();
818
+
819
+ // Calculate reserves for starting state
820
+ const startReserves = this.calculateReservesByPrice(startPriceDec, k);
821
+ if (!startReserves) return null;
822
+
823
+ const [startSolReserve, startTokenReserve] = startReserves;
824
+
825
+ // Calculate token reserves for ending state
826
+ const endTokenReserve = startTokenReserve.sub(tokenOutputDec);
827
+
828
+ //console.log('buyFromPriceWithTokenOutput 结束token储备 = 起始token储备 - token输出量:', endTokenReserve.toString());
829
+
830
+ // Check if token reserves are sufficient
831
+ if (endTokenReserve.lte(0)) {
832
+ return null;
833
+ }
834
+
835
+ // 根据AMM公式计算结束状态的SOL储备量
836
+ const endSolReserve = k.div(endTokenReserve);
837
+
838
+ // Calculate required SOL input amount
839
+ const solInputAmount = endSolReserve.sub(startSolReserve);
840
+
841
+ // Calculate ending price
842
+ const endPrice = endSolReserve.div(endTokenReserve);
843
+
844
+ // Check if calculation results are valid
845
+ if (solInputAmount.lte(0) || endPrice.lte(0)) {
846
+ return null;
847
+ }
848
+
849
+ // Convert back to appropriate types with required rounding
850
+ const endPriceU128 = this.decimalToU128(endPrice); // Price rounded down
851
+ const solAmountU64 = this.solDecimalToU64Ceil(solInputAmount); // SOL rounded up
852
+
853
+ if (endPriceU128 === null || solAmountU64 === null) {
854
+ return null;
855
+ }
856
+
857
+ return [endPriceU128, solAmountU64];
858
+ }
859
+
860
+ /**
861
+ * 基于起始价格和期望token输出量计算需要的SOL输入量和结束价格(带自定义流动池参数)
862
+ * Based on starting price and desired token output, calculate required SOL input and ending price (with custom pool params)
863
+ *
864
+ * @param {bigint|string|number} startLowPrice - Starting price (lower)
865
+ * @param {bigint|string|number} tokenOutputAmount - Desired token amount to obtain
866
+ * @param {string|number|Decimal} initialVirtualSol - Initial virtual SOL reserve
867
+ * @param {string|number|Decimal} initialVirtualToken - Initial virtual token reserve
868
+ * @returns {[bigint, bigint]|null} Returns [price after transaction, SOL amount to pay] on success, null on failure
869
+ * Price rounded down, SOL amount rounded up
870
+ */
871
+ static buyFromPriceWithTokenOutputWithParams(startLowPrice, tokenOutputAmount, initialVirtualSol, initialVirtualToken) {
872
+ // Convert to Decimal for calculation
873
+ const startPriceDec = this.u128ToDecimal(startLowPrice);
874
+ const tokenOutputDec = this.u64ToTokenDecimal(tokenOutputAmount);
875
+
876
+ // Check if input parameters are valid
877
+ if (startPriceDec.lte(0)) {
878
+ return null;
879
+ }
880
+
881
+ // If token output amount is 0, return unchanged price and SOL input of 0
882
+ if (tokenOutputDec.eq(0)) {
883
+ const endPriceU128 = this.decimalToU128(startPriceDec);
884
+ if (endPriceU128 === null) return null;
885
+ return [endPriceU128, 0n];
886
+ }
887
+
888
+ if (tokenOutputDec.lt(0)) {
889
+ return null;
890
+ }
891
+
892
+ // Use custom pool parameters to calculate k value
893
+ const k = this.calculateK(initialVirtualSol, initialVirtualToken);
894
+
895
+ // Calculate reserves for starting state
896
+ const startReserves = this.calculateReservesByPrice(startPriceDec, k);
897
+ if (!startReserves) return null;
898
+
899
+ const [startSolReserve, startTokenReserve] = startReserves;
900
+
901
+ // Calculate token reserves for ending state
902
+ const endTokenReserve = startTokenReserve.sub(tokenOutputDec);
903
+
904
+ // Check if token reserves are sufficient
905
+ if (endTokenReserve.lte(0)) {
906
+ return null;
907
+ }
908
+
909
+ // Calculate SOL reserve for ending state using AMM formula
910
+ const endSolReserve = k.div(endTokenReserve);
911
+
912
+ // Calculate required SOL input amount
913
+ const solInputAmount = endSolReserve.sub(startSolReserve);
914
+
915
+ // Calculate ending price
916
+ const endPrice = endSolReserve.div(endTokenReserve);
917
+
918
+ // Check if calculation results are valid
919
+ if (solInputAmount.lte(0) || endPrice.lte(0)) {
920
+ return null;
921
+ }
922
+
923
+ // Convert back to appropriate types with required rounding
924
+ const endPriceU128 = this.decimalToU128(endPrice); // Price rounded down
925
+ const solAmountU64 = this.solDecimalToU64Ceil(solInputAmount); // SOL rounded up
926
+
927
+ if (endPriceU128 === null || solAmountU64 === null) {
928
+ return null;
929
+ }
930
+
931
+ return [endPriceU128, solAmountU64];
932
+ }
933
+
934
+ /**
935
+ * Calculate required token input amount and ending price based on starting price and expected SOL output amount
936
+ *
937
+ * @param {bigint|string|number} startHighPrice - Starting price
938
+ * @param {bigint|string|number} solOutputAmount - Desired SOL amount to obtain
939
+ * @returns {[bigint, bigint]|null} Returns [price after transaction, token amount to pay] on success, null on failure
940
+ * Price rounded down, token amount rounded up
941
+ */
942
+ static sellFromPriceWithSolOutput(startHighPrice, solOutputAmount) {
943
+ // Convert to Decimal for calculation
944
+ const startPriceDec = this.u128ToDecimal(startHighPrice);
945
+ const solOutputDec = this.u64ToSolDecimal(solOutputAmount);
946
+
947
+ // Check if input parameters are valid
948
+ if (startPriceDec.lte(0)) {
949
+ return null;
950
+ }
951
+
952
+ // If SOL output amount is 0, return unchanged price and token input of 0
953
+ if (solOutputDec.eq(0)) {
954
+ const endPriceU128 = this.decimalToU128(startPriceDec);
955
+ if (endPriceU128 === null) return null;
956
+ return [endPriceU128, 0n];
957
+ }
958
+
959
+ if (solOutputDec.lt(0)) {
960
+ return null;
961
+ }
962
+
963
+ // Use initial k value
964
+ const k = this.calculateInitialK();
965
+
966
+ // Calculate reserves for starting state
967
+ const startReserves = this.calculateReservesByPrice(startPriceDec, k);
968
+ if (!startReserves) return null;
969
+
970
+ const [startSolReserve, startTokenReserve] = startReserves;
971
+
972
+ // Calculate SOL reserves for ending state
973
+ const endSolReserve = startSolReserve.sub(solOutputDec);
974
+
975
+ // Check if SOL reserves are sufficient
976
+ if (endSolReserve.lte(0)) {
977
+ return null;
978
+ }
979
+
980
+ // Calculate token reserves for ending state according to AMM formula
981
+ const endTokenReserve = k.div(endSolReserve);
982
+
983
+ // Calculate required token input amount
984
+ const tokenInputAmount = endTokenReserve.sub(startTokenReserve);
985
+
986
+ // Calculate ending price
987
+ const endPrice = endSolReserve.div(endTokenReserve);
988
+
989
+ // Check if calculation results are valid
990
+ if (tokenInputAmount.lte(0) || endPrice.lte(0)) {
991
+ return null;
992
+ }
993
+
994
+ // Convert back to appropriate types with required rounding
995
+ const endPriceU128 = this.decimalToU128(endPrice); // Price rounded down
996
+ const tokenAmountU64 = this.tokenDecimalToU64Ceil(tokenInputAmount); // Token rounded up
997
+
998
+ if (endPriceU128 === null || tokenAmountU64 === null) {
999
+ return null;
1000
+ }
1001
+
1002
+ return [endPriceU128, tokenAmountU64];
1003
+ }
1004
+
1005
+ /**
1006
+ * 基于起始价格和期望SOL输出量计算需要的token输入量和结束价格(带自定义流动池参数)
1007
+ * Based on starting price and desired SOL output, calculate required token input and ending price (with custom pool params)
1008
+ *
1009
+ * @param {bigint|string|number} startHighPrice - Starting price (higher)
1010
+ * @param {bigint|string|number} solOutputAmount - Desired SOL amount to obtain
1011
+ * @param {string|number|Decimal} initialVirtualSol - Initial virtual SOL reserve
1012
+ * @param {string|number|Decimal} initialVirtualToken - Initial virtual token reserve
1013
+ * @returns {[bigint, bigint]|null} Returns [price after transaction, token amount to pay] on success, null on failure
1014
+ * Price rounded down, token amount rounded up
1015
+ */
1016
+ static sellFromPriceWithSolOutputWithParams(startHighPrice, solOutputAmount, initialVirtualSol, initialVirtualToken) {
1017
+ // Convert to Decimal for calculation
1018
+ const startPriceDec = this.u128ToDecimal(startHighPrice);
1019
+ const solOutputDec = this.u64ToSolDecimal(solOutputAmount);
1020
+
1021
+ // Check if input parameters are valid
1022
+ if (startPriceDec.lte(0)) {
1023
+ return null;
1024
+ }
1025
+
1026
+ // If SOL output amount is 0, return unchanged price and token input of 0
1027
+ if (solOutputDec.eq(0)) {
1028
+ const endPriceU128 = this.decimalToU128(startPriceDec);
1029
+ if (endPriceU128 === null) return null;
1030
+ return [endPriceU128, 0n];
1031
+ }
1032
+
1033
+ if (solOutputDec.lt(0)) {
1034
+ return null;
1035
+ }
1036
+
1037
+ // Use custom pool parameters to calculate k value
1038
+ const k = this.calculateK(initialVirtualSol, initialVirtualToken);
1039
+
1040
+ // Calculate reserves for starting state
1041
+ const startReserves = this.calculateReservesByPrice(startPriceDec, k);
1042
+ if (!startReserves) return null;
1043
+
1044
+ const [startSolReserve, startTokenReserve] = startReserves;
1045
+
1046
+ // Calculate SOL reserves for ending state
1047
+ const endSolReserve = startSolReserve.sub(solOutputDec);
1048
+
1049
+ // Check if SOL reserves are sufficient
1050
+ if (endSolReserve.lte(0)) {
1051
+ return null;
1052
+ }
1053
+
1054
+ // Calculate token reserves for ending state according to AMM formula
1055
+ const endTokenReserve = k.div(endSolReserve);
1056
+
1057
+ // Calculate required token input amount
1058
+ const tokenInputAmount = endTokenReserve.sub(startTokenReserve);
1059
+
1060
+ // Calculate ending price
1061
+ const endPrice = endSolReserve.div(endTokenReserve);
1062
+
1063
+ // Check if calculation results are valid
1064
+ if (tokenInputAmount.lte(0) || endPrice.lte(0)) {
1065
+ return null;
1066
+ }
1067
+
1068
+ // Convert back to appropriate types with required rounding
1069
+ const endPriceU128 = this.decimalToU128(endPrice); // Price rounded down
1070
+ const tokenAmountU64 = this.tokenDecimalToU64Ceil(tokenInputAmount); // Token rounded up
1071
+
1072
+ if (endPriceU128 === null || tokenAmountU64 === null) {
1073
+ return null;
1074
+ }
1075
+
1076
+ return [endPriceU128, tokenAmountU64];
1077
+ }
1078
+
1079
+ /**
1080
+ * Calculate remaining amount after deducting fees
1081
+ *
1082
+ * @param {bigint|string|number} amount - Original amount
1083
+ * @param {number} fee - Fee rate, expressed with FEE_DENOMINATOR as denominator
1084
+ * Example: 1000 represents 1% fee (1000/100000)
1085
+ * 2000 represents 2% fee (2000/100000)
1086
+ * @returns {bigint|null} Returns remaining amount after deducting fees on success, null on failure
1087
+ * Fee calculation uses floor rounding, which is the most favorable calculation method for users
1088
+ */
1089
+ static calculateAmountAfterFee(amount, fee) {
1090
+ // Convert input parameters to BigInt
1091
+ try {
1092
+ const amountBigInt = BigInt(amount.toString());
1093
+ const feeBigInt = BigInt(fee);
1094
+
1095
+ // Check if fee rate is valid (must be less than or equal to 10%)
1096
+ if (feeBigInt > MAX_FEE_RATE) {
1097
+ return null;
1098
+ }
1099
+
1100
+ // Calculate fee amount: amount * fee / FEE_DENOMINATOR
1101
+ const feeAmount = (amountBigInt * feeBigInt) / FEE_DENOMINATOR;
1102
+
1103
+ // Calculate remaining amount after deducting fees
1104
+ const amountAfterFee = amountBigInt - feeAmount;
1105
+
1106
+ return amountAfterFee;
1107
+ } catch (error) {
1108
+ return null;
1109
+ }
1110
+ }
1111
+
1112
+ /**
1113
+ * Calculate total amount including fee (with ceiling division, matching Rust contract)
1114
+ *
1115
+ * @param {bigint|string|number} solAmount - Net amount (base amount)
1116
+ * @param {number} fee - Fee rate, expressed with FEE_DENOMINATOR as denominator
1117
+ * Example: 1000 represents 1% fee (1000/100000)
1118
+ * 2000 represents 2% fee (2000/100000)
1119
+ * @returns {bigint|null} Returns total amount including fee on success, null on failure
1120
+ * Total amount calculation uses ceiling division (rounding up), matching Rust contract behavior
1121
+ */
1122
+ static calculateTotalAmountWithFee(solAmount, fee) {
1123
+ try {
1124
+ const amount = BigInt(solAmount.toString());
1125
+ const feeBig = BigInt(fee);
1126
+
1127
+ // Check if fee rate is valid (must be less than or equal to 10%)
1128
+ if (feeBig > MAX_FEE_RATE) {
1129
+ return null;
1130
+ }
1131
+
1132
+ // Ceiling division: (amount * (FEE_DENOMINATOR + fee) + FEE_DENOMINATOR - 1) / FEE_DENOMINATOR
1133
+ const numerator = amount * (FEE_DENOMINATOR + feeBig);
1134
+ const totalAmount = (numerator + FEE_DENOMINATOR - 1n) / FEE_DENOMINATOR;
1135
+
1136
+ return totalAmount;
1137
+ } catch (error) {
1138
+ return null;
1139
+ }
1140
+ }
1141
+
1142
+ /**
1143
+ * Convert u128 price to readable decimal string format for display
1144
+ *
1145
+ * @param {bigint|string|number} price - u128 price to be converted
1146
+ * @param {number} decimalPlaces - Number of decimal places to retain, default is 28
1147
+ * @returns {string} Formatted price string
1148
+ */
1149
+ static formatPriceForDisplay(price, decimalPlaces = 28) {
1150
+ if (typeof price === 'bigint') {
1151
+ price = price.toString();
1152
+ }
1153
+ const priceDecimal = new Decimal(price);
1154
+ const convertedPrice = priceDecimal.div(this.PRICE_PRECISION_FACTOR_DECIMAL);
1155
+ return convertedPrice.toFixed(decimalPlaces);
1156
+ }
1157
+
1158
+ /**
1159
+ * Create complete price display string, including both integer and decimal formats
1160
+ *
1161
+ * @param {bigint|string|number} price - u128 price to be converted
1162
+ * @param {number} decimalPlaces - Number of decimal places to retain, default is 28
1163
+ * @returns {string} Formatted complete price string, format: "integer price (decimal price)"
1164
+ */
1165
+ static createPriceDisplayString(price, decimalPlaces = 28) {
1166
+ const integerPrice = (typeof price === 'bigint') ? price.toString() : price.toString();
1167
+ const decimalPrice = this.formatPriceForDisplay(price, decimalPlaces);
1168
+ return `${integerPrice} (${decimalPrice})`;
1169
+ }
1170
+
1171
+ /**
1172
+ * Calculate price based on liquidity pool reserves (how much SOL 1 token is worth)
1173
+ *
1174
+ * @param {bigint|string|number|BN} lpTokenReserve - Token reserves in liquidity pool (u64 format, 9-digit precision)
1175
+ * @param {bigint|string|number|BN} lpSolReserve - SOL reserves in liquidity pool (u64 format, 9-digit precision)
1176
+ * @returns {string|null} Returns 28-digit decimal price string on success, null on failure
1177
+ */
1178
+ static calculatePoolPrice(lpTokenReserve, lpSolReserve) {
1179
+ try {
1180
+ // Handle BN objects, convert to string
1181
+ let tokenReserveStr = lpTokenReserve;
1182
+ let solReserveStr = lpSolReserve;
1183
+
1184
+ // If it's a BN object, use toString() method
1185
+ if (lpTokenReserve && typeof lpTokenReserve === 'object' && lpTokenReserve.toString) {
1186
+ tokenReserveStr = lpTokenReserve.toString();
1187
+ }
1188
+ if (lpSolReserve && typeof lpSolReserve === 'object' && lpSolReserve.toString) {
1189
+ solReserveStr = lpSolReserve.toString();
1190
+ }
1191
+
1192
+ // Convert to Decimal for calculation
1193
+ const tokenReserveDec = this.u64ToTokenDecimal(tokenReserveStr);
1194
+ const solReserveDec = this.u64ToSolDecimal(solReserveStr);
1195
+
1196
+ // Check if reserves are valid
1197
+ if (tokenReserveDec.lte(0) || solReserveDec.lte(0)) {
1198
+ return null;
1199
+ }
1200
+
1201
+ // Calculate price: 1 token = SOL reserve / Token reserve
1202
+ const price = solReserveDec.div(tokenReserveDec);
1203
+
1204
+ // Return 28-digit decimal string
1205
+ return price.toFixed(28);
1206
+ } catch (error) {
1207
+ return null;
1208
+ }
1209
+ }
1210
+ }
1211
+
1212
+ module.exports = CurveAMM;
1213
+
1214
+
1215
+