@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.
- package/.turbo/turbo-build.log +10444 -0
- package/README.md +654 -0
- package/dist/index.d.mts +1050 -0
- package/dist/index.d.ts +1050 -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 +882 -0
- package/src/modules/rewardModule.ts +174 -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/ilm.ts +33 -0
- package/src/types/index.ts +3 -0
- package/src/utils/binUtils.ts +552 -0
- package/src/utils/feeUtils.ts +92 -0
- package/src/utils/ilmUtils.ts +187 -0
- package/src/utils/index.ts +6 -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 +73 -0
- package/tests/ilm.test.ts +31 -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,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,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
|
+
}
|