100x-sdk 1.0.4 → 1.0.6

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "address": "sGecRTjTZmnqJBmLK4ZMNCzsaMrgkFfNqEcYk1GhRde",
2
+ "address": "EVNaaiyg9z876PUmLCVQcdc5L5eJukT4pni5GtVJ8P37",
3
3
  "metadata": {
4
4
  "name": "fun100x",
5
5
  "version": "0.1.0",
@@ -3013,271 +3013,281 @@
3013
3013
  },
3014
3014
  {
3015
3015
  "code": 6062,
3016
+ "name": "InsufficientLongPayerBalance",
3017
+ "msg": "Payer wallet balance insufficient for long margin and fees"
3018
+ },
3019
+ {
3020
+ "code": 6063,
3021
+ "name": "InsufficientShortPayerBalance",
3022
+ "msg": "Payer wallet balance insufficient for short margin and fees"
3023
+ },
3024
+ {
3025
+ "code": 6064,
3016
3026
  "name": "InvalidAccountOwner",
3017
3027
  "msg": "Invalid account owner"
3018
3028
  },
3019
3029
  {
3020
- "code": 6063,
3030
+ "code": 6065,
3021
3031
  "name": "SellAmountExceedsOrderAmount",
3022
3032
  "msg": "Sell amount exceeds order's token holdings"
3023
3033
  },
3024
3034
  {
3025
- "code": 6064,
3035
+ "code": 6066,
3026
3036
  "name": "OrderNotExpiredMustCloseByOwner",
3027
3037
  "msg": "Non-expired order must be closed by owner"
3028
3038
  },
3029
3039
  {
3030
- "code": 6065,
3040
+ "code": 6067,
3031
3041
  "name": "SettlementAddressMustBeOwnerAddress",
3032
3042
  "msg": "Settlement address must be owner address"
3033
3043
  },
3034
3044
  {
3035
- "code": 6066,
3045
+ "code": 6068,
3036
3046
  "name": "BuyAmountExceedsOrderAmount",
3037
3047
  "msg": "Buy amount exceeds order's token holdings"
3038
3048
  },
3039
3049
  {
3040
- "code": 6067,
3050
+ "code": 6069,
3041
3051
  "name": "InsufficientTradeAmount",
3042
3052
  "msg": "Trade amount below minimum requirement"
3043
3053
  },
3044
3054
  {
3045
- "code": 6068,
3055
+ "code": 6070,
3046
3056
  "name": "SolAmountTooLarge",
3047
3057
  "msg": "SOL amount exceeds maximum limit (10000000 SOL per transaction)"
3048
3058
  },
3049
3059
  {
3050
- "code": 6069,
3060
+ "code": 6071,
3051
3061
  "name": "RemainingTokenAmountTooSmall",
3052
3062
  "msg": "Remaining token amount below minimum trade requirement"
3053
3063
  },
3054
3064
  {
3055
- "code": 6070,
3065
+ "code": 6072,
3056
3066
  "name": "TradeCooldownNotExpired",
3057
3067
  "msg": "Trade cooldown period not expired, please try again later"
3058
3068
  },
3059
3069
  {
3060
- "code": 6071,
3070
+ "code": 6073,
3061
3071
  "name": "ExceedApprovalAmount",
3062
3072
  "msg": "Sell amount exceeds approved amount, please call approval function first"
3063
3073
  },
3064
3074
  {
3065
- "code": 6072,
3075
+ "code": 6074,
3066
3076
  "name": "CooldownNotInitialized",
3067
3077
  "msg": "Sell trade requires calling approval or buy function first to initialize cooldown PDA"
3068
3078
  },
3069
3079
  {
3070
- "code": 6073,
3080
+ "code": 6075,
3071
3081
  "name": "CannotCloseCooldownWithBalance",
3072
3082
  "msg": "Cannot close cooldown PDA with non-zero token balance"
3073
3083
  },
3074
3084
  {
3075
- "code": 6074,
3085
+ "code": 6076,
3076
3086
  "name": "PriceCalculationError",
3077
3087
  "msg": "Price calculation error"
3078
3088
  },
3079
3089
  {
3080
- "code": 6075,
3090
+ "code": 6077,
3081
3091
  "name": "InvalidPartnerFeeRecipientAccount",
3082
3092
  "msg": "Invalid partner fee recipient account"
3083
3093
  },
