@cetusprotocol/dlmm-sdk 0.0.1

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 (44) hide show
  1. package/.turbo/turbo-build.log +10423 -0
  2. package/README.md +646 -0
  3. package/dist/index.d.mts +1015 -0
  4. package/dist/index.d.ts +1015 -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/package.json +35 -0
  10. package/src/config/index.ts +2 -0
  11. package/src/config/mainnet.ts +25 -0
  12. package/src/config/testnet.ts +30 -0
  13. package/src/errors/errors.ts +40 -0
  14. package/src/index.ts +8 -0
  15. package/src/modules/configModule.ts +184 -0
  16. package/src/modules/index.ts +1 -0
  17. package/src/modules/partnerModule.ts +302 -0
  18. package/src/modules/poolModule.ts +578 -0
  19. package/src/modules/positionModule.ts +888 -0
  20. package/src/modules/rewardModule.ts +175 -0
  21. package/src/modules/swapModule.ts +129 -0
  22. package/src/sdk.ts +88 -0
  23. package/src/types/constants.ts +23 -0
  24. package/src/types/dlmm.ts +445 -0
  25. package/src/types/index.ts +2 -0
  26. package/src/utils/binUtils.ts +552 -0
  27. package/src/utils/feeUtils.ts +92 -0
  28. package/src/utils/index.ts +5 -0
  29. package/src/utils/parseData.ts +519 -0
  30. package/src/utils/strategyUtils.ts +121 -0
  31. package/src/utils/weightUtils.ts +510 -0
  32. package/tests/add_liquidity_bidask.test.ts +180 -0
  33. package/tests/add_liquidity_curve.test.ts +244 -0
  34. package/tests/add_liquidity_spot.test.ts +262 -0
  35. package/tests/bin.test.ts +80 -0
  36. package/tests/config.test.ts +51 -0
  37. package/tests/partner.test.ts +74 -0
  38. package/tests/pool.test.ts +174 -0
  39. package/tests/position.test.ts +76 -0
  40. package/tests/remove_liquidity.test.ts +137 -0
  41. package/tests/swap.test.ts +96 -0
  42. package/tests/tsconfig.json +26 -0
  43. package/tsconfig.json +5 -0
  44. package/tsup.config.ts +9 -0
