@cetusprotocol/sui-clmm-sdk 1.0.0
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/.turbo/turbo-build.log +11100 -0
- package/README.md +108 -0
- package/dist/index.d.mts +2251 -0
- package/dist/index.d.ts +2251 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +13 -0
- package/dist/index.mjs.map +1 -0
- package/docs/add_liquidity.md +145 -0
- package/docs/close_position.md +57 -0
- package/docs/collect_fees.md +37 -0
- package/docs/create_clmm_pool.md +228 -0
- package/docs/error_code.md +69 -0
- package/docs/get_clmm_pools.md +92 -0
- package/docs/get_positions.md +70 -0
- package/docs/get_reward.md +53 -0
- package/docs/get_ticks.md +39 -0
- package/docs/migrate_to_version_6.0.md +143 -0
- package/docs/open_position.md +224 -0
- package/docs/partner_swap.md +60 -0
- package/docs/pre_swap.md +136 -0
- package/docs/remove_liquidity.md +124 -0
- package/docs/swap.md +153 -0
- package/docs/utils.md +85 -0
- package/package.json +37 -0
- package/src/config/index.ts +2 -0
- package/src/config/mainnet.ts +41 -0
- package/src/config/testnet.ts +40 -0
- package/src/errors/errors.ts +93 -0
- package/src/errors/index.ts +1 -0
- package/src/index.ts +10 -0
- package/src/math/apr.ts +167 -0
- package/src/math/index.ts +1 -0
- package/src/modules/configModule.ts +540 -0
- package/src/modules/index.ts +5 -0
- package/src/modules/poolModule.ts +1066 -0
- package/src/modules/positionModule.ts +932 -0
- package/src/modules/rewarderModule.ts +430 -0
- package/src/modules/swapModule.ts +389 -0
- package/src/sdk.ts +131 -0
- package/src/types/clmm_type.ts +1002 -0
- package/src/types/clmmpool.ts +366 -0
- package/src/types/config_type.ts +241 -0
- package/src/types/index.ts +8 -0
- package/src/types/sui.ts +124 -0
- package/src/types/token_type.ts +189 -0
- package/src/utils/common.ts +426 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/positionUtils.ts +434 -0
- package/src/utils/swapUtils.ts +499 -0
- package/tests/add_liquidity.test.ts +121 -0
- package/tests/add_liquidity_fix_token.test.ts +182 -0
- package/tests/apr.test.ts +71 -0
- package/tests/cetus_config.test.ts +26 -0
- package/tests/collect_fees.test.ts +11 -0
- package/tests/pool.test.ts +267 -0
- package/tests/position.test.ts +145 -0
- package/tests/remove_liquidity.test.ts +119 -0
- package/tests/rewarder.test.ts +60 -0
- package/tests/sdk_config.test.ts +49 -0
- package/tests/swap.test.ts +254 -0
- package/tests/tsconfig.json +26 -0
- package/tsconfig.json +5 -0
- package/tsup.config.ts +10 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import { Transaction, TransactionObjectArgument } from '@mysten/sui/transactions'
|
|
2
|
+
import BN from 'bn.js'
|
|
3
|
+
import {
|
|
4
|
+
adjustForSlippage,
|
|
5
|
+
BuildCoinResult,
|
|
6
|
+
CLOCK_ADDRESS,
|
|
7
|
+
CoinAsset,
|
|
8
|
+
CoinAssist,
|
|
9
|
+
getPackagerConfigs,
|
|
10
|
+
MathUtil,
|
|
11
|
+
MAX_SQRT_PRICE,
|
|
12
|
+
MIN_SQRT_PRICE,
|
|
13
|
+
U64_MAX,
|
|
14
|
+
ZERO,
|
|
15
|
+
} from '@cetusprotocol/common-sdk'
|
|
16
|
+
import { ConfigErrorCode, handleError, handleMessageError, UtilsErrorCode } from '../errors/errors'
|
|
17
|
+
import { CetusClmmSDK, SdkOptions } from '../sdk'
|
|
18
|
+
import { SwapGasEstimateArg, SwapParams } from '../types/clmm_type'
|
|
19
|
+
import { ClmmIntegratePoolV2Module, ClmmIntegrateRouterModule, ClmmIntegrateRouterWithPartnerModule } from '../types/sui'
|
|
20
|
+
import { PositionUtils } from './positionUtils'
|
|
21
|
+
|
|
22
|
+
export class SwapUtils {
|
|
23
|
+
/**
|
|
24
|
+
* Get the default sqrt price limit for a swap.
|
|
25
|
+
*
|
|
26
|
+
* @param a2b - true if the swap is A to B, false if the swap is B to A.
|
|
27
|
+
* @returns The default sqrt price limit for the swap.
|
|
28
|
+
*/
|
|
29
|
+
static getDefaultSqrtPriceLimit(a2b: boolean): BN {
|
|
30
|
+
return new BN(a2b ? MIN_SQRT_PRICE : MAX_SQRT_PRICE)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get the default values for the otherAmountThreshold in a swap.
|
|
35
|
+
*
|
|
36
|
+
* @param amount_specified_is_input - The direction of a swap
|
|
37
|
+
* @returns The default values for the otherAmountThreshold parameter in a swap.
|
|
38
|
+
*/
|
|
39
|
+
static getDefaultOtherAmountThreshold(amount_specified_is_input: boolean): BN {
|
|
40
|
+
return amount_specified_is_input ? ZERO : U64_MAX
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* build add liquidity transaction
|
|
45
|
+
* @param params
|
|
46
|
+
* @param slippage
|
|
47
|
+
* @param curSqrtPrice
|
|
48
|
+
* @returns
|
|
49
|
+
*/
|
|
50
|
+
static async buildSwapTransactionForGas(
|
|
51
|
+
sdk: CetusClmmSDK,
|
|
52
|
+
params: SwapParams,
|
|
53
|
+
all_coin_asset: CoinAsset[],
|
|
54
|
+
gas_estimate_arg: SwapGasEstimateArg
|
|
55
|
+
): Promise<Transaction> {
|
|
56
|
+
let tx = this.buildSwapTransaction(sdk, params, all_coin_asset)
|
|
57
|
+
tx.setSender(sdk.getSenderAddress())
|
|
58
|
+
const newResult = await this.adjustTransactionForGas(
|
|
59
|
+
sdk,
|
|
60
|
+
CoinAssist.getCoinAssets(params.a2b ? params.coin_type_a : params.coin_type_b, all_coin_asset),
|
|
61
|
+
BigInt(params.by_amount_in ? params.amount : params.amount_limit),
|
|
62
|
+
tx
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
const { fixAmount, newTx } = newResult
|
|
66
|
+
|
|
67
|
+
if (newTx !== undefined) {
|
|
68
|
+
newTx.setSender(sdk.getSenderAddress())
|
|
69
|
+
if (params.by_amount_in) {
|
|
70
|
+
params.amount = fixAmount.toString()
|
|
71
|
+
} else {
|
|
72
|
+
params.amount_limit = fixAmount.toString()
|
|
73
|
+
}
|
|
74
|
+
params = await this.fixSwapParams(sdk, params, gas_estimate_arg)
|
|
75
|
+
|
|
76
|
+
const primaryCoinInputA = CoinAssist.buildCoinForAmount(
|
|
77
|
+
tx,
|
|
78
|
+
all_coin_asset,
|
|
79
|
+
params.a2b ? BigInt(params.by_amount_in ? params.amount : params.amount_limit) : BigInt(0),
|
|
80
|
+
params.coin_type_a
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const primaryCoinInputB = CoinAssist.buildCoinForAmount(
|
|
84
|
+
tx,
|
|
85
|
+
all_coin_asset,
|
|
86
|
+
params.a2b ? BigInt(0) : BigInt(params.by_amount_in ? params.amount : params.amount_limit),
|
|
87
|
+
params.coin_type_b
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
tx = this.buildSwapTransactionArgs(newTx, params, sdk.sdkOptions, primaryCoinInputA, primaryCoinInputB)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return tx
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* build swap transaction
|
|
98
|
+
* @param params
|
|
99
|
+
* @param packageId
|
|
100
|
+
* @returns
|
|
101
|
+
*/
|
|
102
|
+
static buildSwapTransactionArgs(
|
|
103
|
+
tx: Transaction,
|
|
104
|
+
params: SwapParams,
|
|
105
|
+
sdk_options: SdkOptions,
|
|
106
|
+
primary_coin_input_a: BuildCoinResult,
|
|
107
|
+
primary_coin_input_b: BuildCoinResult
|
|
108
|
+
): Transaction {
|
|
109
|
+
const { clmm_pool, integrate } = sdk_options
|
|
110
|
+
|
|
111
|
+
const sqrtPriceLimit = SwapUtils.getDefaultSqrtPriceLimit(params.a2b)
|
|
112
|
+
const typeArguments = [params.coin_type_a, params.coin_type_b]
|
|
113
|
+
const { global_config_id } = getPackagerConfigs(clmm_pool)
|
|
114
|
+
|
|
115
|
+
if (global_config_id === undefined) {
|
|
116
|
+
handleMessageError(ConfigErrorCode.InvalidConfig, 'clmm.config.global_config_id is undefined')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const hasSwapPartner = params.swap_partner !== undefined
|
|
120
|
+
|
|
121
|
+
const functionName = hasSwapPartner
|
|
122
|
+
? params.a2b
|
|
123
|
+
? 'swap_a2b_with_partner'
|
|
124
|
+
: 'swap_b2a_with_partner'
|
|
125
|
+
: params.a2b
|
|
126
|
+
? 'swap_a2b'
|
|
127
|
+
: 'swap_b2a'
|
|
128
|
+
|
|
129
|
+
const args = hasSwapPartner
|
|
130
|
+
? [
|
|
131
|
+
tx.object(global_config_id),
|
|
132
|
+
tx.object(params.pool_id),
|
|
133
|
+
tx.object(params.swap_partner!),
|
|
134
|
+
primary_coin_input_a.target_coin,
|
|
135
|
+
primary_coin_input_b.target_coin,
|
|
136
|
+
tx.pure.bool(params.by_amount_in),
|
|
137
|
+
tx.pure.u64(params.amount),
|
|
138
|
+
tx.pure.u64(params.amount_limit),
|
|
139
|
+
tx.pure.u128(sqrtPriceLimit.toString()),
|
|
140
|
+
tx.object(CLOCK_ADDRESS),
|
|
141
|
+
]
|
|
142
|
+
: [
|
|
143
|
+
tx.object(global_config_id),
|
|
144
|
+
tx.object(params.pool_id),
|
|
145
|
+
primary_coin_input_a.target_coin,
|
|
146
|
+
primary_coin_input_b.target_coin,
|
|
147
|
+
tx.pure.bool(params.by_amount_in),
|
|
148
|
+
tx.pure.u64(params.amount),
|
|
149
|
+
tx.pure.u64(params.amount_limit),
|
|
150
|
+
tx.pure.u128(sqrtPriceLimit.toString()),
|
|
151
|
+
tx.object(CLOCK_ADDRESS),
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
tx.moveCall({
|
|
155
|
+
target: `${integrate.published_at}::${ClmmIntegratePoolV2Module}::${functionName}`,
|
|
156
|
+
typeArguments,
|
|
157
|
+
arguments: args,
|
|
158
|
+
})
|
|
159
|
+
return tx
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* adjust transaction for gas
|
|
164
|
+
* @param sdk
|
|
165
|
+
* @param amount
|
|
166
|
+
* @param tx
|
|
167
|
+
* @returns
|
|
168
|
+
*/
|
|
169
|
+
static async adjustTransactionForGas(
|
|
170
|
+
sdk: CetusClmmSDK,
|
|
171
|
+
all_coins: CoinAsset[],
|
|
172
|
+
amount: bigint,
|
|
173
|
+
tx: Transaction
|
|
174
|
+
): Promise<{ fixAmount: bigint; newTx?: Transaction }> {
|
|
175
|
+
tx.setSender(sdk.getSenderAddress())
|
|
176
|
+
// amount coins
|
|
177
|
+
const amount_coins = CoinAssist.selectCoinAssetGreaterThanOrEqual(all_coins, amount).selected_coins
|
|
178
|
+
const total_amount = CoinAssist.calculateTotalBalance(all_coins)
|
|
179
|
+
|
|
180
|
+
if (amount_coins.length === 0) {
|
|
181
|
+
handleMessageError(UtilsErrorCode.InsufficientBalance, `Insufficient balance exceed amount ${amount} real amount ${total_amount}`)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// If the remaining coin balance is greater than 1000000000, no gas fee correction will be done
|
|
185
|
+
if (total_amount - amount > 1000000000) {
|
|
186
|
+
return { fixAmount: amount }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// payload Estimated gas consumption
|
|
190
|
+
const estimate_gas = await sdk.FullClient.calculationTxGas(tx)
|
|
191
|
+
|
|
192
|
+
// Find estimateGas objectIds
|
|
193
|
+
const gas_coins = CoinAssist.selectCoinAssetGreaterThanOrEqual(
|
|
194
|
+
all_coins,
|
|
195
|
+
BigInt(estimate_gas),
|
|
196
|
+
amount_coins.map((item) => item.coin_object_id)
|
|
197
|
+
).selected_coins
|
|
198
|
+
|
|
199
|
+
// There is not enough gas and the amount needs to be adjusted
|
|
200
|
+
if (gas_coins.length === 0) {
|
|
201
|
+
// Readjust the amount , Reserve 500 gas for the spit
|
|
202
|
+
const new_gas = BigInt(estimate_gas) + BigInt(500)
|
|
203
|
+
if (total_amount - amount < new_gas) {
|
|
204
|
+
amount -= new_gas
|
|
205
|
+
if (amount < 0) {
|
|
206
|
+
handleMessageError(UtilsErrorCode.InsufficientBalance, `gas Insufficient balance`)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const newTx = new Transaction()
|
|
210
|
+
return { fixAmount: amount, newTx }
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return { fixAmount: amount }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* build swap transaction
|
|
218
|
+
* @param params
|
|
219
|
+
* @param packageId
|
|
220
|
+
* @returns
|
|
221
|
+
*/
|
|
222
|
+
static buildSwapTransaction(sdk: CetusClmmSDK, params: SwapParams, all_coin_asset: CoinAsset[]): Transaction {
|
|
223
|
+
let tx = new Transaction()
|
|
224
|
+
tx.setSender(sdk.getSenderAddress())
|
|
225
|
+
|
|
226
|
+
const primaryCoinInputA = CoinAssist.buildCoinForAmount(
|
|
227
|
+
tx,
|
|
228
|
+
all_coin_asset,
|
|
229
|
+
params.a2b ? BigInt(params.by_amount_in ? params.amount : params.amount_limit) : BigInt(0),
|
|
230
|
+
params.coin_type_a,
|
|
231
|
+
false
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
const primaryCoinInputB = CoinAssist.buildCoinForAmount(
|
|
235
|
+
tx,
|
|
236
|
+
all_coin_asset,
|
|
237
|
+
params.a2b ? BigInt(0) : BigInt(params.by_amount_in ? params.amount : params.amount_limit),
|
|
238
|
+
params.coin_type_b,
|
|
239
|
+
false
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
tx = this.buildSwapTransactionArgs(tx, params, sdk.sdkOptions, primaryCoinInputA, primaryCoinInputB)
|
|
243
|
+
return tx
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
static async fixSwapParams(sdk: CetusClmmSDK, params: SwapParams, gasEstimateArg: SwapGasEstimateArg): Promise<SwapParams> {
|
|
247
|
+
const { current_pool } = gasEstimateArg
|
|
248
|
+
try {
|
|
249
|
+
const res: any = await sdk.Swap.preSwap({
|
|
250
|
+
decimals_a: gasEstimateArg.decimals_a,
|
|
251
|
+
decimals_b: gasEstimateArg.decimals_b,
|
|
252
|
+
a2b: params.a2b,
|
|
253
|
+
by_amount_in: params.by_amount_in,
|
|
254
|
+
amount: params.amount,
|
|
255
|
+
pool: current_pool,
|
|
256
|
+
current_sqrt_price: current_pool.current_sqrt_price,
|
|
257
|
+
coin_type_a: current_pool.coin_type_a,
|
|
258
|
+
coin_type_b: current_pool.coin_type_b,
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const to_amount = gasEstimateArg.by_amount_in ? res.estimated_amount_out : res.estimated_amount_in
|
|
262
|
+
|
|
263
|
+
const amount_limit = adjustForSlippage(to_amount, gasEstimateArg.slippage, !gasEstimateArg.by_amount_in)
|
|
264
|
+
params.amount_limit = amount_limit.toString()
|
|
265
|
+
} catch (error) {
|
|
266
|
+
handleError(ConfigErrorCode.InvalidConfig, error as Error)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return params
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
static async buildSwapTransactionWithoutTransferCoinsForGas(
|
|
273
|
+
sdk: CetusClmmSDK,
|
|
274
|
+
params: SwapParams,
|
|
275
|
+
all_coin_asset: CoinAsset[],
|
|
276
|
+
gas_estimate_arg: SwapGasEstimateArg
|
|
277
|
+
): Promise<{ tx: Transaction; coin_ab_s: TransactionObjectArgument[] }> {
|
|
278
|
+
let { tx, coin_ab_s } = SwapUtils.buildSwapTransactionWithoutTransferCoins(sdk, params, all_coin_asset)
|
|
279
|
+
tx.setSender(sdk.getSenderAddress())
|
|
280
|
+
const newResult = await SwapUtils.adjustTransactionForGas(
|
|
281
|
+
sdk,
|
|
282
|
+
CoinAssist.getCoinAssets(params.a2b ? params.coin_type_a : params.coin_type_b, all_coin_asset),
|
|
283
|
+
BigInt(params.by_amount_in ? params.amount : params.amount_limit),
|
|
284
|
+
tx
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
const { fixAmount, newTx } = newResult
|
|
288
|
+
|
|
289
|
+
if (newTx !== undefined) {
|
|
290
|
+
newTx.setSender(sdk.getSenderAddress())
|
|
291
|
+
if (params.by_amount_in) {
|
|
292
|
+
params.amount = fixAmount.toString()
|
|
293
|
+
} else {
|
|
294
|
+
params.amount_limit = fixAmount.toString()
|
|
295
|
+
}
|
|
296
|
+
params = await SwapUtils.fixSwapParams(sdk, params, gas_estimate_arg)
|
|
297
|
+
|
|
298
|
+
const primaryCoinInputA = CoinAssist.buildCoinForAmount(
|
|
299
|
+
tx,
|
|
300
|
+
all_coin_asset,
|
|
301
|
+
params.a2b ? BigInt(params.by_amount_in ? params.amount : params.amount_limit) : BigInt(0),
|
|
302
|
+
params.coin_type_a,
|
|
303
|
+
false,
|
|
304
|
+
true
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
const primaryCoinInputB = CoinAssist.buildCoinForAmount(
|
|
308
|
+
tx,
|
|
309
|
+
all_coin_asset,
|
|
310
|
+
params.a2b ? BigInt(0) : BigInt(params.by_amount_in ? params.amount : params.amount_limit),
|
|
311
|
+
params.coin_type_b,
|
|
312
|
+
false,
|
|
313
|
+
true
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
const res = SwapUtils.buildSwapTransactionWithoutTransferCoinArgs(
|
|
317
|
+
sdk,
|
|
318
|
+
newTx,
|
|
319
|
+
params,
|
|
320
|
+
sdk.sdkOptions,
|
|
321
|
+
primaryCoinInputA,
|
|
322
|
+
primaryCoinInputB
|
|
323
|
+
)
|
|
324
|
+
tx = res.tx
|
|
325
|
+
coin_ab_s = res.txRes
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return { tx, coin_ab_s }
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* build swap transaction and return swapped coin
|
|
333
|
+
* @param params
|
|
334
|
+
* @param packageId
|
|
335
|
+
* @returns
|
|
336
|
+
*/
|
|
337
|
+
static buildSwapTransactionWithoutTransferCoins(
|
|
338
|
+
sdk: CetusClmmSDK,
|
|
339
|
+
params: SwapParams,
|
|
340
|
+
all_coin_asset: CoinAsset[]
|
|
341
|
+
): { tx: Transaction; coin_ab_s: TransactionObjectArgument[] } {
|
|
342
|
+
const tx = new Transaction()
|
|
343
|
+
tx.setSender(sdk.getSenderAddress())
|
|
344
|
+
|
|
345
|
+
// Fix amount must set true, to support amount limit.
|
|
346
|
+
const primaryCoinInputA = CoinAssist.buildCoinForAmount(
|
|
347
|
+
tx,
|
|
348
|
+
all_coin_asset,
|
|
349
|
+
params.a2b ? BigInt(params.by_amount_in ? params.amount : params.amount_limit) : BigInt(0),
|
|
350
|
+
params.coin_type_a,
|
|
351
|
+
false,
|
|
352
|
+
true
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
const primaryCoinInputB = CoinAssist.buildCoinForAmount(
|
|
356
|
+
tx,
|
|
357
|
+
all_coin_asset,
|
|
358
|
+
params.a2b ? BigInt(0) : BigInt(params.by_amount_in ? params.amount : params.amount_limit),
|
|
359
|
+
params.coin_type_b,
|
|
360
|
+
false,
|
|
361
|
+
true
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
const res = SwapUtils.buildSwapTransactionWithoutTransferCoinArgs(sdk, tx, params, sdk.sdkOptions, primaryCoinInputA, primaryCoinInputB)
|
|
365
|
+
return { tx: res.tx, coin_ab_s: res.txRes }
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* build swap transaction
|
|
370
|
+
* @param params
|
|
371
|
+
* @param packageId
|
|
372
|
+
* @returns
|
|
373
|
+
*/
|
|
374
|
+
static buildSwapTransactionWithoutTransferCoinArgs(
|
|
375
|
+
sdk: CetusClmmSDK,
|
|
376
|
+
tx: Transaction,
|
|
377
|
+
params: SwapParams,
|
|
378
|
+
sdk_options: SdkOptions,
|
|
379
|
+
primary_coin_input_a: BuildCoinResult,
|
|
380
|
+
primary_coin_input_b: BuildCoinResult
|
|
381
|
+
): { tx: Transaction; txRes: TransactionObjectArgument[] } {
|
|
382
|
+
const { clmm_pool, integrate } = sdk_options
|
|
383
|
+
|
|
384
|
+
const sqrtPriceLimit = SwapUtils.getDefaultSqrtPriceLimit(params.a2b)
|
|
385
|
+
|
|
386
|
+
const { global_config_id } = getPackagerConfigs(clmm_pool)
|
|
387
|
+
|
|
388
|
+
if (global_config_id === undefined) {
|
|
389
|
+
handleMessageError(ConfigErrorCode.InvalidConfig, 'clmm.config.global_config_id is undefined')
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const hasSwapPartner = params.swap_partner !== undefined
|
|
393
|
+
|
|
394
|
+
const functionName = hasSwapPartner ? 'swap_with_partner' : 'swap'
|
|
395
|
+
|
|
396
|
+
const moduleName = hasSwapPartner ? ClmmIntegrateRouterWithPartnerModule : ClmmIntegrateRouterModule
|
|
397
|
+
|
|
398
|
+
const args = hasSwapPartner
|
|
399
|
+
? [
|
|
400
|
+
tx.object(global_config_id),
|
|
401
|
+
tx.object(params.pool_id),
|
|
402
|
+
tx.object(params.swap_partner!),
|
|
403
|
+
primary_coin_input_a.target_coin,
|
|
404
|
+
primary_coin_input_b.target_coin,
|
|
405
|
+
tx.pure.bool(params.a2b),
|
|
406
|
+
tx.pure.bool(params.by_amount_in),
|
|
407
|
+
tx.pure.u64(params.amount),
|
|
408
|
+
tx.pure.u128(sqrtPriceLimit.toString()),
|
|
409
|
+
tx.pure.bool(false), // use coin value always set false.
|
|
410
|
+
tx.object(CLOCK_ADDRESS),
|
|
411
|
+
]
|
|
412
|
+
: [
|
|
413
|
+
tx.object(global_config_id),
|
|
414
|
+
tx.object(params.pool_id),
|
|
415
|
+
primary_coin_input_a.target_coin,
|
|
416
|
+
primary_coin_input_b.target_coin,
|
|
417
|
+
tx.pure.bool(params.a2b),
|
|
418
|
+
tx.pure.bool(params.by_amount_in),
|
|
419
|
+
tx.pure.u64(params.amount),
|
|
420
|
+
tx.pure.u128(sqrtPriceLimit.toString()),
|
|
421
|
+
tx.pure.bool(false), // use coin value always set false.
|
|
422
|
+
tx.object(CLOCK_ADDRESS),
|
|
423
|
+
]
|
|
424
|
+
|
|
425
|
+
const typeArguments = [params.coin_type_a, params.coin_type_b]
|
|
426
|
+
const coinABs: TransactionObjectArgument[] = tx.moveCall({
|
|
427
|
+
target: `${integrate.published_at}::${moduleName}::${functionName}`,
|
|
428
|
+
typeArguments,
|
|
429
|
+
arguments: args,
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
if (params.by_amount_in) {
|
|
433
|
+
const toCoinType = params.a2b ? params.coin_type_b : params.coin_type_a
|
|
434
|
+
const toCoin = params.a2b ? coinABs[1] : coinABs[0]
|
|
435
|
+
const totalAmount = Number(params.amount_limit)
|
|
436
|
+
PositionUtils.checkCoinThreshold(sdk, params.by_amount_in, tx, toCoin, totalAmount, toCoinType)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return { tx, txRes: coinABs }
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Get lower sqrt price from token A.
|
|
445
|
+
*
|
|
446
|
+
* @param amount - The amount of tokens the user wanted to swap from.
|
|
447
|
+
* @param liquidity - The liquidity of the pool.
|
|
448
|
+
* @param sqrt_price_x64 - The sqrt price of the pool.
|
|
449
|
+
* @returns LowerSqrtPriceX64
|
|
450
|
+
*/
|
|
451
|
+
export function getLowerSqrtPriceFromCoinA(amount: BN, liquidity: BN, sqrt_price_x64: BN): BN {
|
|
452
|
+
const numerator = liquidity.mul(sqrt_price_x64).shln(64)
|
|
453
|
+
const denominator = liquidity.shln(64).add(amount.mul(sqrt_price_x64))
|
|
454
|
+
|
|
455
|
+
// always round up
|
|
456
|
+
return MathUtil.divRoundUp(numerator, denominator)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get upper sqrt price from token A.
|
|
461
|
+
*
|
|
462
|
+
* @param amount - The amount of tokens the user wanted to swap from.
|
|
463
|
+
* @param liquidity - The liquidity of the pool.
|
|
464
|
+
* @param sqrt_price_x64 - The sqrt price of the pool.
|
|
465
|
+
* @returns LowerSqrtPriceX64
|
|
466
|
+
*/
|
|
467
|
+
export function getUpperSqrtPriceFromCoinA(amount: BN, liquidity: BN, sqrt_price_x64: BN): BN {
|
|
468
|
+
const numerator = liquidity.mul(sqrt_price_x64).shln(64)
|
|
469
|
+
const denominator = liquidity.shln(64).sub(amount.mul(sqrt_price_x64))
|
|
470
|
+
|
|
471
|
+
// always round up
|
|
472
|
+
return MathUtil.divRoundUp(numerator, denominator)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Get lower sqrt price from coin B.
|
|
477
|
+
*
|
|
478
|
+
* @param amount - The amount of coins the user wanted to swap from.
|
|
479
|
+
* @param liquidity - The liquidity of the pool.
|
|
480
|
+
* @param sqrt_price_x64 - The sqrt price of the pool.
|
|
481
|
+
* @returns LowerSqrtPriceX64
|
|
482
|
+
*/
|
|
483
|
+
export function getLowerSqrtPriceFromCoinB(amount: BN, liquidity: BN, sqrt_price_x64: BN): BN {
|
|
484
|
+
// always round down(rounding up a negative number)
|
|
485
|
+
return sqrt_price_x64.sub(MathUtil.divRoundUp(amount.shln(64), liquidity))
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Get upper sqrt price from coin B.
|
|
490
|
+
*
|
|
491
|
+
* @param amount - The amount of coins the user wanted to swap from.
|
|
492
|
+
* @param liquidity - The liquidity of the pool.
|
|
493
|
+
* @param sqrtPriceX64 - The sqrt price of the pool.
|
|
494
|
+
* @returns LowerSqrtPriceX64
|
|
495
|
+
*/
|
|
496
|
+
export function getUpperSqrtPriceFromCoinB(amount: BN, liquidity: BN, sqrt_price_x64: BN): BN {
|
|
497
|
+
// always round down (rounding up a negative number)
|
|
498
|
+
return sqrt_price_x64.add(amount.shln(64).div(liquidity))
|
|
499
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'
|
|
2
|
+
import BN from 'bn.js'
|
|
3
|
+
import { adjustForCoinSlippage, ClmmPoolUtil, Percentage, printTransaction, TickMath } from '@cetusprotocol/common-sdk'
|
|
4
|
+
import { buildTestAccount } from '@cetusprotocol/test-utils'
|
|
5
|
+
import 'isomorphic-fetch'
|
|
6
|
+
import CetusClmmSDK, { AddLiquidityParams, CustomRangeParams, FullRangeParams } from '../src'
|
|
7
|
+
|
|
8
|
+
let send_key_pair: Ed25519Keypair
|
|
9
|
+
|
|
10
|
+
const poolId = '0xb8d7d9e66a60c239e7a60110efcf8de6c705580ed924d0dde141f4a0e2c90105'
|
|
11
|
+
const position_nft_id = '0xcf995f40b0f9c40a8b03e0b9d9554fea2bc12a18fe63db3a04c59c46be5c10be'
|
|
12
|
+
describe('add Liquidity Module', () => {
|
|
13
|
+
const sdk = CetusClmmSDK.createSDK({ env: 'mainnet' })
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
send_key_pair = buildTestAccount()
|
|
17
|
+
sdk.setSenderAddress(send_key_pair.getPublicKey().toSuiAddress())
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test('custom tick range add liquidity for input liquidity', async () => {
|
|
21
|
+
const pool = await sdk.Pool.getPool(poolId)
|
|
22
|
+
const position = await sdk.Position.getPositionById(position_nft_id)
|
|
23
|
+
const cur_sqrt_price = new BN(pool.current_sqrt_price)
|
|
24
|
+
const lower_tick = Number(position.tick_lower_index)
|
|
25
|
+
const upper_tick = Number(position.tick_upper_index)
|
|
26
|
+
|
|
27
|
+
const lower_sqrt_price = TickMath.tickIndexToSqrtPriceX64(lower_tick)
|
|
28
|
+
const upper_sqrt_price = TickMath.tickIndexToSqrtPriceX64(upper_tick)
|
|
29
|
+
|
|
30
|
+
const slippage_tolerance = new Percentage(new BN(5), new BN(100))
|
|
31
|
+
const liquidity = 10000
|
|
32
|
+
|
|
33
|
+
const coin_amounts = ClmmPoolUtil.getCoinAmountFromLiquidity(
|
|
34
|
+
new BN(liquidity),
|
|
35
|
+
cur_sqrt_price,
|
|
36
|
+
lower_sqrt_price,
|
|
37
|
+
upper_sqrt_price,
|
|
38
|
+
false
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
const { coin_amount_limit_a, coin_amount_limit_b } = adjustForCoinSlippage(coin_amounts, slippage_tolerance, true)
|
|
42
|
+
|
|
43
|
+
const add_liquidity_payload_params: AddLiquidityParams = {
|
|
44
|
+
coin_type_a: pool.coin_type_a,
|
|
45
|
+
coin_type_b: pool.coin_type_b,
|
|
46
|
+
pool_id: pool.id,
|
|
47
|
+
tick_lower: lower_tick.toString(),
|
|
48
|
+
tick_upper: upper_tick.toString(),
|
|
49
|
+
delta_liquidity: liquidity.toString(),
|
|
50
|
+
max_amount_a: coin_amount_limit_a.toString(),
|
|
51
|
+
max_amount_b: coin_amount_limit_b.toString(),
|
|
52
|
+
pos_id: position.pos_object_id,
|
|
53
|
+
rewarder_coin_types: [],
|
|
54
|
+
collect_fee: false,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const payload = await sdk.Position.createAddLiquidityPayload(add_liquidity_payload_params)
|
|
58
|
+
|
|
59
|
+
printTransaction(payload)
|
|
60
|
+
|
|
61
|
+
const transfer_txn = await sdk.FullClient.executeTx(send_key_pair, payload, true)
|
|
62
|
+
console.log('createAddLiquidityPayload: ', transfer_txn)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('custom price range add liquidity for input liquidity ', async () => {
|
|
66
|
+
const params: CustomRangeParams = {
|
|
67
|
+
is_full_range: false,
|
|
68
|
+
min_price: '0.2',
|
|
69
|
+
max_price: '0.7',
|
|
70
|
+
coin_decimals_a: 6,
|
|
71
|
+
coin_decimals_b: 9,
|
|
72
|
+
price_base_coin: 'coin_a',
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const result = await sdk.Position.calculateAddLiquidityResultWithPrice({
|
|
76
|
+
add_mode_params: params,
|
|
77
|
+
pool_id: poolId,
|
|
78
|
+
liquidity: '10000',
|
|
79
|
+
slippage: 0.01,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
console.log('result: ', result)
|
|
83
|
+
|
|
84
|
+
const payload = await sdk.Position.addLiquidityWithPricePayload({
|
|
85
|
+
pool_id: poolId,
|
|
86
|
+
calculate_result: result,
|
|
87
|
+
add_mode_params: params,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
printTransaction(payload)
|
|
91
|
+
|
|
92
|
+
const transfer_txn = await sdk.FullClient.executeTx(send_key_pair, payload, true)
|
|
93
|
+
console.log('createAddLiquidityPayload: ', transfer_txn)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('full range price range add liquidity for input liquidity ', async () => {
|
|
97
|
+
const params: FullRangeParams = {
|
|
98
|
+
is_full_range: true,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const result = await sdk.Position.calculateAddLiquidityResultWithPrice({
|
|
102
|
+
add_mode_params: params,
|
|
103
|
+
pool_id: poolId,
|
|
104
|
+
liquidity: '1000000',
|
|
105
|
+
slippage: 0.01,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
console.log('result: ', result)
|
|
109
|
+
|
|
110
|
+
const payload = await sdk.Position.addLiquidityWithPricePayload({
|
|
111
|
+
pool_id: poolId,
|
|
112
|
+
calculate_result: result,
|
|
113
|
+
add_mode_params: params,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
printTransaction(payload)
|
|
117
|
+
|
|
118
|
+
const transfer_txn = await sdk.FullClient.executeTx(send_key_pair, payload, false)
|
|
119
|
+
console.log('createAddLiquidityPayload: ', transfer_txn)
|
|
120
|
+
})
|
|
121
|
+
})
|