@cetusprotocol/dlmm-sdk 0.0.5 → 0.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.
Files changed (42) hide show
  1. package/README.md +376 -51
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +1 -1
  5. package/dist/index.mjs.map +1 -1
  6. package/package.json +1 -1
  7. package/.turbo/turbo-build.log +0 -10485
  8. package/src/config/index.ts +0 -2
  9. package/src/config/mainnet.ts +0 -25
  10. package/src/config/testnet.ts +0 -30
  11. package/src/errors/errors.ts +0 -40
  12. package/src/index.ts +0 -8
  13. package/src/modules/configModule.ts +0 -184
  14. package/src/modules/index.ts +0 -1
  15. package/src/modules/partnerModule.ts +0 -302
  16. package/src/modules/poolModule.ts +0 -625
  17. package/src/modules/positionModule.ts +0 -901
  18. package/src/modules/rewardModule.ts +0 -174
  19. package/src/modules/swapModule.ts +0 -129
  20. package/src/sdk.ts +0 -88
  21. package/src/types/constants.ts +0 -23
  22. package/src/types/dlmm.ts +0 -466
  23. package/src/types/index.ts +0 -2
  24. package/src/utils/binUtils.ts +0 -552
  25. package/src/utils/feeUtils.ts +0 -92
  26. package/src/utils/index.ts +0 -5
  27. package/src/utils/parseData.ts +0 -556
  28. package/src/utils/strategyUtils.ts +0 -202
  29. package/src/utils/weightUtils.ts +0 -684
  30. package/tests/add_liquidity_bidask.test.ts +0 -192
  31. package/tests/add_liquidity_curve.test.ts +0 -244
  32. package/tests/add_liquidity_spot.test.ts +0 -314
  33. package/tests/bin.test.ts +0 -95
  34. package/tests/config.test.ts +0 -73
  35. package/tests/partner.test.ts +0 -74
  36. package/tests/pool.test.ts +0 -184
  37. package/tests/position.test.ts +0 -76
  38. package/tests/remove_liquidity.test.ts +0 -137
  39. package/tests/swap.test.ts +0 -96
  40. package/tests/tsconfig.json +0 -26
  41. package/tsconfig.json +0 -5
  42. package/tsup.config.ts +0 -9
