@exponent-labs/market-three-math 0.1.8
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/CHANGELOG.md +8 -0
- package/README.md +197 -0
- package/build/addLiquidity.d.ts +67 -0
- package/build/addLiquidity.js +269 -0
- package/build/addLiquidity.js.map +1 -0
- package/build/bisect.d.ts +1 -0
- package/build/bisect.js +62 -0
- package/build/bisect.js.map +1 -0
- package/build/index.d.ts +24 -0
- package/build/index.js +76 -0
- package/build/index.js.map +1 -0
- package/build/liquidityHistogram.d.ts +50 -0
- package/build/liquidityHistogram.js +162 -0
- package/build/liquidityHistogram.js.map +1 -0
- package/build/quote.d.ts +18 -0
- package/build/quote.js +106 -0
- package/build/quote.js.map +1 -0
- package/build/swap-v2.d.ts +20 -0
- package/build/swap-v2.js +261 -0
- package/build/swap-v2.js.map +1 -0
- package/build/swap.d.ts +15 -0
- package/build/swap.js +249 -0
- package/build/swap.js.map +1 -0
- package/build/swapLegacy.d.ts +16 -0
- package/build/swapLegacy.js +229 -0
- package/build/swapLegacy.js.map +1 -0
- package/build/swapV2.d.ts +11 -0
- package/build/swapV2.js +406 -0
- package/build/swapV2.js.map +1 -0
- package/build/types.d.ts +73 -0
- package/build/types.js +9 -0
- package/build/types.js.map +1 -0
- package/build/utils.d.ts +119 -0
- package/build/utils.js +219 -0
- package/build/utils.js.map +1 -0
- package/build/utilsV2.d.ts +88 -0
- package/build/utilsV2.js +180 -0
- package/build/utilsV2.js.map +1 -0
- package/build/withdrawLiquidity.d.ts +8 -0
- package/build/withdrawLiquidity.js +174 -0
- package/build/withdrawLiquidity.js.map +1 -0
- package/build/ytTrades.d.ts +106 -0
- package/build/ytTrades.js +292 -0
- package/build/ytTrades.js.map +1 -0
- package/build/ytTradesLegacy.d.ts +106 -0
- package/build/ytTradesLegacy.js +292 -0
- package/build/ytTradesLegacy.js.map +1 -0
- package/examples/.env.example +1 -0
- package/examples/test-histogram-simple.ts +172 -0
- package/examples/test-histogram.ts +112 -0
- package/package.json +26 -0
- package/src/addLiquidity.ts +384 -0
- package/src/bisect.ts +72 -0
- package/src/index.ts +74 -0
- package/src/liquidityHistogram.ts +192 -0
- package/src/quote.ts +128 -0
- package/src/swap.ts +299 -0
- package/src/swapLegacy.ts +272 -0
- package/src/types.ts +80 -0
- package/src/utils.ts +235 -0
- package/src/withdrawLiquidity.ts +240 -0
- package/src/ytTrades.ts +419 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSwapQuote = exports.simulateSwap = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* CLMM Swap simulation
|
|
6
|
+
* Ported from exponent_clmm/src/state/market_three/helpers/swap.rs
|
|
7
|
+
*/
|
|
8
|
+
const types_1 = require("./types");
|
|
9
|
+
const utils_1 = require("./utils");
|
|
10
|
+
const BASE_POINTS = 10000;
|
|
11
|
+
/**
|
|
12
|
+
* Simulate a swap on the CLMM market
|
|
13
|
+
* This is a pure function that does not mutate the market state
|
|
14
|
+
* Returns the swap outcome including amounts and final state
|
|
15
|
+
*/
|
|
16
|
+
function simulateSwap(marketState, args) {
|
|
17
|
+
const { financials, configurationOptions, ticks } = marketState;
|
|
18
|
+
const secondsRemaining = Math.max(0, Number(financials.expirationTs) - Date.now() / 1000);
|
|
19
|
+
// Create effective price snapshot
|
|
20
|
+
const snapshot = new utils_1.EffSnap((0, utils_1.normalizedTimeRemaining)(secondsRemaining), args.syExchangeRate);
|
|
21
|
+
// Current state
|
|
22
|
+
let currentPriceSpot = ticks.currentSpotPrice;
|
|
23
|
+
let currentLeftBoundaryIndex = ticks.currentTick;
|
|
24
|
+
// Use currentPrefixSum if available, otherwise fall back to calculating it
|
|
25
|
+
let activeLiquidityU64 = ticks.currentPrefixSum ?? 0n;
|
|
26
|
+
let activeLiquidityF64 = Number(activeLiquidityU64);
|
|
27
|
+
// Fees
|
|
28
|
+
const lpFeeRate = (0, utils_1.calculateFeeRate)(configurationOptions.lnFeeRateRoot, secondsRemaining);
|
|
29
|
+
const protocolFeeBps = configurationOptions.treasuryFeeBps;
|
|
30
|
+
// Check price limits
|
|
31
|
+
if (args.priceSpotLimit !== undefined) {
|
|
32
|
+
if (args.direction === types_1.SwapDirection.PtToSy) {
|
|
33
|
+
if (args.priceSpotLimit < currentPriceSpot) {
|
|
34
|
+
throw new Error("Price limit violated: limit must be >= current price for PtToSy");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
if (args.priceSpotLimit > currentPriceSpot) {
|
|
39
|
+
throw new Error("Price limit violated: limit must be <= current price for SyToPt");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Accumulators
|
|
44
|
+
let amountOutNet = 0;
|
|
45
|
+
let feeLpOut = 0;
|
|
46
|
+
let feeProtocolOut = 0;
|
|
47
|
+
let amountInLeft = args.amountIn;
|
|
48
|
+
// Main loop across contiguous intervals
|
|
49
|
+
let iterations = 0;
|
|
50
|
+
const maxIterations = 1000;
|
|
51
|
+
while (amountInLeft > 0 && iterations < maxIterations) {
|
|
52
|
+
iterations++;
|
|
53
|
+
// Get right boundary of current interval
|
|
54
|
+
const rightBoundaryIndexOpt = (0, utils_1.getSuccessorTickKey)(ticks, currentLeftBoundaryIndex);
|
|
55
|
+
if (rightBoundaryIndexOpt === null) {
|
|
56
|
+
if (args.direction === types_1.SwapDirection.SyToPt) {
|
|
57
|
+
// Cross to create a new interval
|
|
58
|
+
const predecessor = (0, utils_1.getPredecessorTickKey)(ticks, currentLeftBoundaryIndex);
|
|
59
|
+
if (predecessor === null)
|
|
60
|
+
break;
|
|
61
|
+
// When crossing downward (SyToPt), update state
|
|
62
|
+
currentPriceSpot = (0, utils_1.getImpliedRate)(currentLeftBoundaryIndex); // Boundary we're crossing
|
|
63
|
+
currentLeftBoundaryIndex = predecessor; // New left boundary
|
|
64
|
+
// Update active liquidity by subtracting liquidity_net at boundary
|
|
65
|
+
const boundaryTick = (0, utils_1.findTickByKey)(ticks, predecessor);
|
|
66
|
+
if (boundaryTick) {
|
|
67
|
+
activeLiquidityU64 = (0, utils_1.bigIntMax)(0n, activeLiquidityU64 - boundaryTick.tick.liquidityNet);
|
|
68
|
+
activeLiquidityF64 = Number(activeLiquidityU64);
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// No more liquidity available
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const rightBoundaryIndex = rightBoundaryIndexOpt;
|
|
78
|
+
// Get anchor prices for interval boundaries
|
|
79
|
+
const anchorULeft = (0, utils_1.getImpliedRate)(currentLeftBoundaryIndex);
|
|
80
|
+
const anchorURight = (0, utils_1.getImpliedRate)(rightBoundaryIndex);
|
|
81
|
+
// Effective price at current spot
|
|
82
|
+
const cEffOld = snapshot.getEffectivePrice(currentPriceSpot);
|
|
83
|
+
// Get principal ledgers for the interval
|
|
84
|
+
const currentTickData = (0, utils_1.findTickByKey)(ticks, currentLeftBoundaryIndex);
|
|
85
|
+
const principalPt = currentTickData?.tick.principalPt ?? 0n;
|
|
86
|
+
const principalSy = currentTickData?.tick.principalSy ?? 0n;
|
|
87
|
+
const eps = configurationOptions.epsilonClamp;
|
|
88
|
+
// Calculate kappa (scaling factor based on available principal)
|
|
89
|
+
// Y_max = (L/τ) * (C(u_old) - C(u_right))
|
|
90
|
+
const cEffAtBoundary = snapshot.getEffectivePrice(anchorURight);
|
|
91
|
+
const yMaxToBoundaryF = (Number(activeLiquidityF64) / snapshot.timeFactor) * (cEffOld - cEffAtBoundary);
|
|
92
|
+
const kappaSy = yMaxToBoundaryF > 0 ? Number(principalSy) / Number(yMaxToBoundaryF) : 0;
|
|
93
|
+
const duToLeft = currentPriceSpot - anchorULeft;
|
|
94
|
+
const ptMaxToLeftF = Number(activeLiquidityF64) * duToLeft;
|
|
95
|
+
const kappaPt = ptMaxToLeftF > 0 ? Number(principalPt) / ptMaxToLeftF : 0;
|
|
96
|
+
const kappa = Math.min(kappaPt, kappaSy, 1.0);
|
|
97
|
+
const lTradeF64 = Number(activeLiquidityF64) * kappa;
|
|
98
|
+
if (args.direction === types_1.SwapDirection.PtToSy) {
|
|
99
|
+
// PT -> SY swap (buying SY with PT)
|
|
100
|
+
const duByInput = lTradeF64 > 0 ? amountInLeft / lTradeF64 : 0;
|
|
101
|
+
const duToBoundary = anchorURight - currentPriceSpot;
|
|
102
|
+
const duActual = Math.min(duByInput, duToBoundary);
|
|
103
|
+
if (duToBoundary <= eps) {
|
|
104
|
+
// Cross boundary
|
|
105
|
+
const boundaryTick = (0, utils_1.findTickByKey)(ticks, rightBoundaryIndex);
|
|
106
|
+
if (boundaryTick) {
|
|
107
|
+
activeLiquidityU64 += boundaryTick.tick.liquidityNet;
|
|
108
|
+
activeLiquidityF64 = Number(activeLiquidityU64);
|
|
109
|
+
}
|
|
110
|
+
currentLeftBoundaryIndex = rightBoundaryIndex;
|
|
111
|
+
currentPriceSpot = anchorURight;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
// Token flows for this segment
|
|
115
|
+
const ptInSegment = Math.floor(lTradeF64 * duActual);
|
|
116
|
+
const anchorUNew = currentPriceSpot + duActual;
|
|
117
|
+
const cEffNew = snapshot.getEffectivePrice(anchorUNew);
|
|
118
|
+
const syOutGross = Math.floor((lTradeF64 / snapshot.timeFactor) * (cEffOld - cEffNew));
|
|
119
|
+
const syOutGrossClamped = Math.min(syOutGross, Number(principalSy));
|
|
120
|
+
if (syOutGrossClamped > 0) {
|
|
121
|
+
const totalFeeOut = (0, utils_1.getFeeFromAmount)(syOutGrossClamped, lpFeeRate);
|
|
122
|
+
const protocolFeeOut = Math.floor((totalFeeOut * protocolFeeBps) / BASE_POINTS);
|
|
123
|
+
const lpFeeOut = totalFeeOut - protocolFeeOut;
|
|
124
|
+
const syOutNet = syOutGrossClamped - totalFeeOut;
|
|
125
|
+
amountOutNet += syOutNet;
|
|
126
|
+
feeLpOut += lpFeeOut;
|
|
127
|
+
feeProtocolOut += protocolFeeOut;
|
|
128
|
+
}
|
|
129
|
+
amountInLeft -= ptInSegment;
|
|
130
|
+
currentPriceSpot = anchorUNew;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// SY -> PT swap (buying PT with SY)
|
|
134
|
+
const cEffLeft = snapshot.getEffectivePrice(anchorULeft);
|
|
135
|
+
const deltaCByInput = lTradeF64 > 0 ? (snapshot.timeFactor / lTradeF64) * amountInLeft : 0;
|
|
136
|
+
const deltaCToLeftBoundary = Math.max(0, cEffLeft - cEffOld);
|
|
137
|
+
const deltaCActual = Math.min(deltaCByInput, deltaCToLeftBoundary);
|
|
138
|
+
if (deltaCToLeftBoundary <= eps) {
|
|
139
|
+
// Cross boundary to the left
|
|
140
|
+
const predecessor = (0, utils_1.getPredecessorTickKey)(ticks, currentLeftBoundaryIndex);
|
|
141
|
+
if (predecessor === null)
|
|
142
|
+
break;
|
|
143
|
+
// Update active liquidity
|
|
144
|
+
const boundaryTick = (0, utils_1.findTickByKey)(ticks, currentLeftBoundaryIndex);
|
|
145
|
+
if (boundaryTick) {
|
|
146
|
+
activeLiquidityU64 = (0, utils_1.bigIntMax)(0n, activeLiquidityU64 - boundaryTick.tick.liquidityNet);
|
|
147
|
+
activeLiquidityF64 = Number(activeLiquidityU64);
|
|
148
|
+
}
|
|
149
|
+
currentPriceSpot = (0, utils_1.getImpliedRate)(currentLeftBoundaryIndex);
|
|
150
|
+
currentLeftBoundaryIndex = predecessor;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
// New effective price and spot price after consuming ΔC
|
|
154
|
+
const cEffNew = cEffOld + deltaCActual;
|
|
155
|
+
const spotPriceNew = snapshot.spotPriceFromEffectivePrice(cEffNew);
|
|
156
|
+
// Token flows
|
|
157
|
+
const syInSegmentF = (lTradeF64 / snapshot.timeFactor) * (cEffNew - cEffOld);
|
|
158
|
+
const duAbs = currentPriceSpot - spotPriceNew;
|
|
159
|
+
const ptOutGrossF = lTradeF64 * duAbs;
|
|
160
|
+
// Clamp gross PT by available principal
|
|
161
|
+
const ptOutGrossU64 = (0, utils_1.bigIntMin)(BigInt(Math.floor(ptOutGrossF)), principalPt);
|
|
162
|
+
const syInSegmentU64 = BigInt(Math.floor(syInSegmentF));
|
|
163
|
+
if (ptOutGrossU64 === 0n) {
|
|
164
|
+
// Nothing to pay out; try to cross
|
|
165
|
+
const predecessor = (0, utils_1.getPredecessorTickKey)(ticks, currentLeftBoundaryIndex);
|
|
166
|
+
if (predecessor === null)
|
|
167
|
+
break;
|
|
168
|
+
// Update active liquidity
|
|
169
|
+
const boundaryTick = (0, utils_1.findTickByKey)(ticks, currentLeftBoundaryIndex);
|
|
170
|
+
if (boundaryTick) {
|
|
171
|
+
activeLiquidityU64 = (0, utils_1.bigIntMax)(0n, activeLiquidityU64 - boundaryTick.tick.liquidityNet);
|
|
172
|
+
activeLiquidityF64 = Number(activeLiquidityU64);
|
|
173
|
+
}
|
|
174
|
+
currentPriceSpot = (0, utils_1.getImpliedRate)(currentLeftBoundaryIndex);
|
|
175
|
+
currentLeftBoundaryIndex = predecessor;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
// Fees in token_out (PT)
|
|
179
|
+
const totalFeeOut = (0, utils_1.getFeeFromAmount)(Number(ptOutGrossU64), lpFeeRate);
|
|
180
|
+
const protocolFeeOut = Math.floor((totalFeeOut * protocolFeeBps) / BASE_POINTS);
|
|
181
|
+
const lpFeeOut = totalFeeOut - protocolFeeOut;
|
|
182
|
+
const ptOutNet = Number(ptOutGrossU64) - totalFeeOut;
|
|
183
|
+
// Accumulate to user
|
|
184
|
+
amountOutNet += ptOutNet;
|
|
185
|
+
feeLpOut += lpFeeOut;
|
|
186
|
+
feeProtocolOut += protocolFeeOut;
|
|
187
|
+
// Consume input and advance state
|
|
188
|
+
amountInLeft -= Number(syInSegmentU64);
|
|
189
|
+
currentPriceSpot = spotPriceNew;
|
|
190
|
+
// If we hit boundary, cross
|
|
191
|
+
if (Math.abs(currentPriceSpot - anchorULeft) <= eps && amountInLeft > 0) {
|
|
192
|
+
const predecessor = (0, utils_1.getPredecessorTickKey)(ticks, currentLeftBoundaryIndex);
|
|
193
|
+
if (predecessor === null)
|
|
194
|
+
break;
|
|
195
|
+
// Update active liquidity
|
|
196
|
+
const boundaryTick = (0, utils_1.findTickByKey)(ticks, currentLeftBoundaryIndex);
|
|
197
|
+
if (boundaryTick) {
|
|
198
|
+
activeLiquidityU64 = (0, utils_1.bigIntMax)(0n, activeLiquidityU64 - boundaryTick.tick.liquidityNet);
|
|
199
|
+
activeLiquidityF64 = Number(activeLiquidityU64);
|
|
200
|
+
}
|
|
201
|
+
currentPriceSpot = (0, utils_1.getImpliedRate)(currentLeftBoundaryIndex);
|
|
202
|
+
currentLeftBoundaryIndex = predecessor;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
amountInConsumed: args.amountIn - amountInLeft,
|
|
208
|
+
amountOut: amountOutNet,
|
|
209
|
+
lpFeeChargedOutToken: feeLpOut,
|
|
210
|
+
protocolFeeChargedOutToken: feeProtocolOut,
|
|
211
|
+
finalSpotPrice: currentPriceSpot,
|
|
212
|
+
finalTickIndex: currentLeftBoundaryIndex,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
exports.simulateSwap = simulateSwap;
|
|
216
|
+
/**
|
|
217
|
+
* Calculate the expected output for a given input amount
|
|
218
|
+
* This is a convenience wrapper around simulateSwap
|
|
219
|
+
*/
|
|
220
|
+
function getSwapQuote(marketState, amountIn, direction) {
|
|
221
|
+
return simulateSwap(marketState, {
|
|
222
|
+
direction,
|
|
223
|
+
amountIn,
|
|
224
|
+
syExchangeRate: marketState.currentSyExchangeRate,
|
|
225
|
+
isCurrentFlashSwap: false,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
exports.getSwapQuote = getSwapQuote;
|
|
229
|
+
//# sourceMappingURL=swapLegacy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swapLegacy.js","sourceRoot":"","sources":["../src/swapLegacy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLMM Swap simulation V2
|
|
3
|
+
* Closely mirrors the Rust on-chain implementation from swap.rs
|
|
4
|
+
* Uses TicksWrapper to simulate RB-tree behavior
|
|
5
|
+
*/
|
|
6
|
+
import { MarketThreeState, SwapArgs, SwapOutcomeV2 } from "./types";
|
|
7
|
+
/**
|
|
8
|
+
* Simulate a swap on the CLMM market (V2 - mirrors Rust closely)
|
|
9
|
+
* This is a pure function that does not mutate the market state
|
|
10
|
+
*/
|
|
11
|
+
export declare function simulateSwap(marketState: MarketThreeState, args: SwapArgs): SwapOutcomeV2;
|
package/build/swapV2.js
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.simulateSwap = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* CLMM Swap simulation V2
|
|
6
|
+
* Closely mirrors the Rust on-chain implementation from swap.rs
|
|
7
|
+
* Uses TicksWrapper to simulate RB-tree behavior
|
|
8
|
+
*/
|
|
9
|
+
const types_1 = require("./types");
|
|
10
|
+
const utilsV2_1 = require("./utilsV2");
|
|
11
|
+
const BASE_POINTS = 10000;
|
|
12
|
+
/**
|
|
13
|
+
* Simulate a swap on the CLMM market (V2 - mirrors Rust closely)
|
|
14
|
+
* This is a pure function that does not mutate the market state
|
|
15
|
+
*/
|
|
16
|
+
function simulateSwap(marketState, args) {
|
|
17
|
+
const DEBUG = false; // Set to true for debugging
|
|
18
|
+
const { financials, configurationOptions, ticks } = marketState;
|
|
19
|
+
const secondsRemaining = Math.max(0, Number(financials.expirationTs) - Date.now() / 1000);
|
|
20
|
+
// 0) Snapshot of effective price parameters
|
|
21
|
+
const snapshot = new utilsV2_1.EffSnap((0, utilsV2_1.normalizedTimeRemaining)(secondsRemaining), args.syExchangeRate);
|
|
22
|
+
// Wrap ticks for RB-tree-like operations
|
|
23
|
+
const ticksWrapper = new utilsV2_1.TicksWrapper(ticks);
|
|
24
|
+
// Current state in both spot and anchor coordinates
|
|
25
|
+
let currentPriceSpot = ticksWrapper.currentSpotPrice;
|
|
26
|
+
let currentLeftBoundaryKey = ticksWrapper.currentTickKey;
|
|
27
|
+
// Active liquidity at current interval (prefix-sum at left boundary)
|
|
28
|
+
let activeLiquidityU64 = ticksWrapper.currentPrefixSum;
|
|
29
|
+
let activeLiquidityF64 = Number(activeLiquidityU64);
|
|
30
|
+
// Fees
|
|
31
|
+
const lpFeeRate = (0, utilsV2_1.calculateFeeRate)(configurationOptions.lnFeeRateRoot, secondsRemaining);
|
|
32
|
+
const protocolFeeBps = configurationOptions.treasuryFeeBps;
|
|
33
|
+
const eps = configurationOptions.epsilonClamp;
|
|
34
|
+
// Limits (optional spot)
|
|
35
|
+
if (args.priceSpotLimit !== undefined) {
|
|
36
|
+
if (args.direction === types_1.SwapDirection.PtToSy) {
|
|
37
|
+
if (args.priceSpotLimit < currentPriceSpot) {
|
|
38
|
+
throw new Error("Price limit violated: limit must be >= current price for PtToSy");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
if (args.priceSpotLimit > currentPriceSpot) {
|
|
43
|
+
throw new Error("Price limit violated: limit must be <= current price for SyToPt");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Accumulators
|
|
48
|
+
let amountOutNetU64 = 0;
|
|
49
|
+
let feeLpOutU64 = 0;
|
|
50
|
+
let feeProtocolOutU64 = 0;
|
|
51
|
+
let amountInLeft = args.amountIn;
|
|
52
|
+
if (DEBUG) {
|
|
53
|
+
console.log(`\nSwapV2 Debug: direction=${args.direction}, amountIn=${args.amountIn}`);
|
|
54
|
+
console.log(`Initial: currentTickKey=${currentLeftBoundaryKey}, spotPrice=${currentPriceSpot}, activeLiq=${activeLiquidityU64}`);
|
|
55
|
+
}
|
|
56
|
+
// Main loop across contiguous intervals while we have input left
|
|
57
|
+
// Rust: while amount_in_left > 1
|
|
58
|
+
let iterations = 0;
|
|
59
|
+
const MAX_ITERATIONS = 30;
|
|
60
|
+
while (amountInLeft > 1 && iterations < MAX_ITERATIONS) {
|
|
61
|
+
iterations++;
|
|
62
|
+
if (DEBUG) {
|
|
63
|
+
console.log(`\n--- Iteration ${iterations}, amountInLeft=${amountInLeft} ---`);
|
|
64
|
+
}
|
|
65
|
+
// Right boundary of current interval (must exist to have a half-open [left, right))
|
|
66
|
+
const rightBoundaryKeyOpt = ticksWrapper.successorKey(currentLeftBoundaryKey);
|
|
67
|
+
if (DEBUG) {
|
|
68
|
+
console.log(`rightBoundaryKey=${rightBoundaryKeyOpt}`);
|
|
69
|
+
}
|
|
70
|
+
// Handle case when no right boundary exists
|
|
71
|
+
if (rightBoundaryKeyOpt === null) {
|
|
72
|
+
if (args.direction === types_1.SwapDirection.SyToPt) {
|
|
73
|
+
// Cross boundary to the left (no right boundary means we need to go left)
|
|
74
|
+
const crossed = crossOneBoundary(ticksWrapper, types_1.SwapDirection.SyToPt, currentLeftBoundaryKey, {
|
|
75
|
+
currentLeftBoundaryKey,
|
|
76
|
+
currentPriceSpot,
|
|
77
|
+
activeLiquidityU64,
|
|
78
|
+
activeLiquidityF64,
|
|
79
|
+
});
|
|
80
|
+
if (!crossed)
|
|
81
|
+
break;
|
|
82
|
+
currentLeftBoundaryKey = crossed.currentLeftBoundaryKey;
|
|
83
|
+
currentPriceSpot = crossed.currentPriceSpot;
|
|
84
|
+
activeLiquidityU64 = crossed.activeLiquidityU64;
|
|
85
|
+
activeLiquidityF64 = crossed.activeLiquidityF64;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const rightBoundaryKey = rightBoundaryKeyOpt;
|
|
93
|
+
// Interval anchors (spot prices at boundaries)
|
|
94
|
+
const anchorULeft = ticksWrapper.getSpotPrice(currentLeftBoundaryKey);
|
|
95
|
+
const anchorURight = ticksWrapper.getSpotPrice(rightBoundaryKey);
|
|
96
|
+
// Effective price at current spot
|
|
97
|
+
const cEffOld = snapshot.getEffectivePrice(currentPriceSpot);
|
|
98
|
+
// Load principal ledgers for the interval (stored on the left boundary)
|
|
99
|
+
const { principalPt, principalSy } = ticksWrapper.getPrincipals(currentLeftBoundaryKey);
|
|
100
|
+
// Y_max = L * (C(u_old) - C(u_right))
|
|
101
|
+
const cEffAtBoundary = snapshot.getEffectivePrice(anchorURight);
|
|
102
|
+
const yMaxToBoundaryF = activeLiquidityF64 * (cEffOld - cEffAtBoundary);
|
|
103
|
+
// PT_max_left = L * (u_old - u_left)
|
|
104
|
+
const duToLeft = currentPriceSpot - anchorULeft;
|
|
105
|
+
const ptMaxToLeftF = activeLiquidityF64 * duToLeft;
|
|
106
|
+
// Validate y_max and pt_max values before using them
|
|
107
|
+
if (!Number.isFinite(yMaxToBoundaryF) ||
|
|
108
|
+
!Number.isFinite(ptMaxToLeftF) ||
|
|
109
|
+
yMaxToBoundaryF < 0 ||
|
|
110
|
+
ptMaxToLeftF < 0) {
|
|
111
|
+
const crossed = crossOneBoundary(ticksWrapper, args.direction, args.direction === types_1.SwapDirection.PtToSy ? rightBoundaryKey : currentLeftBoundaryKey, {
|
|
112
|
+
currentLeftBoundaryKey,
|
|
113
|
+
currentPriceSpot,
|
|
114
|
+
activeLiquidityU64,
|
|
115
|
+
activeLiquidityF64,
|
|
116
|
+
});
|
|
117
|
+
if (!crossed)
|
|
118
|
+
break;
|
|
119
|
+
currentLeftBoundaryKey = crossed.currentLeftBoundaryKey;
|
|
120
|
+
currentPriceSpot = crossed.currentPriceSpot;
|
|
121
|
+
activeLiquidityU64 = crossed.activeLiquidityU64;
|
|
122
|
+
activeLiquidityF64 = crossed.activeLiquidityF64;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
// kappa_sy = principal_sy / y_max (with proper edge case handling)
|
|
126
|
+
// Matches Rust logic: if max <= eps and principal == 0, constraint is not binding (1.0)
|
|
127
|
+
let kappaSy;
|
|
128
|
+
if (yMaxToBoundaryF > eps) {
|
|
129
|
+
kappaSy = Number(principalSy) / yMaxToBoundaryF;
|
|
130
|
+
}
|
|
131
|
+
else if (principalSy === 0n) {
|
|
132
|
+
kappaSy = 1.0; // No constraint since nothing to provide
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
kappaSy = Infinity; // Has principal but can't use it
|
|
136
|
+
}
|
|
137
|
+
// kappa_pt = principal_pt / pt_max_left (with proper edge case handling)
|
|
138
|
+
// Matches Rust logic: if max <= eps and principal == 0, constraint is not binding (1.0)
|
|
139
|
+
let kappaPt;
|
|
140
|
+
if (ptMaxToLeftF > eps) {
|
|
141
|
+
kappaPt = Number(principalPt) / ptMaxToLeftF;
|
|
142
|
+
}
|
|
143
|
+
else if (principalPt === 0n) {
|
|
144
|
+
kappaPt = 1.0; // No constraint since nothing to provide
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
kappaPt = Infinity; // Has principal but can't use it
|
|
148
|
+
}
|
|
149
|
+
// kappa = min(kappa_pt, kappa_sy, 1.0) -- MUST be clamped to 1.0
|
|
150
|
+
const kappa = Math.min(kappaPt, kappaSy, 1.0);
|
|
151
|
+
if (DEBUG) {
|
|
152
|
+
console.log(` anchorULeft=${anchorULeft}, anchorURight=${anchorURight}`);
|
|
153
|
+
console.log(` principalPt=${principalPt}, principalSy=${principalSy}`);
|
|
154
|
+
console.log(` kappaPt=${kappaPt}, kappaSy=${kappaSy}, kappa=${kappa}`);
|
|
155
|
+
}
|
|
156
|
+
// Check kappa validity - if invalid or zero, cross boundary
|
|
157
|
+
if (!Number.isFinite(kappa) || kappa <= 0) {
|
|
158
|
+
const crossed = crossOneBoundary(ticksWrapper, args.direction, args.direction === types_1.SwapDirection.PtToSy ? rightBoundaryKey : currentLeftBoundaryKey, {
|
|
159
|
+
currentLeftBoundaryKey,
|
|
160
|
+
currentPriceSpot,
|
|
161
|
+
activeLiquidityU64,
|
|
162
|
+
activeLiquidityF64,
|
|
163
|
+
});
|
|
164
|
+
if (!crossed)
|
|
165
|
+
break;
|
|
166
|
+
currentLeftBoundaryKey = crossed.currentLeftBoundaryKey;
|
|
167
|
+
currentPriceSpot = crossed.currentPriceSpot;
|
|
168
|
+
activeLiquidityU64 = crossed.activeLiquidityU64;
|
|
169
|
+
activeLiquidityF64 = crossed.activeLiquidityF64;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const lTradeF64 = activeLiquidityF64 * kappa;
|
|
173
|
+
// Check l_trade validity
|
|
174
|
+
if (!Number.isFinite(lTradeF64) || lTradeF64 <= eps) {
|
|
175
|
+
const crossed = crossOneBoundary(ticksWrapper, args.direction, args.direction === types_1.SwapDirection.PtToSy ? rightBoundaryKey : currentLeftBoundaryKey, {
|
|
176
|
+
currentLeftBoundaryKey,
|
|
177
|
+
currentPriceSpot,
|
|
178
|
+
activeLiquidityU64,
|
|
179
|
+
activeLiquidityF64,
|
|
180
|
+
});
|
|
181
|
+
if (!crossed)
|
|
182
|
+
break;
|
|
183
|
+
currentLeftBoundaryKey = crossed.currentLeftBoundaryKey;
|
|
184
|
+
currentPriceSpot = crossed.currentPriceSpot;
|
|
185
|
+
activeLiquidityU64 = crossed.activeLiquidityU64;
|
|
186
|
+
activeLiquidityF64 = crossed.activeLiquidityF64;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (args.direction === types_1.SwapDirection.PtToSy) {
|
|
190
|
+
// PT -> SY swap
|
|
191
|
+
// Final du in this interval
|
|
192
|
+
const duByInput = amountInLeft / lTradeF64;
|
|
193
|
+
const duToBoundary = anchorURight - currentPriceSpot;
|
|
194
|
+
const duActual = Math.min(duByInput, duToBoundary);
|
|
195
|
+
if (DEBUG) {
|
|
196
|
+
console.log(` PtToSy: duByInput=${duByInput}, duToBoundary=${duToBoundary}, duActual=${duActual}`);
|
|
197
|
+
console.log(` lTradeF64=${lTradeF64}, kappa=${kappa}`);
|
|
198
|
+
}
|
|
199
|
+
// Check if we need to cross boundary first (at boundary)
|
|
200
|
+
if (duToBoundary <= eps) {
|
|
201
|
+
// Cross boundary to unlock the next interval
|
|
202
|
+
const crossed = crossOneBoundary(ticksWrapper, types_1.SwapDirection.PtToSy, rightBoundaryKey, {
|
|
203
|
+
currentLeftBoundaryKey,
|
|
204
|
+
currentPriceSpot,
|
|
205
|
+
activeLiquidityU64,
|
|
206
|
+
activeLiquidityF64,
|
|
207
|
+
});
|
|
208
|
+
if (!crossed)
|
|
209
|
+
break;
|
|
210
|
+
currentLeftBoundaryKey = crossed.currentLeftBoundaryKey;
|
|
211
|
+
currentPriceSpot = crossed.currentPriceSpot;
|
|
212
|
+
activeLiquidityU64 = crossed.activeLiquidityU64;
|
|
213
|
+
activeLiquidityF64 = crossed.activeLiquidityF64;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
// Token flows for this segment (ceil for input to protect pool)
|
|
217
|
+
const ptInSegment = Math.ceil(lTradeF64 * duActual);
|
|
218
|
+
const anchorUNew = currentPriceSpot + duActual;
|
|
219
|
+
const cEffNew = snapshot.getEffectivePrice(anchorUNew);
|
|
220
|
+
const syOutGrossF = lTradeF64 * (cEffOld - cEffNew);
|
|
221
|
+
// Clamp by SY principal
|
|
222
|
+
let syOutGrossU64 = Math.floor(syOutGrossF);
|
|
223
|
+
syOutGrossU64 = Math.min(syOutGrossU64, Number(principalSy));
|
|
224
|
+
if (DEBUG) {
|
|
225
|
+
console.log(` ptInSegment=${ptInSegment}, syOutGrossU64=${syOutGrossU64}`);
|
|
226
|
+
}
|
|
227
|
+
// Fees in token_out (SY)
|
|
228
|
+
// For flash swaps, fee is based on YT value = (pt - sy × sy_exchange_rate) / sy_exchange_rate (in SY terms)
|
|
229
|
+
if (syOutGrossU64 > 0) {
|
|
230
|
+
let totalFeeOut;
|
|
231
|
+
if (args.isCurrentFlashSwap) {
|
|
232
|
+
const syOutBase = syOutGrossU64 * args.syExchangeRate;
|
|
233
|
+
const ytValueBase = ptInSegment - syOutBase;
|
|
234
|
+
const ytValueSy = ytValueBase / args.syExchangeRate;
|
|
235
|
+
totalFeeOut = (0, utilsV2_1.getFeeFromAmount)(Math.max(0, Math.floor(ytValueSy)), lpFeeRate);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
totalFeeOut = (0, utilsV2_1.getFeeFromAmount)(syOutGrossU64, lpFeeRate);
|
|
239
|
+
}
|
|
240
|
+
const protocolFeeOut = Math.floor((totalFeeOut * protocolFeeBps) / BASE_POINTS);
|
|
241
|
+
const lpFeeOut = totalFeeOut - protocolFeeOut;
|
|
242
|
+
const syOutNet = syOutGrossU64 - totalFeeOut;
|
|
243
|
+
// Accumulate to user
|
|
244
|
+
amountOutNetU64 += syOutNet;
|
|
245
|
+
feeLpOutU64 += lpFeeOut;
|
|
246
|
+
feeProtocolOutU64 += protocolFeeOut;
|
|
247
|
+
// Consume input and advance state
|
|
248
|
+
amountInLeft -= ptInSegment;
|
|
249
|
+
currentPriceSpot = anchorUNew;
|
|
250
|
+
}
|
|
251
|
+
// If we hit boundary, cross
|
|
252
|
+
if (anchorURight - currentPriceSpot <= eps && amountInLeft > 0) {
|
|
253
|
+
const crossed = crossOneBoundary(ticksWrapper, types_1.SwapDirection.PtToSy, rightBoundaryKey, {
|
|
254
|
+
currentLeftBoundaryKey,
|
|
255
|
+
currentPriceSpot,
|
|
256
|
+
activeLiquidityU64,
|
|
257
|
+
activeLiquidityF64,
|
|
258
|
+
});
|
|
259
|
+
if (!crossed)
|
|
260
|
+
break;
|
|
261
|
+
currentLeftBoundaryKey = crossed.currentLeftBoundaryKey;
|
|
262
|
+
currentPriceSpot = crossed.currentPriceSpot;
|
|
263
|
+
activeLiquidityU64 = crossed.activeLiquidityU64;
|
|
264
|
+
activeLiquidityF64 = crossed.activeLiquidityF64;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
// SY -> PT swap
|
|
269
|
+
const cEffLeft = snapshot.getEffectivePrice(anchorULeft);
|
|
270
|
+
// Final ΔC in this interval
|
|
271
|
+
const deltaCByInput = amountInLeft / lTradeF64;
|
|
272
|
+
const deltaCToLeftBoundary = cEffLeft - cEffOld;
|
|
273
|
+
const deltaCActual = Math.min(deltaCByInput, deltaCToLeftBoundary);
|
|
274
|
+
if (DEBUG) {
|
|
275
|
+
console.log(` SyToPt: deltaCByInput=${deltaCByInput}, deltaCToLeftBoundary=${deltaCToLeftBoundary}, deltaCActual=${deltaCActual}`);
|
|
276
|
+
console.log(` lTradeF64=${lTradeF64}, kappa=${kappa}, eps=${eps}`);
|
|
277
|
+
}
|
|
278
|
+
// Check if we need to cross boundary first (at boundary)
|
|
279
|
+
if (deltaCToLeftBoundary <= eps) {
|
|
280
|
+
// Cross boundary to the left
|
|
281
|
+
const crossed = crossOneBoundary(ticksWrapper, types_1.SwapDirection.SyToPt, currentLeftBoundaryKey, {
|
|
282
|
+
currentLeftBoundaryKey,
|
|
283
|
+
currentPriceSpot,
|
|
284
|
+
activeLiquidityU64,
|
|
285
|
+
activeLiquidityF64,
|
|
286
|
+
});
|
|
287
|
+
if (!crossed)
|
|
288
|
+
break;
|
|
289
|
+
currentLeftBoundaryKey = crossed.currentLeftBoundaryKey;
|
|
290
|
+
currentPriceSpot = crossed.currentPriceSpot;
|
|
291
|
+
activeLiquidityU64 = crossed.activeLiquidityU64;
|
|
292
|
+
activeLiquidityF64 = crossed.activeLiquidityF64;
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
// New effective price and spot after consuming ΔC
|
|
296
|
+
const cEffNew = cEffOld + deltaCActual;
|
|
297
|
+
const spotPriceNew = snapshot.spotPriceFromEffectivePrice(cEffNew);
|
|
298
|
+
// Token flows
|
|
299
|
+
const syInSegmentF = lTradeF64 * (cEffNew - cEffOld);
|
|
300
|
+
const duAbs = currentPriceSpot - spotPriceNew;
|
|
301
|
+
const ptOutGrossF = lTradeF64 * duAbs;
|
|
302
|
+
// Clamp gross PT by available principal
|
|
303
|
+
let ptOutGrossU64 = Math.floor(ptOutGrossF);
|
|
304
|
+
ptOutGrossU64 = Math.min(ptOutGrossU64, Number(principalPt));
|
|
305
|
+
// ceil for input to protect pool
|
|
306
|
+
const syInSegmentU64 = Math.ceil(syInSegmentF);
|
|
307
|
+
if (DEBUG) {
|
|
308
|
+
console.log(` spotPriceNew=${spotPriceNew}, duAbs=${duAbs}`);
|
|
309
|
+
console.log(` ptOutGrossU64=${ptOutGrossU64}, syInSegmentU64=${syInSegmentU64}`);
|
|
310
|
+
}
|
|
311
|
+
if (ptOutGrossU64 > 0) {
|
|
312
|
+
// Fees in token_out (PT)
|
|
313
|
+
// For flash swaps, fee is based on YT value = pt - sy × sy_exchange_rate
|
|
314
|
+
let totalFeeOut;
|
|
315
|
+
if (args.isCurrentFlashSwap) {
|
|
316
|
+
const syInBase = syInSegmentU64 * args.syExchangeRate;
|
|
317
|
+
const ytValue = ptOutGrossU64 - syInBase;
|
|
318
|
+
totalFeeOut = (0, utilsV2_1.getFeeFromAmount)(Math.max(0, Math.floor(ytValue)), lpFeeRate);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
totalFeeOut = (0, utilsV2_1.getFeeFromAmount)(ptOutGrossU64, lpFeeRate);
|
|
322
|
+
}
|
|
323
|
+
const protocolFeeOut = Math.floor((totalFeeOut * protocolFeeBps) / BASE_POINTS);
|
|
324
|
+
const lpFeeOut = totalFeeOut - protocolFeeOut;
|
|
325
|
+
const ptOutNet = ptOutGrossU64 - totalFeeOut;
|
|
326
|
+
// Accumulate to user
|
|
327
|
+
amountOutNetU64 += ptOutNet;
|
|
328
|
+
feeLpOutU64 += lpFeeOut;
|
|
329
|
+
feeProtocolOutU64 += protocolFeeOut;
|
|
330
|
+
}
|
|
331
|
+
// Consume input and advance state
|
|
332
|
+
amountInLeft -= syInSegmentU64;
|
|
333
|
+
currentPriceSpot = spotPriceNew;
|
|
334
|
+
// If we hit boundary, cross
|
|
335
|
+
// Use effective price difference for consistency with pre-swap epsilon check
|
|
336
|
+
if (Math.abs(cEffNew - cEffLeft) <= eps && amountInLeft > 0) {
|
|
337
|
+
const crossed = crossOneBoundary(ticksWrapper, types_1.SwapDirection.SyToPt, currentLeftBoundaryKey, {
|
|
338
|
+
currentLeftBoundaryKey,
|
|
339
|
+
currentPriceSpot,
|
|
340
|
+
activeLiquidityU64,
|
|
341
|
+
activeLiquidityF64,
|
|
342
|
+
});
|
|
343
|
+
if (!crossed)
|
|
344
|
+
break;
|
|
345
|
+
currentLeftBoundaryKey = crossed.currentLeftBoundaryKey;
|
|
346
|
+
currentPriceSpot = crossed.currentPriceSpot;
|
|
347
|
+
activeLiquidityU64 = crossed.activeLiquidityU64;
|
|
348
|
+
activeLiquidityF64 = crossed.activeLiquidityF64;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
amountInConsumed: args.amountIn - amountInLeft,
|
|
354
|
+
amountOut: amountOutNetU64,
|
|
355
|
+
lpFeeChargedOutToken: feeLpOutU64,
|
|
356
|
+
protocolFeeChargedOutToken: feeProtocolOutU64,
|
|
357
|
+
finalSpotPrice: currentPriceSpot,
|
|
358
|
+
finalTickKey: currentLeftBoundaryKey, // This is now the key, not array index
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
exports.simulateSwap = simulateSwap;
|
|
362
|
+
/**
|
|
363
|
+
* Cross one boundary and update core state
|
|
364
|
+
* Mirrors Rust's cross_one_boundary_and_update_core
|
|
365
|
+
*/
|
|
366
|
+
function crossOneBoundary(ticksWrapper, direction, boundaryKeyToCross, state) {
|
|
367
|
+
// Get liquidity_net at the boundary we're crossing
|
|
368
|
+
const deltaNetI64 = ticksWrapper.getLiquidityNet(boundaryKeyToCross);
|
|
369
|
+
const lOldI64 = state.activeLiquidityU64;
|
|
370
|
+
// Update active liquidity
|
|
371
|
+
let lNewI64;
|
|
372
|
+
if (direction === types_1.SwapDirection.PtToSy) {
|
|
373
|
+
// Crossing upward boundary: add liquidity_net
|
|
374
|
+
lNewI64 = lOldI64 + deltaNetI64;
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
// Crossing downward boundary: subtract liquidity_net
|
|
378
|
+
lNewI64 = lOldI64 - deltaNetI64;
|
|
379
|
+
}
|
|
380
|
+
// Ensure non-negative
|
|
381
|
+
if (lNewI64 < 0n) {
|
|
382
|
+
lNewI64 = 0n;
|
|
383
|
+
}
|
|
384
|
+
// Move spot to the boundary we cross
|
|
385
|
+
const boundarySpot = ticksWrapper.getSpotPrice(boundaryKeyToCross);
|
|
386
|
+
// Advance current left boundary key
|
|
387
|
+
let newLeftBoundaryKey;
|
|
388
|
+
if (direction === types_1.SwapDirection.PtToSy) {
|
|
389
|
+
// Moving right: new left boundary is the crossed boundary
|
|
390
|
+
newLeftBoundaryKey = boundaryKeyToCross;
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
// Moving left: new left boundary is predecessor of current
|
|
394
|
+
newLeftBoundaryKey = ticksWrapper.predecessorKey(state.currentLeftBoundaryKey);
|
|
395
|
+
}
|
|
396
|
+
if (newLeftBoundaryKey === null) {
|
|
397
|
+
return null; // End of range
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
currentLeftBoundaryKey: newLeftBoundaryKey,
|
|
401
|
+
currentPriceSpot: boundarySpot,
|
|
402
|
+
activeLiquidityU64: lNewI64,
|
|
403
|
+
activeLiquidityF64: Number(lNewI64),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
//# sourceMappingURL=swapV2.js.map
|