pinpet-sdk 0.1.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.
- package/README.md +674 -0
- package/dist/index.d.ts +342 -0
- package/dist/pinpet-sdk.cjs.js +50648 -0
- package/dist/pinpet-sdk.cjs.js.map +1 -0
- package/dist/pinpet-sdk.esm.js +39265 -0
- package/dist/pinpet-sdk.esm.js.map +1 -0
- package/dist/pinpet-sdk.js +39277 -0
- package/dist/pinpet-sdk.js.map +1 -0
- package/package.json +67 -0
- package/src/idl/pinpet.json +3511 -0
- package/src/index.js +43 -0
- package/src/modules/chain.js +1102 -0
- package/src/modules/close.js +0 -0
- package/src/modules/fast.js +431 -0
- package/src/modules/param.js +171 -0
- package/src/modules/simulator/buy.js_del +711 -0
- package/src/modules/simulator/buy_sell_token.js +300 -0
- package/src/modules/simulator/calcLiq.js +701 -0
- package/src/modules/simulator/long_shrot_stop.js +602 -0
- package/src/modules/simulator/sell.js_del +323 -0
- package/src/modules/simulator/stop_loss_utils.js +223 -0
- package/src/modules/simulator/utils.js +44 -0
- package/src/modules/simulator.js +133 -0
- package/src/modules/token.js +140 -0
- package/src/modules/trading.js +1087 -0
- package/src/sdk.js +370 -0
- package/src/types/index.d.ts +342 -0
- package/src/utils/constants.js +49 -0
- package/src/utils/curve_amm.js +884 -0
- package/src/utils/orderUtils.js +465 -0
|
@@ -0,0 +1,884 @@
|
|
|
1
|
+
const Decimal = require('decimal.js');
|
|
2
|
+
|
|
3
|
+
// Configure Decimal.js to use 28-bit precision to match rust_decimal
|
|
4
|
+
Decimal.set({ precision: 28 });
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Denominator used for fee calculations (10^5)
|
|
9
|
+
* @type {bigint}
|
|
10
|
+
*/
|
|
11
|
+
const FEE_DENOMINATOR = 100_000n;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Maximum fee rate (10%)
|
|
15
|
+
* @type {bigint}
|
|
16
|
+
*/
|
|
17
|
+
const MAX_FEE_RATE = 10_000n;
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Traditional AMM trading model class
|
|
24
|
+
* Implements constant product (xy=k) algorithm for automated market maker functionality
|
|
25
|
+
* Usage when importing: const CurveAMM = require("../tools/curve_amm");
|
|
26
|
+
*/
|
|
27
|
+
class CurveAMM {
|
|
28
|
+
/**
|
|
29
|
+
* Initial SOL reserve amount, represented as Decimal
|
|
30
|
+
* @type {Decimal}
|
|
31
|
+
*/
|
|
32
|
+
static INITIAL_SOL_RESERVE_DECIMAL = new Decimal('30');
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Initial Token reserve amount, represented as Decimal
|
|
36
|
+
* @type {Decimal}
|
|
37
|
+
*/
|
|
38
|
+
static INITIAL_TOKEN_RESERVE_DECIMAL = new Decimal('1073000000');
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Initial constant K value, represented as Decimal
|
|
42
|
+
* @type {Decimal}
|
|
43
|
+
*/
|
|
44
|
+
static INITIAL_K_DECIMAL = new Decimal('32190000000');
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Minimum price that can appear, below this price may cause overflow
|
|
48
|
+
* @type {Decimal}
|
|
49
|
+
*/
|
|
50
|
+
static INITIAL_MIN_PRICE_DECIMAL = new Decimal('0.000000001');
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Decimal representation of precision factor = 10000000000000000000000000000
|
|
54
|
+
* @type {Decimal}
|
|
55
|
+
*/
|
|
56
|
+
//static PRICE_PRECISION_FACTOR_DECIMAL = new Decimal('10000000000000000000000000000');
|
|
57
|
+
static PRICE_PRECISION_FACTOR_DECIMAL = new Decimal('100000000000000000000000000');
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Decimal representation of Token precision factor = 1000000
|
|
61
|
+
* @type {Decimal}
|
|
62
|
+
*/
|
|
63
|
+
static TOKEN_PRECISION_FACTOR_DECIMAL = new Decimal('1000000');
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Decimal representation of SOL precision factor = 1000000000
|
|
67
|
+
* @type {Decimal}
|
|
68
|
+
*/
|
|
69
|
+
static SOL_PRECISION_FACTOR_DECIMAL = new Decimal('1000000000');
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Maximum price for u128
|
|
74
|
+
* @type {bigint}
|
|
75
|
+
*/
|
|
76
|
+
static MAX_U128_PRICE = 50000000000000000000000000000n;
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Minimum price for u128
|
|
81
|
+
* @type {bigint}
|
|
82
|
+
*/
|
|
83
|
+
static MIN_U128_PRICE = 11958993476234855500n;
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Convert u128 price to Decimal
|
|
90
|
+
*
|
|
91
|
+
* @param {bigint|string|number} price - u128 price to be converted
|
|
92
|
+
* @returns {Decimal} Converted Decimal price
|
|
93
|
+
*/
|
|
94
|
+
static u128ToDecimal(price) {
|
|
95
|
+
if (typeof price === 'bigint') {
|
|
96
|
+
price = price.toString();
|
|
97
|
+
}
|
|
98
|
+
const priceDecimal = new Decimal(price);
|
|
99
|
+
return priceDecimal.div(this.PRICE_PRECISION_FACTOR_DECIMAL);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Convert Decimal price to u128, rounded down
|
|
104
|
+
*
|
|
105
|
+
* @param {Decimal} price - Decimal price to be converted
|
|
106
|
+
* @returns {bigint|null} Converted u128 price, returns null if overflow
|
|
107
|
+
*/
|
|
108
|
+
static decimalToU128(price) {
|
|
109
|
+
const scaled = price.mul(this.PRICE_PRECISION_FACTOR_DECIMAL);
|
|
110
|
+
const floored = scaled.floor();
|
|
111
|
+
if (floored.isNaN() || floored.isNegative() || floored.gt(this.MAX_U128_PRICE.toString())) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
// Use toFixed() to avoid scientific notation
|
|
115
|
+
const flooredStr = floored.toFixed(0);
|
|
116
|
+
return BigInt(flooredStr);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Convert Decimal price to u128, rounded up
|
|
121
|
+
*
|
|
122
|
+
* @param {Decimal} price - Decimal price to be converted
|
|
123
|
+
* @returns {bigint|null} Converted u128 price, returns null if overflow
|
|
124
|
+
*/
|
|
125
|
+
static decimalToU128Ceil(price) {
|
|
126
|
+
const scaled = price.mul(this.PRICE_PRECISION_FACTOR_DECIMAL);
|
|
127
|
+
const ceiled = scaled.ceil();
|
|
128
|
+
if (ceiled.isNaN() || ceiled.isNegative() || ceiled.gt(this.MAX_U128_PRICE.toString())) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
// Use toFixed() to avoid scientific notation
|
|
132
|
+
const ceiledStr = ceiled.toFixed(0);
|
|
133
|
+
return BigInt(ceiledStr);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Convert u64 price to Decimal (legacy compatibility)
|
|
138
|
+
*
|
|
139
|
+
* @param {bigint|string|number} price - u64 price to be converted
|
|
140
|
+
* @returns {Decimal} Converted Decimal price
|
|
141
|
+
* @deprecated Please use u128ToDecimal instead
|
|
142
|
+
*/
|
|
143
|
+
static u64ToDecimal(price) {
|
|
144
|
+
// Directly use old precision factor for conversion
|
|
145
|
+
if (typeof price === 'bigint') {
|
|
146
|
+
price = price.toString();
|
|
147
|
+
}
|
|
148
|
+
const priceDecimal = new Decimal(price);
|
|
149
|
+
// Use old precision factor 10^15
|
|
150
|
+
const oldPrecisionFactor = new Decimal('1000000000000000');
|
|
151
|
+
return priceDecimal.div(oldPrecisionFactor);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Convert Decimal price to u64, rounded down (legacy compatibility)
|
|
156
|
+
*
|
|
157
|
+
* @param {Decimal} price - Decimal price to be converted
|
|
158
|
+
* @returns {bigint|null} Converted u64 price, returns null if overflow
|
|
159
|
+
* @deprecated Please use decimalToU128 instead
|
|
160
|
+
*/
|
|
161
|
+
static decimalToU64(price) {
|
|
162
|
+
// Directly use old precision factor for conversion
|
|
163
|
+
const oldPrecisionFactor = new Decimal('1000000000000000');
|
|
164
|
+
const scaled = price.mul(oldPrecisionFactor);
|
|
165
|
+
const floored = scaled.floor();
|
|
166
|
+
if (floored.isNaN() || floored.isNegative() || floored.gt('18446744073709551615')) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const flooredStr = floored.toFixed(0);
|
|
170
|
+
return BigInt(flooredStr);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Convert Decimal price to u64, rounded up (legacy compatibility)
|
|
175
|
+
*
|
|
176
|
+
* @param {Decimal} price - Decimal price to be converted
|
|
177
|
+
* @returns {bigint|null} Converted u64 price, returns null if overflow
|
|
178
|
+
* @deprecated Please use decimalToU128Ceil instead
|
|
179
|
+
*/
|
|
180
|
+
static decimalToU64Ceil(price) {
|
|
181
|
+
// Directly use old precision factor for conversion
|
|
182
|
+
const oldPrecisionFactor = new Decimal('1000000000000000');
|
|
183
|
+
const scaled = price.mul(oldPrecisionFactor);
|
|
184
|
+
const ceiled = scaled.ceil();
|
|
185
|
+
if (ceiled.isNaN() || ceiled.isNegative() || ceiled.gt('18446744073709551615')) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const ceiledStr = ceiled.toFixed(0);
|
|
189
|
+
return BigInt(ceiledStr);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Convert Decimal token amount to u64, using 6-digit precision, rounded down
|
|
194
|
+
*
|
|
195
|
+
* @param {Decimal} amount - Decimal token amount to be converted
|
|
196
|
+
* @returns {bigint|null} Converted u64 token amount, returns null if overflow
|
|
197
|
+
*/
|
|
198
|
+
static tokenDecimalToU64(amount) {
|
|
199
|
+
const scaled = amount.mul(this.TOKEN_PRECISION_FACTOR_DECIMAL);
|
|
200
|
+
const floored = scaled.floor();
|
|
201
|
+
if (floored.isNaN() || floored.isNegative() || floored.gt('18446744073709551615')) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
const flooredStr = floored.toFixed(0);
|
|
205
|
+
return BigInt(flooredStr);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Convert Decimal token amount to u64, using 6-digit precision, rounded up
|
|
210
|
+
*
|
|
211
|
+
* @param {Decimal} amount - Decimal token amount to be converted
|
|
212
|
+
* @returns {bigint|null} Converted u64 token amount, returns null if overflow
|
|
213
|
+
*/
|
|
214
|
+
static tokenDecimalToU64Ceil(amount) {
|
|
215
|
+
const scaled = amount.mul(this.TOKEN_PRECISION_FACTOR_DECIMAL);
|
|
216
|
+
const ceiled = scaled.ceil();
|
|
217
|
+
if (ceiled.isNaN() || ceiled.isNegative() || ceiled.gt('18446744073709551615')) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
const ceiledStr = ceiled.toFixed(0);
|
|
221
|
+
return BigInt(ceiledStr);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Convert Decimal SOL amount to u64, using 9-digit precision, rounded down
|
|
226
|
+
*
|
|
227
|
+
* @param {Decimal} amount - Decimal SOL amount to be converted
|
|
228
|
+
* @returns {bigint|null} Converted u64 SOL amount, returns null if overflow
|
|
229
|
+
*/
|
|
230
|
+
static solDecimalToU64(amount) {
|
|
231
|
+
const scaled = amount.mul(this.SOL_PRECISION_FACTOR_DECIMAL);
|
|
232
|
+
const floored = scaled.floor();
|
|
233
|
+
if (floored.isNaN() || floored.isNegative() || floored.gt('18446744073709551615')) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
const flooredStr = floored.toFixed(0);
|
|
237
|
+
return BigInt(flooredStr);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Convert Decimal SOL amount to u64, using 9-digit precision, rounded up
|
|
242
|
+
*
|
|
243
|
+
* @param {Decimal} amount - Decimal SOL amount to be converted
|
|
244
|
+
* @returns {bigint|null} Converted u64 SOL amount, returns null if overflow
|
|
245
|
+
*/
|
|
246
|
+
static solDecimalToU64Ceil(amount) {
|
|
247
|
+
const scaled = amount.mul(this.SOL_PRECISION_FACTOR_DECIMAL);
|
|
248
|
+
const ceiled = scaled.ceil();
|
|
249
|
+
if (ceiled.isNaN() || ceiled.isNegative() || ceiled.gt('18446744073709551615')) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
const ceiledStr = ceiled.toFixed(0);
|
|
253
|
+
return BigInt(ceiledStr);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Convert u64 token amount to Decimal, using 6-digit precision
|
|
258
|
+
*
|
|
259
|
+
* @param {bigint|string|number} amount - u64 token amount to be converted
|
|
260
|
+
* @returns {Decimal} Converted Decimal token amount
|
|
261
|
+
*/
|
|
262
|
+
static u64ToTokenDecimal(amount) {
|
|
263
|
+
if (typeof amount === 'bigint') {
|
|
264
|
+
amount = amount.toString();
|
|
265
|
+
}
|
|
266
|
+
const amountDecimal = new Decimal(amount);
|
|
267
|
+
return amountDecimal.div(this.TOKEN_PRECISION_FACTOR_DECIMAL);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Convert u64 SOL amount to Decimal, using 9-digit precision
|
|
272
|
+
*
|
|
273
|
+
* @param {bigint|string|number} amount - u64 SOL amount to be converted
|
|
274
|
+
* @returns {Decimal} Converted Decimal SOL amount
|
|
275
|
+
*/
|
|
276
|
+
static u64ToSolDecimal(amount) {
|
|
277
|
+
if (typeof amount === 'bigint') {
|
|
278
|
+
amount = amount.toString();
|
|
279
|
+
}
|
|
280
|
+
const amountDecimal = new Decimal(amount);
|
|
281
|
+
return amountDecimal.div(this.SOL_PRECISION_FACTOR_DECIMAL);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Calculate initial k value
|
|
286
|
+
*
|
|
287
|
+
* @returns {Decimal} Product k value of initial reserves
|
|
288
|
+
*/
|
|
289
|
+
static calculateInitialK() {
|
|
290
|
+
return this.INITIAL_SOL_RESERVE_DECIMAL.mul(this.INITIAL_TOKEN_RESERVE_DECIMAL);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Get initial price (SOL amount for 1 token)
|
|
295
|
+
*
|
|
296
|
+
* @returns {bigint|null} Initial price in u128 format, returns null if calculation fails
|
|
297
|
+
*/
|
|
298
|
+
static getInitialPrice() {
|
|
299
|
+
// Calculate initial price = initial SOL reserve / initial Token reserve
|
|
300
|
+
const initialPrice = this.INITIAL_SOL_RESERVE_DECIMAL.div(this.INITIAL_TOKEN_RESERVE_DECIMAL);
|
|
301
|
+
|
|
302
|
+
// Convert to u128 format
|
|
303
|
+
return this.decimalToU128(initialPrice);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Calculate SOL required and token amount obtained when buying tokens from low to high price
|
|
308
|
+
*
|
|
309
|
+
* @param {bigint|string|number} startLowPrice - Starting price (lower)
|
|
310
|
+
* @param {bigint|string|number} endHighPrice - Target price (higher)
|
|
311
|
+
* @returns {[bigint, bigint]|null} Returns [SOL amount to invest, token amount to obtain] on success, null on failure
|
|
312
|
+
* SOL amount in 9-digit precision rounded up; token amount in 6-digit precision rounded down
|
|
313
|
+
*/
|
|
314
|
+
static buyFromPriceToPrice(startLowPrice, endHighPrice) {
|
|
315
|
+
// Convert to Decimal for calculation
|
|
316
|
+
const startPriceDec = this.u128ToDecimal(startLowPrice);
|
|
317
|
+
const endPriceDec = this.u128ToDecimal(endHighPrice);
|
|
318
|
+
|
|
319
|
+
// Ensure starting price is lower than ending price
|
|
320
|
+
if (startPriceDec.gte(endPriceDec)) {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Use initial k value
|
|
325
|
+
const k = this.calculateInitialK();
|
|
326
|
+
|
|
327
|
+
// Calculate reserves for starting and ending states
|
|
328
|
+
const startReserves = this.calculateReservesByPrice(startPriceDec, k);
|
|
329
|
+
const endReserves = this.calculateReservesByPrice(endPriceDec, k);
|
|
330
|
+
|
|
331
|
+
if (!startReserves || !endReserves) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const [startSolReserve, startTokenReserve] = startReserves;
|
|
336
|
+
const [endSolReserve, endTokenReserve] = endReserves;
|
|
337
|
+
|
|
338
|
+
// Calculate SOL amount to invest (increase in SOL reserves)
|
|
339
|
+
const solInputAmount = endSolReserve.sub(startSolReserve);
|
|
340
|
+
|
|
341
|
+
// Calculate token amount to obtain (decrease in token reserves)
|
|
342
|
+
const tokenOutputAmount = startTokenReserve.sub(endTokenReserve);
|
|
343
|
+
|
|
344
|
+
// Check if calculation results are valid
|
|
345
|
+
if (solInputAmount.lte(0) || tokenOutputAmount.lte(0)) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Convert back to u64
|
|
350
|
+
// SOL uses 9-digit precision rounded up, token uses 6-digit precision rounded down
|
|
351
|
+
const solAmountU64 = this.solDecimalToU64Ceil(solInputAmount);
|
|
352
|
+
const tokenAmountU64 = this.tokenDecimalToU64(tokenOutputAmount);
|
|
353
|
+
|
|
354
|
+
if (solAmountU64 === null || tokenAmountU64 === null) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return [solAmountU64, tokenAmountU64];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Calculate SOL amount obtained when selling tokens from high to low price
|
|
363
|
+
*
|
|
364
|
+
* @param {bigint|string|number} startHighPrice - Starting price (higher)
|
|
365
|
+
* @param {bigint|string|number} endLowPrice - Target price (lower)
|
|
366
|
+
* @returns {[bigint, bigint]|null} Returns [token amount to sell, SOL amount to obtain] on success, null on failure
|
|
367
|
+
* token amount in 6-digit precision rounded up; SOL amount in 9-digit precision rounded down
|
|
368
|
+
*/
|
|
369
|
+
static sellFromPriceToPrice(startHighPrice, endLowPrice) {
|
|
370
|
+
// console.log('\n=== sellFromPriceToPrice debug info ===');
|
|
371
|
+
// console.log('Input parameters:');
|
|
372
|
+
// console.log(' startHighPrice:', startHighPrice);
|
|
373
|
+
// console.log(' endLowPrice:', endLowPrice);
|
|
374
|
+
|
|
375
|
+
// Convert to Decimal for calculation
|
|
376
|
+
const startPriceDec = this.u128ToDecimal(startHighPrice);
|
|
377
|
+
const endPriceDec = this.u128ToDecimal(endLowPrice);
|
|
378
|
+
|
|
379
|
+
// console.log('Price conversion results:');
|
|
380
|
+
// console.log(' startPriceDec:', startPriceDec.toString());
|
|
381
|
+
// console.log(' endPriceDec:', endPriceDec.toString());
|
|
382
|
+
|
|
383
|
+
// Ensure starting price is higher than ending price
|
|
384
|
+
if (startPriceDec.lte(endPriceDec)) {
|
|
385
|
+
// console.log('❌ Failure reason: starting price is lower than or equal to ending price');
|
|
386
|
+
// console.log(' startPriceDec.lte(endPriceDec):', startPriceDec.lte(endPriceDec));
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Use initial k value
|
|
391
|
+
const k = this.calculateInitialK();
|
|
392
|
+
//console.log('k value:', k.toString());
|
|
393
|
+
|
|
394
|
+
// Calculate reserves for starting and ending states
|
|
395
|
+
const startReserves = this.calculateReservesByPrice(startPriceDec, k);
|
|
396
|
+
const endReserves = this.calculateReservesByPrice(endPriceDec, k);
|
|
397
|
+
|
|
398
|
+
// console.log('Reserve calculation results:');
|
|
399
|
+
// console.log(' startReserves:', startReserves ? [startReserves[0].toString(), startReserves[1].toString()] : null);
|
|
400
|
+
// console.log(' endReserves:', endReserves ? [endReserves[0].toString(), endReserves[1].toString()] : null);
|
|
401
|
+
|
|
402
|
+
// if (!startReserves || !endReserves) {
|
|
403
|
+
// console.log('❌ Failure reason: reserve calculation failed');
|
|
404
|
+
// console.log(' startReserves:', startReserves);
|
|
405
|
+
// console.log(' endReserves:', endReserves);
|
|
406
|
+
// return null;
|
|
407
|
+
// }
|
|
408
|
+
|
|
409
|
+
const [startSolReserve, startTokenReserve] = startReserves;
|
|
410
|
+
const [endSolReserve, endTokenReserve] = endReserves;
|
|
411
|
+
|
|
412
|
+
// console.log('Detailed reserves:');
|
|
413
|
+
// console.log(' Starting state - SOL reserve:', startSolReserve.toString());
|
|
414
|
+
// console.log(' Starting state - Token reserve:', startTokenReserve.toString());
|
|
415
|
+
// console.log(' Ending state - SOL reserve:', endSolReserve.toString());
|
|
416
|
+
// console.log(' Ending state - Token reserve:', endTokenReserve.toString());
|
|
417
|
+
|
|
418
|
+
// Calculate token amount to sell (increase in token reserves)
|
|
419
|
+
const tokenInputAmount = endTokenReserve.sub(startTokenReserve);
|
|
420
|
+
|
|
421
|
+
// Calculate SOL amount to obtain (decrease in SOL reserves)
|
|
422
|
+
const solOutputAmount = startSolReserve.sub(endSolReserve);
|
|
423
|
+
|
|
424
|
+
// console.log('Transaction calculation results:');
|
|
425
|
+
// console.log(' Token amount to sell (tokenInputAmount):', tokenInputAmount.toString());
|
|
426
|
+
// console.log(' SOL amount to obtain (solOutputAmount):', solOutputAmount.toString());
|
|
427
|
+
|
|
428
|
+
// Check if calculation results are valid
|
|
429
|
+
if (tokenInputAmount.lte(0) || solOutputAmount.lte(0)) {
|
|
430
|
+
// console.log('❌ Failure reason: invalid transaction amount calculation results');
|
|
431
|
+
// console.log(' tokenInputAmount.lte(0):', tokenInputAmount.lte(0));
|
|
432
|
+
// console.log(' solOutputAmount.lte(0):', solOutputAmount.lte(0));
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Convert back to u64
|
|
437
|
+
// token uses 6-digit precision rounded up, SOL uses 9-digit precision rounded down
|
|
438
|
+
const tokenAmountU64 = this.tokenDecimalToU64Ceil(tokenInputAmount);
|
|
439
|
+
const solAmountU64 = this.solDecimalToU64(solOutputAmount);
|
|
440
|
+
|
|
441
|
+
// console.log('u64 conversion results:');
|
|
442
|
+
// console.log(' tokenAmountU64:', tokenAmountU64);
|
|
443
|
+
// console.log(' solAmountU64:', solAmountU64);
|
|
444
|
+
|
|
445
|
+
if (tokenAmountU64 === null || solAmountU64 === null) {
|
|
446
|
+
// console.log('❌ Failure reason: u64 conversion failed');
|
|
447
|
+
// console.log(' tokenAmountU64 === null:', tokenAmountU64 === null);
|
|
448
|
+
// console.log(' solAmountU64 === null:', solAmountU64 === null);
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// console.log('✅ Success! Return results:');
|
|
453
|
+
// console.log(' Token amount to sell:', tokenAmountU64.toString());
|
|
454
|
+
// console.log(' SOL amount to obtain:', solAmountU64.toString());
|
|
455
|
+
// console.log('=== sellFromPriceToPrice debug end ===\n');
|
|
456
|
+
|
|
457
|
+
return [tokenAmountU64, solAmountU64];
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Calculate reserves given a price
|
|
462
|
+
*
|
|
463
|
+
* @param {Decimal} price - Price, representing SOL amount for 1 token
|
|
464
|
+
* @param {Decimal} k - Constant product
|
|
465
|
+
* @returns {[Decimal, Decimal]|null} Returns [SOL reserve, token reserve] on success, null on failure
|
|
466
|
+
*/
|
|
467
|
+
static calculateReservesByPrice(price, k) {
|
|
468
|
+
// Check if input parameters are valid
|
|
469
|
+
if (price.lte(0) || k.lte(0)) {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Minimum price check to prevent overflow
|
|
474
|
+
if (price.lt(this.INITIAL_MIN_PRICE_DECIMAL)) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// According to AMM formula: k = sol_reserve * token_reserve
|
|
479
|
+
// and price = sol_reserve / token_reserve
|
|
480
|
+
// We get: sol_reserve = price * token_reserve
|
|
481
|
+
// Substituting into k formula: k = price * token_reserve^2
|
|
482
|
+
// Therefore: token_reserve = sqrt(k / price)
|
|
483
|
+
// sol_reserve = sqrt(k * price)
|
|
484
|
+
|
|
485
|
+
// Calculate k / price
|
|
486
|
+
const kDivPrice = k.div(price);
|
|
487
|
+
|
|
488
|
+
// Calculate token_reserve = sqrt(k / price)
|
|
489
|
+
const tokenReserve = kDivPrice.sqrt();
|
|
490
|
+
|
|
491
|
+
// Calculate sol_reserve = price * token_reserve
|
|
492
|
+
const solReserve = price.mul(tokenReserve);
|
|
493
|
+
|
|
494
|
+
if (tokenReserve.isNaN() || solReserve.isNaN()) {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return [solReserve, tokenReserve];
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Calculate token output amount and ending price based on starting price and SOL input amount
|
|
503
|
+
*
|
|
504
|
+
* @param {bigint|string|number} startLowPrice - Starting price
|
|
505
|
+
* @param {bigint|string|number} solInputAmount - SOL amount for buying
|
|
506
|
+
* @returns {[bigint, bigint]|null} Returns [price after transaction, token amount obtained] on success, null on failure
|
|
507
|
+
* Price rounded down, token amount rounded down
|
|
508
|
+
*/
|
|
509
|
+
static buyFromPriceWithSolInput(startLowPrice, solInputAmount) {
|
|
510
|
+
// Convert to Decimal for calculation
|
|
511
|
+
const startPriceDec = this.u128ToDecimal(startLowPrice);
|
|
512
|
+
const solInputDec = this.u64ToSolDecimal(solInputAmount);
|
|
513
|
+
|
|
514
|
+
// Check if input parameters are valid
|
|
515
|
+
if (startPriceDec.lte(0)) {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// If SOL input amount is 0, return unchanged price and token output of 0
|
|
520
|
+
if (solInputDec.eq(0)) {
|
|
521
|
+
const endPriceU128 = this.decimalToU128(startPriceDec);
|
|
522
|
+
if (endPriceU128 === null) return null;
|
|
523
|
+
return [endPriceU128, 0n];
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (solInputDec.lt(0)) {
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Use initial k value
|
|
531
|
+
const k = this.calculateInitialK();
|
|
532
|
+
|
|
533
|
+
// Calculate reserves for starting state
|
|
534
|
+
const startReserves = this.calculateReservesByPrice(startPriceDec, k);
|
|
535
|
+
if (!startReserves) return null;
|
|
536
|
+
|
|
537
|
+
const [startSolReserve, startTokenReserve] = startReserves;
|
|
538
|
+
|
|
539
|
+
// Calculate SOL reserves for ending state
|
|
540
|
+
const endSolReserve = startSolReserve.add(solInputDec);
|
|
541
|
+
|
|
542
|
+
// Calculate token reserves for ending state according to AMM formula
|
|
543
|
+
const endTokenReserve = k.div(endSolReserve);
|
|
544
|
+
|
|
545
|
+
// Calculate token output amount
|
|
546
|
+
const tokenOutputAmount = startTokenReserve.sub(endTokenReserve);
|
|
547
|
+
|
|
548
|
+
// Calculate ending price
|
|
549
|
+
const endPrice = endSolReserve.div(endTokenReserve);
|
|
550
|
+
|
|
551
|
+
// Check if calculation results are valid
|
|
552
|
+
if (tokenOutputAmount.lte(0) || endPrice.lte(0)) {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Convert back to appropriate types with required rounding
|
|
557
|
+
const endPriceU128 = this.decimalToU128(endPrice); // Price rounded down
|
|
558
|
+
const tokenAmountU64 = this.tokenDecimalToU64(tokenOutputAmount); // Token rounded down
|
|
559
|
+
|
|
560
|
+
if (endPriceU128 === null || tokenAmountU64 === null) {
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return [endPriceU128, tokenAmountU64];
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Calculate SOL output amount and ending price based on starting price and token input amount
|
|
569
|
+
*
|
|
570
|
+
* @param {bigint|string|number} startHighPrice - Starting price
|
|
571
|
+
* @param {bigint|string|number} tokenInputAmount - Token amount to sell
|
|
572
|
+
* @returns {[bigint, bigint]|null} Returns [price after transaction, SOL amount obtained] on success, null on failure
|
|
573
|
+
* Price rounded down, SOL amount rounded down
|
|
574
|
+
*/
|
|
575
|
+
static sellFromPriceWithTokenInput(startHighPrice, tokenInputAmount) {
|
|
576
|
+
// Convert to Decimal for calculation
|
|
577
|
+
const startPriceDec = this.u128ToDecimal(startHighPrice);
|
|
578
|
+
const tokenInputDec = this.u64ToTokenDecimal(tokenInputAmount);
|
|
579
|
+
|
|
580
|
+
//console.log("startHighPrice, tokenInputAmount",startHighPrice, tokenInputAmount)
|
|
581
|
+
// Check if input parameters are valid
|
|
582
|
+
if (startPriceDec.lte(0)) {
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// If token input amount is 0, return unchanged price and SOL output of 0
|
|
587
|
+
if (tokenInputDec.eq(0)) {
|
|
588
|
+
const endPriceU128 = this.decimalToU128(startPriceDec);
|
|
589
|
+
if (endPriceU128 === null) return null;
|
|
590
|
+
return [endPriceU128, 0n];
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (tokenInputDec.lt(0)) {
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Use initial k value
|
|
598
|
+
const k = this.calculateInitialK();
|
|
599
|
+
|
|
600
|
+
// Calculate reserves for starting state
|
|
601
|
+
const startReserves = this.calculateReservesByPrice(startPriceDec, k);
|
|
602
|
+
if (!startReserves) return null;
|
|
603
|
+
|
|
604
|
+
const [startSolReserve, startTokenReserve] = startReserves;
|
|
605
|
+
|
|
606
|
+
// Calculate token reserves for ending state
|
|
607
|
+
const endTokenReserve = startTokenReserve.add(tokenInputDec);
|
|
608
|
+
|
|
609
|
+
// 根据AMM公式计算结束状态的SOL储备量
|
|
610
|
+
const endSolReserve = k.div(endTokenReserve);
|
|
611
|
+
|
|
612
|
+
// Calculate SOL output amount
|
|
613
|
+
const solOutputAmount = startSolReserve.sub(endSolReserve);
|
|
614
|
+
|
|
615
|
+
// Calculate ending price
|
|
616
|
+
const endPrice = endSolReserve.div(endTokenReserve);
|
|
617
|
+
|
|
618
|
+
// Check if calculation results are valid
|
|
619
|
+
if (solOutputAmount.lte(0) || endPrice.lte(0)) {
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Convert back to appropriate types with required rounding
|
|
624
|
+
const endPriceU128 = this.decimalToU128(endPrice); // Price rounded down
|
|
625
|
+
const solAmountU64 = this.solDecimalToU64(solOutputAmount); // SOL rounded down
|
|
626
|
+
|
|
627
|
+
if (endPriceU128 === null || solAmountU64 === null) {
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return [endPriceU128, solAmountU64];
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Calculate required SOL input amount and ending price based on starting price and expected token output amount
|
|
636
|
+
*
|
|
637
|
+
* @param {bigint|string|number} startLowPrice - Starting price
|
|
638
|
+
* @param {bigint|string|number} tokenOutputAmount - Desired token amount to obtain
|
|
639
|
+
* @returns {[bigint, bigint]|null} Returns [price after transaction, SOL amount to pay] on success, null on failure
|
|
640
|
+
* Price rounded down, SOL amount rounded up
|
|
641
|
+
*/
|
|
642
|
+
static buyFromPriceWithTokenOutput(startLowPrice, tokenOutputAmount) {
|
|
643
|
+
// Convert to Decimal for calculation
|
|
644
|
+
const startPriceDec = this.u128ToDecimal(startLowPrice);
|
|
645
|
+
const tokenOutputDec = this.u64ToTokenDecimal(tokenOutputAmount);
|
|
646
|
+
|
|
647
|
+
// Check if input parameters are valid
|
|
648
|
+
if (startPriceDec.lte(0)) {
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// If token output amount is 0, return unchanged price and SOL input of 0
|
|
653
|
+
if (tokenOutputDec.eq(0)) {
|
|
654
|
+
const endPriceU128 = this.decimalToU128(startPriceDec);
|
|
655
|
+
if (endPriceU128 === null) return null;
|
|
656
|
+
return [endPriceU128, 0n];
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (tokenOutputDec.lt(0)) {
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Use initial k value
|
|
664
|
+
const k = this.calculateInitialK();
|
|
665
|
+
|
|
666
|
+
// Calculate reserves for starting state
|
|
667
|
+
const startReserves = this.calculateReservesByPrice(startPriceDec, k);
|
|
668
|
+
if (!startReserves) return null;
|
|
669
|
+
|
|
670
|
+
const [startSolReserve, startTokenReserve] = startReserves;
|
|
671
|
+
|
|
672
|
+
// Calculate token reserves for ending state
|
|
673
|
+
const endTokenReserve = startTokenReserve.sub(tokenOutputDec);
|
|
674
|
+
|
|
675
|
+
//console.log('buyFromPriceWithTokenOutput 结束token储备 = 起始token储备 - token输出量:', endTokenReserve.toString());
|
|
676
|
+
|
|
677
|
+
// Check if token reserves are sufficient
|
|
678
|
+
if (endTokenReserve.lte(0)) {
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// 根据AMM公式计算结束状态的SOL储备量
|
|
683
|
+
const endSolReserve = k.div(endTokenReserve);
|
|
684
|
+
|
|
685
|
+
// Calculate required SOL input amount
|
|
686
|
+
const solInputAmount = endSolReserve.sub(startSolReserve);
|
|
687
|
+
|
|
688
|
+
// Calculate ending price
|
|
689
|
+
const endPrice = endSolReserve.div(endTokenReserve);
|
|
690
|
+
|
|
691
|
+
// Check if calculation results are valid
|
|
692
|
+
if (solInputAmount.lte(0) || endPrice.lte(0)) {
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Convert back to appropriate types with required rounding
|
|
697
|
+
const endPriceU128 = this.decimalToU128(endPrice); // Price rounded down
|
|
698
|
+
const solAmountU64 = this.solDecimalToU64Ceil(solInputAmount); // SOL rounded up
|
|
699
|
+
|
|
700
|
+
if (endPriceU128 === null || solAmountU64 === null) {
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
return [endPriceU128, solAmountU64];
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Calculate required token input amount and ending price based on starting price and expected SOL output amount
|
|
709
|
+
*
|
|
710
|
+
* @param {bigint|string|number} startHighPrice - Starting price
|
|
711
|
+
* @param {bigint|string|number} solOutputAmount - Desired SOL amount to obtain
|
|
712
|
+
* @returns {[bigint, bigint]|null} Returns [price after transaction, token amount to pay] on success, null on failure
|
|
713
|
+
* Price rounded down, token amount rounded up
|
|
714
|
+
*/
|
|
715
|
+
static sellFromPriceWithSolOutput(startHighPrice, solOutputAmount) {
|
|
716
|
+
// Convert to Decimal for calculation
|
|
717
|
+
const startPriceDec = this.u128ToDecimal(startHighPrice);
|
|
718
|
+
const solOutputDec = this.u64ToSolDecimal(solOutputAmount);
|
|
719
|
+
|
|
720
|
+
// Check if input parameters are valid
|
|
721
|
+
if (startPriceDec.lte(0)) {
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// If SOL output amount is 0, return unchanged price and token input of 0
|
|
726
|
+
if (solOutputDec.eq(0)) {
|
|
727
|
+
const endPriceU128 = this.decimalToU128(startPriceDec);
|
|
728
|
+
if (endPriceU128 === null) return null;
|
|
729
|
+
return [endPriceU128, 0n];
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (solOutputDec.lt(0)) {
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Use initial k value
|
|
737
|
+
const k = this.calculateInitialK();
|
|
738
|
+
|
|
739
|
+
// Calculate reserves for starting state
|
|
740
|
+
const startReserves = this.calculateReservesByPrice(startPriceDec, k);
|
|
741
|
+
if (!startReserves) return null;
|
|
742
|
+
|
|
743
|
+
const [startSolReserve, startTokenReserve] = startReserves;
|
|
744
|
+
|
|
745
|
+
// Calculate SOL reserves for ending state
|
|
746
|
+
const endSolReserve = startSolReserve.sub(solOutputDec);
|
|
747
|
+
|
|
748
|
+
// Check if SOL reserves are sufficient
|
|
749
|
+
if (endSolReserve.lte(0)) {
|
|
750
|
+
return null;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Calculate token reserves for ending state according to AMM formula
|
|
754
|
+
const endTokenReserve = k.div(endSolReserve);
|
|
755
|
+
|
|
756
|
+
// Calculate required token input amount
|
|
757
|
+
const tokenInputAmount = endTokenReserve.sub(startTokenReserve);
|
|
758
|
+
|
|
759
|
+
// Calculate ending price
|
|
760
|
+
const endPrice = endSolReserve.div(endTokenReserve);
|
|
761
|
+
|
|
762
|
+
// Check if calculation results are valid
|
|
763
|
+
if (tokenInputAmount.lte(0) || endPrice.lte(0)) {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Convert back to appropriate types with required rounding
|
|
768
|
+
const endPriceU128 = this.decimalToU128(endPrice); // Price rounded down
|
|
769
|
+
const tokenAmountU64 = this.tokenDecimalToU64Ceil(tokenInputAmount); // Token rounded up
|
|
770
|
+
|
|
771
|
+
if (endPriceU128 === null || tokenAmountU64 === null) {
|
|
772
|
+
return null;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
return [endPriceU128, tokenAmountU64];
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Calculate remaining amount after deducting fees
|
|
780
|
+
*
|
|
781
|
+
* @param {bigint|string|number} amount - Original amount
|
|
782
|
+
* @param {number} fee - Fee rate, expressed with FEE_DENOMINATOR as denominator
|
|
783
|
+
* Example: 1000 represents 1% fee (1000/100000)
|
|
784
|
+
* 2000 represents 2% fee (2000/100000)
|
|
785
|
+
* @returns {bigint|null} Returns remaining amount after deducting fees on success, null on failure
|
|
786
|
+
* Fee calculation uses floor rounding, which is the most favorable calculation method for users
|
|
787
|
+
*/
|
|
788
|
+
static calculateAmountAfterFee(amount, fee) {
|
|
789
|
+
// Convert input parameters to BigInt
|
|
790
|
+
try {
|
|
791
|
+
const amountBigInt = BigInt(amount.toString());
|
|
792
|
+
const feeBigInt = BigInt(fee);
|
|
793
|
+
|
|
794
|
+
// Check if fee rate is valid (must be less than or equal to 10%)
|
|
795
|
+
if (feeBigInt > MAX_FEE_RATE) {
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Calculate fee amount: amount * fee / FEE_DENOMINATOR
|
|
800
|
+
const feeAmount = (amountBigInt * feeBigInt) / FEE_DENOMINATOR;
|
|
801
|
+
|
|
802
|
+
// Calculate remaining amount after deducting fees
|
|
803
|
+
const amountAfterFee = amountBigInt - feeAmount;
|
|
804
|
+
|
|
805
|
+
return amountAfterFee;
|
|
806
|
+
} catch (error) {
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Convert u128 price to readable decimal string format for display
|
|
813
|
+
*
|
|
814
|
+
* @param {bigint|string|number} price - u128 price to be converted
|
|
815
|
+
* @param {number} decimalPlaces - Number of decimal places to retain, default is 28
|
|
816
|
+
* @returns {string} Formatted price string
|
|
817
|
+
*/
|
|
818
|
+
static formatPriceForDisplay(price, decimalPlaces = 28) {
|
|
819
|
+
if (typeof price === 'bigint') {
|
|
820
|
+
price = price.toString();
|
|
821
|
+
}
|
|
822
|
+
const priceDecimal = new Decimal(price);
|
|
823
|
+
const convertedPrice = priceDecimal.div(this.PRICE_PRECISION_FACTOR_DECIMAL);
|
|
824
|
+
return convertedPrice.toFixed(decimalPlaces);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Create complete price display string, including both integer and decimal formats
|
|
829
|
+
*
|
|
830
|
+
* @param {bigint|string|number} price - u128 price to be converted
|
|
831
|
+
* @param {number} decimalPlaces - Number of decimal places to retain, default is 28
|
|
832
|
+
* @returns {string} Formatted complete price string, format: "integer price (decimal price)"
|
|
833
|
+
*/
|
|
834
|
+
static createPriceDisplayString(price, decimalPlaces = 28) {
|
|
835
|
+
const integerPrice = (typeof price === 'bigint') ? price.toString() : price.toString();
|
|
836
|
+
const decimalPrice = this.formatPriceForDisplay(price, decimalPlaces);
|
|
837
|
+
return `${integerPrice} (${decimalPrice})`;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Calculate price based on liquidity pool reserves (how much SOL 1 token is worth)
|
|
842
|
+
*
|
|
843
|
+
* @param {bigint|string|number|BN} lpTokenReserve - Token reserves in liquidity pool (u64 format, 6-digit precision)
|
|
844
|
+
* @param {bigint|string|number|BN} lpSolReserve - SOL reserves in liquidity pool (u64 format, 9-digit precision)
|
|
845
|
+
* @returns {string|null} Returns 28-digit decimal price string on success, null on failure
|
|
846
|
+
*/
|
|
847
|
+
static calculatePoolPrice(lpTokenReserve, lpSolReserve) {
|
|
848
|
+
try {
|
|
849
|
+
// Handle BN objects, convert to string
|
|
850
|
+
let tokenReserveStr = lpTokenReserve;
|
|
851
|
+
let solReserveStr = lpSolReserve;
|
|
852
|
+
|
|
853
|
+
// If it's a BN object, use toString() method
|
|
854
|
+
if (lpTokenReserve && typeof lpTokenReserve === 'object' && lpTokenReserve.toString) {
|
|
855
|
+
tokenReserveStr = lpTokenReserve.toString();
|
|
856
|
+
}
|
|
857
|
+
if (lpSolReserve && typeof lpSolReserve === 'object' && lpSolReserve.toString) {
|
|
858
|
+
solReserveStr = lpSolReserve.toString();
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Convert to Decimal for calculation
|
|
862
|
+
const tokenReserveDec = this.u64ToTokenDecimal(tokenReserveStr);
|
|
863
|
+
const solReserveDec = this.u64ToSolDecimal(solReserveStr);
|
|
864
|
+
|
|
865
|
+
// Check if reserves are valid
|
|
866
|
+
if (tokenReserveDec.lte(0) || solReserveDec.lte(0)) {
|
|
867
|
+
return null;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Calculate price: 1 token = SOL reserve / Token reserve
|
|
871
|
+
const price = solReserveDec.div(tokenReserveDec);
|
|
872
|
+
|
|
873
|
+
// Return 28-digit decimal string
|
|
874
|
+
return price.toFixed(28);
|
|
875
|
+
} catch (error) {
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
module.exports = CurveAMM;
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
|