@@ -1,901 +0,0 @@
1
- import { asUintN, CLOCK_ADDRESS, CoinAssist, d, DETAILS_KEYS, getPackagerConfigs, IModule } from '@cetusprotocol/common-sdk'
2
- import { Transaction, TransactionObjectArgument, TransactionResult } from '@mysten/sui/transactions'
3
- import { normalizeSuiAddress } from '@mysten/sui/utils'
4
- import { DlmmErrorCode, handleError } from '../errors/errors'
5
- import { CetusDlmmSDK } from '../sdk'
6
- import { BASIS_POINT, MAX_BIN_PER_POSITION } from '../types/constants'
7
- import {
8
- AddLiquidityOption,
9
- BinAmount,
10
- BinLiquidityInfo,
11
- CalculateAddLiquidityAutoFillOption,
12
- CalculateAddLiquidityOption,
13
- CalculateRemoveLiquidityBothOption,
14
- CalculateRemoveLiquidityOnlyOption,
15
- ClosePositionOption,
16
- CollectFeeOption,
17
- CollectRewardAndFeeOption,
18
- CollectRewardOption,
19
- DlmmPosition,
20
- OpenAndAddLiquidityOption,
21
- OpenAndAddLiquidityWithPriceOption,
22
- PositionFee,
23
- PositionReward,
24
- RemoveLiquidityOption,
25
- StrategyType,
26
- UpdatePositionFeeAndRewardsOption,
27
- ValidateActiveIdSlippageOption,
28
- } from '../types/dlmm'
29
- import {
30
- BinUtils,
31
- FeeUtils,
32
- getRouterModule,
33
- parsedDlmmPosFeeData,
34
- parsedDlmmPosRewardData,
35
- parseDlmmPosition,
36
- parseStrategyType,
37
- StrategyUtils,
38
- } from '../utils'
39
- import Decimal from 'decimal.js'
40
-
41
- export class PositionModule implements IModule<CetusDlmmSDK> {
42
- protected _sdk: CetusDlmmSDK
43
-
44
- constructor(sdk: CetusDlmmSDK) {
45
- this._sdk = sdk
46
- }
47
-
48
- get sdk() {
49
- return this._sdk
50
- }
51
-
52
- buildPositionType(): string {
53
- const package_id = this._sdk.sdkOptions.dlmm_pool.package_id
54
- return `${package_id}::position::Position`
55
- }
56
-
57
- async getOwnerPositionList(owner: string): Promise<DlmmPosition[]> {
58
- const list: DlmmPosition[] = []
59
- try {
60
- const res = await this._sdk.FullClient.getOwnedObjectsByPage(owner, {
61
- options: { showType: true, showContent: true, showOwner: true },
62
- filter: {
63
- StructType: this.buildPositionType(),
64
- },
65
- })
66
-
67
- res.data.forEach((obj) => {
68
- list.push(parseDlmmPosition(obj))
69
- })
70
- } catch (error) {
71
- console.log('🚀 ~ PositionModule ~ getOwnerPositionList ~ error:', error)
72
- handleError(DlmmErrorCode.GetObjectError, error as Error, {
73
- [DETAILS_KEYS.METHOD_NAME]: 'getOwnerPositionList',
74
- [DETAILS_KEYS.REQUEST_PARAMS]: owner,
75
- })
76
- }
77
-
78
- return list
79
- }
80
-
81
- async getPosition(position_id: string): Promise<DlmmPosition> {
82
- try {
83
- const res = await this._sdk.FullClient.getObject({ id: position_id, options: { showType: true, showContent: true, showOwner: true } })
84
- return parseDlmmPosition(res)
85
- } catch (error) {
86
- console.log('🚀 ~ PositionModule ~ getPosition ~ error:', error)
87
- return handleError(DlmmErrorCode.GetObjectError, error as Error, {
88
- [DETAILS_KEYS.METHOD_NAME]: 'getPosition',
89
- [DETAILS_KEYS.REQUEST_PARAMS]: position_id,
90
- })
91
- }
92
- }
93
-
94
- /**
95
- * Collect fee
96
- * @param option - The option for collecting fee
97
- * @param tx - The transaction object
98
- * @returns The transaction object
99
- */
100
- collectFeePayload(option: CollectFeeOption, tx?: Transaction): Transaction {
101
- const { pool_id, position_id, coin_type_a, coin_type_b } = option
102
- const { dlmm_pool } = this.sdk.sdkOptions
103
- const { versioned_id, global_config_id } = getPackagerConfigs(dlmm_pool)
104
-
105
- tx = tx || new Transaction()
106
-
107
- const [fee_a_balance, fee_b_balance] = tx.moveCall({
108
- target: `${dlmm_pool.published_at}::pool::collect_position_fee`,
109
- arguments: [tx.object(pool_id), tx.object(position_id), tx.object(global_config_id), tx.object(versioned_id)],
110
- typeArguments: [coin_type_a, coin_type_b],
111
- })
112
-
113
- const fee_a_obj = CoinAssist.fromBalance(fee_a_balance, coin_type_a, tx)
114
- const fee_b_obj = CoinAssist.fromBalance(fee_b_balance, coin_type_b, tx)
115
- tx.transferObjects([fee_a_obj, fee_b_obj], this.sdk.getSenderAddress())
116
-
117
- return tx
118
- }
119
-
120
- /**
121
- * Update the fee and rewards of the position
122
- * @param option - The option for updating the fee and rewards of the position
123
- * @param tx - The transaction object
124
- * @returns The transaction object
125
- */
126
- updatePositionFeeAndRewards(option: UpdatePositionFeeAndRewardsOption, tx: Transaction) {
127
- const { dlmm_pool } = this.sdk.sdkOptions
128
- const { versioned_id } = getPackagerConfigs(dlmm_pool)
129
- const { pool_id, position_id, coin_type_a, coin_type_b } = option
130
- tx.moveCall({
131
- target: `${dlmm_pool.published_at}::pool::update_position_fee_and_rewards`,
132
- arguments: [tx.object(pool_id), tx.pure.id(position_id), tx.object(versioned_id), tx.object(CLOCK_ADDRESS)],
133
- typeArguments: [coin_type_a, coin_type_b],
134
- })
135
-
136
- return tx
137
- }
138
-
139
- /**
140
- * Collect reward
141
- * @param options - The option for collecting reward
142
- * @param tx - The transaction object
143
- * @returns The transaction object
144
- */
145
- collectRewardPayload(options: CollectRewardOption[], tx?: Transaction): Transaction {
146
- const { dlmm_pool } = this.sdk.sdkOptions
147
- const { versioned_id, global_config_id } = getPackagerConfigs(dlmm_pool)
148
-
149
- tx = tx || new Transaction()
150
-
151
- options.forEach((option) => {
152
- const { pool_id, position_id, reward_coins, coin_type_a, coin_type_b } = option
153
-
154
- reward_coins.forEach((reward_coin) => {
155
- const reward_coin_balance = tx.moveCall({
156
- target: `${dlmm_pool.published_at}::pool::collect_position_reward`,
157
- arguments: [tx.object(pool_id), tx.object(position_id), tx.object(global_config_id), tx.object(versioned_id)],
158
- typeArguments: [coin_type_a, coin_type_b, reward_coin],
159
- })
160
-
161
- const reward_coin_obj = CoinAssist.fromBalance(reward_coin_balance, reward_coin, tx)
162
- tx.transferObjects([reward_coin_obj], this.sdk.getSenderAddress())
163
- })
164
- })
165
-
166
- return tx
167
- }
168
-
169
- collectRewardAndFeePayload(options: CollectRewardAndFeeOption[], tx?: Transaction): Transaction {
170
- tx = tx || new Transaction()
171
-
172
- options.forEach((option) => {
173
- const { pool_id, position_id, reward_coins, coin_type_a, coin_type_b } = option
174
-
175
- this.updatePositionFeeAndRewards({ pool_id, position_id, coin_type_a, coin_type_b }, tx)
176
-
177
- this.collectFeePayload({ pool_id, position_id, coin_type_a, coin_type_b }, tx)
178
- this.collectRewardPayload([{ pool_id, position_id, reward_coins, coin_type_a, coin_type_b }], tx)
179
- })
180
-
181
- return tx
182
- }
183
-
184
- /**
185
- * Validate the active id slippage
186
- * @param options - The option for validating the active id slippage
187
- * @param tx - The transaction object
188
- * @returns The transaction object
189
- */
190
- validateActiveIdSlippage(options: ValidateActiveIdSlippageOption, tx: Transaction): Transaction {
191
- const { pool_id, active_id, max_price_slippage, bin_step, coin_type_a, coin_type_b } = options
192
- const { dlmm_router } = this.sdk.sdkOptions
193
-
194
- const bin_shift = BinUtils.getBinShift(active_id, bin_step, max_price_slippage)
195
- const active_id_u32 = Number(asUintN(BigInt(active_id)))
196
-
197
- tx.moveCall({
198
- target: `${dlmm_router.published_at}::utils::validate_active_id_slippage`,
199
- arguments: [
200
- typeof pool_id === 'string' ? tx.object(pool_id) : pool_id,
201
- tx.pure.u32(Number(active_id_u32)),
202
- tx.pure.u32(Number(bin_shift)),
203
- ],
204
- typeArguments: [coin_type_a, coin_type_b],
205
- })
206
-
207
- return tx
208
- }
209
-
210
- /**
211
- * Close a position
212
- * @param option - The option for closing a position
213
- * @returns The transaction object
214
- */
215
- closePositionPayload(option: ClosePositionOption, tx?: Transaction): Transaction {
216
- const { pool_id, position_id, reward_coins, coin_type_a, coin_type_b } = option
217
- const { dlmm_pool } = this.sdk.sdkOptions
218
- const { versioned_id, global_config_id } = getPackagerConfigs(dlmm_pool)
219
-
220
- tx = tx || new Transaction()
221
-
222
- this.updatePositionFeeAndRewards({ pool_id, position_id, coin_type_a, coin_type_b }, tx)
223
-
224
- this.collectRewardPayload([{ pool_id, position_id, reward_coins, coin_type_a, coin_type_b }], tx)
225
-
226
- const [close_position_cert, coin_a_balance, coin_b_balance] = tx.moveCall({
227
- target: `${dlmm_pool.published_at}::pool::close_position`,
228
- arguments: [
229
- tx.object(pool_id),
230
- tx.object(position_id),
231
- tx.object(global_config_id),
232
- tx.object(versioned_id),
233
- tx.object(CLOCK_ADDRESS),
234
- ],
235
- typeArguments: [coin_type_a, coin_type_b],
236
- })
237
-
238
- const coin_a_obj = CoinAssist.fromBalance(coin_a_balance, coin_type_a, tx)
239
- const coin_b_obj = CoinAssist.fromBalance(coin_b_balance, coin_type_b, tx)
240
-
241
- tx.moveCall({
242
- target: `${dlmm_pool.published_at}::pool::destroy_close_position_cert`,
243
- arguments: [close_position_cert, tx.object(versioned_id)],
244
- typeArguments: [],
245
- })
246
-
247
- tx.transferObjects([coin_a_obj, coin_b_obj], this.sdk.getSenderAddress())
248
-
249
- return tx
250
- }
251
-
252
- /**
253
- * Get the amounts in the active bin if in range
254
- * @param bin_manager_handle - The bin manager handle
255
- * @param lower_bin_id - The lower bin id
256
- * @param upper_bin_id - The upper bin id
257
- * @param active_id - The active id
258
- * @returns The amounts in the active bin if in range
259
- */
260
- async getActiveBinIfInRange(
261
- bin_manager_handle: string,
262
- lower_bin_id: number,
263
- upper_bin_id: number,
264
- active_id: number,
265
- bin_step: number,
266
- force_refresh = false
267
- ): Promise<BinAmount | undefined> {
268
- if (active_id <= upper_bin_id && active_id >= lower_bin_id) {
269
- const bin_info = await this._sdk.Pool.getBinInfo(bin_manager_handle, active_id, bin_step, force_refresh)
270
- return bin_info
271
- }
272
- return undefined
273
- }
274
-
275
- /**
276
- * Calculate the result of removing liquidity
277
- * @param option - The option for calculating the result of removing liquidity
278
- * @returns The result of removing liquidity
279
- */
280
- calculateRemoveLiquidityInfo(option: CalculateRemoveLiquidityBothOption | CalculateRemoveLiquidityOnlyOption): BinLiquidityInfo {
281
- const { bins, active_id, coin_amount } = option
282
- const isBothSide = 'fix_amount_a' in option
283
-
284
- const bins_b = bins.filter((bin) => bin.bin_id < active_id)
285
- const bins_a = bins.filter((bin) => bin.bin_id > active_id)
286
-
287
- let total_amount = d(0)
288
- let amount_rate = d(0)
289
- let used_bins: BinAmount[] = []
290
-
291
- if (isBothSide) {
292
- used_bins = [...bins]
293
- const { fix_amount_a } = option
294
- const active_bin = bins.find((bin) => bin.bin_id === active_id)
295
- const total_amount_a = bins_a.reduce((acc, bin) => d(acc).plus(bin.amount_a), d(0)).add(active_bin?.amount_a || '0')
296
- const total_amount_b = bins_b.reduce((acc, bin) => d(acc).plus(bin.amount_b), d(0)).add(active_bin?.amount_b || '0')
297
- total_amount = fix_amount_a ? total_amount_a : total_amount_b
298
-
299
- amount_rate = d(coin_amount).gte(total_amount) ? d(1) : d(coin_amount).div(total_amount)
300
- } else {
301
- const { is_only_a } = option
302
- used_bins = is_only_a ? bins_a : bins_b
303
- total_amount = used_bins.reduce((acc, bin) => d(acc).plus(is_only_a ? bin.amount_a : bin.amount_b), d(0))
304
-
305
- amount_rate = d(coin_amount).gte(total_amount) ? d(1) : d(coin_amount).div(total_amount)
306
- }
307
-
308
- if (d(total_amount).isZero()) {
309
- return handleError(DlmmErrorCode.InsufficientLiquidity, new Error('Insufficient liquidity'), {
310
- [DETAILS_KEYS.METHOD_NAME]: 'calculateRemoveLiquidityResult',
311
- [DETAILS_KEYS.REQUEST_PARAMS]: option,
312
- })
313
- }
314
-
315
- const info = BinUtils.processBinsByRate([...used_bins], amount_rate.toFixed())
316
- // If the proportional allocation results in an invalid amount, iterate through the bins and remove them one by one
317
- if (info.has_invalid_amount) {
318
- let remaining_amount = d(coin_amount)
319
- const processed_bins: BinAmount[] = []
320
- const is_amount_a = isBothSide ? option.fix_amount_a : option.is_only_a
321
- for (const bin of used_bins) {
322
- if (remaining_amount.lte(0)) break
323
-
324
- const bin_amount = is_amount_a ? bin.amount_a : bin.amount_b
325
- const bin_amount_other = is_amount_a ? bin.amount_b : bin.amount_a
326
-
327
- let rate = d(d(remaining_amount).div(bin_amount).toFixed(9, Decimal.ROUND_UP))
328
- rate = rate.gt(1) ? d(1) : rate
329
- let amount_to_remove = d(bin_amount).mul(rate)
330
- remaining_amount = remaining_amount.minus(amount_to_remove)
331
-
332
- const amount_to_remove_other = d(bin_amount_other).mul(rate)
333
- const liquidity_to_remove = d(bin.liquidity!).mul(rate)
334
-
335
- processed_bins.push({
336
- ...bin,
337
- amount_a: is_amount_a ? amount_to_remove.toFixed(0) : amount_to_remove_other.toFixed(0),
338
- amount_b: is_amount_a ? amount_to_remove_other.toFixed(0) : amount_to_remove.toFixed(0),
339
- liquidity: liquidity_to_remove.toFixed(0),
340
- })
341
- }
342
-
343
- return {
344
- bins: processed_bins,
345
- amount_a: processed_bins.reduce((acc, bin) => d(acc).plus(bin.amount_a), d(0)).toFixed(0),
346
- amount_b: processed_bins.reduce((acc, bin) => d(acc).plus(bin.amount_b), d(0)).toFixed(0),
347
- }
348
- }
349
-
350
- return info.bins
351
- }
352
-
353
- /**
354
- * Calculate the result of adding liquidity
355
- * @param option - The option for calculating the result of adding liquidity
356
- * @returns The result of adding liquidity
357
- */
358
- async calculateAddLiquidityInfo(option: CalculateAddLiquidityOption | CalculateAddLiquidityAutoFillOption): Promise<BinLiquidityInfo> {
359
- const isAutoFill = 'fix_amount_a' in option
360
- const { active_id, bin_step, lower_bin_id, upper_bin_id, active_bin_of_pool, strategy_type, pool_id } = option
361
-
362
- let bin_infos
363
- if (isAutoFill) {
364
- const { coin_amount, fix_amount_a } = option
365
- bin_infos = StrategyUtils.autoFillCoinByStrategyV2(
366
- active_id,
367
- bin_step,
368
- coin_amount,
369
- fix_amount_a,
370
- lower_bin_id,
371
- upper_bin_id,
372
- strategy_type,
373
- active_bin_of_pool
374
- )
375
- } else {
376
- bin_infos = StrategyUtils.toAmountsBothSideByStrategy(
377
- active_id,
378
- bin_step,
379
- lower_bin_id,
380
- upper_bin_id,
381
- option.amount_a,
382
- option.amount_b,
383
- strategy_type,
384
- active_bin_of_pool
385
- )
386
- }
387
- if (active_bin_of_pool && pool_id) {
388
- const active_bin_index = bin_infos.bins.findIndex((bin) => bin.bin_id === active_id)
389
- if (active_bin_index !== -1) {
390
- const pool = await this._sdk.Pool.getPool(pool_id, false)
391
- if (pool) {
392
- const { fees_a, fees_b } = FeeUtils.getCompositionFees(
393
- active_bin_of_pool,
394
- bin_infos.bins[active_bin_index],
395
- pool.variable_parameters
396
- )
397
- const active_bin = bin_infos.bins[active_bin_index]
398
- active_bin.amount_a = d(active_bin.amount_a).sub(fees_a).toFixed(0)
399
- active_bin.amount_b = d(active_bin.amount_b).sub(fees_b).toFixed(0)
400
- bin_infos.bins[active_bin_index] = active_bin
401
- console.log('🚀 ~ PositionModule ~ calculateAddLiquidityInfo ~ fees_a:', fees_a)
402
- console.log('🚀 ~ PositionModule ~ calculateAddLiquidityInfo ~ fees_b:', fees_b)
403
- }
404
- }
405
- }
406
-
407
- return bin_infos
408
- }
409
-
410
- /**
411
- * Remove liquidity
412
- * @param option - The option for removing liquidity
413
- * @returns The transaction
414
- */
415
- removeLiquidityPayload(option: RemoveLiquidityOption): Transaction {
416
- const {
417
- pool_id,
418
- position_id,
419
- bin_infos,
420
- reward_coins,
421
- slippage,
422
- coin_type_a,
423
- coin_type_b,
424
- active_id,
425
- collect_fee,
426
- bin_step,
427
- remove_percent,
428
- } = option
429
- const { dlmm_pool } = this.sdk.sdkOptions
430
- const { bins } = bin_infos
431
- const tx = new Transaction()
432
-
433
- if (collect_fee || reward_coins.length > 0) {
434
- this.updatePositionFeeAndRewards({ pool_id, position_id, coin_type_a, coin_type_b }, tx)
435
- }
436
- if (collect_fee) {
437
- this.collectFeePayload({ pool_id, position_id, coin_type_a, coin_type_b }, tx)
438
- }
439
-
440
- this.collectRewardPayload([{ pool_id, position_id, reward_coins, coin_type_a, coin_type_b }], tx)
441
-
442
- const { versioned_id, global_config_id } = getPackagerConfigs(dlmm_pool)
443
-
444
- if (remove_percent) {
445
- const min_bin_id_u32 = asUintN(BigInt(bins[0].bin_id))
446
- const max_bin_id_u32 = asUintN(BigInt(bins[bins.length - 1].bin_id))
447
- const remove_percent_fixed = Number(d(remove_percent).mul(BASIS_POINT).toFixed(0))
448
- const [coin_a_balance, coin_b_balance] = tx.moveCall({
449
- target: `${dlmm_pool.published_at}::pool::remove_liquidity_by_percent`,
450
- arguments: [
451
- tx.object(pool_id),
452
- tx.object(position_id),
453
- tx.pure.u32(Number(min_bin_id_u32)),
454
- tx.pure.u32(Number(max_bin_id_u32)),
455
- tx.pure.u16(remove_percent_fixed),
456
- tx.object(global_config_id),
457
- tx.object(versioned_id),
458
- tx.object(CLOCK_ADDRESS),
459
- ],
460
- typeArguments: [coin_type_a, coin_type_b],
461
- })
462
- const coin_a_obj = CoinAssist.fromBalance(coin_a_balance, coin_type_a, tx)
463
- const coin_b_obj = CoinAssist.fromBalance(coin_b_balance, coin_type_b, tx)
464
- tx.transferObjects([coin_a_obj, coin_b_obj], this.sdk.getSenderAddress())
465
- } else {
466
- const bin_amounts = tx.pure.vector(
467
- 'u32',
468
- bins.map((bin) => Number(asUintN(BigInt(bin.bin_id))))
469
- )
470
- const remove_liquiditys = tx.pure.vector(
471
- 'u128',
472
- bins.map((bin) => bin.liquidity!)
473
- )
474
-
475
- const [coin_a_balance, coin_b_balance] = tx.moveCall({
476
- target: `${dlmm_pool.published_at}::pool::remove_liquidity`,
477
- arguments: [
478
- tx.object(pool_id),
479
- tx.object(position_id),
480
- bin_amounts,
481
- remove_liquiditys,
482
- tx.object(global_config_id),
483
- tx.object(versioned_id),
484
- tx.object(CLOCK_ADDRESS),
485
- ],
486
- typeArguments: [coin_type_a, coin_type_b],
487
- })
488
- const coin_a_obj = CoinAssist.fromBalance(coin_a_balance, coin_type_a, tx)
489
- const coin_b_obj = CoinAssist.fromBalance(coin_b_balance, coin_type_b, tx)
490
- tx.transferObjects([coin_a_obj, coin_b_obj], this.sdk.getSenderAddress())
491
- }
492
-
493
- // validate the active id slippage
494
- this.validateActiveIdSlippage({ pool_id, active_id, max_price_slippage: slippage, bin_step, coin_type_a, coin_type_b }, tx)
495
-
496
- return tx
497
- }
498
-
499
- /**
500
- * Add liquidity with price
501
- * @param option - The option for adding liquidity with price
502
- * @returns The transaction
503
- */
504
- async addLiquidityWithPricePayload(option: OpenAndAddLiquidityWithPriceOption): Promise<Transaction> {
505
- const {
506
- pool_id,
507
- bin_infos,
508
- coin_type_a,
509
- coin_type_b,
510
- price_base_coin,
511
- price,
512
- lower_price,
513
- upper_price,
514
- bin_step,
515
- strategy_type,
516
- active_bin_of_pool,
517
- decimals_a,
518
- decimals_b,
519
- use_bin_infos,
520
- max_price_slippage,
521
- } = option
522
- let lower_bin_id
523
- let upper_bin_id
524
- let active_id
525
- let new_bin_infos: BinLiquidityInfo = bin_infos
526
-
527
- const is_coin_a_base = price_base_coin === 'coin_a'
528
-
529
- if (is_coin_a_base) {
530
- lower_bin_id = BinUtils.getBinIdFromPrice(lower_price, bin_step, false, decimals_a, decimals_b)
531
- upper_bin_id = BinUtils.getBinIdFromPrice(upper_price, bin_step, true, decimals_a, decimals_b)
532
- active_id = BinUtils.getBinIdFromPrice(price, bin_step, true, decimals_a, decimals_b)
533
- } else {
534
- lower_bin_id = BinUtils.getBinIdFromPrice(d(1).div(upper_price).toString(), bin_step, false, decimals_a, decimals_b)
535
- upper_bin_id = BinUtils.getBinIdFromPrice(d(1).div(lower_price).toString(), bin_step, true, decimals_a, decimals_b)
536
- active_id = BinUtils.getBinIdFromPrice(d(1).div(price).toString(), bin_step, false, decimals_a, decimals_b)
537
-
538
- const calculateOption: CalculateAddLiquidityOption = {
539
- amount_a: bin_infos.amount_b,
540
- amount_b: bin_infos.amount_a,
541
- active_id,
542
- bin_step,
543
- lower_bin_id,
544
- upper_bin_id,
545
- active_bin_of_pool,
546
- strategy_type: strategy_type,
547
- }
548
-
549
- new_bin_infos = await this.sdk.Position.calculateAddLiquidityInfo(calculateOption)
550
- }
551
-
552
- const openAndAddLiquidityOption: OpenAndAddLiquidityOption = {
553
- pool_id,
554
- active_id,
555
- bin_infos: new_bin_infos,
556
- coin_type_a,
557
- coin_type_b,
558
- lower_bin_id,
559
- upper_bin_id,
560
- strategy_type,
561
- use_bin_infos,
562
- max_price_slippage,
563
- bin_step,
564
- }
565
-
566
- return this.addLiquidityPayload(openAndAddLiquidityOption)
567
- }
568
-
569
- /**
570
- * Add liquidity
571
- * @param option - The option for adding liquidity
572
- * @returns The transaction object
573
- */
574
- addLiquidityPayload(option: AddLiquidityOption | OpenAndAddLiquidityOption, tx?: Transaction): Transaction {
575
- const {
576
- pool_id,
577
- bin_infos,
578
- coin_type_a,
579
- coin_type_b,
580
- active_id,
581
- strategy_type,
582
- max_price_slippage,
583
- bin_step,
584
- use_bin_infos = false,
585
- } = option
586
- tx = tx || new Transaction()
587
-
588
- const isOpenPosition = 'lower_bin_id' in option
589
-
590
- const liquidity_bins: BinLiquidityInfo[] = []
591
-
592
- if (isOpenPosition) {
593
- const position_bins = BinUtils.splitBinLiquidityInfo(bin_infos, option.lower_bin_id, option.upper_bin_id)
594
- liquidity_bins.push(...position_bins)
595
- } else {
596
- const position_id = option.position_id
597
- liquidity_bins.push(bin_infos)
598
-
599
- if (option.collect_fee || option.reward_coins.length > 0) {
600
- this.updatePositionFeeAndRewards({ pool_id: pool_id as string, position_id, coin_type_a, coin_type_b }, tx)
601
- }
602
-
603
- if (option.collect_fee) {
604
- this.collectFeePayload({ pool_id: pool_id as string, position_id, coin_type_a, coin_type_b }, tx)
605
- }
606
-
607
- if (option.reward_coins.length > 0) {
608
- this.collectRewardPayload(
609
- [{ pool_id: pool_id as string, position_id, reward_coins: option.reward_coins, coin_type_a, coin_type_b }],
610
- tx
611
- )
612
- }
613
- }
614
-
615
- liquidity_bins.forEach((liquidity_bin, index) => {
616
- console.log('🚀 ~ PositionModule ~ addLiquidityPayload ~ liquidity_bin:', index, liquidity_bin)
617
- const { amount_a, amount_b, bins } = liquidity_bin
618
-
619
- const coin_a_obj_id = CoinAssist.buildCoinWithBalance(BigInt(amount_a), coin_type_a, tx)
620
- const coin_b_obj_id = CoinAssist.buildCoinWithBalance(BigInt(amount_b), coin_type_b, tx)
621
-
622
- if (use_bin_infos) {
623
- this.addLiquidityInternal({
624
- pool_id,
625
- coin_type_a,
626
- coin_type_b,
627
- active_id,
628
- liquidity_bin,
629
- tx,
630
- coin_a_obj_id,
631
- coin_b_obj_id,
632
- position_id: isOpenPosition ? undefined : option.position_id,
633
- max_price_slippage,
634
- bin_step,
635
- })
636
- } else {
637
- this.addLiquidityStrategyInternal({
638
- pool_id,
639
- coin_type_a,
640
- coin_type_b,
641
- active_id,
642
- liquidity_bin,
643
- tx,
644
- max_price_slippage,
645
- bin_step,
646
- coin_a_obj_id,
647
- coin_b_obj_id,
648
- strategy_type,
649
- position_id: isOpenPosition ? undefined : option.position_id,
650
- })
651
- }
652
- })
653
-
654
- return tx
655
- }
656
-
657
- private addLiquidityStrategyInternal(option: {
658
- pool_id: string | TransactionObjectArgument
659
- coin_type_a: string
660
- coin_type_b: string
661
- active_id: number
662
- liquidity_bin: BinLiquidityInfo
663
- tx: Transaction
664
- coin_a_obj_id: TransactionObjectArgument
665
- coin_b_obj_id: TransactionObjectArgument
666
- position_id?: string
667
- strategy_type: StrategyType
668
- max_price_slippage: number
669
- bin_step: number
670
- }): Transaction {
671
- const {
672
- max_price_slippage,
673
- bin_step,
674
- position_id,
675
- pool_id,
676
- coin_type_a,
677
- coin_type_b,
678
- active_id,
679
- liquidity_bin,
680
- tx,
681
- coin_a_obj_id,
682
- coin_b_obj_id,
683
- strategy_type,
684
- } = option
685
-
686
- const { dlmm_pool, dlmm_router } = this.sdk.sdkOptions
687
- const { versioned_id, global_config_id } = getPackagerConfigs(dlmm_pool)
688
-
689
- const { bins, amount_a, amount_b } = liquidity_bin
690
-
691
- let position: string | undefined | TransactionObjectArgument = position_id
692
-
693
- const lower_bin_id = bins[0].bin_id
694
- const upper_bin_id = bins[bins.length - 1].bin_id
695
- const lower_bin_id_u32 = asUintN(BigInt(lower_bin_id))
696
-
697
- const active_id_u32 = Number(asUintN(BigInt(active_id)))
698
- const bin_shift = BinUtils.getBinShift(active_id, bin_step, max_price_slippage)
699
- const routerModule = getRouterModule(strategy_type)
700
-
701
- if (position_id === undefined) {
702
- const width = upper_bin_id - lower_bin_id + 1
703
- if (width > MAX_BIN_PER_POSITION) {
704
- handleError(DlmmErrorCode.InvalidBinWidth, new Error('Width is too large'), {
705
- [DETAILS_KEYS.METHOD_NAME]: 'openPosition',
706
- })
707
- }
708
-
709
- position = tx.moveCall({
710
- target: `${dlmm_router.published_at}::${routerModule}::open_position`,
711
- arguments: [
712
- typeof pool_id === 'string' ? tx.object(pool_id) : pool_id,
713
- coin_a_obj_id,
714
- coin_b_obj_id,
715
- tx.pure.u64(amount_a),
716
- tx.pure.u64(amount_b),
717
- tx.pure.u32(Number(lower_bin_id_u32)),
718
- tx.pure.u16(Number(width)),
719
- tx.pure.u32(Number(active_id_u32)),
720
- tx.pure.u32(Number(bin_shift)),
721
- tx.object(global_config_id),
722
- tx.object(versioned_id),
723
- tx.object(CLOCK_ADDRESS),
724
- ],
725
- typeArguments: [coin_type_a, coin_type_b],
726
- })
727
- } else {
728
- const valid_bins = bins.filter((bin) => bin.amount_a !== '0' || bin.amount_b !== '0')
729
- if (valid_bins.length === 0) {
730
- return handleError(DlmmErrorCode.InvalidParams, new Error('No bins to add liquidity'), {
731
- [DETAILS_KEYS.METHOD_NAME]: 'addLiquidityStrategyInternal',
732
- [DETAILS_KEYS.REQUEST_PARAMS]: option,
733
- })
734
- }
735
- const valid_lower_bin_id_u32 = asUintN(BigInt(valid_bins[0].bin_id))
736
- const valid_upper_bin_id_u32 = asUintN(BigInt(valid_bins[valid_bins.length - 1].bin_id))
737
-
738
- tx.moveCall({
739
- target: `${dlmm_router.published_at}::${routerModule}::add_liquidity`,
740
- arguments: [
741
- typeof pool_id === 'string' ? tx.object(pool_id) : pool_id,
742
- tx.object(position_id!),
743
- coin_a_obj_id,
744
- coin_b_obj_id,
745
- tx.pure.u64(amount_a),
746
- tx.pure.u64(amount_b),
747
- tx.pure.u32(Number(valid_lower_bin_id_u32)),
748
- tx.pure.u32(Number(valid_upper_bin_id_u32)),
749
- tx.pure.u32(Number(active_id_u32)),
750
- tx.pure.u32(Number(bin_shift)),
751
- tx.object(global_config_id),
752
- tx.object(versioned_id),
753
- tx.object(CLOCK_ADDRESS),
754
- ],
755
- typeArguments: [coin_type_a, coin_type_b],
756
- })
757
- }
758
-
759
- if (position) {
760
- tx.transferObjects([position, coin_a_obj_id, coin_b_obj_id], this.sdk.getSenderAddress())
761
- } else {
762
- tx.transferObjects([coin_a_obj_id, coin_b_obj_id], this.sdk.getSenderAddress())
763
- }
764
-
765
- return tx
766
- }
767
-
768
- private addLiquidityInternal(option: {
769
- pool_id: string | TransactionObjectArgument
770
- coin_type_a: string
771
- coin_type_b: string
772
- active_id: number
773
- liquidity_bin: BinLiquidityInfo
774
- tx: Transaction
775
- coin_a_obj_id: TransactionObjectArgument
776
- coin_b_obj_id: TransactionObjectArgument
777
- position_id?: string
778
- max_price_slippage: number
779
- bin_step: number
780
- }): Transaction {
781
- const {
782
- position_id,
783
- pool_id,
784
- coin_type_a,
785
- coin_type_b,
786
- active_id,
787
- liquidity_bin,
788
- tx,
789
- coin_a_obj_id,
790
- coin_b_obj_id,
791
- max_price_slippage,
792
- bin_step,
793
- } = option
794
- const { bins } = liquidity_bin
795
-
796
- const { dlmm_pool, dlmm_router } = this.sdk.sdkOptions
797
- const { versioned_id, global_config_id } = getPackagerConfigs(dlmm_pool)
798
- const amounts_a = tx.pure.vector(
799
- 'u64',
800
- bins.map((bin) => bin.amount_a)
801
- )
802
- const amounts_b = tx.pure.vector(
803
- 'u64',
804
- bins.map((bin) => bin.amount_b)
805
- )
806
-
807
- const bin_ids = tx.makeMoveVec({
808
- elements: bins.map((bin) => tx.pure.u32(Number(asUintN(BigInt(bin.bin_id))))),
809
- type: 'u32',
810
- })
811
- const lower_bin_id = liquidity_bin.bins[0].bin_id
812
- const upper_bin_id = liquidity_bin.bins[liquidity_bin.bins.length - 1].bin_id
813
- if (position_id === undefined) {
814
- const width = upper_bin_id - lower_bin_id + 1
815
- if (width > MAX_BIN_PER_POSITION) {
816
- handleError(DlmmErrorCode.InvalidBinWidth, new Error('Width is too large'), {
817
- [DETAILS_KEYS.METHOD_NAME]: 'openPosition',
818
- })
819
- }
820
- const open_position_id = tx.moveCall({
821
- target: `${dlmm_router.published_at}::add_liquidity::open_position`,
822
- arguments: [
823
- typeof pool_id === 'string' ? tx.object(pool_id) : pool_id,
824
- coin_a_obj_id,
825
- coin_b_obj_id,
826
- bin_ids,
827
- amounts_a,
828
- amounts_b,
829
- tx.object(global_config_id),
830
- tx.object(versioned_id),
831
- tx.object(CLOCK_ADDRESS),
832
- ],
833
- typeArguments: [coin_type_a, coin_type_b],
834
- })
835
- // validate the active id slippage
836
- if (active_id >= lower_bin_id && active_id <= upper_bin_id) {
837
- this.validateActiveIdSlippage({ pool_id, active_id, max_price_slippage, bin_step, coin_type_a, coin_type_b }, tx)
838
- }
839
- tx.transferObjects([coin_a_obj_id, coin_b_obj_id, open_position_id], this.sdk.getSenderAddress())
840
- } else {
841
- tx.moveCall({
842
- target: `${dlmm_router.published_at}::add_liquidity::add_liquidity`,
843
- arguments: [
844
- typeof pool_id === 'string' ? tx.object(pool_id) : pool_id,
845
- tx.object(position_id!),
846
- coin_a_obj_id,
847
- coin_b_obj_id,
848
- bin_ids,
849
- amounts_a,
850
- amounts_b,
851
- tx.object(global_config_id),
852
- tx.object(versioned_id),
853
- tx.object(CLOCK_ADDRESS),
854
- ],
855
- typeArguments: [coin_type_a, coin_type_b],
856
- })
857
- // validate the active id slippage
858
- if (active_id >= lower_bin_id && active_id <= upper_bin_id) {
859
- this.validateActiveIdSlippage({ pool_id, active_id, max_price_slippage, bin_step, coin_type_a, coin_type_b }, tx)
860
- }
861
- tx.transferObjects([coin_a_obj_id, coin_b_obj_id], this.sdk.getSenderAddress())
862
- }
863
-
864
- return tx
865
- }
866
-
867
- /**
868
- * Fetch the fee and reward of the position
869
- * @param options - The option for fetching the fee and reward of the position
870
- * @returns The fee and reward of the position
871
- */
872
- async fetchPositionFeeAndReward(
873
- options: CollectRewardAndFeeOption[]
874
- ): Promise<{ feeData: Record<string, PositionFee>; rewardData: Record<string, PositionReward> }> {
875
- const tx = new Transaction()
876
- this.collectRewardAndFeePayload(options, tx)
877
-
878
- const simulateRes = await this.sdk.FullClient.devInspectTransactionBlock({
879
- transactionBlock: tx,
880
- sender: normalizeSuiAddress('0x0'),
881
- })
882
-
883
- if (simulateRes.error != null) {
884
- return handleError(DlmmErrorCode.FetchError, new Error(simulateRes.error), {
885
- [DETAILS_KEYS.METHOD_NAME]: 'fetchPositionFeeAndReward',
886
- [DETAILS_KEYS.REQUEST_PARAMS]: {
887
- options,
888
- totalOptions: options.length,
889
- },
890
- })
891
- }
892
-
893
- const feeData = parsedDlmmPosFeeData(simulateRes)
894
- const rewardData = parsedDlmmPosRewardData(simulateRes)
895
-
896
- return {
897
- feeData,
898
- rewardData,
899
- }
900
- }
901
- }