@panoptic-eng/sdk 1.0.49 → 1.0.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/cow/index.js.map +1 -1
  2. package/dist/{cow-DMX9ebPk.js → cow-CSSKPknb.js} +1 -1
  3. package/dist/deployments.d.ts +12 -4
  4. package/dist/deployments.d.ts.map +1 -1
  5. package/dist/deployments.js +78 -10
  6. package/dist/deployments.js.map +1 -1
  7. package/dist/{greeks-9aTE-DwO.js → greeks-CNi1-cnp.js} +39 -16
  8. package/dist/index.d.ts +518 -76
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +701 -84
  11. package/dist/index.js.map +1 -1
  12. package/dist/{irm-mySi_YMF.js → irm-BJaJSUjF.js} +80 -12
  13. package/dist/panoptic/v2/greeks/index.d.ts +12 -1
  14. package/dist/panoptic/v2/greeks/index.d.ts.map +1 -1
  15. package/dist/panoptic/v2/greeks/index.js +39 -16
  16. package/dist/panoptic/v2/greeks/index.js.map +1 -1
  17. package/dist/panoptic/v2/index.d.ts +1336 -74
  18. package/dist/panoptic/v2/index.d.ts.map +1 -1
  19. package/dist/panoptic/v2/index.js +20322 -18660
  20. package/dist/panoptic/v2/index.js.map +1 -1
  21. package/dist/panoptic/v2/react-public.d.ts +1518 -240
  22. package/dist/panoptic/v2/react-public.d.ts.map +1 -1
  23. package/dist/panoptic/v2/react-public.js +11518 -9319
  24. package/dist/panoptic/v2/react-public.js.map +1 -1
  25. package/dist/{rates-CDLIUGDz.js → rates-COAJpKRd.js} +612 -73
  26. package/dist/{router-45ulPnWe.js → router-B-I1eACz.js} +5 -43
  27. package/dist/router-BSS7HKd2.js +837 -0
  28. package/dist/uniswap/index.d.ts +465 -215
  29. package/dist/uniswap/index.d.ts.map +1 -1
  30. package/dist/uniswap/index.js +1799 -1076
  31. package/dist/uniswap/index.js.map +1 -1
  32. package/dist/{v2-DshssxX4.js → v2-zF6b01PI.js} +2488 -1596
  33. package/dist/{writes-2reV_omF.js → writes-D1zN0v9x.js} +4 -3
  34. package/dist/zodiac/index.d.ts +51 -7
  35. package/dist/zodiac/index.d.ts.map +1 -1
  36. package/dist/zodiac/index.js +154 -2
  37. package/dist/zodiac/index.js.map +1 -1
  38. package/package.json +2 -1
  39. package/dist/router-B2jY9Nyb.js +0 -312
@@ -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/uniswap/v4/router/errors.ts
3611
+ //#region src/panoptic/v2/tokenId/constants.ts
3544
3612
  /**
3545
- * The chain has no configured Uniswap v4 addresses and none were supplied via
3546
- * overrides.
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
- * `tokenIn` is neither `currency0` nor `currency1` of the resolved pool.
3617
+ * Default vegoid value used in pool ID encoding.
3557
3618
  */
