@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
@@ -0,0 +1,837 @@
1
+ import { PanopticError, getBlockMeta, getPoolMetadata, submitWrite } from "./rates-COAJpKRd.js";
2
+ import { tickToSqrtPriceX96 } from "./greeks-CNi1-cnp.js";
3
+ import { decodeAllLegs } from "./writes-D1zN0v9x.js";
4
+ import { BaseError, ContractFunctionRevertedError, encodeAbiParameters, encodeFunctionData, encodePacked, isAddressEqual, zeroAddress } from "viem";
5
+
6
+ //#region src/uniswap/lpDeposit.ts
7
+ const Q96 = 1n << 96n;
8
+ const ceilDiv = (n, d) => (n + d - 1n) / d;
9
+ /** Uniswap mint principal in raw token units, rounded up per leg, plus 5% headroom. */
10
+ function getLpDepositAmounts(ranges, sqrtPriceX96) {
11
+ return getLpDepositBreakdown(ranges, sqrtPriceX96).total;
12
+ }
13
+ /** Principal and separately identified funding headroom, in native token units. */
14
+ function getLpDepositBreakdown(ranges, sqrtPriceX96) {
15
+ if (sqrtPriceX96 <= 0n) throw new Error("Invalid pool price");
16
+ let amount0 = 0n;
17
+ let amount1 = 0n;
18
+ for (const { tickLower, tickUpper, liquidity } of ranges) {
19
+ if (!Number.isInteger(tickLower) || !Number.isInteger(tickUpper) || tickLower < -887272 || tickUpper > 887272 || tickLower >= tickUpper || liquidity < 0n) throw new Error("Invalid LP range");
20
+ const lower = tickToSqrtPriceX96(BigInt(tickLower));
21
+ const upper = tickToSqrtPriceX96(BigInt(tickUpper));
22
+ const price = sqrtPriceX96 < lower ? lower : sqrtPriceX96 > upper ? upper : sqrtPriceX96;
23
+ amount0 += ceilDiv(liquidity * Q96 * (upper - price), upper * price);
24
+ amount1 += ceilDiv(liquidity * (price - lower), Q96);
25
+ }
26
+ const buffer = {
27
+ amount0: ceilDiv(amount0 * 5n, 100n),
28
+ amount1: ceilDiv(amount1 * 5n, 100n)
29
+ };
30
+ return {
31
+ principal: {
32
+ amount0,
33
+ amount1
34
+ },
35
+ buffer,
36
+ total: {
37
+ amount0: amount0 + buffer.amount0,
38
+ amount1: amount1 + buffer.amount1
39
+ }
40
+ };
41
+ }
42
+ /** Full deployed liquidity value; never a leveraged protocol margin estimate. */
43
+ function getLpPositionFunding(params) {
44
+ const { tokenId, positionSize, tickSpacing, sqrtPriceX96, valuationSqrtPriceX96, quoteTokenIndex } = params;
45
+ if (positionSize < 0n || tickSpacing <= 0n || valuationSqrtPriceX96 <= 0n) throw new Error("Invalid LP funding input");
46
+ const legs = decodeAllLegs(tokenId);
47
+ 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");
48
+ const ranges = legs.map((leg) => {
49
+ const width = leg.width * tickSpacing;
50
+ const tickLower = leg.strike - width / 2n;
51
+ const tickUpper = leg.strike + (width + 1n) / 2n;
52
+ const lower = tickToSqrtPriceX96(tickLower);
53
+ const upper = tickToSqrtPriceX96(tickUpper);
54
+ const amount = positionSize * leg.optionRatio;
55
+ const liquidity = leg.asset === 0n ? amount * (lower * upper / Q96) / (upper - lower) : amount * Q96 / (upper - lower);
56
+ return {
57
+ tickLower: Number(tickLower),
58
+ tickUpper: Number(tickUpper),
59
+ liquidity
60
+ };
61
+ });
62
+ const breakdown = getLpDepositBreakdown(ranges, sqrtPriceX96);
63
+ const priceSquared = valuationSqrtPriceX96 * valuationSqrtPriceX96;
64
+ const value = ({ amount0, amount1 }) => quoteTokenIndex === 0 ? amount0 + ceilDiv(amount1 * Q96 * Q96, priceSquared) : amount1 + ceilDiv(amount0 * priceSquared, Q96 * Q96);
65
+ const principalInQuote = value(breakdown.principal);
66
+ const totalInQuote = value(breakdown.total);
67
+ return {
68
+ ...breakdown,
69
+ principalInQuote,
70
+ bufferInQuote: totalInQuote - principalInQuote,
71
+ totalInQuote
72
+ };
73
+ }
74
+ /** Remaining deposit in the requested token mix, rounded up to cover the shortfall. */
75
+ function getLpFundingDeposit({ funding, availableInQuote, minimumInQuote = 0n, quoteTokenIndex, valuationSqrtPriceX96, quotePercent }) {
76
+ if (valuationSqrtPriceX96 <= 0n || quotePercent !== void 0 && (quotePercent < 0n || quotePercent > 100n)) throw new Error("Invalid LP funding split");
77
+ const requiredInQuote = minimumInQuote > funding.totalInQuote ? minimumInQuote : funding.totalInQuote;
78
+ const shortfallInQuote = requiredInQuote > availableInQuote ? requiredInQuote - availableInQuote : 0n;
79
+ const defaultQuotePercent = funding.totalInQuote > 0n ? (quoteTokenIndex === 0 ? funding.total.amount0 : funding.total.amount1) * 100n / funding.totalInQuote : 100n;
80
+ if (quotePercent === void 0) return {
81
+ requiredInQuote,
82
+ shortfallInQuote,
83
+ defaultQuotePercent,
84
+ amount0: funding.totalInQuote > 0n ? ceilDiv(funding.total.amount0 * shortfallInQuote, funding.totalInQuote) : 0n,
85
+ amount1: funding.totalInQuote > 0n ? ceilDiv(funding.total.amount1 * shortfallInQuote, funding.totalInQuote) : 0n
86
+ };
87
+ const quoteAmount = shortfallInQuote * quotePercent / 100n;
88
+ const assetValue = shortfallInQuote - quoteAmount;
89
+ const priceSquared = valuationSqrtPriceX96 * valuationSqrtPriceX96;
90
+ return {
91
+ requiredInQuote,
92
+ shortfallInQuote,
93
+ defaultQuotePercent,
94
+ amount0: quoteTokenIndex === 0 ? quoteAmount : ceilDiv(assetValue * Q96 * Q96, priceSquared),
95
+ amount1: quoteTokenIndex === 1 ? quoteAmount : ceilDiv(assetValue * priceSquared, Q96 * Q96)
96
+ };
97
+ }
98
+ /** Largest raw size funded by available collateral, including native-token rounding. */
99
+ function getMaxLpPositionSize(params) {
100
+ if (params.minimumRequirement && (params.minimumRequirement.referenceSize <= 0n || params.minimumRequirement.requiredInQuote < 0n)) throw new Error("Invalid reference requirement");
101
+ if (params.availableInQuote <= 0n) return 0n;
102
+ let low = 0n;
103
+ let high = (1n << 128n) - 1n;
104
+ while (low < high) {
105
+ const mid = (low + high + 1n) / 2n;
106
+ const funding = getLpPositionFunding({
107
+ ...params,
108
+ positionSize: mid
109
+ });
110
+ const minimum = params.minimumRequirement ? ceilDiv(params.minimumRequirement.requiredInQuote * mid, params.minimumRequirement.referenceSize) : 0n;
111
+ if (funding.totalInQuote <= params.availableInQuote && minimum <= params.availableInQuote) low = mid;
112
+ else high = mid - 1n;
113
+ }
114
+ return low;
115
+ }
116
+
117
+ //#endregion
118
+ //#region src/uniswap/v4/router/errors.ts
119
+ /**
120
+ * The chain has no configured Uniswap v4 addresses and none were supplied via
121
+ * overrides.
122
+ */
123
+ var UnsupportedChainError = class extends PanopticError {
124
+ name = "UnsupportedChainError";
125
+ constructor(chainId, cause) {
126
+ super(`Uniswap v4 router not configured for chain ${chainId}`, cause);
127
+ this.chainId = chainId;
128
+ }
129
+ };
130
+ /**
131
+ * `tokenIn` is neither `currency0` nor `currency1` of the resolved pool.
132
+ */
133
+ var InvalidSwapTokenError = class extends PanopticError {
134
+ name = "InvalidSwapTokenError";
135
+ constructor(token, currency0, currency1, cause) {
136
+ super(`Token ${token} is not part of the pool (currency0=${currency0}, currency1=${currency1})`, cause);
137
+ this.token = token;
138
+ this.currency0 = currency0;
139
+ this.currency1 = currency1;
140
+ }
141
+ };
142
+ /**
143
+ * An amount exceeds the uint128 range required by the v4 swap encoding.
144
+ */
145
+ var AmountExceedsUint128Error = class extends PanopticError {
146
+ name = "AmountExceedsUint128Error";
147
+ constructor(amount, cause) {
148
+ super(`Amount ${amount} exceeds uint128 maximum`, cause);
149
+ this.amount = amount;
150
+ }
151
+ };
152
+ /**
153
+ * A native-ETH swap needs a trailing Universal Router SWEEP to deliver the ETH
154
+ * output (or refund the input overpay), but no `recipient` was supplied.
155
+ */
156
+ var MissingSweepRecipientError = class extends PanopticError {
157
+ name = "MissingSweepRecipientError";
158
+ constructor(cause) {
159
+ super("A recipient is required to sweep native ETH back to the user", cause);
160
+ }
161
+ };
162
+ /**
163
+ * The V4Quoter is not available for the chain (no fallback in v1).
164
+ */
165
+ var QuoterUnavailableError = class extends PanopticError {
166
+ name = "QuoterUnavailableError";
167
+ constructor(chainId, cause) {
168
+ super(`V4Quoter unavailable for chain ${chainId}`, cause);
169
+ this.chainId = chainId;
170
+ }
171
+ };
172
+
173
+ //#endregion
174
+ //#region src/uniswap/v3/addresses.ts
175
+ /**
176
+ * Verified Uniswap v3 QuoterV2 addresses keyed by chainId. Add a chain only
177
+ * after verifying against the canonical Uniswap v3 deployment listing.
178
+ */
179
+ const UNISWAP_V3_ADDRESSES = {
180
+ 1: {
181
+ quoterV2: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e",
182
+ nonfungiblePositionManager: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88"
183
+ },
184
+ 4663: {
185
+ quoterV2: "0x33e885ed0ec9bf04ecfb19341582aadcb4c8a9e7",
186
+ nonfungiblePositionManager: "0x73991a25c818bf1f1128deaab1492d45638de0d3"
187
+ }
188
+ };
189
+ /**
190
+ * Resolve Uniswap v3 addresses for a chain, applying optional overrides.
191
+ *
192
+ * @throws {UnsupportedChainError} when the chain is not listed and overrides do
193
+ * not supply every required address.
194
+ */
195
+ function getUniswapV3Addresses(chainId, overrides) {
196
+ const id = Number(chainId);
197
+ const base = UNISWAP_V3_ADDRESSES[id];
198
+ const merged = {
199
+ ...base,
200
+ ...overrides
201
+ };
202
+ if (!merged.quoterV2 || !merged.nonfungiblePositionManager) throw new UnsupportedChainError(BigInt(id));
203
+ return merged;
204
+ }
205
+
206
+ //#endregion
207
+ //#region src/uniswap/v4/abis/universalRouter.ts
208
+ /**
209
+ * Minimal Universal Router ABI (only the `execute` overload we use).
210
+ * @module uniswap/v4/abis/universalRouter
211
+ */
212
+ const universalRouterAbi = [{
213
+ type: "function",
214
+ name: "execute",
215
+ stateMutability: "payable",
216
+ inputs: [
217
+ {
218
+ name: "commands",
219
+ type: "bytes"
220
+ },
221
+ {
222
+ name: "inputs",
223
+ type: "bytes[]"
224
+ },
225
+ {
226
+ name: "deadline",
227
+ type: "uint256"
228
+ }
229
+ ],
230
+ outputs: []
231
+ }];
232
+
233
+ //#endregion
234
+ //#region src/uniswap/v3/router/encodeSwap.ts
235
+ /** Universal Router command byte for a v3 exact-in swap. */
236
+ const V3_SWAP_EXACT_IN = 0;
237
+ /** Universal Router command byte for a v3 exact-out swap. */
238
+ const V3_SWAP_EXACT_OUT = 1;
239
+ /**
240
+ * Universal Router recipient sentinel: the router maps `address(1)` to
241
+ * `msg.sender` (the `execute` caller). Uniswap `Constants.MSG_SENDER`.
242
+ */
243
+ const MSG_SENDER = "0x0000000000000000000000000000000000000001";
244
+ const UINT128_MAX$1 = (1n << 128n) - 1n;
245
+ const v3ExactInInputAbi = [
246
+ {
247
+ name: "recipient",
248
+ type: "address"
249
+ },
250
+ {
251
+ name: "amountIn",
252
+ type: "uint256"
253
+ },
254
+ {
255
+ name: "amountOutMinimum",
256
+ type: "uint256"
257
+ },
258
+ {
259
+ name: "path",
260
+ type: "bytes"
261
+ },
262
+ {
263
+ name: "payerIsUser",
264
+ type: "bool"
265
+ }
266
+ ];
267
+ function assertUint128(amount) {
268
+ if (amount < 0n || amount > UINT128_MAX$1) throw new AmountExceedsUint128Error(amount);
269
+ }
270
+ /**
271
+ * Encode the packed v3 single-hop path `tokenIn ++ fee(uint24) ++ tokenOut`.
272
+ */
273
+ function encodeV3Path(tokenIn, fee, tokenOut) {
274
+ if (fee < 0n || fee > 0xffffffn) throw new PanopticError(`v3 fee ${fee} exceeds uint24`);
275
+ return encodePacked([
276
+ "address",
277
+ "uint24",
278
+ "address"
279
+ ], [
280
+ tokenIn,
281
+ Number(fee),
282
+ tokenOut
283
+ ]);
284
+ }
285
+ /**
286
+ * Build the typed `execute(...)` args for an exact-in single-hop v3 swap whose
287
+ * output is delivered to the `execute` caller (recipient = MSG_SENDER).
288
+ */
289
+ function buildV3SwapExecuteArgs(args) {
290
+ const { tokenIn, tokenOut, fee, amountIn, amountOutMinimum, deadline } = args;
291
+ if (tokenIn === zeroAddress || tokenOut === zeroAddress) throw new PanopticError("native ETH is not supported by the v3 exact-in router builder");
292
+ assertUint128(amountIn);
293
+ assertUint128(amountOutMinimum);
294
+ const path = encodeV3Path(tokenIn, fee, tokenOut);
295
+ const input = encodeAbiParameters(v3ExactInInputAbi, [
296
+ MSG_SENDER,
297
+ amountIn,
298
+ amountOutMinimum,
299
+ path,
300
+ true
301
+ ]);
302
+ const commands = encodePacked(["uint8"], [V3_SWAP_EXACT_IN]);
303
+ return {
304
+ args: [
305
+ commands,
306
+ [input],
307
+ deadline
308
+ ],
309
+ value: 0n
310
+ };
311
+ }
312
+ /**
313
+ * Build the `execute(commands, inputs, deadline)` calldata for an exact-in
314
+ * single-hop v3 swap.
315
+ */
316
+ function buildV3SwapExecuteCalldata(args) {
317
+ const { args: executeArgs, value } = buildV3SwapExecuteArgs(args);
318
+ const data = encodeFunctionData({
319
+ abi: universalRouterAbi,
320
+ functionName: "execute",
321
+ args: executeArgs
322
+ });
323
+ return {
324
+ data,
325
+ value
326
+ };
327
+ }
328
+ const v3ExactOutInputAbi = [
329
+ {
330
+ name: "recipient",
331
+ type: "address"
332
+ },
333
+ {
334
+ name: "amountOut",
335
+ type: "uint256"
336
+ },
337
+ {
338
+ name: "amountInMaximum",
339
+ type: "uint256"
340
+ },
341
+ {
342
+ name: "path",
343
+ type: "bytes"
344
+ },
345
+ {
346
+ name: "payerIsUser",
347
+ type: "bool"
348
+ }
349
+ ];
350
+ /**
351
+ * Build the typed `execute(...)` args for an exact-out single-hop v3 swap.
352
+ * The v3 exact-out path is reversed: `tokenOut ++ fee ++ tokenIn`.
353
+ */
354
+ function buildV3ExactOutSwapExecuteArgs(args) {
355
+ const { tokenIn, tokenOut, fee, amountOut, amountInMaximum, deadline } = args;
356
+ if (tokenIn === zeroAddress || tokenOut === zeroAddress) throw new PanopticError("native ETH is not supported by the v3 exact-out router builder");
357
+ assertUint128(amountOut);
358
+ assertUint128(amountInMaximum);
359
+ const path = encodeV3Path(tokenOut, fee, tokenIn);
360
+ const input = encodeAbiParameters(v3ExactOutInputAbi, [
361
+ MSG_SENDER,
362
+ amountOut,
363
+ amountInMaximum,
364
+ path,
365
+ true
366
+ ]);
367
+ const commands = encodePacked(["uint8"], [V3_SWAP_EXACT_OUT]);
368
+ return {
369
+ args: [
370
+ commands,
371
+ [input],
372
+ deadline
373
+ ],
374
+ value: 0n
375
+ };
376
+ }
377
+ function buildV3ExactOutSwapExecuteCalldata(args) {
378
+ const { args: executeArgs, value } = buildV3ExactOutSwapExecuteArgs(args);
379
+ const data = encodeFunctionData({
380
+ abi: universalRouterAbi,
381
+ functionName: "execute",
382
+ args: executeArgs
383
+ });
384
+ return {
385
+ data,
386
+ value
387
+ };
388
+ }
389
+
390
+ //#endregion
391
+ //#region src/uniswap/v3/abis/quoterV2.ts
392
+ /**
393
+ * Minimal Uniswap v3 QuoterV2 ABI.
394
+ *
395
+ * QuoterV2 is revert/staticcall-based (nonpayable but intended for `eth_call` /
396
+ * `simulateContract`), so it must be simulated, not read.
397
+ * @module uniswap/v3/abis/quoterV2
398
+ */
399
+ const quoterV2Abi = [{
400
+ type: "function",
401
+ name: "quoteExactInputSingle",
402
+ stateMutability: "nonpayable",
403
+ inputs: [{
404
+ name: "params",
405
+ type: "tuple",
406
+ components: [
407
+ {
408
+ name: "tokenIn",
409
+ type: "address"
410
+ },
411
+ {
412
+ name: "tokenOut",
413
+ type: "address"
414
+ },
415
+ {
416
+ name: "amountIn",
417
+ type: "uint256"
418
+ },
419
+ {
420
+ name: "fee",
421
+ type: "uint24"
422
+ },
423
+ {
424
+ name: "sqrtPriceLimitX96",
425
+ type: "uint160"
426
+ }
427
+ ]
428
+ }],
429
+ outputs: [
430
+ {
431
+ name: "amountOut",
432
+ type: "uint256"
433
+ },
434
+ {
435
+ name: "sqrtPriceX96After",
436
+ type: "uint160"
437
+ },
438
+ {
439
+ name: "initializedTicksCrossed",
440
+ type: "uint32"
441
+ },
442
+ {
443
+ name: "gasEstimate",
444
+ type: "uint256"
445
+ }
446
+ ]
447
+ }, {
448
+ type: "function",
449
+ name: "quoteExactOutputSingle",
450
+ stateMutability: "nonpayable",
451
+ inputs: [{
452
+ name: "params",
453
+ type: "tuple",
454
+ components: [
455
+ {
456
+ name: "tokenIn",
457
+ type: "address"
458
+ },
459
+ {
460
+ name: "tokenOut",
461
+ type: "address"
462
+ },
463
+ {
464
+ name: "amount",
465
+ type: "uint256"
466
+ },
467
+ {
468
+ name: "fee",
469
+ type: "uint24"
470
+ },
471
+ {
472
+ name: "sqrtPriceLimitX96",
473
+ type: "uint160"
474
+ }
475
+ ]
476
+ }],
477
+ outputs: [
478
+ {
479
+ name: "amountIn",
480
+ type: "uint256"
481
+ },
482
+ {
483
+ name: "sqrtPriceX96After",
484
+ type: "uint160"
485
+ },
486
+ {
487
+ name: "initializedTicksCrossed",
488
+ type: "uint32"
489
+ },
490
+ {
491
+ name: "gasEstimate",
492
+ type: "uint256"
493
+ }
494
+ ]
495
+ }];
496
+
497
+ //#endregion
498
+ //#region src/uniswap/v3/router/quote.ts
499
+ const BPS_DENOMINATOR = 10000n;
500
+ const UINT128_MAX = (1n << 128n) - 1n;
501
+ /**
502
+ * Quote an exact-in v3 swap. Returns `null` when the quote reverts (e.g. the
503
+ * pool does not exist / has no liquidity) so callers can skip that pool when
504
+ * ranking venues, rather than aborting the whole cycle.
505
+ */
506
+ async function quoteV3ExactIn(params) {
507
+ const { client, chainId, tokenIn, tokenOut, fee, amountIn, slippageBps, blockNumber } = params;
508
+ if (amountIn < 0n || amountIn > UINT128_MAX) throw new PanopticError(`amountIn ${amountIn} exceeds uint128 maximum`);
509
+ assertSlippageBps(slippageBps);
510
+ const { quoterV2 } = getUniswapV3Addresses(chainId, params.addresses);
511
+ try {
512
+ const { result } = await client.simulateContract({
513
+ address: quoterV2,
514
+ abi: quoterV2Abi,
515
+ functionName: "quoteExactInputSingle",
516
+ blockNumber,
517
+ args: [{
518
+ tokenIn,
519
+ tokenOut,
520
+ amountIn,
521
+ fee: Number(fee),
522
+ sqrtPriceLimitX96: 0n
523
+ }]
524
+ });
525
+ const [amountOut, , , gasEstimate] = result;
526
+ const amountOutMinimum = amountOut * (BPS_DENOMINATOR - slippageBps) / BPS_DENOMINATOR;
527
+ return {
528
+ amountOut,
529
+ amountOutMinimum,
530
+ gasEstimate
531
+ };
532
+ } catch (err) {
533
+ if (isRevert(err)) return null;
534
+ throw err;
535
+ }
536
+ }
537
+ /**
538
+ * Quote an exact-out v3 swap. Returns `null` on revert (no pool / no liquidity).
539
+ */
540
+ async function quoteV3ExactOut(params) {
541
+ const { client, chainId, tokenIn, tokenOut, fee, amountOut, slippageBps, blockNumber } = params;
542
+ if (amountOut < 0n || amountOut > UINT128_MAX) throw new PanopticError(`amountOut ${amountOut} exceeds uint128 maximum`);
543
+ assertSlippageBps(slippageBps);
544
+ const { quoterV2 } = getUniswapV3Addresses(chainId, params.addresses);
545
+ try {
546
+ const { result } = await client.simulateContract({
547
+ address: quoterV2,
548
+ abi: quoterV2Abi,
549
+ functionName: "quoteExactOutputSingle",
550
+ blockNumber,
551
+ args: [{
552
+ tokenIn,
553
+ tokenOut,
554
+ amount: amountOut,
555
+ fee: Number(fee),
556
+ sqrtPriceLimitX96: 0n
557
+ }]
558
+ });
559
+ const [amountIn, , , gasEstimate] = result;
560
+ const amountInMaximum = (amountIn * (BPS_DENOMINATOR + slippageBps) + BPS_DENOMINATOR - 1n) / BPS_DENOMINATOR;
561
+ return {
562
+ amountIn,
563
+ amountInMaximum,
564
+ gasEstimate
565
+ };
566
+ } catch (err) {
567
+ if (isRevert(err)) return null;
568
+ throw err;
569
+ }
570
+ }
571
+ function assertSlippageBps(slippageBps) {
572
+ if (slippageBps < 0n || slippageBps > BPS_DENOMINATOR) throw new PanopticError(`invalid slippageBps ${slippageBps}, must be 0..10000`);
573
+ }
574
+ function isRevert(err) {
575
+ return err instanceof BaseError && err.walk((e) => e instanceof ContractFunctionRevertedError) instanceof ContractFunctionRevertedError;
576
+ }
577
+
578
+ //#endregion
579
+ //#region src/uniswap/v3/router/resolveRoute.ts
580
+ async function resolveV3SwapRoute(params) {
581
+ const { client, poolAddress, tokenIn } = params;
582
+ const meta = await getPoolMetadata({
583
+ client,
584
+ poolAddress
585
+ });
586
+ if (meta.isV4) throw new PanopticError("resolveV3SwapRoute called on a v4 pool");
587
+ const isToken0 = isAddressEqual(tokenIn, meta.token0Asset);
588
+ const isToken1 = isAddressEqual(tokenIn, meta.token1Asset);
589
+ if (!isToken0 && !isToken1) throw new InvalidSwapTokenError(tokenIn, meta.token0Asset, meta.token1Asset);
590
+ const zeroForOne = isToken0;
591
+ return {
592
+ tokenIn,
593
+ tokenOut: zeroForOne ? meta.token1Asset : meta.token0Asset,
594
+ fee: meta.fee,
595
+ zeroForOne
596
+ };
597
+ }
598
+
599
+ //#endregion
600
+ //#region src/uniswap/v3/router/quoteViaRouter.ts
601
+ const FALLBACK_META = {
602
+ blockNumber: 0n,
603
+ blockTimestamp: 0n,
604
+ blockHash: "0x0"
605
+ };
606
+ async function quoteSwapExactInViaV3Router(params) {
607
+ const { client, poolAddress, chainId, tokenIn, amountIn, slippageBps, blockNumber, addresses } = params;
608
+ try {
609
+ const targetBlockNumber = blockNumber ?? await client.getBlockNumber();
610
+ const metaPromise = getBlockMeta({
611
+ client,
612
+ blockNumber: targetBlockNumber
613
+ });
614
+ metaPromise.catch(() => void 0);
615
+ const route = await resolveV3SwapRoute({
616
+ client,
617
+ poolAddress,
618
+ tokenIn
619
+ });
620
+ const quote = await quoteV3ExactIn({
621
+ client,
622
+ chainId,
623
+ tokenIn: route.tokenIn,
624
+ tokenOut: route.tokenOut,
625
+ fee: route.fee,
626
+ amountIn,
627
+ slippageBps,
628
+ blockNumber: targetBlockNumber,
629
+ addresses
630
+ });
631
+ if (!quote) throw new PanopticError("v3 quote returned no result (pool may have no liquidity)");
632
+ const _meta = await metaPromise;
633
+ return {
634
+ success: true,
635
+ data: {
636
+ amountOut: quote.amountOut,
637
+ amountOutMinimum: quote.amountOutMinimum,
638
+ zeroForOne: route.zeroForOne,
639
+ tokenOut: route.tokenOut,
640
+ gasEstimate: quote.gasEstimate
641
+ },
642
+ gasEstimate: quote.gasEstimate,
643
+ _meta
644
+ };
645
+ } catch (error) {
646
+ return {
647
+ success: false,
648
+ error: error instanceof PanopticError ? error : new PanopticError(error instanceof Error ? error.message : "Quote failed", error instanceof Error ? error : void 0),
649
+ _meta: FALLBACK_META
650
+ };
651
+ }
652
+ }
653
+ async function quoteSwapExactOutViaV3Router(params) {
654
+ const { client, poolAddress, chainId, tokenIn, amountOut, slippageBps, blockNumber, addresses } = params;
655
+ try {
656
+ const targetBlockNumber = blockNumber ?? await client.getBlockNumber();
657
+ const metaPromise = getBlockMeta({
658
+ client,
659
+ blockNumber: targetBlockNumber
660
+ });
661
+ metaPromise.catch(() => void 0);
662
+ const route = await resolveV3SwapRoute({
663
+ client,
664
+ poolAddress,
665
+ tokenIn
666
+ });
667
+ const quote = await quoteV3ExactOut({
668
+ client,
669
+ chainId,
670
+ tokenIn: route.tokenIn,
671
+ tokenOut: route.tokenOut,
672
+ fee: route.fee,
673
+ amountOut,
674
+ slippageBps,
675
+ blockNumber: targetBlockNumber,
676
+ addresses
677
+ });
678
+ if (!quote) throw new PanopticError("v3 quote returned no result (pool may have no liquidity)");
679
+ const _meta = await metaPromise;
680
+ return {
681
+ success: true,
682
+ data: {
683
+ amountIn: quote.amountIn,
684
+ amountInMaximum: quote.amountInMaximum,
685
+ zeroForOne: route.zeroForOne,
686
+ tokenOut: route.tokenOut,
687
+ gasEstimate: quote.gasEstimate
688
+ },
689
+ gasEstimate: quote.gasEstimate,
690
+ _meta
691
+ };
692
+ } catch (error) {
693
+ return {
694
+ success: false,
695
+ error: error instanceof PanopticError ? error : new PanopticError(error instanceof Error ? error.message : "Quote failed", error instanceof Error ? error : void 0),
696
+ _meta: FALLBACK_META
697
+ };
698
+ }
699
+ }
700
+
701
+ //#endregion
702
+ //#region src/uniswap/v4/addresses.ts
703
+ /**
704
+ * Canonical Permit2, identical across all chains.
705
+ */
706
+ const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
707
+ /**
708
+ * Verified Uniswap v4 deployment addresses keyed by chainId.
709
+ *
710
+ * Sourced from the official Uniswap v4 deployments. Add a chain here only after
711
+ * verifying each address against the canonical Uniswap deployment listing —
712
+ * Universal Router in particular is NOT the same address across chains.
713
+ */
714
+ const UNISWAP_V4_ADDRESSES = {
715
+ 1: {
716
+ universalRouter: "0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af",
717
+ v4Quoter: "0x52F0E24D1c21C8A0cB1e5a5dD6198556BD9E1203",
718
+ poolManager: "0x000000000004444c5dc75cB358380D2e3dE08A90",
719
+ positionManager: "0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e",
720
+ permit2: PERMIT2_ADDRESS
721
+ },
722
+ 4663: {
723
+ universalRouter: "0x8876789976decbfcbbbe364623c63652db8c0904",
724
+ v4Quoter: "0x8dc178efb8111bb0973dd9d722ebeff267c98f94",
725
+ poolManager: "0x8366a39cc670b4001a1121b8f6a443a643e40951",
726
+ positionManager: "0x58daec3116aae6d93017baaea7749052e8a04fa7",
727
+ permit2: PERMIT2_ADDRESS
728
+ }
729
+ };
730
+ /**
731
+ * Resolve the Uniswap v4 addresses for a chain, applying optional overrides.
732
+ *
733
+ * @param chainId - Target chain ID.
734
+ * @param overrides - Partial override of any address (e.g. for fork tests).
735
+ * @returns Fully-resolved {@link UniswapV4Addresses}.
736
+ * @throws {UnsupportedChainError} when the chain is not listed and the
737
+ * overrides do not supply every required address.
738
+ */
739
+ function getUniswapV4Addresses(chainId, overrides) {
740
+ const base = UNISWAP_V4_ADDRESSES[Number(chainId)];
741
+ const merged = {
742
+ ...base,
743
+ ...overrides,
744
+ permit2: overrides?.permit2 ?? base?.permit2 ?? PERMIT2_ADDRESS
745
+ };
746
+ 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);
747
+ return merged;
748
+ }
749
+
750
+ //#endregion
751
+ //#region src/uniswap/v3/router/swap.ts
752
+ const DEFAULT_DEADLINE_SECONDS = 1800n;
753
+ async function swapExactInViaV3Router(params) {
754
+ const { client, walletClient, account, poolAddress, chainId, tokenIn, amountIn, slippageBps, deadline, recipient, txOverrides, addresses } = params;
755
+ if (recipient !== void 0 && !isAddressEqual(recipient, account)) throw new PanopticError("Custom recipient is not supported yet; output goes to the sender");
756
+ const resolved = getUniswapV4Addresses(chainId, addresses);
757
+ const route = await resolveV3SwapRoute({
758
+ client,
759
+ poolAddress,
760
+ tokenIn
761
+ });
762
+ const quote = await quoteV3ExactIn({
763
+ client,
764
+ chainId,
765
+ tokenIn: route.tokenIn,
766
+ tokenOut: route.tokenOut,
767
+ fee: route.fee,
768
+ amountIn,
769
+ slippageBps,
770
+ addresses
771
+ });
772
+ if (!quote) throw new PanopticError("v3 quote failed (pool may have no liquidity)");
773
+ const resolvedDeadline = deadline ?? (await getBlockMeta({ client })).blockTimestamp + DEFAULT_DEADLINE_SECONDS;
774
+ const { args, value } = buildV3SwapExecuteArgs({
775
+ tokenIn: route.tokenIn,
776
+ tokenOut: route.tokenOut,
777
+ fee: route.fee,
778
+ amountIn,
779
+ amountOutMinimum: quote.amountOutMinimum,
780
+ deadline: resolvedDeadline
781
+ });
782
+ return submitWrite({
783
+ client,
784
+ walletClient,
785
+ account,
786
+ address: resolved.universalRouter,
787
+ abi: universalRouterAbi,
788
+ functionName: "execute",
789
+ args,
790
+ value,
791
+ txOverrides
792
+ });
793
+ }
794
+ async function swapExactOutViaV3Router(params) {
795
+ const { client, walletClient, account, poolAddress, chainId, tokenIn, amountOut, slippageBps, deadline, recipient, txOverrides, addresses } = params;
796
+ if (recipient !== void 0 && !isAddressEqual(recipient, account)) throw new PanopticError("Custom recipient is not supported yet; output goes to the sender");
797
+ const resolved = getUniswapV4Addresses(chainId, addresses);
798
+ const route = await resolveV3SwapRoute({
799
+ client,
800
+ poolAddress,
801
+ tokenIn
802
+ });
803
+ const quote = await quoteV3ExactOut({
804
+ client,
805
+ chainId,
806
+ tokenIn: route.tokenIn,
807
+ tokenOut: route.tokenOut,
808
+ fee: route.fee,
809
+ amountOut,
810
+ slippageBps,
811
+ addresses
812
+ });
813
+ if (!quote) throw new PanopticError("v3 quote failed (pool may have no liquidity)");
814
+ const resolvedDeadline = deadline ?? (await getBlockMeta({ client })).blockTimestamp + DEFAULT_DEADLINE_SECONDS;
815
+ const { args, value } = buildV3ExactOutSwapExecuteArgs({
816
+ tokenIn: route.tokenIn,
817
+ tokenOut: route.tokenOut,
818
+ fee: route.fee,
819
+ amountOut,
820
+ amountInMaximum: quote.amountInMaximum,
821
+ deadline: resolvedDeadline
822
+ });
823
+ return submitWrite({
824
+ client,
825
+ walletClient,
826
+ account,
827
+ address: resolved.universalRouter,
828
+ abi: universalRouterAbi,
829
+ functionName: "execute",
830
+ args,
831
+ value,
832
+ txOverrides
833
+ });
834
+ }
835
+
836
+ //#endregion
837
+ export { AmountExceedsUint128Error, InvalidSwapTokenError, MSG_SENDER, MissingSweepRecipientError, PERMIT2_ADDRESS, QuoterUnavailableError, UNISWAP_V3_ADDRESSES, UNISWAP_V4_ADDRESSES, UnsupportedChainError, V3_SWAP_EXACT_IN, V3_SWAP_EXACT_OUT, buildV3ExactOutSwapExecuteArgs, buildV3ExactOutSwapExecuteCalldata, buildV3SwapExecuteArgs, buildV3SwapExecuteCalldata, encodeV3Path, getLpDepositAmounts, getLpDepositBreakdown, getLpFundingDeposit, getLpPositionFunding, getMaxLpPositionSize, getUniswapV3Addresses, getUniswapV4Addresses, quoteSwapExactInViaV3Router, quoteSwapExactOutViaV3Router, quoteV3ExactIn, quoteV3ExactOut, resolveV3SwapRoute, swapExactInViaV3Router, swapExactOutViaV3Router, universalRouterAbi };