3084
3094
  {
3085
- "code": 6076,
3095
+ "code": 6078,
3086
3096
  "name": "InvalidBaseFeeRecipientAccount",
3087
3097
  "msg": "Invalid base fee recipient account"
3088
3098
  },
3089
3099
  {
3090
- "code": 6077,
3100
+ "code": 6079,
3091
3101
  "name": "InvalidOrderbookAddress",
3092
3102
  "msg": "Orderbook address does not match curve account orderbook"
3093
3103
  },
3094
3104
  {
3095
- "code": 6078,
3105
+ "code": 6080,
3096
3106
  "name": "InvalidFeePercentage",
3097
3107
  "msg": "Fee percentage must be between 0-100"
3098
3108
  },
3099
3109
  {
3100
- "code": 6079,
3110
+ "code": 6081,
3101
3111
  "name": "InvalidFeeRate",
3102
3112
  "msg": "Fee rate exceeds maximum limit (10%)"
3103
3113
  },
3104
3114
  {
3105
- "code": 6080,
3115
+ "code": 6082,
3106
3116
  "name": "InvalidCustomFeeRate",
3107
3117
  "msg": "Custom fee rate must be between 1000 (1%) and 5000 (5%)"
3108
3118
  },
3109
3119
  {
3110
- "code": 6081,
3120
+ "code": 6083,
3111
3121
  "name": "InvalidBorrowDuration",
3112
3122
  "msg": "Borrow duration out of valid range (3-30 days)"
3113
3123
  },
3114
3124
  {
3115
- "code": 6082,
3125
+ "code": 6084,
3116
3126
  "name": "InvalidStopLossPrice",
3117
3127
  "msg": "Stop loss price does not meet minimum interval requirement"
3118
3128
  },
3119
3129
  {
3120
- "code": 6083,
3130
+ "code": 6085,
3121
3131
  "name": "NoProfitableFunds",
3122
3132
  "msg": "No profitable funds to transfer"
3123
3133
  },
3124
3134
  {
3125
- "code": 6084,
3135
+ "code": 6086,
3126
3136
  "name": "InsufficientPoolFunds",
3127
3137
  "msg": "Insufficient pool funds"
3128
3138
  },
3129
3139
  {
3130
- "code": 6085,
3140
+ "code": 6087,
3131
3141
  "name": "InsufficientPoolBalance",
3132
3142
  "msg": "Pool SOL account balance would fall below minimum required balance"
3133
3143
  },
3134
3144
  {
3135
- "code": 6086,
3145
+ "code": 6088,
3136
3146
  "name": "OrderBookManagerOverflow",
3137
3147
  "msg": "Math operation overflow"
3138
3148
  },
3139
3149
  {
3140
- "code": 6087,
3150
+ "code": 6089,
3141
3151
  "name": "OrderBookManagerInvalidSlotIndex",
3142
3152
  "msg": "Invalid slot index"
3143
3153
  },
3144
3154
  {
3145
- "code": 6088,
3155
+ "code": 6090,
3146
3156
  "name": "OrderBookManagerInvalidAccountData",
3147
3157
  "msg": "Invalid account data"
3148
3158
  },
3149
3159
  {
3150
- "code": 6089,
3160
+ "code": 6091,
3151
3161
  "name": "OrderBookManagerExceedsMaxCapacity",
3152
3162
  "msg": "New capacity exceeds maximum limit"
3153
3163
  },
3154
3164
  {
3155
- "code": 6090,
3165
+ "code": 6092,
3156
3166
  "name": "OrderBookManagerExceedsAccountSizeLimit",
3157
3167
  "msg": "Account size exceeds 10MB limit"
3158
3168
  },
3159
3169
  {
3160
- "code": 6091,
3170
+ "code": 6093,
3161
3171
  "name": "OrderBookManagerOrderIdMismatch",
3162
3172
  "msg": "Order ID mismatch"
3163
3173
  },
3164
3174
  {
3165
- "code": 6092,
3175
+ "code": 6094,
3166
3176
  "name": "OrderBookManagerEmptyOrderBook",
3167
3177
  "msg": "Order book is empty"
3168
3178
  },
3169
3179
  {
3170
- "code": 6093,
3180
+ "code": 6095,
3171
3181
  "name": "OrderBookManagerAccountNotWritable",
3172
3182
  "msg": "Account is not writable"
3173
3183
  },