3558
- var InvalidSwapTokenError = class extends PanopticError {
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
- * An amount exceeds the uint128 range required by the v4 swap encoding.
3621
+ * Bit positions and sizes for TokenId encoding.
3569
3622
  */
3570
- var AmountExceedsUint128Error = class extends PanopticError {
3571
- name = "AmountExceedsUint128Error";
3572
- constructor(amount, cause) {
3573
- super(`Amount ${amount} exceeds uint128 maximum`, cause);
3574
- this.amount = amount;
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
- * A native-ETH swap needs a trailing Universal Router SWEEP to deliver the ETH
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
- var MissingSweepRecipientError = class extends PanopticError {
3582
- name = "MissingSweepRecipientError";
3583
- constructor(cause) {
3584
- super("A recipient is required to sweep native ETH back to the user", cause);
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
- * The V4Quoter is not available for the chain (no fallback in v1).
3652
+ * Masks for extracting/encoding leg fields.
3589
3653
  */
3590
- var QuoterUnavailableError = class extends PanopticError {
3591
- name = "QuoterUnavailableError";
3592
- constructor(chainId, cause) {
3593
- super(`V4Quoter unavailable for chain ${chainId}`, cause);
3594
- this.chainId = chainId;
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
- * Verified Uniswap v3 QuoterV2 addresses keyed by chainId. Add a chain only
3602
- * after verifying against the canonical Uniswap v3 deployment listing.
3665
+ * Maximum and minimum values for leg fields.
3603
3666
  */
3604
- const UNISWAP_V3_ADDRESSES = { 1: {
3605
- quoterV2: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e",
3606
- nonfungiblePositionManager: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88"
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
- * Resolve Uniswap v3 addresses for a chain, applying optional overrides.
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
- function getUniswapV3Addresses(chainId, overrides) {
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/uniswap/v4/abis/universalRouter.ts
3680
+ //#region src/panoptic/v2/tokenId/encoding.ts
3627
3681
  /**
3628
- * Minimal Universal Router ABI (only the `execute` overload we use).
3629
- * @module uniswap/v4/abis/universalRouter
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
- const universalRouterAbi = [{
3632
- type: "function",
3633
- name: "execute",
3634
- stateMutability: "payable",
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
- * Universal Router recipient sentinel: the router maps `address(1)` to
3658
- * `msg.sender` (the `execute` caller). Uniswap `Constants.MSG_SENDER`.
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
- const MSG_SENDER = "0x0000000000000000000000000000000000000001";
3661
- const UINT128_MAX$4 = (1n << 128n) - 1n;
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
- * Encode the packed v3 single-hop path `tokenIn ++ fee(uint24) ++ tokenOut`.
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 encodeV3Path(tokenIn, fee, tokenOut) {
3691
- if (fee < 0n || fee > 0xffffffn) throw new PanopticError(`v3 fee ${fee} exceeds uint24`);
3692
- return encodePacked([
3693
- "address",
3694
- "uint24",
3695
- "address"
3696
- ], [
3697
- tokenIn,
3698
- Number(fee),
3699
- tokenOut
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
- * Build the typed `execute(...)` args for an exact-in single-hop v3 swap whose
3704
- * output is delivered to the `execute` caller (recipient = MSG_SENDER).
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 buildV3SwapExecuteArgs(args) {
3707
- const { tokenIn, tokenOut, fee, amountIn, amountOutMinimum, deadline } = args;
3708
- if (tokenIn === zeroAddress || tokenOut === zeroAddress) throw new PanopticError("native ETH is not supported by the v3 exact-in router builder");
3709
- assertUint128$1(amountIn);
3710
- assertUint128$1(amountOutMinimum);
3711
- const path = encodeV3Path(tokenIn, fee, tokenOut);
3712
- const input = encodeAbiParameters(v3ExactInInputAbi, [
3713
- MSG_SENDER,
3714
- amountIn,
3715
- amountOutMinimum,
3716
- path,
3717
- true
3718
- ]);
3719
- const commands = encodePacked(["uint8"], [V3_SWAP_EXACT_IN]);
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
- args: [
3722
- commands,
3723
- [input],
3724
- deadline
3725
- ],
3726
- value: 0n
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
- * Build the `execute(commands, inputs, deadline)` calldata for an exact-in
3731
- * single-hop v3 swap.
3732
- */
3733
- function buildV3SwapExecuteCalldata(args) {
3734
- const { args: executeArgs, value } = buildV3SwapExecuteArgs(args);
3735
- const data = encodeFunctionData({
3736
- abi: universalRouterAbi,
3737
- functionName: "execute",
3738
- args: executeArgs
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
- data,
3742
- value
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/v3/abis/quoterV2.ts
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
- * Minimal Uniswap v3 QuoterV2 ABI (`quoteExactInputSingle`).
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
- * QuoterV2 is revert/staticcall-based (nonpayable but intended for `eth_call` /
3752
- * `simulateContract`), so it must be simulated, not read.
3753
- * @module uniswap/v3/abis/quoterV2
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
- const quoterV2Abi = [{
3756
- type: "function",
3757
- name: "quoteExactInputSingle",
3758
- stateMutability: "nonpayable",
3759
- inputs: [{
3760
- name: "params",
3761
- type: "tuple",
3762
- components: [
3763
- {
3764
- name: "tokenIn",
3765
- type: "address"
3766
- },
3767
- {
3768
- name: "tokenOut",
3769
- type: "address"
3770
- },
3771
- {
3772
- name: "amountIn",
3773
- type: "uint256"
3774
- },
3775
- {
3776
- name: "fee",
3777
- type: "uint24"
3778
- },
3779
- {
3780
- name: "sqrtPriceLimitX96",
3781
- type: "uint160"
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/v3/router/quote.ts
3807
- const BPS_DENOMINATOR$2 = 10000n;
3808
- const UINT128_MAX$3 = (1n << 128n) - 1n;
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
- * Quote an exact-in v3 swap. Returns `null` when the quote reverts (e.g. the
3811
- * pool does not exist / has no liquidity) so callers can skip that pool when
3812
- * ranking venues, rather than aborting the whole cycle.
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
- async function quoteV3ExactIn(params) {
3815
- const { client, chainId, tokenIn, tokenOut, fee, amountIn, slippageBps, blockNumber } = params;
3816
- if (amountIn < 0n || amountIn > UINT128_MAX$3) throw new PanopticError(`amountIn ${amountIn} exceeds uint128 maximum`);
3817
- if (slippageBps < 0n || slippageBps > BPS_DENOMINATOR$2) throw new PanopticError(`invalid slippageBps ${slippageBps}, must be 0..10000`);
3818
- const { quoterV2 } = getUniswapV3Addresses(chainId, params.addresses);
3819
- try {
3820
- const { result } = await client.simulateContract({
3821
- address: quoterV2,
3822
- abi: quoterV2Abi,
3823
- functionName: "quoteExactInputSingle",
3824
- blockNumber,
3825
- args: [{
3826
- tokenIn,
3827
- tokenOut,
3828
- amountIn,
3829
- fee: Number(fee),
3830
- sqrtPriceLimitX96: 0n
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
- amountOut,
3837
- amountOutMinimum,
3838
- gasEstimate
3975
+ ...point,
3976
+ feeReturnPercent: fees.div(capital).mul(100)
3839
3977
  };
3840
- } catch (err) {
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/addresses.ts
3982
+ //#region src/uniswap/v4/router/errors.ts
3852
3983
  /**
3853
- * Canonical Permit2, identical across all chains.
3984
+ * The chain has no configured Uniswap v4 addresses and none were supplied via
3985
+ * overrides.
3854
3986
  */
3855
- const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
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
- * Verified Uniswap v4 deployment addresses keyed by chainId.
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
- const UNISWAP_V4_ADDRESSES = { 1: {
3864
- universalRouter: "0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af",
3865
- v4Quoter: "0x52F0E24D1c21C8A0cB1e5a5dD6198556BD9E1203",
3866
- poolManager: "0x000000000004444c5dc75cB358380D2e3dE08A90",
3867
- positionManager: "0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e",
3868
- permit2: PERMIT2_ADDRESS
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
- * Resolve the Uniswap v4 addresses for a chain, applying optional overrides.
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
- function getUniswapV4Addresses(chainId, overrides) {
3880
- const base = UNISWAP_V4_ADDRESSES[Number(chainId)];
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.universalRouter === void 0 || merged.v4Quoter === void 0 || merged.poolManager === void 0 || merged.positionManager === void 0 || merged.permit2 === void 0) throw new UnsupportedChainError(chainId);
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/router/encodeSwap.ts
3892
- /** Universal Router command byte for a v4 swap. */
3893
- const V4_SWAP = 16;
3894
- /** v4 action: exact-in single-hop swap. */
3895
- const SWAP_EXACT_IN_SINGLE = 6;
3896
- /** v4 action: pay all of the input currency owed. */
3897
- const SETTLE_ALL = 12;
3898
- /** v4 action: take all of the output currency owed. */
3899
- const TAKE_ALL = 15;
3900
- /** v4 action: take an output currency to an explicit recipient. */
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: "hookData",
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: "amountInMaximum",
3975
- type: "uint128"
4086
+ name: "inputs",
4087
+ type: "bytes[]"
3976
4088
  },
3977
4089
  {
3978
- name: "hookData",
3979
- type: "bytes"
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
- const takeToRecipientAbi = [
3991
- {
3992
- name: "currency",
3993
- type: "address"
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: "amount",
4115
+ name: "amountIn",
4001
4116
  type: "uint256"
4002
- }
4003
- ];
4004
- const sweepParamsAbi = [
4117
+ },
4005
4118
  {
4006
- name: "token",
4007
- type: "address"
4119
+ name: "amountOutMinimum",
4120
+ type: "uint256"
4008
4121
  },
4009
4122
  {
4010
- name: "recipient",
4011
- type: "address"
4123
+ name: "path",
4124
+ type: "bytes"
4012
4125
  },
4013
4126
  {
4014
- name: "amountMin",
4015
- type: "uint256"
4127
+ name: "payerIsUser",
4128
+ type: "bool"
4016
4129
  }
4017
4130
  ];
4018
- function assertUint128(amount) {
4019
- if (amount < 0n || amount > UINT128_MAX$2) throw new AmountExceedsUint128Error(amount);
4131
+ function assertUint128$1(amount) {
4132
+ if (amount < 0n || amount > UINT128_MAX$4) throw new AmountExceedsUint128Error(amount);
4020
4133
  }
4021
4134
  /**
4022
- * Encode the input for a Universal Router `SWEEP` command that forwards 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 encodeEthSweepInput(recipient) {
4026
- return encodeAbiParameters(sweepParamsAbi, [
4027
- zeroAddress,
4028
- recipient,
4029
- 0n
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 output-take v4 action byte + param.
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 buildOutputTake(tokenOut, recipient, nativeAmount, erc20Amount) {
4047
- if (tokenOut === zeroAddress) {
4048
- if (recipient === void 0) throw new MissingSweepRecipientError();
4049
- return {
4050
- action: TAKE,
4051
- param: encodeAbiParameters(takeToRecipientAbi, [
4052
- zeroAddress,
4053
- recipient,
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
- hookData
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 v4Input = encodeAbiParameters([{
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
- [v4Input],
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 + msg.value for an
4125
- * exact-in single-hop v4 swap.
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 buildV4SwapExecuteCalldata(args) {
4130
- const { args: executeArgs, value } = buildV4SwapExecuteArgs(args);
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 + msg.value for an exact-out single-hop v4
4143
- * swap. Use this when submitting via viem (`writeContract` / `submitWrite`).
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 buildV4ExactOutSwapExecuteArgs(args) {
4152
- const { poolKey, zeroForOne, amountOut, amountInMaximum, tokenIn, tokenOut, deadline, recipient, hookData = "0x" } = args;
4153
- assertUint128(amountOut);
4154
- assertUint128(amountInMaximum);
4155
- const swapParam = encodeAbiParameters(exactOutputSingleParamsAbi, [{
4156
- poolKey: {
4157
- currency0: poolKey.currency0,
4158
- currency1: poolKey.currency1,
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
- hookData
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 v4Input = encodeAbiParameters([{
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
- inputs,
4235
+ [input],
4203
4236
  deadline
4204
4237
  ],
4205
- value
4238
+ value: 0n
4206
4239
  };
4207
4240
  }
4208
- /**
4209
- * Build the `execute(commands, inputs, deadline)` calldata + msg.value for an
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/panoptic/v2/clients/blockMeta.ts
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
- * Parse Panoptic events from transaction logs.
4257
+ * Minimal Uniswap v3 QuoterV2 ABI.
4297
4258
  *
4298
- * @param logs - The transaction logs
4299
- * @returns Parsed Panoptic events
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
- function parsePanopticEvents(logs) {
4302
- const events = [];
4303
- for (const log of logs) {
4304
- const baseEvent = {
4305
- blockNumber: log.blockNumber ?? 0n,
4306
- blockHash: log.blockHash ?? "0x0000000000000000000000000000000000000000000000000000000000000000",
4307
- transactionHash: log.transactionHash ?? "0x",
4308
- logIndex: BigInt(log.logIndex ?? 0)
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
- internalType: "uint160",
4539
- name: "sqrtPriceX96",
4540
- type: "uint160"
4272
+ name: "tokenIn",
4273
+ type: "address"
4541
4274
  },
4542
4275
  {
4543
- internalType: "int24",
4544
- name: "tick",
4545
- type: "int24"
4276
+ name: "tokenOut",
4277
+ type: "address"
4546
4278
  },
4547
4279
  {
4548
- internalType: "uint24",
4549
- name: "protocolFee",
4550
- type: "uint24"
4280
+ name: "amountIn",
4281
+ type: "uint256"
4551
4282
  },
4552
4283
  {
4553
- internalType: "uint24",
4554
- name: "lpFee",
4284
+ name: "fee",
4555
4285
  type: "uint24"
4286
+ },
4287
+ {
4288
+ name: "sqrtPriceLimitX96",
4289
+ type: "uint160"
4556
4290
  }
4557
- ],
4558
- stateMutability: "view",
4559
- type: "function"
4560
- },
4561
- {
4562
- inputs: [{
4563
- internalType: "PoolId",
4564
- name: "poolId",
4565
- type: "bytes32"
4566
- }],
4567
- name: "getFeeGrowthGlobals",
4568
- outputs: [{
4569
- internalType: "uint256",
4570
- name: "feeGrowthGlobal0",
4571
- type: "uint256"
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
- * Extract tickSpacing from the encoded 64-bit poolId.
5195
- * Layout: poolAddress (5 bytes) + vegoid (1 byte) + tickSpacing (2 bytes).
5196
- * tickSpacing occupies bits 48–63.
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 tickSpacingFromPoolId(poolId) {
5199
- return poolId >> 48n & 0xffffn;
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
- * Parse pool key from ABI-encoded bytes.
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
- * V4 pools: PoolKey struct is ABI-encoded as 5 consecutive 32-byte slots
5205
- * (currency0, currency1, fee, tickSpacing, hooks).
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
- * V3 pools: poolKey() returns abi.encode(uniswapV3PoolAddress) — a single
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 parsePoolKey(poolKeyBytes) {
5213
- const hex = poolKeyBytes.slice(2);
5214
- if (hex.length <= 64) return {
5215
- currency0: zeroAddress,
5216
- currency1: zeroAddress,
5217
- fee: 0n,
5218
- tickSpacing: 0n,
5219
- hooks: zeroAddress
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
- if (hex.length < 320) throw new Error(`Malformed V4 pool key: expected 320 hex chars (160 bytes), got ${hex.length}`);
5222
- const currency0 = `0x${hex.slice(24, 64)}`;
5223
- const currency1 = `0x${hex.slice(88, 128)}`;
5224
- const fee = BigInt(`0x${hex.slice(128, 192)}`);
5225
- const tickSpacing = BigInt(`0x${hex.slice(192, 256)}`);
5226
- const hooks = `0x${hex.slice(280, 320)}`;
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
- currency0,
5229
- currency1,
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$2 = 1n << 192n;
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