@exponent-labs/market-three-math 0.1.8 → 0.9.0
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/build/addLiquidity.d.ts +65 -4
- package/build/addLiquidity.js +757 -30
- package/build/addLiquidity.js.map +1 -1
- package/build/bisect.d.ts +11 -0
- package/build/bisect.js +21 -10
- package/build/bisect.js.map +1 -1
- package/build/index.d.ts +5 -4
- package/build/index.js +14 -7
- package/build/index.js.map +1 -1
- package/build/liquidityHistogram.d.ts +6 -1
- package/build/liquidityHistogram.js +55 -9
- package/build/liquidityHistogram.js.map +1 -1
- package/build/quote.d.ts +1 -1
- package/build/quote.js +68 -82
- package/build/quote.js.map +1 -1
- package/build/swap.js +35 -16
- package/build/swap.js.map +1 -1
- package/build/swapV2.d.ts +6 -1
- package/build/swapV2.js +394 -51
- package/build/swapV2.js.map +1 -1
- package/build/types.d.ts +51 -0
- package/build/utils.d.ts +8 -2
- package/build/utils.js +23 -5
- package/build/utils.js.map +1 -1
- package/build/utilsV2.d.ts +9 -0
- package/build/utilsV2.js +127 -5
- package/build/utilsV2.js.map +1 -1
- package/build/withdrawLiquidity.js +11 -5
- package/build/withdrawLiquidity.js.map +1 -1
- package/build/ytTrades.d.ts +7 -0
- package/build/ytTrades.js +163 -142
- package/build/ytTrades.js.map +1 -1
- package/package.json +2 -2
- package/src/addLiquidity.ts +1012 -38
- package/src/bisect.ts +22 -11
- package/src/index.ts +11 -5
- package/src/liquidityHistogram.ts +54 -9
- package/src/quote.ts +73 -95
- package/src/swap.ts +35 -19
- package/src/swapV2.ts +999 -0
- package/src/types.ts +55 -0
- package/src/utils.ts +24 -3
- package/src/utilsV2.ts +337 -0
- package/src/withdrawLiquidity.ts +12 -6
- package/src/ytTrades.ts +191 -172
- package/src/ytTradesLegacy.ts +419 -0
- package/build/swap-v2.d.ts +0 -20
- package/build/swap-v2.js +0 -261
- package/build/swap-v2.js.map +0 -1
- package/src/swapLegacy.ts +0 -272
package/src/types.ts
CHANGED
|
@@ -29,6 +29,21 @@ export interface SwapArgs {
|
|
|
29
29
|
isCurrentFlashSwap: boolean
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export interface SwapExactOutArgs {
|
|
33
|
+
/** Direction of the swap (only SyToPt is supported) */
|
|
34
|
+
direction: SwapDirection
|
|
35
|
+
/** Exact amount out target (net, after fees) */
|
|
36
|
+
amountOut: number
|
|
37
|
+
/** Optional spot price limit (anti-sandwich) */
|
|
38
|
+
priceSpotLimit?: number
|
|
39
|
+
/** SY exchange rate */
|
|
40
|
+
syExchangeRate: number
|
|
41
|
+
/** Is this a flash swap? */
|
|
42
|
+
isCurrentFlashSwap: boolean
|
|
43
|
+
/** Optional max input budget constraint */
|
|
44
|
+
amountInConstraint?: number
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
export interface SwapOutcome {
|
|
33
48
|
/** Amount of input consumed */
|
|
34
49
|
amountInConsumed: number
|
|
@@ -44,6 +59,23 @@ export interface SwapOutcome {
|
|
|
44
59
|
finalTickIndex: number
|
|
45
60
|
}
|
|
46
61
|
|
|
62
|
+
export interface SwapOutcomeV2 {
|
|
63
|
+
/** Amount of input consumed */
|
|
64
|
+
amountInConsumed: number
|
|
65
|
+
/** Amount out (after fees) */
|
|
66
|
+
amountOut: number
|
|
67
|
+
/** LP fee charged in out token */
|
|
68
|
+
lpFeeChargedOutToken: number
|
|
69
|
+
/** Protocol fee charged in out token */
|
|
70
|
+
protocolFeeChargedOutToken: number
|
|
71
|
+
/** Final spot price after swap */
|
|
72
|
+
finalSpotPrice: number
|
|
73
|
+
/** Final tick key after swap */
|
|
74
|
+
finalTickKey: number
|
|
75
|
+
/** Post-trade market state after applying in-swap state updates */
|
|
76
|
+
postMarketState?: MarketThreeState
|
|
77
|
+
}
|
|
78
|
+
|
|
47
79
|
export interface AddLiquidityArgs {
|
|
48
80
|
/** Lower tick key (ln implied rate in bps) */
|
|
49
81
|
lowerTick: number
|
|
@@ -77,4 +109,27 @@ export interface LiquidityNeeds {
|
|
|
77
109
|
priceSplitForNeed: number
|
|
78
110
|
/** Tick index of the split point */
|
|
79
111
|
priceSplitTickIdx: number
|
|
112
|
+
/** Original max SY before CLMM trimming (for crossing tick scaling) */
|
|
113
|
+
originalMaxSy: number
|
|
114
|
+
/** Original max PT before CLMM trimming (for crossing tick scaling) */
|
|
115
|
+
originalMaxPt: number
|
|
116
|
+
/** PT distribution extent: current_spot - lower_price */
|
|
117
|
+
duLeftTotal: number
|
|
118
|
+
/** SY distribution extent: eff_price_current - eff_price_upper */
|
|
119
|
+
deltaCRightTotal: number
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** State of the crossing (current) tick for proportional distribution */
|
|
123
|
+
export interface CrossingTickState {
|
|
124
|
+
principalPt: number
|
|
125
|
+
principalSy: number
|
|
126
|
+
principalShareSupply: number
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Parameters for scaling crossing tick inputs from original max values */
|
|
130
|
+
export interface CrossingScaleParams {
|
|
131
|
+
originalMaxSy: number
|
|
132
|
+
originalMaxPt: number
|
|
133
|
+
duLeftTotal: number
|
|
134
|
+
deltaCRightTotal: number
|
|
80
135
|
}
|
package/src/utils.ts
CHANGED
|
@@ -131,7 +131,7 @@ export function getSuccessorTickKey(ticks: Ticks, currentTickKey: number): numbe
|
|
|
131
131
|
* Find the successor tick by index in the tick tree
|
|
132
132
|
* This is almost equivalent to the Rust successor_idx method
|
|
133
133
|
*/
|
|
134
|
-
export function
|
|
134
|
+
export function getSuccessorTickIdxByIdx(ticks: Ticks, tickIdx: number): number | null {
|
|
135
135
|
//TODO Refactor to make it more CPU efficient
|
|
136
136
|
const tick = ticks.ticksTree.at(tickIdx - 1) ?? null
|
|
137
137
|
|
|
@@ -147,6 +147,26 @@ export function getSuccessorTickByIdx(ticks: Ticks, tickIdx: number): number | n
|
|
|
147
147
|
return !!successorTick ? ticks.ticksTree.indexOf(successorTick) + 1 : null
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Find the predecessor tick by index in the tick tree
|
|
152
|
+
* This is the inverse of getSuccessorTickIdxByIdx - finds the tick with the largest apyBasePoints that is still less than the current tick
|
|
153
|
+
*/
|
|
154
|
+
export function getPredecessorTickIdxByIdx(ticks: Ticks, tickIdx: number): number | null {
|
|
155
|
+
//TODO Refactor to make it more CPU efficient
|
|
156
|
+
const tick = ticks.ticksTree.at(tickIdx - 1) ?? null
|
|
157
|
+
|
|
158
|
+
if (!tick) return null
|
|
159
|
+
|
|
160
|
+
// Find ticks with apyBasePoints less than current tick
|
|
161
|
+
const predecessorTicks = ticks.ticksTree
|
|
162
|
+
.filter((t) => t.apyBasePoints < tick.apyBasePoints)
|
|
163
|
+
.sort((a, b) => b.apyBasePoints - a.apyBasePoints) // Sort descending to get the largest that's still less
|
|
164
|
+
|
|
165
|
+
const predecessorTick = predecessorTicks.at(0) ?? null
|
|
166
|
+
|
|
167
|
+
return !!predecessorTick ? ticks.ticksTree.indexOf(predecessorTick) + 1 : null
|
|
168
|
+
}
|
|
169
|
+
|
|
150
170
|
/**
|
|
151
171
|
* Find the predecessor tick (next tick to the left)
|
|
152
172
|
* In Rust: ticks.predecessor_idx(current_left_boundary_index)
|
|
@@ -183,12 +203,13 @@ export function getImpliedRate(tickKey: number): number {
|
|
|
183
203
|
}
|
|
184
204
|
|
|
185
205
|
/**
|
|
186
|
-
* Find a tick by its key
|
|
206
|
+
* Find a tick by its key (apyBasePoints)
|
|
207
|
+
* @returns tick and its 1-based index, or null if not found
|
|
187
208
|
*/
|
|
188
209
|
export function findTickByKey(ticks: Ticks, tickKey: number): { tick: Tick; index: number } | null {
|
|
189
210
|
const index = ticks.ticksTree.findIndex((t) => t.apyBasePoints === tickKey)
|
|
190
211
|
if (index === -1) return null
|
|
191
|
-
return { tick: ticks.ticksTree[index
|
|
212
|
+
return { tick: ticks.ticksTree[index], index: index + 1 } // Return 1-based index to match Rust
|
|
192
213
|
}
|
|
193
214
|
|
|
194
215
|
/**
|
package/src/utilsV2.ts
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities V2 for CLMM calculations
|
|
3
|
+
* More closely mirrors the Rust on-chain implementation
|
|
4
|
+
* Uses tick key (apyBasePoints) as the primary identifier instead of array index
|
|
5
|
+
*/
|
|
6
|
+
import { Tick, Ticks } from "@exponent-labs/exponent-fetcher"
|
|
7
|
+
|
|
8
|
+
const SECONDS_PER_YEAR = 365 * 24 * 60 * 60
|
|
9
|
+
const TICK_KEY_BASE_POINTS = 1_000_000
|
|
10
|
+
const PRECISE_NUMBER_DENOM = 1_000_000_000_000_000_000n
|
|
11
|
+
|
|
12
|
+
function cloneTick(tick: Tick): Tick {
|
|
13
|
+
return {
|
|
14
|
+
...tick,
|
|
15
|
+
farms: tick.farms.map((farm) => ({ ...farm })),
|
|
16
|
+
emissions: tick.emissions.map((emission) => ({ ...emission })),
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Effective price snapshot
|
|
22
|
+
* Matches the Rust EffSnap struct
|
|
23
|
+
*/
|
|
24
|
+
export class EffSnap {
|
|
25
|
+
timeFactor: number
|
|
26
|
+
syExchangeRate: number
|
|
27
|
+
|
|
28
|
+
constructor(timeFactor: number, syExchangeRate: number) {
|
|
29
|
+
this.timeFactor = timeFactor
|
|
30
|
+
this.syExchangeRate = syExchangeRate
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Calculate effective price from spot price
|
|
35
|
+
* Rust: C(u) = u^(-(τ-1)) / (r * (τ - 1))
|
|
36
|
+
*/
|
|
37
|
+
getEffectivePrice(u: number): number {
|
|
38
|
+
if (this.timeFactor === 1.0) {
|
|
39
|
+
throw new Error("time_factor cannot be 1.0")
|
|
40
|
+
}
|
|
41
|
+
return Math.pow(u, -this.timeFactor + 1.0) / (this.syExchangeRate * (this.timeFactor - 1.0))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Convert effective price back to spot price
|
|
46
|
+
* Rust: u = (r * C * (τ - 1))^(1/(1-τ))
|
|
47
|
+
*/
|
|
48
|
+
spotPriceFromEffectivePrice(cEff: number): number {
|
|
49
|
+
const base = this.syExchangeRate * cEff * (this.timeFactor - 1.0)
|
|
50
|
+
return Math.pow(base, 1.0 / (1.0 - this.timeFactor))
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* TicksWrapper - wraps the ticks array and provides RB-tree-like operations
|
|
56
|
+
* This simulates the Rust RB-tree behavior using a sorted map
|
|
57
|
+
*/
|
|
58
|
+
export class TicksWrapper {
|
|
59
|
+
private ticksByKey: Map<number, Tick> // key (apyBasePoints) -> Tick
|
|
60
|
+
private sortedKeys: number[] // sorted list of keys for successor/predecessor
|
|
61
|
+
private baseTicksTree: Tick[]
|
|
62
|
+
private market: Ticks["market"]
|
|
63
|
+
|
|
64
|
+
currentSpotPrice: number
|
|
65
|
+
currentTickKey: number // Store key instead of array index
|
|
66
|
+
currentPrefixSum: bigint
|
|
67
|
+
feeGrowthIndexGlobalPt: bigint
|
|
68
|
+
feeGrowthIndexGlobalSy: bigint
|
|
69
|
+
|
|
70
|
+
constructor(ticks: Ticks) {
|
|
71
|
+
this.ticksByKey = new Map()
|
|
72
|
+
this.baseTicksTree = ticks.ticksTree.map(cloneTick)
|
|
73
|
+
this.market = ticks.market
|
|
74
|
+
|
|
75
|
+
// Build map from apyBasePoints -> Tick
|
|
76
|
+
for (const tick of this.baseTicksTree) {
|
|
77
|
+
if (tick.apyBasePoints > 0) {
|
|
78
|
+
this.ticksByKey.set(tick.apyBasePoints, tick)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Get sorted keys for efficient successor/predecessor lookup
|
|
83
|
+
this.sortedKeys = Array.from(this.ticksByKey.keys()).sort((a, b) => a - b)
|
|
84
|
+
|
|
85
|
+
this.currentSpotPrice = ticks.currentSpotPrice
|
|
86
|
+
this.currentPrefixSum = ticks.currentPrefixSum
|
|
87
|
+
this.feeGrowthIndexGlobalPt = ticks.feeGrowthIndexGlobalPt
|
|
88
|
+
this.feeGrowthIndexGlobalSy = ticks.feeGrowthIndexGlobalSy
|
|
89
|
+
|
|
90
|
+
// Convert currentTick (1-based index) to key
|
|
91
|
+
// In Rust, currentTick is stored as 1-based index into the ticks tree
|
|
92
|
+
// We need to find the corresponding key (apyBasePoints)
|
|
93
|
+
const currentTickFromArray = this.baseTicksTree[ticks.currentTick - 1]
|
|
94
|
+
if (currentTickFromArray && currentTickFromArray.apyBasePoints > 0) {
|
|
95
|
+
this.currentTickKey = currentTickFromArray.apyBasePoints
|
|
96
|
+
} else {
|
|
97
|
+
// Fallback: find tick with spot price closest to currentSpotPrice
|
|
98
|
+
this.currentTickKey = this.findTickKeyBySpotPrice(ticks.currentSpotPrice)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Find tick key by spot price (for initialization)
|
|
104
|
+
*/
|
|
105
|
+
private findTickKeyBySpotPrice(spotPrice: number): number {
|
|
106
|
+
let closestKey = this.sortedKeys[0] || 0
|
|
107
|
+
|
|
108
|
+
// Iterate through sorted keys to ensure order
|
|
109
|
+
for (const key of this.sortedKeys) {
|
|
110
|
+
const tick = this.ticksByKey.get(key)
|
|
111
|
+
if (!tick) continue
|
|
112
|
+
|
|
113
|
+
const diff = spotPrice - tick.impliedRate
|
|
114
|
+
if (diff < 0) {
|
|
115
|
+
// If diff is negative, return the previous key (the one we just passed)
|
|
116
|
+
return closestKey
|
|
117
|
+
}
|
|
118
|
+
closestKey = key
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return closestKey
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get tick by key (apyBasePoints)
|
|
126
|
+
* Equivalent to Rust's ticks_tree.get_node(idx).value
|
|
127
|
+
*/
|
|
128
|
+
getTickByKey(key: number): Tick | null {
|
|
129
|
+
return this.ticksByKey.get(key) ?? null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get spot price for a tick key
|
|
134
|
+
* Equivalent to Rust's ticks.get_spot_price(idx)
|
|
135
|
+
* In Rust: spot_price = 1.0 + key / TICK_KEY_BASE_POINTS
|
|
136
|
+
*/
|
|
137
|
+
getSpotPrice(key: number): number {
|
|
138
|
+
const tick = this.ticksByKey.get(key)
|
|
139
|
+
if (tick) {
|
|
140
|
+
return tick.impliedRate
|
|
141
|
+
}
|
|
142
|
+
// Fallback calculation if tick not found
|
|
143
|
+
return 1.0 + key / TICK_KEY_BASE_POINTS
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Find successor key (next tick with greater apyBasePoints)
|
|
148
|
+
* Equivalent to Rust's ticks.successor_idx(idx)
|
|
149
|
+
*/
|
|
150
|
+
successorKey(currentKey: number): number | null {
|
|
151
|
+
const idx = this.sortedKeys.findIndex((k) => k > currentKey)
|
|
152
|
+
return idx !== -1 ? this.sortedKeys[idx] : null
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Find predecessor key (previous tick with smaller apyBasePoints)
|
|
157
|
+
* Equivalent to Rust's ticks.predecessor_idx(idx)
|
|
158
|
+
*/
|
|
159
|
+
predecessorKey(currentKey: number): number | null {
|
|
160
|
+
// Find the largest key that is smaller than currentKey
|
|
161
|
+
for (let i = this.sortedKeys.length - 1; i >= 0; i--) {
|
|
162
|
+
if (this.sortedKeys[i] < currentKey) {
|
|
163
|
+
return this.sortedKeys[i]
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get principal values for a tick
|
|
171
|
+
*/
|
|
172
|
+
getPrincipals(key: number): { principalPt: bigint; principalSy: bigint } {
|
|
173
|
+
const tick = this.ticksByKey.get(key)
|
|
174
|
+
return {
|
|
175
|
+
principalPt: tick?.principalPt ?? 0n,
|
|
176
|
+
principalSy: tick?.principalSy ?? 0n,
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get liquidity net for a tick
|
|
182
|
+
*/
|
|
183
|
+
getLiquidityNet(key: number): bigint {
|
|
184
|
+
const tick = this.ticksByKey.get(key)
|
|
185
|
+
return tick?.liquidityNet ?? 0n
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private clamp01(value: number): number {
|
|
189
|
+
if (!Number.isFinite(value)) return 0
|
|
190
|
+
if (value <= 0) return 0
|
|
191
|
+
if (value >= 1) return 1
|
|
192
|
+
return value
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private ratioToPrecise(value: number): bigint {
|
|
196
|
+
const clamped = this.clamp01(value)
|
|
197
|
+
if (clamped <= 0) return 0n
|
|
198
|
+
if (clamped >= 1) return PRECISE_NUMBER_DENOM
|
|
199
|
+
return BigInt(Math.round(clamped * Number(PRECISE_NUMBER_DENOM)))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private scaleByRatioFloor(value: bigint, ratioScaled: bigint): bigint {
|
|
203
|
+
return (value * ratioScaled) / PRECISE_NUMBER_DENOM
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private splitPrincipalsAfterInsert(insertedKey: number, snap: EffSnap): void {
|
|
207
|
+
const leftKey = this.predecessorKey(insertedKey)
|
|
208
|
+
const rightKey = this.successorKey(insertedKey)
|
|
209
|
+
if (leftKey == null || rightKey == null) {
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const leftTick = this.ticksByKey.get(leftKey)
|
|
214
|
+
const insertedTick = this.ticksByKey.get(insertedKey)
|
|
215
|
+
if (!leftTick || !insertedTick) {
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const lowerPrice = this.getSpotPrice(leftKey)
|
|
220
|
+
const insertedPrice = this.getSpotPrice(insertedKey)
|
|
221
|
+
const upperPrice = this.getSpotPrice(rightKey)
|
|
222
|
+
|
|
223
|
+
const du = upperPrice - lowerPrice
|
|
224
|
+
const rLen = du <= 0 ? 0 : this.clamp01((insertedPrice - lowerPrice) / du)
|
|
225
|
+
const rLenScaled = this.ratioToPrecise(rLen)
|
|
226
|
+
|
|
227
|
+
const uPtR = Math.min(upperPrice, this.currentSpotPrice)
|
|
228
|
+
const duPtTot = uPtR - lowerPrice
|
|
229
|
+
const duPtLeft = Math.min(insertedPrice, this.currentSpotPrice) - lowerPrice
|
|
230
|
+
const rPt = duPtTot <= 0 ? 1 : this.clamp01(duPtLeft / duPtTot)
|
|
231
|
+
const rPtScaled = this.ratioToPrecise(rPt)
|
|
232
|
+
|
|
233
|
+
const uSyL = Math.max(lowerPrice, this.currentSpotPrice)
|
|
234
|
+
const cL = snap.getEffectivePrice(uSyL)
|
|
235
|
+
const cM = snap.getEffectivePrice(Math.min(Math.max(insertedPrice, this.currentSpotPrice), upperPrice))
|
|
236
|
+
const cR = snap.getEffectivePrice(upperPrice)
|
|
237
|
+
const dcTot = cL - cR
|
|
238
|
+
const dcLeft = cL - cM
|
|
239
|
+
const rSy = dcTot <= 0 ? 1 : this.clamp01(dcLeft / dcTot)
|
|
240
|
+
const rSyScaled = this.ratioToPrecise(rSy)
|
|
241
|
+
|
|
242
|
+
const ptLowerBefore = leftTick.principalPt
|
|
243
|
+
const syLowerBefore = leftTick.principalSy
|
|
244
|
+
const supplyLowerBefore = leftTick.principalShareSupply
|
|
245
|
+
|
|
246
|
+
const ptLeft = this.scaleByRatioFloor(ptLowerBefore, rPtScaled)
|
|
247
|
+
const syLeft = this.scaleByRatioFloor(syLowerBefore, rSyScaled)
|
|
248
|
+
const supplyLeft = this.scaleByRatioFloor(supplyLowerBefore, rLenScaled)
|
|
249
|
+
|
|
250
|
+
leftTick.principalPt = ptLeft
|
|
251
|
+
leftTick.principalSy = syLeft
|
|
252
|
+
leftTick.principalShareSupply = supplyLeft
|
|
253
|
+
|
|
254
|
+
insertedTick.principalPt += ptLowerBefore - ptLeft
|
|
255
|
+
insertedTick.principalSy += syLowerBefore - syLeft
|
|
256
|
+
insertedTick.principalShareSupply += supplyLowerBefore - supplyLeft
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
upsertBoundaryTick(key: number, snap: EffSnap): void {
|
|
260
|
+
if (this.ticksByKey.has(key)) {
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const templateTick = this.baseTicksTree[0]
|
|
265
|
+
const newTick: Tick = {
|
|
266
|
+
liquidityNet: 0n,
|
|
267
|
+
liquidityGross: 0n,
|
|
268
|
+
feeGrowthOutsidePt: 0n,
|
|
269
|
+
feeGrowthOutsideSy: 0n,
|
|
270
|
+
impliedRate: 1 + key / TICK_KEY_BASE_POINTS,
|
|
271
|
+
principalPt: 0n,
|
|
272
|
+
principalSy: 0n,
|
|
273
|
+
apyBasePoints: key,
|
|
274
|
+
principalShareSupply: 0n,
|
|
275
|
+
farms: templateTick ? templateTick.farms.map(() => ({ lastSeenIndex: 0 })) : [],
|
|
276
|
+
emissions: templateTick
|
|
277
|
+
? templateTick.emissions.map(() => ({ lastSeenIndex: 0, lastPositionIndex: 0 }))
|
|
278
|
+
: [],
|
|
279
|
+
lastSplitEpoch: 0n,
|
|
280
|
+
frozenLiquidity: 0n,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
this.baseTicksTree.push(newTick)
|
|
284
|
+
this.ticksByKey.set(key, newTick)
|
|
285
|
+
this.sortedKeys.push(key)
|
|
286
|
+
this.sortedKeys.sort((a, b) => a - b)
|
|
287
|
+
|
|
288
|
+
this.splitPrincipalsAfterInsert(key, snap)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
setPrincipals(key: number, principalPt: bigint, principalSy: bigint): void {
|
|
292
|
+
const tick = this.ticksByKey.get(key)
|
|
293
|
+
if (!tick) return
|
|
294
|
+
tick.principalPt = principalPt < 0n ? 0n : principalPt
|
|
295
|
+
tick.principalSy = principalSy < 0n ? 0n : principalSy
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
toTicks(currentTickKey: number, currentSpotPrice: number, currentPrefixSum: bigint): Ticks {
|
|
299
|
+
const currentTickIdx = this.baseTicksTree.findIndex((tick) => tick.apyBasePoints === currentTickKey)
|
|
300
|
+
const fallbackTickKey = this.findTickKeyBySpotPrice(currentSpotPrice)
|
|
301
|
+
const fallbackTickIdx = this.baseTicksTree.findIndex((tick) => tick.apyBasePoints === fallbackTickKey)
|
|
302
|
+
const resolvedCurrentTick = currentTickIdx >= 0 ? currentTickIdx + 1 : fallbackTickIdx >= 0 ? fallbackTickIdx + 1 : 0
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
currentTick: resolvedCurrentTick,
|
|
306
|
+
ticksTree: this.baseTicksTree.map(cloneTick),
|
|
307
|
+
market: this.market,
|
|
308
|
+
feeGrowthIndexGlobalPt: this.feeGrowthIndexGlobalPt,
|
|
309
|
+
feeGrowthIndexGlobalSy: this.feeGrowthIndexGlobalSy,
|
|
310
|
+
currentPrefixSum,
|
|
311
|
+
currentSpotPrice,
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Calculate normalized time remaining
|
|
318
|
+
*/
|
|
319
|
+
export function normalizedTimeRemaining(secondsRemaining: number): number {
|
|
320
|
+
return secondsRemaining / SECONDS_PER_YEAR
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Calculate current fee rate
|
|
325
|
+
*/
|
|
326
|
+
export function calculateFeeRate(lnFeeRateRoot: number, secondsRemaining: number): number {
|
|
327
|
+
const timeFactor = normalizedTimeRemaining(secondsRemaining)
|
|
328
|
+
return Math.exp(lnFeeRateRoot * timeFactor)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Calculate fee from amount
|
|
333
|
+
* Mirrors Rust: ((amount as f64) * (fee_rate - 1.0)).ceil() as u64
|
|
334
|
+
*/
|
|
335
|
+
export function getFeeFromAmount(amount: number, feeRate: number): number {
|
|
336
|
+
return Math.ceil(amount * (feeRate - 1.0))
|
|
337
|
+
}
|
package/src/withdrawLiquidity.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LpPositionCLMM, MarketThree, Ticks } from "@exponent-labs/exponent-fetcher"
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { getSuccessorTickIdxByIdx } from "./utils"
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* TypeScript version of project_anchor_shares_to_current_ticks from Rust
|
|
@@ -20,7 +20,14 @@ type ShareTracker = LpPositionCLMM["shareTrackers"][0]
|
|
|
20
20
|
function projectAnchorSharesToCurrentTicks(ticks: Ticks, rootShares: ShareTracker[]): ShareTracker[] {
|
|
21
21
|
const SENTINEL = 0xffffffff // Sentinel value for tree traversal (matches Rust implementation)
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
// Deep clone the trackers to avoid mutating the original objects (important for React Query caching)
|
|
24
|
+
const stack: ShareTracker[] = rootShares.map((s) => ({
|
|
25
|
+
tickIdx: s.tickIdx,
|
|
26
|
+
rightTickIdx: s.rightTickIdx,
|
|
27
|
+
splitEpoch: s.splitEpoch,
|
|
28
|
+
lpShare: s.lpShare,
|
|
29
|
+
emissions: s.emissions.map((e) => ({ staged: e.staged, lastSeenIndex: e.lastSeenIndex })),
|
|
30
|
+
}))
|
|
24
31
|
const newShares: ShareTracker[] = [] // This will hold the leaf shares
|
|
25
32
|
|
|
26
33
|
while (stack.length > 0) {
|
|
@@ -42,7 +49,7 @@ function projectAnchorSharesToCurrentTicks(ticks: Ticks, rootShares: ShareTracke
|
|
|
42
49
|
|
|
43
50
|
if (rightIndex !== SENTINEL) {
|
|
44
51
|
// Find the successor tick (the split point)
|
|
45
|
-
const splitedIndex =
|
|
52
|
+
const splitedIndex = getSuccessorTickIdxByIdx(ticks, principalShare.tickIdx)
|
|
46
53
|
|
|
47
54
|
if (splitedIndex === null) {
|
|
48
55
|
//? No successor tick found for splitting
|
|
@@ -201,7 +208,6 @@ function calculatePtSyRemoval(
|
|
|
201
208
|
const tickNode = ticks.ticksTree.at(share.tickIdx - 1) ?? null
|
|
202
209
|
|
|
203
210
|
if (!tickNode) {
|
|
204
|
-
//? Tick node not found for provided tickIdx
|
|
205
211
|
continue
|
|
206
212
|
}
|
|
207
213
|
|
|
@@ -213,11 +219,11 @@ function calculatePtSyRemoval(
|
|
|
213
219
|
|
|
214
220
|
// Calculate PT output for this tick
|
|
215
221
|
// pt_out = principal_pt * burn_shares / supply
|
|
216
|
-
const ptOut = (tickNode.principalPt * burnShares) / supply
|
|
222
|
+
const ptOut = supply > 0n ? (tickNode.principalPt * burnShares) / supply : 0n
|
|
217
223
|
|
|
218
224
|
// Calculate SY output for this tick
|
|
219
225
|
// sy_out = principal_sy * burn_shares / supply
|
|
220
|
-
const syOut = (tickNode.principalSy * burnShares) / supply
|
|
226
|
+
const syOut = supply > 0n ? (tickNode.principalSy * burnShares) / supply : 0n
|
|
221
227
|
|
|
222
228
|
totalPtOut += ptOut
|
|
223
229
|
totalSyOut += syOut
|