3174
3184
  {
3175
- "code": 6094,
3185
+ "code": 6096,
3176
3186
  "name": "OrderBookManagerNotRentExempt",
3177
3187
  "msg": "Account not rent-exempt"
3178
3188
  },
3179
3189
  {
3180
- "code": 6095,
3190
+ "code": 6097,
3181
3191
  "name": "OrderBookManagerInvalidRentBalance",
3182
3192
  "msg": "Invalid rent balance"
3183
3193
  },
3184
3194
  {
3185
- "code": 6096,
3195
+ "code": 6098,
3186
3196
  "name": "OrderBookManagerInsufficientFunds",
3187
3197
  "msg": "Insufficient funds"
3188
3198
  },
3189
3199
  {
3190
- "code": 6097,
3200
+ "code": 6099,
3191
3201
  "name": "OrderBookManagerInvalidAccountOwner",
3192
3202
  "msg": "OrderBook account owner mismatch"
3193
3203
  },
3194
3204
  {
3195
- "code": 6098,
3205
+ "code": 6100,
3196
3206
  "name": "OrderBookManagerDataOutOfBounds",
3197
3207
  "msg": "Data access out of bounds"
3198
3208
  },
3199
3209
  {
3200
- "code": 6099,
3210
+ "code": 6101,
3201
3211
  "name": "NoValidInsertPosition",
3202
3212
  "msg": "Cannot find valid insert position, all candidates failed due to price range overlap"
3203
3213
  },
3204
3214
  {
3205
- "code": 6100,
3215
+ "code": 6102,
3206
3216
  "name": "EmptyCloseInsertIndices",
3207
3217
  "msg": "close_insert_indices array cannot be empty"
3208
3218
  },
3209
3219
  {
3210
- "code": 6101,
3220
+ "code": 6103,
3211
3221
  "name": "TooManyCloseInsertIndices",
3212
3222
  "msg": "close_insert_indices array cannot exceed 20 elements"
3213
3223
  },
3214
3224
  {
3215
- "code": 6102,
3225
+ "code": 6104,
3216
3226
  "name": "CloseOrderNotFound",
3217
3227
  "msg": "Specified close order not found"
3218
3228
  },
3219
3229
  {
3220
- "code": 6103,
3230
+ "code": 6105,
3221
3231
  "name": "LinkedListDeleteCountMismatch",
3222
3232
  "msg": "Linked list delete count mismatch: count inconsistent before/after deletion"
3223
3233
  },
3224
3234
  {
3225
- "code": 6104,
3235
+ "code": 6106,
3226
3236
  "name": "NameTooLong",
3227
3237
  "msg": "Token name too long, max 32 bytes"
3228
3238
  },
3229
3239
  {
3230
- "code": 6105,
3240
+ "code": 6107,
3231
3241
  "name": "NameEmpty",
3232
3242
  "msg": "Token name cannot be empty"
3233
3243
  },
3234
3244
  {
3235
- "code": 6106,
3245
+ "code": 6108,
3236
3246
  "name": "SymbolTooLong",
3237
3247
  "msg": "Token symbol too long, max 10 bytes"
3238
3248
  },
3239
3249
  {
3240
- "code": 6107,
3250
+ "code": 6109,
3241
3251
  "name": "SymbolEmpty",
3242
3252
  "msg": "Token symbol cannot be empty"
3243
3253
  },
3244
3254
  {
3245
- "code": 6108,
3255
+ "code": 6110,
3246
3256
  "name": "UriTooLong",
3247
3257
  "msg": "URI too long, max 200 bytes"
3248
3258
  },
3249
3259
  {
3250
- "code": 6109,
3260
+ "code": 6111,
3251
3261
  "name": "UriEmpty",
3252
3262
  "msg": "URI cannot be empty"
3253
3263
  },
3254
3264
  {
3255
- "code": 6110,
3265
+ "code": 6112,
3256
3266
  "name": "IncompleteAdvancedPoolParams",
3257
3267
  "msg": "Incomplete advanced pool parameters: custom_lp_sol, custom_lp_token, custom_borrow_ratio, custom_borrow_duration must be provided together"
3258
3268
  },
3259
3269
  {
3260
- "code": 6111,
3270
+ "code": 6113,
3261
3271
  "name": "InvalidInitialVirtualSol",
3262
3272
  "msg": "Initial virtual SOL out of valid range"
3263
3273
  },
