@exponent-labs/market-three-math 0.9.16 → 0.9.18
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 +4 -0
- package/build/addLiquidity.d.ts +11 -41
- package/build/addLiquidity.js +161 -192
- package/build/addLiquidity.js.map +1 -1
- package/build/existingPositionEqualization.d.ts +3 -3
- package/build/existingPositionEqualization.js +42 -62
- package/build/existingPositionEqualization.js.map +1 -1
- package/build/index.d.ts +2 -2
- package/build/index.js +1 -3
- package/build/index.js.map +1 -1
- package/build/swap.js +1 -0
- package/build/swap.js.map +1 -1
- package/build/swapV2.js +54 -17
- package/build/swapV2.js.map +1 -1
- package/build/types.d.ts +4 -21
- package/build/utilsV2.js +4 -3
- package/build/utilsV2.js.map +1 -1
- package/build/withdrawLiquidity.d.ts +1 -1
- package/build/withdrawLiquidity.js +119 -70
- package/build/withdrawLiquidity.js.map +1 -1
- package/package.json +2 -2
- package/src/addLiquidity.ts +203 -246
- package/src/existingPositionEqualization.test.ts +29 -0
- package/src/existingPositionEqualization.ts +52 -83
- package/src/index.ts +0 -4
- package/src/swap.ts +1 -0
- package/src/swapV2.ts +96 -18
- package/src/types.ts +4 -23
- package/src/utilsV2.ts +7 -4
- package/src/withdrawLiquidity.test.ts +189 -0
- package/src/withdrawLiquidity.ts +148 -89
package/CHANGELOG.md
CHANGED
package/build/addLiquidity.d.ts
CHANGED
|
@@ -2,46 +2,19 @@
|
|
|
2
2
|
* CLMM Add Liquidity simulation
|
|
3
3
|
* Ported from exponent_clmm/src/state/market_three/helpers/add_liquidity.rs
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import { AddLiquidityArgs, AddLiquidityOutcome,
|
|
5
|
+
import type { LpPositionCLMM } from "@exponent-labs/exponent-fetcher";
|
|
6
|
+
import { AddLiquidityArgs, AddLiquidityOutcome, LiquidityNeeds, MarketThreeState } from "./types";
|
|
7
7
|
import { EffSnap } from "./utilsV2";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* Matches wrapper_provide_liquidity.rs: current node's spot_price, principal_pt/sy/supply, successor's spot_price.
|
|
11
|
-
*/
|
|
12
|
-
export declare function getCrossingTickStateFromTicks(ticks: Ticks): {
|
|
13
|
-
crossingTickState: CrossingTickState;
|
|
14
|
-
crossingTickPriceLeft: number;
|
|
15
|
-
crossingTickPriceRight: number;
|
|
8
|
+
type AddLiquiditySimulationOptions = {
|
|
9
|
+
existingPosition?: LpPositionCLMM;
|
|
16
10
|
};
|
|
17
11
|
/**
|
|
18
12
|
* Compute liquidity target and token needs based on position range and budgets
|
|
19
13
|
* Ported from compute_liquidity_target_and_token_needs in math.rs
|
|
20
14
|
*/
|
|
21
15
|
export declare function computeLiquidityTargetAndTokenNeeds(snap: EffSnap, spotPriceCurrent: number, priceEffLower: number, priceEffUpper: number, lowerPrice: number, upperPrice: number, lowerTickIdx: number, upperTickIdx: number, currentIndex: number, maxSy: number, maxPt: number, epsilonClamp: number): LiquidityNeeds;
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
*
|
|
25
|
-
* When adding liquidity that spans the current price (crossing tick), tokens get trimmed twice:
|
|
26
|
-
* 1. First by CLMM formula in computeLiquidityTargetAndTokenNeeds
|
|
27
|
-
* 2. Then by AMM formula in simulateAddLiquidityProportional
|
|
28
|
-
*
|
|
29
|
-
* This function calculates the segment's proportion of the total distribution and scales
|
|
30
|
-
* the original max values accordingly, ensuring maximum token utilization.
|
|
31
|
-
*
|
|
32
|
-
* @param scaleParams - Original max values and CLMM distribution extents
|
|
33
|
-
* @param duPtPart - PT segment length (spot price delta for this segment)
|
|
34
|
-
* @param syPerL1 - SY per unit liquidity for this segment
|
|
35
|
-
* @param clmmPtDelta - PT amount calculated from CLMM formula
|
|
36
|
-
* @param clmmSyDelta - SY amount calculated from CLMM formula
|
|
37
|
-
* @returns Tuple of [scaledPtIn, scaledSyIn] - maximum of CLMM-calculated and scaled original values
|
|
38
|
-
*/
|
|
39
|
-
export declare function scaleCrossingTickInputs(scaleParams: CrossingScaleParams, duPtPart: number, syPerL1: number, clmmPtDelta: number, clmmSyDelta: number): [number, number];
|
|
40
|
-
/**
|
|
41
|
-
* Compute token needs with crossing tick adjustment
|
|
42
|
-
* This matches the Rust compute_token_needs_with_crossing function
|
|
43
|
-
*/
|
|
44
|
-
export declare function computeTokenNeedsWithCrossing(snap: EffSnap, spotPriceCurrent: number, priceEffLower: number, priceEffUpper: number, lowerPrice: number, upperPrice: number, maxSy: number, maxPt: number, epsilonClamp: number, crossingTickState: CrossingTickState, crossingTickPriceLeft: number, crossingTickPriceRight: number): [number, number];
|
|
16
|
+
/** Compute curve-level token needs without raw crossing-tick cap scaling. */
|
|
17
|
+
export declare function computeTokenNeedsWithCrossing(snap: EffSnap, spotPriceCurrent: number, priceEffLower: number, priceEffUpper: number, lowerPrice: number, upperPrice: number, maxSy: number, maxPt: number, epsilonClamp: number): [number, number];
|
|
45
18
|
/**
|
|
46
19
|
* Simulate adding liquidity to the CLMM market
|
|
47
20
|
* This is a pure function that does not mutate the market state
|
|
@@ -69,12 +42,6 @@ export declare function calcDepositSyAndPtFromBaseAmount(params: {
|
|
|
69
42
|
expirationTs: number;
|
|
70
43
|
currentSpotPrice: number;
|
|
71
44
|
syExchangeRate: number;
|
|
72
|
-
/** Optional crossing tick state for accurate ratio prediction.
|
|
73
|
-
* When provided, the prediction accounts for the existing PT/SY proportions
|
|
74
|
-
* in the active tick, matching the on-chain behaviour more closely. */
|
|
75
|
-
crossingTickState?: CrossingTickState;
|
|
76
|
-
crossingTickPriceLeft?: number;
|
|
77
|
-
crossingTickPriceRight?: number;
|
|
78
45
|
epsilonClamp?: number;
|
|
79
46
|
}): {
|
|
80
47
|
syNeeded: number;
|
|
@@ -94,9 +61,10 @@ export declare function calcDepositSyAndPtFromBaseAmount(params: {
|
|
|
94
61
|
* @param lowerTick - Lower tick (APY in basis points)
|
|
95
62
|
* @param upperTick - Upper tick (APY in basis points)
|
|
96
63
|
* @param syExchangeRate - SY exchange rate
|
|
64
|
+
* @param options - Optional existing LP position to include fixed crossing-split equalization spend
|
|
97
65
|
* @returns Simulation result with LP out, YT out, amounts spent, etc.
|
|
98
66
|
*/
|
|
99
|
-
export declare function simulateWrapperProvideLiquidity(marketState: MarketThreeState, amountBase: number, lowerTick: number, upperTick: number, syExchangeRate: number): {
|
|
67
|
+
export declare function simulateWrapperProvideLiquidity(marketState: MarketThreeState, amountBase: number, lowerTick: number, upperTick: number, syExchangeRate: number, options?: AddLiquiditySimulationOptions): {
|
|
100
68
|
lpOut: number;
|
|
101
69
|
ytOut: number;
|
|
102
70
|
syToStrip: number;
|
|
@@ -118,9 +86,10 @@ export declare function simulateWrapperProvideLiquidity(marketState: MarketThree
|
|
|
118
86
|
* @param lowerTick - Lower tick key (APY in basis points)
|
|
119
87
|
* @param upperTick - Upper tick key (APY in basis points)
|
|
120
88
|
* @param syExchangeRate - SY exchange rate
|
|
89
|
+
* @param options - Optional existing LP position to include fixed crossing-split equalization spend
|
|
121
90
|
* @returns Simulation result with LP out, PT to buy, SY constraint, etc.
|
|
122
91
|
*/
|
|
123
|
-
export declare function simulateSwapAndSupply(marketState: MarketThreeState, amountBase: number, lowerTick: number, upperTick: number, syExchangeRate: number): {
|
|
92
|
+
export declare function simulateSwapAndSupply(marketState: MarketThreeState, amountBase: number, lowerTick: number, upperTick: number, syExchangeRate: number, options?: AddLiquiditySimulationOptions): {
|
|
124
93
|
lpOut: number;
|
|
125
94
|
ptToBuy: number;
|
|
126
95
|
syConstraint: number;
|
|
@@ -130,3 +99,4 @@ export declare function simulateSwapAndSupply(marketState: MarketThreeState, amo
|
|
|
130
99
|
syDeposited: number;
|
|
131
100
|
ptDeposited: number;
|
|
132
101
|
} | null;
|
|
102
|
+
export {};
|
package/build/addLiquidity.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getCrossingTickStateFromTicks = getCrossingTickStateFromTicks;
|
|
4
3
|
exports.computeLiquidityTargetAndTokenNeeds = computeLiquidityTargetAndTokenNeeds;
|
|
5
|
-
exports.scaleCrossingTickInputs = scaleCrossingTickInputs;
|
|
6
4
|
exports.computeTokenNeedsWithCrossing = computeTokenNeedsWithCrossing;
|
|
7
5
|
exports.simulateAddLiquidity = simulateAddLiquidity;
|
|
8
6
|
exports.calculateLpOut = calculateLpOut;
|
|
@@ -10,9 +8,9 @@ exports.estimateBalancedDeposit = estimateBalancedDeposit;
|
|
|
10
8
|
exports.calcDepositSyAndPtFromBaseAmount = calcDepositSyAndPtFromBaseAmount;
|
|
11
9
|
exports.simulateWrapperProvideLiquidity = simulateWrapperProvideLiquidity;
|
|
12
10
|
exports.simulateSwapAndSupply = simulateSwapAndSupply;
|
|
11
|
+
const existingPositionEqualization_1 = require("./existingPositionEqualization");
|
|
13
12
|
const swapV2_1 = require("./swapV2");
|
|
14
13
|
const types_1 = require("./types");
|
|
15
|
-
const utils_1 = require("./utils");
|
|
16
14
|
const utilsV2_1 = require("./utilsV2");
|
|
17
15
|
const TICK_KEY_BASE_POINTS = 1_000_000;
|
|
18
16
|
// Keep this in sync with exponent_clmm's add_liquidity helper so off-chain
|
|
@@ -21,44 +19,13 @@ const GAP_TOKEN_NEEDS = 20;
|
|
|
21
19
|
const MIN_SWAP_EXACT_OUT_SY_HEADROOM = 100;
|
|
22
20
|
const SWAP_EXACT_OUT_SY_HEADROOM = 100;
|
|
23
21
|
const SWAP_EXACT_OUT_HEADROOM_PT_THRESHOLD = 10_000_000;
|
|
24
|
-
const MIN_SWAP_AND_SUPPLY_SY_BUDGET = 1_000_000;
|
|
25
22
|
const FLASH_SY_POSITION_HEADROOM = 1_000;
|
|
26
23
|
const MAX_SWAP_AND_SUPPLY_UNDERSPEND_LAMPORTS = 1_000;
|
|
27
24
|
const MAX_SWAP_AND_SUPPLY_SIMULATED_OVERSPEND_LAMPORTS = 0;
|
|
28
25
|
const LARGE_SWAP_AND_SUPPLY_SY_BUDGET = 50_000_000_000;
|
|
29
26
|
const PT_ONLY_SWAP_EXACT_OUT_ESTIMATE_SLACK = 0;
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
principalSy: 0,
|
|
33
|
-
principalShareSupply: 0,
|
|
34
|
-
};
|
|
35
|
-
/**
|
|
36
|
-
* Get crossing tick state and price boundaries from Ticks.
|
|
37
|
-
* Matches wrapper_provide_liquidity.rs: current node's spot_price, principal_pt/sy/supply, successor's spot_price.
|
|
38
|
-
*/
|
|
39
|
-
function getCrossingTickStateFromTicks(ticks) {
|
|
40
|
-
const currentTickNode = ticks.ticksTree[ticks.currentTick - 1];
|
|
41
|
-
if (!currentTickNode || ticks.currentTick <= 0) {
|
|
42
|
-
return {
|
|
43
|
-
crossingTickState: EMPTY_CROSSING_TICK_STATE,
|
|
44
|
-
crossingTickPriceLeft: 0,
|
|
45
|
-
crossingTickPriceRight: 0,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
const crossingTickPriceLeft = currentTickNode.impliedRate;
|
|
49
|
-
const successorKey = (0, utils_1.getSuccessorTickKey)(ticks, currentTickNode.apyBasePoints);
|
|
50
|
-
const successorTick = successorKey != null ? (0, utils_1.findTickByKey)(ticks, successorKey)?.tick : null;
|
|
51
|
-
const crossingTickPriceRight = successorTick?.impliedRate ?? Number.POSITIVE_INFINITY;
|
|
52
|
-
return {
|
|
53
|
-
crossingTickState: {
|
|
54
|
-
principalPt: Number(currentTickNode.principalPt),
|
|
55
|
-
principalSy: Number(currentTickNode.principalSy),
|
|
56
|
-
principalShareSupply: Number(currentTickNode.principalShareSupply),
|
|
57
|
-
},
|
|
58
|
-
crossingTickPriceLeft,
|
|
59
|
-
crossingTickPriceRight,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
27
|
+
const ZERO_BIGINT = 0n;
|
|
28
|
+
const MAX_SAFE_INTEGER_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
|
|
62
29
|
/**
|
|
63
30
|
* Compute liquidity target and token needs based on position range and budgets
|
|
64
31
|
* Ported from compute_liquidity_target_and_token_needs in math.rs
|
|
@@ -78,11 +45,6 @@ function computeLiquidityTargetAndTokenNeeds(snap, spotPriceCurrent, priceEffLow
|
|
|
78
45
|
ptNeeded: 0,
|
|
79
46
|
priceSplitForNeed: lowerPrice,
|
|
80
47
|
priceSplitTickIdx: lowerTickIdx,
|
|
81
|
-
// No crossing possible when below range
|
|
82
|
-
originalMaxSy: maxSy,
|
|
83
|
-
originalMaxPt: maxPt,
|
|
84
|
-
duLeftTotal: 0,
|
|
85
|
-
deltaCRightTotal: deltaCTotal,
|
|
86
48
|
};
|
|
87
49
|
}
|
|
88
50
|
else if (spotPriceCurrent >= upperPrice) {
|
|
@@ -97,11 +59,6 @@ function computeLiquidityTargetAndTokenNeeds(snap, spotPriceCurrent, priceEffLow
|
|
|
97
59
|
ptNeeded: Math.floor(ptNeed),
|
|
98
60
|
priceSplitForNeed: upperPrice,
|
|
99
61
|
priceSplitTickIdx: upperTickIdx,
|
|
100
|
-
// No crossing possible when above range
|
|
101
|
-
originalMaxSy: maxSy,
|
|
102
|
-
originalMaxPt: maxPt,
|
|
103
|
-
duLeftTotal: duTotal,
|
|
104
|
-
deltaCRightTotal: 0,
|
|
105
62
|
};
|
|
106
63
|
}
|
|
107
64
|
else {
|
|
@@ -120,75 +77,29 @@ function computeLiquidityTargetAndTokenNeeds(snap, spotPriceCurrent, priceEffLow
|
|
|
120
77
|
ptNeeded: Math.floor(ptNeed),
|
|
121
78
|
priceSplitForNeed: spotPriceCurrent,
|
|
122
79
|
priceSplitTickIdx: currentIndex,
|
|
123
|
-
// Store for crossing tick scaling
|
|
124
|
-
originalMaxSy: maxSy,
|
|
125
|
-
originalMaxPt: maxPt,
|
|
126
|
-
duLeftTotal: duLeft,
|
|
127
|
-
deltaCRightTotal: deltaCRight,
|
|
128
80
|
};
|
|
129
81
|
}
|
|
130
82
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
*
|
|
134
|
-
* When adding liquidity that spans the current price (crossing tick), tokens get trimmed twice:
|
|
135
|
-
* 1. First by CLMM formula in computeLiquidityTargetAndTokenNeeds
|
|
136
|
-
* 2. Then by AMM formula in simulateAddLiquidityProportional
|
|
137
|
-
*
|
|
138
|
-
* This function calculates the segment's proportion of the total distribution and scales
|
|
139
|
-
* the original max values accordingly, ensuring maximum token utilization.
|
|
140
|
-
*
|
|
141
|
-
* @param scaleParams - Original max values and CLMM distribution extents
|
|
142
|
-
* @param duPtPart - PT segment length (spot price delta for this segment)
|
|
143
|
-
* @param syPerL1 - SY per unit liquidity for this segment
|
|
144
|
-
* @param clmmPtDelta - PT amount calculated from CLMM formula
|
|
145
|
-
* @param clmmSyDelta - SY amount calculated from CLMM formula
|
|
146
|
-
* @returns Tuple of [scaledPtIn, scaledSyIn] - maximum of CLMM-calculated and scaled original values
|
|
147
|
-
*/
|
|
148
|
-
function scaleCrossingTickInputs(scaleParams, duPtPart, syPerL1, clmmPtDelta, clmmSyDelta) {
|
|
149
|
-
const { duLeftTotal, deltaCRightTotal, originalMaxPt, originalMaxSy } = scaleParams;
|
|
150
|
-
if (duLeftTotal > 0 && deltaCRightTotal > 0) {
|
|
151
|
-
// Calculate segment's proportion of total distribution
|
|
152
|
-
const ptSegmentRatio = duPtPart / duLeftTotal;
|
|
153
|
-
const sySegmentRatio = syPerL1 / deltaCRightTotal;
|
|
154
|
-
// Scale original max values by segment proportion
|
|
155
|
-
const scaledPt = Math.ceil(originalMaxPt * ptSegmentRatio);
|
|
156
|
-
const scaledSy = Math.ceil(originalMaxSy * sySegmentRatio);
|
|
157
|
-
// Use max of CLMM-calculated value and scaled original value
|
|
158
|
-
// This ensures we don't lose tokens due to double trimming
|
|
159
|
-
return [Math.max(clmmPtDelta, scaledPt), Math.max(clmmSyDelta, scaledSy)];
|
|
160
|
-
}
|
|
161
|
-
// Fallback to original logic if no crossing scale params
|
|
162
|
-
return [clmmPtDelta, clmmSyDelta];
|
|
83
|
+
function toU64BigInt(value) {
|
|
84
|
+
return BigInt(Math.max(0, Math.floor(value)));
|
|
163
85
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
function simulateAddLiquidityProportional(intentPt, intentSy, marketTotalPt, marketTotalSy, marketTotalLp) {
|
|
169
|
-
if (marketTotalLp === 0 || marketTotalPt === 0 || marketTotalSy === 0) {
|
|
170
|
-
// Empty tick - use all intended amounts
|
|
171
|
-
return [intentPt, intentSy];
|
|
172
|
-
}
|
|
173
|
-
const lpFromPt = (marketTotalLp * intentPt) / marketTotalPt;
|
|
174
|
-
const lpFromSy = (marketTotalLp * intentSy) / marketTotalSy;
|
|
175
|
-
if (lpFromPt < lpFromSy) {
|
|
176
|
-
// PT is the limiting factor
|
|
177
|
-
const lpTokensOut = lpFromPt;
|
|
178
|
-
const usedPt = intentPt;
|
|
179
|
-
const usedSy = Math.ceil((marketTotalSy * lpTokensOut) / marketTotalLp);
|
|
180
|
-
return [usedPt, usedSy];
|
|
86
|
+
function lamportsNumberToBigInt(value, label) {
|
|
87
|
+
const lamports = Math.max(0, Math.floor(value));
|
|
88
|
+
if (!Number.isSafeInteger(lamports)) {
|
|
89
|
+
throw new Error(`${label} exceeds JS safe integer range: ${value}`);
|
|
181
90
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return [usedPt, usedSy];
|
|
91
|
+
return BigInt(lamports);
|
|
92
|
+
}
|
|
93
|
+
function safeLamportsBigIntToNumber(value, label) {
|
|
94
|
+
if (value < ZERO_BIGINT || value > MAX_SAFE_INTEGER_BIGINT) {
|
|
95
|
+
throw new Error(`${label} exceeds JS safe integer range: ${value.toString()}`);
|
|
188
96
|
}
|
|
97
|
+
return Number(value);
|
|
189
98
|
}
|
|
190
|
-
function
|
|
191
|
-
|
|
99
|
+
function finalizeEqualizedSpend(params) {
|
|
100
|
+
const grossSpend = lamportsNumberToBigInt(params.simulatedSpend, params.label) + params.fixedSpent;
|
|
101
|
+
const finalSpend = grossSpend > params.fixedReleased ? grossSpend - params.fixedReleased : ZERO_BIGINT;
|
|
102
|
+
return safeLamportsBigIntToNumber(finalSpend, params.label);
|
|
192
103
|
}
|
|
193
104
|
function ceilDivBigInt(numerator, denominator) {
|
|
194
105
|
if (denominator <= 0n) {
|
|
@@ -223,6 +134,78 @@ function getVirtualTickState(key, ticksWrapper, virtualStates) {
|
|
|
223
134
|
virtualStates.set(key, state);
|
|
224
135
|
return state;
|
|
225
136
|
}
|
|
137
|
+
function ceilNormalizedPrincipal(value, cap) {
|
|
138
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
139
|
+
throw new Error("Invalid active tick normalization value");
|
|
140
|
+
}
|
|
141
|
+
const capNumber = Number(cap);
|
|
142
|
+
if (value >= capNumber) {
|
|
143
|
+
return cap;
|
|
144
|
+
}
|
|
145
|
+
return BigInt(Math.min(Math.ceil(value), capNumber));
|
|
146
|
+
}
|
|
147
|
+
function computeNormalizedActiveTickPrincipals(params) {
|
|
148
|
+
const { principalPt, principalSy, ptPerL, syPerL } = params;
|
|
149
|
+
if (!Number.isFinite(ptPerL) || !Number.isFinite(syPerL) || ptPerL < 0 || syPerL < 0) {
|
|
150
|
+
throw new Error("Invalid active tick normalization ratio");
|
|
151
|
+
}
|
|
152
|
+
if (ptPerL === 0 && syPerL === 0) {
|
|
153
|
+
return { normalizedPt: 0n, normalizedSy: 0n };
|
|
154
|
+
}
|
|
155
|
+
if (ptPerL === 0) {
|
|
156
|
+
return { normalizedPt: 0n, normalizedSy: principalSy };
|
|
157
|
+
}
|
|
158
|
+
if (syPerL === 0) {
|
|
159
|
+
return { normalizedPt: principalPt, normalizedSy: 0n };
|
|
160
|
+
}
|
|
161
|
+
const lFromPt = Number(principalPt) / ptPerL;
|
|
162
|
+
const lFromSy = Number(principalSy) / syPerL;
|
|
163
|
+
if (!Number.isFinite(lFromPt) || !Number.isFinite(lFromSy)) {
|
|
164
|
+
throw new Error("Invalid active tick normalization liquidity");
|
|
165
|
+
}
|
|
166
|
+
if (lFromPt <= lFromSy) {
|
|
167
|
+
return {
|
|
168
|
+
normalizedPt: principalPt,
|
|
169
|
+
normalizedSy: ceilNormalizedPrincipal(lFromPt * syPerL, principalSy),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
normalizedPt: ceilNormalizedPrincipal(lFromSy * ptPerL, principalPt),
|
|
174
|
+
normalizedSy: principalSy,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function normalizeActiveTickPrincipalsBeforeDeposit(ticksWrapper, snap, lowerTickKey, upperTickKey) {
|
|
178
|
+
if (ticksWrapper.currentPrefixSum === 0n) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const currentTickKey = ticksWrapper.currentTickKey;
|
|
182
|
+
if (lowerTickKey > currentTickKey || currentTickKey >= upperTickKey) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const rightTickKey = ticksWrapper.successorKey(currentTickKey);
|
|
186
|
+
if (rightTickKey == null) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const spotPrice = ticksWrapper.currentSpotPrice;
|
|
190
|
+
const leftPrice = ticksWrapper.getSpotPrice(currentTickKey);
|
|
191
|
+
const rightPrice = ticksWrapper.getSpotPrice(rightTickKey);
|
|
192
|
+
if (spotPrice < leftPrice || spotPrice > rightPrice) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const { principalPt, principalSy } = ticksWrapper.getPrincipals(currentTickKey);
|
|
196
|
+
if (principalPt === 0n && principalSy === 0n) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const ptPerL = Math.max(spotPrice - leftPrice, 0);
|
|
200
|
+
const syPerL = spotPrice >= rightPrice ? 0 : snap.getEffectivePrice(spotPrice) - snap.getEffectivePrice(rightPrice);
|
|
201
|
+
const { normalizedPt, normalizedSy } = computeNormalizedActiveTickPrincipals({
|
|
202
|
+
principalPt,
|
|
203
|
+
principalSy,
|
|
204
|
+
ptPerL,
|
|
205
|
+
syPerL,
|
|
206
|
+
});
|
|
207
|
+
ticksWrapper.setPrincipals(currentTickKey, normalizedPt, normalizedSy);
|
|
208
|
+
}
|
|
226
209
|
function simulateMintSharesForTickPrestateCalcUsed(params) {
|
|
227
210
|
const { key, dptIn, dsyIn, ticksWrapper, virtualStates } = params;
|
|
228
211
|
const state = getVirtualTickState(key, ticksWrapper, virtualStates);
|
|
@@ -310,10 +293,11 @@ function simulateMintSharesForTickPrestateCalcUsed(params) {
|
|
|
310
293
|
return { minted, usedPt, usedSy };
|
|
311
294
|
}
|
|
312
295
|
function simulateAccruePrincipalForDeposit(params) {
|
|
313
|
-
const { ticks, snap, lowerPrice, upperPrice, priceSplitForNeed, splitTickKey, lowerTickKey, upperTickKey, liquidityTarget,
|
|
296
|
+
const { ticks, snap, lowerPrice, upperPrice, priceSplitForNeed, splitTickKey, lowerTickKey, upperTickKey, liquidityTarget, } = params;
|
|
314
297
|
const ticksWrapper = new utilsV2_1.TicksWrapper(ticks);
|
|
315
298
|
ticksWrapper.upsertBoundaryTick(lowerTickKey, snap);
|
|
316
299
|
ticksWrapper.upsertBoundaryTick(upperTickKey, snap);
|
|
300
|
+
normalizeActiveTickPrincipalsBeforeDeposit(ticksWrapper, snap, lowerTickKey, upperTickKey);
|
|
317
301
|
const virtualStates = new Map();
|
|
318
302
|
let totalSySpend = 0n;
|
|
319
303
|
let totalPtSpend = 0n;
|
|
@@ -363,9 +347,7 @@ function simulateAccruePrincipalForDeposit(params) {
|
|
|
363
347
|
if (isCrossing) {
|
|
364
348
|
pendingCrossPt = {
|
|
365
349
|
leftKey,
|
|
366
|
-
rightKey,
|
|
367
350
|
ptDelta: principalPtDelta,
|
|
368
|
-
duPart: segmentLength,
|
|
369
351
|
};
|
|
370
352
|
return;
|
|
371
353
|
}
|
|
@@ -400,10 +382,7 @@ function simulateAccruePrincipalForDeposit(params) {
|
|
|
400
382
|
const isCrossing = leftPrice < priceSplitForNeed && rightPrice > priceSplitForNeed;
|
|
401
383
|
if (isCrossing) {
|
|
402
384
|
pendingCrossSy = {
|
|
403
|
-
leftKey,
|
|
404
|
-
rightKey,
|
|
405
385
|
syDelta: principalSyDelta,
|
|
406
|
-
syPerL,
|
|
407
386
|
};
|
|
408
387
|
return;
|
|
409
388
|
}
|
|
@@ -421,14 +400,10 @@ function simulateAccruePrincipalForDeposit(params) {
|
|
|
421
400
|
totalSySpend += minted.usedSy;
|
|
422
401
|
});
|
|
423
402
|
if (pendingCrossPt && pendingCrossSy) {
|
|
424
|
-
const ptLeft = BigInt(Math.max(0, Math.floor(originalMaxPt) - Number(totalPtSpend) - GAP_TOKEN_NEEDS));
|
|
425
|
-
const syLeft = BigInt(Math.max(0, Math.floor(originalMaxSy) - Number(totalSySpend) - GAP_TOKEN_NEEDS));
|
|
426
|
-
const scaledPtIn = pendingCrossPt.ptDelta > ptLeft ? pendingCrossPt.ptDelta : ptLeft;
|
|
427
|
-
const scaledSyIn = pendingCrossSy.syDelta > syLeft ? pendingCrossSy.syDelta : syLeft;
|
|
428
403
|
const minted = simulateMintSharesForTickPrestateCalcUsed({
|
|
429
404
|
key: pendingCrossPt.leftKey,
|
|
430
|
-
dptIn:
|
|
431
|
-
dsyIn:
|
|
405
|
+
dptIn: pendingCrossPt.ptDelta,
|
|
406
|
+
dsyIn: pendingCrossSy.syDelta,
|
|
432
407
|
ticksWrapper,
|
|
433
408
|
virtualStates,
|
|
434
409
|
});
|
|
@@ -445,11 +420,8 @@ function simulateAccruePrincipalForDeposit(params) {
|
|
|
445
420
|
}
|
|
446
421
|
return { sySpent, ptSpent };
|
|
447
422
|
}
|
|
448
|
-
/**
|
|
449
|
-
|
|
450
|
-
* This matches the Rust compute_token_needs_with_crossing function
|
|
451
|
-
*/
|
|
452
|
-
function computeTokenNeedsWithCrossing(snap, spotPriceCurrent, priceEffLower, priceEffUpper, lowerPrice, upperPrice, maxSy, maxPt, epsilonClamp, crossingTickState, crossingTickPriceLeft, crossingTickPriceRight) {
|
|
423
|
+
/** Compute curve-level token needs without raw crossing-tick cap scaling. */
|
|
424
|
+
function computeTokenNeedsWithCrossing(snap, spotPriceCurrent, priceEffLower, priceEffUpper, lowerPrice, upperPrice, maxSy, maxPt, epsilonClamp) {
|
|
453
425
|
// Below range: SY only
|
|
454
426
|
if (spotPriceCurrent <= lowerPrice) {
|
|
455
427
|
const deltaCTotal = priceEffLower - priceEffUpper;
|
|
@@ -471,45 +443,8 @@ function computeTokenNeedsWithCrossing(snap, spotPriceCurrent, priceEffLower, pr
|
|
|
471
443
|
const liquidityFromPt = maxPt / duLeft;
|
|
472
444
|
const liquidityFromSy = maxSy / deltaCRight;
|
|
473
445
|
const liquidityTarget = Math.min(liquidityFromPt, liquidityFromSy);
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
// Apply crossing tick adjustment if the tick has existing liquidity
|
|
477
|
-
// Match Rust: !crossing_tick_state.is_empty() where is_empty = (principal_share_supply == 0)
|
|
478
|
-
const isCrossingTickActive = crossingTickState.principalShareSupply > 0 &&
|
|
479
|
-
crossingTickPriceLeft < spotPriceCurrent &&
|
|
480
|
-
crossingTickPriceRight > spotPriceCurrent;
|
|
481
|
-
if (isCrossingTickActive) {
|
|
482
|
-
// Calculate PT and SY portions that would go into the crossing tick
|
|
483
|
-
// PT portion: from crossing_tick_price_left to spot_price_current (match Rust)
|
|
484
|
-
const crossingPtSegment = spotPriceCurrent - Math.max(crossingTickPriceLeft, lowerPrice);
|
|
485
|
-
const crossingPtIntended = Math.ceil(liquidityTarget * crossingPtSegment);
|
|
486
|
-
// SY portion: from spotPriceCurrent to crossingTickPriceRight
|
|
487
|
-
const crossingSySegmentStart = Math.max(spotPriceCurrent, crossingTickPriceLeft);
|
|
488
|
-
const crossingSySegmentEnd = Math.min(crossingTickPriceRight, upperPrice);
|
|
489
|
-
const syPerL = snap.getEffectivePrice(crossingSySegmentStart) - snap.getEffectivePrice(crossingSySegmentEnd);
|
|
490
|
-
const crossingSyIntended = Math.ceil(liquidityTarget * Math.max(syPerL, 0));
|
|
491
|
-
if (crossingPtIntended > 0 && crossingSyIntended > 0) {
|
|
492
|
-
// Tokens already allocated to non-crossing segments before crossing processing.
|
|
493
|
-
const ptOutsideCrossing = Math.max(ptNeed - crossingPtIntended, 0);
|
|
494
|
-
const syOutsideCrossing = Math.max(syNeed - crossingSyIntended, 0);
|
|
495
|
-
// Apply scaling from original max values to prevent double trimming
|
|
496
|
-
const [scaledPtIn, scaledSyIn] = duLeft > 0 && deltaCRight > 0
|
|
497
|
-
? (() => {
|
|
498
|
-
const totalPtSpend = Math.ceil(ptOutsideCrossing);
|
|
499
|
-
const totalSySpend = Math.ceil(syOutsideCrossing);
|
|
500
|
-
const ptLeft = Math.max(maxPt - totalPtSpend - GAP_TOKEN_NEEDS, 0);
|
|
501
|
-
const syLeft = Math.max(maxSy - totalSySpend - GAP_TOKEN_NEEDS, 0);
|
|
502
|
-
// Use max of CLMM-calculated value and scaled original value
|
|
503
|
-
return [Math.max(crossingPtIntended, ptLeft), Math.max(crossingSyIntended, syLeft)];
|
|
504
|
-
})()
|
|
505
|
-
: [crossingPtIntended, crossingSyIntended];
|
|
506
|
-
// Simulate add_liquidity proportional logic with scaled inputs
|
|
507
|
-
const [usedPt, usedSy] = simulateAddLiquidityProportional(scaledPtIn, scaledSyIn, crossingTickState.principalPt, crossingTickState.principalSy, crossingTickState.principalShareSupply);
|
|
508
|
-
// Adjust needs based on what would actually be used
|
|
509
|
-
ptNeed = ptOutsideCrossing + usedPt;
|
|
510
|
-
syNeed = syOutsideCrossing + usedSy;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
446
|
+
const ptNeed = liquidityTarget * duLeft;
|
|
447
|
+
const syNeed = liquidityTarget * deltaCRight;
|
|
513
448
|
return [Math.ceil(syNeed), Math.ceil(ptNeed)];
|
|
514
449
|
}
|
|
515
450
|
/**
|
|
@@ -546,8 +481,6 @@ function simulateAddLiquidity(marketState, args) {
|
|
|
546
481
|
lowerTickKey: args.lowerTick,
|
|
547
482
|
upperTickKey: args.upperTick,
|
|
548
483
|
liquidityTarget: liquidityNeeds.liquidityTarget,
|
|
549
|
-
originalMaxSy: liquidityNeeds.originalMaxSy,
|
|
550
|
-
originalMaxPt: liquidityNeeds.originalMaxPt,
|
|
551
484
|
});
|
|
552
485
|
// Enforce budgets
|
|
553
486
|
const sySpent = syNeededWithCrossing;
|
|
@@ -564,6 +497,37 @@ function simulateAddLiquidity(marketState, args) {
|
|
|
564
497
|
ptSpent,
|
|
565
498
|
};
|
|
566
499
|
}
|
|
500
|
+
function simulateAddLiquidityForPosition(marketState, args, existingPosition) {
|
|
501
|
+
if (!existingPosition) {
|
|
502
|
+
return simulateAddLiquidity(marketState, args);
|
|
503
|
+
}
|
|
504
|
+
const budgetEffect = (0, existingPositionEqualization_1.computeExistingPositionBudgetEffect)({
|
|
505
|
+
ticks: marketState.ticks,
|
|
506
|
+
position: existingPosition,
|
|
507
|
+
userMaxSy: lamportsNumberToBigInt(args.maxSy, "user max SY"),
|
|
508
|
+
userMaxPt: lamportsNumberToBigInt(args.maxPt, "user max PT"),
|
|
509
|
+
});
|
|
510
|
+
const addLiquidityResult = simulateAddLiquidity(marketState, {
|
|
511
|
+
...args,
|
|
512
|
+
maxSy: safeLamportsBigIntToNumber(budgetEffect.effectiveMaxSy, "effective max SY"),
|
|
513
|
+
maxPt: safeLamportsBigIntToNumber(budgetEffect.effectiveMaxPt, "effective max PT"),
|
|
514
|
+
});
|
|
515
|
+
return {
|
|
516
|
+
deltaL: addLiquidityResult.deltaL,
|
|
517
|
+
sySpent: finalizeEqualizedSpend({
|
|
518
|
+
simulatedSpend: addLiquidityResult.sySpent,
|
|
519
|
+
fixedSpent: budgetEffect.fixedSySpent,
|
|
520
|
+
fixedReleased: budgetEffect.fixedSyReleased,
|
|
521
|
+
label: "equalized SY spend",
|
|
522
|
+
}),
|
|
523
|
+
ptSpent: finalizeEqualizedSpend({
|
|
524
|
+
simulatedSpend: addLiquidityResult.ptSpent,
|
|
525
|
+
fixedSpent: budgetEffect.fixedPtSpent,
|
|
526
|
+
fixedReleased: budgetEffect.fixedPtReleased,
|
|
527
|
+
label: "equalized PT spend",
|
|
528
|
+
}),
|
|
529
|
+
};
|
|
530
|
+
}
|
|
567
531
|
/**
|
|
568
532
|
* Calculate the LP tokens that will be received for a given deposit
|
|
569
533
|
* This is useful for UI display and slippage calculations
|
|
@@ -611,7 +575,7 @@ function estimateBalancedDeposit(marketState, targetLiquidity, lowerTickApy, upp
|
|
|
611
575
|
/** Calculate SY and PT needed to deposit into liquidity pool from base token amount */
|
|
612
576
|
/** Off-chain analogue of on-chain wrapper_provide_liquidity function */
|
|
613
577
|
function calcDepositSyAndPtFromBaseAmount(params) {
|
|
614
|
-
const { expirationTs, currentSpotPrice, syExchangeRate, lowerPrice, upperPrice, baseTokenAmount,
|
|
578
|
+
const { expirationTs, currentSpotPrice, syExchangeRate, lowerPrice, upperPrice, baseTokenAmount, epsilonClamp = 1e-18, } = params;
|
|
615
579
|
if (baseTokenAmount <= 0 || syExchangeRate <= 0) {
|
|
616
580
|
return {
|
|
617
581
|
syNeeded: 0,
|
|
@@ -623,13 +587,12 @@ function calcDepositSyAndPtFromBaseAmount(params) {
|
|
|
623
587
|
const priceEffLower = effSnap.getEffectivePrice(lowerPrice);
|
|
624
588
|
const priceEffUpper = effSnap.getEffectivePrice(upperPrice);
|
|
625
589
|
// We mirror the on-chain logic in `wrapper_provide_liquidity`:
|
|
626
|
-
// 1. Use
|
|
627
|
-
//
|
|
628
|
-
//
|
|
629
|
-
// PT/SY proportions in the active tick, significantly improving accuracy.
|
|
590
|
+
// 1. Use curve-level token needs to infer the SY/PT ratio for this range.
|
|
591
|
+
// Raw active-tick proportions are ignored because the deposit CPI
|
|
592
|
+
// normalizes the active tick before minting new shares.
|
|
630
593
|
// 2. Use that ratio plus the current SY exchange rate to decide how much of the
|
|
631
594
|
// minted SY should be stripped into PT (calc_strip_amount).
|
|
632
|
-
const [syMock, ptMock] = computeTokenNeedsWithCrossing(effSnap, currentSpotPrice, priceEffLower, priceEffUpper, lowerPrice, upperPrice, 1e9, 1e9, epsilonClamp
|
|
595
|
+
const [syMock, ptMock] = computeTokenNeedsWithCrossing(effSnap, currentSpotPrice, priceEffLower, priceEffUpper, lowerPrice, upperPrice, 1e9, 1e9, epsilonClamp);
|
|
633
596
|
// Total SY the user would get by minting SY from base off-chain
|
|
634
597
|
// (we approximate on-chain `mint_sy_return_data.sy_out_amount`).
|
|
635
598
|
const syAmount = Math.floor(baseTokenAmount * syExchangeRate);
|
|
@@ -679,9 +642,10 @@ function calcStripAmount(totalAmountSy, curSyRate, marketPtLiq, marketSyLiq) {
|
|
|
679
642
|
* @param lowerTick - Lower tick (APY in basis points)
|
|
680
643
|
* @param upperTick - Upper tick (APY in basis points)
|
|
681
644
|
* @param syExchangeRate - SY exchange rate
|
|
645
|
+
* @param options - Optional existing LP position to include fixed crossing-split equalization spend
|
|
682
646
|
* @returns Simulation result with LP out, YT out, amounts spent, etc.
|
|
683
647
|
*/
|
|
684
|
-
function simulateWrapperProvideLiquidity(marketState, amountBase, lowerTick, upperTick, syExchangeRate) {
|
|
648
|
+
function simulateWrapperProvideLiquidity(marketState, amountBase, lowerTick, upperTick, syExchangeRate, options = {}) {
|
|
685
649
|
try {
|
|
686
650
|
const { financials, configurationOptions, ticks } = marketState;
|
|
687
651
|
const secondsRemaining = Math.max(0, Number(financials.expirationTs) - Date.now() / 1000);
|
|
@@ -698,24 +662,22 @@ function simulateWrapperProvideLiquidity(marketState, amountBase, lowerTick, upp
|
|
|
698
662
|
// Precompute effective prices
|
|
699
663
|
const priceEffLower = snap.getEffectivePrice(lowerPrice);
|
|
700
664
|
const priceEffUpper = snap.getEffectivePrice(upperPrice);
|
|
701
|
-
// Step 3:
|
|
702
|
-
const { crossingTickState, crossingTickPriceLeft, crossingTickPriceRight } = getCrossingTickStateFromTicks(ticks);
|
|
703
|
-
// Step 4: Calculate mock token needs using compute_token_needs_with_crossing
|
|
665
|
+
// Step 3: Calculate curve-level token needs.
|
|
704
666
|
// max_sy = syAmount, max_pt = syAmount * syExchangeRate (as on-chain)
|
|
705
667
|
const maxPt = syAmount * syExchangeRate;
|
|
706
|
-
const [syMock, ptMock] = computeTokenNeedsWithCrossing(snap, currentSpot, priceEffLower, priceEffUpper, lowerPrice, upperPrice, syAmount, maxPt, configurationOptions.epsilonClamp
|
|
707
|
-
// Step
|
|
668
|
+
const [syMock, ptMock] = computeTokenNeedsWithCrossing(snap, currentSpot, priceEffLower, priceEffUpper, lowerPrice, upperPrice, syAmount, maxPt, configurationOptions.epsilonClamp);
|
|
669
|
+
// Step 4: Calculate how much SY to strip (calc_strip_amount on-chain)
|
|
708
670
|
// Cap syToStrip to syAmount to prevent negative remainder from ceil rounding
|
|
709
671
|
const syToStripRaw = calcStripAmount(syAmount, syExchangeRate, ptMock, syMock);
|
|
710
672
|
const syToStrip = Math.min(syToStripRaw, syAmount);
|
|
711
|
-
// Step
|
|
673
|
+
// Step 5: Calculate PT and YT from stripping
|
|
712
674
|
// When you strip SY, you get PT = SY * syExchangeRate and YT = SY * syExchangeRate
|
|
713
675
|
const ptFromStrip = syToStrip * syExchangeRate;
|
|
714
676
|
const ytOut = ptFromStrip; // YT amount equals PT amount from strip
|
|
715
|
-
// Step
|
|
677
|
+
// Step 6: Calculate remaining SY after strip
|
|
716
678
|
// Use Math.max to ensure non-negative (safety net for floating point edge cases)
|
|
717
679
|
const syRemainder = Math.max(0, syAmount - syToStrip);
|
|
718
|
-
// Step
|
|
680
|
+
// Step 7: Simulate deposit liquidity with remaining SY and PT
|
|
719
681
|
const depositResult = simulateAddLiquidity(marketState, {
|
|
720
682
|
lowerTick,
|
|
721
683
|
upperTick,
|
|
@@ -754,15 +716,16 @@ function simulateWrapperProvideLiquidity(marketState, amountBase, lowerTick, upp
|
|
|
754
716
|
* @param lowerTick - Lower tick key (APY in basis points)
|
|
755
717
|
* @param upperTick - Upper tick key (APY in basis points)
|
|
756
718
|
* @param syExchangeRate - SY exchange rate
|
|
719
|
+
* @param options - Optional existing LP position to include fixed crossing-split equalization spend
|
|
757
720
|
* @returns Simulation result with LP out, PT to buy, SY constraint, etc.
|
|
758
721
|
*/
|
|
759
|
-
function simulateSwapAndSupply(marketState, amountBase, lowerTick, upperTick, syExchangeRate) {
|
|
722
|
+
function simulateSwapAndSupply(marketState, amountBase, lowerTick, upperTick, syExchangeRate, options = {}) {
|
|
760
723
|
try {
|
|
761
724
|
// Wrapper provide-liquidity-base debits base as:
|
|
762
725
|
// base_needed = ceil(total_sy_spent * sy_exchange_rate)
|
|
763
726
|
// So the strict SY budget for a user-provided base input is floor(base / rate).
|
|
764
727
|
const syBudget = convertBaseToSyBudget(amountBase, syExchangeRate);
|
|
765
|
-
if (syBudget <= 0
|
|
728
|
+
if (syBudget <= 0) {
|
|
766
729
|
return {
|
|
767
730
|
lpOut: 0,
|
|
768
731
|
ptToBuy: 0,
|
|
@@ -831,7 +794,7 @@ function simulateSwapAndSupply(marketState, amountBase, lowerTick, upperTick, sy
|
|
|
831
794
|
if (cached !== undefined) {
|
|
832
795
|
return cached;
|
|
833
796
|
}
|
|
834
|
-
const candidate = simulateSwapAndSupplyForPtConstraint(marketState, lowerTick, upperTick, syBudget, key, syExchangeRate, exactOutEstimateSlack);
|
|
797
|
+
const candidate = simulateSwapAndSupplyForPtConstraint(marketState, lowerTick, upperTick, syBudget, key, syExchangeRate, exactOutEstimateSlack, options.existingPosition);
|
|
835
798
|
candidateCache.set(key, candidate);
|
|
836
799
|
return candidate;
|
|
837
800
|
};
|
|
@@ -951,7 +914,7 @@ function simulateSwapAndSupply(marketState, amountBase, lowerTick, upperTick, sy
|
|
|
951
914
|
return null;
|
|
952
915
|
}
|
|
953
916
|
}
|
|
954
|
-
function simulateSwapAndSupplyForPtConstraint(marketState, lowerTick, upperTick, syBudget, ptConstraint, syExchangeRate, exactOutEstimateSlack) {
|
|
917
|
+
function simulateSwapAndSupplyForPtConstraint(marketState, lowerTick, upperTick, syBudget, ptConstraint, syExchangeRate, exactOutEstimateSlack, existingPosition) {
|
|
955
918
|
let swapResult;
|
|
956
919
|
try {
|
|
957
920
|
swapResult = simulateBuyPtExactOutWrapper(marketState, syBudget, ptConstraint, syExchangeRate, exactOutEstimateSlack);
|
|
@@ -961,13 +924,19 @@ function simulateSwapAndSupplyForPtConstraint(marketState, lowerTick, upperTick,
|
|
|
961
924
|
return null;
|
|
962
925
|
}
|
|
963
926
|
const depositState = swapResult.postMarketState ?? marketState;
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
927
|
+
let depositResult;
|
|
928
|
+
try {
|
|
929
|
+
depositResult = simulateAddLiquidityForPosition(depositState, {
|
|
930
|
+
lowerTick,
|
|
931
|
+
upperTick,
|
|
932
|
+
maxSy: syBudget,
|
|
933
|
+
maxPt: swapResult.ptOut,
|
|
934
|
+
syExchangeRate,
|
|
935
|
+
}, existingPosition);
|
|
936
|
+
}
|
|
937
|
+
catch {
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
971
940
|
// Swap & supply must actually add liquidity.
|
|
972
941
|
// Discard candidates that end up as swap-only (deltaL == 0).
|
|
973
942
|
if (depositResult.deltaL <= 0) {
|