@cetusprotocol/dlmm-sdk 0.0.0-experimental-20250925173459

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 (47) hide show
  1. package/.turbo/turbo-build.log +10444 -0
  2. package/README.md +654 -0
  3. package/dist/index.d.mts +1050 -0
  4. package/dist/index.d.ts +1050 -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 +882 -0
  20. package/src/modules/rewardModule.ts +174 -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/ilm.ts +33 -0
  26. package/src/types/index.ts +3 -0
  27. package/src/utils/binUtils.ts +552 -0
  28. package/src/utils/feeUtils.ts +92 -0
  29. package/src/utils/ilmUtils.ts +187 -0
  30. package/src/utils/index.ts +6 -0
  31. package/src/utils/parseData.ts +519 -0
  32. package/src/utils/strategyUtils.ts +121 -0
  33. package/src/utils/weightUtils.ts +510 -0
  34. package/tests/add_liquidity_bidask.test.ts +180 -0
  35. package/tests/add_liquidity_curve.test.ts +244 -0
  36. package/tests/add_liquidity_spot.test.ts +262 -0
  37. package/tests/bin.test.ts +80 -0
  38. package/tests/config.test.ts +73 -0
  39. package/tests/ilm.test.ts +31 -0
  40. package/tests/partner.test.ts +74 -0
  41. package/tests/pool.test.ts +174 -0
  42. package/tests/position.test.ts +76 -0
  43. package/tests/remove_liquidity.test.ts +137 -0
  44. package/tests/swap.test.ts +96 -0
  45. package/tests/tsconfig.json +26 -0
  46. package/tsconfig.json +5 -0
  47. package/tsup.config.ts +9 -0