3264
3274
  {
3265
- "code": 6112,
3275
+ "code": 6114,
3266
3276
  "name": "InvalidInitialVirtualToken",
3267
3277
  "msg": "Initial virtual Token out of valid range"
3268
3278
  },
3269
3279
  {
3270
- "code": 6113,
3280
+ "code": 6115,
3271
3281
  "name": "InvalidBorrowPoolRatio",
3272
3282
  "msg": "Borrow pool ratio out of valid range"
3273
3283
  },
3274
3284
  {
3275
- "code": 6114,
3285
+ "code": 6116,
3276
3286
  "name": "BorrowTokenCalculationOverflow",
3277
3287
  "msg": "Borrow pool token amount calculation overflow"
3278
3288
  },
3279
3289
  {
3280
- "code": 6115,
3290
+ "code": 6117,
3281
3291
  "name": "BorrowTokenAmountZero",
3282
3292
  "msg": "Borrow pool token amount cannot be zero"
3283
3293
  }
@@ -3424,8 +3434,10 @@
3424
3434
  "name": "pool_type",
3425
3435
  "docs": [
3426
3436
  "Pool type",
3427
- "0 = Basic version (uses default parameters, supports fee halving)",
3428
- "1 = Advanced version (custom parameters, fees never halved)"
3437
+ "0 = Basic version (uses default parameters)",
3438
+ "1 = Advanced version (custom parameters)",
3439
+ "Note: BOTH pool types participate in fee halving milestones",
3440
+ "(dynamic thresholds at 100x/1000x/10000x of the pool's own initial price)"
3429
3441
  ],
3430
3442
  "type": "u8"
3431
3443
  },
@@ -3433,8 +3445,8 @@
3433
3445
  "name": "borrow_pool_ratio",
3434
3446
  "docs": [
3435
3447
  "Borrow pool token ratio (recorded only for information display)",
3436
- "Actual value range: 5-30 (represents 5%-30%)",
3437
- "Basic version fixed at 20"
3448
+ "Actual value range: 2-8 (represents 2%-8%, deducted from total supply)",
3449
+ "Basic version fixed at 4"
3438
3450
  ],
3439
3451
  "type": "u8"
3440
3452
  }
@@ -1,6 +1,7 @@
1
1
 
2
2
 
3
3
  const { calcLiqTokenBuy, calcLiqTokenSell } = require('./calcLiq');
4
+ const { formatRatio } = require('./precision');
4
5
 