@@ -0,0 +1,578 @@
1
+ import { Transaction, TransactionObjectArgument } from '@mysten/sui/transactions'
2
+ import {
3
+ asUintN,
4
+ CLOCK_ADDRESS,
5
+ CoinAssist,
6
+ createFullClient,
7
+ d,
8
+ DataPage,
9
+ DETAILS_KEYS,
10
+ fixCoinType,
11
+ getObjectFields,
12
+ getPackagerConfigs,
13
+ IModule,
14
+ isSortedSymbols,
15
+ PageQuery,
16
+ PaginationArgs,
17
+ } from '@cetusprotocol/common-sdk'
18
+ import { DlmmErrorCode, handleError } from '../errors/errors'
19
+ import { BinUtils, parseBinInfo, parseBinInfoList, parseDlmmBasePool, parseDlmmPool, parsePoolTransactionInfo } from '../utils'
20
+ import { CetusDlmmSDK } from '../sdk'
21
+ import {
22
+ BinAmount,
23
+ BinLiquidityInfo,
24
+ CalculateAddLiquidityOption,
25
+ CreatePoolAndAddOption,
26
+ CreatePoolAndAddWithPriceOption,
27
+ CreatePoolOption,
28
+ DlmmBasePool,
29
+ DlmmConfigs,
30
+ DlmmPool,
31
+ FeeRate,
32
+ GetPoolBinInfoOption,
33
+ GetTotalFeeRateOption,
34
+ OpenAndAddLiquidityOption,
35
+ PoolTransactionInfo,
36
+ } from '../types/dlmm'
37
+ import { MAX_BIN_PER_POSITION } from '../types/constants'
38
+ import { SuiClient } from '@mysten/sui/client'
39
+ import { normalizeSuiAddress } from '@mysten/sui/utils'
40
+ import { bcs } from '@mysten/sui/bcs'
41
+
42
+ export class PoolModule implements IModule<CetusDlmmSDK> {
43
+ protected _sdk: CetusDlmmSDK
44
+
45
+ constructor(sdk: CetusDlmmSDK) {
46
+ this._sdk = sdk
47
+ }
48
+
49
+ get sdk() {
50
+ return this._sdk
51
+ }
52
+
53
+ /**
54
+ * Get the list of DLMM base pools
55
+ * @param pagination_args - The pagination arguments
56
+ * @returns The list of DLMM base pools
57
+ */
58
+ async getBasePoolList(pagination_args: PaginationArgs = 'all', force_refresh = false): Promise<DataPage<DlmmBasePool>> {
59
+ const { dlmm_pool } = this._sdk.sdkOptions
60
+ const dataPage: DataPage<DlmmBasePool> = {
61
+ data: [],
62
+ has_next_page: false,
63
+ }
64
+
65
+ const queryAll = pagination_args === 'all'
66
+ const cacheAllKey = `${dlmm_pool.package_id}_getBasePoolList`
67
+ if (queryAll) {
68
+ const cacheDate = this._sdk.getCache<DlmmBasePool[]>(cacheAllKey, force_refresh)
69
+ if (cacheDate && cacheDate.length > 0) {
70
+ dataPage.data.push(...cacheDate)
71
+ return dataPage
72
+ }
73
+ }
74
+
75
+ try {
76
+ const moveEventType = `${dlmm_pool.package_id}::registry::CreatePoolEvent`
77
+ const res = await this._sdk.FullClient.queryEventsByPage({ MoveEventType: moveEventType }, pagination_args)
78
+ dataPage.has_next_page = res.has_next_page
79
+ dataPage.next_cursor = res.next_cursor
80
+ res.data.forEach((object) => {
81
+ const pool = parseDlmmBasePool(object)
82
+ dataPage.data.push(pool)
83
+ })
84
+
85
+ if (queryAll) {
86
+ this._sdk.updateCache(`${dlmm_pool.package_id}_getPoolImmutables`, dataPage.data)
87
+ }
88
+ } catch (error) {
89
+ return handleError(DlmmErrorCode.FetchError, error as Error, {
90
+ [DETAILS_KEYS.METHOD_NAME]: 'getBasePoolList',
91
+ })
92
+ }
93
+
94
+ return dataPage
95
+ }
96
+
97
+ /**
98
+ * Get the list of DLMM pools
99
+ * @param pagination_args - The pagination arguments
100
+ * @returns The list of DLMM pools
101
+ */
102
+ async getPools(pagination_args: PaginationArgs = 'all', force_refresh = false): Promise<DataPage<DlmmPool>> {
103
+ const dataPage: DataPage<DlmmPool> = {
104
+ data: [],
105
+ has_next_page: false,
106
+ }
107
+
108
+ const basePoolPage = await this.getBasePoolList(pagination_args, force_refresh)
109
+ if (basePoolPage.data.length === 0) {
110
+ return dataPage
111
+ }
112
+
113
+ try {
114
+ const res = await this._sdk.FullClient.batchGetObjects(
115
+ basePoolPage.data.map((item) => item.id),
116
+ {
117
+ showContent: true,
118
+ showType: true,
119
+ }
120
+ )
121
+ dataPage.has_next_page = basePoolPage.has_next_page
122
+ dataPage.next_cursor = basePoolPage.next_cursor
123
+ for (const suiObj of res) {
124
+ const pool = parseDlmmPool(suiObj)
125
+ const cacheKey = `${pool.id}_getDlmmPool`
126
+ this._sdk.updateCache(cacheKey, pool)
127
+ dataPage.data.push(pool)
128
+ }
129
+ } catch (error) {
130
+ return handleError(DlmmErrorCode.FetchError, error as Error, {
131
+ [DETAILS_KEYS.METHOD_NAME]: 'getPools',
132
+ })
133
+ }
134
+
135
+ return dataPage
136
+ }
137
+
138
+ /**
139
+ * Get the bin info by bin id
140
+ * @param bin_manager_handle - The bin manager handle
141
+ * @param bin_id - The bin id
142
+ * @param bin_step - The bin step
143
+ * @param force_refresh - Whether to force a refresh of the cache
144
+ * @returns The bin info
145
+ */
146
+ async getBinInfo(bin_manager_handle: string, bin_id: number, bin_step: number, force_refresh = true): Promise<BinAmount> {
147
+ try {
148
+ const cacheKey = `${bin_manager_handle}_getBinInfo_${bin_id}`
149
+ const cacheData = this._sdk.getCache<BinAmount>(cacheKey, force_refresh)
150
+ if (cacheData !== undefined) {
151
+ return cacheData
152
+ }
153
+
154
+ const score = BinUtils.binScore(bin_id)
155
+ const [groupIndex, offsetInGroup] = BinUtils.resolveBinPosition(score)
156
+
157
+ const res: any = await this._sdk.FullClient.getDynamicFieldObject({
158
+ parentId: bin_manager_handle,
159
+ name: { type: 'u64', value: groupIndex },
160
+ })
161
+ const fields = res.data.content.fields.value.fields.value.fields.group.fields.bins[offsetInGroup].fields
162
+ const bin_info = parseBinInfo(fields)
163
+ this._sdk.updateCache(cacheKey, bin_info)
164
+ return bin_info
165
+ } catch (error) {
166
+ return {
167
+ bin_id,
168
+ amount_a: '0',
169
+ amount_b: '0',
170
+ liquidity: '0',
171
+ price_per_lamport: BinUtils.getPricePerLamportFromBinId(bin_id, bin_step),
172
+ }
173
+ }
174
+ }
175
+
176
+ async getTotalFeeRate(option: GetTotalFeeRateOption): Promise<FeeRate> {
177
+ const { dlmm_pool } = this._sdk.sdkOptions
178
+ const { pool_id, coin_type_a, coin_type_b } = option
179
+ const tx: Transaction = new Transaction()
180
+ tx.moveCall({
181
+ target: `${dlmm_pool.published_at}::pool::get_total_fee_rate`,
182
+ arguments: [tx.object(pool_id)],
183
+ typeArguments: [coin_type_a, coin_type_b],
184
+ })
185
+
186
+ const res = await this._sdk.FullClient.devInspectTransactionBlock({
187
+ transactionBlock: tx,
188
+ sender: normalizeSuiAddress('0x0'),
189
+ })
190
+ const bcsFeeRate = bcs.struct('FeeRate', {
191
+ base_fee_rate: bcs.u64(),
192
+ var_fee_rate: bcs.u64(),
193
+ total_fee_rate: bcs.u64(),
194
+ })
195
+
196
+ const feeRate = bcsFeeRate.parse(Uint8Array.from(res.results![0].returnValues![0][0]))
197
+
198
+ return feeRate
199
+ }
200
+
201
+ async getPoolBinInfo(option: GetPoolBinInfoOption): Promise<BinAmount[]> {
202
+ const { dlmm_pool } = this._sdk.sdkOptions
203
+ const { pool_id, coin_type_a, coin_type_b } = option
204
+ const limit = 1000
205
+ const bin_infos: BinAmount[] = []
206
+ let start_bin_id: number | undefined = undefined
207
+ let hasNext = true
208
+
209
+ while (hasNext) {
210
+ const tx: Transaction = new Transaction()
211
+ tx.moveCall({
212
+ target: `${dlmm_pool.published_at}::pool::fetch_bins`,
213
+ arguments: [
214
+ tx.object(pool_id),
215
+ tx.pure.vector('u32', start_bin_id ? [Number(asUintN(BigInt(start_bin_id)))] : []),
216
+ tx.pure.u64(limit),
217
+ ],
218
+ typeArguments: [coin_type_a, coin_type_b],
219
+ })
220
+
221
+ const res = await this._sdk.FullClient.devInspectTransactionBlock({
222
+ transactionBlock: tx,
223
+ sender: normalizeSuiAddress('0x0'),
224
+ })
225
+
226
+ const list = parseBinInfoList(res)
227
+ bin_infos.push(...list)
228
+ start_bin_id = list.length > 0 ? list[list.length - 1].bin_id : undefined
229
+ hasNext = list.length === limit
230
+ }
231
+
232
+ return bin_infos.sort((a, b) => a.bin_id - b.bin_id)
233
+ }
234
+
235
+ async getPoolTransactionList({
236
+ pool_id,
237
+ pagination_args,
238
+ order = 'descending',
239
+ full_rpc_url,
240
+ }: {
241
+ pool_id: string
242
+ full_rpc_url?: string
243
+ pagination_args: PageQuery
244
+ order?: 'ascending' | 'descending' | null | undefined
245
+ }): Promise<DataPage<PoolTransactionInfo>> {
246
+ const { FullClient: fullClient, sdkOptions } = this._sdk
247
+ let client
248
+ if (full_rpc_url) {
249
+ client = createFullClient(new SuiClient({ url: full_rpc_url }))
250
+ } else {
251
+ client = fullClient
252
+ }
253
+ const data: DataPage<PoolTransactionInfo> = {
254
+ data: [],
255
+ has_next_page: false,
256
+ }
257
+
258
+ const limit = 50
259
+ const query = pagination_args
260
+ const user_limit = pagination_args.limit || 10
261
+ do {
262
+ const res = await client.queryTransactionBlocksByPage({ ChangedObject: pool_id }, { ...query, limit: 50 }, order)
263
+ res.data.forEach((item, index) => {
264
+ const dataList = parsePoolTransactionInfo(item, index, sdkOptions.dlmm_pool.package_id, pool_id)
265
+ data.data = [...data.data, ...dataList]
266
+ })
267
+ data.has_next_page = res.has_next_page
268
+ data.next_cursor = res.next_cursor
269
+ query.cursor = res.next_cursor
270
+ } while (data.data.length < user_limit && data.has_next_page)
271
+
272
+ if (data.data.length > user_limit) {
273
+ data.data = data.data.slice(0, user_limit)
274
+ data.has_next_page = true
275
+ }
276
+ if (data.data.length > 0) {
277
+ data.next_cursor = data.data[data.data.length - 1].tx
278
+ }
279
+
280
+ return data
281
+ }
282
+
283
+ /**
284
+ * Get the bin info by range (TODO: need to optimize this method)
285
+ * @param bin_manager_handle - The bin manager handle
286
+ * @param lower_bin_id - The lower bin id
287
+ * @param upper_bin_id - The upper bin id
288
+ * @param bin_step - The bin step
289
+ * @returns The bin info by range
290
+ */
291
+ async getRangeBinInfo(bin_manager_handle: string, lower_bin_id: number, upper_bin_id: number, bin_step: number): Promise<BinAmount[]> {
292
+ const bin_infos: BinAmount[] = []
293
+ for (let bin_id = lower_bin_id; bin_id <= upper_bin_id; bin_id++) {
294
+ const bin_info = await Promise.all([this.getBinInfo(bin_manager_handle, bin_id, bin_step)])
295
+ bin_infos.push(...bin_info)
296
+ }
297
+ return bin_infos
298
+ }
299
+
300
+ /**
301
+ * Get the list of DLMM pools by assign pool ids
302
+ * @param assign_pool_ids - The assign pool ids
303
+ * @returns The list of DLMM pools
304
+ */
305
+ async getAssignPoolList(assign_pool_ids: string[]): Promise<DlmmPool[]> {
306
+ if (assign_pool_ids.length === 0) {
307
+ return []
308
+ }
309
+
310
+ const allPool: DlmmPool[] = []
311
+
312
+ try {
313
+ const res = await this._sdk.FullClient.batchGetObjects(assign_pool_ids, {
314
+ showContent: true,
315
+ showType: true,
316
+ })
317
+ for (const suiObj of res) {
318
+ const pool = parseDlmmPool(suiObj)
319
+ const cacheKey = `${pool.id}_getDlmmPool`
320
+ this._sdk.updateCache(cacheKey, pool)
321
+ allPool.push(pool)
322
+ }
323
+ } catch (error) {
324
+ return handleError(DlmmErrorCode.FetchError, error as Error, {
325
+ [DETAILS_KEYS.METHOD_NAME]: 'getAssignPoolList',
326
+ })
327
+ }
328
+
329
+ return allPool
330
+ }
331
+
332
+ /**
333
+ * Get a DLMM pool by its object ID.
334
+ * @param {string} pool_id The object ID of the pool to get.
335
+ * @param {true} force_refresh Whether to force a refresh of the cache.
336
+ * @returns {Promise<DlmmPool>} A promise that resolves to a DlmmPool object.
337
+ */
338
+ async getPool(pool_id: string, force_refresh = true): Promise<DlmmPool> {
339
+ try {
340
+ const cacheKey = `${pool_id}_getDlmmPool`
341
+ const cacheData = this._sdk.getCache<DlmmPool>(cacheKey, force_refresh)
342
+ if (cacheData !== undefined) {
343
+ return cacheData
344
+ }
345
+ const suiObj = await this._sdk.FullClient.getObject({
346
+ id: pool_id,
347
+ options: {
348
+ showType: true,
349
+ showContent: true,
350
+ },
351
+ })
352
+ const pool = parseDlmmPool(suiObj)
353
+ this._sdk.updateCache(cacheKey, pool)
354
+ return pool
355
+ } catch (error) {
356
+ return handleError(DlmmErrorCode.FetchError, error as Error, {
357
+ [DETAILS_KEYS.METHOD_NAME]: 'getPool',
358
+ [DETAILS_KEYS.REQUEST_PARAMS]: pool_id,
359
+ })
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Create a pool and add liquidity with a given price
365
+ * @param option - The option for creating a pool and adding liquidity with a given price
366
+ * @returns The transaction for creating a pool and adding liquidity with a given price
367
+ */
368
+ async createPoolAndAddWithPricePayload(option: CreatePoolAndAddWithPriceOption): Promise<Transaction> {
369
+ const {
370
+ bin_step,
371
+ url,
372
+ coin_type_a,
373
+ coin_type_b,
374
+ bin_infos,
375
+ price_base_coin,
376
+ price,
377
+ lower_price,
378
+ upper_price,
379
+ decimals_a,
380
+ decimals_b,
381
+ strategy_type,
382
+ use_bin_infos,
383
+ base_factor,
384
+ } = option
385
+
386
+ let lower_bin_id
387
+ let upper_bin_id
388
+ let active_id
389
+ let new_bin_infos: BinLiquidityInfo = bin_infos
390
+
391
+ const is_coin_a_base = price_base_coin === 'coin_a'
392
+
393
+ if (is_coin_a_base) {
394
+ lower_bin_id = BinUtils.getBinIdFromPrice(lower_price, bin_step, false, decimals_a, decimals_b)
395
+ upper_bin_id = BinUtils.getBinIdFromPrice(upper_price, bin_step, true, decimals_a, decimals_b)
396
+ active_id = BinUtils.getBinIdFromPrice(price, bin_step, true, decimals_a, decimals_b)
397
+ } else {
398
+ lower_bin_id = BinUtils.getBinIdFromPrice(d(1).div(upper_price).toString(), bin_step, false, decimals_a, decimals_b)
399
+ upper_bin_id = BinUtils.getBinIdFromPrice(d(1).div(lower_price).toString(), bin_step, true, decimals_a, decimals_b)
400
+ active_id = BinUtils.getBinIdFromPrice(d(1).div(price).toString(), bin_step, false, decimals_a, decimals_b)
401
+
402
+ const calculateOption: CalculateAddLiquidityOption = {
403
+ amount_a: bin_infos.amount_b,
404
+ amount_b: bin_infos.amount_a,
405
+ active_id,
406
+ bin_step,
407
+ lower_bin_id,
408
+ upper_bin_id,
409
+ amount_a_in_active_bin: '0',
410
+ amount_b_in_active_bin: '0',
411
+ strategy_type: option.strategy_type,
412
+ }
413
+
414
+ new_bin_infos = this.sdk.Position.calculateAddLiquidityInfo(calculateOption)
415
+ }
416
+
417
+ const createPoolAndAddOption: CreatePoolAndAddOption = {
418
+ bin_step,
419
+ url,
420
+ coin_type_a,
421
+ coin_type_b,
422
+ bin_infos: new_bin_infos,
423
+ lower_bin_id,
424
+ upper_bin_id,
425
+ active_id,
426
+ strategy_type,
427
+ use_bin_infos,
428
+ base_factor,
429
+ }
430
+
431
+ return this.createPoolAndAddLiquidityPayload(createPoolAndAddOption)
432
+ }
433
+
434
+ /**
435
+ * Create a pool
436
+ * @param option - The option for creating a pool
437
+ * @param tx - The transaction object
438
+ * @returns The transaction object
439
+ */
440
+ async createPoolPayload(option: CreatePoolOption, tx: Transaction): Promise<TransactionObjectArgument> {
441
+ const { dlmm_pool } = this._sdk.sdkOptions
442
+ const { bin_step, base_factor, url, coin_type_a, coin_type_b, active_id } = option
443
+ tx = tx || new Transaction()
444
+
445
+ const { registry_id, global_config_id, versioned_id } = getPackagerConfigs(dlmm_pool)
446
+
447
+ const coin_a_metadata = await this._sdk.FullClient.fetchCoinMetadata(coin_type_a)
448
+ if (!coin_a_metadata?.id) {
449
+ handleError(DlmmErrorCode.FetchError, new Error(`coin_a_metadata not found: ${coin_type_a}`), {
450
+ [DETAILS_KEYS.METHOD_NAME]: 'createPoolAndAddLiquidityPayload',
451
+ [DETAILS_KEYS.REQUEST_PARAMS]: option,
452
+ })
453
+ }
454
+ const coin_b_metadata = await this._sdk.FullClient.fetchCoinMetadata(coin_type_b)
455
+ if (!coin_b_metadata?.id) {
456
+ handleError(DlmmErrorCode.FetchError, new Error(`coin_b_metadata not found: ${coin_type_b}`), {
457
+ [DETAILS_KEYS.METHOD_NAME]: 'createPoolAndAddLiquidityPayload',
458
+ [DETAILS_KEYS.REQUEST_PARAMS]: option,
459
+ })
460
+ }
461
+
462
+ const [cert, pool_id] = tx.moveCall({
463
+ target: `${dlmm_pool.published_at}::registry::create_pool`,
464
+ arguments: [
465
+ tx.object(registry_id),
466
+ tx.object(coin_a_metadata!.id!),
467
+ tx.object(coin_b_metadata!.id!),
468
+ tx.pure.u16(bin_step),
469
+ tx.pure.u16(base_factor),
470
+ tx.pure.u32(Number(asUintN(BigInt(active_id)))),
471
+ tx.pure.string(url || ''),
472
+ tx.object(global_config_id),
473
+ tx.object(versioned_id),
474
+ tx.object(CLOCK_ADDRESS),
475
+ ],
476
+ typeArguments: [coin_type_a, coin_type_b],
477
+ })
478
+
479
+ tx.moveCall({
480
+ target: `${dlmm_pool.published_at}::registry::destroy_receipt`,
481
+ arguments: [tx.object(cert), pool_id, tx.object(versioned_id)],
482
+ typeArguments: [coin_type_a, coin_type_b],
483
+ })
484
+
485
+ return pool_id
486
+ }
487
+
488
+ /**
489
+ * Create a pool and add liquidity
490
+ * @param option - The option for creating a pool and adding liquidity
491
+ * @returns The transaction for creating a pool and adding liquidity
492
+ */
493
+ async createPoolAndAddLiquidityPayload(option: CreatePoolAndAddOption): Promise<Transaction> {
494
+ const { dlmm_pool } = this._sdk.sdkOptions
495
+ const {
496
+ bin_step,
497
+ base_factor,
498
+ url,
499
+ active_id,
500
+ coin_type_a,
501
+ coin_type_b,
502
+ bin_infos,
503
+ lower_bin_id,
504
+ upper_bin_id,
505
+ strategy_type,
506
+ use_bin_infos,
507
+ } = option
508
+
509
+ const { registry_id, global_config_id, versioned_id } = getPackagerConfigs(dlmm_pool)
510
+
511
+ const tx = new Transaction()
512
+
513
+ if (isSortedSymbols(fixCoinType(coin_type_a, false), fixCoinType(coin_type_b, false))) {
514
+ handleError(DlmmErrorCode.InvalidCoinTypeSequence, new Error('invalid coin type sequence'), {
515
+ [DETAILS_KEYS.METHOD_NAME]: 'createPoolAndAddLiquidityPayload',
516
+ [DETAILS_KEYS.REQUEST_PARAMS]: option,
517
+ })
518
+ }
519
+
520
+ const width = upper_bin_id - lower_bin_id + 1
521
+ if (width > MAX_BIN_PER_POSITION) {
522
+ handleError(DlmmErrorCode.InvalidBinWidth, new Error('Width is too large'), {
523
+ [DETAILS_KEYS.METHOD_NAME]: 'openPosition',
524
+ [DETAILS_KEYS.REQUEST_PARAMS]: option,
525
+ })
526
+ }
527
+
528
+ const coin_a_metadata = await this._sdk.FullClient.fetchCoinMetadata(coin_type_a)
529
+ if (!coin_a_metadata?.id) {
530
+ handleError(DlmmErrorCode.FetchError, new Error(`coin_a_metadata not found: ${coin_type_a}`), {
531
+ [DETAILS_KEYS.METHOD_NAME]: 'createPoolAndAddLiquidityPayload',
532
+ [DETAILS_KEYS.REQUEST_PARAMS]: option,
533
+ })
534
+ }
535
+ const coin_b_metadata = await this._sdk.FullClient.fetchCoinMetadata(coin_type_b)
536
+ if (!coin_b_metadata?.id) {
537
+ handleError(DlmmErrorCode.FetchError, new Error(`coin_b_metadata not found: ${coin_type_b}`), {
538
+ [DETAILS_KEYS.METHOD_NAME]: 'createPoolAndAddLiquidityPayload',
539
+ [DETAILS_KEYS.REQUEST_PARAMS]: option,
540
+ })
541
+ }
542
+
543
+ console.log('🚀 ~ createPoolAndAddLiquidityPayload ~ option:', {
544
+ ...option,
545
+ width,
546
+ })
547
+
548
+ // create pool
549
+ const pool_id = await this.createPoolPayload(
550
+ {
551
+ active_id,
552
+ bin_step,
553
+ base_factor,
554
+ coin_type_a,
555
+ coin_type_b,
556
+ },
557
+ tx
558
+ )
559
+
560
+ // add liquidity
561
+ const addOption: OpenAndAddLiquidityOption = {
562
+ pool_id,
563
+ bin_infos,
564
+ coin_type_a,
565
+ coin_type_b,
566
+ lower_bin_id,
567
+ upper_bin_id,
568
+ active_id,
569
+ strategy_type,
570
+ use_bin_infos,
571
+ max_price_slippage: 0,
572
+ bin_step,
573
+ }
574
+ this.sdk.Position.addLiquidityPayload(addOption, tx)
575
+
576
+ return tx
577
+ }
578
+ }