@exponent-labs/market-three-math 0.1.8 → 0.9.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/build/addLiquidity.d.ts +65 -4
- package/build/addLiquidity.js +762 -36
- package/build/addLiquidity.js.map +1 -1
- package/build/bisect.d.ts +11 -0
- package/build/bisect.js +22 -12
- 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 +57 -12
- package/build/liquidityHistogram.js.map +1 -1
- package/build/quote.d.ts +1 -1
- package/build/quote.js +70 -84
- package/build/quote.js.map +1 -1
- package/build/swap.js +36 -18
- package/build/swap.js.map +1 -1
- package/build/swapV2.d.ts +6 -1
- package/build/swapV2.js +394 -52
- 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 +37 -19
- package/build/utils.js.map +1 -1
- package/build/utilsV2.d.ts +9 -0
- package/build/utilsV2.js +131 -9
- package/build/utilsV2.js.map +1 -1
- package/build/withdrawLiquidity.js +12 -7
- package/build/withdrawLiquidity.js.map +1 -1
- package/build/ytTrades.d.ts +7 -0
- package/build/ytTrades.js +166 -146
- package/build/ytTrades.js.map +1 -1
- package/build/ytTradesLegacy.js +3 -4
- package/build/ytTradesLegacy.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/build/swapLegacy.d.ts +0 -16
- package/build/swapLegacy.js +0 -229
- package/build/swapLegacy.js.map +0 -1
- package/src/swapLegacy.ts +0 -272
package/src/bisect.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binary search (bisection method) to find root of a monotonic function.
|
|
3
|
+
* Finds x where f(x) = 0 within the range [lo, hi].
|
|
4
|
+
*
|
|
5
|
+
* @param func - Monotonic function to find root of
|
|
6
|
+
* @param lo - Lower bound of search range
|
|
7
|
+
* @param hi - Upper bound of search range
|
|
8
|
+
* @param epsilon - Convergence tolerance (default 0.0001)
|
|
9
|
+
* @param maxIterations - Maximum iterations (default 2000)
|
|
10
|
+
* @returns The x value where f(x) ≈ 0, or null if root is not bracketed
|
|
11
|
+
*/
|
|
1
12
|
export function bisectSearch2(
|
|
2
13
|
func: (x: number) => number,
|
|
3
14
|
lo: number,
|
|
@@ -9,13 +20,12 @@ export function bisectSearch2(
|
|
|
9
20
|
throw new Error("lo must be less than hi")
|
|
10
21
|
}
|
|
11
22
|
|
|
12
|
-
let iterations = 0
|
|
13
|
-
|
|
14
23
|
// Evaluate boundaries once
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
let fLo = func(lo)
|
|
25
|
+
let fHi = func(hi)
|
|
17
26
|
|
|
18
27
|
// Check if the target is bracketed between lo and hi
|
|
28
|
+
// Root exists only if f(lo) and f(hi) have opposite signs
|
|
19
29
|
if (fLo * fHi > 0) {
|
|
20
30
|
return null
|
|
21
31
|
}
|
|
@@ -28,6 +38,8 @@ export function bisectSearch2(
|
|
|
28
38
|
let bestMid = (lo + hi) / 2
|
|
29
39
|
let bestError = Infinity
|
|
30
40
|
|
|
41
|
+
let iterations = 0
|
|
42
|
+
|
|
31
43
|
// Binary search within the range
|
|
32
44
|
while (iterations < maxIterations) {
|
|
33
45
|
// Use midpoint for standard bisection
|
|
@@ -48,22 +60,21 @@ export function bisectSearch2(
|
|
|
48
60
|
return mid
|
|
49
61
|
}
|
|
50
62
|
|
|
51
|
-
// Early termination if range is too small
|
|
63
|
+
// Early termination if range is too small
|
|
52
64
|
const rangeSize = hi - lo
|
|
53
65
|
if (rangeSize < epsilon * 0.01) {
|
|
54
66
|
return bestMid
|
|
55
67
|
}
|
|
56
68
|
|
|
57
|
-
// Adjust the search range based on
|
|
58
|
-
//
|
|
69
|
+
// Adjust the search range based on signs
|
|
70
|
+
// If f(lo) and f(mid) have opposite signs, root is in [lo, mid]
|
|
71
|
+
// Otherwise, root is in [mid, hi]
|
|
59
72
|
if (fLo * fMid < 0) {
|
|
60
|
-
// eslint-disable-next-line no-param-reassign
|
|
61
73
|
hi = mid
|
|
62
|
-
|
|
74
|
+
fHi = fMid
|
|
63
75
|
} else {
|
|
64
|
-
// eslint-disable-next-line no-param-reassign
|
|
65
76
|
lo = mid
|
|
66
|
-
|
|
77
|
+
fLo = fMid
|
|
67
78
|
}
|
|
68
79
|
}
|
|
69
80
|
|
package/src/index.ts
CHANGED
|
@@ -15,10 +15,7 @@ export * from "./types"
|
|
|
15
15
|
|
|
16
16
|
// Export utility functions
|
|
17
17
|
export {
|
|
18
|
-
EffSnap,
|
|
19
18
|
normalizedTimeRemaining,
|
|
20
|
-
calculateFeeRate,
|
|
21
|
-
getFeeFromAmount,
|
|
22
19
|
getActiveLiquidity,
|
|
23
20
|
getSuccessorTickKey,
|
|
24
21
|
getPredecessorTickKey,
|
|
@@ -27,13 +24,16 @@ export {
|
|
|
27
24
|
convertApyToApyBp,
|
|
28
25
|
convertApyBpToApy,
|
|
29
26
|
calcPtPriceInAsset,
|
|
30
|
-
|
|
27
|
+
getSuccessorTickIdxByIdx,
|
|
31
28
|
} from "./utils"
|
|
32
29
|
|
|
33
30
|
// Export swap functions
|
|
34
|
-
export { simulateSwap } from "./
|
|
31
|
+
export { simulateSwap } from "./swapV2"
|
|
35
32
|
export { getSwapQuote, QuoteDirection } from "./quote"
|
|
36
33
|
|
|
34
|
+
// Export V2 utilities
|
|
35
|
+
export { EffSnap, TicksWrapper, calculateFeeRate, getFeeFromAmount } from "./utilsV2"
|
|
36
|
+
|
|
37
37
|
// Export YT trade functions
|
|
38
38
|
export { simulateBuyYt, simulateSellYt, simulateBuyYtWithSyIn } from "./ytTrades"
|
|
39
39
|
export type {
|
|
@@ -47,10 +47,14 @@ export type {
|
|
|
47
47
|
export {
|
|
48
48
|
simulateAddLiquidity,
|
|
49
49
|
computeLiquidityTargetAndTokenNeeds,
|
|
50
|
+
computeTokenNeedsWithCrossing,
|
|
51
|
+
getCrossingTickStateFromTicks,
|
|
52
|
+
scaleCrossingTickInputs,
|
|
50
53
|
calculateLpOut,
|
|
51
54
|
estimateBalancedDeposit,
|
|
52
55
|
calcDepositSyAndPtFromBaseAmount,
|
|
53
56
|
simulateWrapperProvideLiquidity,
|
|
57
|
+
simulateSwapAndSupply,
|
|
54
58
|
} from "./addLiquidity"
|
|
55
59
|
|
|
56
60
|
export { getPtAndSyOnWithdrawLiquidity } from "./withdrawLiquidity"
|
|
@@ -69,6 +73,8 @@ export type {
|
|
|
69
73
|
AddLiquidityArgs,
|
|
70
74
|
AddLiquidityOutcome,
|
|
71
75
|
LiquidityNeeds,
|
|
76
|
+
CrossingTickState,
|
|
77
|
+
CrossingScaleParams,
|
|
72
78
|
MarketThreeState,
|
|
73
79
|
} from "./types"
|
|
74
80
|
export { SwapDirection } from "./types"
|
|
@@ -25,6 +25,10 @@ export interface LiquidityHistogramBin {
|
|
|
25
25
|
* from remove_liquidity.rs, where liquidity is distributed proportionally based on
|
|
26
26
|
* the spot price ranges.
|
|
27
27
|
*
|
|
28
|
+
* When currentTickBp is provided, PT is only assigned to bins below current price
|
|
29
|
+
* and SY is only assigned to bins above current price. This ensures the visual
|
|
30
|
+
* representation correctly shows the boundary between PT and SY zones.
|
|
31
|
+
*
|
|
28
32
|
* Example:
|
|
29
33
|
* - Created ticks at: 1%, 5%, 6%
|
|
30
34
|
* - tickSpace: 1% (100 basis points)
|
|
@@ -39,9 +43,10 @@ export interface LiquidityHistogramBin {
|
|
|
39
43
|
*
|
|
40
44
|
* @param ticks - The market's ticks state
|
|
41
45
|
* @param tickSpace - The tick spacing in basis points (e.g., 100 for 1%)
|
|
46
|
+
* @param currentTickBp - Optional current tick in basis points for proper PT/SY zone splitting
|
|
42
47
|
* @returns Array of histogram bins aligned to tickSpace
|
|
43
48
|
*/
|
|
44
|
-
export function buildLiquidityHistogram(ticks: Ticks, tickSpace: number): LiquidityHistogramBin[] {
|
|
49
|
+
export function buildLiquidityHistogram(ticks: Ticks, tickSpace: number, currentTickBp?: number): LiquidityHistogramBin[] {
|
|
45
50
|
const histogram: LiquidityHistogramBin[] = []
|
|
46
51
|
|
|
47
52
|
// Edge case: no ticks created yet
|
|
@@ -49,8 +54,15 @@ export function buildLiquidityHistogram(ticks: Ticks, tickSpace: number): Liquid
|
|
|
49
54
|
return histogram
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
//
|
|
53
|
-
const
|
|
57
|
+
// Only consider ticks with actual liquidity when determining range
|
|
58
|
+
const ticksWithLiquidity = ticks.ticksTree.filter(t => t.principalPt > 0 || t.principalSy > 0)
|
|
59
|
+
|
|
60
|
+
if (ticksWithLiquidity.length === 0) {
|
|
61
|
+
return histogram
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Find min and max tick keys (apyBasePoints) from ticks with liquidity
|
|
65
|
+
const tickKeys = ticksWithLiquidity.map((t) => t.apyBasePoints).sort((a, b) => a - b)
|
|
54
66
|
const minTickKey = tickKeys[0]
|
|
55
67
|
const maxTickKey = tickKeys[tickKeys.length - 1]
|
|
56
68
|
|
|
@@ -102,8 +114,19 @@ export function buildLiquidityHistogram(ticks: Ticks, tickSpace: number): Liquid
|
|
|
102
114
|
const containingBinKey = Math.floor(intervalStartKey / tickSpace) * tickSpace
|
|
103
115
|
const bin = binMap.get(containingBinKey)
|
|
104
116
|
if (bin) {
|
|
105
|
-
|
|
106
|
-
|
|
117
|
+
// If currentTickBp is provided, only assign PT to bins below current price
|
|
118
|
+
// and SY to bins above current price
|
|
119
|
+
if (currentTickBp !== undefined) {
|
|
120
|
+
if (containingBinKey < currentTickBp) {
|
|
121
|
+
bin.principalPt += Number(intervalPrincipalPt)
|
|
122
|
+
}
|
|
123
|
+
if (containingBinKey >= currentTickBp) {
|
|
124
|
+
bin.principalSy += Number(intervalPrincipalSy)
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
bin.principalPt += Number(intervalPrincipalPt)
|
|
128
|
+
bin.principalSy += Number(intervalPrincipalSy)
|
|
129
|
+
}
|
|
107
130
|
}
|
|
108
131
|
} else {
|
|
109
132
|
// Distribute proportionally across bins
|
|
@@ -119,8 +142,19 @@ export function buildLiquidityHistogram(ticks: Ticks, tickSpace: number): Liquid
|
|
|
119
142
|
// Proportional share of liquidity for this bin
|
|
120
143
|
const share = binRange / fullRange
|
|
121
144
|
|
|
122
|
-
|
|
123
|
-
|
|
145
|
+
// If currentTickBp is provided, only assign PT to bins below current price
|
|
146
|
+
// and SY to bins above current price
|
|
147
|
+
if (currentTickBp !== undefined) {
|
|
148
|
+
if (binKey < currentTickBp) {
|
|
149
|
+
bin.principalPt += Math.floor(Number(intervalPrincipalPt) * share)
|
|
150
|
+
}
|
|
151
|
+
if (binKey >= currentTickBp) {
|
|
152
|
+
bin.principalSy += Math.floor(Number(intervalPrincipalSy) * share)
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
bin.principalPt += Math.floor(Number(intervalPrincipalPt) * share)
|
|
156
|
+
bin.principalSy += Math.floor(Number(intervalPrincipalSy) * share)
|
|
157
|
+
}
|
|
124
158
|
}
|
|
125
159
|
}
|
|
126
160
|
} else {
|
|
@@ -128,8 +162,19 @@ export function buildLiquidityHistogram(ticks: Ticks, tickSpace: number): Liquid
|
|
|
128
162
|
const containingBinKey = Math.floor(currentTickKey / tickSpace) * tickSpace
|
|
129
163
|
const bin = binMap.get(containingBinKey)
|
|
130
164
|
if (bin) {
|
|
131
|
-
|
|
132
|
-
|
|
165
|
+
// If currentTickBp is provided, only assign PT to bins below current price
|
|
166
|
+
// and SY to bins above current price
|
|
167
|
+
if (currentTickBp !== undefined) {
|
|
168
|
+
if (containingBinKey < currentTickBp) {
|
|
169
|
+
bin.principalPt += Number(intervalPrincipalPt)
|
|
170
|
+
}
|
|
171
|
+
if (containingBinKey >= currentTickBp) {
|
|
172
|
+
bin.principalSy += Number(intervalPrincipalSy)
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
bin.principalPt += Number(intervalPrincipalPt)
|
|
176
|
+
bin.principalSy += Number(intervalPrincipalSy)
|
|
177
|
+
}
|
|
133
178
|
}
|
|
134
179
|
}
|
|
135
180
|
}
|
package/src/quote.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { simulateSwap } from "./swap"
|
|
4
|
-
import { MarketThreeState, SwapDirection, SwapOutcome } from "./types"
|
|
1
|
+
import { simulateSwap } from "./swapV2"
|
|
2
|
+
import { MarketThreeState, SwapDirection } from "./types"
|
|
5
3
|
import { simulateBuyYtWithSyIn, simulateSellYt } from "./ytTrades"
|
|
6
4
|
|
|
7
5
|
export enum QuoteDirection {
|
|
@@ -20,109 +18,89 @@ export type Quote = {
|
|
|
20
18
|
|
|
21
19
|
/**
|
|
22
20
|
* Calculate the expected output for a given input amount
|
|
23
|
-
* This is a convenience wrapper around
|
|
21
|
+
* This is a convenience wrapper around simulateSwapV2
|
|
24
22
|
*/
|
|
25
23
|
export function getSwapQuote(marketState: MarketThreeState, amountIn: number, direction: QuoteDirection): Quote {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
ticks: {
|
|
33
|
-
...marketState.ticks,
|
|
34
|
-
currentTick: currentTickIndex,
|
|
35
|
-
},
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (direction === QuoteDirection.PtToSy) {
|
|
39
|
-
const { amountInConsumed, amountOut, lpFeeChargedOutToken, protocolFeeChargedOutToken } = simulateSwap(
|
|
40
|
-
marketStateMutated,
|
|
41
|
-
{
|
|
42
|
-
direction: SwapDirection.PtToSy,
|
|
43
|
-
amountIn,
|
|
44
|
-
syExchangeRate: marketStateMutated.currentSyExchangeRate,
|
|
45
|
-
isCurrentFlashSwap: false,
|
|
46
|
-
},
|
|
47
|
-
)
|
|
48
|
-
return {
|
|
49
|
-
amountIn: amountInConsumed,
|
|
50
|
-
amountOut: amountOut,
|
|
51
|
-
lpFee: lpFeeChargedOutToken,
|
|
52
|
-
protocolFee: protocolFeeChargedOutToken,
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
if (direction === QuoteDirection.SyToPt) {
|
|
56
|
-
const { amountInConsumed, amountOut, lpFeeChargedOutToken, protocolFeeChargedOutToken } = simulateSwap(
|
|
57
|
-
marketStateMutated,
|
|
58
|
-
{
|
|
59
|
-
direction: SwapDirection.SyToPt,
|
|
60
|
-
amountIn,
|
|
61
|
-
syExchangeRate: marketStateMutated.currentSyExchangeRate,
|
|
62
|
-
isCurrentFlashSwap: false,
|
|
24
|
+
try {
|
|
25
|
+
const marketStateMutated: MarketThreeState = {
|
|
26
|
+
...marketState,
|
|
27
|
+
ticks: {
|
|
28
|
+
...marketState.ticks,
|
|
29
|
+
currentTick: marketState.ticks.currentTick,
|
|
63
30
|
},
|
|
64
|
-
)
|
|
65
|
-
return {
|
|
66
|
-
amountIn: amountInConsumed,
|
|
67
|
-
amountOut: amountOut,
|
|
68
|
-
lpFee: lpFeeChargedOutToken,
|
|
69
|
-
protocolFee: protocolFeeChargedOutToken,
|
|
70
31
|
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (direction === QuoteDirection.YtToSy) {
|
|
74
|
-
const { ytIn, netSyReceived, lpFee, protocolFee } = simulateSellYt(marketStateMutated, {
|
|
75
|
-
ytIn: amountIn,
|
|
76
|
-
syExchangeRate: marketStateMutated.currentSyExchangeRate,
|
|
77
|
-
})
|
|
78
32
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
33
|
+
if (direction === QuoteDirection.PtToSy) {
|
|
34
|
+
const { amountInConsumed, amountOut, lpFeeChargedOutToken, protocolFeeChargedOutToken } = simulateSwap(
|
|
35
|
+
marketStateMutated,
|
|
36
|
+
{
|
|
37
|
+
direction: SwapDirection.PtToSy,
|
|
38
|
+
amountIn,
|
|
39
|
+
syExchangeRate: marketStateMutated.currentSyExchangeRate,
|
|
40
|
+
isCurrentFlashSwap: false,
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
return {
|
|
44
|
+
amountIn: amountInConsumed,
|
|
45
|
+
amountOut: amountOut,
|
|
46
|
+
lpFee: lpFeeChargedOutToken,
|
|
47
|
+
protocolFee: protocolFeeChargedOutToken,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (direction === QuoteDirection.SyToPt) {
|
|
51
|
+
const { amountInConsumed, amountOut, lpFeeChargedOutToken, protocolFeeChargedOutToken } = simulateSwap(
|
|
52
|
+
marketStateMutated,
|
|
53
|
+
{
|
|
54
|
+
direction: SwapDirection.SyToPt,
|
|
55
|
+
amountIn,
|
|
56
|
+
syExchangeRate: marketStateMutated.currentSyExchangeRate,
|
|
57
|
+
isCurrentFlashSwap: false,
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
return {
|
|
61
|
+
amountIn: amountInConsumed,
|
|
62
|
+
amountOut: amountOut,
|
|
63
|
+
lpFee: lpFeeChargedOutToken,
|
|
64
|
+
protocolFee: protocolFeeChargedOutToken,
|
|
65
|
+
}
|
|
84
66
|
}
|
|
85
|
-
}
|
|
86
67
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
68
|
+
if (direction === QuoteDirection.YtToSy) {
|
|
69
|
+
const { ytIn, netSyReceived, lpFee, protocolFee } = simulateSellYt(marketStateMutated, {
|
|
70
|
+
ytIn: amountIn,
|
|
71
|
+
syExchangeRate: marketStateMutated.currentSyExchangeRate,
|
|
72
|
+
})
|
|
92
73
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
74
|
+
return {
|
|
75
|
+
amountIn: ytIn,
|
|
76
|
+
amountOut: netSyReceived,
|
|
77
|
+
lpFee: lpFee,
|
|
78
|
+
protocolFee: protocolFee,
|
|
79
|
+
}
|
|
98
80
|
}
|
|
99
|
-
}
|
|
100
81
|
|
|
101
|
-
|
|
102
|
-
}
|
|
82
|
+
if (direction === QuoteDirection.SyToYt) {
|
|
83
|
+
const { ytOut, netSyCost, lpFee, protocolFee } = simulateBuyYtWithSyIn(marketStateMutated, {
|
|
84
|
+
syIn: amountIn,
|
|
85
|
+
syExchangeRate: marketStateMutated.currentSyExchangeRate,
|
|
86
|
+
})
|
|
103
87
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
*/
|
|
112
|
-
function calculateCurrentTickIndex(ticksData: Ticks): number {
|
|
113
|
-
// Find the tick with highest impliedRate <= currentSpotPrice
|
|
114
|
-
let currentTickApyBps = ticksData.ticksTree[0]?.apyBasePoints || 0
|
|
88
|
+
return {
|
|
89
|
+
amountIn: netSyCost,
|
|
90
|
+
amountOut: ytOut,
|
|
91
|
+
lpFee: lpFee,
|
|
92
|
+
protocolFee,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
115
95
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
96
|
+
throw new Error(`Unknown quote direction: ${direction}`)
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error("Unable to get swap quote", error)
|
|
99
|
+
return {
|
|
100
|
+
amountIn,
|
|
101
|
+
amountOut: 0,
|
|
102
|
+
lpFee: 0,
|
|
103
|
+
protocolFee: 0,
|
|
120
104
|
}
|
|
121
105
|
}
|
|
122
|
-
|
|
123
|
-
// Find the array index (0-based) of this tick
|
|
124
|
-
const arrayIndex = ticksData.ticksTree.findIndex((t: Tick) => t.apyBasePoints === currentTickApyBps)
|
|
125
|
-
|
|
126
|
-
// Convert to 1-based index for simulateSwap
|
|
127
|
-
return arrayIndex !== -1 ? arrayIndex + 1 : 1
|
|
128
106
|
}
|
package/src/swap.ts
CHANGED
|
@@ -13,11 +13,10 @@ import {
|
|
|
13
13
|
bigIntMin,
|
|
14
14
|
calculateFeeRate,
|
|
15
15
|
findTickByIndex,
|
|
16
|
-
findTickByKey,
|
|
17
16
|
getFeeFromAmount,
|
|
18
17
|
getImpliedRate,
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
getPredecessorTickIdxByIdx,
|
|
19
|
+
getSuccessorTickIdxByIdx,
|
|
21
20
|
normalizedTimeRemaining,
|
|
22
21
|
} from "./utils"
|
|
23
22
|
|
|
@@ -81,25 +80,25 @@ export function simulateSwap(marketState: MarketThreeState, args: SwapArgs): Swa
|
|
|
81
80
|
iterations++
|
|
82
81
|
if (debug) console.log(`\n--- Iteration ${iterations}, amountInLeft=${amountInLeft} ---`)
|
|
83
82
|
|
|
84
|
-
// Get right boundary of current interval
|
|
85
|
-
const rightBoundaryIndexOpt =
|
|
83
|
+
// Get right boundary of current interval (successor tick index)
|
|
84
|
+
const rightBoundaryIndexOpt = getSuccessorTickIdxByIdx(ticks, currentLeftBoundaryIndex)
|
|
86
85
|
if (debug) console.log(`rightBoundary=${rightBoundaryIndexOpt}`)
|
|
87
86
|
|
|
88
87
|
if (rightBoundaryIndexOpt === null) {
|
|
89
88
|
if (args.direction === SwapDirection.SyToPt) {
|
|
90
89
|
// Cross to create a new interval
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
90
|
+
const predecessorIdx = getPredecessorTickIdxByIdx(ticks, currentLeftBoundaryIndex)
|
|
91
|
+
if (predecessorIdx === null) break
|
|
93
92
|
|
|
94
93
|
// Update active liquidity by subtracting liquidity_net at boundary
|
|
95
|
-
const boundaryTick =
|
|
94
|
+
const boundaryTick = findTickByIndex(ticks, currentLeftBoundaryIndex)
|
|
96
95
|
|
|
97
96
|
// When crossing downward (SyToPt), update state
|
|
98
|
-
currentPriceSpot = getImpliedRate(
|
|
99
|
-
currentLeftBoundaryIndex =
|
|
97
|
+
currentPriceSpot = getImpliedRate(boundaryTick?.apyBasePoints ?? 0) // Boundary we're crossing
|
|
98
|
+
currentLeftBoundaryIndex = predecessorIdx // New left boundary
|
|
100
99
|
|
|
101
100
|
if (boundaryTick) {
|
|
102
|
-
activeLiquidityU64 = bigIntMax(0n, activeLiquidityU64 - boundaryTick.
|
|
101
|
+
activeLiquidityU64 = bigIntMax(0n, activeLiquidityU64 - boundaryTick.liquidityNet)
|
|
103
102
|
activeLiquidityF64 = Number(activeLiquidityU64)
|
|
104
103
|
}
|
|
105
104
|
continue
|
|
@@ -167,7 +166,16 @@ export function simulateSwap(marketState: MarketThreeState, args: SwapArgs): Swa
|
|
|
167
166
|
const syOutGrossClamped: number = Math.min(syOutGross, Number(principalSy))
|
|
168
167
|
|
|
169
168
|
if (syOutGrossClamped > 0) {
|
|
170
|
-
|
|
169
|
+
// For flash swaps, fee is based on YT value = (pt - sy × sy_exchange_rate) / sy_exchange_rate (in SY terms)
|
|
170
|
+
let totalFeeOut: number
|
|
171
|
+
if (args.isCurrentFlashSwap) {
|
|
172
|
+
const syOutBase = syOutGrossClamped * args.syExchangeRate
|
|
173
|
+
const ytValueBase = ptInSegment - syOutBase
|
|
174
|
+
const ytValueSy = ytValueBase / args.syExchangeRate
|
|
175
|
+
totalFeeOut = getFeeFromAmount(Math.max(0, Math.floor(ytValueSy)), lpFeeRate)
|
|
176
|
+
} else {
|
|
177
|
+
totalFeeOut = getFeeFromAmount(syOutGrossClamped, lpFeeRate)
|
|
178
|
+
}
|
|
171
179
|
const protocolFeeOut: number = Math.floor((totalFeeOut * protocolFeeBps) / BASE_POINTS)
|
|
172
180
|
const lpFeeOut: number = totalFeeOut - protocolFeeOut
|
|
173
181
|
const syOutNet: number = syOutGrossClamped - totalFeeOut
|
|
@@ -204,7 +212,7 @@ export function simulateSwap(marketState: MarketThreeState, args: SwapArgs): Swa
|
|
|
204
212
|
|
|
205
213
|
if (deltaCToLeftBoundary <= eps) {
|
|
206
214
|
// Cross boundary to the left
|
|
207
|
-
const predecessor =
|
|
215
|
+
const predecessor = getPredecessorTickIdxByIdx(ticks, currentLeftBoundaryIndex)
|
|
208
216
|
if (predecessor === null) break
|
|
209
217
|
|
|
210
218
|
// Update active liquidity by subtracting liquidity_net at boundary
|
|
@@ -214,7 +222,7 @@ export function simulateSwap(marketState: MarketThreeState, args: SwapArgs): Swa
|
|
|
214
222
|
activeLiquidityF64 = Number(activeLiquidityU64)
|
|
215
223
|
}
|
|
216
224
|
|
|
217
|
-
currentPriceSpot = getImpliedRate(
|
|
225
|
+
currentPriceSpot = getImpliedRate(boundaryTick?.apyBasePoints ?? 0)
|
|
218
226
|
currentLeftBoundaryIndex = predecessor
|
|
219
227
|
continue
|
|
220
228
|
}
|
|
@@ -240,7 +248,7 @@ export function simulateSwap(marketState: MarketThreeState, args: SwapArgs): Swa
|
|
|
240
248
|
|
|
241
249
|
if (ptOutGrossU64 === 0n) {
|
|
242
250
|
// Nothing to pay out; try to cross
|
|
243
|
-
const predecessor =
|
|
251
|
+
const predecessor = getPredecessorTickIdxByIdx(ticks, currentLeftBoundaryIndex)
|
|
244
252
|
if (predecessor === null) break
|
|
245
253
|
|
|
246
254
|
// Update active liquidity
|
|
@@ -250,13 +258,21 @@ export function simulateSwap(marketState: MarketThreeState, args: SwapArgs): Swa
|
|
|
250
258
|
activeLiquidityF64 = Number(activeLiquidityU64)
|
|
251
259
|
}
|
|
252
260
|
|
|
253
|
-
currentPriceSpot = getImpliedRate(
|
|
261
|
+
currentPriceSpot = getImpliedRate(boundaryTick?.apyBasePoints ?? 0)
|
|
254
262
|
currentLeftBoundaryIndex = predecessor
|
|
255
263
|
continue
|
|
256
264
|
}
|
|
257
265
|
|
|
258
266
|
// Fees in token_out (PT)
|
|
259
|
-
|
|
267
|
+
// For flash swaps, fee is based on YT value = pt - sy × sy_exchange_rate
|
|
268
|
+
let totalFeeOut: number
|
|
269
|
+
if (args.isCurrentFlashSwap) {
|
|
270
|
+
const syInBase = Number(syInSegmentU64) * args.syExchangeRate
|
|
271
|
+
const ytValue = Number(ptOutGrossU64) - syInBase
|
|
272
|
+
totalFeeOut = getFeeFromAmount(Math.max(0, Math.floor(ytValue)), lpFeeRate)
|
|
273
|
+
} else {
|
|
274
|
+
totalFeeOut = getFeeFromAmount(Number(ptOutGrossU64), lpFeeRate)
|
|
275
|
+
}
|
|
260
276
|
const protocolFeeOut: number = Math.floor((totalFeeOut * protocolFeeBps) / BASE_POINTS)
|
|
261
277
|
const lpFeeOut: number = totalFeeOut - protocolFeeOut
|
|
262
278
|
const ptOutNet: number = Number(ptOutGrossU64) - totalFeeOut
|
|
@@ -272,7 +288,7 @@ export function simulateSwap(marketState: MarketThreeState, args: SwapArgs): Swa
|
|
|
272
288
|
|
|
273
289
|
// If we hit boundary, cross
|
|
274
290
|
if (Math.abs(currentPriceSpot - anchorULeft) <= eps && amountInLeft > 0) {
|
|
275
|
-
const predecessor =
|
|
291
|
+
const predecessor = getPredecessorTickIdxByIdx(ticks, currentLeftBoundaryIndex)
|
|
276
292
|
if (predecessor === null) break
|
|
277
293
|
|
|
278
294
|
// Update active liquidity
|
|
@@ -282,7 +298,7 @@ export function simulateSwap(marketState: MarketThreeState, args: SwapArgs): Swa
|
|
|
282
298
|
activeLiquidityF64 = Number(activeLiquidityU64)
|
|
283
299
|
}
|
|
284
300
|
|
|
285
|
-
currentPriceSpot = getImpliedRate(
|
|
301
|
+
currentPriceSpot = getImpliedRate(boundaryTick?.apyBasePoints ?? 0)
|
|
286
302
|
currentLeftBoundaryIndex = predecessor
|
|
287
303
|
}
|
|
288
304
|
}
|