@panoptic-eng/sdk 1.0.49 → 1.0.51
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/dist/cow/index.js.map +1 -1
- package/dist/{cow-DMX9ebPk.js → cow-CSSKPknb.js} +1 -1
- package/dist/deployments.d.ts +12 -4
- package/dist/deployments.d.ts.map +1 -1
- package/dist/deployments.js +78 -10
- package/dist/deployments.js.map +1 -1
- package/dist/{greeks-9aTE-DwO.js → greeks-CNi1-cnp.js} +39 -16
- package/dist/index.d.ts +518 -71
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +724 -91
- package/dist/index.js.map +1 -1
- package/dist/{irm-mySi_YMF.js → irm-BJaJSUjF.js} +80 -12
- package/dist/panoptic/v2/greeks/index.d.ts +12 -1
- package/dist/panoptic/v2/greeks/index.d.ts.map +1 -1
- package/dist/panoptic/v2/greeks/index.js +39 -16
- package/dist/panoptic/v2/greeks/index.js.map +1 -1
- package/dist/panoptic/v2/index.d.ts +1336 -74
- package/dist/panoptic/v2/index.d.ts.map +1 -1
- package/dist/panoptic/v2/index.js +20322 -18660
- package/dist/panoptic/v2/index.js.map +1 -1
- package/dist/panoptic/v2/react-public.d.ts +1518 -240
- package/dist/panoptic/v2/react-public.d.ts.map +1 -1
- package/dist/panoptic/v2/react-public.js +11518 -9319
- package/dist/panoptic/v2/react-public.js.map +1 -1
- package/dist/{rates-CDLIUGDz.js → rates-COAJpKRd.js} +612 -73
- package/dist/{router-45ulPnWe.js → router-B-I1eACz.js} +5 -43
- package/dist/router-BSS7HKd2.js +837 -0
- package/dist/{transactionFees-DUm6ilXB.js → transactionFees-DE-Whxab.js} +24 -8
- package/dist/uniswap/index.d.ts +465 -215
- package/dist/uniswap/index.d.ts.map +1 -1
- package/dist/uniswap/index.js +1799 -1076
- package/dist/uniswap/index.js.map +1 -1
- package/dist/{v2-DshssxX4.js → v2-zF6b01PI.js} +2488 -1596
- package/dist/vault-transaction-fees.d.ts +9 -4
- package/dist/vault-transaction-fees.d.ts.map +1 -1
- package/dist/vault-transaction-fees.js +24 -8
- package/dist/vault-transaction-fees.js.map +1 -1
- package/dist/{writes-2reV_omF.js → writes-D1zN0v9x.js} +4 -3
- package/dist/zodiac/index.d.ts +51 -7
- package/dist/zodiac/index.d.ts.map +1 -1
- package/dist/zodiac/index.js +154 -2
- package/dist/zodiac/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/router-B2jY9Nyb.js +0 -312
package/dist/uniswap/index.js
CHANGED
|
@@ -1,5 +1,132 @@
|
|
|
1
|
+
import Decimal from "decimal.js";
|
|
1
2
|
import { BaseError, ContractFunctionRevertedError, decodeAbiParameters, decodeEventLog, encodeAbiParameters, encodeFunctionData, encodePacked, erc20Abi, getAbiItem, isAddressEqual, keccak256, maxUint256, parseAbi, toFunctionSelector, zeroAddress } from "viem";
|
|
2
3
|
|
|
4
|
+
//#region src/uniswap/estimateLpFees.ts
|
|
5
|
+
const D$1 = Decimal.clone({ precision: 60 });
|
|
6
|
+
const validTick = (tick) => Number.isInteger(tick) && Math.abs(tick) <= 887272;
|
|
7
|
+
/**
|
|
8
|
+
* Hypothetical added LP liquidity, with no compounding or price impact. Fees are raw
|
|
9
|
+
* token units. Bullish candles follow O→L→H→C; bearish candles O→H→L→C.
|
|
10
|
+
* Uniswap v3 deltas distribute absolute volume between inputs and outputs; only
|
|
11
|
+
* inputs pay fees. Split moves at every LP boundary, so overlapping ranges share
|
|
12
|
+
* the same denominator and out-of-range portions earn nothing.
|
|
13
|
+
*
|
|
14
|
+
* Closing historical liquidity approximates liquidity throughout a candle. If
|
|
15
|
+
* absent, infer it from volume / path deltas. Flat candles with volume use equal
|
|
16
|
+
* buy/sell flow and require historical liquidity; otherwise report them skipped.
|
|
17
|
+
* OHLC cannot recover unseen oscillations, exact swap ordering or liquidity changes.
|
|
18
|
+
*/
|
|
19
|
+
function estimateLpFees({ candles, ranges, feePips }) {
|
|
20
|
+
if (feePips < 0n || feePips >= 1000000n) throw new Error("Invalid swap fee");
|
|
21
|
+
for (const range of ranges) if (!validTick(range.tickLower) || !validTick(range.tickUpper) || range.tickLower >= range.tickUpper || range.liquidity < 0n) throw new Error("Invalid LP range");
|
|
22
|
+
const fee = new D$1(feePips.toString()).div(1e6);
|
|
23
|
+
const net = new D$1(1).minus(fee);
|
|
24
|
+
const sqrtCache = new Map();
|
|
25
|
+
const sqrtAt = (tick) => {
|
|
26
|
+
const cached = sqrtCache.get(tick);
|
|
27
|
+
if (cached) return cached;
|
|
28
|
+
const sqrt = new D$1("1.0001").pow(new D$1(tick).div(2));
|
|
29
|
+
sqrtCache.set(tick, sqrt);
|
|
30
|
+
return sqrt;
|
|
31
|
+
};
|
|
32
|
+
const positions = ranges.map((r) => ({
|
|
33
|
+
...r,
|
|
34
|
+
liquidity: new D$1(r.liquidity.toString())
|
|
35
|
+
}));
|
|
36
|
+
let fees0 = new D$1(0);
|
|
37
|
+
let fees1 = new D$1(0);
|
|
38
|
+
let skippedCandles = 0;
|
|
39
|
+
let inferredCandles = 0;
|
|
40
|
+
let previousTime = -Infinity;
|
|
41
|
+
const points = candles.map((candle) => {
|
|
42
|
+
const { o, h, l, c, t } = candle;
|
|
43
|
+
if (!Number.isSafeInteger(t) || t <= previousTime || ![
|
|
44
|
+
o,
|
|
45
|
+
h,
|
|
46
|
+
l,
|
|
47
|
+
c
|
|
48
|
+
].every(validTick) || l > Math.min(o, c) || h < Math.max(o, c) || l > h) throw new Error("Invalid or unordered OHLC candles");
|
|
49
|
+
previousTime = t;
|
|
50
|
+
const v0 = new D$1(candle.v0);
|
|
51
|
+
const v1 = new D$1(candle.v1);
|
|
52
|
+
const historical = new D$1(candle.liq ?? "0");
|
|
53
|
+
if (![
|
|
54
|
+
v0,
|
|
55
|
+
v1,
|
|
56
|
+
historical
|
|
57
|
+
].every((v) => v.isFinite() && v.isInteger() && v.gte(0))) throw new Error("Invalid candle volume or liquidity");
|
|
58
|
+
const path = c >= o ? [
|
|
59
|
+
o,
|
|
60
|
+
l,
|
|
61
|
+
h,
|
|
62
|
+
c
|
|
63
|
+
] : [
|
|
64
|
+
o,
|
|
65
|
+
h,
|
|
66
|
+
l,
|
|
67
|
+
c
|
|
68
|
+
];
|
|
69
|
+
const steps = [];
|
|
70
|
+
for (let i = 1; i < path.length; i++) {
|
|
71
|
+
const from = path[i - 1];
|
|
72
|
+
const to = path[i];
|
|
73
|
+
if (from === void 0 || to === void 0 || from === to) continue;
|
|
74
|
+
const lower = Math.min(from, to);
|
|
75
|
+
const upper = Math.max(from, to);
|
|
76
|
+
const cuts = [...new Set([
|
|
77
|
+
lower,
|
|
78
|
+
upper,
|
|
79
|
+
...positions.flatMap((p) => [p.tickLower, p.tickUpper])
|
|
80
|
+
])].filter((tick) => tick >= lower && tick <= upper).sort((a, b) => a - b);
|
|
81
|
+
for (let j = 1; j < cuts.length; j++) {
|
|
82
|
+
const a = cuts[j - 1];
|
|
83
|
+
const b = cuts[j];
|
|
84
|
+
if (a === void 0 || b === void 0) continue;
|
|
85
|
+
const sa = sqrtAt(a);
|
|
86
|
+
const sb = sqrtAt(b);
|
|
87
|
+
steps.push({
|
|
88
|
+
lower: a,
|
|
89
|
+
upper: b,
|
|
90
|
+
up: to > from,
|
|
91
|
+
d0: new D$1(1).div(sa).minus(new D$1(1).div(sb)),
|
|
92
|
+
d1: sb.minus(sa)
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const total0 = steps.reduce((sum, s) => sum.plus(s.up ? s.d0 : s.d0.div(net)), new D$1(0));
|
|
97
|
+
const total1 = steps.reduce((sum, s) => sum.plus(s.up ? s.d1.div(net) : s.d1), new D$1(0));
|
|
98
|
+
const hasVolume = v0.gt(0) || v1.gt(0);
|
|
99
|
+
const inferred = total0.gt(0) && total1.gt(0) ? v0.div(total0).plus(v1.div(total1)).div(2) : new D$1(0);
|
|
100
|
+
const poolLiquidity = historical.gt(0) ? historical : inferred;
|
|
101
|
+
if (hasVolume && poolLiquidity.eq(0)) skippedCandles++;
|
|
102
|
+
else if (hasVolume && fee.gt(0)) {
|
|
103
|
+
if (historical.eq(0)) inferredCandles++;
|
|
104
|
+
if (steps.length === 0) {
|
|
105
|
+
const active = positions.filter((p) => p.tickLower <= c && c < p.tickUpper).reduce((sum, p) => sum.plus(p.liquidity), new D$1(0));
|
|
106
|
+
const share = active.div(poolLiquidity.plus(active));
|
|
107
|
+
fees0 = fees0.plus(v0.div(new D$1(2).minus(fee)).mul(fee).mul(share));
|
|
108
|
+
fees1 = fees1.plus(v1.div(new D$1(2).minus(fee)).mul(fee).mul(share));
|
|
109
|
+
} else for (const step of steps) {
|
|
110
|
+
const active = positions.filter((p) => p.tickLower <= step.lower && p.tickUpper >= step.upper).reduce((sum, p) => sum.plus(p.liquidity), new D$1(0));
|
|
111
|
+
const share = active.div(poolLiquidity.plus(active));
|
|
112
|
+
if (step.up) fees1 = fees1.plus(v1.mul(step.d1.div(net)).div(total1).mul(fee).mul(share));
|
|
113
|
+
else fees0 = fees0.plus(v0.mul(step.d0.div(net)).div(total0).mul(fee).mul(share));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
time: t,
|
|
118
|
+
fees0: BigInt(fees0.floor().toFixed(0)),
|
|
119
|
+
fees1: BigInt(fees1.floor().toFixed(0))
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
return {
|
|
123
|
+
points,
|
|
124
|
+
skippedCandles,
|
|
125
|
+
inferredCandles
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
3
130
|
//#region src/panoptic/v2/utils/constants.ts
|
|
4
131
|
/**
|
|
5
132
|
* Constants for the Panoptic v2 SDK.
|
|
@@ -63,65 +190,6 @@ function tickToSqrtPriceX96(tick) {
|
|
|
63
190
|
const MIN_SQRT_PRICE_X96 = tickToSqrtPriceX96(MIN_TICK);
|
|
64
191
|
const MAX_SQRT_PRICE_X96 = tickToSqrtPriceX96(MAX_TICK);
|
|
65
192
|
|
|
66
|
-
//#endregion
|
|
67
|
-
//#region src/uniswap/lpGreeks.ts
|
|
68
|
-
const Q96$2 = 1n << 96n;
|
|
69
|
-
const Q192$3 = 1n << 192n;
|
|
70
|
-
/**
|
|
71
|
-
* Compute the token0/token1 amounts backing `liquidity` over `[sqrtA, sqrtB]`
|
|
72
|
-
* at the current price `sqrtP`, mirroring Uniswap's `LiquidityAmounts`
|
|
73
|
-
* (`getAmountsForLiquidity`). All sqrt prices are X96. The current price is
|
|
74
|
-
* clamped into the range, so out-of-range positions collapse to a single token.
|
|
75
|
-
*/
|
|
76
|
-
function getAmountsForLiquidity(sqrtPriceX96, sqrtRatioAX96, sqrtRatioBX96, liquidity) {
|
|
77
|
-
const [sqrtA, sqrtB] = sqrtRatioAX96 <= sqrtRatioBX96 ? [sqrtRatioAX96, sqrtRatioBX96] : [sqrtRatioBX96, sqrtRatioAX96];
|
|
78
|
-
const sqrtC = sqrtPriceX96 < sqrtA ? sqrtA : sqrtPriceX96 > sqrtB ? sqrtB : sqrtPriceX96;
|
|
79
|
-
const amount0 = sqrtC < sqrtB ? liquidity * (sqrtB - sqrtC) * Q96$2 / (sqrtC * sqrtB) : 0n;
|
|
80
|
-
const amount1 = sqrtC > sqrtA ? liquidity * (sqrtC - sqrtA) / Q96$2 : 0n;
|
|
81
|
-
return {
|
|
82
|
-
amount0,
|
|
83
|
-
amount1
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Greeks (value, delta, gamma) for a Uniswap LP position in the chosen asset
|
|
88
|
-
* frame. See the module docs for the unit conventions.
|
|
89
|
-
*
|
|
90
|
-
* Closed forms (in-range, numeraire = token1 / asset = token0):
|
|
91
|
-
* value = amount1 + amount0·P (P = sqrtP² / 2^192)
|
|
92
|
-
* delta = amount0 (= dV/dP)
|
|
93
|
-
* gamma = P²·d²V/dP² = -L·sqrt(P)/2 (short gamma)
|
|
94
|
-
* The `assetIndex = 1` frame is the symmetric inverse (numeraire = token0).
|
|
95
|
-
* Gamma is zero when the price is outside the range (no curvature there).
|
|
96
|
-
*/
|
|
97
|
-
function getLpGreeks(input) {
|
|
98
|
-
const { liquidity, tickLower, tickUpper, currentTick, assetIndex } = input;
|
|
99
|
-
const sqrtP = tickToSqrtPriceX96(currentTick);
|
|
100
|
-
const sqrtA = tickToSqrtPriceX96(tickLower);
|
|
101
|
-
const sqrtB = tickToSqrtPriceX96(tickUpper);
|
|
102
|
-
const { amount0, amount1 } = getAmountsForLiquidity(sqrtP, sqrtA, sqrtB, liquidity);
|
|
103
|
-
const sqrtP2 = sqrtP * sqrtP;
|
|
104
|
-
const inRange = sqrtP > sqrtA && sqrtP < sqrtB;
|
|
105
|
-
if (assetIndex === 0) {
|
|
106
|
-
const value$1 = amount1 + amount0 * sqrtP2 / Q192$3;
|
|
107
|
-
const delta$1 = amount0;
|
|
108
|
-
const gamma$1 = inRange ? -(liquidity * sqrtP) / (2n * Q96$2) : 0n;
|
|
109
|
-
return {
|
|
110
|
-
value: value$1,
|
|
111
|
-
delta: delta$1,
|
|
112
|
-
gamma: gamma$1
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
const value = amount0 + amount1 * Q192$3 / sqrtP2;
|
|
116
|
-
const delta = amount1;
|
|
117
|
-
const gamma = inRange ? -(liquidity * Q96$2) / (2n * sqrtP) : 0n;
|
|
118
|
-
return {
|
|
119
|
-
value,
|
|
120
|
-
delta,
|
|
121
|
-
gamma
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
193
|
//#endregion
|
|
126
194
|
//#region src/panoptic/v2/errors/base.ts
|
|
127
195
|
/**
|
|
@@ -3540,594 +3608,577 @@ const combinedAbi = [
|
|
|
3540
3608
|
];
|
|
3541
3609
|
|
|
3542
3610
|
//#endregion
|
|
3543
|
-
//#region src/
|
|
3611
|
+
//#region src/panoptic/v2/tokenId/constants.ts
|
|
3544
3612
|
/**
|
|
3545
|
-
*
|
|
3546
|
-
*
|
|
3613
|
+
* TokenId constants for the Panoptic v2 SDK.
|
|
3614
|
+
* @module v2/tokenId/constants
|
|
3547
3615
|
*/
|
|
3548
|
-
var UnsupportedChainError = class extends PanopticError {
|
|
3549
|
-
name = "UnsupportedChainError";
|
|
3550
|
-
constructor(chainId, cause) {
|
|
3551
|
-
super(`Uniswap v4 router not configured for chain ${chainId}`, cause);
|
|
3552
|
-
this.chainId = chainId;
|
|
3553
|
-
}
|
|
3554
|
-
};
|
|
3555
3616
|
/**
|
|
3556
|
-
*
|
|
3617
|
+
* Default vegoid value used in pool ID encoding.
|
|
3557
3618
|
*/
|
|
3558
|
-
|
|
3559
|
-
name = "InvalidSwapTokenError";
|
|
3560
|
-
constructor(token, currency0, currency1, cause) {
|
|
3561
|
-
super(`Token ${token} is not part of the pool (currency0=${currency0}, currency1=${currency1})`, cause);
|
|
3562
|
-
this.token = token;
|
|
3563
|
-
this.currency0 = currency0;
|
|
3564
|
-
this.currency1 = currency1;
|
|
3565
|
-
}
|
|
3566
|
-
};
|
|
3619
|
+
const DEFAULT_VEGOID = 8n;
|
|
3567
3620
|
/**
|
|
3568
|
-
*
|
|
3621
|
+
* Bit positions and sizes for TokenId encoding.
|
|
3569
3622
|
*/
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3623
|
+
const TOKEN_ID_BITS = {
|
|
3624
|
+
POOL_ID_SIZE: 64n,
|
|
3625
|
+
VEGOID_STARTING_BIT: 40n,
|
|
3626
|
+
VEGOID_SIZE: 8n,
|
|
3627
|
+
TICK_SPACING_STARTING_BIT: 48n,
|
|
3628
|
+
TICK_SPACING_SIZE: 16n,
|
|
3629
|
+
LEG_SIZE: 48n,
|
|
3630
|
+
MAX_LEGS: 4n
|
|
3576
3631
|
};
|
|
3577
3632
|
/**
|
|
3578
|
-
*
|
|
3579
|
-
* output (or refund the input overpay), but no `recipient` was supplied.
|
|
3633
|
+
* Bit positions within a leg (relative to leg start).
|
|
3580
3634
|
*/
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3635
|
+
const LEG_BITS = {
|
|
3636
|
+
ASSET_BIT: 0n,
|
|
3637
|
+
ASSET_SIZE: 1n,
|
|
3638
|
+
RATIO_BIT: 1n,
|
|
3639
|
+
RATIO_SIZE: 7n,
|
|
3640
|
+
IS_LONG_BIT: 8n,
|
|
3641
|
+
IS_LONG_SIZE: 1n,
|
|
3642
|
+
TOKEN_TYPE_BIT: 9n,
|
|
3643
|
+
TOKEN_TYPE_SIZE: 1n,
|
|
3644
|
+
RISK_PARTNER_BIT: 10n,
|
|
3645
|
+
RISK_PARTNER_SIZE: 2n,
|
|
3646
|
+
STRIKE_BIT: 12n,
|
|
3647
|
+
STRIKE_SIZE: 24n,
|
|
3648
|
+
WIDTH_BIT: 36n,
|
|
3649
|
+
WIDTH_SIZE: 12n
|
|
3586
3650
|
};
|
|
3587
3651
|
/**
|
|
3588
|
-
*
|
|
3652
|
+
* Masks for extracting/encoding leg fields.
|
|
3589
3653
|
*/
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3654
|
+
const LEG_MASKS = {
|
|
3655
|
+
ASSET: (1n << LEG_BITS.ASSET_SIZE) - 1n,
|
|
3656
|
+
RATIO: (1n << LEG_BITS.RATIO_SIZE) - 1n,
|
|
3657
|
+
IS_LONG: (1n << LEG_BITS.IS_LONG_SIZE) - 1n,
|
|
3658
|
+
TOKEN_TYPE: (1n << LEG_BITS.TOKEN_TYPE_SIZE) - 1n,
|
|
3659
|
+
RISK_PARTNER: (1n << LEG_BITS.RISK_PARTNER_SIZE) - 1n,
|
|
3660
|
+
STRIKE: (1n << LEG_BITS.STRIKE_SIZE) - 1n,
|
|
3661
|
+
WIDTH: (1n << LEG_BITS.WIDTH_SIZE) - 1n,
|
|
3662
|
+
LEG: (1n << TOKEN_ID_BITS.LEG_SIZE) - 1n
|
|
3596
3663
|
};
|
|
3597
|
-
|
|
3598
|
-
//#endregion
|
|
3599
|
-
//#region src/uniswap/v3/addresses.ts
|
|
3600
3664
|
/**
|
|
3601
|
-
*
|
|
3602
|
-
* after verifying against the canonical Uniswap v3 deployment listing.
|
|
3665
|
+
* Maximum and minimum values for leg fields.
|
|
3603
3666
|
*/
|
|
3604
|
-
const
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3667
|
+
const LEG_LIMITS = {
|
|
3668
|
+
MAX_RATIO: 127n,
|
|
3669
|
+
MAX_WIDTH: 4095n,
|
|
3670
|
+
MAX_STRIKE: 8388607n,
|
|
3671
|
+
MIN_STRIKE: -8388608n
|
|
3672
|
+
};
|
|
3608
3673
|
/**
|
|
3609
|
-
*
|
|
3610
|
-
*
|
|
3611
|
-
* @throws {UnsupportedChainError} when the chain is not listed and overrides do
|
|
3612
|
-
* not supply every required address.
|
|
3674
|
+
* Value used to convert negative strike to unsigned representation.
|
|
3675
|
+
* Strike is stored as int24, so we use 2^24 for conversion.
|
|
3613
3676
|
*/
|
|
3614
|
-
|
|
3615
|
-
const id = Number(chainId);
|
|
3616
|
-
const base = UNISWAP_V3_ADDRESSES[id];
|
|
3617
|
-
const merged = {
|
|
3618
|
-
...base,
|
|
3619
|
-
...overrides
|
|
3620
|
-
};
|
|
3621
|
-
if (!merged.quoterV2 || !merged.nonfungiblePositionManager) throw new UnsupportedChainError(BigInt(id));
|
|
3622
|
-
return merged;
|
|
3623
|
-
}
|
|
3677
|
+
const STRIKE_CONVERSION_FACTOR = 16777216n;
|
|
3624
3678
|
|
|
3625
3679
|
//#endregion
|
|
3626
|
-
//#region src/
|
|
3680
|
+
//#region src/panoptic/v2/tokenId/encoding.ts
|
|
3627
3681
|
/**
|
|
3628
|
-
*
|
|
3629
|
-
*
|
|
3682
|
+
* Convert an unsigned encoded strike back to signed representation.
|
|
3683
|
+
*
|
|
3684
|
+
* @param encodedStrike - The unsigned encoded strike
|
|
3685
|
+
* @returns The signed strike tick
|
|
3630
3686
|
*/
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
inputs: [
|
|
3636
|
-
{
|
|
3637
|
-
name: "commands",
|
|
3638
|
-
type: "bytes"
|
|
3639
|
-
},
|
|
3640
|
-
{
|
|
3641
|
-
name: "inputs",
|
|
3642
|
-
type: "bytes[]"
|
|
3643
|
-
},
|
|
3644
|
-
{
|
|
3645
|
-
name: "deadline",
|
|
3646
|
-
type: "uint256"
|
|
3647
|
-
}
|
|
3648
|
-
],
|
|
3649
|
-
outputs: []
|
|
3650
|
-
}];
|
|
3651
|
-
|
|
3652
|
-
//#endregion
|
|
3653
|
-
//#region src/uniswap/v3/router/encodeSwap.ts
|
|
3654
|
-
/** Universal Router command byte for a v3 exact-in swap. */
|
|
3655
|
-
const V3_SWAP_EXACT_IN = 0;
|
|
3687
|
+
function convertStrikeToSigned(encodedStrike) {
|
|
3688
|
+
if (encodedStrike > 2n ** 23n - 1n) return encodedStrike - STRIKE_CONVERSION_FACTOR;
|
|
3689
|
+
return encodedStrike;
|
|
3690
|
+
}
|
|
3656
3691
|
/**
|
|
3657
|
-
*
|
|
3658
|
-
*
|
|
3692
|
+
* Get the bit offset for a leg at the given index.
|
|
3693
|
+
*
|
|
3694
|
+
* @param legIndex - The leg index (0-3)
|
|
3695
|
+
* @returns The bit offset from the start of the legs section
|
|
3659
3696
|
*/
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
const v3ExactInInputAbi = [
|
|
3663
|
-
{
|
|
3664
|
-
name: "recipient",
|
|
3665
|
-
type: "address"
|
|
3666
|
-
},
|
|
3667
|
-
{
|
|
3668
|
-
name: "amountIn",
|
|
3669
|
-
type: "uint256"
|
|
3670
|
-
},
|
|
3671
|
-
{
|
|
3672
|
-
name: "amountOutMinimum",
|
|
3673
|
-
type: "uint256"
|
|
3674
|
-
},
|
|
3675
|
-
{
|
|
3676
|
-
name: "path",
|
|
3677
|
-
type: "bytes"
|
|
3678
|
-
},
|
|
3679
|
-
{
|
|
3680
|
-
name: "payerIsUser",
|
|
3681
|
-
type: "bool"
|
|
3682
|
-
}
|
|
3683
|
-
];
|
|
3684
|
-
function assertUint128$1(amount) {
|
|
3685
|
-
if (amount < 0n || amount > UINT128_MAX$4) throw new AmountExceedsUint128Error(amount);
|
|
3697
|
+
function getLegOffset(legIndex) {
|
|
3698
|
+
return legIndex * TOKEN_ID_BITS.LEG_SIZE;
|
|
3686
3699
|
}
|
|
3687
3700
|
/**
|
|
3688
|
-
*
|
|
3701
|
+
* Decode a single leg from a TokenId.
|
|
3702
|
+
*
|
|
3703
|
+
* @param tokenId - The TokenId to decode
|
|
3704
|
+
* @param legIndex - The leg index (0-3)
|
|
3705
|
+
* @returns The decoded leg data
|
|
3689
3706
|
*/
|
|
3690
|
-
function
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3707
|
+
function decodeLeg(tokenId, legIndex) {
|
|
3708
|
+
const offset = getLegOffset(legIndex) + TOKEN_ID_BITS.POOL_ID_SIZE;
|
|
3709
|
+
const leg = tokenId >> offset & LEG_MASKS.LEG;
|
|
3710
|
+
const asset = leg & LEG_MASKS.ASSET;
|
|
3711
|
+
const optionRatio = leg >> LEG_BITS.RATIO_BIT & LEG_MASKS.RATIO;
|
|
3712
|
+
const isLong = (leg >> LEG_BITS.IS_LONG_BIT & LEG_MASKS.IS_LONG) === 1n;
|
|
3713
|
+
const tokenType = leg >> LEG_BITS.TOKEN_TYPE_BIT & LEG_MASKS.TOKEN_TYPE;
|
|
3714
|
+
const riskPartner = leg >> LEG_BITS.RISK_PARTNER_BIT & LEG_MASKS.RISK_PARTNER;
|
|
3715
|
+
const encodedStrike = leg >> LEG_BITS.STRIKE_BIT & LEG_MASKS.STRIKE;
|
|
3716
|
+
const strike = convertStrikeToSigned(encodedStrike);
|
|
3717
|
+
const width = leg >> LEG_BITS.WIDTH_BIT & LEG_MASKS.WIDTH;
|
|
3718
|
+
return {
|
|
3719
|
+
index: legIndex,
|
|
3720
|
+
asset,
|
|
3721
|
+
optionRatio,
|
|
3722
|
+
isLong,
|
|
3723
|
+
tokenType,
|
|
3724
|
+
riskPartner,
|
|
3725
|
+
strike,
|
|
3726
|
+
width
|
|
3727
|
+
};
|
|
3701
3728
|
}
|
|
3702
3729
|
/**
|
|
3703
|
-
*
|
|
3704
|
-
*
|
|
3730
|
+
* Decode all active legs from a TokenId.
|
|
3731
|
+
*
|
|
3732
|
+
* @param tokenId - The TokenId to decode
|
|
3733
|
+
* @returns Array of decoded legs (only active legs with optionRatio > 0)
|
|
3705
3734
|
*/
|
|
3706
|
-
function
|
|
3707
|
-
const
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3735
|
+
function decodeAllLegs(tokenId) {
|
|
3736
|
+
const legs = [];
|
|
3737
|
+
for (let i = 0n; i < TOKEN_ID_BITS.MAX_LEGS; i++) {
|
|
3738
|
+
const leg = decodeLeg(tokenId, i);
|
|
3739
|
+
if (leg.optionRatio > 0n) legs.push(leg);
|
|
3740
|
+
}
|
|
3741
|
+
return legs;
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3744
|
+
//#endregion
|
|
3745
|
+
//#region src/panoptic/v2/tokenId/deriveUniqueTokenId.ts
|
|
3746
|
+
const POOL_ID_MASK$2 = (1n << 64n) - 1n;
|
|
3747
|
+
const MAX_LEGS = TOKEN_ID_BITS.MAX_LEGS;
|
|
3748
|
+
const MAX_OPTION_RATIO = LEG_LIMITS.MAX_RATIO;
|
|
3749
|
+
const Q192$3 = 1n << 192n;
|
|
3750
|
+
|
|
3751
|
+
//#endregion
|
|
3752
|
+
//#region src/panoptic/v2/formatters/rates.ts
|
|
3753
|
+
const SECONDS_PER_DAY = 86400n;
|
|
3754
|
+
const DAYS_PER_YEAR = 365n;
|
|
3755
|
+
const SECONDS_PER_YEAR = SECONDS_PER_DAY * DAYS_PER_YEAR;
|
|
3756
|
+
|
|
3757
|
+
//#endregion
|
|
3758
|
+
//#region src/panoptic/v2/tokenId/generateOverlapping.ts
|
|
3759
|
+
const MAX_UINT64 = (1n << 64n) - 1n;
|
|
3760
|
+
const MAX_UINT128$2 = (1n << 128n) - 1n;
|
|
3761
|
+
const Q192$2 = 1n << 192n;
|
|
3762
|
+
|
|
3763
|
+
//#endregion
|
|
3764
|
+
//#region src/panoptic/v2/tokenId/split.ts
|
|
3765
|
+
const POOL_ID_MASK$1 = (1n << TOKEN_ID_BITS.POOL_ID_SIZE) - 1n;
|
|
3766
|
+
const MAX_UINT128$1 = (1n << 128n) - 1n;
|
|
3767
|
+
const MAX_UINT256 = (1n << 256n) - 1n;
|
|
3768
|
+
|
|
3769
|
+
//#endregion
|
|
3770
|
+
//#region src/uniswap/lpDeposit.ts
|
|
3771
|
+
const Q96$3 = 1n << 96n;
|
|
3772
|
+
const ceilDiv = (n, d) => (n + d - 1n) / d;
|
|
3773
|
+
/** Uniswap mint principal in raw token units, rounded up per leg, plus 5% headroom. */
|
|
3774
|
+
function getLpDepositAmounts(ranges, sqrtPriceX96) {
|
|
3775
|
+
return getLpDepositBreakdown(ranges, sqrtPriceX96).total;
|
|
3776
|
+
}
|
|
3777
|
+
/** Principal and separately identified funding headroom, in native token units. */
|
|
3778
|
+
function getLpDepositBreakdown(ranges, sqrtPriceX96) {
|
|
3779
|
+
if (sqrtPriceX96 <= 0n) throw new Error("Invalid pool price");
|
|
3780
|
+
let amount0 = 0n;
|
|
3781
|
+
let amount1 = 0n;
|
|
3782
|
+
for (const { tickLower, tickUpper, liquidity } of ranges) {
|
|
3783
|
+
if (!Number.isInteger(tickLower) || !Number.isInteger(tickUpper) || tickLower < -887272 || tickUpper > 887272 || tickLower >= tickUpper || liquidity < 0n) throw new Error("Invalid LP range");
|
|
3784
|
+
const lower = tickToSqrtPriceX96(BigInt(tickLower));
|
|
3785
|
+
const upper = tickToSqrtPriceX96(BigInt(tickUpper));
|
|
3786
|
+
const price = sqrtPriceX96 < lower ? lower : sqrtPriceX96 > upper ? upper : sqrtPriceX96;
|
|
3787
|
+
amount0 += ceilDiv(liquidity * Q96$3 * (upper - price), upper * price);
|
|
3788
|
+
amount1 += ceilDiv(liquidity * (price - lower), Q96$3);
|
|
3789
|
+
}
|
|
3790
|
+
const buffer = {
|
|
3791
|
+
amount0: ceilDiv(amount0 * 5n, 100n),
|
|
3792
|
+
amount1: ceilDiv(amount1 * 5n, 100n)
|
|
3793
|
+
};
|
|
3720
3794
|
return {
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3795
|
+
principal: {
|
|
3796
|
+
amount0,
|
|
3797
|
+
amount1
|
|
3798
|
+
},
|
|
3799
|
+
buffer,
|
|
3800
|
+
total: {
|
|
3801
|
+
amount0: amount0 + buffer.amount0,
|
|
3802
|
+
amount1: amount1 + buffer.amount1
|
|
3803
|
+
}
|
|
3727
3804
|
};
|
|
3728
3805
|
}
|
|
3729
|
-
/**
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
const
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3806
|
+
/** Full deployed liquidity value; never a leveraged protocol margin estimate. */
|
|
3807
|
+
function getLpPositionFunding(params) {
|
|
3808
|
+
const { tokenId, positionSize, tickSpacing, sqrtPriceX96, valuationSqrtPriceX96, quoteTokenIndex } = params;
|
|
3809
|
+
if (positionSize < 0n || tickSpacing <= 0n || valuationSqrtPriceX96 <= 0n) throw new Error("Invalid LP funding input");
|
|
3810
|
+
const legs = decodeAllLegs(tokenId);
|
|
3811
|
+
if (legs.length === 0 || legs.some((leg) => leg.isLong || leg.width === 0n)) throw new Error("AMM Liquidity requires short liquidity legs without loans or credits");
|
|
3812
|
+
const ranges = legs.map((leg) => {
|
|
3813
|
+
const width = leg.width * tickSpacing;
|
|
3814
|
+
const tickLower = leg.strike - width / 2n;
|
|
3815
|
+
const tickUpper = leg.strike + (width + 1n) / 2n;
|
|
3816
|
+
const lower = tickToSqrtPriceX96(tickLower);
|
|
3817
|
+
const upper = tickToSqrtPriceX96(tickUpper);
|
|
3818
|
+
const amount = positionSize * leg.optionRatio;
|
|
3819
|
+
const liquidity = leg.asset === 0n ? amount * (lower * upper / Q96$3) / (upper - lower) : amount * Q96$3 / (upper - lower);
|
|
3820
|
+
return {
|
|
3821
|
+
tickLower: Number(tickLower),
|
|
3822
|
+
tickUpper: Number(tickUpper),
|
|
3823
|
+
liquidity
|
|
3824
|
+
};
|
|
3739
3825
|
});
|
|
3826
|
+
const breakdown = getLpDepositBreakdown(ranges, sqrtPriceX96);
|
|
3827
|
+
const priceSquared = valuationSqrtPriceX96 * valuationSqrtPriceX96;
|
|
3828
|
+
const value = ({ amount0, amount1 }) => quoteTokenIndex === 0 ? amount0 + ceilDiv(amount1 * Q96$3 * Q96$3, priceSquared) : amount1 + ceilDiv(amount0 * priceSquared, Q96$3 * Q96$3);
|
|
3829
|
+
const principalInQuote = value(breakdown.principal);
|
|
3830
|
+
const totalInQuote = value(breakdown.total);
|
|
3740
3831
|
return {
|
|
3741
|
-
|
|
3742
|
-
|
|
3832
|
+
...breakdown,
|
|
3833
|
+
principalInQuote,
|
|
3834
|
+
bufferInQuote: totalInQuote - principalInQuote,
|
|
3835
|
+
totalInQuote
|
|
3836
|
+
};
|
|
3837
|
+
}
|
|
3838
|
+
/** Remaining deposit in the requested token mix, rounded up to cover the shortfall. */
|
|
3839
|
+
function getLpFundingDeposit({ funding, availableInQuote, minimumInQuote = 0n, quoteTokenIndex, valuationSqrtPriceX96, quotePercent }) {
|
|
3840
|
+
if (valuationSqrtPriceX96 <= 0n || quotePercent !== void 0 && (quotePercent < 0n || quotePercent > 100n)) throw new Error("Invalid LP funding split");
|
|
3841
|
+
const requiredInQuote = minimumInQuote > funding.totalInQuote ? minimumInQuote : funding.totalInQuote;
|
|
3842
|
+
const shortfallInQuote = requiredInQuote > availableInQuote ? requiredInQuote - availableInQuote : 0n;
|
|
3843
|
+
const defaultQuotePercent = funding.totalInQuote > 0n ? (quoteTokenIndex === 0 ? funding.total.amount0 : funding.total.amount1) * 100n / funding.totalInQuote : 100n;
|
|
3844
|
+
if (quotePercent === void 0) return {
|
|
3845
|
+
requiredInQuote,
|
|
3846
|
+
shortfallInQuote,
|
|
3847
|
+
defaultQuotePercent,
|
|
3848
|
+
amount0: funding.totalInQuote > 0n ? ceilDiv(funding.total.amount0 * shortfallInQuote, funding.totalInQuote) : 0n,
|
|
3849
|
+
amount1: funding.totalInQuote > 0n ? ceilDiv(funding.total.amount1 * shortfallInQuote, funding.totalInQuote) : 0n
|
|
3743
3850
|
};
|
|
3851
|
+
const quoteAmount = shortfallInQuote * quotePercent / 100n;
|
|
3852
|
+
const assetValue = shortfallInQuote - quoteAmount;
|
|
3853
|
+
const priceSquared = valuationSqrtPriceX96 * valuationSqrtPriceX96;
|
|
3854
|
+
return {
|
|
3855
|
+
requiredInQuote,
|
|
3856
|
+
shortfallInQuote,
|
|
3857
|
+
defaultQuotePercent,
|
|
3858
|
+
amount0: quoteTokenIndex === 0 ? quoteAmount : ceilDiv(assetValue * Q96$3 * Q96$3, priceSquared),
|
|
3859
|
+
amount1: quoteTokenIndex === 1 ? quoteAmount : ceilDiv(assetValue * priceSquared, Q96$3 * Q96$3)
|
|
3860
|
+
};
|
|
3861
|
+
}
|
|
3862
|
+
/** Largest raw size funded by available collateral, including native-token rounding. */
|
|
3863
|
+
function getMaxLpPositionSize(params) {
|
|
3864
|
+
if (params.minimumRequirement && (params.minimumRequirement.referenceSize <= 0n || params.minimumRequirement.requiredInQuote < 0n)) throw new Error("Invalid reference requirement");
|
|
3865
|
+
if (params.availableInQuote <= 0n) return 0n;
|
|
3866
|
+
let low = 0n;
|
|
3867
|
+
let high = (1n << 128n) - 1n;
|
|
3868
|
+
while (low < high) {
|
|
3869
|
+
const mid = (low + high + 1n) / 2n;
|
|
3870
|
+
const funding = getLpPositionFunding({
|
|
3871
|
+
...params,
|
|
3872
|
+
positionSize: mid
|
|
3873
|
+
});
|
|
3874
|
+
const minimum = params.minimumRequirement ? ceilDiv(params.minimumRequirement.requiredInQuote * mid, params.minimumRequirement.referenceSize) : 0n;
|
|
3875
|
+
if (funding.totalInQuote <= params.availableInQuote && minimum <= params.availableInQuote) low = mid;
|
|
3876
|
+
else high = mid - 1n;
|
|
3877
|
+
}
|
|
3878
|
+
return low;
|
|
3744
3879
|
}
|
|
3745
3880
|
|
|
3746
3881
|
//#endregion
|
|
3747
|
-
//#region src/uniswap/
|
|
3882
|
+
//#region src/uniswap/lpGreeks.ts
|
|
3883
|
+
const Q96$2 = 1n << 96n;
|
|
3884
|
+
const Q192$1 = 1n << 192n;
|
|
3885
|
+
/**
|
|
3886
|
+
* Compute the token0/token1 amounts backing `liquidity` over `[sqrtA, sqrtB]`
|
|
3887
|
+
* at the current price `sqrtP`, mirroring Uniswap's `LiquidityAmounts`
|
|
3888
|
+
* (`getAmountsForLiquidity`). All sqrt prices are X96. The current price is
|
|
3889
|
+
* clamped into the range, so out-of-range positions collapse to a single token.
|
|
3890
|
+
*/
|
|
3891
|
+
function getAmountsForLiquidity(sqrtPriceX96, sqrtRatioAX96, sqrtRatioBX96, liquidity) {
|
|
3892
|
+
const [sqrtA, sqrtB] = sqrtRatioAX96 <= sqrtRatioBX96 ? [sqrtRatioAX96, sqrtRatioBX96] : [sqrtRatioBX96, sqrtRatioAX96];
|
|
3893
|
+
const sqrtC = sqrtPriceX96 < sqrtA ? sqrtA : sqrtPriceX96 > sqrtB ? sqrtB : sqrtPriceX96;
|
|
3894
|
+
const amount0 = sqrtC < sqrtB ? liquidity * (sqrtB - sqrtC) * Q96$2 / (sqrtC * sqrtB) : 0n;
|
|
3895
|
+
const amount1 = sqrtC > sqrtA ? liquidity * (sqrtC - sqrtA) / Q96$2 : 0n;
|
|
3896
|
+
return {
|
|
3897
|
+
amount0,
|
|
3898
|
+
amount1
|
|
3899
|
+
};
|
|
3900
|
+
}
|
|
3748
3901
|
/**
|
|
3749
|
-
*
|
|
3902
|
+
* Greeks (value, delta, gamma) for a Uniswap LP position in the chosen asset
|
|
3903
|
+
* frame. See the module docs for the unit conventions.
|
|
3750
3904
|
*
|
|
3751
|
-
*
|
|
3752
|
-
*
|
|
3753
|
-
*
|
|
3905
|
+
* Closed forms (in-range, numeraire = token1 / asset = token0):
|
|
3906
|
+
* value = amount1 + amount0·P (P = sqrtP² / 2^192)
|
|
3907
|
+
* delta = amount0 (= dV/dP)
|
|
3908
|
+
* gamma = P²·d²V/dP² = -L·sqrt(P)/2 (short gamma)
|
|
3909
|
+
* The `assetIndex = 1` frame is the symmetric inverse (numeraire = token0).
|
|
3910
|
+
* Gamma is zero when the price is outside the range (no curvature there).
|
|
3754
3911
|
*/
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
}
|
|
3783
|
-
]
|
|
3784
|
-
}],
|
|
3785
|
-
outputs: [
|
|
3786
|
-
{
|
|
3787
|
-
name: "amountOut",
|
|
3788
|
-
type: "uint256"
|
|
3789
|
-
},
|
|
3790
|
-
{
|
|
3791
|
-
name: "sqrtPriceX96After",
|
|
3792
|
-
type: "uint160"
|
|
3793
|
-
},
|
|
3794
|
-
{
|
|
3795
|
-
name: "initializedTicksCrossed",
|
|
3796
|
-
type: "uint32"
|
|
3797
|
-
},
|
|
3798
|
-
{
|
|
3799
|
-
name: "gasEstimate",
|
|
3800
|
-
type: "uint256"
|
|
3801
|
-
}
|
|
3802
|
-
]
|
|
3803
|
-
}];
|
|
3912
|
+
function getLpGreeks(input) {
|
|
3913
|
+
const { liquidity, tickLower, tickUpper, currentTick, assetIndex } = input;
|
|
3914
|
+
const sqrtP = tickToSqrtPriceX96(currentTick);
|
|
3915
|
+
const sqrtA = tickToSqrtPriceX96(tickLower);
|
|
3916
|
+
const sqrtB = tickToSqrtPriceX96(tickUpper);
|
|
3917
|
+
const { amount0, amount1 } = getAmountsForLiquidity(sqrtP, sqrtA, sqrtB, liquidity);
|
|
3918
|
+
const sqrtP2 = sqrtP * sqrtP;
|
|
3919
|
+
const inRange = sqrtP > sqrtA && sqrtP < sqrtB;
|
|
3920
|
+
if (assetIndex === 0) {
|
|
3921
|
+
const value$1 = amount1 + amount0 * sqrtP2 / Q192$1;
|
|
3922
|
+
const delta$1 = amount0;
|
|
3923
|
+
const gamma$1 = inRange ? -(liquidity * sqrtP) / (2n * Q96$2) : 0n;
|
|
3924
|
+
return {
|
|
3925
|
+
value: value$1,
|
|
3926
|
+
delta: delta$1,
|
|
3927
|
+
gamma: gamma$1
|
|
3928
|
+
};
|
|
3929
|
+
}
|
|
3930
|
+
const value = amount0 + amount1 * Q192$1 / sqrtP2;
|
|
3931
|
+
const delta = amount1;
|
|
3932
|
+
const gamma = inRange ? -(liquidity * Q96$2) / (2n * sqrtP) : 0n;
|
|
3933
|
+
return {
|
|
3934
|
+
value,
|
|
3935
|
+
delta,
|
|
3936
|
+
gamma
|
|
3937
|
+
};
|
|
3938
|
+
}
|
|
3804
3939
|
|
|
3805
3940
|
//#endregion
|
|
3806
|
-
//#region src/uniswap/
|
|
3807
|
-
const
|
|
3808
|
-
|
|
3941
|
+
//#region src/uniswap/lpFeeReturn.ts
|
|
3942
|
+
const D = Decimal.clone({ precision: 60 });
|
|
3943
|
+
/** Simple fee APR over the full selected viewport, without compounding. */
|
|
3944
|
+
function annualizeLpFeeReturn(feeReturnPercent, durationSeconds) {
|
|
3945
|
+
if (durationSeconds <= 0n) throw new Error("APR requires a positive window duration");
|
|
3946
|
+
return new D(feeReturnPercent).mul(31536e3).div(durationSeconds.toString());
|
|
3947
|
+
}
|
|
3809
3948
|
/**
|
|
3810
|
-
*
|
|
3811
|
-
*
|
|
3812
|
-
*
|
|
3949
|
+
* Combine both fee tokens into a fee-only return on deployed LP capital.
|
|
3950
|
+
* Value both capital and fees at the window's opening price: this keeps fee
|
|
3951
|
+
* return flat when accrual stops and makes the ratio independent of quote
|
|
3952
|
+
* orientation. Raw token amounts already account for differing decimals.
|
|
3953
|
+
* Excludes principal price changes, impermanent loss and costs; not annualized.
|
|
3954
|
+
* Returns undefined when the position has no representable deployed capital.
|
|
3813
3955
|
*/
|
|
3814
|
-
|
|
3815
|
-
const
|
|
3816
|
-
if (
|
|
3817
|
-
|
|
3818
|
-
const
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
});
|
|
3833
|
-
const [amountOut, , , gasEstimate] = result;
|
|
3834
|
-
const amountOutMinimum = amountOut * (BPS_DENOMINATOR$2 - slippageBps) / BPS_DENOMINATOR$2;
|
|
3956
|
+
function getLpFeeReturnSeries({ points, ranges, startTick }) {
|
|
3957
|
+
const validTick$1 = (tick) => Number.isInteger(tick) && Math.abs(tick) <= 887272;
|
|
3958
|
+
if (!validTick$1(startTick)) throw new Error("Invalid valuation tick");
|
|
3959
|
+
const sqrtP = tickToSqrtPriceX96(BigInt(startTick));
|
|
3960
|
+
const price = new D(sqrtP.toString()).pow(2).div(new D((1n << 192n).toString()));
|
|
3961
|
+
let capital0 = 0n;
|
|
3962
|
+
let capital1 = 0n;
|
|
3963
|
+
for (const range of ranges) {
|
|
3964
|
+
if (!validTick$1(range.tickLower) || !validTick$1(range.tickUpper) || range.tickLower >= range.tickUpper || range.liquidity < 0n) throw new Error("Invalid LP range");
|
|
3965
|
+
const amounts = getAmountsForLiquidity(sqrtP, tickToSqrtPriceX96(BigInt(range.tickLower)), tickToSqrtPriceX96(BigInt(range.tickUpper)), range.liquidity);
|
|
3966
|
+
capital0 += amounts.amount0;
|
|
3967
|
+
capital1 += amounts.amount1;
|
|
3968
|
+
}
|
|
3969
|
+
const capital = new D(capital0.toString()).mul(price).plus(capital1.toString());
|
|
3970
|
+
if (capital.isZero()) return void 0;
|
|
3971
|
+
return points.map((point) => {
|
|
3972
|
+
if (point.fees0 < 0n || point.fees1 < 0n) throw new Error("Invalid fee amount");
|
|
3973
|
+
const fees = new D(point.fees0.toString()).mul(price).plus(point.fees1.toString());
|
|
3835
3974
|
return {
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
gasEstimate
|
|
3975
|
+
...point,
|
|
3976
|
+
feeReturnPercent: fees.div(capital).mul(100)
|
|
3839
3977
|
};
|
|
3840
|
-
}
|
|
3841
|
-
if (isRevert$1(err)) return null;
|
|
3842
|
-
throw err;
|
|
3843
|
-
}
|
|
3844
|
-
}
|
|
3845
|
-
/** True only for genuine contract reverts (missing pool / no liquidity). */
|
|
3846
|
-
function isRevert$1(err) {
|
|
3847
|
-
return err instanceof BaseError && err.walk((e) => e instanceof ContractFunctionRevertedError) instanceof ContractFunctionRevertedError;
|
|
3978
|
+
});
|
|
3848
3979
|
}
|
|
3849
3980
|
|
|
3850
3981
|
//#endregion
|
|
3851
|
-
//#region src/uniswap/v4/
|
|
3982
|
+
//#region src/uniswap/v4/router/errors.ts
|
|
3852
3983
|
/**
|
|
3853
|
-
*
|
|
3984
|
+
* The chain has no configured Uniswap v4 addresses and none were supplied via
|
|
3985
|
+
* overrides.
|
|
3854
3986
|
*/
|
|
3855
|
-
|
|
3987
|
+
var UnsupportedChainError = class extends PanopticError {
|
|
3988
|
+
name = "UnsupportedChainError";
|
|
3989
|
+
constructor(chainId, cause) {
|
|
3990
|
+
super(`Uniswap v4 router not configured for chain ${chainId}`, cause);
|
|
3991
|
+
this.chainId = chainId;
|
|
3992
|
+
}
|
|
3993
|
+
};
|
|
3856
3994
|
/**
|
|
3857
|
-
*
|
|
3858
|
-
*
|
|
3859
|
-
* Sourced from the official Uniswap v4 deployments. Add a chain here only after
|
|
3860
|
-
* verifying each address against the canonical Uniswap deployment listing —
|
|
3861
|
-
* Universal Router in particular is NOT the same address across chains.
|
|
3995
|
+
* `tokenIn` is neither `currency0` nor `currency1` of the resolved pool.
|
|
3862
3996
|
*/
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3997
|
+
var InvalidSwapTokenError = class extends PanopticError {
|
|
3998
|
+
name = "InvalidSwapTokenError";
|
|
3999
|
+
constructor(token, currency0, currency1, cause) {
|
|
4000
|
+
super(`Token ${token} is not part of the pool (currency0=${currency0}, currency1=${currency1})`, cause);
|
|
4001
|
+
this.token = token;
|
|
4002
|
+
this.currency0 = currency0;
|
|
4003
|
+
this.currency1 = currency1;
|
|
4004
|
+
}
|
|
4005
|
+
};
|
|
3870
4006
|
/**
|
|
3871
|
-
*
|
|
3872
|
-
*
|
|
3873
|
-
* @param chainId - Target chain ID.
|
|
3874
|
-
* @param overrides - Partial override of any address (e.g. for fork tests).
|
|
3875
|
-
* @returns Fully-resolved {@link UniswapV4Addresses}.
|
|
3876
|
-
* @throws {UnsupportedChainError} when the chain is not listed and the
|
|
3877
|
-
* overrides do not supply every required address.
|
|
4007
|
+
* An amount exceeds the uint128 range required by the v4 swap encoding.
|
|
3878
4008
|
*/
|
|
3879
|
-
|
|
3880
|
-
|
|
4009
|
+
var AmountExceedsUint128Error = class extends PanopticError {
|
|
4010
|
+
name = "AmountExceedsUint128Error";
|
|
4011
|
+
constructor(amount, cause) {
|
|
4012
|
+
super(`Amount ${amount} exceeds uint128 maximum`, cause);
|
|
4013
|
+
this.amount = amount;
|
|
4014
|
+
}
|
|
4015
|
+
};
|
|
4016
|
+
/**
|
|
4017
|
+
* A native-ETH swap needs a trailing Universal Router SWEEP to deliver the ETH
|
|
4018
|
+
* output (or refund the input overpay), but no `recipient` was supplied.
|
|
4019
|
+
*/
|
|
4020
|
+
var MissingSweepRecipientError = class extends PanopticError {
|
|
4021
|
+
name = "MissingSweepRecipientError";
|
|
4022
|
+
constructor(cause) {
|
|
4023
|
+
super("A recipient is required to sweep native ETH back to the user", cause);
|
|
4024
|
+
}
|
|
4025
|
+
};
|
|
4026
|
+
/**
|
|
4027
|
+
* The V4Quoter is not available for the chain (no fallback in v1).
|
|
4028
|
+
*/
|
|
4029
|
+
var QuoterUnavailableError = class extends PanopticError {
|
|
4030
|
+
name = "QuoterUnavailableError";
|
|
4031
|
+
constructor(chainId, cause) {
|
|
4032
|
+
super(`V4Quoter unavailable for chain ${chainId}`, cause);
|
|
4033
|
+
this.chainId = chainId;
|
|
4034
|
+
}
|
|
4035
|
+
};
|
|
4036
|
+
|
|
4037
|
+
//#endregion
|
|
4038
|
+
//#region src/uniswap/v3/addresses.ts
|
|
4039
|
+
/**
|
|
4040
|
+
* Verified Uniswap v3 QuoterV2 addresses keyed by chainId. Add a chain only
|
|
4041
|
+
* after verifying against the canonical Uniswap v3 deployment listing.
|
|
4042
|
+
*/
|
|
4043
|
+
const UNISWAP_V3_ADDRESSES = {
|
|
4044
|
+
1: {
|
|
4045
|
+
quoterV2: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e",
|
|
4046
|
+
nonfungiblePositionManager: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88"
|
|
4047
|
+
},
|
|
4048
|
+
4663: {
|
|
4049
|
+
quoterV2: "0x33e885ed0ec9bf04ecfb19341582aadcb4c8a9e7",
|
|
4050
|
+
nonfungiblePositionManager: "0x73991a25c818bf1f1128deaab1492d45638de0d3"
|
|
4051
|
+
}
|
|
4052
|
+
};
|
|
4053
|
+
/**
|
|
4054
|
+
* Resolve Uniswap v3 addresses for a chain, applying optional overrides.
|
|
4055
|
+
*
|
|
4056
|
+
* @throws {UnsupportedChainError} when the chain is not listed and overrides do
|
|
4057
|
+
* not supply every required address.
|
|
4058
|
+
*/
|
|
4059
|
+
function getUniswapV3Addresses(chainId, overrides) {
|
|
4060
|
+
const id = Number(chainId);
|
|
4061
|
+
const base = UNISWAP_V3_ADDRESSES[id];
|
|
3881
4062
|
const merged = {
|
|
3882
4063
|
...base,
|
|
3883
|
-
...overrides
|
|
3884
|
-
permit2: overrides?.permit2 ?? base?.permit2 ?? PERMIT2_ADDRESS
|
|
4064
|
+
...overrides
|
|
3885
4065
|
};
|
|
3886
|
-
if (merged.
|
|
4066
|
+
if (!merged.quoterV2 || !merged.nonfungiblePositionManager) throw new UnsupportedChainError(BigInt(id));
|
|
3887
4067
|
return merged;
|
|
3888
4068
|
}
|
|
3889
4069
|
|
|
3890
4070
|
//#endregion
|
|
3891
|
-
//#region src/uniswap/v4/
|
|
3892
|
-
/**
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
const TAKE = 14;
|
|
3902
|
-
/** v4 action: exact-out single-hop swap. */
|
|
3903
|
-
const SWAP_EXACT_OUT_SINGLE = 8;
|
|
3904
|
-
/** Universal Router command: sweep the router's token balance to a recipient. */
|
|
3905
|
-
const SWEEP = 4;
|
|
3906
|
-
/** v4 sentinel: take the full positive currency delta (used with TAKE). */
|
|
3907
|
-
const OPEN_DELTA = 0n;
|
|
3908
|
-
const UINT128_MAX$2 = (1n << 128n) - 1n;
|
|
3909
|
-
const poolKeyComponents = [
|
|
3910
|
-
{
|
|
3911
|
-
name: "currency0",
|
|
3912
|
-
type: "address"
|
|
3913
|
-
},
|
|
3914
|
-
{
|
|
3915
|
-
name: "currency1",
|
|
3916
|
-
type: "address"
|
|
3917
|
-
},
|
|
3918
|
-
{
|
|
3919
|
-
name: "fee",
|
|
3920
|
-
type: "uint24"
|
|
3921
|
-
},
|
|
3922
|
-
{
|
|
3923
|
-
name: "tickSpacing",
|
|
3924
|
-
type: "int24"
|
|
3925
|
-
},
|
|
3926
|
-
{
|
|
3927
|
-
name: "hooks",
|
|
3928
|
-
type: "address"
|
|
3929
|
-
}
|
|
3930
|
-
];
|
|
3931
|
-
const exactInputSingleParamsAbi = [{
|
|
3932
|
-
type: "tuple",
|
|
3933
|
-
components: [
|
|
3934
|
-
{
|
|
3935
|
-
name: "poolKey",
|
|
3936
|
-
type: "tuple",
|
|
3937
|
-
components: poolKeyComponents
|
|
3938
|
-
},
|
|
3939
|
-
{
|
|
3940
|
-
name: "zeroForOne",
|
|
3941
|
-
type: "bool"
|
|
3942
|
-
},
|
|
3943
|
-
{
|
|
3944
|
-
name: "amountIn",
|
|
3945
|
-
type: "uint128"
|
|
3946
|
-
},
|
|
3947
|
-
{
|
|
3948
|
-
name: "amountOutMinimum",
|
|
3949
|
-
type: "uint128"
|
|
3950
|
-
},
|
|
4071
|
+
//#region src/uniswap/v4/abis/universalRouter.ts
|
|
4072
|
+
/**
|
|
4073
|
+
* Minimal Universal Router ABI (only the `execute` overload we use).
|
|
4074
|
+
* @module uniswap/v4/abis/universalRouter
|
|
4075
|
+
*/
|
|
4076
|
+
const universalRouterAbi = [{
|
|
4077
|
+
type: "function",
|
|
4078
|
+
name: "execute",
|
|
4079
|
+
stateMutability: "payable",
|
|
4080
|
+
inputs: [
|
|
3951
4081
|
{
|
|
3952
|
-
name: "
|
|
4082
|
+
name: "commands",
|
|
3953
4083
|
type: "bytes"
|
|
3954
|
-
}
|
|
3955
|
-
]
|
|
3956
|
-
}];
|
|
3957
|
-
const exactOutputSingleParamsAbi = [{
|
|
3958
|
-
type: "tuple",
|
|
3959
|
-
components: [
|
|
3960
|
-
{
|
|
3961
|
-
name: "poolKey",
|
|
3962
|
-
type: "tuple",
|
|
3963
|
-
components: poolKeyComponents
|
|
3964
|
-
},
|
|
3965
|
-
{
|
|
3966
|
-
name: "zeroForOne",
|
|
3967
|
-
type: "bool"
|
|
3968
|
-
},
|
|
3969
|
-
{
|
|
3970
|
-
name: "amountOut",
|
|
3971
|
-
type: "uint128"
|
|
3972
4084
|
},
|
|
3973
4085
|
{
|
|
3974
|
-
name: "
|
|
3975
|
-
type: "
|
|
4086
|
+
name: "inputs",
|
|
4087
|
+
type: "bytes[]"
|
|
3976
4088
|
},
|
|
3977
4089
|
{
|
|
3978
|
-
name: "
|
|
3979
|
-
type: "
|
|
4090
|
+
name: "deadline",
|
|
4091
|
+
type: "uint256"
|
|
3980
4092
|
}
|
|
3981
|
-
]
|
|
3982
|
-
|
|
3983
|
-
const currencyAmountAbi = [{
|
|
3984
|
-
name: "currency",
|
|
3985
|
-
type: "address"
|
|
3986
|
-
}, {
|
|
3987
|
-
name: "amount",
|
|
3988
|
-
type: "uint256"
|
|
4093
|
+
],
|
|
4094
|
+
outputs: []
|
|
3989
4095
|
}];
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
4096
|
+
|
|
4097
|
+
//#endregion
|
|
4098
|
+
//#region src/uniswap/v3/router/encodeSwap.ts
|
|
4099
|
+
/** Universal Router command byte for a v3 exact-in swap. */
|
|
4100
|
+
const V3_SWAP_EXACT_IN = 0;
|
|
4101
|
+
/** Universal Router command byte for a v3 exact-out swap. */
|
|
4102
|
+
const V3_SWAP_EXACT_OUT = 1;
|
|
4103
|
+
/**
|
|
4104
|
+
* Universal Router recipient sentinel: the router maps `address(1)` to
|
|
4105
|
+
* `msg.sender` (the `execute` caller). Uniswap `Constants.MSG_SENDER`.
|
|
4106
|
+
*/
|
|
4107
|
+
const MSG_SENDER = "0x0000000000000000000000000000000000000001";
|
|
4108
|
+
const UINT128_MAX$4 = (1n << 128n) - 1n;
|
|
4109
|
+
const v3ExactInInputAbi = [
|
|
3995
4110
|
{
|
|
3996
4111
|
name: "recipient",
|
|
3997
4112
|
type: "address"
|
|
3998
4113
|
},
|
|
3999
4114
|
{
|
|
4000
|
-
name: "
|
|
4115
|
+
name: "amountIn",
|
|
4001
4116
|
type: "uint256"
|
|
4002
|
-
}
|
|
4003
|
-
];
|
|
4004
|
-
const sweepParamsAbi = [
|
|
4117
|
+
},
|
|
4005
4118
|
{
|
|
4006
|
-
name: "
|
|
4007
|
-
type: "
|
|
4119
|
+
name: "amountOutMinimum",
|
|
4120
|
+
type: "uint256"
|
|
4008
4121
|
},
|
|
4009
4122
|
{
|
|
4010
|
-
name: "
|
|
4011
|
-
type: "
|
|
4123
|
+
name: "path",
|
|
4124
|
+
type: "bytes"
|
|
4012
4125
|
},
|
|
4013
4126
|
{
|
|
4014
|
-
name: "
|
|
4015
|
-
type: "
|
|
4127
|
+
name: "payerIsUser",
|
|
4128
|
+
type: "bool"
|
|
4016
4129
|
}
|
|
4017
4130
|
];
|
|
4018
|
-
function assertUint128(amount) {
|
|
4019
|
-
if (amount < 0n || amount > UINT128_MAX$
|
|
4131
|
+
function assertUint128$1(amount) {
|
|
4132
|
+
if (amount < 0n || amount > UINT128_MAX$4) throw new AmountExceedsUint128Error(amount);
|
|
4020
4133
|
}
|
|
4021
4134
|
/**
|
|
4022
|
-
* Encode the
|
|
4023
|
-
* router's full native-ETH balance (above `amountMin = 0`) to `recipient`.
|
|
4135
|
+
* Encode the packed v3 single-hop path `tokenIn ++ fee(uint24) ++ tokenOut`.
|
|
4024
4136
|
*/
|
|
4025
|
-
function
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4137
|
+
function encodeV3Path(tokenIn, fee, tokenOut) {
|
|
4138
|
+
if (fee < 0n || fee > 0xffffffn) throw new PanopticError(`v3 fee ${fee} exceeds uint24`);
|
|
4139
|
+
return encodePacked([
|
|
4140
|
+
"address",
|
|
4141
|
+
"uint24",
|
|
4142
|
+
"address"
|
|
4143
|
+
], [
|
|
4144
|
+
tokenIn,
|
|
4145
|
+
Number(fee),
|
|
4146
|
+
tokenOut
|
|
4030
4147
|
]);
|
|
4031
4148
|
}
|
|
4032
4149
|
/**
|
|
4033
|
-
* Build the
|
|
4034
|
-
*
|
|
4035
|
-
* Native-ETH output is taken with the explicit-recipient `TAKE` action so the
|
|
4036
|
-
* router forwards the ETH straight to `recipient` (the implicit-`msgSender`
|
|
4037
|
-
* `TAKE_ALL` leaves native ETH held by the router). ERC20 output uses
|
|
4038
|
-
* `TAKE_ALL`, which already credits the caller.
|
|
4039
|
-
*
|
|
4040
|
-
* @param nativeAmount - TAKE amount for native-ETH output: `OPEN_DELTA` (0) to
|
|
4041
|
-
* take the full credit (exact-in, protected by the swap's min) or the exact
|
|
4042
|
-
* output (exact-out).
|
|
4043
|
-
* @param erc20Amount - TAKE_ALL min/amount for ERC20 output: `amountOutMinimum`
|
|
4044
|
-
* (exact-in) or the exact output (exact-out).
|
|
4150
|
+
* Build the typed `execute(...)` args for an exact-in single-hop v3 swap whose
|
|
4151
|
+
* output is delivered to the `execute` caller (recipient = MSG_SENDER).
|
|
4045
4152
|
*/
|
|
4046
|
-
function
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
nativeAmount
|
|
4055
|
-
])
|
|
4056
|
-
};
|
|
4057
|
-
}
|
|
4058
|
-
return {
|
|
4059
|
-
action: TAKE_ALL,
|
|
4060
|
-
param: encodeAbiParameters(currencyAmountAbi, [tokenOut, erc20Amount])
|
|
4061
|
-
};
|
|
4062
|
-
}
|
|
4063
|
-
/**
|
|
4064
|
-
* Build the typed `execute(...)` args + msg.value for an exact-in single-hop v4
|
|
4065
|
-
* swap. Use this when submitting via viem (`writeContract` / `submitWrite`).
|
|
4066
|
-
*
|
|
4067
|
-
* When `tokenOut` is native ETH the explicit-recipient `TAKE` action forwards
|
|
4068
|
-
* the bought ETH to `recipient` (no trailing SWEEP is needed for exact-in).
|
|
4069
|
-
*
|
|
4070
|
-
* @returns `args` ready to spread into `execute` and the ETH `value` to send
|
|
4071
|
-
* (= `amountIn` for native-ETH input, otherwise `0n`).
|
|
4072
|
-
*/
|
|
4073
|
-
function buildV4SwapExecuteArgs(args) {
|
|
4074
|
-
const { poolKey, zeroForOne, amountIn, amountOutMinimum, tokenIn, tokenOut, deadline, recipient, hookData = "0x" } = args;
|
|
4075
|
-
assertUint128(amountIn);
|
|
4076
|
-
assertUint128(amountOutMinimum);
|
|
4077
|
-
const swapParam = encodeAbiParameters(exactInputSingleParamsAbi, [{
|
|
4078
|
-
poolKey: {
|
|
4079
|
-
currency0: poolKey.currency0,
|
|
4080
|
-
currency1: poolKey.currency1,
|
|
4081
|
-
fee: Number(poolKey.fee),
|
|
4082
|
-
tickSpacing: Number(poolKey.tickSpacing),
|
|
4083
|
-
hooks: poolKey.hooks
|
|
4084
|
-
},
|
|
4085
|
-
zeroForOne,
|
|
4153
|
+
function buildV3SwapExecuteArgs(args) {
|
|
4154
|
+
const { tokenIn, tokenOut, fee, amountIn, amountOutMinimum, deadline } = args;
|
|
4155
|
+
if (tokenIn === zeroAddress || tokenOut === zeroAddress) throw new PanopticError("native ETH is not supported by the v3 exact-in router builder");
|
|
4156
|
+
assertUint128$1(amountIn);
|
|
4157
|
+
assertUint128$1(amountOutMinimum);
|
|
4158
|
+
const path = encodeV3Path(tokenIn, fee, tokenOut);
|
|
4159
|
+
const input = encodeAbiParameters(v3ExactInInputAbi, [
|
|
4160
|
+
MSG_SENDER,
|
|
4086
4161
|
amountIn,
|
|
4087
4162
|
amountOutMinimum,
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
const settleParam = encodeAbiParameters(currencyAmountAbi, [tokenIn, amountIn]);
|
|
4091
|
-
const take = buildOutputTake(tokenOut, recipient, OPEN_DELTA, amountOutMinimum);
|
|
4092
|
-
const actions = encodePacked([
|
|
4093
|
-
"uint8",
|
|
4094
|
-
"uint8",
|
|
4095
|
-
"uint8"
|
|
4096
|
-
], [
|
|
4097
|
-
SWAP_EXACT_IN_SINGLE,
|
|
4098
|
-
SETTLE_ALL,
|
|
4099
|
-
take.action
|
|
4163
|
+
path,
|
|
4164
|
+
true
|
|
4100
4165
|
]);
|
|
4101
|
-
const
|
|
4102
|
-
name: "actions",
|
|
4103
|
-
type: "bytes"
|
|
4104
|
-
}, {
|
|
4105
|
-
name: "params",
|
|
4106
|
-
type: "bytes[]"
|
|
4107
|
-
}], [actions, [
|
|
4108
|
-
swapParam,
|
|
4109
|
-
settleParam,
|
|
4110
|
-
take.param
|
|
4111
|
-
]]);
|
|
4112
|
-
const commands = encodePacked(["uint8"], [V4_SWAP]);
|
|
4113
|
-
const value = tokenIn === zeroAddress ? amountIn : 0n;
|
|
4166
|
+
const commands = encodePacked(["uint8"], [V3_SWAP_EXACT_IN]);
|
|
4114
4167
|
return {
|
|
4115
4168
|
args: [
|
|
4116
4169
|
commands,
|
|
4117
|
-
[
|
|
4170
|
+
[input],
|
|
4118
4171
|
deadline
|
|
4119
4172
|
],
|
|
4120
|
-
value
|
|
4173
|
+
value: 0n
|
|
4121
4174
|
};
|
|
4122
4175
|
}
|
|
4123
4176
|
/**
|
|
4124
|
-
* Build the `execute(commands, inputs, deadline)` calldata
|
|
4125
|
-
*
|
|
4126
|
-
*
|
|
4127
|
-
* @returns The encoded calldata and the ETH `value` to send.
|
|
4177
|
+
* Build the `execute(commands, inputs, deadline)` calldata for an exact-in
|
|
4178
|
+
* single-hop v3 swap.
|
|
4128
4179
|
*/
|
|
4129
|
-
function
|
|
4130
|
-
const { args: executeArgs, value } =
|
|
4180
|
+
function buildV3SwapExecuteCalldata(args) {
|
|
4181
|
+
const { args: executeArgs, value } = buildV3SwapExecuteArgs(args);
|
|
4131
4182
|
const data = encodeFunctionData({
|
|
4132
4183
|
abi: universalRouterAbi,
|
|
4133
4184
|
functionName: "execute",
|
|
@@ -4138,81 +4189,57 @@ function buildV4SwapExecuteCalldata(args) {
|
|
|
4138
4189
|
value
|
|
4139
4190
|
};
|
|
4140
4191
|
}
|
|
4192
|
+
const v3ExactOutInputAbi = [
|
|
4193
|
+
{
|
|
4194
|
+
name: "recipient",
|
|
4195
|
+
type: "address"
|
|
4196
|
+
},
|
|
4197
|
+
{
|
|
4198
|
+
name: "amountOut",
|
|
4199
|
+
type: "uint256"
|
|
4200
|
+
},
|
|
4201
|
+
{
|
|
4202
|
+
name: "amountInMaximum",
|
|
4203
|
+
type: "uint256"
|
|
4204
|
+
},
|
|
4205
|
+
{
|
|
4206
|
+
name: "path",
|
|
4207
|
+
type: "bytes"
|
|
4208
|
+
},
|
|
4209
|
+
{
|
|
4210
|
+
name: "payerIsUser",
|
|
4211
|
+
type: "bool"
|
|
4212
|
+
}
|
|
4213
|
+
];
|
|
4141
4214
|
/**
|
|
4142
|
-
* Build the typed `execute(...)` args
|
|
4143
|
-
*
|
|
4144
|
-
*
|
|
4145
|
-
* For native-ETH input, `value` is `amountInMaximum` (an overpay) and a trailing
|
|
4146
|
-
* `SWEEP` action refunds the unused ETH to `recipient`. For ERC20 input, Permit2
|
|
4147
|
-
* pulls only the settled amount, so no SWEEP is appended and `value` is `0n`.
|
|
4148
|
-
*
|
|
4149
|
-
* @returns `args` ready to spread into `execute` and the ETH `value` to send.
|
|
4215
|
+
* Build the typed `execute(...)` args for an exact-out single-hop v3 swap.
|
|
4216
|
+
* The v3 exact-out path is reversed: `tokenOut ++ fee ++ tokenIn`.
|
|
4150
4217
|
*/
|
|
4151
|
-
function
|
|
4152
|
-
const {
|
|
4153
|
-
|
|
4154
|
-
assertUint128(
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
fee: Number(poolKey.fee),
|
|
4160
|
-
tickSpacing: Number(poolKey.tickSpacing),
|
|
4161
|
-
hooks: poolKey.hooks
|
|
4162
|
-
},
|
|
4163
|
-
zeroForOne,
|
|
4218
|
+
function buildV3ExactOutSwapExecuteArgs(args) {
|
|
4219
|
+
const { tokenIn, tokenOut, fee, amountOut, amountInMaximum, deadline } = args;
|
|
4220
|
+
if (tokenIn === zeroAddress || tokenOut === zeroAddress) throw new PanopticError("native ETH is not supported by the v3 exact-out router builder");
|
|
4221
|
+
assertUint128$1(amountOut);
|
|
4222
|
+
assertUint128$1(amountInMaximum);
|
|
4223
|
+
const path = encodeV3Path(tokenOut, fee, tokenIn);
|
|
4224
|
+
const input = encodeAbiParameters(v3ExactOutInputAbi, [
|
|
4225
|
+
MSG_SENDER,
|
|
4164
4226
|
amountOut,
|
|
4165
4227
|
amountInMaximum,
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
const settleParam = encodeAbiParameters(currencyAmountAbi, [tokenIn, amountInMaximum]);
|
|
4169
|
-
const take = buildOutputTake(tokenOut, recipient, amountOut, amountOut);
|
|
4170
|
-
const actions = encodePacked([
|
|
4171
|
-
"uint8",
|
|
4172
|
-
"uint8",
|
|
4173
|
-
"uint8"
|
|
4174
|
-
], [
|
|
4175
|
-
SWAP_EXACT_OUT_SINGLE,
|
|
4176
|
-
SETTLE_ALL,
|
|
4177
|
-
take.action
|
|
4228
|
+
path,
|
|
4229
|
+
true
|
|
4178
4230
|
]);
|
|
4179
|
-
const
|
|
4180
|
-
name: "actions",
|
|
4181
|
-
type: "bytes"
|
|
4182
|
-
}, {
|
|
4183
|
-
name: "params",
|
|
4184
|
-
type: "bytes[]"
|
|
4185
|
-
}], [actions, [
|
|
4186
|
-
swapParam,
|
|
4187
|
-
settleParam,
|
|
4188
|
-
take.param
|
|
4189
|
-
]]);
|
|
4190
|
-
const isNativeIn = tokenIn === zeroAddress;
|
|
4191
|
-
const commandList = [V4_SWAP];
|
|
4192
|
-
const inputs = [v4Input];
|
|
4193
|
-
if (isNativeIn) {
|
|
4194
|
-
commandList.push(SWEEP);
|
|
4195
|
-
inputs.push(encodeEthSweepInput(recipient));
|
|
4196
|
-
}
|
|
4197
|
-
const commands = encodePacked(commandList.map(() => "uint8"), commandList);
|
|
4198
|
-
const value = isNativeIn ? amountInMaximum : 0n;
|
|
4231
|
+
const commands = encodePacked(["uint8"], [V3_SWAP_EXACT_OUT]);
|
|
4199
4232
|
return {
|
|
4200
4233
|
args: [
|
|
4201
4234
|
commands,
|
|
4202
|
-
|
|
4235
|
+
[input],
|
|
4203
4236
|
deadline
|
|
4204
4237
|
],
|
|
4205
|
-
value
|
|
4238
|
+
value: 0n
|
|
4206
4239
|
};
|
|
4207
4240
|
}
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
* exact-out single-hop v4 swap.
|
|
4211
|
-
*
|
|
4212
|
-
* @returns The encoded calldata and the ETH `value` to send.
|
|
4213
|
-
*/
|
|
4214
|
-
function buildV4ExactOutSwapExecuteCalldata(args) {
|
|
4215
|
-
const { args: executeArgs, value } = buildV4ExactOutSwapExecuteArgs(args);
|
|
4241
|
+
function buildV3ExactOutSwapExecuteCalldata(args) {
|
|
4242
|
+
const { args: executeArgs, value } = buildV3ExactOutSwapExecuteArgs(args);
|
|
4216
4243
|
const data = encodeFunctionData({
|
|
4217
4244
|
abi: universalRouterAbi,
|
|
4218
4245
|
functionName: "execute",
|
|
@@ -4225,351 +4252,266 @@ function buildV4ExactOutSwapExecuteCalldata(args) {
|
|
|
4225
4252
|
}
|
|
4226
4253
|
|
|
4227
4254
|
//#endregion
|
|
4228
|
-
//#region src/
|
|
4229
|
-
/**
|
|
4230
|
-
* Get block metadata for a given block.
|
|
4231
|
-
* Used to tag all read results with same-block consistency proof.
|
|
4232
|
-
*
|
|
4233
|
-
* @param params - The parameters
|
|
4234
|
-
* @returns Block metadata
|
|
4235
|
-
*/
|
|
4236
|
-
async function getBlockMeta(params) {
|
|
4237
|
-
const { client, blockNumber } = params;
|
|
4238
|
-
const block = await client.getBlock({
|
|
4239
|
-
blockNumber,
|
|
4240
|
-
includeTransactions: false
|
|
4241
|
-
});
|
|
4242
|
-
return {
|
|
4243
|
-
blockNumber: block.number,
|
|
4244
|
-
blockHash: block.hash,
|
|
4245
|
-
blockTimestamp: block.timestamp
|
|
4246
|
-
};
|
|
4247
|
-
}
|
|
4248
|
-
|
|
4249
|
-
//#endregion
|
|
4250
|
-
//#region src/panoptic/v2/writes/utils.ts
|
|
4251
|
-
/**
|
|
4252
|
-
* Decode PositionBalance packed data.
|
|
4253
|
-
*
|
|
4254
|
-
* Layout (from LSB to MSB):
|
|
4255
|
-
* - positionSize: bits 0-127 (uint128)
|
|
4256
|
-
* - poolUtilization0: bits 128-143 (uint16)
|
|
4257
|
-
* - poolUtilization1: bits 144-159 (uint16)
|
|
4258
|
-
* - tickAtMint: bits 160-183 (int24)
|
|
4259
|
-
* - timestampAtMint: bits 184-215 (uint32)
|
|
4260
|
-
* - blockAtMint: bits 216-254 (uint39)
|
|
4261
|
-
* - swapAtMint: bit 255 (bool)
|
|
4262
|
-
*/
|
|
4263
|
-
function decodePositionBalance(balanceData) {
|
|
4264
|
-
const positionSize = balanceData & (1n << 128n) - 1n;
|
|
4265
|
-
const poolUtilization0 = balanceData >> 128n & 0xffffn;
|
|
4266
|
-
const poolUtilization1 = balanceData >> 144n & 0xffffn;
|
|
4267
|
-
let tickAtMint = balanceData >> 160n & 0xffffffn;
|
|
4268
|
-
if (tickAtMint > 0x7fffffn) tickAtMint = tickAtMint - 0x1000000n;
|
|
4269
|
-
const timestampAtMint = balanceData >> 184n & 0xffffffffn;
|
|
4270
|
-
const blockAtMint = balanceData >> 216n & (1n << 39n) - 1n;
|
|
4271
|
-
const swapAtMint = balanceData >> 255n === 1n;
|
|
4272
|
-
return {
|
|
4273
|
-
positionSize,
|
|
4274
|
-
poolUtilization0,
|
|
4275
|
-
poolUtilization1,
|
|
4276
|
-
tickAtMint,
|
|
4277
|
-
timestampAtMint,
|
|
4278
|
-
blockAtMint,
|
|
4279
|
-
swapAtMint
|
|
4280
|
-
};
|
|
4281
|
-
}
|
|
4282
|
-
/**
|
|
4283
|
-
* Decode LeftRightSigned packed value.
|
|
4284
|
-
*/
|
|
4285
|
-
function decodeLeftRightSigned(value) {
|
|
4286
|
-
let right = value & (1n << 128n) - 1n;
|
|
4287
|
-
let left = value >> 128n;
|
|
4288
|
-
if (right >= 1n << 127n) right = right - (1n << 128n);
|
|
4289
|
-
if (left >= 1n << 127n) left = left - (1n << 128n);
|
|
4290
|
-
return {
|
|
4291
|
-
right,
|
|
4292
|
-
left
|
|
4293
|
-
};
|
|
4294
|
-
}
|
|
4255
|
+
//#region src/uniswap/v3/abis/quoterV2.ts
|
|
4295
4256
|
/**
|
|
4296
|
-
*
|
|
4257
|
+
* Minimal Uniswap v3 QuoterV2 ABI.
|
|
4297
4258
|
*
|
|
4298
|
-
*
|
|
4299
|
-
*
|
|
4259
|
+
* QuoterV2 is revert/staticcall-based (nonpayable but intended for `eth_call` /
|
|
4260
|
+
* `simulateContract`), so it must be simulated, not read.
|
|
4261
|
+
* @module uniswap/v3/abis/quoterV2
|
|
4300
4262
|
*/
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
};
|
|
4310
|
-
try {
|
|
4311
|
-
const poolEvent = decodeEventLog({
|
|
4312
|
-
abi: panopticPoolV2Abi,
|
|
4313
|
-
data: log.data,
|
|
4314
|
-
topics: log.topics
|
|
4315
|
-
});
|
|
4316
|
-
if (poolEvent.eventName === "OptionMinted") {
|
|
4317
|
-
const args = poolEvent.args;
|
|
4318
|
-
const balance = decodePositionBalance(args.balanceData);
|
|
4319
|
-
events.push({
|
|
4320
|
-
type: "OptionMinted",
|
|
4321
|
-
...baseEvent,
|
|
4322
|
-
recipient: args.recipient,
|
|
4323
|
-
tokenId: args.tokenId,
|
|
4324
|
-
positionSize: balance.positionSize,
|
|
4325
|
-
poolUtilization0: balance.poolUtilization0,
|
|
4326
|
-
poolUtilization1: balance.poolUtilization1,
|
|
4327
|
-
tickAtMint: balance.tickAtMint,
|
|
4328
|
-
timestampAtMint: balance.timestampAtMint,
|
|
4329
|
-
blockAtMint: balance.blockAtMint,
|
|
4330
|
-
swapAtMint: balance.swapAtMint
|
|
4331
|
-
});
|
|
4332
|
-
} else if (poolEvent.eventName === "OptionBurnt") {
|
|
4333
|
-
const args = poolEvent.args;
|
|
4334
|
-
events.push({
|
|
4335
|
-
type: "OptionBurnt",
|
|
4336
|
-
...baseEvent,
|
|
4337
|
-
recipient: args.recipient,
|
|
4338
|
-
tokenId: args.tokenId,
|
|
4339
|
-
positionSize: args.positionSize,
|
|
4340
|
-
premiaByLeg: [
|
|
4341
|
-
args.premiaByLeg[0] ?? 0n,
|
|
4342
|
-
args.premiaByLeg[1] ?? 0n,
|
|
4343
|
-
args.premiaByLeg[2] ?? 0n,
|
|
4344
|
-
args.premiaByLeg[3] ?? 0n
|
|
4345
|
-
]
|
|
4346
|
-
});
|
|
4347
|
-
} else if (poolEvent.eventName === "AccountLiquidated") {
|
|
4348
|
-
const args = poolEvent.args;
|
|
4349
|
-
const { right, left } = decodeLeftRightSigned(args.bonusAmounts);
|
|
4350
|
-
events.push({
|
|
4351
|
-
type: "AccountLiquidated",
|
|
4352
|
-
...baseEvent,
|
|
4353
|
-
liquidator: args.liquidator,
|
|
4354
|
-
liquidatee: args.liquidatee,
|
|
4355
|
-
bonusAmount0: right,
|
|
4356
|
-
bonusAmount1: left
|
|
4357
|
-
});
|
|
4358
|
-
} else if (poolEvent.eventName === "ForcedExercised") {
|
|
4359
|
-
const args = poolEvent.args;
|
|
4360
|
-
const { right, left } = decodeLeftRightSigned(args.exerciseFee);
|
|
4361
|
-
events.push({
|
|
4362
|
-
type: "ForcedExercised",
|
|
4363
|
-
...baseEvent,
|
|
4364
|
-
exercisor: args.exercisor,
|
|
4365
|
-
user: args.user,
|
|
4366
|
-
tokenId: args.tokenId,
|
|
4367
|
-
exerciseFee0: right,
|
|
4368
|
-
exerciseFee1: left
|
|
4369
|
-
});
|
|
4370
|
-
} else if (poolEvent.eventName === "PremiumSettled") {
|
|
4371
|
-
const args = poolEvent.args;
|
|
4372
|
-
const { right, left } = decodeLeftRightSigned(args.settledAmounts);
|
|
4373
|
-
events.push({
|
|
4374
|
-
type: "PremiumSettled",
|
|
4375
|
-
...baseEvent,
|
|
4376
|
-
user: args.user,
|
|
4377
|
-
tokenId: args.tokenId,
|
|
4378
|
-
legIndex: args.legIndex,
|
|
4379
|
-
settledAmount0: right,
|
|
4380
|
-
settledAmount1: left
|
|
4381
|
-
});
|
|
4382
|
-
}
|
|
4383
|
-
} catch {
|
|
4384
|
-
try {
|
|
4385
|
-
const collateralEvent = decodeEventLog({
|
|
4386
|
-
abi: collateralTrackerV2Abi,
|
|
4387
|
-
data: log.data,
|
|
4388
|
-
topics: log.topics
|
|
4389
|
-
});
|
|
4390
|
-
if (collateralEvent.eventName === "Deposit") {
|
|
4391
|
-
const args = collateralEvent.args;
|
|
4392
|
-
events.push({
|
|
4393
|
-
type: "Deposit",
|
|
4394
|
-
...baseEvent,
|
|
4395
|
-
sender: args.sender,
|
|
4396
|
-
owner: args.owner,
|
|
4397
|
-
assets: args.assets,
|
|
4398
|
-
shares: args.shares
|
|
4399
|
-
});
|
|
4400
|
-
} else if (collateralEvent.eventName === "Withdraw") {
|
|
4401
|
-
const args = collateralEvent.args;
|
|
4402
|
-
events.push({
|
|
4403
|
-
type: "Withdraw",
|
|
4404
|
-
...baseEvent,
|
|
4405
|
-
sender: args.sender,
|
|
4406
|
-
receiver: args.receiver,
|
|
4407
|
-
owner: args.owner,
|
|
4408
|
-
assets: args.assets,
|
|
4409
|
-
shares: args.shares
|
|
4410
|
-
});
|
|
4411
|
-
}
|
|
4412
|
-
} catch {}
|
|
4413
|
-
}
|
|
4414
|
-
}
|
|
4415
|
-
return events;
|
|
4416
|
-
}
|
|
4417
|
-
/**
|
|
4418
|
-
* Create a TxResult from a transaction hash.
|
|
4419
|
-
*
|
|
4420
|
-
* @param client - The public client
|
|
4421
|
-
* @param hash - The transaction hash
|
|
4422
|
-
* @returns TxResult with wait function
|
|
4423
|
-
*/
|
|
4424
|
-
function createTxResult(client, hash) {
|
|
4425
|
-
return {
|
|
4426
|
-
hash,
|
|
4427
|
-
wait: async (confirmations) => {
|
|
4428
|
-
const receipt = await client.waitForTransactionReceipt({
|
|
4429
|
-
hash,
|
|
4430
|
-
confirmations: confirmations !== void 0 ? Number(confirmations) : void 0
|
|
4431
|
-
});
|
|
4432
|
-
const events = parsePanopticEvents(receipt.logs);
|
|
4433
|
-
return {
|
|
4434
|
-
hash: receipt.transactionHash,
|
|
4435
|
-
blockNumber: receipt.blockNumber,
|
|
4436
|
-
blockHash: receipt.blockHash,
|
|
4437
|
-
gasUsed: receipt.gasUsed,
|
|
4438
|
-
status: receipt.status === "success" ? "success" : "reverted",
|
|
4439
|
-
events
|
|
4440
|
-
};
|
|
4441
|
-
}
|
|
4442
|
-
};
|
|
4443
|
-
}
|
|
4444
|
-
/**
|
|
4445
|
-
* Submit a write operation, supporting both direct wallet submission
|
|
4446
|
-
* and custom broadcaster paths with gas overrides.
|
|
4447
|
-
*
|
|
4448
|
-
* Normal path (no broadcaster): calls walletClient.writeContract() with gas overrides.
|
|
4449
|
-
* Broadcaster path: prepareTransactionRequest → apply overrides → signTransaction → broadcast.
|
|
4450
|
-
*
|
|
4451
|
-
* @param params - Submit write parameters
|
|
4452
|
-
* @returns TxResult
|
|
4453
|
-
*/
|
|
4454
|
-
async function submitWrite(params) {
|
|
4455
|
-
const { client, walletClient, account, address, abi, functionName, args, value, txOverrides } = params;
|
|
4456
|
-
const broadcaster = txOverrides?.broadcaster;
|
|
4457
|
-
if (broadcaster) {
|
|
4458
|
-
const data = encodeFunctionData({
|
|
4459
|
-
abi,
|
|
4460
|
-
functionName,
|
|
4461
|
-
args
|
|
4462
|
-
});
|
|
4463
|
-
const broadcastAccount = walletClient.account ?? account;
|
|
4464
|
-
const request = await walletClient.prepareTransactionRequest({
|
|
4465
|
-
account: broadcastAccount,
|
|
4466
|
-
to: address,
|
|
4467
|
-
chain: walletClient.chain,
|
|
4468
|
-
data,
|
|
4469
|
-
...value !== void 0 && { value },
|
|
4470
|
-
...txOverrides?.maxFeePerGas !== void 0 && { maxFeePerGas: txOverrides.maxFeePerGas },
|
|
4471
|
-
...txOverrides?.maxPriorityFeePerGas !== void 0 && { maxPriorityFeePerGas: txOverrides.maxPriorityFeePerGas },
|
|
4472
|
-
...txOverrides?.gas !== void 0 && { gas: txOverrides.gas },
|
|
4473
|
-
...txOverrides?.nonce !== void 0 && { nonce: Number(txOverrides.nonce) }
|
|
4474
|
-
});
|
|
4475
|
-
const signedTx = await walletClient.signTransaction({
|
|
4476
|
-
...request,
|
|
4477
|
-
account: broadcastAccount
|
|
4478
|
-
});
|
|
4479
|
-
const hash$1 = await broadcaster.broadcast(signedTx);
|
|
4480
|
-
return createTxResult(client, hash$1);
|
|
4481
|
-
}
|
|
4482
|
-
const gasOverrides = {};
|
|
4483
|
-
if (txOverrides?.maxFeePerGas !== void 0) gasOverrides.maxFeePerGas = txOverrides.maxFeePerGas;
|
|
4484
|
-
if (txOverrides?.maxPriorityFeePerGas !== void 0) gasOverrides.maxPriorityFeePerGas = txOverrides.maxPriorityFeePerGas;
|
|
4485
|
-
if (txOverrides?.nonce !== void 0) gasOverrides.nonce = Number(txOverrides.nonce);
|
|
4486
|
-
const resolvedAccount = walletClient.account ?? account;
|
|
4487
|
-
if (txOverrides?.gas !== void 0) gasOverrides.gas = txOverrides.gas;
|
|
4488
|
-
else {
|
|
4489
|
-
const estimated = await client.estimateContractGas({
|
|
4490
|
-
address,
|
|
4491
|
-
abi,
|
|
4492
|
-
functionName,
|
|
4493
|
-
args,
|
|
4494
|
-
account: resolvedAccount,
|
|
4495
|
-
...value !== void 0 && { value }
|
|
4496
|
-
});
|
|
4497
|
-
gasOverrides.gas = estimated * 120n / 100n;
|
|
4498
|
-
}
|
|
4499
|
-
const hash = await walletClient.writeContract({
|
|
4500
|
-
address,
|
|
4501
|
-
abi,
|
|
4502
|
-
functionName,
|
|
4503
|
-
args,
|
|
4504
|
-
account: resolvedAccount,
|
|
4505
|
-
chain: walletClient.chain,
|
|
4506
|
-
...value !== void 0 && { value },
|
|
4507
|
-
...gasOverrides
|
|
4508
|
-
});
|
|
4509
|
-
return createTxResult(client, hash);
|
|
4510
|
-
}
|
|
4511
|
-
|
|
4512
|
-
//#endregion
|
|
4513
|
-
//#region src/panoptic/v2/sync/snapshotRecovery.ts
|
|
4514
|
-
const rolesExecutionAbi = parseAbi(["function execTransactionWithRole(address to, uint256 value, bytes data, uint8 operation, bytes32 roleKey, bool shouldRevert)"]);
|
|
4515
|
-
/**
|
|
4516
|
-
* 4-byte function selectors for dispatch/dispatchFrom, derived from the generated ABI.
|
|
4517
|
-
* Used for scanning raw calldata inside smart contract wallet wrappers.
|
|
4518
|
-
*/
|
|
4519
|
-
const DISPATCH_SELECTOR = toFunctionSelector(panopticPoolV2Abi.find((e) => e.type === "function" && e.name === "dispatch")).slice(2);
|
|
4520
|
-
const DISPATCH_FROM_SELECTOR = toFunctionSelector(panopticPoolV2Abi.find((e) => e.type === "function" && e.name === "dispatchFrom")).slice(2);
|
|
4521
|
-
|
|
4522
|
-
//#endregion
|
|
4523
|
-
//#region src/panoptic/v2/abis/stateView.ts
|
|
4524
|
-
/**
|
|
4525
|
-
* Minimal Uniswap V4 StateView ABI for fee growth reads.
|
|
4526
|
-
* Only includes functions needed by streamia history.
|
|
4527
|
-
*/
|
|
4528
|
-
const stateViewAbi = [
|
|
4529
|
-
{
|
|
4530
|
-
inputs: [{
|
|
4531
|
-
internalType: "PoolId",
|
|
4532
|
-
name: "poolId",
|
|
4533
|
-
type: "bytes32"
|
|
4534
|
-
}],
|
|
4535
|
-
name: "getSlot0",
|
|
4536
|
-
outputs: [
|
|
4263
|
+
const quoterV2Abi = [{
|
|
4264
|
+
type: "function",
|
|
4265
|
+
name: "quoteExactInputSingle",
|
|
4266
|
+
stateMutability: "nonpayable",
|
|
4267
|
+
inputs: [{
|
|
4268
|
+
name: "params",
|
|
4269
|
+
type: "tuple",
|
|
4270
|
+
components: [
|
|
4537
4271
|
{
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
type: "uint160"
|
|
4272
|
+
name: "tokenIn",
|
|
4273
|
+
type: "address"
|
|
4541
4274
|
},
|
|
4542
4275
|
{
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
type: "int24"
|
|
4276
|
+
name: "tokenOut",
|
|
4277
|
+
type: "address"
|
|
4546
4278
|
},
|
|
4547
4279
|
{
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
type: "uint24"
|
|
4280
|
+
name: "amountIn",
|
|
4281
|
+
type: "uint256"
|
|
4551
4282
|
},
|
|
4552
4283
|
{
|
|
4553
|
-
|
|
4554
|
-
name: "lpFee",
|
|
4284
|
+
name: "fee",
|
|
4555
4285
|
type: "uint24"
|
|
4286
|
+
},
|
|
4287
|
+
{
|
|
4288
|
+
name: "sqrtPriceLimitX96",
|
|
4289
|
+
type: "uint160"
|
|
4556
4290
|
}
|
|
4557
|
-
]
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4291
|
+
]
|
|
4292
|
+
}],
|
|
4293
|
+
outputs: [
|
|
4294
|
+
{
|
|
4295
|
+
name: "amountOut",
|
|
4296
|
+
type: "uint256"
|
|
4297
|
+
},
|
|
4298
|
+
{
|
|
4299
|
+
name: "sqrtPriceX96After",
|
|
4300
|
+
type: "uint160"
|
|
4301
|
+
},
|
|
4302
|
+
{
|
|
4303
|
+
name: "initializedTicksCrossed",
|
|
4304
|
+
type: "uint32"
|
|
4305
|
+
},
|
|
4306
|
+
{
|
|
4307
|
+
name: "gasEstimate",
|
|
4308
|
+
type: "uint256"
|
|
4309
|
+
}
|
|
4310
|
+
]
|
|
4311
|
+
}, {
|
|
4312
|
+
type: "function",
|
|
4313
|
+
name: "quoteExactOutputSingle",
|
|
4314
|
+
stateMutability: "nonpayable",
|
|
4315
|
+
inputs: [{
|
|
4316
|
+
name: "params",
|
|
4317
|
+
type: "tuple",
|
|
4318
|
+
components: [
|
|
4319
|
+
{
|
|
4320
|
+
name: "tokenIn",
|
|
4321
|
+
type: "address"
|
|
4322
|
+
},
|
|
4323
|
+
{
|
|
4324
|
+
name: "tokenOut",
|
|
4325
|
+
type: "address"
|
|
4326
|
+
},
|
|
4327
|
+
{
|
|
4328
|
+
name: "amount",
|
|
4329
|
+
type: "uint256"
|
|
4330
|
+
},
|
|
4331
|
+
{
|
|
4332
|
+
name: "fee",
|
|
4333
|
+
type: "uint24"
|
|
4334
|
+
},
|
|
4335
|
+
{
|
|
4336
|
+
name: "sqrtPriceLimitX96",
|
|
4337
|
+
type: "uint160"
|
|
4338
|
+
}
|
|
4339
|
+
]
|
|
4340
|
+
}],
|
|
4341
|
+
outputs: [
|
|
4342
|
+
{
|
|
4343
|
+
name: "amountIn",
|
|
4344
|
+
type: "uint256"
|
|
4345
|
+
},
|
|
4346
|
+
{
|
|
4347
|
+
name: "sqrtPriceX96After",
|
|
4348
|
+
type: "uint160"
|
|
4349
|
+
},
|
|
4350
|
+
{
|
|
4351
|
+
name: "initializedTicksCrossed",
|
|
4352
|
+
type: "uint32"
|
|
4353
|
+
},
|
|
4354
|
+
{
|
|
4355
|
+
name: "gasEstimate",
|
|
4356
|
+
type: "uint256"
|
|
4357
|
+
}
|
|
4358
|
+
]
|
|
4359
|
+
}];
|
|
4360
|
+
|
|
4361
|
+
//#endregion
|
|
4362
|
+
//#region src/uniswap/v3/router/quote.ts
|
|
4363
|
+
const BPS_DENOMINATOR$2 = 10000n;
|
|
4364
|
+
const UINT128_MAX$3 = (1n << 128n) - 1n;
|
|
4365
|
+
/**
|
|
4366
|
+
* Quote an exact-in v3 swap. Returns `null` when the quote reverts (e.g. the
|
|
4367
|
+
* pool does not exist / has no liquidity) so callers can skip that pool when
|
|
4368
|
+
* ranking venues, rather than aborting the whole cycle.
|
|
4369
|
+
*/
|
|
4370
|
+
async function quoteV3ExactIn(params) {
|
|
4371
|
+
const { client, chainId, tokenIn, tokenOut, fee, amountIn, slippageBps, blockNumber } = params;
|
|
4372
|
+
if (amountIn < 0n || amountIn > UINT128_MAX$3) throw new PanopticError(`amountIn ${amountIn} exceeds uint128 maximum`);
|
|
4373
|
+
assertSlippageBps$1(slippageBps);
|
|
4374
|
+
const { quoterV2 } = getUniswapV3Addresses(chainId, params.addresses);
|
|
4375
|
+
try {
|
|
4376
|
+
const { result } = await client.simulateContract({
|
|
4377
|
+
address: quoterV2,
|
|
4378
|
+
abi: quoterV2Abi,
|
|
4379
|
+
functionName: "quoteExactInputSingle",
|
|
4380
|
+
blockNumber,
|
|
4381
|
+
args: [{
|
|
4382
|
+
tokenIn,
|
|
4383
|
+
tokenOut,
|
|
4384
|
+
amountIn,
|
|
4385
|
+
fee: Number(fee),
|
|
4386
|
+
sqrtPriceLimitX96: 0n
|
|
4387
|
+
}]
|
|
4388
|
+
});
|
|
4389
|
+
const [amountOut, , , gasEstimate] = result;
|
|
4390
|
+
const amountOutMinimum = amountOut * (BPS_DENOMINATOR$2 - slippageBps) / BPS_DENOMINATOR$2;
|
|
4391
|
+
return {
|
|
4392
|
+
amountOut,
|
|
4393
|
+
amountOutMinimum,
|
|
4394
|
+
gasEstimate
|
|
4395
|
+
};
|
|
4396
|
+
} catch (err) {
|
|
4397
|
+
if (isRevert$1(err)) return null;
|
|
4398
|
+
throw err;
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
/**
|
|
4402
|
+
* Quote an exact-out v3 swap. Returns `null` on revert (no pool / no liquidity).
|
|
4403
|
+
*/
|
|
4404
|
+
async function quoteV3ExactOut(params) {
|
|
4405
|
+
const { client, chainId, tokenIn, tokenOut, fee, amountOut, slippageBps, blockNumber } = params;
|
|
4406
|
+
if (amountOut < 0n || amountOut > UINT128_MAX$3) throw new PanopticError(`amountOut ${amountOut} exceeds uint128 maximum`);
|
|
4407
|
+
assertSlippageBps$1(slippageBps);
|
|
4408
|
+
const { quoterV2 } = getUniswapV3Addresses(chainId, params.addresses);
|
|
4409
|
+
try {
|
|
4410
|
+
const { result } = await client.simulateContract({
|
|
4411
|
+
address: quoterV2,
|
|
4412
|
+
abi: quoterV2Abi,
|
|
4413
|
+
functionName: "quoteExactOutputSingle",
|
|
4414
|
+
blockNumber,
|
|
4415
|
+
args: [{
|
|
4416
|
+
tokenIn,
|
|
4417
|
+
tokenOut,
|
|
4418
|
+
amount: amountOut,
|
|
4419
|
+
fee: Number(fee),
|
|
4420
|
+
sqrtPriceLimitX96: 0n
|
|
4421
|
+
}]
|
|
4422
|
+
});
|
|
4423
|
+
const [amountIn, , , gasEstimate] = result;
|
|
4424
|
+
const amountInMaximum = (amountIn * (BPS_DENOMINATOR$2 + slippageBps) + BPS_DENOMINATOR$2 - 1n) / BPS_DENOMINATOR$2;
|
|
4425
|
+
return {
|
|
4426
|
+
amountIn,
|
|
4427
|
+
amountInMaximum,
|
|
4428
|
+
gasEstimate
|
|
4429
|
+
};
|
|
4430
|
+
} catch (err) {
|
|
4431
|
+
if (isRevert$1(err)) return null;
|
|
4432
|
+
throw err;
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
function assertSlippageBps$1(slippageBps) {
|
|
4436
|
+
if (slippageBps < 0n || slippageBps > BPS_DENOMINATOR$2) throw new PanopticError(`invalid slippageBps ${slippageBps}, must be 0..10000`);
|
|
4437
|
+
}
|
|
4438
|
+
function isRevert$1(err) {
|
|
4439
|
+
return err instanceof BaseError && err.walk((e) => e instanceof ContractFunctionRevertedError) instanceof ContractFunctionRevertedError;
|
|
4440
|
+
}
|
|
4441
|
+
|
|
4442
|
+
//#endregion
|
|
4443
|
+
//#region src/panoptic/v2/clients/blockMeta.ts
|
|
4444
|
+
/**
|
|
4445
|
+
* Get block metadata for a given block.
|
|
4446
|
+
* Used to tag all read results with same-block consistency proof.
|
|
4447
|
+
*
|
|
4448
|
+
* @param params - The parameters
|
|
4449
|
+
* @returns Block metadata
|
|
4450
|
+
*/
|
|
4451
|
+
async function getBlockMeta(params) {
|
|
4452
|
+
const { client, blockNumber } = params;
|
|
4453
|
+
const block = await client.getBlock({
|
|
4454
|
+
blockNumber,
|
|
4455
|
+
includeTransactions: false
|
|
4456
|
+
});
|
|
4457
|
+
return {
|
|
4458
|
+
blockNumber: block.number,
|
|
4459
|
+
blockHash: block.hash,
|
|
4460
|
+
blockTimestamp: block.timestamp
|
|
4461
|
+
};
|
|
4462
|
+
}
|
|
4463
|
+
|
|
4464
|
+
//#endregion
|
|
4465
|
+
//#region src/panoptic/v2/abis/stateView.ts
|
|
4466
|
+
/**
|
|
4467
|
+
* Minimal Uniswap V4 StateView ABI for fee growth reads.
|
|
4468
|
+
* Only includes functions needed by streamia history.
|
|
4469
|
+
*/
|
|
4470
|
+
const stateViewAbi = [
|
|
4471
|
+
{
|
|
4472
|
+
inputs: [{
|
|
4473
|
+
internalType: "PoolId",
|
|
4474
|
+
name: "poolId",
|
|
4475
|
+
type: "bytes32"
|
|
4476
|
+
}],
|
|
4477
|
+
name: "getSlot0",
|
|
4478
|
+
outputs: [
|
|
4479
|
+
{
|
|
4480
|
+
internalType: "uint160",
|
|
4481
|
+
name: "sqrtPriceX96",
|
|
4482
|
+
type: "uint160"
|
|
4483
|
+
},
|
|
4484
|
+
{
|
|
4485
|
+
internalType: "int24",
|
|
4486
|
+
name: "tick",
|
|
4487
|
+
type: "int24"
|
|
4488
|
+
},
|
|
4489
|
+
{
|
|
4490
|
+
internalType: "uint24",
|
|
4491
|
+
name: "protocolFee",
|
|
4492
|
+
type: "uint24"
|
|
4493
|
+
},
|
|
4494
|
+
{
|
|
4495
|
+
internalType: "uint24",
|
|
4496
|
+
name: "lpFee",
|
|
4497
|
+
type: "uint24"
|
|
4498
|
+
}
|
|
4499
|
+
],
|
|
4500
|
+
stateMutability: "view",
|
|
4501
|
+
type: "function"
|
|
4502
|
+
},
|
|
4503
|
+
{
|
|
4504
|
+
inputs: [{
|
|
4505
|
+
internalType: "PoolId",
|
|
4506
|
+
name: "poolId",
|
|
4507
|
+
type: "bytes32"
|
|
4508
|
+
}],
|
|
4509
|
+
name: "getFeeGrowthGlobals",
|
|
4510
|
+
outputs: [{
|
|
4511
|
+
internalType: "uint256",
|
|
4512
|
+
name: "feeGrowthGlobal0",
|
|
4513
|
+
type: "uint256"
|
|
4514
|
+
}, {
|
|
4573
4515
|
internalType: "uint256",
|
|
4574
4516
|
name: "feeGrowthGlobal1",
|
|
4575
4517
|
type: "uint256"
|
|
@@ -5174,134 +5116,938 @@ async function getPool(params) {
|
|
|
5174
5116
|
const healthStatus = safeModeRaw === 0 ? "active" : safeModeRaw === 1 ? "low_liquidity" : "paused";
|
|
5175
5117
|
const sqrtPriceX96 = tickToSqrtPriceX96(BigInt(currentTick));
|
|
5176
5118
|
return {
|
|
5177
|
-
address: poolAddress,
|
|
5178
|
-
chainId,
|
|
5179
|
-
poolId: metadata.poolId,
|
|
5180
|
-
poolKey,
|
|
5181
|
-
tickSpacing,
|
|
5182
|
-
collateralTracker0,
|
|
5183
|
-
collateralTracker1,
|
|
5184
|
-
riskEngine,
|
|
5185
|
-
currentTick: BigInt(currentTick),
|
|
5186
|
-
sqrtPriceX96,
|
|
5187
|
-
uniswapPoolLiquidity,
|
|
5188
|
-
healthStatus,
|
|
5189
|
-
metadata,
|
|
5190
|
-
_meta
|
|
5119
|
+
address: poolAddress,
|
|
5120
|
+
chainId,
|
|
5121
|
+
poolId: metadata.poolId,
|
|
5122
|
+
poolKey,
|
|
5123
|
+
tickSpacing,
|
|
5124
|
+
collateralTracker0,
|
|
5125
|
+
collateralTracker1,
|
|
5126
|
+
riskEngine,
|
|
5127
|
+
currentTick: BigInt(currentTick),
|
|
5128
|
+
sqrtPriceX96,
|
|
5129
|
+
uniswapPoolLiquidity,
|
|
5130
|
+
healthStatus,
|
|
5131
|
+
metadata,
|
|
5132
|
+
_meta
|
|
5133
|
+
};
|
|
5134
|
+
}
|
|
5135
|
+
/**
|
|
5136
|
+
* Extract tickSpacing from the encoded 64-bit poolId.
|
|
5137
|
+
* Layout: poolAddress (5 bytes) + vegoid (1 byte) + tickSpacing (2 bytes).
|
|
5138
|
+
* tickSpacing occupies bits 48–63.
|
|
5139
|
+
*/
|
|
5140
|
+
function tickSpacingFromPoolId(poolId) {
|
|
5141
|
+
return poolId >> 48n & 0xffffn;
|
|
5142
|
+
}
|
|
5143
|
+
/**
|
|
5144
|
+
* Parse pool key from ABI-encoded bytes.
|
|
5145
|
+
*
|
|
5146
|
+
* V4 pools: PoolKey struct is ABI-encoded as 5 consecutive 32-byte slots
|
|
5147
|
+
* (currency0, currency1, fee, tickSpacing, hooks).
|
|
5148
|
+
*
|
|
5149
|
+
* V3 pools: poolKey() returns abi.encode(uniswapV3PoolAddress) — a single
|
|
5150
|
+
* 32-byte slot. The struct fields are not available, so currency0/currency1
|
|
5151
|
+
* are zeroed and tickSpacing/fee are set to 0 (callers should use
|
|
5152
|
+
* getV3PoolTickSpacing and getV3PoolFee respectively).
|
|
5153
|
+
*/
|
|
5154
|
+
function parsePoolKey(poolKeyBytes) {
|
|
5155
|
+
const hex = poolKeyBytes.slice(2);
|
|
5156
|
+
if (hex.length <= 64) return {
|
|
5157
|
+
currency0: zeroAddress,
|
|
5158
|
+
currency1: zeroAddress,
|
|
5159
|
+
fee: 0n,
|
|
5160
|
+
tickSpacing: 0n,
|
|
5161
|
+
hooks: zeroAddress
|
|
5162
|
+
};
|
|
5163
|
+
if (hex.length < 320) throw new Error(`Malformed V4 pool key: expected 320 hex chars (160 bytes), got ${hex.length}`);
|
|
5164
|
+
const currency0 = `0x${hex.slice(24, 64)}`;
|
|
5165
|
+
const currency1 = `0x${hex.slice(88, 128)}`;
|
|
5166
|
+
const fee = BigInt(`0x${hex.slice(128, 192)}`);
|
|
5167
|
+
const tickSpacing = BigInt(`0x${hex.slice(192, 256)}`);
|
|
5168
|
+
const hooks = `0x${hex.slice(280, 320)}`;
|
|
5169
|
+
return {
|
|
5170
|
+
currency0,
|
|
5171
|
+
currency1,
|
|
5172
|
+
fee,
|
|
5173
|
+
tickSpacing,
|
|
5174
|
+
hooks
|
|
5175
|
+
};
|
|
5176
|
+
}
|
|
5177
|
+
|
|
5178
|
+
//#endregion
|
|
5179
|
+
//#region src/uniswap/v3/router/resolveRoute.ts
|
|
5180
|
+
async function resolveV3SwapRoute(params) {
|
|
5181
|
+
const { client, poolAddress, tokenIn } = params;
|
|
5182
|
+
const meta = await getPoolMetadata({
|
|
5183
|
+
client,
|
|
5184
|
+
poolAddress
|
|
5185
|
+
});
|
|
5186
|
+
if (meta.isV4) throw new PanopticError("resolveV3SwapRoute called on a v4 pool");
|
|
5187
|
+
const isToken0 = isAddressEqual(tokenIn, meta.token0Asset);
|
|
5188
|
+
const isToken1 = isAddressEqual(tokenIn, meta.token1Asset);
|
|
5189
|
+
if (!isToken0 && !isToken1) throw new InvalidSwapTokenError(tokenIn, meta.token0Asset, meta.token1Asset);
|
|
5190
|
+
const zeroForOne = isToken0;
|
|
5191
|
+
return {
|
|
5192
|
+
tokenIn,
|
|
5193
|
+
tokenOut: zeroForOne ? meta.token1Asset : meta.token0Asset,
|
|
5194
|
+
fee: meta.fee,
|
|
5195
|
+
zeroForOne
|
|
5196
|
+
};
|
|
5197
|
+
}
|
|
5198
|
+
|
|
5199
|
+
//#endregion
|
|
5200
|
+
//#region src/uniswap/v3/router/quoteViaRouter.ts
|
|
5201
|
+
const FALLBACK_META$1 = {
|
|
5202
|
+
blockNumber: 0n,
|
|
5203
|
+
blockTimestamp: 0n,
|
|
5204
|
+
blockHash: "0x0"
|
|
5205
|
+
};
|
|
5206
|
+
async function quoteSwapExactInViaV3Router(params) {
|
|
5207
|
+
const { client, poolAddress, chainId, tokenIn, amountIn, slippageBps, blockNumber, addresses } = params;
|
|
5208
|
+
try {
|
|
5209
|
+
const targetBlockNumber = blockNumber ?? await client.getBlockNumber();
|
|
5210
|
+
const metaPromise = getBlockMeta({
|
|
5211
|
+
client,
|
|
5212
|
+
blockNumber: targetBlockNumber
|
|
5213
|
+
});
|
|
5214
|
+
metaPromise.catch(() => void 0);
|
|
5215
|
+
const route = await resolveV3SwapRoute({
|
|
5216
|
+
client,
|
|
5217
|
+
poolAddress,
|
|
5218
|
+
tokenIn
|
|
5219
|
+
});
|
|
5220
|
+
const quote = await quoteV3ExactIn({
|
|
5221
|
+
client,
|
|
5222
|
+
chainId,
|
|
5223
|
+
tokenIn: route.tokenIn,
|
|
5224
|
+
tokenOut: route.tokenOut,
|
|
5225
|
+
fee: route.fee,
|
|
5226
|
+
amountIn,
|
|
5227
|
+
slippageBps,
|
|
5228
|
+
blockNumber: targetBlockNumber,
|
|
5229
|
+
addresses
|
|
5230
|
+
});
|
|
5231
|
+
if (!quote) throw new PanopticError("v3 quote returned no result (pool may have no liquidity)");
|
|
5232
|
+
const _meta = await metaPromise;
|
|
5233
|
+
return {
|
|
5234
|
+
success: true,
|
|
5235
|
+
data: {
|
|
5236
|
+
amountOut: quote.amountOut,
|
|
5237
|
+
amountOutMinimum: quote.amountOutMinimum,
|
|
5238
|
+
zeroForOne: route.zeroForOne,
|
|
5239
|
+
tokenOut: route.tokenOut,
|
|
5240
|
+
gasEstimate: quote.gasEstimate
|
|
5241
|
+
},
|
|
5242
|
+
gasEstimate: quote.gasEstimate,
|
|
5243
|
+
_meta
|
|
5244
|
+
};
|
|
5245
|
+
} catch (error) {
|
|
5246
|
+
return {
|
|
5247
|
+
success: false,
|
|
5248
|
+
error: error instanceof PanopticError ? error : new PanopticError(error instanceof Error ? error.message : "Quote failed", error instanceof Error ? error : void 0),
|
|
5249
|
+
_meta: FALLBACK_META$1
|
|
5250
|
+
};
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
5253
|
+
async function quoteSwapExactOutViaV3Router(params) {
|
|
5254
|
+
const { client, poolAddress, chainId, tokenIn, amountOut, slippageBps, blockNumber, addresses } = params;
|
|
5255
|
+
try {
|
|
5256
|
+
const targetBlockNumber = blockNumber ?? await client.getBlockNumber();
|
|
5257
|
+
const metaPromise = getBlockMeta({
|
|
5258
|
+
client,
|
|
5259
|
+
blockNumber: targetBlockNumber
|
|
5260
|
+
});
|
|
5261
|
+
metaPromise.catch(() => void 0);
|
|
5262
|
+
const route = await resolveV3SwapRoute({
|
|
5263
|
+
client,
|
|
5264
|
+
poolAddress,
|
|
5265
|
+
tokenIn
|
|
5266
|
+
});
|
|
5267
|
+
const quote = await quoteV3ExactOut({
|
|
5268
|
+
client,
|
|
5269
|
+
chainId,
|
|
5270
|
+
tokenIn: route.tokenIn,
|
|
5271
|
+
tokenOut: route.tokenOut,
|
|
5272
|
+
fee: route.fee,
|
|
5273
|
+
amountOut,
|
|
5274
|
+
slippageBps,
|
|
5275
|
+
blockNumber: targetBlockNumber,
|
|
5276
|
+
addresses
|
|
5277
|
+
});
|
|
5278
|
+
if (!quote) throw new PanopticError("v3 quote returned no result (pool may have no liquidity)");
|
|
5279
|
+
const _meta = await metaPromise;
|
|
5280
|
+
return {
|
|
5281
|
+
success: true,
|
|
5282
|
+
data: {
|
|
5283
|
+
amountIn: quote.amountIn,
|
|
5284
|
+
amountInMaximum: quote.amountInMaximum,
|
|
5285
|
+
zeroForOne: route.zeroForOne,
|
|
5286
|
+
tokenOut: route.tokenOut,
|
|
5287
|
+
gasEstimate: quote.gasEstimate
|
|
5288
|
+
},
|
|
5289
|
+
gasEstimate: quote.gasEstimate,
|
|
5290
|
+
_meta
|
|
5291
|
+
};
|
|
5292
|
+
} catch (error) {
|
|
5293
|
+
return {
|
|
5294
|
+
success: false,
|
|
5295
|
+
error: error instanceof PanopticError ? error : new PanopticError(error instanceof Error ? error.message : "Quote failed", error instanceof Error ? error : void 0),
|
|
5296
|
+
_meta: FALLBACK_META$1
|
|
5297
|
+
};
|
|
5298
|
+
}
|
|
5299
|
+
}
|
|
5300
|
+
|
|
5301
|
+
//#endregion
|
|
5302
|
+
//#region src/panoptic/v2/writes/utils.ts
|
|
5303
|
+
/**
|
|
5304
|
+
* Decode PositionBalance packed data.
|
|
5305
|
+
*
|
|
5306
|
+
* Layout (from LSB to MSB):
|
|
5307
|
+
* - positionSize: bits 0-127 (uint128)
|
|
5308
|
+
* - poolUtilization0: bits 128-143 (uint16)
|
|
5309
|
+
* - poolUtilization1: bits 144-159 (uint16)
|
|
5310
|
+
* - tickAtMint: bits 160-183 (int24)
|
|
5311
|
+
* - timestampAtMint: bits 184-215 (uint32)
|
|
5312
|
+
* - blockAtMint: bits 216-254 (uint39)
|
|
5313
|
+
* - swapAtMint: bit 255 (bool)
|
|
5314
|
+
*/
|
|
5315
|
+
function decodePositionBalance(balanceData) {
|
|
5316
|
+
const positionSize = balanceData & (1n << 128n) - 1n;
|
|
5317
|
+
const poolUtilization0 = balanceData >> 128n & 0xffffn;
|
|
5318
|
+
const poolUtilization1 = balanceData >> 144n & 0xffffn;
|
|
5319
|
+
let tickAtMint = balanceData >> 160n & 0xffffffn;
|
|
5320
|
+
if (tickAtMint > 0x7fffffn) tickAtMint = tickAtMint - 0x1000000n;
|
|
5321
|
+
const timestampAtMint = balanceData >> 184n & 0xffffffffn;
|
|
5322
|
+
const blockAtMint = balanceData >> 216n & (1n << 39n) - 1n;
|
|
5323
|
+
const swapAtMint = balanceData >> 255n === 1n;
|
|
5324
|
+
return {
|
|
5325
|
+
positionSize,
|
|
5326
|
+
poolUtilization0,
|
|
5327
|
+
poolUtilization1,
|
|
5328
|
+
tickAtMint,
|
|
5329
|
+
timestampAtMint,
|
|
5330
|
+
blockAtMint,
|
|
5331
|
+
swapAtMint
|
|
5332
|
+
};
|
|
5333
|
+
}
|
|
5334
|
+
/**
|
|
5335
|
+
* Decode LeftRightSigned packed value.
|
|
5336
|
+
*/
|
|
5337
|
+
function decodeLeftRightSigned(value) {
|
|
5338
|
+
let right = value & (1n << 128n) - 1n;
|
|
5339
|
+
let left = value >> 128n;
|
|
5340
|
+
if (right >= 1n << 127n) right = right - (1n << 128n);
|
|
5341
|
+
if (left >= 1n << 127n) left = left - (1n << 128n);
|
|
5342
|
+
return {
|
|
5343
|
+
right,
|
|
5344
|
+
left
|
|
5345
|
+
};
|
|
5346
|
+
}
|
|
5347
|
+
/**
|
|
5348
|
+
* Parse Panoptic events from transaction logs.
|
|
5349
|
+
*
|
|
5350
|
+
* @param logs - The transaction logs
|
|
5351
|
+
* @returns Parsed Panoptic events
|
|
5352
|
+
*/
|
|
5353
|
+
function parsePanopticEvents(logs) {
|
|
5354
|
+
const events = [];
|
|
5355
|
+
for (const log of logs) {
|
|
5356
|
+
const baseEvent = {
|
|
5357
|
+
blockNumber: log.blockNumber ?? 0n,
|
|
5358
|
+
blockHash: log.blockHash ?? "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
5359
|
+
transactionHash: log.transactionHash ?? "0x",
|
|
5360
|
+
logIndex: BigInt(log.logIndex ?? 0)
|
|
5361
|
+
};
|
|
5362
|
+
try {
|
|
5363
|
+
const poolEvent = decodeEventLog({
|
|
5364
|
+
abi: panopticPoolV2Abi,
|
|
5365
|
+
data: log.data,
|
|
5366
|
+
topics: log.topics
|
|
5367
|
+
});
|
|
5368
|
+
if (poolEvent.eventName === "OptionMinted") {
|
|
5369
|
+
const args = poolEvent.args;
|
|
5370
|
+
const balance = decodePositionBalance(args.balanceData);
|
|
5371
|
+
events.push({
|
|
5372
|
+
type: "OptionMinted",
|
|
5373
|
+
...baseEvent,
|
|
5374
|
+
recipient: args.recipient,
|
|
5375
|
+
tokenId: args.tokenId,
|
|
5376
|
+
positionSize: balance.positionSize,
|
|
5377
|
+
poolUtilization0: balance.poolUtilization0,
|
|
5378
|
+
poolUtilization1: balance.poolUtilization1,
|
|
5379
|
+
tickAtMint: balance.tickAtMint,
|
|
5380
|
+
timestampAtMint: balance.timestampAtMint,
|
|
5381
|
+
blockAtMint: balance.blockAtMint,
|
|
5382
|
+
swapAtMint: balance.swapAtMint
|
|
5383
|
+
});
|
|
5384
|
+
} else if (poolEvent.eventName === "OptionBurnt") {
|
|
5385
|
+
const args = poolEvent.args;
|
|
5386
|
+
events.push({
|
|
5387
|
+
type: "OptionBurnt",
|
|
5388
|
+
...baseEvent,
|
|
5389
|
+
recipient: args.recipient,
|
|
5390
|
+
tokenId: args.tokenId,
|
|
5391
|
+
positionSize: args.positionSize,
|
|
5392
|
+
premiaByLeg: [
|
|
5393
|
+
args.premiaByLeg[0] ?? 0n,
|
|
5394
|
+
args.premiaByLeg[1] ?? 0n,
|
|
5395
|
+
args.premiaByLeg[2] ?? 0n,
|
|
5396
|
+
args.premiaByLeg[3] ?? 0n
|
|
5397
|
+
]
|
|
5398
|
+
});
|
|
5399
|
+
} else if (poolEvent.eventName === "AccountLiquidated") {
|
|
5400
|
+
const args = poolEvent.args;
|
|
5401
|
+
const { right, left } = decodeLeftRightSigned(args.bonusAmounts);
|
|
5402
|
+
events.push({
|
|
5403
|
+
type: "AccountLiquidated",
|
|
5404
|
+
...baseEvent,
|
|
5405
|
+
liquidator: args.liquidator,
|
|
5406
|
+
liquidatee: args.liquidatee,
|
|
5407
|
+
bonusAmount0: right,
|
|
5408
|
+
bonusAmount1: left
|
|
5409
|
+
});
|
|
5410
|
+
} else if (poolEvent.eventName === "ForcedExercised") {
|
|
5411
|
+
const args = poolEvent.args;
|
|
5412
|
+
const { right, left } = decodeLeftRightSigned(args.exerciseFee);
|
|
5413
|
+
events.push({
|
|
5414
|
+
type: "ForcedExercised",
|
|
5415
|
+
...baseEvent,
|
|
5416
|
+
exercisor: args.exercisor,
|
|
5417
|
+
user: args.user,
|
|
5418
|
+
tokenId: args.tokenId,
|
|
5419
|
+
exerciseFee0: right,
|
|
5420
|
+
exerciseFee1: left
|
|
5421
|
+
});
|
|
5422
|
+
} else if (poolEvent.eventName === "PremiumSettled") {
|
|
5423
|
+
const args = poolEvent.args;
|
|
5424
|
+
const { right, left } = decodeLeftRightSigned(args.settledAmounts);
|
|
5425
|
+
events.push({
|
|
5426
|
+
type: "PremiumSettled",
|
|
5427
|
+
...baseEvent,
|
|
5428
|
+
user: args.user,
|
|
5429
|
+
tokenId: args.tokenId,
|
|
5430
|
+
legIndex: args.legIndex,
|
|
5431
|
+
settledAmount0: right,
|
|
5432
|
+
settledAmount1: left
|
|
5433
|
+
});
|
|
5434
|
+
}
|
|
5435
|
+
} catch {
|
|
5436
|
+
try {
|
|
5437
|
+
const collateralEvent = decodeEventLog({
|
|
5438
|
+
abi: collateralTrackerV2Abi,
|
|
5439
|
+
data: log.data,
|
|
5440
|
+
topics: log.topics
|
|
5441
|
+
});
|
|
5442
|
+
if (collateralEvent.eventName === "Deposit") {
|
|
5443
|
+
const args = collateralEvent.args;
|
|
5444
|
+
events.push({
|
|
5445
|
+
type: "Deposit",
|
|
5446
|
+
...baseEvent,
|
|
5447
|
+
sender: args.sender,
|
|
5448
|
+
owner: args.owner,
|
|
5449
|
+
assets: args.assets,
|
|
5450
|
+
shares: args.shares
|
|
5451
|
+
});
|
|
5452
|
+
} else if (collateralEvent.eventName === "Withdraw") {
|
|
5453
|
+
const args = collateralEvent.args;
|
|
5454
|
+
events.push({
|
|
5455
|
+
type: "Withdraw",
|
|
5456
|
+
...baseEvent,
|
|
5457
|
+
sender: args.sender,
|
|
5458
|
+
receiver: args.receiver,
|
|
5459
|
+
owner: args.owner,
|
|
5460
|
+
assets: args.assets,
|
|
5461
|
+
shares: args.shares
|
|
5462
|
+
});
|
|
5463
|
+
}
|
|
5464
|
+
} catch {}
|
|
5465
|
+
}
|
|
5466
|
+
}
|
|
5467
|
+
return events;
|
|
5468
|
+
}
|
|
5469
|
+
/**
|
|
5470
|
+
* Create a TxResult from a transaction hash.
|
|
5471
|
+
*
|
|
5472
|
+
* @param client - The public client
|
|
5473
|
+
* @param hash - The transaction hash
|
|
5474
|
+
* @returns TxResult with wait function
|
|
5475
|
+
*/
|
|
5476
|
+
function createTxResult(client, hash) {
|
|
5477
|
+
return {
|
|
5478
|
+
hash,
|
|
5479
|
+
wait: async (confirmations) => {
|
|
5480
|
+
const receipt = await client.waitForTransactionReceipt({
|
|
5481
|
+
hash,
|
|
5482
|
+
confirmations: confirmations !== void 0 ? Number(confirmations) : void 0
|
|
5483
|
+
});
|
|
5484
|
+
const events = parsePanopticEvents(receipt.logs);
|
|
5485
|
+
return {
|
|
5486
|
+
hash: receipt.transactionHash,
|
|
5487
|
+
blockNumber: receipt.blockNumber,
|
|
5488
|
+
blockHash: receipt.blockHash,
|
|
5489
|
+
gasUsed: receipt.gasUsed,
|
|
5490
|
+
status: receipt.status === "success" ? "success" : "reverted",
|
|
5491
|
+
events
|
|
5492
|
+
};
|
|
5493
|
+
}
|
|
5494
|
+
};
|
|
5495
|
+
}
|
|
5496
|
+
/**
|
|
5497
|
+
* Submit a write operation, supporting both direct wallet submission
|
|
5498
|
+
* and custom broadcaster paths with gas overrides.
|
|
5499
|
+
*
|
|
5500
|
+
* Normal path (no broadcaster): calls walletClient.writeContract() with gas overrides.
|
|
5501
|
+
* Broadcaster path: prepareTransactionRequest → apply overrides → signTransaction → broadcast.
|
|
5502
|
+
*
|
|
5503
|
+
* @param params - Submit write parameters
|
|
5504
|
+
* @returns TxResult
|
|
5505
|
+
*/
|
|
5506
|
+
async function submitWrite(params) {
|
|
5507
|
+
const { client, walletClient, account, address, abi, functionName, args, value, txOverrides } = params;
|
|
5508
|
+
const broadcaster = txOverrides?.broadcaster;
|
|
5509
|
+
if (broadcaster) {
|
|
5510
|
+
const data = encodeFunctionData({
|
|
5511
|
+
abi,
|
|
5512
|
+
functionName,
|
|
5513
|
+
args
|
|
5514
|
+
});
|
|
5515
|
+
const broadcastAccount = walletClient.account ?? account;
|
|
5516
|
+
const request = await walletClient.prepareTransactionRequest({
|
|
5517
|
+
account: broadcastAccount,
|
|
5518
|
+
to: address,
|
|
5519
|
+
chain: walletClient.chain,
|
|
5520
|
+
data,
|
|
5521
|
+
...value !== void 0 && { value },
|
|
5522
|
+
...txOverrides?.maxFeePerGas !== void 0 && { maxFeePerGas: txOverrides.maxFeePerGas },
|
|
5523
|
+
...txOverrides?.maxPriorityFeePerGas !== void 0 && { maxPriorityFeePerGas: txOverrides.maxPriorityFeePerGas },
|
|
5524
|
+
...txOverrides?.gas !== void 0 && { gas: txOverrides.gas },
|
|
5525
|
+
...txOverrides?.nonce !== void 0 && { nonce: Number(txOverrides.nonce) }
|
|
5526
|
+
});
|
|
5527
|
+
const signedTx = await walletClient.signTransaction({
|
|
5528
|
+
...request,
|
|
5529
|
+
account: broadcastAccount
|
|
5530
|
+
});
|
|
5531
|
+
const hash$1 = await broadcaster.broadcast(signedTx);
|
|
5532
|
+
return createTxResult(client, hash$1);
|
|
5533
|
+
}
|
|
5534
|
+
const gasOverrides = {};
|
|
5535
|
+
if (txOverrides?.maxFeePerGas !== void 0) gasOverrides.maxFeePerGas = txOverrides.maxFeePerGas;
|
|
5536
|
+
if (txOverrides?.maxPriorityFeePerGas !== void 0) gasOverrides.maxPriorityFeePerGas = txOverrides.maxPriorityFeePerGas;
|
|
5537
|
+
if (txOverrides?.nonce !== void 0) gasOverrides.nonce = Number(txOverrides.nonce);
|
|
5538
|
+
const resolvedAccount = walletClient.account ?? account;
|
|
5539
|
+
if (txOverrides?.gas !== void 0) gasOverrides.gas = txOverrides.gas;
|
|
5540
|
+
else {
|
|
5541
|
+
const estimated = await client.estimateContractGas({
|
|
5542
|
+
address,
|
|
5543
|
+
abi,
|
|
5544
|
+
functionName,
|
|
5545
|
+
args,
|
|
5546
|
+
account: resolvedAccount,
|
|
5547
|
+
...value !== void 0 && { value }
|
|
5548
|
+
});
|
|
5549
|
+
gasOverrides.gas = estimated * 120n / 100n;
|
|
5550
|
+
}
|
|
5551
|
+
const hash = await walletClient.writeContract({
|
|
5552
|
+
address,
|
|
5553
|
+
abi,
|
|
5554
|
+
functionName,
|
|
5555
|
+
args,
|
|
5556
|
+
account: resolvedAccount,
|
|
5557
|
+
chain: walletClient.chain,
|
|
5558
|
+
...value !== void 0 && { value },
|
|
5559
|
+
...gasOverrides
|
|
5560
|
+
});
|
|
5561
|
+
return createTxResult(client, hash);
|
|
5562
|
+
}
|
|
5563
|
+
|
|
5564
|
+
//#endregion
|
|
5565
|
+
//#region src/uniswap/v4/addresses.ts
|
|
5566
|
+
/**
|
|
5567
|
+
* Canonical Permit2, identical across all chains.
|
|
5568
|
+
*/
|
|
5569
|
+
const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
5570
|
+
/**
|
|
5571
|
+
* Verified Uniswap v4 deployment addresses keyed by chainId.
|
|
5572
|
+
*
|
|
5573
|
+
* Sourced from the official Uniswap v4 deployments. Add a chain here only after
|
|
5574
|
+
* verifying each address against the canonical Uniswap deployment listing —
|
|
5575
|
+
* Universal Router in particular is NOT the same address across chains.
|
|
5576
|
+
*/
|
|
5577
|
+
const UNISWAP_V4_ADDRESSES = {
|
|
5578
|
+
1: {
|
|
5579
|
+
universalRouter: "0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af",
|
|
5580
|
+
v4Quoter: "0x52F0E24D1c21C8A0cB1e5a5dD6198556BD9E1203",
|
|
5581
|
+
poolManager: "0x000000000004444c5dc75cB358380D2e3dE08A90",
|
|
5582
|
+
positionManager: "0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e",
|
|
5583
|
+
permit2: PERMIT2_ADDRESS
|
|
5584
|
+
},
|
|
5585
|
+
4663: {
|
|
5586
|
+
universalRouter: "0x8876789976decbfcbbbe364623c63652db8c0904",
|
|
5587
|
+
v4Quoter: "0x8dc178efb8111bb0973dd9d722ebeff267c98f94",
|
|
5588
|
+
poolManager: "0x8366a39cc670b4001a1121b8f6a443a643e40951",
|
|
5589
|
+
positionManager: "0x58daec3116aae6d93017baaea7749052e8a04fa7",
|
|
5590
|
+
permit2: PERMIT2_ADDRESS
|
|
5591
|
+
}
|
|
5592
|
+
};
|
|
5593
|
+
/**
|
|
5594
|
+
* Resolve the Uniswap v4 addresses for a chain, applying optional overrides.
|
|
5595
|
+
*
|
|
5596
|
+
* @param chainId - Target chain ID.
|
|
5597
|
+
* @param overrides - Partial override of any address (e.g. for fork tests).
|
|
5598
|
+
* @returns Fully-resolved {@link UniswapV4Addresses}.
|
|
5599
|
+
* @throws {UnsupportedChainError} when the chain is not listed and the
|
|
5600
|
+
* overrides do not supply every required address.
|
|
5601
|
+
*/
|
|
5602
|
+
function getUniswapV4Addresses(chainId, overrides) {
|
|
5603
|
+
const base = UNISWAP_V4_ADDRESSES[Number(chainId)];
|
|
5604
|
+
const merged = {
|
|
5605
|
+
...base,
|
|
5606
|
+
...overrides,
|
|
5607
|
+
permit2: overrides?.permit2 ?? base?.permit2 ?? PERMIT2_ADDRESS
|
|
5608
|
+
};
|
|
5609
|
+
if (merged.universalRouter === void 0 || merged.v4Quoter === void 0 || merged.poolManager === void 0 || merged.positionManager === void 0 || merged.permit2 === void 0) throw new UnsupportedChainError(chainId);
|
|
5610
|
+
return merged;
|
|
5611
|
+
}
|
|
5612
|
+
|
|
5613
|
+
//#endregion
|
|
5614
|
+
//#region src/uniswap/v3/router/swap.ts
|
|
5615
|
+
const DEFAULT_DEADLINE_SECONDS$1 = 1800n;
|
|
5616
|
+
async function swapExactInViaV3Router(params) {
|
|
5617
|
+
const { client, walletClient, account, poolAddress, chainId, tokenIn, amountIn, slippageBps, deadline, recipient, txOverrides, addresses } = params;
|
|
5618
|
+
if (recipient !== void 0 && !isAddressEqual(recipient, account)) throw new PanopticError("Custom recipient is not supported yet; output goes to the sender");
|
|
5619
|
+
const resolved = getUniswapV4Addresses(chainId, addresses);
|
|
5620
|
+
const route = await resolveV3SwapRoute({
|
|
5621
|
+
client,
|
|
5622
|
+
poolAddress,
|
|
5623
|
+
tokenIn
|
|
5624
|
+
});
|
|
5625
|
+
const quote = await quoteV3ExactIn({
|
|
5626
|
+
client,
|
|
5627
|
+
chainId,
|
|
5628
|
+
tokenIn: route.tokenIn,
|
|
5629
|
+
tokenOut: route.tokenOut,
|
|
5630
|
+
fee: route.fee,
|
|
5631
|
+
amountIn,
|
|
5632
|
+
slippageBps,
|
|
5633
|
+
addresses
|
|
5634
|
+
});
|
|
5635
|
+
if (!quote) throw new PanopticError("v3 quote failed (pool may have no liquidity)");
|
|
5636
|
+
const resolvedDeadline = deadline ?? (await getBlockMeta({ client })).blockTimestamp + DEFAULT_DEADLINE_SECONDS$1;
|
|
5637
|
+
const { args, value } = buildV3SwapExecuteArgs({
|
|
5638
|
+
tokenIn: route.tokenIn,
|
|
5639
|
+
tokenOut: route.tokenOut,
|
|
5640
|
+
fee: route.fee,
|
|
5641
|
+
amountIn,
|
|
5642
|
+
amountOutMinimum: quote.amountOutMinimum,
|
|
5643
|
+
deadline: resolvedDeadline
|
|
5644
|
+
});
|
|
5645
|
+
return submitWrite({
|
|
5646
|
+
client,
|
|
5647
|
+
walletClient,
|
|
5648
|
+
account,
|
|
5649
|
+
address: resolved.universalRouter,
|
|
5650
|
+
abi: universalRouterAbi,
|
|
5651
|
+
functionName: "execute",
|
|
5652
|
+
args,
|
|
5653
|
+
value,
|
|
5654
|
+
txOverrides
|
|
5655
|
+
});
|
|
5656
|
+
}
|
|
5657
|
+
async function swapExactOutViaV3Router(params) {
|
|
5658
|
+
const { client, walletClient, account, poolAddress, chainId, tokenIn, amountOut, slippageBps, deadline, recipient, txOverrides, addresses } = params;
|
|
5659
|
+
if (recipient !== void 0 && !isAddressEqual(recipient, account)) throw new PanopticError("Custom recipient is not supported yet; output goes to the sender");
|
|
5660
|
+
const resolved = getUniswapV4Addresses(chainId, addresses);
|
|
5661
|
+
const route = await resolveV3SwapRoute({
|
|
5662
|
+
client,
|
|
5663
|
+
poolAddress,
|
|
5664
|
+
tokenIn
|
|
5665
|
+
});
|
|
5666
|
+
const quote = await quoteV3ExactOut({
|
|
5667
|
+
client,
|
|
5668
|
+
chainId,
|
|
5669
|
+
tokenIn: route.tokenIn,
|
|
5670
|
+
tokenOut: route.tokenOut,
|
|
5671
|
+
fee: route.fee,
|
|
5672
|
+
amountOut,
|
|
5673
|
+
slippageBps,
|
|
5674
|
+
addresses
|
|
5675
|
+
});
|
|
5676
|
+
if (!quote) throw new PanopticError("v3 quote failed (pool may have no liquidity)");
|
|
5677
|
+
const resolvedDeadline = deadline ?? (await getBlockMeta({ client })).blockTimestamp + DEFAULT_DEADLINE_SECONDS$1;
|
|
5678
|
+
const { args, value } = buildV3ExactOutSwapExecuteArgs({
|
|
5679
|
+
tokenIn: route.tokenIn,
|
|
5680
|
+
tokenOut: route.tokenOut,
|
|
5681
|
+
fee: route.fee,
|
|
5682
|
+
amountOut,
|
|
5683
|
+
amountInMaximum: quote.amountInMaximum,
|
|
5684
|
+
deadline: resolvedDeadline
|
|
5685
|
+
});
|
|
5686
|
+
return submitWrite({
|
|
5687
|
+
client,
|
|
5688
|
+
walletClient,
|
|
5689
|
+
account,
|
|
5690
|
+
address: resolved.universalRouter,
|
|
5691
|
+
abi: universalRouterAbi,
|
|
5692
|
+
functionName: "execute",
|
|
5693
|
+
args,
|
|
5694
|
+
value,
|
|
5695
|
+
txOverrides
|
|
5696
|
+
});
|
|
5697
|
+
}
|
|
5698
|
+
|
|
5699
|
+
//#endregion
|
|
5700
|
+
//#region src/uniswap/v4/router/encodeSwap.ts
|
|
5701
|
+
/** Universal Router command byte for a v4 swap. */
|
|
5702
|
+
const V4_SWAP = 16;
|
|
5703
|
+
/** v4 action: exact-in single-hop swap. */
|
|
5704
|
+
const SWAP_EXACT_IN_SINGLE = 6;
|
|
5705
|
+
/** v4 action: pay all of the input currency owed. */
|
|
5706
|
+
const SETTLE_ALL = 12;
|
|
5707
|
+
/** v4 action: take all of the output currency owed. */
|
|
5708
|
+
const TAKE_ALL = 15;
|
|
5709
|
+
/** v4 action: take an output currency to an explicit recipient. */
|
|
5710
|
+
const TAKE = 14;
|
|
5711
|
+
/** v4 action: exact-out single-hop swap. */
|
|
5712
|
+
const SWAP_EXACT_OUT_SINGLE = 8;
|
|
5713
|
+
/** Universal Router command: sweep the router's token balance to a recipient. */
|
|
5714
|
+
const SWEEP = 4;
|
|
5715
|
+
/** v4 sentinel: take the full positive currency delta (used with TAKE). */
|
|
5716
|
+
const OPEN_DELTA = 0n;
|
|
5717
|
+
const UINT128_MAX$2 = (1n << 128n) - 1n;
|
|
5718
|
+
const poolKeyComponents = [
|
|
5719
|
+
{
|
|
5720
|
+
name: "currency0",
|
|
5721
|
+
type: "address"
|
|
5722
|
+
},
|
|
5723
|
+
{
|
|
5724
|
+
name: "currency1",
|
|
5725
|
+
type: "address"
|
|
5726
|
+
},
|
|
5727
|
+
{
|
|
5728
|
+
name: "fee",
|
|
5729
|
+
type: "uint24"
|
|
5730
|
+
},
|
|
5731
|
+
{
|
|
5732
|
+
name: "tickSpacing",
|
|
5733
|
+
type: "int24"
|
|
5734
|
+
},
|
|
5735
|
+
{
|
|
5736
|
+
name: "hooks",
|
|
5737
|
+
type: "address"
|
|
5738
|
+
}
|
|
5739
|
+
];
|
|
5740
|
+
const exactInputSingleParamsAbi = [{
|
|
5741
|
+
type: "tuple",
|
|
5742
|
+
components: [
|
|
5743
|
+
{
|
|
5744
|
+
name: "poolKey",
|
|
5745
|
+
type: "tuple",
|
|
5746
|
+
components: poolKeyComponents
|
|
5747
|
+
},
|
|
5748
|
+
{
|
|
5749
|
+
name: "zeroForOne",
|
|
5750
|
+
type: "bool"
|
|
5751
|
+
},
|
|
5752
|
+
{
|
|
5753
|
+
name: "amountIn",
|
|
5754
|
+
type: "uint128"
|
|
5755
|
+
},
|
|
5756
|
+
{
|
|
5757
|
+
name: "amountOutMinimum",
|
|
5758
|
+
type: "uint128"
|
|
5759
|
+
},
|
|
5760
|
+
{
|
|
5761
|
+
name: "hookData",
|
|
5762
|
+
type: "bytes"
|
|
5763
|
+
}
|
|
5764
|
+
]
|
|
5765
|
+
}];
|
|
5766
|
+
const exactOutputSingleParamsAbi = [{
|
|
5767
|
+
type: "tuple",
|
|
5768
|
+
components: [
|
|
5769
|
+
{
|
|
5770
|
+
name: "poolKey",
|
|
5771
|
+
type: "tuple",
|
|
5772
|
+
components: poolKeyComponents
|
|
5773
|
+
},
|
|
5774
|
+
{
|
|
5775
|
+
name: "zeroForOne",
|
|
5776
|
+
type: "bool"
|
|
5777
|
+
},
|
|
5778
|
+
{
|
|
5779
|
+
name: "amountOut",
|
|
5780
|
+
type: "uint128"
|
|
5781
|
+
},
|
|
5782
|
+
{
|
|
5783
|
+
name: "amountInMaximum",
|
|
5784
|
+
type: "uint128"
|
|
5785
|
+
},
|
|
5786
|
+
{
|
|
5787
|
+
name: "hookData",
|
|
5788
|
+
type: "bytes"
|
|
5789
|
+
}
|
|
5790
|
+
]
|
|
5791
|
+
}];
|
|
5792
|
+
const currencyAmountAbi = [{
|
|
5793
|
+
name: "currency",
|
|
5794
|
+
type: "address"
|
|
5795
|
+
}, {
|
|
5796
|
+
name: "amount",
|
|
5797
|
+
type: "uint256"
|
|
5798
|
+
}];
|
|
5799
|
+
const takeToRecipientAbi = [
|
|
5800
|
+
{
|
|
5801
|
+
name: "currency",
|
|
5802
|
+
type: "address"
|
|
5803
|
+
},
|
|
5804
|
+
{
|
|
5805
|
+
name: "recipient",
|
|
5806
|
+
type: "address"
|
|
5807
|
+
},
|
|
5808
|
+
{
|
|
5809
|
+
name: "amount",
|
|
5810
|
+
type: "uint256"
|
|
5811
|
+
}
|
|
5812
|
+
];
|
|
5813
|
+
const sweepParamsAbi = [
|
|
5814
|
+
{
|
|
5815
|
+
name: "token",
|
|
5816
|
+
type: "address"
|
|
5817
|
+
},
|
|
5818
|
+
{
|
|
5819
|
+
name: "recipient",
|
|
5820
|
+
type: "address"
|
|
5821
|
+
},
|
|
5822
|
+
{
|
|
5823
|
+
name: "amountMin",
|
|
5824
|
+
type: "uint256"
|
|
5825
|
+
}
|
|
5826
|
+
];
|
|
5827
|
+
function assertUint128(amount) {
|
|
5828
|
+
if (amount < 0n || amount > UINT128_MAX$2) throw new AmountExceedsUint128Error(amount);
|
|
5829
|
+
}
|
|
5830
|
+
/**
|
|
5831
|
+
* Encode the input for a Universal Router `SWEEP` command that forwards the
|
|
5832
|
+
* router's full native-ETH balance (above `amountMin = 0`) to `recipient`.
|
|
5833
|
+
*/
|
|
5834
|
+
function encodeEthSweepInput(recipient) {
|
|
5835
|
+
return encodeAbiParameters(sweepParamsAbi, [
|
|
5836
|
+
zeroAddress,
|
|
5837
|
+
recipient,
|
|
5838
|
+
0n
|
|
5839
|
+
]);
|
|
5840
|
+
}
|
|
5841
|
+
/**
|
|
5842
|
+
* Build the output-take v4 action byte + param.
|
|
5843
|
+
*
|
|
5844
|
+
* Native-ETH output is taken with the explicit-recipient `TAKE` action so the
|
|
5845
|
+
* router forwards the ETH straight to `recipient` (the implicit-`msgSender`
|
|
5846
|
+
* `TAKE_ALL` leaves native ETH held by the router). ERC20 output uses
|
|
5847
|
+
* `TAKE_ALL`, which already credits the caller.
|
|
5848
|
+
*
|
|
5849
|
+
* @param nativeAmount - TAKE amount for native-ETH output: `OPEN_DELTA` (0) to
|
|
5850
|
+
* take the full credit (exact-in, protected by the swap's min) or the exact
|
|
5851
|
+
* output (exact-out).
|
|
5852
|
+
* @param erc20Amount - TAKE_ALL min/amount for ERC20 output: `amountOutMinimum`
|
|
5853
|
+
* (exact-in) or the exact output (exact-out).
|
|
5854
|
+
*/
|
|
5855
|
+
function buildOutputTake(tokenOut, recipient, nativeAmount, erc20Amount) {
|
|
5856
|
+
if (tokenOut === zeroAddress) {
|
|
5857
|
+
if (recipient === void 0) throw new MissingSweepRecipientError();
|
|
5858
|
+
return {
|
|
5859
|
+
action: TAKE,
|
|
5860
|
+
param: encodeAbiParameters(takeToRecipientAbi, [
|
|
5861
|
+
zeroAddress,
|
|
5862
|
+
recipient,
|
|
5863
|
+
nativeAmount
|
|
5864
|
+
])
|
|
5865
|
+
};
|
|
5866
|
+
}
|
|
5867
|
+
return {
|
|
5868
|
+
action: TAKE_ALL,
|
|
5869
|
+
param: encodeAbiParameters(currencyAmountAbi, [tokenOut, erc20Amount])
|
|
5870
|
+
};
|
|
5871
|
+
}
|
|
5872
|
+
/**
|
|
5873
|
+
* Build the typed `execute(...)` args + msg.value for an exact-in single-hop v4
|
|
5874
|
+
* swap. Use this when submitting via viem (`writeContract` / `submitWrite`).
|
|
5875
|
+
*
|
|
5876
|
+
* When `tokenOut` is native ETH the explicit-recipient `TAKE` action forwards
|
|
5877
|
+
* the bought ETH to `recipient` (no trailing SWEEP is needed for exact-in).
|
|
5878
|
+
*
|
|
5879
|
+
* @returns `args` ready to spread into `execute` and the ETH `value` to send
|
|
5880
|
+
* (= `amountIn` for native-ETH input, otherwise `0n`).
|
|
5881
|
+
*/
|
|
5882
|
+
function buildV4SwapExecuteArgs(args) {
|
|
5883
|
+
const { poolKey, zeroForOne, amountIn, amountOutMinimum, tokenIn, tokenOut, deadline, recipient, hookData = "0x" } = args;
|
|
5884
|
+
assertUint128(amountIn);
|
|
5885
|
+
assertUint128(amountOutMinimum);
|
|
5886
|
+
const swapParam = encodeAbiParameters(exactInputSingleParamsAbi, [{
|
|
5887
|
+
poolKey: {
|
|
5888
|
+
currency0: poolKey.currency0,
|
|
5889
|
+
currency1: poolKey.currency1,
|
|
5890
|
+
fee: Number(poolKey.fee),
|
|
5891
|
+
tickSpacing: Number(poolKey.tickSpacing),
|
|
5892
|
+
hooks: poolKey.hooks
|
|
5893
|
+
},
|
|
5894
|
+
zeroForOne,
|
|
5895
|
+
amountIn,
|
|
5896
|
+
amountOutMinimum,
|
|
5897
|
+
hookData
|
|
5898
|
+
}]);
|
|
5899
|
+
const settleParam = encodeAbiParameters(currencyAmountAbi, [tokenIn, amountIn]);
|
|
5900
|
+
const take = buildOutputTake(tokenOut, recipient, OPEN_DELTA, amountOutMinimum);
|
|
5901
|
+
const actions = encodePacked([
|
|
5902
|
+
"uint8",
|
|
5903
|
+
"uint8",
|
|
5904
|
+
"uint8"
|
|
5905
|
+
], [
|
|
5906
|
+
SWAP_EXACT_IN_SINGLE,
|
|
5907
|
+
SETTLE_ALL,
|
|
5908
|
+
take.action
|
|
5909
|
+
]);
|
|
5910
|
+
const v4Input = encodeAbiParameters([{
|
|
5911
|
+
name: "actions",
|
|
5912
|
+
type: "bytes"
|
|
5913
|
+
}, {
|
|
5914
|
+
name: "params",
|
|
5915
|
+
type: "bytes[]"
|
|
5916
|
+
}], [actions, [
|
|
5917
|
+
swapParam,
|
|
5918
|
+
settleParam,
|
|
5919
|
+
take.param
|
|
5920
|
+
]]);
|
|
5921
|
+
const commands = encodePacked(["uint8"], [V4_SWAP]);
|
|
5922
|
+
const value = tokenIn === zeroAddress ? amountIn : 0n;
|
|
5923
|
+
return {
|
|
5924
|
+
args: [
|
|
5925
|
+
commands,
|
|
5926
|
+
[v4Input],
|
|
5927
|
+
deadline
|
|
5928
|
+
],
|
|
5929
|
+
value
|
|
5191
5930
|
};
|
|
5192
5931
|
}
|
|
5193
5932
|
/**
|
|
5194
|
-
*
|
|
5195
|
-
*
|
|
5196
|
-
*
|
|
5933
|
+
* Build the `execute(commands, inputs, deadline)` calldata + msg.value for an
|
|
5934
|
+
* exact-in single-hop v4 swap.
|
|
5935
|
+
*
|
|
5936
|
+
* @returns The encoded calldata and the ETH `value` to send.
|
|
5197
5937
|
*/
|
|
5198
|
-
function
|
|
5199
|
-
|
|
5938
|
+
function buildV4SwapExecuteCalldata(args) {
|
|
5939
|
+
const { args: executeArgs, value } = buildV4SwapExecuteArgs(args);
|
|
5940
|
+
const data = encodeFunctionData({
|
|
5941
|
+
abi: universalRouterAbi,
|
|
5942
|
+
functionName: "execute",
|
|
5943
|
+
args: executeArgs
|
|
5944
|
+
});
|
|
5945
|
+
return {
|
|
5946
|
+
data,
|
|
5947
|
+
value
|
|
5948
|
+
};
|
|
5200
5949
|
}
|
|
5201
5950
|
/**
|
|
5202
|
-
*
|
|
5951
|
+
* Build the typed `execute(...)` args + msg.value for an exact-out single-hop v4
|
|
5952
|
+
* swap. Use this when submitting via viem (`writeContract` / `submitWrite`).
|
|
5203
5953
|
*
|
|
5204
|
-
*
|
|
5205
|
-
*
|
|
5954
|
+
* For native-ETH input, `value` is `amountInMaximum` (an overpay) and a trailing
|
|
5955
|
+
* `SWEEP` action refunds the unused ETH to `recipient`. For ERC20 input, Permit2
|
|
5956
|
+
* pulls only the settled amount, so no SWEEP is appended and `value` is `0n`.
|
|
5206
5957
|
*
|
|
5207
|
-
*
|
|
5208
|
-
* 32-byte slot. The struct fields are not available, so currency0/currency1
|
|
5209
|
-
* are zeroed and tickSpacing/fee are set to 0 (callers should use
|
|
5210
|
-
* getV3PoolTickSpacing and getV3PoolFee respectively).
|
|
5958
|
+
* @returns `args` ready to spread into `execute` and the ETH `value` to send.
|
|
5211
5959
|
*/
|
|
5212
|
-
function
|
|
5213
|
-
const
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5960
|
+
function buildV4ExactOutSwapExecuteArgs(args) {
|
|
5961
|
+
const { poolKey, zeroForOne, amountOut, amountInMaximum, tokenIn, tokenOut, deadline, recipient, hookData = "0x" } = args;
|
|
5962
|
+
assertUint128(amountOut);
|
|
5963
|
+
assertUint128(amountInMaximum);
|
|
5964
|
+
const swapParam = encodeAbiParameters(exactOutputSingleParamsAbi, [{
|
|
5965
|
+
poolKey: {
|
|
5966
|
+
currency0: poolKey.currency0,
|
|
5967
|
+
currency1: poolKey.currency1,
|
|
5968
|
+
fee: Number(poolKey.fee),
|
|
5969
|
+
tickSpacing: Number(poolKey.tickSpacing),
|
|
5970
|
+
hooks: poolKey.hooks
|
|
5971
|
+
},
|
|
5972
|
+
zeroForOne,
|
|
5973
|
+
amountOut,
|
|
5974
|
+
amountInMaximum,
|
|
5975
|
+
hookData
|
|
5976
|
+
}]);
|
|
5977
|
+
const settleParam = encodeAbiParameters(currencyAmountAbi, [tokenIn, amountInMaximum]);
|
|
5978
|
+
const take = buildOutputTake(tokenOut, recipient, amountOut, amountOut);
|
|
5979
|
+
const actions = encodePacked([
|
|
5980
|
+
"uint8",
|
|
5981
|
+
"uint8",
|
|
5982
|
+
"uint8"
|
|
5983
|
+
], [
|
|
5984
|
+
SWAP_EXACT_OUT_SINGLE,
|
|
5985
|
+
SETTLE_ALL,
|
|
5986
|
+
take.action
|
|
5987
|
+
]);
|
|
5988
|
+
const v4Input = encodeAbiParameters([{
|
|
5989
|
+
name: "actions",
|
|
5990
|
+
type: "bytes"
|
|
5991
|
+
}, {
|
|
5992
|
+
name: "params",
|
|
5993
|
+
type: "bytes[]"
|
|
5994
|
+
}], [actions, [
|
|
5995
|
+
swapParam,
|
|
5996
|
+
settleParam,
|
|
5997
|
+
take.param
|
|
5998
|
+
]]);
|
|
5999
|
+
const isNativeIn = tokenIn === zeroAddress;
|
|
6000
|
+
const commandList = [V4_SWAP];
|
|
6001
|
+
const inputs = [v4Input];
|
|
6002
|
+
if (isNativeIn) {
|
|
6003
|
+
commandList.push(SWEEP);
|
|
6004
|
+
inputs.push(encodeEthSweepInput(recipient));
|
|
6005
|
+
}
|
|
6006
|
+
const commands = encodePacked(commandList.map(() => "uint8"), commandList);
|
|
6007
|
+
const value = isNativeIn ? amountInMaximum : 0n;
|
|
6008
|
+
return {
|
|
6009
|
+
args: [
|
|
6010
|
+
commands,
|
|
6011
|
+
inputs,
|
|
6012
|
+
deadline
|
|
6013
|
+
],
|
|
6014
|
+
value
|
|
5220
6015
|
};
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
6016
|
+
}
|
|
6017
|
+
/**
|
|
6018
|
+
* Build the `execute(commands, inputs, deadline)` calldata + msg.value for an
|
|
6019
|
+
* exact-out single-hop v4 swap.
|
|
6020
|
+
*
|
|
6021
|
+
* @returns The encoded calldata and the ETH `value` to send.
|
|
6022
|
+
*/
|
|
6023
|
+
function buildV4ExactOutSwapExecuteCalldata(args) {
|
|
6024
|
+
const { args: executeArgs, value } = buildV4ExactOutSwapExecuteArgs(args);
|
|
6025
|
+
const data = encodeFunctionData({
|
|
6026
|
+
abi: universalRouterAbi,
|
|
6027
|
+
functionName: "execute",
|
|
6028
|
+
args: executeArgs
|
|
6029
|
+
});
|
|
5227
6030
|
return {
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
fee,
|
|
5231
|
-
tickSpacing,
|
|
5232
|
-
hooks
|
|
6031
|
+
data,
|
|
6032
|
+
value
|
|
5233
6033
|
};
|
|
5234
6034
|
}
|
|
5235
6035
|
|
|
6036
|
+
//#endregion
|
|
6037
|
+
//#region src/panoptic/v2/sync/snapshotRecovery.ts
|
|
6038
|
+
const rolesExecutionAbi = parseAbi(["function execTransactionWithRole(address to, uint256 value, bytes data, uint8 operation, bytes32 roleKey, bool shouldRevert)"]);
|
|
6039
|
+
/**
|
|
6040
|
+
* 4-byte function selectors for dispatch/dispatchFrom, derived from the generated ABI.
|
|
6041
|
+
* Used for scanning raw calldata inside smart contract wallet wrappers.
|
|
6042
|
+
*/
|
|
6043
|
+
const DISPATCH_SELECTOR = toFunctionSelector(panopticPoolV2Abi.find((e) => e.type === "function" && e.name === "dispatch")).slice(2);
|
|
6044
|
+
const DISPATCH_FROM_SELECTOR = toFunctionSelector(panopticPoolV2Abi.find((e) => e.type === "function" && e.name === "dispatchFrom")).slice(2);
|
|
6045
|
+
|
|
5236
6046
|
//#endregion
|
|
5237
6047
|
//#region src/panoptic/v2/greeks/index.ts
|
|
5238
6048
|
/** Fixed-point scale constants for sqrtPriceX96 arithmetic */
|
|
5239
6049
|
const Q96$1 = 1n << 96n;
|
|
5240
|
-
const Q192
|
|
5241
|
-
|
|
5242
|
-
//#endregion
|
|
5243
|
-
//#region src/panoptic/v2/tokenId/constants.ts
|
|
5244
|
-
/**
|
|
5245
|
-
* TokenId constants for the Panoptic v2 SDK.
|
|
5246
|
-
* @module v2/tokenId/constants
|
|
5247
|
-
*/
|
|
5248
|
-
/**
|
|
5249
|
-
* Default vegoid value used in pool ID encoding.
|
|
5250
|
-
*/
|
|
5251
|
-
const DEFAULT_VEGOID = 8n;
|
|
5252
|
-
/**
|
|
5253
|
-
* Bit positions and sizes for TokenId encoding.
|
|
5254
|
-
*/
|
|
5255
|
-
const TOKEN_ID_BITS = {
|
|
5256
|
-
POOL_ID_SIZE: 64n,
|
|
5257
|
-
VEGOID_STARTING_BIT: 40n,
|
|
5258
|
-
VEGOID_SIZE: 8n,
|
|
5259
|
-
TICK_SPACING_STARTING_BIT: 48n,
|
|
5260
|
-
TICK_SPACING_SIZE: 16n,
|
|
5261
|
-
LEG_SIZE: 48n,
|
|
5262
|
-
MAX_LEGS: 4n
|
|
5263
|
-
};
|
|
5264
|
-
/**
|
|
5265
|
-
* Bit positions within a leg (relative to leg start).
|
|
5266
|
-
*/
|
|
5267
|
-
const LEG_BITS = {
|
|
5268
|
-
ASSET_BIT: 0n,
|
|
5269
|
-
ASSET_SIZE: 1n,
|
|
5270
|
-
RATIO_BIT: 1n,
|
|
5271
|
-
RATIO_SIZE: 7n,
|
|
5272
|
-
IS_LONG_BIT: 8n,
|
|
5273
|
-
IS_LONG_SIZE: 1n,
|
|
5274
|
-
TOKEN_TYPE_BIT: 9n,
|
|
5275
|
-
TOKEN_TYPE_SIZE: 1n,
|
|
5276
|
-
RISK_PARTNER_BIT: 10n,
|
|
5277
|
-
RISK_PARTNER_SIZE: 2n,
|
|
5278
|
-
STRIKE_BIT: 12n,
|
|
5279
|
-
STRIKE_SIZE: 24n,
|
|
5280
|
-
WIDTH_BIT: 36n,
|
|
5281
|
-
WIDTH_SIZE: 12n
|
|
5282
|
-
};
|
|
5283
|
-
/**
|
|
5284
|
-
* Masks for extracting/encoding leg fields.
|
|
5285
|
-
*/
|
|
5286
|
-
const LEG_MASKS = {
|
|
5287
|
-
ASSET: (1n << LEG_BITS.ASSET_SIZE) - 1n,
|
|
5288
|
-
RATIO: (1n << LEG_BITS.RATIO_SIZE) - 1n,
|
|
5289
|
-
IS_LONG: (1n << LEG_BITS.IS_LONG_SIZE) - 1n,
|
|
5290
|
-
TOKEN_TYPE: (1n << LEG_BITS.TOKEN_TYPE_SIZE) - 1n,
|
|
5291
|
-
RISK_PARTNER: (1n << LEG_BITS.RISK_PARTNER_SIZE) - 1n,
|
|
5292
|
-
STRIKE: (1n << LEG_BITS.STRIKE_SIZE) - 1n,
|
|
5293
|
-
WIDTH: (1n << LEG_BITS.WIDTH_SIZE) - 1n,
|
|
5294
|
-
LEG: (1n << TOKEN_ID_BITS.LEG_SIZE) - 1n
|
|
5295
|
-
};
|
|
5296
|
-
/**
|
|
5297
|
-
* Maximum and minimum values for leg fields.
|
|
5298
|
-
*/
|
|
5299
|
-
const LEG_LIMITS = {
|
|
5300
|
-
MAX_RATIO: 127n,
|
|
5301
|
-
MAX_WIDTH: 4095n,
|
|
5302
|
-
MAX_STRIKE: 8388607n,
|
|
5303
|
-
MIN_STRIKE: -8388608n
|
|
5304
|
-
};
|
|
6050
|
+
const Q192 = 1n << 192n;
|
|
5305
6051
|
|
|
5306
6052
|
//#endregion
|
|
5307
6053
|
//#region src/panoptic/v2/utils/option-encoding-v2.ts
|
|
@@ -5334,31 +6080,6 @@ const OPTION_BURNT_EVENT = getAbiItem({
|
|
|
5334
6080
|
name: "OptionBurnt"
|
|
5335
6081
|
});
|
|
5336
6082
|
|
|
5337
|
-
//#endregion
|
|
5338
|
-
//#region src/panoptic/v2/formatters/rates.ts
|
|
5339
|
-
const SECONDS_PER_DAY = 86400n;
|
|
5340
|
-
const DAYS_PER_YEAR = 365n;
|
|
5341
|
-
const SECONDS_PER_YEAR = SECONDS_PER_DAY * DAYS_PER_YEAR;
|
|
5342
|
-
|
|
5343
|
-
//#endregion
|
|
5344
|
-
//#region src/panoptic/v2/tokenId/deriveUniqueTokenId.ts
|
|
5345
|
-
const POOL_ID_MASK$2 = (1n << 64n) - 1n;
|
|
5346
|
-
const MAX_LEGS = TOKEN_ID_BITS.MAX_LEGS;
|
|
5347
|
-
const MAX_OPTION_RATIO = LEG_LIMITS.MAX_RATIO;
|
|
5348
|
-
const Q192$1 = 1n << 192n;
|
|
5349
|
-
|
|
5350
|
-
//#endregion
|
|
5351
|
-
//#region src/panoptic/v2/tokenId/generateOverlapping.ts
|
|
5352
|
-
const MAX_UINT64 = (1n << 64n) - 1n;
|
|
5353
|
-
const MAX_UINT128$2 = (1n << 128n) - 1n;
|
|
5354
|
-
const Q192 = 1n << 192n;
|
|
5355
|
-
|
|
5356
|
-
//#endregion
|
|
5357
|
-
//#region src/panoptic/v2/tokenId/split.ts
|
|
5358
|
-
const POOL_ID_MASK$1 = (1n << TOKEN_ID_BITS.POOL_ID_SIZE) - 1n;
|
|
5359
|
-
const MAX_UINT128$1 = (1n << 128n) - 1n;
|
|
5360
|
-
const MAX_UINT256 = (1n << 256n) - 1n;
|
|
5361
|
-
|
|
5362
6083
|
//#endregion
|
|
5363
6084
|
//#region src/panoptic/v2/writes/protectedSettle.ts
|
|
5364
6085
|
const POOL_ID_MASK = (1n << 64n) - 1n;
|
|
@@ -5906,6 +6627,7 @@ async function swapExactInViaRouter(params) {
|
|
|
5906
6627
|
});
|
|
5907
6628
|
if (!quote.success) throw quote.error;
|
|
5908
6629
|
const resolvedDeadline = deadline ?? (await getBlockMeta({ client })).blockTimestamp + DEFAULT_DEADLINE_SECONDS;
|
|
6630
|
+
if (!quote.data.poolKey) throw new PanopticError("v4 swap requires a poolKey from the quote");
|
|
5909
6631
|
const { args, value } = buildV4SwapExecuteArgs({
|
|
5910
6632
|
poolKey: quote.data.poolKey,
|
|
5911
6633
|
zeroForOne: quote.data.zeroForOne,
|
|
@@ -5961,6 +6683,7 @@ async function swapExactOutViaRouter(params) {
|
|
|
5961
6683
|
});
|
|
5962
6684
|
if (!quote.success) throw quote.error;
|
|
5963
6685
|
const resolvedDeadline = deadline ?? (await getBlockMeta({ client })).blockTimestamp + DEFAULT_DEADLINE_SECONDS;
|
|
6686
|
+
if (!quote.data.poolKey) throw new PanopticError("v4 swap requires a poolKey from the quote");
|
|
5964
6687
|
const { args, value } = buildV4ExactOutSwapExecuteArgs({
|
|
5965
6688
|
poolKey: quote.data.poolKey,
|
|
5966
6689
|
zeroForOne: quote.data.zeroForOne,
|
|
@@ -5992,5 +6715,5 @@ async function swapExactOutViaRouterAndWait(params) {
|
|
|
5992
6715
|
}
|
|
5993
6716
|
|
|
5994
6717
|
//#endregion
|
|
5995
|
-
export { AmountExceedsUint128Error, InvalidSwapTokenError, MSG_SENDER, MissingSweepRecipientError, PERMIT2_ADDRESS, QuoterUnavailableError, SETTLE_ALL, SWAP_EXACT_IN_SINGLE, SWAP_EXACT_OUT_SINGLE, SWEEP, TAKE_ALL, UNISWAP_V3_ADDRESSES, UNISWAP_V4_ADDRESSES, UnsupportedChainError, V3_SWAP_EXACT_IN, V4_SWAP, approveErc20ForPermit2, approveErc20ForPermit2AndWait, approveRouterViaPermit2, approveRouterViaPermit2AndWait, buildV3SwapExecuteArgs, buildV3SwapExecuteCalldata, buildV4ExactOutSwapExecuteArgs, buildV4ExactOutSwapExecuteCalldata, buildV4SwapExecuteArgs, buildV4SwapExecuteCalldata, checkRouterApproval, encodeV3Path, getAmountsForLiquidity, getLpGreeks, getUniswapV3Addresses, getUniswapV4Addresses, quoteSwapExactInViaRouter, quoteSwapExactOutViaRouter, quoteV3ExactIn, quoteV4ExactInByPoolKey, resolveSwapRoute, swapExactInViaRouter, swapExactInViaRouterAndWait, swapExactOutViaRouter, swapExactOutViaRouterAndWait };
|
|
6718
|
+
export { AmountExceedsUint128Error, InvalidSwapTokenError, MSG_SENDER, MissingSweepRecipientError, PERMIT2_ADDRESS, QuoterUnavailableError, SETTLE_ALL, SWAP_EXACT_IN_SINGLE, SWAP_EXACT_OUT_SINGLE, SWEEP, TAKE_ALL, UNISWAP_V3_ADDRESSES, UNISWAP_V4_ADDRESSES, UnsupportedChainError, V3_SWAP_EXACT_IN, V3_SWAP_EXACT_OUT, V4_SWAP, annualizeLpFeeReturn, approveErc20ForPermit2, approveErc20ForPermit2AndWait, approveRouterViaPermit2, approveRouterViaPermit2AndWait, buildV3ExactOutSwapExecuteArgs, buildV3ExactOutSwapExecuteCalldata, buildV3SwapExecuteArgs, buildV3SwapExecuteCalldata, buildV4ExactOutSwapExecuteArgs, buildV4ExactOutSwapExecuteCalldata, buildV4SwapExecuteArgs, buildV4SwapExecuteCalldata, checkRouterApproval, encodeV3Path, estimateLpFees, getAmountsForLiquidity, getLpDepositAmounts, getLpDepositBreakdown, getLpFeeReturnSeries, getLpFundingDeposit, getLpGreeks, getLpPositionFunding, getMaxLpPositionSize, getUniswapV3Addresses, getUniswapV4Addresses, quoteSwapExactInViaRouter, quoteSwapExactInViaV3Router, quoteSwapExactOutViaRouter, quoteSwapExactOutViaV3Router, quoteV3ExactIn, quoteV3ExactOut, quoteV4ExactInByPoolKey, resolveSwapRoute, resolveV3SwapRoute, swapExactInViaRouter, swapExactInViaRouterAndWait, swapExactInViaV3Router, swapExactOutViaRouter, swapExactOutViaRouterAndWait, swapExactOutViaV3Router };
|
|
5996
6719
|
//# sourceMappingURL=index.js.map
|