5
6
  /**
6
7
  * Simulate token buy transaction - calculate if target token amount can be purchased
@@ -95,8 +96,7 @@ async function simulateTokenBuy(mint, buyTokenAmount, passOrder = null, lastPric
95
96
  if (freeTokenAmount >= buyTokenAmountBig) {
96
97
  completionPercentage = "100.0";
97
98
  } else {
98
- const percentage = Math.floor((Number(freeTokenAmount) / Number(buyTokenAmountBig)) * 1000) / 10;
99
- completionPercentage = percentage.toFixed(1);
99
+ completionPercentage = formatRatio(freeTokenAmount, buyTokenAmountBig, 1, 100n);
100
100
  }
101
101
 
102
102
  // 2. Calculate slippage percentage and get final SOL amount
@@ -107,8 +107,7 @@ async function simulateTokenBuy(mint, buyTokenAmount, passOrder = null, lastPric
107
107
  if (realSolAmount > 0n) {
108
108
  // Normal case: calculate slippage
109
109
  const diff = idealSolAmount > realSolAmount ? idealSolAmount - realSolAmount : realSolAmount - idealSolAmount;
110
- const slippage = Math.floor((Number(diff) / Number(idealSolAmount)) * 1000) / 10;
111
- slippagePercentage = slippage.toFixed(1);
110
+ slippagePercentage = formatRatio(diff, idealSolAmount, 1, 100n);
112
111
  } else {
113
112
  // Special case: real SOL amount is 0, need to recalculate with suggested liquidity
114
113
  const suggestedAmount = (freeTokenAmount * BigInt(this.sdk.SUGGEST_LIQ_RATIO)) / 1000n;
@@ -133,8 +132,7 @@ async function simulateTokenBuy(mint, buyTokenAmount, passOrder = null, lastPric
133
132
  finalRealSolAmount = recalcRealSol;
134
133
 
135
134
  const diff = recalcIdealSol > recalcRealSol ? recalcIdealSol - recalcRealSol : recalcRealSol - recalcIdealSol;
136
- const slippage = Math.floor((Number(diff) / Number(recalcIdealSol)) * 1000) / 10;
137
- slippagePercentage = slippage.toFixed(1);
135
+ slippagePercentage = formatRatio(diff, recalcIdealSol, 1, 100n);
138
136
  }
139
137
 
140
138
  // 3. Calculate suggested liquidity
@@ -253,8 +251,7 @@ async function simulateTokenSell(mint, sellTokenAmount, passOrder = null, lastPr
253
251
  if (freeTokenAmount >= sellTokenAmountBig) {
254
252
  completionPercentage = "100.0";
255
253
  } else {
256
- const percentage = Math.floor((Number(freeTokenAmount) / Number(sellTokenAmountBig)) * 1000) / 10;
257
- completionPercentage = percentage.toFixed(1);
254
+ completionPercentage = formatRatio(freeTokenAmount, sellTokenAmountBig, 1, 100n);
258
255
  }
259
256
 
260
257
  // 2. Calculate slippage percentage and get final SOL amount
@@ -265,8 +262,7 @@ async function simulateTokenSell(mint, sellTokenAmount, passOrder = null, lastPr
265
262
  if (realSolAmount > 0n) {
266
263
  // Normal case: calculate slippage
267
264
  const diff = idealSolAmount > realSolAmount ? idealSolAmount - realSolAmount : realSolAmount - idealSolAmount;
268
- const slippage = Math.floor((Number(diff) / Number(idealSolAmount)) * 1000) / 10;
269
- slippagePercentage = slippage.toFixed(1);
265
+ slippagePercentage = formatRatio(diff, idealSolAmount, 1, 100n);
270
266
  } else {
271
267
  // Special case: real SOL amount is 0, need to recalculate with suggested liquidity
272
268
  const suggestedAmount = (freeTokenAmount * BigInt(this.sdk.SUGGEST_LIQ_RATIO)) / 1000n;
@@ -291,8 +287,7 @@ async function simulateTokenSell(mint, sellTokenAmount, passOrder = null, lastPr
291
287
  finalRealSolAmount = recalcRealSol;
292
288
 
293
289
  const diff = recalcIdealSol > recalcRealSol ? recalcIdealSol - recalcRealSol : recalcRealSol - recalcIdealSol;
294
- const slippage = Math.floor((Number(diff) / Number(recalcIdealSol)) * 1000) / 10;
295
- slippagePercentage = slippage.toFixed(1);
290
+ slippagePercentage = formatRatio(diff, recalcIdealSol, 1, 100n);
296
291
  }
297
292
 
298
293
  // 3. Calculate suggested liquidity
@@ -4,6 +4,7 @@ const CurveAMM = require('../../utils/curve_amm');
4
4
  const {transformOrdersData , checkPriceRangeOverlap} = require('./stop_loss_utils')
5
5
  const { PRICE_ADJUSTMENT_PERCENTAGE, MIN_STOP_LOSS_PERCENT } = require('./utils');
6
6
  const JSONbig = require('json-bigint')({ storeAsString: false });
7
+ const { formatRatio, ceilDiv } = require('./precision');
7
8
 
8
9
  /**
9
10
  * Simulate long position stop loss calculation
@@ -35,10 +36,11 @@ const JSONbig = require('json-bigint')({ storeAsString: false });
35
36
  * - For example: 3.5 means the stop loss price is 3.5% lower than the current price
36
37
  * - For a long position this value should be positive (stop loss price below current price)
37
38
  *
38
- * @returns {number} returns.leverage - Leverage ratio
39
+ * @returns {number} returns.leverage - Leverage ratio (existing four-decimal downward truncation)
39
40
  * - Formula: currentPrice / (currentPrice - executableStopLossPrice)
40
41
  * - For example: 28.57 means about 28.57x leverage
41
42
  * - The higher the leverage, the higher the risk, but also the higher the potential return
43
+ * @returns {string} returns.leverageDisplay - Rounded display value derived from the executable stop-loss price; not a maximum leverage limit
42
44
  *
43
45
  * @returns {bigint} returns.currentPrice - Current price (u128 format)
44
46
  * - The current token price used in the calculation
@@ -272,10 +274,17 @@ async function simulateLongStopLoss(mint, buyTokenAmount, stopLossPrice, lastPri
272
274
  // Calculate stop loss percentage
273
275
  let stopLossPercentage = 0;
274
276
  let leverage = 1;
277
+ let leverageDisplay = '1';
275
278
 
276
279
  if (currentPrice !== executableStopLossPrice) {
277
- stopLossPercentage = Number((BigInt(10000) * (currentPrice - executableStopLossPrice)) / currentPrice) / 100;
278
- leverage = Number((BigInt(10000) * currentPrice) / (currentPrice - executableStopLossPrice)) / 10000;
280
+ const priceDiff = currentPrice - executableStopLossPrice;
281
+ stopLossPercentage = priceDiff >= 0n
282
+ ? Number(formatRatio(priceDiff, currentPrice, 2, 100n))
283
+ : Number((10000n * priceDiff) / currentPrice) / 100;
284
+ leverage = Number((10000n * currentPrice) / priceDiff) / 10000;
285
+ leverageDisplay = priceDiff > 0n
286
+ ? formatRatio(currentPrice, priceDiff, 2, 1n, 'half-up', true)
287
+ : String(leverage);
279
288
  }
280
289
 
281
290
  // Calculate margin requirement
@@ -311,6 +320,7 @@ async function simulateLongStopLoss(mint, buyTokenAmount, stopLossPrice, lastPri
311
320
  tradeAmount: finalTradeAmount, // SOL output amount
312
321
  stopLossPercentage: stopLossPercentage, // Stop loss percentage relative to current price
313
322
  leverage: leverage, // Leverage ratio
323
+ leverageDisplay: leverageDisplay, // Rounded display value; leverage keeps its existing meaning
314
324
  currentPrice: currentPrice, // Current price
315
325
  iterations: iteration, // Number of adjustments
316
326
  originalStopLossPrice: BigInt(stopLossPrice), // Original stop loss price
@@ -355,10 +365,11 @@ async function simulateLongStopLoss(mint, buyTokenAmount, stopLossPrice, lastPri
355
365
  * - For example: 3.5 means the stop loss price is 3.5% higher than the current price
356
366
  * - For a short position this value should be positive (stop loss price above current price)
357
367
  *
358
- * @returns {number} returns.leverage - Leverage ratio
368
+ * @returns {number} returns.leverage - Leverage ratio (existing four-decimal downward truncation)
359
369
  * - Formula: currentPrice / (executableStopLossPrice - currentPrice)
360
370
  * - For example: 28.57 means about 28.57x leverage
361
371
  * - The higher the leverage, the higher the risk, but also the higher the potential return
372
+ * @returns {string} returns.leverageDisplay - Rounded display value derived from the executable stop-loss price; not a maximum leverage limit
362
373
  *
363
374
  * @returns {bigint} returns.currentPrice - Current price (u128 format)
364
375
  * - The current token price used in the calculation
@@ -584,11 +595,17 @@ async function simulateShortStopLoss(mint, sellTokenAmount, stopLossPrice, lastP
584
595
 
585
596
  // Calculate stop loss percentage
586
597
  // For short position, stop loss price is higher than current price, so it's a positive percentage
587
- const stopLossPercentage = Number((BigInt(10000) * (executableStopLossPrice - currentPrice)) / currentPrice) / 100;
598
+ const priceDiff = executableStopLossPrice - currentPrice;
599
+ const stopLossPercentage = priceDiff >= 0n
600
+ ? Number(formatRatio(priceDiff, currentPrice, 2, 100n))
601
+ : Number((10000n * priceDiff) / currentPrice) / 100;
588
602
 
589
603
  // Calculate leverage ratio
590
604
  // For short position, leverage = current price / (stop loss price - current price)
591
- const leverage = Number((BigInt(10000) * currentPrice) / (executableStopLossPrice - currentPrice)) / 10000;
605
+ const leverage = Number((10000n * currentPrice) / priceDiff) / 10000;
606
+ const leverageDisplay = priceDiff > 0n
607
+ ? formatRatio(currentPrice, priceDiff, 2, 1n, 'half-up', true)
608
+ : String(leverage);
592
609
 
593
610
  // Calculate margin requirement
594
611
  // Consistent with the contract formula (long_short.rs lines 890-894):
@@ -628,6 +645,7 @@ async function simulateShortStopLoss(mint, sellTokenAmount, stopLossPrice, lastP
628
645
  tradeAmount: finalTradeAmount, // SOL input amount (SOL needed to buy back tokens at close)
629
646
  stopLossPercentage: stopLossPercentage, // Stop loss percentage relative to current price
630
647
  leverage: leverage, // Leverage ratio
648
+ leverageDisplay: leverageDisplay, // Rounded display value; leverage keeps its existing meaning
631
649
  currentPrice: currentPrice, // Current price
632
650
  iterations: iteration, // Number of adjustments
633
651
  originalStopLossPrice: BigInt(stopLossPrice), // Original stop loss price
@@ -755,8 +773,9 @@ async function simulateLongSolStopLoss(mint, buySolAmount, stopLossPrice, lastPr
755
773
  // Calculate dynamic binary search upper bound based on leverage
756
774
  const stopLossPriceBigInt = BigInt(stopLossPrice);
757
775
  const priceDiff = currentPrice - stopLossPriceBigInt;
758
- const estimatedLeverage = priceDiff > 0n ? Number(currentPrice * 10000n / priceDiff) / 10000 : 10;
759
- const safeMultiplier = BigInt(Math.ceil(estimatedLeverage * 3)); // 3x safety factor
776
+ // Keep the original four-decimal leverage truncation used to size the search range.
777
+ const scaledLeverage = priceDiff > 0n ? currentPrice * 10000n / priceDiff : 100000n;
778
+ const safeMultiplier = ceilDiv(scaledLeverage * 3n, 10000n); // 3x safety factor
760
779
  const multiplier = safeMultiplier > 10n ? safeMultiplier : 10n; // minimum 10x
761
780
 
762
781
  // Use a binary search algorithm to find the maximum estimatedMargin that is less than buySolAmount
@@ -941,8 +960,9 @@ async function simulateShortSolStopLoss(mint, sellSolAmount, stopLossPrice, last
941
960
  // Calculate dynamic binary search upper bound based on leverage
942
961
  const stopLossPriceBigInt = BigInt(stopLossPrice);
943
962
  const priceDiff = stopLossPriceBigInt - currentPrice;
944
- const estimatedLeverage = priceDiff > 0n ? Number(currentPrice * 10000n / priceDiff) / 10000 : 10;
945
- const safeMultiplier = BigInt(Math.ceil(estimatedLeverage * 3)); // 3x safety factor
963
+ // Keep the original four-decimal leverage truncation used to size the search range.
964
+ const scaledLeverage = priceDiff > 0n ? currentPrice * 10000n / priceDiff : 100000n;
965
+ const safeMultiplier = ceilDiv(scaledLeverage * 3n, 10000n); // 3x safety factor
946
966
  const multiplier = safeMultiplier > 10n ? safeMultiplier : 10n; // minimum 10x
947
967
 
948
968
  // Use a binary search algorithm to find the maximum estimatedMargin that is less than sellSolAmount
@@ -0,0 +1,32 @@
1
+ /** Format a non-negative integer ratio without converting its operands to Number. */
2
+ function formatRatio(numerator, denominator, decimals, multiplier = 1n, rounding = 'down', trim = false) {
3
+ numerator = BigInt(numerator);
4
+ denominator = BigInt(denominator);
5
+ if (numerator < 0n || denominator <= 0n || !Number.isInteger(decimals) || decimals < 0) {
6
+ throw new RangeError('Invalid ratio');
7
+ }
8
+
9
+ const factor = 10n ** BigInt(decimals);
10
+ const scaledNumerator = numerator * multiplier * factor;
11
+ let quotient = scaledNumerator / denominator;
12
+ if (rounding === 'half-up') {
13
+ if ((scaledNumerator % denominator) * 2n >= denominator) quotient++;
14
+ } else if (rounding !== 'down') {
15
+ throw new RangeError('Invalid rounding mode');
16
+ }
17
+
18
+ if (decimals === 0) return quotient.toString();
19
+ const integer = quotient / factor;
20
+ const fraction = (quotient % factor).toString().padStart(decimals, '0');
21
+ const value = `${integer}.${fraction}`;
22
+ return trim ? value.replace(/\.?0+$/, '') : value;
23
+ }
24
+
25
+ function ceilDiv(numerator, denominator) {
26
+ numerator = BigInt(numerator);
27
+ denominator = BigInt(denominator);
28
+ if (numerator < 0n || denominator <= 0n) throw new RangeError('Invalid division');
29
+ return (numerator + denominator - 1n) / denominator;
30
+ }
31
+
32
+ module.exports = { formatRatio, ceilDiv };
@@ -339,9 +339,9 @@ class SimulatorModule {
339
339
  const tokenSellResult = await this.simulateTokenSell(mint, tokenAmountBigInt, null, priceResult, ordersResult);
340
340
 
341
341
  // Estimate ideal SOL amount
342
- const priceDecimal = CurveAMM.u128ToDecimal(currentPrice);
343
- const tokenInDecimal = Number(tokenAmountBigInt) / 1e9; // Convert token lamports to tokens (9-digit precision)
344
- const estimatedSolAmount = BigInt(Math.floor((tokenInDecimal * priceDecimal) * 1e9)); // Convert to SOL lamports
342
+ // Token and SOL both use 9 decimals, so their unit conversions cancel out.
343
+ const priceScale = BigInt(CurveAMM.PRICE_PRECISION_FACTOR_DECIMAL.toFixed(0));
344
+ const estimatedSolAmount = tokenAmountBigInt * currentPrice / priceScale;
345
345
 
346
346
  // Transform result to match simulateSell format
347
347
  return {
@@ -187,6 +187,26 @@ export interface SimulationResult {
187
187
  suggestedSolAmount: string;
188
188
  }
189
189
 
190
+ /** The leverage implied by the executable stop-loss price, not a maximum limit. */
191
+ export interface StopLossSimulationResult {
192
+ executableStopLossPrice: bigint;
193
+ tradeAmount: bigint;
194
+ stopLossPercentage: number;
195
+ /** Existing four-decimal, downward-truncated value. */
196
+ leverage: number;
197
+ /** Rounded to two decimals from the original price ratio, with trailing zeros removed. Display only. */
198
+ leverageDisplay: string;
199
+ currentPrice: bigint;
200
+ iterations: number;
201
+ originalStopLossPrice: bigint;
202
+ close_insert_indices: number[];
203
+ estimatedMargin: bigint;
204
+ rawSellSol?: bigint;
205
+ buyTokenAmount?: bigint;
206
+ sellTokenAmount?: bigint;
207
+ adjustmentIterations?: number;
208
+ }
209
+
190
210
  // ========================= Utility Related Types =========================
191
211
 
192
212
  export interface FindPrevNextResult {
@@ -238,8 +258,10 @@ export interface ParamModule {
238
258
  export interface SimulatorModule {
239
259
  simulateTokenBuy(mint: string, buyTokenAmount: bigint | string | number, passOrder?: string | null): Promise<SimulationResult>;
240
260
  simulateTokenSell(mint: string, sellTokenAmount: bigint | string | number, passOrder?: string | null): Promise<SimulationResult>;
241
- simulateLongStopLoss(mint: string, buyTokenAmount: bigint | string | number, stopLossPrice: bigint | string | number, lastPrice?: any, ordersData?: any): Promise<any>;
242
- simulateSellStopLoss(mint: string, sellTokenAmount: bigint | string | number, stopLossPrice: bigint | string | number, lastPrice?: any, ordersData?: any): Promise<any>;
261
+ simulateLongStopLoss(mint: string, buyTokenAmount: bigint | string | number, stopLossPrice: bigint | string | number, lastPrice?: any, ordersData?: any, borrowFee?: number | null): Promise<StopLossSimulationResult>;
262
+ simulateShortStopLoss(mint: string, sellTokenAmount: bigint | string | number, stopLossPrice: bigint | string | number, lastPrice?: any, ordersData?: any, borrowFee?: number | null): Promise<StopLossSimulationResult>;
263
+ simulateLongSolStopLoss(mint: string, buySolAmount: bigint | string | number, stopLossPrice: bigint | string | number, lastPrice?: any, ordersData?: any, borrowFee?: number | null, initialVirtualSol?: string | number | null, initialVirtualToken?: string | number | null, curveAccount?: any): Promise<StopLossSimulationResult>;
264
+ simulateShortSolStopLoss(mint: string, sellSolAmount: bigint | string | number, stopLossPrice: bigint | string | number, lastPrice?: any, ordersData?: any, borrowFee?: number | null, initialVirtualSol?: string | number | null, initialVirtualToken?: string | number | null, curveAccount?: any): Promise<StopLossSimulationResult>;
243
265
  }
244
266
 
245
267
  // ========================= Data Interface Types =========================