@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.
Files changed (64) hide show
  1. package/.turbo/turbo-build.log +11100 -0
  2. package/README.md +108 -0
  3. package/dist/index.d.mts +2251 -0
  4. package/dist/index.d.ts +2251 -0
  5. package/dist/index.js +13 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +13 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/docs/add_liquidity.md +145 -0
  10. package/docs/close_position.md +57 -0
  11. package/docs/collect_fees.md +37 -0
  12. package/docs/create_clmm_pool.md +228 -0
  13. package/docs/error_code.md +69 -0
  14. package/docs/get_clmm_pools.md +92 -0
  15. package/docs/get_positions.md +70 -0
  16. package/docs/get_reward.md +53 -0
  17. package/docs/get_ticks.md +39 -0
  18. package/docs/migrate_to_version_6.0.md +143 -0
  19. package/docs/open_position.md +224 -0
  20. package/docs/partner_swap.md +60 -0
  21. package/docs/pre_swap.md +136 -0
  22. package/docs/remove_liquidity.md +124 -0
  23. package/docs/swap.md +153 -0
  24. package/docs/utils.md +85 -0
  25. package/package.json +37 -0
  26. package/src/config/index.ts +2 -0
  27. package/src/config/mainnet.ts +41 -0
  28. package/src/config/testnet.ts +40 -0
  29. package/src/errors/errors.ts +93 -0
  30. package/src/errors/index.ts +1 -0
  31. package/src/index.ts +10 -0
  32. package/src/math/apr.ts +167 -0
  33. package/src/math/index.ts +1 -0
  34. package/src/modules/configModule.ts +540 -0
  35. package/src/modules/index.ts +5 -0
  36. package/src/modules/poolModule.ts +1066 -0
  37. package/src/modules/positionModule.ts +932 -0
  38. package/src/modules/rewarderModule.ts +430 -0
  39. package/src/modules/swapModule.ts +389 -0
  40. package/src/sdk.ts +131 -0
  41. package/src/types/clmm_type.ts +1002 -0
  42. package/src/types/clmmpool.ts +366 -0
  43. package/src/types/config_type.ts +241 -0
  44. package/src/types/index.ts +8 -0
  45. package/src/types/sui.ts +124 -0
  46. package/src/types/token_type.ts +189 -0
  47. package/src/utils/common.ts +426 -0
  48. package/src/utils/index.ts +3 -0
  49. package/src/utils/positionUtils.ts +434 -0
  50. package/src/utils/swapUtils.ts +499 -0
  51. package/tests/add_liquidity.test.ts +121 -0
  52. package/tests/add_liquidity_fix_token.test.ts +182 -0
  53. package/tests/apr.test.ts +71 -0
  54. package/tests/cetus_config.test.ts +26 -0
  55. package/tests/collect_fees.test.ts +11 -0
  56. package/tests/pool.test.ts +267 -0
  57. package/tests/position.test.ts +145 -0
  58. package/tests/remove_liquidity.test.ts +119 -0
  59. package/tests/rewarder.test.ts +60 -0
  60. package/tests/sdk_config.test.ts +49 -0
  61. package/tests/swap.test.ts +254 -0
  62. package/tests/tsconfig.json +26 -0
  63. package/tsconfig.json +5 -0
  64. package/tsup.config.ts +10 -0
