@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.
Files changed (50) hide show
  1. package/build/addLiquidity.d.ts +65 -4
  2. package/build/addLiquidity.js +757 -30
  3. package/build/addLiquidity.js.map +1 -1
  4. package/build/bisect.d.ts +11 -0
  5. package/build/bisect.js +21 -10
  6. package/build/bisect.js.map +1 -1
  7. package/build/index.d.ts +5 -4
  8. package/build/index.js +14 -7
  9. package/build/index.js.map +1 -1
  10. package/build/liquidityHistogram.d.ts +6 -1
  11. package/build/liquidityHistogram.js +55 -9
  12. package/build/liquidityHistogram.js.map +1 -1
  13. package/build/quote.d.ts +1 -1
  14. package/build/quote.js +68 -82
  15. package/build/quote.js.map +1 -1
  16. package/build/swap.js +35 -16
  17. package/build/swap.js.map +1 -1
  18. package/build/swapV2.d.ts +6 -1
  19. package/build/swapV2.js +394 -51
  20. package/build/swapV2.js.map +1 -1
  21. package/build/types.d.ts +51 -0
  22. package/build/utils.d.ts +8 -2
  23. package/build/utils.js +23 -5
  24. package/build/utils.js.map +1 -1
  25. package/build/utilsV2.d.ts +9 -0
  26. package/build/utilsV2.js +127 -5
  27. package/build/utilsV2.js.map +1 -1
  28. package/build/withdrawLiquidity.js +11 -5
  29. package/build/withdrawLiquidity.js.map +1 -1
  30. package/build/ytTrades.d.ts +7 -0
  31. package/build/ytTrades.js +163 -142
  32. package/build/ytTrades.js.map +1 -1
  33. package/package.json +2 -2
  34. package/src/addLiquidity.ts +1012 -38
  35. package/src/bisect.ts +22 -11
  36. package/src/index.ts +11 -5
  37. package/src/liquidityHistogram.ts +54 -9
  38. package/src/quote.ts +73 -95
  39. package/src/swap.ts +35 -19
  40. package/src/swapV2.ts +999 -0
  41. package/src/types.ts +55 -0
  42. package/src/utils.ts +24 -3
  43. package/src/utilsV2.ts +337 -0
  44. package/src/withdrawLiquidity.ts +12 -6
  45. package/src/ytTrades.ts +191 -172
  46. package/src/ytTradesLegacy.ts +419 -0
  47. package/build/swap-v2.d.ts +0 -20
  48. package/build/swap-v2.js +0 -261
  49. package/build/swap-v2.js.map +0 -1
  50. 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 getSuccessorTickByIdx(ticks: Ticks, tickIdx: number): number | null {
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 - 1], 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
+ }
@@ -1,6 +1,6 @@
1
1
  import { LpPositionCLMM, MarketThree, Ticks } from "@exponent-labs/exponent-fetcher"
2
2
 
3
- import { getSuccessorTickByIdx } from "./utils"
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
- const stack = [...rootShares] // Clone the trackers array
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 = getSuccessorTickByIdx(ticks, principalShare.tickIdx)
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