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.
- package/README.md +364 -0
- package/dist/100x-sdk.cjs.js +57601 -0
- package/dist/100x-sdk.cjs.js.map +1 -0
- package/dist/100x-sdk.esm.js +46234 -0
- package/dist/100x-sdk.esm.js.map +1 -0
- package/dist/100x-sdk.js +46245 -0
- package/dist/100x-sdk.js.map +1 -0
- package/dist/index.d.ts +386 -0
- package/package.json +69 -0
- package/src/idl/fun100x_localnet.json +3735 -0
- package/src/idl/fun100x_main.json +3735 -0
- package/src/index.js +50 -0
- package/src/modules/chain.js +1310 -0
- package/src/modules/fast.js +734 -0
- package/src/modules/param.js +171 -0
- package/src/modules/simulator/buy_sell_token.js +326 -0
- package/src/modules/simulator/calcLiq.js +778 -0
- package/src/modules/simulator/calc_sol_liq.js +268 -0
- package/src/modules/simulator/close_indices.js +217 -0
- package/src/modules/simulator/long_shrot_stop.js +1028 -0
- package/src/modules/simulator/stop_loss_utils.js +378 -0
- package/src/modules/simulator/utils.js +63 -0
- package/src/modules/simulator.js +386 -0
- package/src/modules/token.js +455 -0
- package/src/modules/tools.js +370 -0
- package/src/modules/trading.js +848 -0
- package/src/sdk.js +206 -0
- package/src/types/index.d.ts +386 -0
- package/src/utils/constants.js +57 -0
- package/src/utils/curve_amm.js +1215 -0
- package/src/utils/orderUtils.js +17 -0
|
@@ -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
|
+
|