100x-sdk 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,386 @@
1
+ const CurveAMM = require('../utils/curve_amm');
2
+ const { simulateLongStopLoss, simulateShortStopLoss, simulateLongSolStopLoss, simulateShortSolStopLoss } = require('./simulator/long_shrot_stop');
3
+ const { simulateTokenBuy, simulateTokenSell } = require('./simulator/buy_sell_token');
4
+ const { simulateLongClose, simulateShortClose } = require('./simulator/close_indices');
5
+ const {calcLiqSolBuy,calcLiqSolSell } = require('./simulator/calc_sol_liq');
6
+
7
+
8
+
9
+ /**
10
+ * Simulator Module Class
11
+ */
12
+ class SimulatorModule {
13
+ constructor(sdk) {
14
+ this.sdk = sdk;
15
+
16
+ // Liquidity reservation ratio - how much liquidity to reserve relative to the last locked liquidity
17
+ this.LIQUIDITY_RESERVATION = 100; // 100%;
18
+ // Price adjustment percentage
19
+ this.PRICE_ADJUSTMENT_PERCENTAGE = 0.5; // 0.5%
20
+ }
21
+
22
+ /**
23
+ * Simulate token buy transaction - calculate if target token amount can be purchased
24
+ * 模拟以 Token 数量为目标的买入交易 - 计算是否能买到指定数量的 Token
25
+ * @param {string} mint - Token address 代币地址
26
+ * @param {bigint|string|number} buyTokenAmount - Target token amount to buy 目标购买的 Token 数量
27
+ * @param {string} passOrder - Optional order address to skip (won't be liquidated) 可选的跳过订单地址
28
+ * @param {Object|null} lastPrice - Token price info, default null
29
+ * @param {Object|null} ordersData - Orders response object, default null
30
+ * @returns {Promise<Object>} Token buy simulation result with the following structure:
31
+ * - liqResult: {Object} Complete liquidity calculation result from calcLiqTokenBuy, containing:
32
+ * - free_lp_sol_amount_sum: {bigint} Total available free liquidity SOL amount
33
+ * - free_lp_token_amount_sum: {bigint} Total available free liquidity token amount
34
+ * - lock_lp_sol_amount_sum: {bigint} Total locked liquidity SOL amount
35
+ * - lock_lp_token_amount_sum: {bigint} Total locked liquidity token amount
36
+ * - has_infinite_lp: {boolean} Whether includes infinite liquidity beyond last order
37
+ * - pass_order_id: {number} Index of skipped order in array (-1 if none skipped)
38
+ * - force_close_num: {number} Number of orders that need force closure for target amount
39
+ * - ideal_lp_sol_amount: {bigint} Theoretical minimum SOL required at current price
40
+ * - real_lp_sol_amount: {bigint} Actual SOL required considering real liquidity distribution
41
+ * - completion: {string} Purchase completion percentage as decimal string (e.g., "85.2", "100.0")
42
+ * - slippage: {string} Price slippage percentage as decimal string (e.g., "2.5", "0.8")
43
+ * - suggestedTokenAmount: {string} Recommended token amount to buy based on available liquidity
44
+ * - suggestedSolAmount: {string} Required SOL amount for suggested token purchase
45
+ */
46
+ async simulateTokenBuy(mint, buyTokenAmount, passOrder = null, lastPrice = null, ordersData = null, curveData = null) {
47
+ return simulateTokenBuy.call(this, mint, buyTokenAmount, passOrder, lastPrice, ordersData, curveData);
48
+ }
49
+
50
+ /**
51
+ * Simulate token sell transaction analysis
52
+ * @param {string} mint - Token address
53
+ * @param {bigint|string|number} sellTokenAmount - Token amount to sell (u64 format, precision 10^9)
54
+ * @param {string} passOrder - Optional order address to skip (won't be liquidated) 可选的跳过订单地址
55
+ * @param {Object|null} lastPrice - Token price info, default null
56
+ * @param {Object|null} ordersData - Orders response object, default null
57
+ * @returns {Promise<Object>} Token sell simulation result with the following structure:
58
+ * - liqResult: {Object} Complete liquidity calculation result from calcLiqTokenSell, containing:
59
+ * - free_lp_sol_amount_sum: {bigint} Total available free liquidity SOL obtainable from selling
60
+ * - free_lp_token_amount_sum: {bigint} Maximum tokens sellable without force closing orders
61
+ * - lock_lp_sol_amount_sum: {bigint} Total locked liquidity SOL amount (excluding skipped orders)
62
+ * - lock_lp_token_amount_sum: {bigint} Total locked liquidity token amount (excluding skipped orders)
63
+ * - has_infinite_lp: {boolean} Whether includes infinite liquidity to minimum price
64
+ * - pass_order_id: {number} Index of skipped order in array (-1 if none skipped)
65
+ * - force_close_num: {number} Number of orders that need force closure for target sell amount
66
+ * - ideal_lp_sol_amount: {bigint} Theoretical maximum SOL obtainable at current price
67
+ * - real_lp_sol_amount: {bigint} Actual SOL obtainable considering real liquidity distribution
68
+ * - completion: {string} Sell completion percentage as decimal string (e.g., "85.2", "100.0")
69
+ * - slippage: {string} Price slippage percentage as decimal string (e.g., "2.5", "0.8")
70
+ * - suggestedTokenAmount: {string} Recommended token amount to sell based on available liquidity
71
+ * - suggestedSolAmount: {string} Expected SOL amount from suggested token sale
72
+ */
73
+ async simulateTokenSell(mint, sellTokenAmount, passOrder = null, lastPrice = null, ordersData = null, curveData = null) {
74
+ return simulateTokenSell.call(this, mint, sellTokenAmount, passOrder, lastPrice, ordersData, curveData);
75
+ }
76
+
77
+ /**
78
+ * Simulate long position stop loss calculation
79
+ * @param {string} mint - Token address
80
+ * @param {bigint|string|number} buyTokenAmount - Token amount to buy for long position (u64 format, precision 10^9)
81
+ * @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
82
+ * @param {Object|null} lastPrice - Token info, default null
83
+ * @param {Object|null} ordersData - Orders data, default null
84
+ * @returns {Promise<Object>} Stop loss analysis result
85
+ */
86
+ async simulateLongStopLoss(mint, buyTokenAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null) {
87
+ return simulateLongStopLoss.call(this, mint, buyTokenAmount, stopLossPrice, lastPrice, ordersData, borrowFee);
88
+ }
89
+
90
+ /**
91
+ * Simulate short position stop loss calculation
92
+ * @param {string} mint - Token address
93
+ * @param {bigint|string|number} sellTokenAmount - Token amount to sell for short position (u64 format, precision 10^9)
94
+ * @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
95
+ * @param {Object|null} lastPrice - Token info, default null
96
+ * @param {Object|null} ordersData - Orders data, default null
97
+ * @returns {Promise<Object>} Stop loss analysis result
98
+ */
99
+ async simulateShortStopLoss(mint, sellTokenAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null) {
100
+ return simulateShortStopLoss.call(this, mint, sellTokenAmount, stopLossPrice, lastPrice, ordersData, borrowFee);
101
+ }
102
+
103
+ /**
104
+ * Simulate long position stop loss calculation with SOL amount input
105
+ * @param {string} mint - Token address
106
+ * @param {bigint|string|number} buySolAmount - SOL amount to spend for long position (u64 format, lamports)
107
+ * @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
108
+ * @param {Object|null} lastPrice - Token info, default null
109
+ * @param {Object|null} ordersData - Orders data, default null
110
+ * @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
111
+ * @returns {Promise<Object>} Stop loss analysis result (same as simulateLongStopLoss)
112
+ */
113
+ async simulateLongSolStopLoss(mint, buySolAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null, initialVirtualSol = null, initialVirtualToken = null, curveAccount = null) {
114
+ return simulateLongSolStopLoss.call(this, mint, buySolAmount, stopLossPrice, lastPrice, ordersData, borrowFee, initialVirtualSol, initialVirtualToken, curveAccount);
115
+ }
116
+
117
+ /**
118
+ * Simulate short position stop loss calculation with SOL amount input
119
+ * @param {string} mint - Token address
120
+ * @param {bigint|string|number} sellSolAmount - SOL amount needed for short position stop loss (u64 format, lamports)
121
+ * @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
122
+ * @param {Object|null} lastPrice - Token info, default null
123
+ * @param {Object|null} ordersData - Orders data, default null
124
+ * @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
125
+ * @returns {Promise<Object>} Stop loss analysis result (same as simulateShortStopLoss)
126
+ */
127
+ async simulateShortSolStopLoss(mint, sellSolAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null, initialVirtualSol = null, initialVirtualToken = null, curveAccount = null) {
128
+ return simulateShortSolStopLoss.call(this, mint, sellSolAmount, stopLossPrice, lastPrice, ordersData, borrowFee, initialVirtualSol, initialVirtualToken, curveAccount);
129
+ }
130
+
131
+ /**
132
+ * Generate candidate insertion indices for closing long position
133
+ * 为做多平仓生成候选插入索引
134
+ * @param {string} mint - Token address 代币地址
135
+ * @param {number|string|anchor.BN} closeOrderId - Order ID to close (order_id, not index) 要平仓的订单ID
136
+ * @param {Object|null} ordersData - Orders data (optional) 订单数据(可选)
137
+ * @returns {Promise<Object>} Result containing closeOrderIndices array 包含候选索引数组的结果
138
+ */
139
+ async simulateLongClose(mint, closeOrderId, ordersData = null) {
140
+ return simulateLongClose.call(this, mint, closeOrderId, ordersData);
141
+ }
142
+
143
+ /**
144
+ * Generate candidate insertion indices for closing short position
145
+ * 为做空平仓生成候选插入索引
146
+ * @param {string} mint - Token address 代币地址
147
+ * @param {number|string|anchor.BN} closeOrderId - Order ID to close (order_id, not index) 要平仓的订单ID
148
+ * @param {Object|null} ordersData - Orders data (optional) 订单数据(可选)
149
+ * @returns {Promise<Object>} Result containing closeOrderIndices array 包含候选索引数组的结果
150
+ */
151
+ async simulateShortClose(mint, closeOrderId, ordersData = null) {
152
+ return simulateShortClose.call(this, mint, closeOrderId, ordersData);
153
+ }
154
+
155
+ /**
156
+ * Simulate buy transaction with SOL amount input
157
+ * 模拟以 SOL 金额为输入的买入交易
158
+ * @param {string} mint - Token address 代币地址
159
+ * @param {bigint|string|number} buySolAmount - SOL amount to spend (u64 format, lamports)
160
+ * @returns {Promise<Object>} Buy simulation result with the following structure:
161
+ * - success: {boolean} Whether the simulation was successful
162
+ * - errorCode: {string|null} Error code if failed ('API_ERROR', 'DATA_ERROR', 'PARAM_ERROR')
163
+ * - errorMessage: {string|null} Error message if failed
164
+ * - data: {Object} Analysis result data containing:
165
+ * - inputType: {string} 'sol' - input type
166
+ * - inputAmount: {bigint} Input SOL amount
167
+ * - maxAllowedPrice: {bigint} Maximum allowed starting price (u128)
168
+ * - totalPriceSpan: {bigint} Total price range for the transaction (u128)
169
+ * - transactionCompletionRate: {number} Transaction completion rate (%)
170
+ * - idealTokenAmount: {bigint} Ideal token amount obtainable
171
+ * - idealSolAmount: {bigint} Ideal SOL amount needed
172
+ * - actualRequiredSolAmount: {bigint} Actual SOL amount required
173
+ * - actualObtainableTokenAmount: {bigint} Actual token amount obtainable
174
+ * - theoreticalSolAmount: {bigint} Theoretical SOL amount needed
175
+ * - minimumSlippagePercentage: {number} Minimum slippage percentage
176
+ * - totalLiquiditySolAmount: {bigint} Total available liquidity in SOL
177
+ * - totalLiquidityTokenAmount: {bigint} Total available liquidity in tokens
178
+ */
179
+ async simulateBuy(mint, buySolAmount) {
180
+ try {
181
+ // Parameter validation
182
+ if (!mint || typeof mint !== 'string') {
183
+ return {
184
+ success: false,
185
+ errorCode: 'PARAM_ERROR',
186
+ errorMessage: 'Invalid mint parameter: must be a non-empty string',
187
+ data: null
188
+ };
189
+ }
190
+
191
+ // Convert buySolAmount to bigint
192
+ let solAmountBigInt;
193
+ try {
194
+ solAmountBigInt = typeof buySolAmount === 'bigint' ? buySolAmount : BigInt(buySolAmount);
195
+ if (solAmountBigInt <= 0n) {
196
+ throw new Error('Amount must be greater than 0');
197
+ }
198
+ } catch (error) {
199
+ return {
200
+ success: false,
201
+ errorCode: 'PARAM_ERROR',
202
+ errorMessage: `Invalid buySolAmount parameter: ${error.message}`,
203
+ data: null
204
+ };
205
+ }
206
+
207
+ // Get current price, orders data, and curve account in parallel
208
+ const [priceResult, ordersResult, upOrdersResult, curveAccount] = await Promise.all([
209
+ this.sdk.data.price(mint),
210
+ this.sdk.data.orders(mint, { type: 'down_orders' }),
211
+ this.sdk.data.orders(mint, { type: 'up_orders' }),
212
+ this.sdk.chain.getCurveAccount(mint, { skipBalances: true })
213
+ ]);
214
+
215
+ if (!priceResult || !ordersResult) {
216
+ return {
217
+ success: false,
218
+ errorCode: 'API_ERROR',
219
+ errorMessage: 'Failed to fetch price or orders data',
220
+ data: null
221
+ };
222
+ }
223
+
224
+ // Use simulateTokenBuy to calculate (approximate token amount first)
225
+ // This is a simplified implementation - you may need to iterate or use calcLiq directly
226
+ const currentPrice = typeof priceResult === 'string' ? BigInt(priceResult) : BigInt(priceResult.last_price || priceResult);
227
+
228
+ // Get curve account data for initialVirtualSol and initialVirtualToken
229
+ const initialVirtualSol = curveAccount.initialVirtualSol;
230
+ const initialVirtualToken = curveAccount.initialVirtualToken;
231
+
232
+ const reSolBuy = calcLiqSolBuy(currentPrice, buySolAmount, upOrdersResult.data.orders, 50, null, initialVirtualSol, initialVirtualToken);
233
+
234
+ //console.log("reSolBuy = ",reSolBuy)
235
+
236
+ const estimatedTokenAmount = reSolBuy.tokenAmount;
237
+
238
+ // Call simulateTokenBuy with estimated amount, pass curveAccount to avoid duplicate RPC
239
+ const tokenBuyResult = await this.simulateTokenBuy(mint, estimatedTokenAmount, null, priceResult, ordersResult, curveAccount);
240
+
241
+ // Transform result to match simulateBuy format
242
+ return {
243
+ success: true,
244
+ errorCode: null,
245
+ errorMessage: null,
246
+ data: {
247
+ inputType: 'sol',
248
+ inputAmount: solAmountBigInt,
249
+ maxAllowedPrice: currentPrice,
250
+ totalPriceSpan: tokenBuyResult.liqResult?.total_price_span || 0n,
251
+ transactionCompletionRate: parseFloat(tokenBuyResult.completion || '0'),
252
+ idealTokenAmount: estimatedTokenAmount,
253
+ idealSolAmount: solAmountBigInt,
254
+ actualRequiredSolAmount: tokenBuyResult.liqResult?.real_lp_sol_amount || solAmountBigInt,
255
+ actualObtainableTokenAmount: tokenBuyResult.liqResult?.free_lp_token_amount_sum || 0n,
256
+ theoreticalSolAmount: tokenBuyResult.liqResult?.ideal_lp_sol_amount || solAmountBigInt,
257
+ minimumSlippagePercentage: parseFloat(tokenBuyResult.slippage || '0'),
258
+ totalLiquiditySolAmount: tokenBuyResult.liqResult?.free_lp_sol_amount_sum || 0n,
259
+ totalLiquidityTokenAmount: tokenBuyResult.liqResult?.free_lp_token_amount_sum || 0n
260
+ }
261
+ };
262
+
263
+ } catch (error) {
264
+ return {
265
+ success: false,
266
+ errorCode: 'DATA_ERROR',
267
+ errorMessage: error.message || 'Unknown error occurred during buy simulation',
268
+ data: null
269
+ };
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Simulate sell transaction with token amount input
275
+ * 模拟以 Token 数量为输入的卖出交易
276
+ * @param {string} mint - Token address 代币地址
277
+ * @param {bigint|string|number} sellTokenAmount - Token amount to sell (u64 format, lamports)
278
+ * @returns {Promise<Object>} Sell simulation result with the following structure:
279
+ * - success: {boolean} Whether the simulation was successful
280
+ * - errorCode: {string|null} Error code if failed ('API_ERROR', 'DATA_ERROR', 'PARAM_ERROR')
281
+ * - errorMessage: {string|null} Error message if failed
282
+ * - data: {Object} Analysis result data containing:
283
+ * - inputType: {string} 'token' - input type
284
+ * - inputAmount: {bigint} Input token amount
285
+ * - minAllowedPrice: {bigint} Minimum allowed starting price (u128)
286
+ * - totalPriceSpan: {bigint} Total price range for the transaction (u128)
287
+ * - transactionCompletionRate: {number} Transaction completion rate (%)
288
+ * - idealSolAmount: {bigint} Ideal SOL amount obtainable
289
+ * - idealTokenAmount: {bigint} Ideal token amount to sell
290
+ * - actualObtainedSolAmount: {bigint} Actual SOL amount obtainable
291
+ * - actualConsumedTokenAmount: {bigint} Actual token amount consumed
292
+ * - theoreticalSolAmount: {bigint} Theoretical SOL amount obtainable
293
+ * - minimumSlippagePercentage: {number} Minimum slippage percentage
294
+ * - totalLiquiditySolAmount: {bigint} Total available liquidity in SOL
295
+ * - totalLiquidityTokenAmount: {bigint} Total available liquidity in tokens
296
+ */
297
+ async simulateSell(mint, sellTokenAmount) {
298
+ try {
299
+ // Parameter validation
300
+ if (!mint || typeof mint !== 'string') {
301
+ return {
302
+ success: false,
303
+ errorCode: 'PARAM_ERROR',
304
+ errorMessage: 'Invalid mint parameter: must be a non-empty string',
305
+ data: null
306
+ };
307
+ }
308
+
309
+ // Convert sellTokenAmount to bigint
310
+ let tokenAmountBigInt;
311
+ try {
312
+ tokenAmountBigInt = typeof sellTokenAmount === 'bigint' ? sellTokenAmount : BigInt(sellTokenAmount);
313
+ if (tokenAmountBigInt <= 0n) {
314
+ throw new Error('Amount must be greater than 0');
315
+ }
316
+ } catch (error) {
317
+ return {
318
+ success: false,
319
+ errorCode: 'PARAM_ERROR',
320
+ errorMessage: `Invalid sellTokenAmount parameter: ${error.message}`,
321
+ data: null
322
+ };
323
+ }
324
+
325
+ // Get current price and orders data in parallel
326
+ // For sell transactions, we need down_orders (long orders that provide buy liquidity)
327
+ const [priceResult, ordersResult] = await Promise.all([
328
+ this.sdk.data.price(mint),
329
+ this.sdk.data.orders(mint, { type: 'down_orders' })
330
+ ]);
331
+
332
+ if (!priceResult || !ordersResult) {
333
+ return {
334
+ success: false,
335
+ errorCode: 'API_ERROR',
336
+ errorMessage: 'Failed to fetch price or orders data',
337
+ data: null
338
+ };
339
+ }
340
+
341
+ const currentPrice = typeof priceResult === 'string' ? BigInt(priceResult) : BigInt(priceResult.last_price || priceResult);
342
+
343
+ // Call simulateTokenSell
344
+ const tokenSellResult = await this.simulateTokenSell(mint, tokenAmountBigInt, null, priceResult, ordersResult);
345
+
346
+ // Estimate ideal SOL amount
347
+ const priceDecimal = CurveAMM.u128ToDecimal(currentPrice);
348
+ const tokenInDecimal = Number(tokenAmountBigInt) / 1e9; // Convert token lamports to tokens (9-digit precision)
349
+ const estimatedSolAmount = BigInt(Math.floor((tokenInDecimal * priceDecimal) * 1e9)); // Convert to SOL lamports
350
+
351
+ // Transform result to match simulateSell format
352
+ return {
353
+ success: true,
354
+ errorCode: null,
355
+ errorMessage: null,
356
+ data: {
357
+ inputType: 'token',
358
+ inputAmount: tokenAmountBigInt,
359
+ minAllowedPrice: currentPrice,
360
+ totalPriceSpan: tokenSellResult.liqResult?.total_price_span || 0n,
361
+ transactionCompletionRate: parseFloat(tokenSellResult.completion || '0'),
362
+ idealSolAmount: estimatedSolAmount,
363
+ idealTokenAmount: tokenAmountBigInt,
364
+ actualObtainedSolAmount: tokenSellResult.liqResult?.real_lp_sol_amount || 0n,
365
+ actualConsumedTokenAmount: tokenAmountBigInt,
366
+ theoreticalSolAmount: tokenSellResult.liqResult?.ideal_lp_sol_amount || estimatedSolAmount,
367
+ minimumSlippagePercentage: parseFloat(tokenSellResult.slippage || '0'),
368
+ totalLiquiditySolAmount: tokenSellResult.liqResult?.free_lp_sol_amount_sum || 0n,
369
+ totalLiquidityTokenAmount: tokenSellResult.liqResult?.free_lp_token_amount_sum || 0n
370
+ }
371
+ };
372
+
373
+ } catch (error) {
374
+ return {
375
+ success: false,
376
+ errorCode: 'DATA_ERROR',
377
+ errorMessage: error.message || 'Unknown error occurred during sell simulation',
378
+ data: null
379
+ };
380
+ }
381
+ }
382
+
383
+
384
+ }
385
+
386
+ module.exports = SimulatorModule;