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.
- package/dist/100x-sdk.cjs.js +217 -146
- package/dist/100x-sdk.esm.js +217 -146
- package/dist/100x-sdk.js +217 -146
- package/dist/100x-sdk.js.map +1 -1
- package/dist/index.d.ts +24 -2
- package/package.json +2 -2
- package/src/idl/fun100x_localnet.json +70 -58
- package/src/idl/fun100x_main.json +70 -58
- package/src/modules/simulator/buy_sell_token.js +7 -12
- package/src/modules/simulator/long_shrot_stop.js +30 -10
- package/src/modules/simulator/precision.js +32 -0
- package/src/modules/simulator.js +3 -3
- package/src/types/index.d.ts +24 -2
- package/src/utils/constants.js +3 -5
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"address": "
|
|
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":
|
|
3030
|
+
"code": 6065,
|
|
3021
3031
|
"name": "SellAmountExceedsOrderAmount",
|
|
3022
3032
|
"msg": "Sell amount exceeds order's token holdings"
|
|
3023
3033
|
},
|
|
3024
3034
|
{
|
|
3025
|
-
"code":
|
|
3035
|
+
"code": 6066,
|
|
3026
3036
|
"name": "OrderNotExpiredMustCloseByOwner",
|
|
3027
3037
|
"msg": "Non-expired order must be closed by owner"
|
|
3028
3038
|
},
|
|
3029
3039
|
{
|
|
3030
|
-
"code":
|
|
3040
|
+
"code": 6067,
|
|
3031
3041
|
"name": "SettlementAddressMustBeOwnerAddress",
|
|
3032
3042
|
"msg": "Settlement address must be owner address"
|
|
3033
3043
|
},
|
|
3034
3044
|
{
|
|
3035
|
-
"code":
|
|
3045
|
+
"code": 6068,
|
|
3036
3046
|
"name": "BuyAmountExceedsOrderAmount",
|
|
3037
3047
|
"msg": "Buy amount exceeds order's token holdings"
|
|
3038
3048
|
},
|
|
3039
3049
|
{
|
|
3040
|
-
"code":
|
|
3050
|
+
"code": 6069,
|
|
3041
3051
|
"name": "InsufficientTradeAmount",
|
|
3042
3052
|
"msg": "Trade amount below minimum requirement"
|
|
3043
3053
|
},
|
|
3044
3054
|
{
|
|
3045
|
-
"code":
|
|
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":
|
|
3060
|
+
"code": 6071,
|
|
3051
3061
|
"name": "RemainingTokenAmountTooSmall",
|
|
3052
3062
|
"msg": "Remaining token amount below minimum trade requirement"
|
|
3053
3063
|
},
|
|
3054
3064
|
{
|
|
3055
|
-
"code":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
3085
|
+
"code": 6076,
|
|
3076
3086
|
"name": "PriceCalculationError",
|
|
3077
3087
|
"msg": "Price calculation error"
|
|
3078
3088
|
},
|
|
3079
3089
|
{
|
|
3080
|
-
"code":
|
|
3090
|
+
"code": 6077,
|
|
3081
3091
|
"name": "InvalidPartnerFeeRecipientAccount",
|
|
3082
3092
|
"msg": "Invalid partner fee recipient account"
|
|
3083
3093
|
},
|
|
3084
3094
|
{
|
|
3085
|
-
"code":
|
|
3095
|
+
"code": 6078,
|
|
3086
3096
|
"name": "InvalidBaseFeeRecipientAccount",
|
|
3087
3097
|
"msg": "Invalid base fee recipient account"
|
|
3088
3098
|
},
|
|
3089
3099
|
{
|
|
3090
|
-
"code":
|
|
3100
|
+
"code": 6079,
|
|
3091
3101
|
"name": "InvalidOrderbookAddress",
|
|
3092
3102
|
"msg": "Orderbook address does not match curve account orderbook"
|
|
3093
3103
|
},
|
|
3094
3104
|
{
|
|
3095
|
-
"code":
|
|
3105
|
+
"code": 6080,
|
|
3096
3106
|
"name": "InvalidFeePercentage",
|
|
3097
3107
|
"msg": "Fee percentage must be between 0-100"
|
|
3098
3108
|
},
|
|
3099
3109
|
{
|
|
3100
|
-
"code":
|
|
3110
|
+
"code": 6081,
|
|
3101
3111
|
"name": "InvalidFeeRate",
|
|
3102
3112
|
"msg": "Fee rate exceeds maximum limit (10%)"
|
|
3103
3113
|
},
|
|
3104
3114
|
{
|
|
3105
|
-
"code":
|
|
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":
|
|
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":
|
|
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":
|
|
3130
|
+
"code": 6085,
|
|
3121
3131
|
"name": "NoProfitableFunds",
|
|
3122
3132
|
"msg": "No profitable funds to transfer"
|
|
3123
3133
|
},
|
|
3124
3134
|
{
|
|
3125
|
-
"code":
|
|
3135
|
+
"code": 6086,
|
|
3126
3136
|
"name": "InsufficientPoolFunds",
|
|
3127
3137
|
"msg": "Insufficient pool funds"
|
|
3128
3138
|
},
|
|
3129
3139
|
{
|
|
3130
|
-
"code":
|
|
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":
|
|
3145
|
+
"code": 6088,
|
|
3136
3146
|
"name": "OrderBookManagerOverflow",
|
|
3137
3147
|
"msg": "Math operation overflow"
|
|
3138
3148
|
},
|
|
3139
3149
|
{
|
|
3140
|
-
"code":
|
|
3150
|
+
"code": 6089,
|
|
3141
3151
|
"name": "OrderBookManagerInvalidSlotIndex",
|
|
3142
3152
|
"msg": "Invalid slot index"
|
|
3143
3153
|
},
|
|
3144
3154
|
{
|
|
3145
|
-
"code":
|
|
3155
|
+
"code": 6090,
|
|
3146
3156
|
"name": "OrderBookManagerInvalidAccountData",
|
|
3147
3157
|
"msg": "Invalid account data"
|
|
3148
3158
|
},
|
|
3149
3159
|
{
|
|
3150
|
-
"code":
|
|
3160
|
+
"code": 6091,
|
|
3151
3161
|
"name": "OrderBookManagerExceedsMaxCapacity",
|
|
3152
3162
|
"msg": "New capacity exceeds maximum limit"
|
|
3153
3163
|
},
|
|
3154
3164
|
{
|
|
3155
|
-
"code":
|
|
3165
|
+
"code": 6092,
|
|
3156
3166
|
"name": "OrderBookManagerExceedsAccountSizeLimit",
|
|
3157
3167
|
"msg": "Account size exceeds 10MB limit"
|
|
3158
3168
|
},
|
|
3159
3169
|
{
|
|
3160
|
-
"code":
|
|
3170
|
+
"code": 6093,
|
|
3161
3171
|
"name": "OrderBookManagerOrderIdMismatch",
|
|
3162
3172
|
"msg": "Order ID mismatch"
|
|
3163
3173
|
},
|
|
3164
3174
|
{
|
|
3165
|
-
"code":
|
|
3175
|
+
"code": 6094,
|
|
3166
3176
|
"name": "OrderBookManagerEmptyOrderBook",
|
|
3167
3177
|
"msg": "Order book is empty"
|
|
3168
3178
|
},
|
|
3169
3179
|
{
|
|
3170
|
-
"code":
|
|
3180
|
+
"code": 6095,
|
|
3171
3181
|
"name": "OrderBookManagerAccountNotWritable",
|
|
3172
3182
|
"msg": "Account is not writable"
|
|
3173
3183
|
},
|
|
3174
3184
|
{
|
|
3175
|
-
"code":
|
|
3185
|
+
"code": 6096,
|
|
3176
3186
|
"name": "OrderBookManagerNotRentExempt",
|
|
3177
3187
|
"msg": "Account not rent-exempt"
|
|
3178
3188
|
},
|
|
3179
3189
|
{
|
|
3180
|
-
"code":
|
|
3190
|
+
"code": 6097,
|
|
3181
3191
|
"name": "OrderBookManagerInvalidRentBalance",
|
|
3182
3192
|
"msg": "Invalid rent balance"
|
|
3183
3193
|
},
|
|
3184
3194
|
{
|
|
3185
|
-
"code":
|
|
3195
|
+
"code": 6098,
|
|
3186
3196
|
"name": "OrderBookManagerInsufficientFunds",
|
|
3187
3197
|
"msg": "Insufficient funds"
|
|
3188
3198
|
},
|
|
3189
3199
|
{
|
|
3190
|
-
"code":
|
|
3200
|
+
"code": 6099,
|
|
3191
3201
|
"name": "OrderBookManagerInvalidAccountOwner",
|
|
3192
3202
|
"msg": "OrderBook account owner mismatch"
|
|
3193
3203
|
},
|
|
3194
3204
|
{
|
|
3195
|
-
"code":
|
|
3205
|
+
"code": 6100,
|
|
3196
3206
|
"name": "OrderBookManagerDataOutOfBounds",
|
|
3197
3207
|
"msg": "Data access out of bounds"
|
|
3198
3208
|
},
|
|
3199
3209
|
{
|
|
3200
|
-
"code":
|
|
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":
|
|
3215
|
+
"code": 6102,
|
|
3206
3216
|
"name": "EmptyCloseInsertIndices",
|
|
3207
3217
|
"msg": "close_insert_indices array cannot be empty"
|
|
3208
3218
|
},
|
|
3209
3219
|
{
|
|
3210
|
-
"code":
|
|
3220
|
+
"code": 6103,
|
|
3211
3221
|
"name": "TooManyCloseInsertIndices",
|
|
3212
3222
|
"msg": "close_insert_indices array cannot exceed 20 elements"
|
|
3213
3223
|
},
|
|
3214
3224
|
{
|
|
3215
|
-
"code":
|
|
3225
|
+
"code": 6104,
|
|
3216
3226
|
"name": "CloseOrderNotFound",
|
|
3217
3227
|
"msg": "Specified close order not found"
|
|
3218
3228
|
},
|
|
3219
3229
|
{
|
|
3220
|
-
"code":
|
|
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":
|
|
3235
|
+
"code": 6106,
|
|
3226
3236
|
"name": "NameTooLong",
|
|
3227
3237
|
"msg": "Token name too long, max 32 bytes"
|
|
3228
3238
|
},
|
|
3229
3239
|
{
|
|
3230
|
-
"code":
|
|
3240
|
+
"code": 6107,
|
|
3231
3241
|
"name": "NameEmpty",
|
|
3232
3242
|
"msg": "Token name cannot be empty"
|
|
3233
3243
|
},
|
|
3234
3244
|
{
|
|
3235
|
-
"code":
|
|
3245
|
+
"code": 6108,
|
|
3236
3246
|
"name": "SymbolTooLong",
|
|
3237
3247
|
"msg": "Token symbol too long, max 10 bytes"
|
|
3238
3248
|
},
|
|
3239
3249
|
{
|
|
3240
|
-
"code":
|
|
3250
|
+
"code": 6109,
|
|
3241
3251
|
"name": "SymbolEmpty",
|
|
3242
3252
|
"msg": "Token symbol cannot be empty"
|
|
3243
3253
|
},
|
|
3244
3254
|
{
|
|
3245
|
-
"code":
|
|
3255
|
+
"code": 6110,
|
|
3246
3256
|
"name": "UriTooLong",
|
|
3247
3257
|
"msg": "URI too long, max 200 bytes"
|
|
3248
3258
|
},
|
|
3249
3259
|
{
|
|
3250
|
-
"code":
|
|
3260
|
+
"code": 6111,
|
|
3251
3261
|
"name": "UriEmpty",
|
|
3252
3262
|
"msg": "URI cannot be empty"
|
|
3253
3263
|
},
|
|
3254
3264
|
{
|
|
3255
|
-
"code":
|
|
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":
|
|
3270
|
+
"code": 6113,
|
|
3261
3271
|
"name": "InvalidInitialVirtualSol",
|
|
3262
3272
|
"msg": "Initial virtual SOL out of valid range"
|
|
3263
3273
|
},
|
|
3264
3274
|
{
|
|
3265
|
-
"code":
|
|
3275
|
+
"code": 6114,
|
|
3266
3276
|
"name": "InvalidInitialVirtualToken",
|
|
3267
3277
|
"msg": "Initial virtual Token out of valid range"
|
|
3268
3278
|
},
|
|
3269
3279
|
{
|
|
3270
|
-
"code":
|
|
3280
|
+
"code": 6115,
|
|
3271
3281
|
"name": "InvalidBorrowPoolRatio",
|
|
3272
3282
|
"msg": "Borrow pool ratio out of valid range"
|
|
3273
3283
|
},
|
|
3274
3284
|
{
|
|
3275
|
-
"code":
|
|
3285
|
+
"code": 6116,
|
|
3276
3286
|
"name": "BorrowTokenCalculationOverflow",
|
|
3277
3287
|
"msg": "Borrow pool token amount calculation overflow"
|
|
3278
3288
|
},
|
|
3279
3289
|
{
|
|
3280
|
-
"code":
|
|
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
|
|
3428
|
-
"1 = Advanced version (custom parameters
|
|
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:
|
|
3437
|
-
"Basic version fixed at
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
278
|
-
|
|
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
|
|
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((
|
|
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
|
-
|
|
759
|
-
const
|
|
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
|
-
|
|
945
|
-
const
|
|
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 };
|
package/src/modules/simulator.js
CHANGED
|
@@ -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
|
-
|
|
343
|
-
const
|
|
344
|
-
const estimatedSolAmount =
|
|
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 {
|
package/src/types/index.d.ts
CHANGED
|
@@ -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<
|
|
242
|
-
|
|
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 =========================
|