@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.
- package/.turbo/turbo-build.log +10423 -0
- package/README.md +646 -0
- package/dist/index.d.mts +1015 -0
- package/dist/index.d.ts +1015 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +13 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +35 -0
- package/src/config/index.ts +2 -0
- package/src/config/mainnet.ts +25 -0
- package/src/config/testnet.ts +30 -0
- package/src/errors/errors.ts +40 -0
- package/src/index.ts +8 -0
- package/src/modules/configModule.ts +184 -0
- package/src/modules/index.ts +1 -0
- package/src/modules/partnerModule.ts +302 -0
- package/src/modules/poolModule.ts +578 -0
- package/src/modules/positionModule.ts +888 -0
- package/src/modules/rewardModule.ts +175 -0
- package/src/modules/swapModule.ts +129 -0
- package/src/sdk.ts +88 -0
- package/src/types/constants.ts +23 -0
- package/src/types/dlmm.ts +445 -0
- package/src/types/index.ts +2 -0
- package/src/utils/binUtils.ts +552 -0
- package/src/utils/feeUtils.ts +92 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/parseData.ts +519 -0
- package/src/utils/strategyUtils.ts +121 -0
- package/src/utils/weightUtils.ts +510 -0
- package/tests/add_liquidity_bidask.test.ts +180 -0
- package/tests/add_liquidity_curve.test.ts +244 -0
- package/tests/add_liquidity_spot.test.ts +262 -0
- package/tests/bin.test.ts +80 -0
- package/tests/config.test.ts +51 -0
- package/tests/partner.test.ts +74 -0
- package/tests/pool.test.ts +174 -0
- package/tests/position.test.ts +76 -0
- package/tests/remove_liquidity.test.ts +137 -0
- package/tests/swap.test.ts +96 -0
- package/tests/tsconfig.json +26 -0
- package/tsconfig.json +5 -0
- package/tsup.config.ts +9 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { d } from '@cetusprotocol/common-sdk'
|
|
2
|
+
import { BinAmount, BinLiquidityInfo, StrategyType } from '../types/dlmm'
|
|
3
|
+
import { WeightUtils } from './weightUtils'
|
|
4
|
+
|
|
5
|
+
export class StrategyUtils {
|
|
6
|
+
/**
|
|
7
|
+
* Given a strategy type and amounts of X and Y, returns the distribution of liquidity.
|
|
8
|
+
* @param active_id The bin id of the active bin.
|
|
9
|
+
* @param bin_step The step size of each bin.
|
|
10
|
+
* @param min_bin_id The min bin id.
|
|
11
|
+
* @param max_bin_id The max bin id.
|
|
12
|
+
* @param amount_a The amount of X token to deposit.
|
|
13
|
+
* @param amount_b The amount of Y token to deposit.
|
|
14
|
+
* @param amount_a_in_active_bin The amount of X token in the active bin.
|
|
15
|
+
* @param amount_b_in_active_bin The amount of Y token in the active bin.
|
|
16
|
+
* @param strategy_type The strategy type.
|
|
17
|
+
* @returns The distribution of liquidity.
|
|
18
|
+
*/
|
|
19
|
+
static toAmountsBothSideByStrategy(
|
|
20
|
+
active_id: number,
|
|
21
|
+
bin_step: number,
|
|
22
|
+
min_bin_id: number,
|
|
23
|
+
max_bin_id: number,
|
|
24
|
+
amount_a: string,
|
|
25
|
+
amount_b: string,
|
|
26
|
+
amount_a_in_active_bin: string,
|
|
27
|
+
amount_b_in_active_bin: string,
|
|
28
|
+
strategy_type: StrategyType
|
|
29
|
+
): BinLiquidityInfo {
|
|
30
|
+
switch (strategy_type) {
|
|
31
|
+
case StrategyType.Spot: {
|
|
32
|
+
let weights = WeightUtils.toWeightSpotBalanced(min_bin_id, max_bin_id)
|
|
33
|
+
return WeightUtils.toAmountBothSide(
|
|
34
|
+
active_id,
|
|
35
|
+
bin_step,
|
|
36
|
+
amount_a,
|
|
37
|
+
amount_b,
|
|
38
|
+
amount_a_in_active_bin,
|
|
39
|
+
amount_b_in_active_bin,
|
|
40
|
+
weights
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
case StrategyType.Curve: {
|
|
44
|
+
let weights = WeightUtils.toWeightCurve(min_bin_id, max_bin_id, active_id)
|
|
45
|
+
return WeightUtils.toAmountBothSide(
|
|
46
|
+
active_id,
|
|
47
|
+
bin_step,
|
|
48
|
+
amount_a,
|
|
49
|
+
amount_b,
|
|
50
|
+
amount_a_in_active_bin,
|
|
51
|
+
amount_b_in_active_bin,
|
|
52
|
+
weights
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
case StrategyType.BidAsk: {
|
|
56
|
+
let weights = WeightUtils.toWeightBidAsk(min_bin_id, max_bin_id, active_id)
|
|
57
|
+
return WeightUtils.toAmountBothSide(
|
|
58
|
+
active_id,
|
|
59
|
+
bin_step,
|
|
60
|
+
amount_a,
|
|
61
|
+
amount_b,
|
|
62
|
+
amount_a_in_active_bin,
|
|
63
|
+
amount_b_in_active_bin,
|
|
64
|
+
weights
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// only apply for
|
|
71
|
+
static autoFillCoinByStrategy(
|
|
72
|
+
active_id: number,
|
|
73
|
+
bin_step: number,
|
|
74
|
+
amount: string,
|
|
75
|
+
fix_amount_a: boolean,
|
|
76
|
+
amount_a_in_active_bin: string,
|
|
77
|
+
amount_b_in_active_bin: string,
|
|
78
|
+
min_bin_id: number,
|
|
79
|
+
max_bin_id: number,
|
|
80
|
+
strategy_type: StrategyType
|
|
81
|
+
): BinLiquidityInfo {
|
|
82
|
+
switch (strategy_type) {
|
|
83
|
+
case StrategyType.Spot: {
|
|
84
|
+
let weights = WeightUtils.toWeightSpotBalanced(min_bin_id, max_bin_id)
|
|
85
|
+
return WeightUtils.autoFillCoinByWeight(
|
|
86
|
+
active_id,
|
|
87
|
+
bin_step,
|
|
88
|
+
amount,
|
|
89
|
+
fix_amount_a,
|
|
90
|
+
amount_a_in_active_bin,
|
|
91
|
+
amount_b_in_active_bin,
|
|
92
|
+
weights
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
case StrategyType.Curve: {
|
|
96
|
+
let weights = WeightUtils.toWeightCurve(min_bin_id, max_bin_id, active_id)
|
|
97
|
+
return WeightUtils.autoFillCoinByWeight(
|
|
98
|
+
active_id,
|
|
99
|
+
bin_step,
|
|
100
|
+
amount,
|
|
101
|
+
fix_amount_a,
|
|
102
|
+
amount_a_in_active_bin,
|
|
103
|
+
amount_b_in_active_bin,
|
|
104
|
+
weights
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
case StrategyType.BidAsk: {
|
|
108
|
+
let weights = WeightUtils.toWeightBidAsk(min_bin_id, max_bin_id, active_id)
|
|
109
|
+
return WeightUtils.autoFillCoinByWeight(
|
|
110
|
+
active_id,
|
|
111
|
+
bin_step,
|
|
112
|
+
amount,
|
|
113
|
+
fix_amount_a,
|
|
114
|
+
amount_a_in_active_bin,
|
|
115
|
+
amount_b_in_active_bin,
|
|
116
|
+
weights
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|