@@ -0,0 +1,187 @@
1
+ import { d } from '@cetusprotocol/common-sdk'
2
+ import { Axis, IlmInputOptions, IlmInputResult, TokenTable } from '../types/ilm'
3
+ import { BinUtils } from './binUtils'
4
+
5
+ export class IlmUtils {
6
+ static calculateIlm(options: IlmInputOptions): IlmInputResult {
7
+ const { curvature, initial_price, max_price, bin_step, total_supply, pool_share_percentage, config } = options
8
+ const { price_curve_points_num, liquidity_distribution_num, tokens_table_num, price_table_num } = config
9
+ if (d(pool_share_percentage).lt(0) || d(pool_share_percentage).gt(100)) {
10
+ throw new Error('Pool Share Percentage must be greater than 0 and less than 100.')
11
+ }
12
+
13
+ let flat = false
14
+
15
+ if (d(max_price).lt(d(initial_price))) {
16
+ throw new Error('Maximum Price must be greater or equal to Initial Price.')
17
+ } else if (d(max_price).eq(initial_price) && !d(curvature).eq(0)) {
18
+ throw new Error('Curvature must be 0 when Maximum and Initial Price are equal.')
19
+ } else if (!d(max_price).eq(initial_price) && d(curvature).eq(0)) {
20
+ throw new Error('Maximum and Initial Price must be equal when Curvature is 0.')
21
+ } else if (d(max_price).eq(initial_price) && d(curvature).eq(0)) {
22
+ flat = true
23
+ }
24
+
25
+ const myFormula = (c: string) => {
26
+ // f * Math.pow((c / A), k) + i;
27
+ return d(price_diff).mul(d(c).div(pool_supply).pow(curvature)).add(d(initial_price)).toString()
28
+ }
29
+
30
+ const integrate = (upper: number, lower: number) => {
31
+ let u = d(price_diff)
32
+ .mul(d(pool_supply).pow(-curvature))
33
+ .mul(d(upper).pow(curvature + 1))
34
+ .div(curvature + 1)
35
+ .add(d(initial_price).mul(upper))
36
+ let l = d(price_diff)
37
+ .mul(d(pool_supply).pow(-curvature))
38
+ .mul(d(lower).pow(curvature + 1))
39
+ .div(curvature + 1)
40
+ .add(d(initial_price).mul(lower))
41
+ return u.sub(l)
42
+ }
43
+
44
+ const reverse_formula = (price: string) => {
45
+ // A * Math.pow(((p- i) / f), 1 / k);
46
+ return d(pool_supply)
47
+ .mul(
48
+ d(price)
49
+ .sub(initial_price)
50
+ .div(price_diff)
51
+ .pow(1 / curvature)
52
+ )
53
+ .toString()
54
+ }
55
+
56
+ const liquidity = (price: string) => {
57
+ // A * Math.pow((p - i), (1 / k) - 1) / (k * Math.pow(f, 1 / k));
58
+ return d(pool_supply)
59
+ .mul(
60
+ d(price)
61
+ .sub(initial_price)
62
+ .pow(1 / curvature - 1)
63
+ )
64
+ .div(d(curvature).mul(d(price_diff).pow(1 / curvature)))
65
+ .toString()
66
+ }
67
+
68
+ const binTokens = (price: string) => {
69
+ return d(price)
70
+ .sub(initial_price)
71
+ .div(price_diff)
72
+ .pow(1 / curvature)
73
+ .mul(pool_supply)
74
+ }
75
+
76
+ const pool_supply = d(total_supply).mul(pool_share_percentage).div(100) // A
77
+ const price_diff = d(max_price).sub(d(initial_price)).toFixed(8) // f
78
+
79
+ const prices: string[] = []
80
+ const heights: string[] = []
81
+
82
+ let minBinId = BinUtils.getBinIdFromLamportPrice(initial_price, bin_step, false)
83
+ let maxBinId = BinUtils.getBinIdFromLamportPrice(max_price, bin_step, false)
84
+ let total = d(0)
85
+
86
+ let binFlag = minBinId
87
+ if (flat) {
88
+ prices.push(initial_price)
89
+ heights.push(d(initial_price).mul(pool_supply).toString())
90
+ } else {
91
+ while (binFlag <= maxBinId) {
92
+ const price = BinUtils.getPricePerLamportFromBinId(binFlag, bin_step)
93
+ const nextPrice = BinUtils.getPricePerLamportFromBinId(binFlag + 1, bin_step)
94
+ const tokenDiff = binTokens(nextPrice).sub(binTokens(price))
95
+ total = total.add(tokenDiff)
96
+ heights.push(d(tokenDiff).mul(price).toString())
97
+ prices.push(price)
98
+ binFlag++
99
+ }
100
+ }
101
+ const result: IlmInputResult = {
102
+ price_curve: [],
103
+ liquidity_curve: [],
104
+ dlmm_bins: [],
105
+ tokens_table: [],
106
+ price_table: [],
107
+ }
108
+ var LDstep = d(price_diff).div(100)
109
+
110
+ // price curve
111
+ const pricePoints: Axis[] = []
112
+ for (let index = 0; index < price_curve_points_num; index++) {
113
+ const x = d(index)
114
+ .mul(pool_supply)
115
+ .div(price_curve_points_num - 1)
116
+ .toString()
117
+ const y = flat ? max_price : myFormula(x)
118
+ pricePoints.push({ x, y })
119
+ }
120
+ result.price_curve = pricePoints
121
+
122
+ // liquidity curve
123
+ let liquidityPoints: Axis[] = []
124
+ if (flat) {
125
+ liquidityPoints = [
126
+ { x: initial_price, y: '0' },
127
+ { x: initial_price, y: d(pool_supply).div(2).toString() },
128
+ { x: initial_price, y: pool_supply.toString() },
129
+ ]
130
+ } else {
131
+ for (let index = 0; index < liquidity_distribution_num; index++) {
132
+ const x = d(initial_price)
133
+ .add(d(index).mul(d(LDstep)))
134
+ .toFixed(9)
135
+ const y = liquidity(x)
136
+ liquidityPoints.push({ x, y })
137
+ }
138
+ }
139
+ result.liquidity_curve = liquidityPoints
140
+
141
+ // dlmm bins
142
+ const dlmmBins: Axis[] = []
143
+ prices.forEach((price, index) => {
144
+ const x = price
145
+ const y = heights[index]
146
+ dlmmBins.push({ x, y })
147
+ })
148
+ result.dlmm_bins = dlmmBins
149
+
150
+ // Tokens Table
151
+ const tokensTable: TokenTable[] = []
152
+ let tokensWithdrawn = d(0)
153
+ let step = d(pool_supply).div(tokens_table_num).toFixed(5)
154
+ while (tokensWithdrawn.lte(pool_supply)) {
155
+ const price = d(myFormula(tokensWithdrawn.toString())).toFixed(8)
156
+ const n = tokensWithdrawn.toFixed(8)
157
+ const amountUsd = integrate(Number(n), 0)
158
+ tokensTable.push({ withdrawn: n, price, usdc_in_pool: amountUsd.toString() })
159
+ tokensWithdrawn = tokensWithdrawn.add(step)
160
+ }
161
+ result.tokens_table = tokensTable
162
+
163
+ // Price Table
164
+ const step2 = d(price_diff).mul(10000000000).floor().div(100000000000)
165
+ const priceTable: TokenTable[] = []
166
+ if (flat) {
167
+ priceTable.push({
168
+ withdrawn: pool_supply.toString(),
169
+ price: initial_price,
170
+ usdc_in_pool: d(pool_supply).mul(initial_price).toString(),
171
+ })
172
+ } else {
173
+ let r = d(0)
174
+ let price = d(initial_price)
175
+ while (r.lte(price_table_num)) {
176
+ const tokensWithdrawn = d(reverse_formula(price.toFixed(8))).toFixed(8)
177
+ const amountUsd = integrate(Number(tokensWithdrawn), 0).toFixed(8)
178
+ priceTable.push({ withdrawn: tokensWithdrawn, price: price.toFixed(8), usdc_in_pool: amountUsd })
179
+ price = d(price).add(step2)
180
+ r = r.add(d(1))
181
+ }
182
+ }
183
+ result.price_table = priceTable
184
+
185
+ return result
186
+ }
187
+ }
@@ -0,0 +1,6 @@
1
+ export * from './parseData'
2
+ export * from './binUtils'
3
+ export * from './weightUtils'
4
+ export * from './strategyUtils'
5
+ export * from './feeUtils'
6
+ export * from './ilmUtils'
@@ -0,0 +1,519 @@
1
+ import {
2
+ asIntN,
3
+ d,
4
+ DETAILS_KEYS,
5
+ extractStructTagFromType,
6
+ fixCoinType,
7
+ getObjectFields,
8
+ getObjectType,
9
+ MathUtil,
10
+ } from '@cetusprotocol/common-sdk'
11
+ import { DevInspectResults, SuiEvent, SuiObjectResponse, SuiTransactionBlockResponse } from '@mysten/sui/client'
12
+ import BN from 'bn.js'
13
+ import Decimal from 'decimal.js'
14
+ import { DlmmErrorCode, handleError, DlmmError } from '../errors/errors'
15
+ import {
16
+ BinAmount,
17
+ BinLiquidityInfo,
18
+ BinManager,
19
+ BinSwap,
20
+ DlmmBasePool,
21
+ DlmmPool,
22
+ DlmmPosition,
23
+ Partner,
24
+ PoolTransactionInfo,
25
+ PositionFee,
26
+ PositionManager,
27
+ PositionReward,
28
+ PreSwapQuote,
29
+ Reward,
30
+ RewardInfo,
31
+ RewardManager,
32
+ RewardPeriodEmission,
33
+ RewardPeriodEmissionFormat,
34
+ StrategyType,
35
+ VariableParameters,
36
+ } from '../types/dlmm'
37
+ import { BinUtils } from './binUtils'
38
+ import { BASIS_POINT } from '../types/constants'
39
+ import { bcs } from '@mysten/sui/bcs'
40
+
41
+ /**
42
+ * Parse the DLMM base pool data
43
+ * @param data - The DLMM base pool data
44
+ * @returns The DLMM base pool
45
+ */
46
+ export function parseDlmmBasePool(data: SuiEvent): DlmmBasePool {
47
+ try {
48
+ const fields = data.parsedJson as any
49
+ const pool: DlmmBasePool = {
50
+ id: fields.pool_id,
51
+ bin_step: Number(fields.bin_step),
52
+ coin_type_a: fixCoinType(fields.coin_type_a, false),
53
+ coin_type_b: fixCoinType(fields.coin_type_b, false),
54
+ }
55
+
56
+ return pool
57
+ } catch (error) {
58
+ return handleError(DlmmErrorCode.ParseError, error as Error, {
59
+ [DETAILS_KEYS.METHOD_NAME]: 'parseDlmmBasePool',
60
+ [DETAILS_KEYS.REQUEST_PARAMS]: data,
61
+ })
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Parse the DLMM pool data
67
+ * @param data - The DLMM pool data
68
+ * @returns The DLMM pool
69
+ */
70
+ export function parseDlmmPool(data: SuiObjectResponse): DlmmPool {
71
+ try {
72
+ const fields = getObjectFields(data)
73
+ const type = getObjectType(data) as string
74
+ const formatType = extractStructTagFromType(type)
75
+
76
+ const bin_manager: BinManager = {
77
+ bin_step: fields.bin_manager.fields.bin_step,
78
+ bin_manager_handle: fields.bin_manager.fields.bins.fields.id.id,
79
+ size: fields.bin_manager.fields.bins.fields.size,
80
+ }
81
+
82
+ const position_manager: PositionManager = {
83
+ bin_step: fields.position_manager.fields.bin_step,
84
+ position_index: fields.position_manager.fields.position_index,
85
+ position_handle: fields.position_manager.fields.positions.fields.id.id,
86
+ size: fields.position_manager.fields.positions.fields.size,
87
+ }
88
+
89
+ const reward_manager_fields = fields.reward_manager.fields
90
+
91
+ const rewards = reward_manager_fields.rewards.map((reward: any) => {
92
+ const current_reward_rate = reward.fields.current_emission_rate
93
+ const emissions_per_second = MathUtil.fromX64(new BN(current_reward_rate))
94
+ const emissions_per_day = Math.floor(emissions_per_second.toNumber() * 60 * 60 * 24).toString()
95
+
96
+ const info: Reward = {
97
+ reward_coin: fixCoinType(reward.fields.reward_coin.fields.name, false),
98
+ emissions_per_second: emissions_per_second.toString(),
99
+ emissions_per_day,
100
+ period_emission_rates: {
101
+ id: reward.fields.period_emission_rates.fields.id.id,
102
+ size: reward.fields.period_emission_rates.fields.size,
103
+ },
104
+ }
105
+ return info
106
+ })
107
+ const reward_manager: RewardManager = {
108
+ is_public: reward_manager_fields.is_public,
109
+ emergency_reward_pause: reward_manager_fields.emergency_reward_pause,
110
+ vault: {
111
+ id: reward_manager_fields.vault.fields.id.id,
112
+ size: reward_manager_fields.vault.fields.size,
113
+ },
114
+ rewards,
115
+ last_updated_time: reward_manager_fields.last_updated_time,
116
+ }
117
+
118
+ const variable_parameters: VariableParameters = {
119
+ volatility_accumulator: fields.v_parameters.fields.volatility_accumulator,
120
+ volatility_reference: fields.v_parameters.fields.volatility_reference,
121
+ index_reference: asIntN(BigInt(fields.v_parameters.fields.index_reference.fields.bits)),
122
+ last_update_timestamp: fields.v_parameters.fields.last_update_timestamp,
123
+ bin_step_config: fields.v_parameters.fields.bin_step_config.fields,
124
+ }
125
+
126
+ const pool: DlmmPool = {
127
+ id: fields.id.id,
128
+ bin_step: Number(fields.bin_step),
129
+ coin_type_a: fixCoinType(formatType.type_arguments[0], false),
130
+ coin_type_b: fixCoinType(formatType.type_arguments[1], false),
131
+ pool_type: type,
132
+ index: Number(fields.index),
133
+ bin_manager,
134
+ variable_parameters,
135
+ active_id: asIntN(BigInt(fields.active_id.fields.bits)),
136
+ permissions: fields.permissions.fields,
137
+ balance_a: fields.balance_a,
138
+ balance_b: fields.balance_b,
139
+ base_fee_rate: fields.base_fee_rate,
140
+ protocol_fee_a: fields.protocol_fee_a,
141
+ protocol_fee_b: fields.protocol_fee_b,
142
+ url: fields.url,
143
+ reward_manager,
144
+ position_manager,
145
+ }
146
+ pool.bin_step = pool.bin_manager.bin_step
147
+ return pool
148
+ } catch (error) {
149
+ console.log('🚀 ~ parseDlmmPool ~ error:', error)
150
+ return handleError(DlmmErrorCode.ParseError, error as Error, {
151
+ [DETAILS_KEYS.METHOD_NAME]: 'parseDlmmPool',
152
+ [DETAILS_KEYS.REQUEST_PARAMS]: data,
153
+ })
154
+ }
155
+ }
156
+
157
+ export function parsePartner(data: SuiObjectResponse): Partner {
158
+ const fields = getObjectFields(data)
159
+ const type = getObjectType(data) as string
160
+ const formatType = extractStructTagFromType(type)
161
+
162
+ const partner: Partner = {
163
+ id: fields.id.id,
164
+ name: fields.name,
165
+ ref_fee_rate: d(fields.ref_fee_rate).div(BASIS_POINT).toNumber(),
166
+ start_time: Number(fields.start_time),
167
+ end_time: Number(fields.end_time),
168
+ balances: {
169
+ id: fields.balances.fields.id.id,
170
+ size: fields.balances.fields.size,
171
+ },
172
+ type: formatType.full_address,
173
+ }
174
+
175
+ return partner
176
+ }
177
+
178
+ export function parseDlmmPosition(data: SuiObjectResponse): DlmmPosition {
179
+ try {
180
+ const fields = getObjectFields(data)
181
+ const position: DlmmPosition = {
182
+ uri: fields.uri,
183
+ index: fields.index,
184
+ id: fields.id.id,
185
+ name: fields.name,
186
+ pool_id: fields.pool_id,
187
+ lower_bin_id: asIntN(BigInt(fields.lower_bin_id.fields.bits)),
188
+ upper_bin_id: asIntN(BigInt(fields.upper_bin_id.fields.bits)),
189
+ liquidity_shares: fields.liquidity_shares,
190
+ description: fields.description,
191
+ coin_type_a: fixCoinType(fields.coin_type_a, false),
192
+ coin_type_b: fixCoinType(fields.coin_type_b, false),
193
+ }
194
+
195
+ return position
196
+ } catch (error) {
197
+ console.log('🚀 ~ parseDlmmPosition ~ error:', error)
198
+ return handleError(DlmmErrorCode.ParseError, error as Error, {
199
+ [DETAILS_KEYS.METHOD_NAME]: 'parseDlmmPosition',
200
+ [DETAILS_KEYS.REQUEST_PARAMS]: data,
201
+ })
202
+ }
203
+ }
204
+
205
+ export function parseLiquidityShares(
206
+ liquidity_shares: string[],
207
+ bin_step: number,
208
+ lower_bin_id: number,
209
+ active_bin: BinAmount
210
+ ): BinLiquidityInfo {
211
+ const bins = liquidity_shares.map((liquidity, index) => {
212
+ const bin_id = lower_bin_id + index
213
+ const price_per_lamport = BinUtils.getPricePerLamportFromBinId(bin_id, bin_step)
214
+ if (bin_id === active_bin.bin_id) {
215
+ const { amount_a, amount_b } = BinUtils.calculateOutByShare(active_bin, liquidity)
216
+ return {
217
+ bin_id,
218
+ amount_a,
219
+ amount_b,
220
+ liquidity,
221
+ price_per_lamport,
222
+ }
223
+ }
224
+
225
+ if (bin_id < active_bin.bin_id) {
226
+ const amount_b = BinUtils.getAmountBFromLiquidity(liquidity)
227
+ return {
228
+ bin_id,
229
+ amount_a: '0',
230
+ amount_b,
231
+ liquidity,
232
+ price_per_lamport,
233
+ }
234
+ }
235
+
236
+ const q_price = BinUtils.getQPriceFromId(bin_id, bin_step)
237
+ const amount_a = BinUtils.getAmountAFromLiquidity(liquidity, q_price)
238
+ return {
239
+ bin_id,
240
+ amount_a,
241
+ amount_b: '0',
242
+ liquidity,
243
+ price_per_lamport,
244
+ }
245
+ })
246
+
247
+ const amount_a = bins
248
+ .reduce((acc, bin) => {
249
+ return acc.add(new Decimal(bin.amount_a))
250
+ }, new Decimal(0))
251
+ .toFixed(0)
252
+
253
+ const amount_b = bins
254
+ .reduce((acc, bin) => {
255
+ return acc.add(new Decimal(bin.amount_b))
256
+ }, new Decimal(0))
257
+ .toFixed(0)
258
+
259
+ return {
260
+ bins,
261
+ amount_a,
262
+ amount_b,
263
+ }
264
+ }
265
+
266
+ export function parseBinInfoList(res: DevInspectResults): BinAmount[] {
267
+ try {
268
+ const bcsCoinAmount = bcs.struct('BinAmount', {
269
+ id: bcs.struct('I32', {
270
+ bits: bcs.u32(),
271
+ }),
272
+ amount_a: bcs.u64(),
273
+ amount_b: bcs.u64(),
274
+ price: bcs.u128(),
275
+ liquidity_supply: bcs.u128(),
276
+ rewards_growth_global: bcs.vector(bcs.u128()),
277
+ fee_a_growth_global: bcs.u128(),
278
+ fee_b_growth_global: bcs.u128(),
279
+ })
280
+
281
+ const bin_amounts = bcs.vector(bcsCoinAmount).parse(Uint8Array.from(res.results![0].returnValues![0][0]))
282
+
283
+ return bin_amounts.map((bin_amount) => {
284
+ const bin_id = asIntN(BigInt(bin_amount.id.bits))
285
+ return {
286
+ bin_id,
287
+ amount_a: bin_amount.amount_a,
288
+ amount_b: bin_amount.amount_b,
289
+ liquidity: bin_amount.liquidity_supply,
290
+ price_per_lamport: BinUtils.getPricePerLamportFromQPrice(bin_amount.price),
291
+ }
292
+ })
293
+ } catch (error) {
294
+ console.log('🚀 ~ parseBinInfo ~ error:', error)
295
+ return []
296
+ }
297
+ }
298
+
299
+ export function parseBinInfo(fields: any): BinAmount {
300
+ try {
301
+ const bin_id = asIntN(BigInt(fields.id.fields.bits))
302
+ const bin_amount: BinAmount = {
303
+ bin_id,
304
+ amount_a: fields.amount_a,
305
+ amount_b: fields.amount_b,
306
+ liquidity: fields.liquidity_supply,
307
+ price_per_lamport: BinUtils.getPricePerLamportFromQPrice(fields.price),
308
+ }
309
+
310
+ return bin_amount
311
+ } catch (error) {
312
+ console.log('🚀 ~ parseBinInfo ~ error:', error)
313
+ return handleError(DlmmErrorCode.ParseError, error as Error, {
314
+ [DETAILS_KEYS.METHOD_NAME]: 'parseBinInfo',
315
+ [DETAILS_KEYS.REQUEST_PARAMS]: {
316
+ fields,
317
+ },
318
+ })
319
+ }
320
+ }
321
+
322
+ export function parsedDlmmPosFeeData(simulate_res: DevInspectResults) {
323
+ const feeData: Record<string, PositionFee> = {}
324
+ const feeValueData: any[] = simulate_res.events?.filter((item: any) => {
325
+ return item.type.includes('pool::CollectFeeEvent')
326
+ })
327
+
328
+ for (let i = 0; i < feeValueData.length; i += 1) {
329
+ const { parsedJson } = feeValueData[i]
330
+ const posObj = {
331
+ position_id: parsedJson.position,
332
+ fee_owned_a: parsedJson.fee_a,
333
+ fee_owned_b: parsedJson.fee_b,
334
+ }
335
+ feeData[parsedJson.position] = posObj
336
+ }
337
+
338
+ return feeData
339
+ }
340
+
341
+ export function parsedDlmmPosRewardData(simulate_res: DevInspectResults) {
342
+ const rewarderData: Record<string, PositionReward> = {}
343
+ const rewarderValueData: any[] = simulate_res.events?.filter((item: any) => {
344
+ return item.type.includes('pool::CollectRewardEvent')
345
+ })
346
+
347
+ for (let i = 0; i < rewarderValueData.length; i += 1) {
348
+ const { parsedJson } = rewarderValueData[i]
349
+ const position_id = parsedJson.position
350
+ const reward_coin = parsedJson.reward
351
+ const reward_amount = parsedJson.amount
352
+ const rewardInfo: RewardInfo = {
353
+ coin_type: fixCoinType(reward_coin.name, false),
354
+ reward_owned: reward_amount,
355
+ }
356
+ let rewarder = rewarderData[position_id]
357
+ if (rewarder) {
358
+ rewarder.rewards.push(rewardInfo)
359
+ } else {
360
+ rewarder = {
361
+ position_id,
362
+ rewards: [rewardInfo],
363
+ }
364
+ }
365
+ rewarderData[position_id] = rewarder
366
+ }
367
+
368
+ return rewarderData
369
+ }
370
+
371
+ export function parsedSwapQuoteData(simulate_res: DevInspectResults, a2b: boolean): PreSwapQuote | undefined {
372
+ const rewarderValueData: any[] = simulate_res.events?.filter((item: any) => {
373
+ return item.type.includes('pool::SwapEvent')
374
+ })
375
+
376
+ for (let i = 0; i < rewarderValueData.length; i += 1) {
377
+ const { parsedJson } = rewarderValueData[i]
378
+ const { partner, pool, amount_in, amount_out, fee, ref_fee, bin_swaps, from, target } = parsedJson
379
+ const bin_swaps_info: BinSwap[] = bin_swaps.map((bin_swap: any) => {
380
+ return {
381
+ bin_id: bin_swap.bin_id.bits,
382
+ in_amount: bin_swap.amount_in,
383
+ out_amount: bin_swap.amount_out,
384
+ fee: bin_swap.fee,
385
+ var_fee_rate: bin_swap.var_fee_rate,
386
+ }
387
+ })
388
+ const info: PreSwapQuote = {
389
+ pool_id: pool,
390
+ a2b,
391
+ in_amount: amount_in,
392
+ out_amount: amount_out,
393
+ ref_fee_amount: ref_fee,
394
+ fee_amount: fee,
395
+ bin_swaps: bin_swaps_info,
396
+ partner,
397
+ from_coin_type: fixCoinType(from.name, false),
398
+ to_coin_type: fixCoinType(target.name, false),
399
+ }
400
+
401
+ return info
402
+ }
403
+ return undefined
404
+ }
405
+
406
+ export function parseStrategyType(strategy_type: StrategyType): number {
407
+ switch (strategy_type) {
408
+ case StrategyType.Spot:
409
+ return 0
410
+ case StrategyType.Curve:
411
+ return 1
412
+ case StrategyType.BidAsk:
413
+ return 2
414
+ }
415
+ }
416
+ export const poolFilterEvenTypes = ['RemoveLiquidityEvent', 'SwapEvent', 'AddLiquidityEvent', 'ClosePositionEvent']
417
+ export function parsePoolTransactionInfo(data: SuiTransactionBlockResponse, txIndex: number, package_id: string, pool_id: string) {
418
+ const list: PoolTransactionInfo[] = []
419
+ const { timestampMs, events } = data
420
+
421
+ events?.forEach((event: any, index) => {
422
+ const { name: type, address: package_address } = extractStructTagFromType(event.type)
423
+ if (poolFilterEvenTypes.includes(type) && package_address === package_id && pool_id === event.parsedJson.pool) {
424
+ const info: PoolTransactionInfo = {
425
+ tx: event.id.txDigest,
426
+ sender: event.sender,
427
+ type: event.type,
428
+ block_time: timestampMs || '0',
429
+ index: `${txIndex}_${index}`,
430
+ parsed_json: event.parsedJson,
431
+ }
432
+ list.push(info)
433
+ }
434
+ })
435
+
436
+ return list
437
+ }
438
+
439
+ export function generateRewardSchedule(baseTime: number, maxIntervals: number, timeInterval: number): number[] {
440
+ const result: number[] = []
441
+
442
+ let intervals = 0 // Start from 0 to include base time if needed
443
+ const baseDateTime = new Date(baseTime * 1000).getTime() // Convert seconds to milliseconds
444
+ const nowTime = new Date().getTime()
445
+ while (true) {
446
+ const rewardTime = baseDateTime + intervals * timeInterval
447
+
448
+ if (rewardTime >= nowTime) {
449
+ result.push(rewardTime)
450
+ }
451
+
452
+ if (intervals >= maxIntervals) {
453
+ break
454
+ }
455
+
456
+ intervals += 1
457
+ }
458
+
459
+ return result
460
+ }
461
+
462
+ export function parseRewardPeriodEmission(
463
+ periodEmissionList: RewardPeriodEmission[],
464
+ startTimeInSeconds: number,
465
+ endTimeInSeconds: number,
466
+ durationSeconds: number
467
+ ) {
468
+ const result: RewardPeriodEmissionFormat[] = []
469
+ for (let time = startTimeInSeconds; time <= endTimeInSeconds; time += durationSeconds) {
470
+ const findRewardPeriodEmission = periodEmissionList.findLast((period) => d(time).gte(period.time))
471
+ if (findRewardPeriodEmission) {
472
+ result.push({
473
+ time: time.toString(),
474
+ emissions_per_second: findRewardPeriodEmission.emissions_per_second,
475
+ emissions_per_day: findRewardPeriodEmission.emissions_per_day,
476
+ visualized_time: new Date(time * 1000).toLocaleString(),
477
+ })
478
+ } else {
479
+ result.push({
480
+ emissions_per_day: '0',
481
+ time: time.toString(),
482
+ emissions_per_second: '0',
483
+ visualized_time: new Date(time * 1000).toLocaleString(),
484
+ })
485
+ }
486
+ }
487
+ return result
488
+ }
489
+
490
+ export function parseCurrentRewardPeriodEmission(periodEmissionList: RewardPeriodEmission[]): RewardPeriodEmission | undefined {
491
+ if (periodEmissionList.length === 0) {
492
+ return undefined
493
+ }
494
+ const currentTime = new Date().getTime() / 1000
495
+ const findRewardPeriodEmission = periodEmissionList.findLast((period) => d(currentTime).gte(period.time))
496
+ if (findRewardPeriodEmission) {
497
+ return findRewardPeriodEmission
498
+ }
499
+ return periodEmissionList[periodEmissionList.length - 1]
500
+ }
501
+
502
+ export function safeMulAmount(amount: Decimal, rate: Decimal): Decimal {
503
+ const result = amount.mul(rate)
504
+ if (result.gt(0) && result.lt(1)) {
505
+ throw new DlmmError(`Multiplication ${result} is less than 1`, DlmmErrorCode.AmountTooSmall)
506
+ }
507
+ return result.floor()
508
+ }
509
+
510
+ export function getRouterModule(strategy_type: StrategyType) {
511
+ switch (strategy_type) {
512
+ case StrategyType.Spot:
513
+ return 'spot'
514
+ case StrategyType.Curve:
515
+ return 'curve'
516
+ case StrategyType.BidAsk:
517
+ return 'bid_ask'
518
+ }
519
+ }