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.
@@ -0,0 +1,711 @@
1
+
2
+
3
+ const CurveAMM = require('../../utils/curve_amm');
4
+ const { convertApiOrdersFormat, absoluteValue } = require('./utils');
5
+
6
+ /**
7
+ * Simulate buy transaction analysis
8
+ * @param {string} mint - Token address
9
+ * @param {bigint|string|number} buySolAmount - SOL amount to buy (u64 format, precision 10^9)
10
+ * @returns {Promise<Object>} Buy analysis result
11
+ */
12
+ async function simulateBuy(mint, buySolAmount) {
13
+ // Initialize return result
14
+ const result = {
15
+ success: false,
16
+ errorCode: null,
17
+ errorMessage: null,
18
+ data: null
19
+ };
20
+
21
+ try {
22
+ // Parameter validation
23
+ if (!mint || typeof mint !== 'string') {
24
+ result.errorCode = 'PARAM_ERROR';
25
+ result.errorMessage = 'Invalid mint parameter';
26
+ return result;
27
+ }
28
+
29
+ if (buySolAmount === undefined || buySolAmount === null || buySolAmount <= 0) {
30
+ result.errorCode = 'PARAM_ERROR';
31
+ result.errorMessage = 'Invalid buySolAmount parameter';
32
+ return result;
33
+ }
34
+
35
+ // Convert buySolAmount to bigint
36
+ const buyingSolAmountU64 = typeof buySolAmount === 'bigint' ? buySolAmount : BigInt(buySolAmount);
37
+
38
+ // Get current price
39
+ let currentPrice;
40
+ try {
41
+ const priceString = await this.sdk.data.price(mint);
42
+ currentPrice = BigInt(priceString);
43
+
44
+ } catch (error) {
45
+ result.errorCode = 'API_ERROR';
46
+ result.errorMessage = `Failed to get token info: ${error.message}`;
47
+ return result;
48
+ }
49
+
50
+ // Get short order list
51
+ let shortOrderList;
52
+ try {
53
+ const ordersData = await this.sdk.data.orders(mint, { type: 'up_orders' });
54
+ if (!ordersData.success || !ordersData.data || !ordersData.data.orders) {
55
+ result.errorCode = 'API_ERROR';
56
+ result.errorMessage = 'Unable to get order info';
57
+ return result;
58
+ }
59
+ shortOrderList = convertApiOrdersFormat(ordersData.data.orders);
60
+ } catch (error) {
61
+ result.errorCode = 'API_ERROR';
62
+ result.errorMessage = `Failed to get order info: ${error.message}`;
63
+ return result;
64
+ }
65
+
66
+ // Handle empty order list
67
+ if (shortOrderList.length === 0) {
68
+ shortOrderList.push(null);
69
+ }
70
+
71
+ // Calculate ideal token amount without slippage
72
+ const idealTradeResult = CurveAMM.buyFromPriceWithSolInput(currentPrice, buyingSolAmountU64);
73
+ const idealTokenAmount = idealTradeResult ? idealTradeResult[1] : 0n;
74
+ const idealSolAmount = buyingSolAmountU64;
75
+
76
+ // Initialize price range and liquidity variables
77
+ let maxAllowedPrice = 0n;
78
+ let totalPriceSpan = 0n;
79
+ let transactionCompletionRate = 0.0;
80
+ let totalLiquiditySolAmount = 0n;
81
+ let totalLiquidityTokenAmount = 0n;
82
+ let targetReachedAtSegmentIndex = -1;
83
+
84
+ // Build price segment analysis list
85
+ const priceSegmentAnalysisList = new Array(shortOrderList.length);
86
+
87
+ // Iterate through order list and calculate parameters for each price segment
88
+ for (let segmentIndex = 0; segmentIndex < shortOrderList.length; segmentIndex++) {
89
+ let segmentStartPrice, segmentEndPrice;
90
+
91
+ // Determine start and end prices based on segment position
92
+ if (segmentIndex === 0) {
93
+ // First segment: start from current price
94
+ segmentStartPrice = currentPrice;
95
+
96
+ if (shortOrderList[0] === null) {
97
+ // If first order is null, no orders exist
98
+ segmentEndPrice = CurveAMM.MAX_U128_PRICE;
99
+ maxAllowedPrice = CurveAMM.MAX_U128_PRICE;
100
+ } else {
101
+ // To one unit before first order start price
102
+ segmentEndPrice = BigInt(shortOrderList[0].lockLpStartPrice);
103
+ maxAllowedPrice = BigInt(shortOrderList[0].lockLpStartPrice);
104
+ }
105
+ } else if (shortOrderList[segmentIndex] === null) {
106
+ // Current iteration reaches null (end of list)
107
+ segmentStartPrice = BigInt(shortOrderList[segmentIndex - 1].lockLpEndPrice);
108
+ segmentEndPrice = CurveAMM.MAX_U128_PRICE;
109
+ } else {
110
+ // Normal case: gap between two orders
111
+ segmentStartPrice = BigInt(shortOrderList[segmentIndex - 1].lockLpEndPrice);
112
+ segmentEndPrice = BigInt(shortOrderList[segmentIndex].lockLpStartPrice);
113
+ }
114
+
115
+ // Validate price segment validity
116
+ if (segmentStartPrice > segmentEndPrice) {
117
+ // Invalid price segment, skip
118
+ priceSegmentAnalysisList[segmentIndex] = {
119
+ startPrice: segmentStartPrice,
120
+ endPrice: segmentEndPrice,
121
+ requiredSolAmount: null,
122
+ obtainableTokenAmount: null,
123
+ isValid: false
124
+ };
125
+ continue;
126
+ }
127
+
128
+ if (segmentStartPrice == segmentEndPrice) {
129
+ // Price segments are equal
130
+ priceSegmentAnalysisList[segmentIndex] = {
131
+ startPrice: segmentStartPrice,
132
+ endPrice: segmentEndPrice,
133
+ requiredSolAmount: 0n,
134
+ obtainableTokenAmount: 0n,
135
+ isValid: true
136
+ };
137
+ continue;
138
+ }
139
+
140
+ // Use AMM to calculate transaction parameters for this segment
141
+ const segmentTradeResult = CurveAMM.buyFromPriceToPrice(segmentStartPrice, segmentEndPrice);
142
+
143
+ if (!segmentTradeResult) {
144
+ // AMM calculation failed
145
+ priceSegmentAnalysisList[segmentIndex] = {
146
+ startPrice: segmentStartPrice,
147
+ endPrice: segmentEndPrice,
148
+ requiredSolAmount: null,
149
+ obtainableTokenAmount: null,
150
+ isValid: false
151
+ };
152
+ } else {
153
+ // Calculation successful, save result
154
+ const [requiredSolAmount, obtainableTokenAmount] = segmentTradeResult;
155
+ priceSegmentAnalysisList[segmentIndex] = {
156
+ startPrice: segmentStartPrice,
157
+ endPrice: segmentEndPrice,
158
+ requiredSolAmount,
159
+ obtainableTokenAmount,
160
+ isValid: true
161
+ };
162
+ }
163
+ }
164
+
165
+ // Accumulate total liquidity depth
166
+ for (let i = 0; i < priceSegmentAnalysisList.length; i++) {
167
+ const segment = priceSegmentAnalysisList[i];
168
+
169
+ if (segment.isValid && segment.requiredSolAmount !== null && segment.obtainableTokenAmount !== null) {
170
+ totalLiquiditySolAmount += BigInt(segment.requiredSolAmount);
171
+ totalLiquidityTokenAmount += BigInt(segment.obtainableTokenAmount);
172
+
173
+ // Check if accumulated token amount has reached ideal target
174
+ if (totalLiquidityTokenAmount >= idealTokenAmount && targetReachedAtSegmentIndex === -1) {
175
+ targetReachedAtSegmentIndex = i;
176
+ }
177
+ }
178
+ }
179
+
180
+ // Calculate actual transaction parameters
181
+ let actualRequiredSolAmount = 0n;
182
+ let actualObtainableTokenAmount = 0n;
183
+
184
+ if (targetReachedAtSegmentIndex !== -1) {
185
+ // Can complete 100% of transaction
186
+ transactionCompletionRate = 100.0;
187
+
188
+ for (let i = 0; i <= targetReachedAtSegmentIndex; i++) {
189
+ const currentSegment = priceSegmentAnalysisList[i];
190
+
191
+ if (i === targetReachedAtSegmentIndex) {
192
+ // Last segment: may only need partial transaction
193
+ const remainingTokenNeeded = idealTokenAmount - actualObtainableTokenAmount;
194
+ const partialTradeResult = CurveAMM.buyFromPriceWithTokenOutput(
195
+ currentSegment.startPrice,
196
+ remainingTokenNeeded
197
+ );
198
+
199
+ if (partialTradeResult) {
200
+ const [finalPrice, requiredSolForPartial] = partialTradeResult;
201
+ actualRequiredSolAmount += requiredSolForPartial;
202
+ actualObtainableTokenAmount += remainingTokenNeeded;
203
+ totalPriceSpan += absoluteValue(currentSegment.startPrice - finalPrice) + 1n;
204
+ }
205
+ } else {
206
+ // Use this segment completely
207
+ actualRequiredSolAmount += currentSegment.requiredSolAmount;
208
+ actualObtainableTokenAmount += currentSegment.obtainableTokenAmount;
209
+ totalPriceSpan += absoluteValue(currentSegment.startPrice - currentSegment.endPrice) + 1n;
210
+ }
211
+ }
212
+ } else {
213
+ // Cannot complete transaction fully, use all available liquidity
214
+ for (let i = 0; i < priceSegmentAnalysisList.length; i++) {
215
+ const segment = priceSegmentAnalysisList[i];
216
+ if (segment.isValid) {
217
+ actualRequiredSolAmount += segment.requiredSolAmount;
218
+ actualObtainableTokenAmount += segment.obtainableTokenAmount;
219
+ totalPriceSpan += absoluteValue(segment.startPrice - segment.endPrice) + 1n;
220
+ }
221
+ }
222
+
223
+ // Calculate transaction completion rate
224
+ if (idealTokenAmount > 0n) {
225
+ transactionCompletionRate = parseFloat(
226
+ CurveAMM.u64ToTokenDecimal(actualObtainableTokenAmount)
227
+ .div(CurveAMM.u64ToTokenDecimal(idealTokenAmount))
228
+ .mul(100)
229
+ .toFixed(2)
230
+ );
231
+ }
232
+
233
+ // Recalculate theoretical SOL needed (based on actual obtainable token amount)
234
+ const theoreticalTradeResult = CurveAMM.buyFromPriceWithTokenOutput(currentPrice, actualObtainableTokenAmount);
235
+ if (theoreticalTradeResult) {
236
+ const [, theoreticalSolNeeded] = theoreticalTradeResult;
237
+ buyingSolAmountU64 = theoreticalSolNeeded;
238
+ }
239
+ }
240
+
241
+ // Calculate minimum slippage percentage
242
+ const minimumSlippagePercentage = Math.abs(
243
+ 100.0 * (
244
+ CurveAMM.u64ToSolDecimal(buyingSolAmountU64)
245
+ .minus(CurveAMM.u64ToSolDecimal(actualRequiredSolAmount))
246
+ .div(CurveAMM.u64ToSolDecimal(buyingSolAmountU64))
247
+ .toNumber()
248
+ )
249
+ );
250
+
251
+ // Set successful result
252
+ result.success = true;
253
+ result.data = {
254
+ inputType: 'sol', // Input currency type
255
+ inputAmount: buyingSolAmountU64, // Input amount
256
+ maxAllowedPrice: maxAllowedPrice, // Maximum allowed start price
257
+ totalPriceSpan: totalPriceSpan, // Transaction price range
258
+ transactionCompletionRate: transactionCompletionRate, // Theoretical transaction completion percentage
259
+ idealTokenAmount: idealTokenAmount, // Ideal token amount obtainable
260
+ idealSolAmount: idealSolAmount, // Ideal SOL amount needed
261
+ actualRequiredSolAmount: actualRequiredSolAmount, // Actual SOL amount required
262
+ actualObtainableTokenAmount: actualObtainableTokenAmount, // Actual token amount obtainable
263
+ theoreticalSolAmount: buyingSolAmountU64, // SOL needed under liquidity pool constraints, no slippage
264
+ minimumSlippagePercentage: minimumSlippagePercentage, // Minimum slippage percentage
265
+ totalLiquiditySolAmount: totalLiquiditySolAmount, // Total liquidity depth SOL
266
+ totalLiquidityTokenAmount: totalLiquidityTokenAmount // Total liquidity depth Token
267
+ };
268
+
269
+ } catch (error) {
270
+ // Catch unexpected errors
271
+ result.errorCode = 'DATA_ERROR';
272
+ result.errorMessage = `Error occurred during calculation: ${error.message}`;
273
+ }
274
+
275
+ return result;
276
+ }
277
+
278
+
279
+ /**
280
+ * Simulate token buy transaction - calculate if target token amount can be purchased
281
+ * 模拟以 Token 数量为目标的买入交易 - 计算是否能买到指定数量的 Token
282
+ * @param {string} mint - Token address 代币地址
283
+ * @param {bigint|string|number} buyTokenAmount - Target token amount to buy 目标购买的 Token 数量
284
+ * @param {string} passOrder - Optional order address to skip (won't be liquidated) 可选的跳过订单地址
285
+ * @returns {Promise<Object>} Token buy simulation result 模拟结果
286
+ */
287
+ async function simulateTokenBuy(mint, buyTokenAmount, passOrder = null) {
288
+ // 初始化返回结果 Initialize return result
289
+ const result = {
290
+ success: false,
291
+ errorCode: null,
292
+ errorMessage: null,
293
+ data: null
294
+ };
295
+
296
+ try {
297
+ // 参数验证 Parameter validation
298
+ if (!mint || typeof mint !== 'string') {
299
+ result.errorCode = 'INVALID_MINT';
300
+ result.errorMessage = 'Invalid mint address';
301
+ return result;
302
+ }
303
+
304
+ // 转换并验证 token 数量 Convert and validate token amount
305
+ const targetTokenAmount = typeof buyTokenAmount === 'bigint' ?
306
+ buyTokenAmount : BigInt(buyTokenAmount);
307
+
308
+ if (targetTokenAmount <= 0n) {
309
+ result.errorCode = 'INVALID_AMOUNT';
310
+ result.errorMessage = 'Token amount must be positive';
311
+ return result;
312
+ }
313
+
314
+ // 验证 passOrder 参数 Validate passOrder parameter
315
+ if (passOrder !== null && typeof passOrder !== 'string') {
316
+ result.errorCode = 'INVALID_PASS_ORDER';
317
+ result.errorMessage = 'Pass order must be a valid address string';
318
+ return result;
319
+ }
320
+
321
+ // 获取当前价格 Get current price
322
+ let currentPrice;
323
+ try {
324
+ const priceString = await this.sdk.data.price(mint);
325
+ currentPrice = BigInt(priceString);
326
+ } catch (error) {
327
+ result.errorCode = 'API_ERROR';
328
+ result.errorMessage = `Failed to get price: ${error.message}`;
329
+ return result;
330
+ }
331
+
332
+ // 获取做空订单列表(限制为 MAX_ORDERS_COUNT + 1)
333
+ // Get short order list (limited to MAX_ORDERS_COUNT + 1)
334
+ let orders;
335
+ try {
336
+ const ordersData = await this.sdk.data.orders(mint, {
337
+ type: 'up_orders',
338
+ limit: this.sdk.MAX_ORDERS_COUNT + 1
339
+ });
340
+
341
+ if (!ordersData.success || !ordersData.data || !ordersData.data.orders) {
342
+ result.errorCode = 'API_ERROR';
343
+ result.errorMessage = 'Unable to get order info';
344
+ return result;
345
+ }
346
+
347
+ // 转换订单格式 Convert order format
348
+ orders = ordersData.data.orders;
349
+ } catch (error) {
350
+ result.errorCode = 'API_ERROR';
351
+ result.errorMessage = `Failed to get orders: ${error.message}`;
352
+ return result;
353
+ }
354
+
355
+ // 场景 A: 无订单,直接计算
356
+ // Scenario A: No orders, direct calculation
357
+ if (orders.length === 0) {
358
+ const calcResult = CurveAMM.buyFromPriceWithTokenOutput(currentPrice, targetTokenAmount);
359
+ if (calcResult) {
360
+ const [finalPrice, requiredSol] = calcResult;
361
+
362
+ result.success = true;
363
+ result.data = {
364
+ inputType: 'token',
365
+ inputAmount: targetTokenAmount,
366
+ currentPrice: currentPrice,
367
+ canComplete: true,
368
+ completionRate: 100.0,
369
+ limitReason: null,
370
+ idealSolRequired: requiredSol,
371
+ idealEndPrice: finalPrice,
372
+ actualObtainableToken: targetTokenAmount,
373
+ actualRequiredSol: requiredSol,
374
+ actualEndPrice: finalPrice,
375
+ ordersToClose: [],
376
+ ordersToCloseCount: 0,
377
+ passOrderIndex: null,
378
+ hasMoreOrders: false,
379
+ totalAvailableToken: CurveAMM.MAX_U64,
380
+ totalAvailableSol: CurveAMM.MAX_U64,
381
+ priceImpact: Number((finalPrice - currentPrice) * 10000n / currentPrice) / 100,
382
+ maxReachablePrice: CurveAMM.MAX_U128_PRICE,
383
+ segments: []
384
+ };
385
+ return result;
386
+ } else {
387
+ result.errorCode = 'CURVE_ERROR';
388
+ result.errorMessage = 'Failed to calculate buy amounts';
389
+ return result;
390
+ }
391
+ }
392
+
393
+ // 初始化变量 Initialize variables
394
+ let totalAvailableToken = 0n; // 累积可购买的 token
395
+ let totalTokenValue = 0n; // 包含订单锁定的 token
396
+ let previousAvailable = 0n; // 上一次的可用量
397
+ let ordersToClose = []; // 需要平仓的订单索引
398
+ let passOrderIndex = null; // 跳过的订单索引
399
+ let targetReached = false; // 是否达到目标
400
+ let finalLpPairsIndex = -1; // 最终区间索引
401
+ let segments = []; // 详细的区间信息
402
+
403
+ // 计算订单数量是否超限 Check if orders exceed limit
404
+ const hasMoreOrders = orders.length > this.sdk.MAX_ORDERS_COUNT;
405
+ const processableOrders = Math.min(orders.length, this.sdk.MAX_ORDERS_COUNT);
406
+
407
+ // 第一步:计算从当前价格到第一个订单前的流动性
408
+ // Step 1: Calculate liquidity from current price to first order
409
+ if (orders.length > 0) {
410
+ const firstOrderStartPrice = BigInt(orders[0].lock_lp_start_price);
411
+ const firstSegmentResult = CurveAMM.buyFromPriceToPrice(currentPrice, firstOrderStartPrice);
412
+
413
+ if (firstSegmentResult) {
414
+ const [solAmount, tokenAmount] = firstSegmentResult;
415
+ totalAvailableToken += tokenAmount;
416
+ totalTokenValue += tokenAmount;
417
+
418
+ segments.push({
419
+ type: 'initial',
420
+ startPrice: currentPrice,
421
+ endPrice: firstOrderStartPrice,
422
+ tokenAmount: tokenAmount,
423
+ solAmount: solAmount
424
+ });
425
+
426
+ if (totalAvailableToken >= targetTokenAmount) {
427
+ targetReached = true;
428
+ finalLpPairsIndex = -1;
429
+ }
430
+ }
431
+ }
432
+
433
+ // 第二步:遍历订单,累积计算
434
+ // Step 2: Iterate orders and calculate cumulatively
435
+ for (let i = 0; i < processableOrders && !targetReached; i++) {
436
+ const order = orders[i];
437
+ const isPassOrder = passOrder && order.order_pda === passOrder;
438
+
439
+ // 处理订单 Process order
440
+ if (isPassOrder) {
441
+ // 跳过的订单:流动性可用但不平仓
442
+ // Skipped order: liquidity available but not liquidated
443
+ previousAvailable = totalAvailableToken;
444
+ totalAvailableToken += BigInt(order.lock_lp_token_amount);
445
+ totalTokenValue += BigInt(order.lock_lp_token_amount);
446
+ passOrderIndex = i;
447
+
448
+ segments.push({
449
+ type: 'order',
450
+ startPrice: BigInt(order.lock_lp_start_price),
451
+ endPrice: BigInt(order.lock_lp_end_price),
452
+ tokenAmount: BigInt(order.lock_lp_token_amount),
453
+ solAmount: BigInt(order.lock_lp_sol_amount),
454
+ orderIndex: i,
455
+ isPassOrder: true
456
+ });
457
+ } else {
458
+ // 需要平仓的订单 Order to be liquidated
459
+ totalTokenValue += BigInt(order.lock_lp_token_amount);
460
+ ordersToClose.push(i);
461
+
462
+ segments.push({
463
+ type: 'order',
464
+ startPrice: BigInt(order.lock_lp_start_price),
465
+ endPrice: BigInt(order.lock_lp_end_price),
466
+ tokenAmount: BigInt(order.lock_lp_token_amount),
467
+ solAmount: BigInt(order.lock_lp_sol_amount),
468
+ orderIndex: i,
469
+ isPassOrder: false
470
+ });
471
+ }
472
+
473
+ // 检查是否达到目标 Check if target reached
474
+ if (totalAvailableToken >= targetTokenAmount) {
475
+ targetReached = true;
476
+ finalLpPairsIndex = i;
477
+ break;
478
+ }
479
+
480
+ // 检查 next_order Check next_order
481
+ const nextOrderAddress = order.next_order || null;
482
+ if (!nextOrderAddress) {
483
+ // 链表结束,上方有无限空间
484
+ // Chain ends, unlimited space above
485
+ finalLpPairsIndex = i;
486
+ previousAvailable = totalAvailableToken;
487
+ totalAvailableToken = CurveAMM.MAX_U64;
488
+ targetReached = true;
489
+
490
+ segments.push({
491
+ type: 'final',
492
+ startPrice: BigInt(order.lock_lp_end_price),
493
+ endPrice: CurveAMM.MAX_U128_PRICE,
494
+ tokenAmount: CurveAMM.MAX_U64,
495
+ solAmount: CurveAMM.MAX_U64
496
+ });
497
+ break;
498
+ }
499
+
500
+ // 处理订单间隙 Process gap between orders
501
+ if (i < orders.length - 1 && i < this.sdk.MAX_ORDERS_COUNT - 1) {
502
+ const gapStartPrice = BigInt(order.lock_lp_end_price);
503
+ const gapEndPrice = BigInt(orders[i + 1].lock_lp_start_price);
504
+
505
+ // 检查间隙是否存在 Check if gap exists
506
+ if (gapStartPrice < gapEndPrice) {
507
+ const gapResult = CurveAMM.buyFromPriceToPrice(gapStartPrice, gapEndPrice);
508
+ if (gapResult) {
509
+ const [gapSol, gapToken] = gapResult;
510
+ previousAvailable = totalAvailableToken;
511
+ totalAvailableToken += gapToken;
512
+ totalTokenValue += gapToken;
513
+
514
+ segments.push({
515
+ type: 'gap',
516
+ startPrice: gapStartPrice,
517
+ endPrice: gapEndPrice,
518
+ tokenAmount: gapToken,
519
+ solAmount: gapSol
520
+ });
521
+
522
+ if (totalAvailableToken >= targetTokenAmount) {
523
+ targetReached = true;
524
+ finalLpPairsIndex = i;
525
+ break;
526
+ }
527
+ }
528
+ }
529
+ } else if (i === processableOrders - 1 && hasMoreOrders) {
530
+ // 最后一个可处理订单且还有更多订单
531
+ // Last processable order with more orders beyond
532
+ const lastGapStartPrice = BigInt(order.lock_lp_end_price);
533
+ const lastGapEndPrice = BigInt(orders[i + 1].lock_lp_start_price);
534
+
535
+ if (lastGapStartPrice < lastGapEndPrice) {
536
+ const lastGapResult = CurveAMM.buyFromPriceToPrice(lastGapStartPrice, lastGapEndPrice);
537
+ if (lastGapResult) {
538
+ const [lastGapSol, lastGapToken] = lastGapResult;
539
+ previousAvailable = totalAvailableToken;
540
+ totalAvailableToken += lastGapToken;
541
+ totalTokenValue += lastGapToken;
542
+
543
+ segments.push({
544
+ type: 'gap',
545
+ startPrice: lastGapStartPrice,
546
+ endPrice: lastGapEndPrice,
547
+ tokenAmount: lastGapToken,
548
+ solAmount: lastGapSol
549
+ });
550
+
551
+ if (totalAvailableToken >= targetTokenAmount) {
552
+ targetReached = true;
553
+ finalLpPairsIndex = i;
554
+ }
555
+ }
556
+ }
557
+ }
558
+ }
559
+
560
+ // 计算理想情况(无订单阻碍)
561
+ // Calculate ideal case (no order obstacles)
562
+ const idealResult = CurveAMM.buyFromPriceWithTokenOutput(currentPrice, targetTokenAmount);
563
+ const [idealEndPrice, idealSolRequired] = idealResult || [0n, 0n];
564
+
565
+ // 计算实际参数 Calculate actual parameters
566
+ let actualRequiredSol = 0n;
567
+ let actualObtainableToken = 0n;
568
+ let actualEndPrice = currentPrice;
569
+ let completionRate = 0.0;
570
+ let limitReason = null;
571
+
572
+ if (targetReached) {
573
+ // 能完成交易,精确计算所需 SOL
574
+ // Can complete trade, calculate exact SOL needed
575
+ completionRate = 100.0;
576
+ actualObtainableToken = targetTokenAmount;
577
+
578
+ // 计算精确的结束价格和所需 SOL
579
+ // Calculate exact end price and required SOL
580
+ if (finalLpPairsIndex === -1) {
581
+ // 在第一个区间就完成 Completed in first segment
582
+ const exactResult = CurveAMM.buyFromPriceWithTokenOutput(currentPrice, targetTokenAmount);
583
+ if (exactResult) {
584
+ actualEndPrice = exactResult[0];
585
+ actualRequiredSol = exactResult[1];
586
+ }
587
+ } else {
588
+ // 需要跨越多个区间 Need to cross multiple segments
589
+ // 计算剩余需要的 token Calculate remaining token needed
590
+ const remainingToken = targetTokenAmount - previousAvailable;
591
+
592
+ // 找到最后的起始价格 Find final start price
593
+ let lastStartPrice = currentPrice;
594
+ if (finalLpPairsIndex >= 0 && finalLpPairsIndex < orders.length) {
595
+ lastStartPrice = BigInt(orders[finalLpPairsIndex].lock_lp_end_price);
596
+ }
597
+
598
+ // 计算最后区间的精确结果 Calculate exact result for last segment
599
+ const partialResult = CurveAMM.buyFromPriceWithTokenOutput(lastStartPrice, remainingToken);
600
+ if (partialResult) {
601
+ actualEndPrice = partialResult[0];
602
+
603
+ // 计算总的 SOL 需求 Calculate total SOL requirement
604
+ const totalResult = CurveAMM.buyFromPriceToPrice(currentPrice, actualEndPrice);
605
+ if (totalResult) {
606
+ // 减去订单占用的 SOL Subtract SOL locked in orders
607
+ let lockedSol = 0n;
608
+ for (const idx of ordersToClose) {
609
+ if (idx < orders.length) {
610
+ lockedSol += BigInt(orders[idx].lock_lp_sol_amount);
611
+ }
612
+ }
613
+ actualRequiredSol = totalResult[0] - lockedSol;
614
+ }
615
+ }
616
+ }
617
+ } else {
618
+ // 无法完成交易 Cannot complete trade
619
+ actualObtainableToken = Math.min(totalAvailableToken, targetTokenAmount);
620
+
621
+ if (targetTokenAmount > 0n && totalAvailableToken > 0n) {
622
+ completionRate = Number(totalAvailableToken * 10000n / targetTokenAmount) / 100;
623
+ completionRate = Math.min(99.99, completionRate);
624
+ }
625
+
626
+ // 判断限制原因 Determine limit reason
627
+ if (hasMoreOrders) {
628
+ limitReason = 'order_count_limit';
629
+ } else if (totalAvailableToken < targetTokenAmount) {
630
+ limitReason = 'insufficient_liquidity';
631
+ } else {
632
+ limitReason = 'unknown';
633
+ }
634
+
635
+ // 计算能买到的部分所需的 SOL Calculate SOL for obtainable amount
636
+ if (actualObtainableToken > 0n) {
637
+ const partialResult = CurveAMM.buyFromPriceWithTokenOutput(currentPrice, actualObtainableToken);
638
+ if (partialResult) {
639
+ actualEndPrice = partialResult[0];
640
+ actualRequiredSol = partialResult[1];
641
+ }
642
+ }
643
+ }
644
+
645
+ // 计算价格影响 Calculate price impact
646
+ let priceImpact = null;
647
+ if (targetReached && actualEndPrice > currentPrice) {
648
+ priceImpact = Number((actualEndPrice - currentPrice) * 10000n / currentPrice) / 100;
649
+ }
650
+
651
+ // 计算最高可达价格 Calculate max reachable price
652
+ let maxReachablePrice = currentPrice;
653
+ if (segments.length > 0) {
654
+ const lastSegment = segments[segments.length - 1];
655
+ maxReachablePrice = lastSegment.endPrice;
656
+ }
657
+
658
+ // 设置成功结果 Set successful result
659
+ result.success = true;
660
+ result.data = {
661
+ // 基本信息 Basic info
662
+ inputType: 'token',
663
+ inputAmount: targetTokenAmount,
664
+ currentPrice: currentPrice,
665
+
666
+ // 可行性分析 Feasibility analysis
667
+ canComplete: targetReached,
668
+ completionRate: completionRate,
669
+ limitReason: limitReason,
670
+
671
+ // 理想情况 Ideal case
672
+ idealSolRequired: idealSolRequired,
673
+ idealEndPrice: idealEndPrice,
674
+
675
+ // 实际情况 Actual case
676
+ actualObtainableToken: actualObtainableToken,
677
+ actualRequiredSol: actualRequiredSol,
678
+ actualEndPrice: actualEndPrice,
679
+
680
+ // 订单处理 Order processing
681
+ ordersToClose: ordersToClose,
682
+ ordersToCloseCount: ordersToClose.length,
683
+ passOrderIndex: passOrderIndex,
684
+ hasMoreOrders: hasMoreOrders,
685
+
686
+ // 流动性分析 Liquidity analysis
687
+ totalAvailableToken: totalAvailableToken,
688
+ totalAvailableSol: actualRequiredSol, // 简化处理
689
+
690
+ // 价格影响 Price impact
691
+ priceImpact: priceImpact,
692
+ maxReachablePrice: maxReachablePrice,
693
+
694
+ // 详细信息 Detailed info
695
+ segments: segments
696
+ };
697
+
698
+ } catch (error) {
699
+ // 捕获意外错误 Catch unexpected errors
700
+ result.errorCode = 'UNEXPECTED_ERROR';
701
+ result.errorMessage = `Unexpected error: ${error.message}`;
702
+ }
703
+
704
+ return result;
705
+ }
706
+
707
+
708
+ module.exports = {
709
+ simulateBuy,
710
+ simulateTokenBuy
711
+ };