@@ -0,0 +1,932 @@
1
+ import { DevInspectResults, SuiClient } from '@mysten/sui/client'
2
+ import type { TransactionObjectArgument } from '@mysten/sui/transactions'
3
+ import { Transaction } from '@mysten/sui/transactions'
4
+ import { isValidSuiObjectId, normalizeSuiAddress } from '@mysten/sui/utils'
5
+ import BN from 'bn.js'
6
+ import type { BuildCoinResult, DataPage, PaginationArgs, SuiObjectIdType } from '@cetusprotocol/common-sdk'
7
+ import {
8
+ asUintN,
9
+ ClmmPoolUtil,
10
+ CLOCK_ADDRESS,
11
+ CoinAssist,
12
+ d,
13
+ DETAILS_KEYS,
14
+ extractStructTagFromType,
15
+ getObjectFields,
16
+ getPackagerConfigs,
17
+ IModule,
18
+ FullClient,
19
+ TickMath,
20
+ TickUtil,
21
+ createFullClient,
22
+ } from '@cetusprotocol/common-sdk'
23
+ import Decimal from 'decimal.js'
24
+ import { handleError, handleMessageError, PoolErrorCode, PositionErrorCode } from '../errors/errors'
25
+ import type { CetusClmmSDK } from '../sdk'
26
+ import type {
27
+ AddLiquidityFixTokenParams,
28
+ AddLiquidityParams,
29
+ AddLiquidityWithPriceRangeParams,
30
+ CalculateAddLiquidityFixCoinWithPriceParams,
31
+ CalculateAddLiquidityResult,
32
+ CalculateAddLiquidityWithPriceParams,
33
+ ClosePositionParams,
34
+ CollectFeeParams,
35
+ CollectFeesQuote,
36
+ FetchPosFeeParams,
37
+ OpenPositionParams,
38
+ OpenPositionWithPriceParams,
39
+ Position,
40
+ PositionReward,
41
+ PositionTransactionInfo,
42
+ RemoveLiquidityParams,
43
+ } from '../types'
44
+ import { ClmmFetcherModule, ClmmIntegratePoolModule, ClmmIntegratePoolV2Module, ClmmIntegratePoolV3Module } from '../types/sui'
45
+ import { buildPosition, buildPositionReward, buildPositionTransactionInfo } from '../utils'
46
+ import { findAdjustCoin, PositionUtils } from '../utils/positionUtils'
47
+ /**
48
+ * Helper class to help interact with clmm position with a position router interface.
49
+ */
50
+ export class PositionModule implements IModule<CetusClmmSDK> {
51
+ protected _sdk: CetusClmmSDK
52
+
53
+ constructor(sdk: CetusClmmSDK) {
54
+ this._sdk = sdk
55
+ }
56
+
57
+ get sdk() {
58
+ return this._sdk
59
+ }
60
+
61
+ /**
62
+ * Builds the full address of the Position type.
63
+ * @returns The full address of the Position type.
64
+ */
65
+ buildPositionType() {
66
+ const cetusClmm = this._sdk.sdkOptions.clmm_pool.package_id
67
+ return `${cetusClmm}::position::Position`
68
+ }
69
+
70
+ /**
71
+ * Gets a list of position transaction information for the given position ID.
72
+ * @param {Object} params - The parameters for the position transaction list.
73
+ * @param {string} params.pos_id - The ID of the position to get transactions for.
74
+ * @param {PaginationArgs} [params.pagination_args] - The pagination arguments for the transaction list.
75
+ * @param {string} [params.order] - The order of the transaction list.
76
+ * @param {string} [params.full_rpc_url] - The full RPC URL for the transaction list.
77
+ * @param {string} [params.origin_pos_id] - The origin position ID for the transaction list.
78
+ * @returns {Promise<DataPage<PositionTransactionInfo>>} A promise that resolves to a DataPage object containing the position transaction information.
79
+ */
80
+ async getPositionTransactionList({
81
+ pos_id,
82
+ origin_pos_id,
83
+ full_rpc_url,
84
+ pagination_args = 'all',
85
+ order = 'ascending',
86
+ }: {
87
+ pos_id: string
88
+ origin_pos_id?: string
89
+ full_rpc_url?: string
90
+ pagination_args?: PaginationArgs
91
+ order?: 'ascending' | 'descending' | null | undefined
92
+ }): Promise<DataPage<PositionTransactionInfo>> {
93
+ const { FullClient: fullClient } = this._sdk
94
+ const filterIds: string[] = [pos_id]
95
+ if (origin_pos_id) {
96
+ filterIds.push(origin_pos_id)
97
+ }
98
+ let client
99
+ if (full_rpc_url) {
100
+ client = createFullClient(new SuiClient({ url: full_rpc_url }))
101
+ } else {
102
+ client = fullClient
103
+ }
104
+ const data: DataPage<PositionTransactionInfo> = {
105
+ data: [],
106
+ has_next_page: false,
107
+ }
108
+ try {
109
+ const res = await client.queryTransactionBlocksByPage({ ChangedObject: pos_id }, pagination_args, order)
110
+
111
+ res.data.forEach((item, index) => {
112
+ const dataList = buildPositionTransactionInfo(item, index, filterIds)
113
+ data.data = [...data.data, ...dataList]
114
+ })
115
+ data.has_next_page = res.has_next_page
116
+ data.next_cursor = res.next_cursor
117
+ return data
118
+ } catch (error) {
119
+ handleError(PoolErrorCode.FetchError, error as Error, {
120
+ [DETAILS_KEYS.METHOD_NAME]: 'getPositionTransactionList',
121
+ })
122
+ }
123
+
124
+ return data
125
+ }
126
+
127
+ /**
128
+ * Gets a list of positions for the given account address.
129
+ * @param account_address The account address to get positions for.
130
+ * @param assign_pool_ids An array of pool ID to filter the positions by.
131
+ * @returns array of Position objects.
132
+ */
133
+ async getPositionList(account_address: string, assign_pool_ids: string[] = [], show_display = true): Promise<Position[]> {
134
+ const all_position: Position[] = []
135
+
136
+ const owner_res: any = await this._sdk.FullClient.getOwnedObjectsByPage(account_address, {
137
+ options: { showType: true, showContent: true, showDisplay: show_display, showOwner: true },
138
+ filter: { Package: this._sdk.sdkOptions.clmm_pool.package_id },
139
+ })
140
+
141
+ const has_assign_pool_ids = assign_pool_ids.length > 0
142
+ for (const item of owner_res.data as any[]) {
143
+ const type = extractStructTagFromType(item.data.type)
144
+
145
+ if (type.full_address === this.buildPositionType()) {
146
+ const position = buildPosition(item)
147
+ const cache_key = `${position.pos_object_id}_getPositionList`
148
+ this._sdk.updateCache(cache_key, position)
149
+ if (has_assign_pool_ids) {
150
+ if (assign_pool_ids.includes(position.pool)) {
151
+ all_position.push(position)
152
+ }
153
+ } else {
154
+ all_position.push(position)
155
+ }
156
+ }
157
+ }
158
+
159
+ return all_position
160
+ }
161
+
162
+ /**
163
+ * Gets a position by its handle and ID. But it needs pool info, so it is not recommended to use this method.
164
+ * if you want to get a position, you can use getPositionById method directly.
165
+ * @param {string} position_handle The handle of the position to get.
166
+ * @param {string} position_id The ID of the position to get.
167
+ * @param {boolean} calculate_rewarder Whether to calculate the rewarder of the position.
168
+ * @returns {Promise<Position>} Position object.
169
+ */
170
+ async getPosition(position_handle: string, position_id: string, calculate_rewarder = true, show_display = true): Promise<Position> {
171
+ let position = await this.getSimplePosition(position_id, show_display)
172
+ if (calculate_rewarder) {
173
+ position = await this.updatePositionRewarders(position_handle, position)
174
+ }
175
+ return position
176
+ }
177
+
178
+ /**
179
+ * Gets a position by its ID.
180
+ * @param {string} position_id The ID of the position to get.
181
+ * @param {boolean} calculate_rewarder Whether to calculate the rewarder of the position.
182
+ * @param {boolean} show_display When some testnet rpc nodes can't return object's display data, you can set this option to false to avoid returning errors. Default is true.
183
+ * @returns {Promise<Position>} Position object.
184
+ */
185
+ async getPositionById(position_id: string, calculate_rewarder = true, show_display = true): Promise<Position> {
186
+ const position = await this.getSimplePosition(position_id, show_display)
187
+ if (calculate_rewarder) {
188
+ const pool = await this._sdk.Pool.getPool(position.pool, false)
189
+ const result = await this.updatePositionRewarders(pool.position_manager.positions_handle, position)
190
+ return result
191
+ }
192
+ return position
193
+ }
194
+
195
+ /**
196
+ * Gets a simple position for the given position ID.
197
+ * @param {string} position_id The ID of the position to get.
198
+ * @returns {Promise<Position>} Position object.
199
+ */
200
+ async getSimplePosition(position_id: string, show_display = true): Promise<Position> {
201
+ const cache_key = `${position_id}_getPositionList`
202
+
203
+ let position = this.getSimplePositionByCache(position_id)
204
+
205
+ if (position === undefined) {
206
+ const object_data_responses = await this.sdk.FullClient.getObject({
207
+ id: position_id,
208
+ options: { showContent: true, showType: true, showDisplay: show_display, showOwner: true },
209
+ })
210
+ position = buildPosition(object_data_responses)
211
+
212
+ this._sdk.updateCache(cache_key, position)
213
+ }
214
+ return position
215
+ }
216
+
217
+ /**
218
+ * Gets a simple position for the given position ID.
219
+ * @param {string} position_id Position object id
220
+ * @returns {Position | undefined} Position object
221
+ */
222
+ private getSimplePositionByCache(position_id: string): Position | undefined {
223
+ const cache_key = `${position_id}_getPositionList`
224
+ return this._sdk.getCache<Position>(cache_key)
225
+ }
226
+
227
+ /**
228
+ * Gets a list of simple positions for the given position ID.
229
+ * @param {SuiObjectIdType[]} position_ids The IDs of the positions to get.
230
+ * @returns {Promise<Position[]>} A promise that resolves to an array of Position objects.
231
+ */
232
+ async getSimplePositionList(position_ids: SuiObjectIdType[], show_display = true): Promise<Position[]> {
233
+ const position_list: Position[] = []
234
+ const not_found_ids: SuiObjectIdType[] = []
235
+
236
+ position_ids.forEach((id) => {
237
+ const position = this.getSimplePositionByCache(id)
238
+ if (position) {
239
+ position_list.push(position)
240
+ } else {
241
+ not_found_ids.push(id)
242
+ }
243
+ })
244
+
245
+ if (not_found_ids.length > 0) {
246
+ const object_data_responses = await this._sdk.FullClient.batchGetObjects(not_found_ids, {
247
+ showOwner: true,
248
+ showContent: true,
249
+ showDisplay: show_display,
250
+ showType: true,
251
+ })
252
+
253
+ object_data_responses.forEach((info) => {
254
+ if (info.error == null) {
255
+ const position = buildPosition(info)
256
+ position_list.push(position)
257
+ const cache_key = `${position.pos_object_id}_getPositionList`
258
+ this._sdk.updateCache(cache_key, position)
259
+ }
260
+ })
261
+ }
262
+
263
+ return position_list
264
+ }
265
+
266
+ /**
267
+ * Updates the rewarders of position
268
+ * @param {string} position_handle Position handle
269
+ * @param {Position} position Position object
270
+ * @returns {Promise<Position>} A promise that resolves to an array of Position objects.
271
+ */
272
+ private async updatePositionRewarders(position_handle: string, position: Position): Promise<Position> {
273
+ const position_reward = await this.getPositionRewarders(position_handle, position.pos_object_id)
274
+ return {
275
+ ...position,
276
+ ...position_reward,
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Gets the position rewarders for the given position handle and position object ID.
282
+ * @param {string} position_handle The handle of the position.
283
+ * @param {string} position_id The ID of the position object.
284
+ * @returns {Promise<PositionReward | undefined>} PositionReward object.
285
+ */
286
+ async getPositionRewarders(position_handle: string, position_id: string): Promise<PositionReward | undefined> {
287
+ try {
288
+ const dynamic_field_object = await this._sdk.FullClient.getDynamicFieldObject({
289
+ parentId: position_handle,
290
+ name: {
291
+ type: '0x2::object::ID',
292
+ value: position_id,
293
+ },
294
+ })
295
+
296
+ const object_fields = getObjectFields(dynamic_field_object.data as any) as any
297
+ const fields = object_fields.value.fields.value
298
+ const position_reward = buildPositionReward(fields)
299
+ return position_reward
300
+ } catch (error) {
301
+ console.log(error)
302
+ return undefined
303
+ }
304
+ }
305
+
306
+ public buildFetchPosFee(params: FetchPosFeeParams, tx: Transaction) {
307
+ const { clmm_pool, integrate } = this.sdk.sdkOptions
308
+ const typeArguments = [params.coin_type_a, params.coin_type_b]
309
+ const args = [tx.object(getPackagerConfigs(clmm_pool).global_config_id), tx.object(params.pool_id), tx.pure.address(params.position_id)]
310
+ tx.moveCall({
311
+ target: `${integrate.published_at}::${ClmmFetcherModule}::fetch_position_fees`,
312
+ arguments: args,
313
+ typeArguments,
314
+ })
315
+ }
316
+
317
+ parsedPosFeeData(simulate_res: DevInspectResults) {
318
+ const feeData: Record<string, { position_id: string; fee_owned_a: string; fee_owned_b: string }> = {}
319
+ const feeValueData: any[] = simulate_res.events?.filter((item: any) => {
320
+ return item.type.includes('fetcher_script::FetchPositionFeesEvent')
321
+ })
322
+
323
+ for (let i = 0; i < feeValueData.length; i += 1) {
324
+ const { parsedJson } = feeValueData[i]
325
+ const posObj = {
326
+ position_id: parsedJson.position_id,
327
+ fee_owned_a: parsedJson.fee_owned_a,
328
+ fee_owned_b: parsedJson.fee_owned_b,
329
+ }
330
+ feeData[parsedJson.position_id] = posObj
331
+ }
332
+
333
+ return feeData
334
+ }
335
+
336
+ /**
337
+ * Fetches the Position fee amount for a given list of addresses.
338
+ * @param {FetchPosFeeParams[]} params An array of FetchPosFeeParams objects containing the target addresses and their corresponding amounts.
339
+ * @returns {Promise<CollectFeesQuote[]>} A Promise that resolves with the fetched position fee amount for the specified addresses.
340
+ */
341
+ public async fetchPosFeeAmount(params: FetchPosFeeParams[]): Promise<CollectFeesQuote[]> {
342
+ const { clmm_pool, integrate } = this.sdk.sdkOptions
343
+ const tx = new Transaction()
344
+
345
+ for (const paramItem of params) {
346
+ this.buildFetchPosFee(paramItem, tx)
347
+ }
348
+
349
+ const simulateRes = await this.sdk.FullClient.devInspectTransactionBlock({
350
+ transactionBlock: tx,
351
+ sender: normalizeSuiAddress('0x0'),
352
+ })
353
+
354
+ if (simulateRes.error != null) {
355
+ handleMessageError(
356
+ PoolErrorCode.InvalidPoolObject,
357
+ `fetch position fee error code: ${simulateRes.error ?? 'unknown error'}, please check config and position and pool object ids`,
358
+ {
359
+ [DETAILS_KEYS.METHOD_NAME]: 'fetchPosFeeAmount',
360
+ }
361
+ )
362
+ }
363
+
364
+ const result: CollectFeesQuote[] = []
365
+ const parsedPosFeeData = this.parsedPosFeeData(simulateRes)
366
+ for (let i = 0; i < params.length; i += 1) {
367
+ const posFeeData = parsedPosFeeData[params[i].position_id]
368
+ if (posFeeData) {
369
+ const posFeeResult: CollectFeesQuote = {
370
+ fee_owned_a: posFeeData.fee_owned_a,
371
+ fee_owned_b: posFeeData.fee_owned_b,
372
+ position_id: params[i].position_id,
373
+ }
374
+ result.push(posFeeResult)
375
+ }
376
+ }
377
+
378
+ return result
379
+ }
380
+
381
+ /**
382
+ * Fetches the Position fee amount for a given list of addresses.
383
+ * @param position_ids An array of position object id.
384
+ * @returns {Promise<Record<string, CollectFeesQuote>>} A Promise that resolves with the fetched position fee amount for the specified position object ids.
385
+ */
386
+ async batchFetchPositionFees(position_ids: string[]): Promise<Record<string, CollectFeesQuote>> {
387
+ const pos_fee_params_list: FetchPosFeeParams[] = []
388
+ for (const id of position_ids) {
389
+ const position = await this._sdk.Position.getPositionById(id, false)
390
+ const pool = await this._sdk.Pool.getPool(position.pool, false)
391
+ pos_fee_params_list.push({
392
+ pool_id: pool.id,
393
+ position_id: position.pos_object_id,
394
+ coin_type_a: pool.coin_type_a,
395
+ coin_type_b: pool.coin_type_b,
396
+ })
397
+ }
398
+
399
+ const positionMap: Record<string, CollectFeesQuote> = {}
400
+
401
+ if (pos_fee_params_list.length > 0) {
402
+ const result: CollectFeesQuote[] = await this.fetchPosFeeAmount(pos_fee_params_list)
403
+ for (const pos_rewarder_info of result) {
404
+ positionMap[pos_rewarder_info.position_id] = pos_rewarder_info
405
+ }
406
+ return positionMap
407
+ }
408
+ return positionMap
409
+ }
410
+
411
+ /**
412
+ * create add liquidity transaction payload with fix token
413
+ * @param {AddLiquidityFixTokenParams} params
414
+ * @param gas_estimate_arg : When the fix input amount is SUI, gasEstimateArg can control whether to recalculate the number of SUI to prevent insufficient gas.
415
+ * If this parameter is not passed, gas estimation is not performed
416
+ * @returns {Promise<TransactionBlock>}
417
+ */
418
+ async createAddLiquidityFixTokenPayload(
419
+ params: AddLiquidityFixTokenParams,
420
+ gas_estimate_arg?: {
421
+ slippage: number
422
+ cur_sqrt_price: BN
423
+ },
424
+ tx?: Transaction,
425
+ input_coin_a?: TransactionObjectArgument,
426
+ input_coin_b?: TransactionObjectArgument
427
+ ): Promise<Transaction> {
428
+ const all_coin_asset = await this._sdk.FullClient.getOwnerCoinAssets(this.sdk.getSenderAddress())
429
+
430
+ if (gas_estimate_arg) {
431
+ const { is_adjust_coin_a, is_adjust_coin_b } = findAdjustCoin(params)
432
+ params = params as AddLiquidityFixTokenParams
433
+ if ((params.fix_amount_a && is_adjust_coin_a) || (!params.fix_amount_a && is_adjust_coin_b)) {
434
+ tx = await PositionUtils.buildAddLiquidityFixTokenForGas(
435
+ this._sdk,
436
+ all_coin_asset,
437
+ params,
438
+ gas_estimate_arg,
439
+ tx,
440
+ input_coin_a,
441
+ input_coin_b
442
+ )
443
+ return tx
444
+ }
445
+ }
446
+
447
+ return PositionUtils.buildAddLiquidityFixToken(this._sdk, all_coin_asset, params, tx, input_coin_a, input_coin_b)
448
+ }
449
+
450
+ /**
451
+ * create add liquidity transaction payload
452
+ * @param {AddLiquidityParams} params
453
+ * @returns {Promise<TransactionBlock>}
454
+ */
455
+ async createAddLiquidityPayload(
456
+ params: AddLiquidityParams,
457
+ tx?: Transaction,
458
+ input_coin_a?: TransactionObjectArgument,
459
+ input_coin_b?: TransactionObjectArgument
460
+ ): Promise<Transaction> {
461
+ const { integrate, clmm_pool } = this._sdk.sdkOptions
462
+
463
+ const tick_lower = asUintN(BigInt(params.tick_lower)).toString()
464
+ const tick_upper = asUintN(BigInt(params.tick_upper)).toString()
465
+
466
+ const typeArguments = [params.coin_type_a, params.coin_type_b]
467
+
468
+ tx = tx || new Transaction()
469
+
470
+ const needOpenPosition = !isValidSuiObjectId(params.pos_id)
471
+ const max_amount_a = BigInt(params.max_amount_a)
472
+ const max_amount_b = BigInt(params.max_amount_b)
473
+
474
+ let primary_coin_a_inputs: BuildCoinResult
475
+ let primary_coin_b_inputs: BuildCoinResult
476
+ if (input_coin_a == null || input_coin_b == null) {
477
+ const all_coin_asset = await this._sdk.FullClient.getOwnerCoinAssets(this.sdk.getSenderAddress())
478
+ primary_coin_a_inputs = CoinAssist.buildCoinForAmount(tx, all_coin_asset, max_amount_a, params.coin_type_a, false, true)
479
+ primary_coin_b_inputs = CoinAssist.buildCoinForAmount(tx, all_coin_asset, max_amount_b, params.coin_type_b, false, true)
480
+ } else {
481
+ primary_coin_a_inputs = {
482
+ target_coin: input_coin_a,
483
+ remain_coins: [],
484
+ is_mint_zero_coin: false,
485
+ target_coin_amount: '0',
486
+ selected_coins: [],
487
+ }
488
+ primary_coin_b_inputs = {
489
+ target_coin: input_coin_b,
490
+ remain_coins: [],
491
+ is_mint_zero_coin: false,
492
+ target_coin_amount: '0',
493
+ selected_coins: [],
494
+ }
495
+ }
496
+
497
+ if (needOpenPosition) {
498
+ tx.moveCall({
499
+ target: `${integrate.published_at}::${ClmmIntegratePoolV2Module}::open_position_with_liquidity`,
500
+ typeArguments,
501
+ arguments: [
502
+ tx.object(getPackagerConfigs(clmm_pool).global_config_id),
503
+ tx.object(params.pool_id),
504
+ tx.pure.u32(Number(tick_lower)),
505
+ tx.pure.u32(Number(tick_upper)),
506
+ primary_coin_a_inputs.target_coin,
507
+ primary_coin_b_inputs.target_coin,
508
+ tx.pure.u64(params.max_amount_a),
509
+ tx.pure.u64(params.max_amount_b),
510
+ tx.pure.u128(params.delta_liquidity),
511
+ tx.object(CLOCK_ADDRESS),
512
+ ],
513
+ })
514
+ } else {
515
+ const all_coin_asset = await this._sdk.FullClient.getOwnerCoinAssets(this._sdk.getSenderAddress())
516
+ tx = PositionUtils.createCollectRewarderAndFeeParams(
517
+ this._sdk,
518
+ tx,
519
+ params,
520
+ all_coin_asset,
521
+ primary_coin_a_inputs.remain_coins,
522
+ primary_coin_b_inputs.remain_coins
523
+ )
524
+ tx.moveCall({
525
+ target: `${integrate.published_at}::${ClmmIntegratePoolV2Module}::add_liquidity`,
526
+ typeArguments,
527
+ arguments: [
528
+ tx.object(getPackagerConfigs(clmm_pool).global_config_id),
529
+ tx.object(params.pool_id),
530
+ tx.object(params.pos_id),
531
+ primary_coin_a_inputs.target_coin,
532
+ primary_coin_b_inputs.target_coin,
533
+ tx.pure.u64(params.max_amount_a),
534
+ tx.pure.u64(params.max_amount_b),
535
+ tx.pure.u128(params.delta_liquidity),
536
+ tx.object(CLOCK_ADDRESS),
537
+ ],
538
+ })
539
+ }
540
+ return tx
541
+ }
542
+
543
+ /**
544
+ * Remove liquidity from a position.
545
+ * @param {RemoveLiquidityParams} params
546
+ * @returns {TransactionBlock}
547
+ */
548
+ async removeLiquidityPayload(params: RemoveLiquidityParams, tx?: Transaction): Promise<Transaction> {
549
+ const { clmm_pool, integrate } = this.sdk.sdkOptions
550
+
551
+ const functionName = 'remove_liquidity'
552
+
553
+ tx = tx || new Transaction()
554
+
555
+ const typeArguments = [params.coin_type_a, params.coin_type_b]
556
+
557
+ const allCoinAsset = await this._sdk.FullClient.getOwnerCoinAssets(this.sdk.getSenderAddress())
558
+
559
+ tx = PositionUtils.createCollectRewarderAndFeeParams(this._sdk, tx, params, allCoinAsset)
560
+
561
+ const args = [
562
+ tx.object(getPackagerConfigs(clmm_pool).global_config_id),
563
+ tx.object(params.pool_id),
564
+ tx.object(params.pos_id),
565
+ tx.pure.u128(params.delta_liquidity),
566
+ tx.pure.u64(params.min_amount_a),
567
+ tx.pure.u64(params.min_amount_b),
568
+ tx.object(CLOCK_ADDRESS),
569
+ ]
570
+
571
+ tx.moveCall({
572
+ target: `${integrate.published_at}::${ClmmIntegratePoolModule}::${functionName}`,
573
+ typeArguments,
574
+ arguments: args,
575
+ })
576
+
577
+ return tx
578
+ }
579
+
580
+ /**
581
+ * Close position and remove all liquidity and collect_reward
582
+ * @param {ClosePositionParams} params
583
+ * @returns {TransactionBlock}
584
+ */
585
+ async closePositionPayload(params: ClosePositionParams, tx?: Transaction): Promise<Transaction> {
586
+ const { clmm_pool, integrate } = this.sdk.sdkOptions
587
+
588
+ tx = tx || new Transaction()
589
+
590
+ const typeArguments = [params.coin_type_a, params.coin_type_b]
591
+
592
+ const allCoinAsset = await this._sdk.FullClient.getOwnerCoinAssets(this.sdk.getSenderAddress())
593
+
594
+ tx = PositionUtils.createCollectRewarderAndFeeParams(this._sdk, tx, params, allCoinAsset)
595
+
596
+ tx.moveCall({
597
+ target: `${integrate.published_at}::${ClmmIntegratePoolModule}::close_position`,
598
+ typeArguments,
599
+ arguments: [
600
+ tx.object(getPackagerConfigs(clmm_pool).global_config_id),
601
+ tx.object(params.pool_id),
602
+ tx.object(params.pos_id),
603
+ tx.pure.u64(params.min_amount_a),
604
+ tx.pure.u64(params.min_amount_b),
605
+ tx.object(CLOCK_ADDRESS),
606
+ ],
607
+ })
608
+
609
+ return tx
610
+ }
611
+
612
+ /**
613
+ * Open position in clmmpool.
614
+ * @param {OpenPositionParams} params
615
+ * @returns {TransactionBlock}
616
+ */
617
+ openPositionPayload(params: OpenPositionParams, tx?: Transaction): Transaction {
618
+ const { clmm_pool, integrate } = this.sdk.sdkOptions
619
+ tx = tx || new Transaction()
620
+
621
+ const typeArguments = [params.coin_type_a, params.coin_type_b]
622
+ const tick_lower = asUintN(BigInt(params.tick_lower)).toString()
623
+ const tick_upper = asUintN(BigInt(params.tick_upper)).toString()
624
+ const args = [
625
+ tx.object(getPackagerConfigs(clmm_pool).global_config_id),
626
+ tx.object(params.pool_id),
627
+ tx.pure.u32(Number(tick_lower)),
628
+ tx.pure.u32(Number(tick_upper)),
629
+ ]
630
+
631
+ tx.moveCall({
632
+ target: `${integrate.published_at}::${ClmmIntegratePoolModule}::open_position`,
633
+ typeArguments,
634
+ arguments: args,
635
+ })
636
+
637
+ return tx
638
+ }
639
+
640
+ /**
641
+ * Open position with price range in clmmpool.
642
+ * @param {OpenPositionWithPriceParams} params
643
+ * @returns {TransactionBlock}
644
+ */
645
+ async openPositionWithPricePayload(params: OpenPositionWithPriceParams, tx?: Transaction): Promise<Transaction> {
646
+ const { pool_id } = params
647
+ const { clmm_pool, integrate } = this.sdk.sdkOptions
648
+ tx = tx || new Transaction()
649
+
650
+ const pool = await this.sdk.Pool.getPool(pool_id, false)
651
+ const tick_spacing = Number(pool.tick_spacing)
652
+
653
+ let tick_lower = 0
654
+ let tick_upper = 0
655
+
656
+ if (params.is_full_range) {
657
+ tick_lower = TickUtil.getMinIndex(tick_spacing)
658
+ tick_upper = TickUtil.getMaxIndex(tick_spacing)
659
+ } else {
660
+ const { price_base_coin, min_price, max_price } = params
661
+ tick_lower = TickMath.priceToInitializeTickIndex(
662
+ price_base_coin === 'coin_a' ? d(min_price) : d(1).div(max_price),
663
+ params.coin_decimals_a,
664
+ params.coin_decimals_b,
665
+ tick_spacing
666
+ )
667
+ tick_upper = TickMath.priceToInitializeTickIndex(
668
+ price_base_coin === 'coin_a' ? d(max_price) : d(1).div(min_price),
669
+ params.coin_decimals_a,
670
+ params.coin_decimals_b,
671
+ tick_spacing
672
+ )
673
+ }
674
+
675
+ const typeArguments = [pool.coin_type_a, pool.coin_type_b]
676
+ const args = [
677
+ tx.object(getPackagerConfigs(clmm_pool).global_config_id),
678
+ tx.object(params.pool_id),
679
+ tx.pure.u32(Number(asUintN(BigInt(tick_lower)))),
680
+ tx.pure.u32(Number(asUintN(BigInt(tick_upper)))),
681
+ ]
682
+
683
+ tx.moveCall({
684
+ target: `${integrate.published_at}::${ClmmIntegratePoolModule}::open_position`,
685
+ typeArguments,
686
+ arguments: args,
687
+ })
688
+
689
+ return tx
690
+ }
691
+
692
+ /**
693
+ * Collect LP fee from Position.
694
+ * @param {CollectFeeParams} params
695
+ * @param {TransactionBlock} tx
696
+ * @returns {TransactionBlock}
697
+ */
698
+ async collectFeePayload(
699
+ params: CollectFeeParams,
700
+ tx?: Transaction,
701
+ input_coin_a?: TransactionObjectArgument,
702
+ input_coin_b?: TransactionObjectArgument
703
+ ): Promise<Transaction> {
704
+ tx = tx || new Transaction()
705
+
706
+ const coin_a = input_coin_a || CoinAssist.buildCoinWithBalance(BigInt(0), params.coin_type_a, tx)
707
+ const coin_b = input_coin_b || CoinAssist.buildCoinWithBalance(BigInt(0), params.coin_type_b, tx)
708
+
709
+ this.createCollectFeePayload(params, tx, coin_a, coin_b)
710
+ return tx
711
+ }
712
+
713
+ createCollectFeePayload(
714
+ params: CollectFeeParams,
715
+ tx: Transaction,
716
+ primary_coin_a_input: TransactionObjectArgument,
717
+ primary_coin_b_input: TransactionObjectArgument
718
+ ) {
719
+ const { clmm_pool, integrate } = this.sdk.sdkOptions
720
+ const typeArguments = [params.coin_type_a, params.coin_type_b]
721
+ const args = [
722
+ tx.object(getPackagerConfigs(clmm_pool).global_config_id),
723
+ tx.object(params.pool_id),
724
+ tx.object(params.pos_id),
725
+ primary_coin_a_input,
726
+ primary_coin_b_input,
727
+ ]
728
+
729
+ tx.moveCall({
730
+ target: `${integrate.published_at}::${ClmmIntegratePoolV2Module}::collect_fee`,
731
+ typeArguments,
732
+ arguments: args,
733
+ })
734
+ return tx
735
+ }
736
+
737
+ /**
738
+ * Calculate the result of add liquidity with price.
739
+ * @param {CalculateAddLiquidityWithPriceParams} params
740
+ * @returns {Promise<CalculateAddLiquidityResult>}
741
+ */
742
+ async calculateAddLiquidityResultWithPrice(
743
+ params: CalculateAddLiquidityWithPriceParams | CalculateAddLiquidityFixCoinWithPriceParams
744
+ ): Promise<CalculateAddLiquidityResult> {
745
+ const { pool_id, slippage, refresh_pool_price, add_mode_params } = params
746
+
747
+ const pool = await this.sdk.Pool.getPool(pool_id, refresh_pool_price)
748
+ const tick_spacing = Number(pool.tick_spacing)
749
+
750
+ let tick_lower = 0
751
+ let tick_upper = 0
752
+
753
+ if (add_mode_params.is_full_range) {
754
+ tick_lower = TickUtil.getMinIndex(tick_spacing)
755
+ tick_upper = TickUtil.getMaxIndex(tick_spacing)
756
+ } else {
757
+ const { price_base_coin, min_price, max_price } = add_mode_params
758
+ tick_lower = TickMath.priceToInitializeTickIndex(
759
+ price_base_coin === 'coin_a' ? d(min_price) : d(1).div(max_price),
760
+ add_mode_params.coin_decimals_a,
761
+ add_mode_params.coin_decimals_b,
762
+ tick_spacing
763
+ )
764
+ tick_upper = TickMath.priceToInitializeTickIndex(
765
+ price_base_coin === 'coin_a' ? d(max_price) : d(1).div(min_price),
766
+ add_mode_params.coin_decimals_a,
767
+ add_mode_params.coin_decimals_b,
768
+ tick_spacing
769
+ )
770
+ }
771
+
772
+ if ('liquidity' in params) {
773
+ const { liquidity } = params
774
+
775
+ const lower_sqrt_price = TickMath.tickIndexToSqrtPriceX64(tick_lower)
776
+ const upper_sqrt_price = TickMath.tickIndexToSqrtPriceX64(tick_upper)
777
+
778
+ const { coin_amount_a, coin_amount_b } = ClmmPoolUtil.getCoinAmountFromLiquidity(
779
+ new BN(liquidity),
780
+ new BN(pool.current_sqrt_price),
781
+ lower_sqrt_price,
782
+ upper_sqrt_price,
783
+ false
784
+ )
785
+
786
+ const coin_amount_limit_a = d(coin_amount_a)
787
+ .mul(1 + slippage)
788
+ .toFixed(0, Decimal.ROUND_UP)
789
+
790
+ const coin_amount_limit_b = d(coin_amount_b)
791
+ .mul(1 + slippage)
792
+ .toFixed(0, Decimal.ROUND_UP)
793
+
794
+ return { coin_amount_a, coin_amount_b, coin_amount_limit_a, coin_amount_limit_b, liquidity, tick_lower, tick_upper }
795
+ }
796
+
797
+ const { coin_amount, fix_amount_a } = params
798
+ const { coin_amount_limit_a, coin_amount_limit_b, liquidity_amount, coin_amount_a, coin_amount_b } =
799
+ ClmmPoolUtil.estLiquidityAndCoinAmountFromOneAmounts(
800
+ tick_lower,
801
+ tick_upper,
802
+ new BN(coin_amount),
803
+ fix_amount_a,
804
+ true,
805
+ slippage,
806
+ new BN(pool.current_sqrt_price)
807
+ )
808
+
809
+ return {
810
+ coin_amount_a,
811
+ coin_amount_b,
812
+ coin_amount_limit_a,
813
+ coin_amount_limit_b,
814
+ liquidity: liquidity_amount,
815
+ tick_lower,
816
+ tick_upper,
817
+ fix_amount_a,
818
+ }
819
+ }
820
+
821
+ /**
822
+ * Add liquidity with price range.
823
+ * @param {AddLiquidityWithPriceRangeParams} params
824
+ * @param {TransactionBlock} tx
825
+ * @returns {TransactionBlock}
826
+ */
827
+ async addLiquidityWithPricePayload(
828
+ params: AddLiquidityWithPriceRangeParams,
829
+ tx?: Transaction,
830
+ input_coin_a?: TransactionObjectArgument,
831
+ input_coin_b?: TransactionObjectArgument
832
+ ): Promise<Transaction> {
833
+ const { pool_id, calculate_result } = params
834
+ const { coin_amount_limit_a, coin_amount_limit_b, liquidity, tick_lower, tick_upper } = calculate_result
835
+ tx = tx || new Transaction()
836
+ const pool = await this.sdk.Pool.getPool(pool_id, false)
837
+
838
+ await this.createAddLiquidityPayload(
839
+ {
840
+ delta_liquidity: liquidity,
841
+ max_amount_a: coin_amount_limit_a,
842
+ max_amount_b: coin_amount_limit_b,
843
+ tick_lower: tick_lower,
844
+ tick_upper: tick_upper,
845
+ collect_fee: false,
846
+ rewarder_coin_types: [],
847
+ coin_type_a: pool.coin_type_a,
848
+ coin_type_b: pool.coin_type_b,
849
+ pool_id: pool_id,
850
+ pos_id: '',
851
+ },
852
+ tx,
853
+ input_coin_a,
854
+ input_coin_b
855
+ )
856
+
857
+ return tx
858
+ }
859
+
860
+ /**
861
+ * Add liquidity with price range.
862
+ * @param {AddLiquidityWithPriceRangeParams} params
863
+ * @param {TransactionBlock} tx
864
+ * @returns {TransactionBlock}
865
+ */
866
+ async createAddLiquidityFixCoinWithPricePayload(
867
+ params: AddLiquidityWithPriceRangeParams,
868
+ tx?: Transaction,
869
+ input_coin_a?: TransactionObjectArgument,
870
+ input_coin_b?: TransactionObjectArgument
871
+ ): Promise<Transaction> {
872
+ const { pool_id, calculate_result } = params
873
+ const { coin_amount_limit_a, coin_amount_limit_b, liquidity, tick_lower, tick_upper, fix_amount_a, coin_amount_a, coin_amount_b } =
874
+ calculate_result
875
+ if (fix_amount_a === undefined) {
876
+ throw handleMessageError(PositionErrorCode.InvalidParams, 'fix_amount_a is undefined', {
877
+ [DETAILS_KEYS.METHOD_NAME]: 'addLiquidityFixCoinCoinWithPricePayload',
878
+ [DETAILS_KEYS.REQUEST_PARAMS]: params,
879
+ })
880
+ }
881
+ tx = tx || new Transaction()
882
+ const pool = await this.sdk.Pool.getPool(pool_id, false)
883
+
884
+ await this.createAddLiquidityFixTokenPayload(
885
+ {
886
+ amount_a: fix_amount_a ? coin_amount_a : coin_amount_limit_a,
887
+ amount_b: fix_amount_a ? coin_amount_limit_b : coin_amount_b,
888
+ slippage: 0,
889
+ fix_amount_a,
890
+ is_open: true,
891
+ tick_lower: tick_lower,
892
+ tick_upper: tick_upper,
893
+ collect_fee: false,
894
+ rewarder_coin_types: [],
895
+ coin_type_a: pool.coin_type_a,
896
+ coin_type_b: pool.coin_type_b,
897
+ pool_id: pool_id,
898
+ pos_id: '',
899
+ },
900
+ undefined,
901
+ tx,
902
+ input_coin_a,
903
+ input_coin_b
904
+ )
905
+
906
+ return tx
907
+ }
908
+
909
+ createCollectFeeNoSendPayload(
910
+ params: CollectFeeParams,
911
+ tx: Transaction,
912
+ primary_coin_a_input: TransactionObjectArgument,
913
+ primary_coin_b_input: TransactionObjectArgument
914
+ ) {
915
+ const { clmm_pool, integrate } = this.sdk.sdkOptions
916
+ const typeArguments = [params.coin_type_a, params.coin_type_b]
917
+ const args = [
918
+ tx.object(getPackagerConfigs(clmm_pool).global_config_id),
919
+ tx.object(params.pool_id),
920
+ tx.object(params.pos_id),
921
+ primary_coin_a_input,
922
+ primary_coin_b_input,
923
+ ]
924
+
925
+ tx.moveCall({
926
+ target: `${integrate.published_at}::${ClmmIntegratePoolV3Module}::collect_fee`,
927
+ typeArguments,
928
+ arguments: args,
929
+ })
930
+ return tx
931
+ }
932
+ }