@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,430 @@
1
+ import { DevInspectResults } from '@mysten/sui/client'
2
+ import { Transaction, TransactionArgument, TransactionObjectArgument } from '@mysten/sui/transactions'
3
+ import { normalizeSuiAddress } from '@mysten/sui/utils'
4
+ import BN from 'bn.js'
5
+ import {
6
+ BuildCoinResult,
7
+ CLOCK_ADDRESS,
8
+ CoinAssist,
9
+ DETAILS_KEYS,
10
+ getPackagerConfigs,
11
+ IModule,
12
+ MathUtil,
13
+ normalizeCoinType,
14
+ ZERO,
15
+ } from '@cetusprotocol/common-sdk'
16
+ import { ConfigErrorCode, handleMessageError } from '../errors/errors'
17
+ import { CetusClmmSDK } from '../sdk'
18
+ import {
19
+ CollectFeesQuote,
20
+ CollectRewarderParams,
21
+ FetchPosFeeParams,
22
+ FetchPosRewardParams,
23
+ Pool,
24
+ Position,
25
+ PosRewarderResult,
26
+ RewarderAmountOwned,
27
+ TickData,
28
+ } from '../types'
29
+ import { ClmmFetcherModule, ClmmIntegratePoolV2Module, ClmmIntegratePoolV3Module } from '../types/sui'
30
+ import { buildTransferCoin, PositionUtils } from '../utils'
31
+
32
+ /**
33
+ * Helper class to help interact with clmm position rewaeder with a rewaeder router interface.
34
+ */
35
+ export class RewarderModule implements IModule<CetusClmmSDK> {
36
+ protected _sdk: CetusClmmSDK
37
+
38
+ private growthGlobal: BN[]
39
+
40
+ constructor(sdk: CetusClmmSDK) {
41
+ this._sdk = sdk
42
+ this.growthGlobal = [ZERO, ZERO, ZERO]
43
+ }
44
+
45
+ get sdk() {
46
+ return this._sdk
47
+ }
48
+
49
+ /**
50
+ * Gets the emissions for the given pool every day.
51
+ *
52
+ * @param {string} pool_id The object ID of the pool.
53
+ * @returns {Promise<Array<{emissions: number, coinAddress: string}>>} A promise that resolves to an array of objects with the emissions and coin address for each rewarder.
54
+ */
55
+ async emissionsEveryDay(pool_id: string) {
56
+ const currentPool: Pool = await this.sdk.Pool.getPool(pool_id)
57
+ const rewarderInfos = currentPool.rewarder_infos
58
+ if (!rewarderInfos) {
59
+ return null
60
+ }
61
+
62
+ const emissionsEveryDay = []
63
+ for (const rewarderInfo of rewarderInfos) {
64
+ const emissionSeconds = MathUtil.fromX64(new BN(rewarderInfo.emissions_per_second))
65
+ emissionsEveryDay.push({
66
+ emissions: Math.floor(emissionSeconds.toNumber() * 60 * 60 * 24),
67
+ coin_type: rewarderInfo.coin_type,
68
+ })
69
+ }
70
+
71
+ return emissionsEveryDay
72
+ }
73
+
74
+ /**
75
+ * Fetches the Position reward amount for a given list of addresses.
76
+ * @param {string[]}position_ids An array of position object id.
77
+ * @returns {Promise<Record<string, RewarderAmountOwned[]>>} A Promise that resolves with the fetched position reward amount for the specified position object ids.
78
+ */
79
+ async batchFetchPositionRewarders(position_ids: string[]): Promise<Record<string, RewarderAmountOwned[]>> {
80
+ const posRewardParamsList: FetchPosRewardParams[] = []
81
+ for (const id of position_ids) {
82
+ const position = await this._sdk.Position.getPositionById(id, false)
83
+ const pool = await this._sdk.Pool.getPool(position.pool, false)
84
+ posRewardParamsList.push({
85
+ pool_id: pool.id,
86
+ position_id: position.pos_object_id,
87
+ coin_type_a: pool.coin_type_a,
88
+ coin_type_b: pool.coin_type_b,
89
+ rewarder_types: pool.rewarder_infos.map((rewarder) => rewarder.coin_type),
90
+ })
91
+ }
92
+
93
+ const positionMap: Record<string, RewarderAmountOwned[]> = {}
94
+
95
+ if (posRewardParamsList.length > 0) {
96
+ const result: PosRewarderResult[] = await this.fetchPosRewardersAmount(posRewardParamsList)
97
+ for (const posRewarderInfo of result) {
98
+ positionMap[posRewarderInfo.position_id] = posRewarderInfo.rewarder_amounts
99
+ }
100
+ return positionMap
101
+ }
102
+ return positionMap
103
+ }
104
+
105
+ /**
106
+ * Fetch the position rewards for a given pool.
107
+ * @param {Pool}pool Pool object
108
+ * @param {string}position_id Position object id
109
+ * @returns {Promise<RewarderAmountOwned[]>} A Promise that resolves with the fetched position reward amount for the specified position object id.
110
+ */
111
+ async fetchPositionRewarders(pool: Pool, position_id: string): Promise<RewarderAmountOwned[]> {
112
+ const param = {
113
+ pool_id: pool.id,
114
+ position_id: position_id,
115
+ coin_type_a: pool.coin_type_a,
116
+ coin_type_b: pool.coin_type_b,
117
+ rewarder_types: pool.rewarder_infos.map((rewarder) => rewarder.coin_type),
118
+ }
119
+
120
+ const result = await this.fetchPosRewardersAmount([param])
121
+
122
+ return result[0].rewarder_amounts
123
+ }
124
+
125
+ /**
126
+ * Fetches the Position fee amount for a given list of addresses.
127
+ * @param position_ids An array of position object id.
128
+ * @returns {Promise<Record<string, CollectFeesQuote>>} A Promise that resolves with the fetched position fee amount for the specified position object ids.
129
+ * @deprecated This method is deprecated and may be removed in future versions. Use alternative methods if available.
130
+ */
131
+ async batchFetchPositionFees(position_ids: string[]): Promise<Record<string, CollectFeesQuote>> {
132
+ return await this._sdk.Position.batchFetchPositionFees(position_ids)
133
+ }
134
+
135
+ /**
136
+ * Fetches the Position fee amount for a given list of addresses.
137
+ * @param params An array of FetchPosFeeParams objects containing the target addresses and their corresponding amounts.
138
+ * @returns
139
+ */
140
+ async fetchPosFeeAmount(params: FetchPosFeeParams[]): Promise<CollectFeesQuote[]> {
141
+ return await this._sdk.Position.fetchPosFeeAmount(params)
142
+ }
143
+
144
+ public buildFetchPosReward(params: FetchPosRewardParams, tx: Transaction) {
145
+ const { clmm_pool, integrate } = this.sdk.sdkOptions
146
+ const typeArguments = [params.coin_type_a, params.coin_type_b]
147
+ const args = [
148
+ tx.object(getPackagerConfigs(clmm_pool).global_config_id),
149
+ tx.object(params.pool_id),
150
+ tx.pure.address(params.position_id),
151
+ tx.object(CLOCK_ADDRESS),
152
+ ]
153
+ tx.moveCall({
154
+ target: `${integrate.published_at}::${ClmmFetcherModule}::fetch_position_rewards`,
155
+ arguments: args,
156
+ typeArguments,
157
+ })
158
+ }
159
+
160
+ /**
161
+ * Fetches the Position reward amount for a given list of addresses.
162
+ * @param params An array of FetchPosRewardParams objects containing the target addresses and their corresponding amounts.
163
+ * @returns
164
+ */
165
+ async fetchPosRewardersAmount(params: FetchPosRewardParams[]) {
166
+ const tx = new Transaction()
167
+
168
+ for (const paramItem of params) {
169
+ this.buildFetchPosReward(paramItem, tx)
170
+ }
171
+
172
+ const simulateRes = await this.sdk.FullClient.devInspectTransactionBlock({
173
+ transactionBlock: tx,
174
+ sender: normalizeSuiAddress('0x'),
175
+ })
176
+
177
+ if (simulateRes.error != null) {
178
+ handleMessageError(
179
+ ConfigErrorCode.InvalidConfig,
180
+ `fetch position rewards error code: ${simulateRes.error ?? 'unknown error'}, please check config and params`,
181
+ {
182
+ [DETAILS_KEYS.METHOD_NAME]: 'fetchPosRewardersAmount',
183
+ [DETAILS_KEYS.REQUEST_PARAMS]: { params },
184
+ }
185
+ )
186
+ }
187
+
188
+ const rewarderData = this.parsedPosRewardData(simulateRes)
189
+
190
+ const result: PosRewarderResult[] = []
191
+
192
+ for (let i = 0; i < params.length; i += 1) {
193
+ const rewarder = rewarderData[params[i].position_id]
194
+ if (rewarder) {
195
+ const posRewarderResult: PosRewarderResult = {
196
+ pool_id: params[i].pool_id,
197
+ position_id: params[i].position_id,
198
+ rewarder_amounts: rewarder.rewarder_amount.map((amount: string, index: number) => {
199
+ return {
200
+ amount_owned: amount,
201
+ coin_type: params[i].rewarder_types[index],
202
+ }
203
+ }),
204
+ }
205
+ result.push(posRewarderResult)
206
+ }
207
+ }
208
+
209
+ return result
210
+ }
211
+
212
+ parsedPosRewardData(simulate_res: DevInspectResults) {
213
+ const rewarderData: Record<string, { position_id: string; rewarder_amount: string[] }> = {}
214
+ const rewarderValueData: any[] = simulate_res.events?.filter((item: any) => {
215
+ return item.type.includes('fetcher_script::FetchPositionRewardsEvent')
216
+ })
217
+
218
+ for (let i = 0; i < rewarderValueData.length; i += 1) {
219
+ const { parsedJson } = rewarderValueData[i]
220
+ const posObj = {
221
+ position_id: parsedJson.position_id,
222
+ rewarder_amount: parsedJson.data,
223
+ }
224
+ rewarderData[parsedJson.position_id] = posObj
225
+ }
226
+
227
+ return rewarderData
228
+ }
229
+
230
+ /**
231
+ * Fetches the pool reward amount for a given account and pool object id.
232
+ * @param {string} account - The target account.
233
+ * @param {string} pool_object_id - The target pool object id.
234
+ * @returns {Promise<number|null>} - A Promise that resolves with the fetched pool reward amount for the specified account and pool, or null if the fetch is unsuccessful.
235
+ */
236
+ async fetchPoolRewardersAmount(account: string, pool_object_id: string) {
237
+ const pool: Pool = await this.sdk.Pool.getPool(pool_object_id)
238
+ const positions = await this.sdk.Position.getPositionList(account, [pool_object_id])
239
+
240
+ const params: FetchPosRewardParams[] = []
241
+
242
+ for (const position of positions) {
243
+ params.push({
244
+ pool_id: pool.id,
245
+ position_id: position.pos_object_id,
246
+ rewarder_types: pool.rewarder_infos.map((rewarder) => rewarder.coin_type),
247
+ coin_type_a: pool.coin_type_a,
248
+ coin_type_b: pool.coin_type_b,
249
+ })
250
+ }
251
+
252
+ const result = await this.fetchPosRewardersAmount(params)
253
+
254
+ const rewarderAmount = [ZERO, ZERO, ZERO]
255
+
256
+ if (result != null) {
257
+ for (const posRewarderInfo of result) {
258
+ for (let j = 0; j < posRewarderInfo.rewarder_amounts.length; j += 1) {
259
+ rewarderAmount[j] = rewarderAmount[j].add(new BN(posRewarderInfo.rewarder_amounts[j].amount_owned))
260
+ }
261
+ }
262
+ }
263
+ return rewarderAmount
264
+ }
265
+
266
+ private async getPoolLowerAndUpperTicks(ticks_handle: string, positions: Position[]): Promise<TickData[][]> {
267
+ const lower_ticks: TickData[] = []
268
+ const upper_ticks: TickData[] = []
269
+
270
+ for (const pos of positions) {
271
+ const tick_lower = await this.sdk.Pool.getTickDataByIndex(ticks_handle, pos.tick_lower_index)
272
+ const tick_upper = await this.sdk.Pool.getTickDataByIndex(ticks_handle, pos.tick_upper_index)
273
+ lower_ticks.push(tick_lower!)
274
+ upper_ticks.push(tick_upper!)
275
+ }
276
+
277
+ return [lower_ticks, upper_ticks]
278
+ }
279
+
280
+ /**
281
+ * Collect rewards from Position.
282
+ * @param params
283
+ * @returns
284
+ */
285
+ async collectRewarderPayload(params: CollectRewarderParams): Promise<Transaction> {
286
+ const allCoinAsset = await this.sdk.FullClient.getOwnerCoinAssets(this.sdk.getSenderAddress(), null)
287
+ let tx = new Transaction()
288
+
289
+ tx = PositionUtils.createCollectRewarderAndFeeParams(this._sdk, tx, params, allCoinAsset)
290
+ return tx
291
+ }
292
+
293
+ /**
294
+ * batch Collect rewards from Position.
295
+ * @param params
296
+ * @param tx
297
+ * @param input_coin_a
298
+ * @param input_coin_b
299
+ * @returns
300
+ */
301
+ async batchCollectRewardsPayload(
302
+ params: CollectRewarderParams[],
303
+ tx?: Transaction,
304
+ input_coin_a?: TransactionObjectArgument,
305
+ input_coin_b?: TransactionObjectArgument
306
+ ) {
307
+ const all_coin_asset = await this.sdk.FullClient.getOwnerCoinAssets(this.sdk.getSenderAddress(), null)
308
+ tx = tx || new Transaction()
309
+ const coin_id_maps: Record<string, BuildCoinResult> = {}
310
+ params.forEach((item) => {
311
+ const coin_type_a = normalizeCoinType(item.coin_type_a)
312
+ const coin_type_b = normalizeCoinType(item.coin_type_b)
313
+
314
+ if (item.collect_fee) {
315
+ let coin_a_input = coin_id_maps[coin_type_a]
316
+ if (coin_a_input == null) {
317
+ if (input_coin_a == null) {
318
+ coin_a_input = CoinAssist.buildCoinForAmount(tx!, all_coin_asset!, BigInt(0), coin_type_a, false)
319
+ } else {
320
+ coin_a_input = {
321
+ target_coin: input_coin_a,
322
+ remain_coins: [],
323
+ is_mint_zero_coin: false,
324
+ target_coin_amount: '0',
325
+ selected_coins: [],
326
+ }
327
+ }
328
+
329
+ coin_id_maps[coin_type_a] = coin_a_input
330
+ }
331
+
332
+ let coin_b_input = coin_id_maps[coin_type_b]
333
+ if (coin_b_input == null) {
334
+ if (input_coin_b == null) {
335
+ coin_b_input = CoinAssist.buildCoinForAmount(tx!, all_coin_asset!, BigInt(0), coin_type_b, false)
336
+ } else {
337
+ coin_b_input = {
338
+ target_coin: input_coin_b,
339
+ remain_coins: [],
340
+ is_mint_zero_coin: false,
341
+ target_coin_amount: '0',
342
+ selected_coins: [],
343
+ }
344
+ }
345
+
346
+ coin_id_maps[coin_type_b] = coin_b_input
347
+ }
348
+
349
+ tx = this._sdk.Position.createCollectFeeNoSendPayload(
350
+ {
351
+ pool_id: item.pool_id,
352
+ pos_id: item.pos_id,
353
+ coin_type_a: item.coin_type_a,
354
+ coin_type_b: item.coin_type_b,
355
+ },
356
+ tx!,
357
+ coin_a_input.target_coin,
358
+ coin_b_input.target_coin
359
+ )
360
+ }
361
+ const primaryCoinInputs: TransactionObjectArgument[] = []
362
+ item.rewarder_coin_types.forEach((type) => {
363
+ const coinType = normalizeCoinType(type)
364
+ let coinInput = coin_id_maps[type]
365
+ if (coinInput === undefined) {
366
+ coinInput = CoinAssist.buildCoinForAmount(tx!, all_coin_asset!, BigInt(0), coinType, false)
367
+ coin_id_maps[coinType] = coinInput
368
+ }
369
+ primaryCoinInputs.push(coinInput.target_coin)
370
+ })
371
+
372
+ tx = this.createCollectRewarderNoSendPayload(item, tx!, primaryCoinInputs)
373
+ })
374
+
375
+ Object.keys(coin_id_maps).forEach((key) => {
376
+ const value = coin_id_maps[key]
377
+ if (value.is_mint_zero_coin) {
378
+ buildTransferCoin(this.sdk, tx!, value.target_coin, key, this.sdk.getSenderAddress())
379
+ }
380
+ })
381
+
382
+ return tx
383
+ }
384
+
385
+ createCollectRewarderPayload(params: CollectRewarderParams, tx: Transaction, primary_coin_inputs: TransactionArgument[]) {
386
+ const { clmm_pool, integrate } = this.sdk.sdkOptions
387
+ const clmm_configs = getPackagerConfigs(clmm_pool)
388
+ const type_arguments = [params.coin_type_a, params.coin_type_b]
389
+ params.rewarder_coin_types.forEach((type, index) => {
390
+ if (tx) {
391
+ tx.moveCall({
392
+ target: `${integrate.published_at}::${ClmmIntegratePoolV2Module}::collect_reward`,
393
+ typeArguments: [...type_arguments, type],
394
+ arguments: [
395
+ tx.object(clmm_configs.global_config_id),
396
+ tx.object(params.pool_id),
397
+ tx.object(params.pos_id),
398
+ tx.object(clmm_configs.global_vault_id),
399
+ primary_coin_inputs[index],
400
+ tx.object(CLOCK_ADDRESS),
401
+ ],
402
+ })
403
+ }
404
+ })
405
+ return tx
406
+ }
407
+
408
+ createCollectRewarderNoSendPayload(params: CollectRewarderParams, tx: Transaction, primary_coin_inputs: TransactionArgument[]) {
409
+ const { clmm_pool, integrate } = this.sdk.sdkOptions
410
+ const clmm_configs = getPackagerConfigs(clmm_pool)
411
+ const type_arguments = [params.coin_type_a, params.coin_type_b]
412
+ params.rewarder_coin_types.forEach((type, index) => {
413
+ if (tx) {
414
+ tx.moveCall({
415
+ target: `${integrate.published_at}::${ClmmIntegratePoolV3Module}::collect_reward`,
416
+ typeArguments: [...type_arguments, type],
417
+ arguments: [
418
+ tx.object(clmm_configs.global_config_id),
419
+ tx.object(params.pool_id),
420
+ tx.object(params.pos_id),
421
+ tx.object(clmm_configs.global_vault_id),
422
+ primary_coin_inputs[index],
423
+ tx.object(CLOCK_ADDRESS),
424
+ ],
425
+ })
426
+ }
427
+ })
428
+ return tx
429
+ }
430
+ }