@panoptic-eng/sdk 1.0.67 → 1.0.69

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 (42) hide show
  1. package/dist/{chainDeployments-CwnYHCg7.js → chainDeployments-BGvEeQSH.js} +24 -6
  2. package/dist/cow/index.d.ts.map +1 -1
  3. package/dist/cow/index.js +47 -2
  4. package/dist/cow/index.js.map +1 -1
  5. package/dist/{cow-CGgPXhY6.js → cow-BBPknr5W.js} +1 -1
  6. package/dist/deployments.d.ts +2 -0
  7. package/dist/deployments.d.ts.map +1 -1
  8. package/dist/deployments.js +24 -6
  9. package/dist/deployments.js.map +1 -1
  10. package/dist/{greeks-CGtgLUyh.js → greeks-DNPP24KL.js} +115 -9
  11. package/dist/index.d.ts +197 -46
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +2359 -2180
  14. package/dist/index.js.map +1 -1
  15. package/dist/{irm-BDxwCFDm.js → irm-DurYJ0ht.js} +3 -3
  16. package/dist/panoptic/v2/greeks/index.d.ts +7 -4
  17. package/dist/panoptic/v2/greeks/index.d.ts.map +1 -1
  18. package/dist/panoptic/v2/greeks/index.js +74 -7
  19. package/dist/panoptic/v2/greeks/index.js.map +1 -1
  20. package/dist/panoptic/v2/index.d.ts +89 -5
  21. package/dist/panoptic/v2/index.d.ts.map +1 -1
  22. package/dist/panoptic/v2/index.js +699 -55
  23. package/dist/panoptic/v2/index.js.map +1 -1
  24. package/dist/panoptic/v2/react-public.d.ts +80 -6
  25. package/dist/panoptic/v2/react-public.d.ts.map +1 -1
  26. package/dist/panoptic/v2/react-public.js +2717 -2463
  27. package/dist/panoptic/v2/react-public.js.map +1 -1
  28. package/dist/{rates-BHS-Qiu7.js → rates-B-eRYOTS.js} +39 -3
  29. package/dist/{router-BW6TqwOO.js → router-ChMhfTqT.js} +969 -39
  30. package/dist/rpc/index.d.ts +3 -0
  31. package/dist/rpc/index.d.ts.map +1 -1
  32. package/dist/rpc/index.js +31 -3
  33. package/dist/rpc/index.js.map +1 -1
  34. package/dist/{transactionFees-B4jz-v7d.js → transactionFees-BGfyq3Jo.js} +1 -1
  35. package/dist/uniswap/index.js +53 -13
  36. package/dist/uniswap/index.js.map +1 -1
  37. package/dist/{v2-Dgh6JxG_.js → v2-Bo1vZXvR.js} +150 -10
  38. package/dist/vault-transaction-fees.js +24 -6
  39. package/dist/vault-transaction-fees.js.map +1 -1
  40. package/dist/{writes-CG612Vuk.js → writes-D53wWDT-.js} +2 -2
  41. package/package.json +1 -1
  42. package/dist/router-IDtCENST.js +0 -942
@@ -1,8 +1,8 @@
1
1
  import { StateViewAbi } from "./StateView-q4yMpBY6.js";
2
- import { PanopticError, getBlockMeta, getPoolMetadata, stateViewAbi, submitWrite, uniswapV3PoolAbi } from "./rates-BHS-Qiu7.js";
3
- import { tickToSqrtPriceX96 } from "./greeks-CGtgLUyh.js";
4
- import { decodeAllLegs, decodeTokenId } from "./writes-CG612Vuk.js";
5
- import { BaseError, ContractFunctionRevertedError, ExecutionRevertedError, encodeAbiParameters, encodeFunctionData, encodePacked, isAddressEqual, toHex, zeroAddress } from "viem";
2
+ import { PanopticError, getBlockMeta, getPool, getPoolMetadata, stateViewAbi, submitWrite, uniswapV3PoolAbi } from "./rates-B-eRYOTS.js";
3
+ import { getLegLiquidityRange, tickToSqrtPriceX96 } from "./greeks-DNPP24KL.js";
4
+ import { decodeAllLegs, decodeTokenId } from "./writes-D53wWDT-.js";
5
+ import { BaseError, ContractFunctionRevertedError, ExecutionRevertedError, encodeAbiParameters, encodeFunctionData, encodePacked, erc20Abi, isAddressEqual, maxUint256, toHex, zeroAddress } from "viem";
6
6
  import Decimal from "decimal.js";
7
7
 
8
8
  //#region src/panoptic/v2/reads/priceHistory.ts
@@ -424,14 +424,7 @@ function preparePositionGamma({ tokenId, positionSize, quoteIsToken0, quoteDecim
424
424
  const decoded = decodeTokenId(tokenId);
425
425
  const chunks = decoded.legs.flatMap((leg) => {
426
426
  if (leg.width === 0n) return [];
427
- const width = leg.width * decoded.tickSpacing;
428
- const lowerTick = leg.strike - width / 2n;
429
- const upperTick = leg.strike + (width + 1n) / 2n;
430
- if (lowerTick < -887272n || upperTick > 887272n || lowerTick >= upperTick) throw new RangeError("Invalid position range");
431
- const lower = tickToSqrtPriceX96(lowerTick);
432
- const upper = tickToSqrtPriceX96(upperTick);
433
- const amount = positionSize * leg.optionRatio;
434
- const liquidity = leg.asset === 0n ? amount * (lower * upper / Q96) / (upper - lower) : amount * Q96 / (upper - lower);
427
+ const { lowerTick, upperTick, liquidity } = getLegLiquidityRange(leg, positionSize, decoded.tickSpacing);
435
428
  if (liquidity >= 1n << 128n) throw new RangeError("Liquidity exceeds uint128");
436
429
  return [{
437
430
  lowerTick: Number(lowerTick),
@@ -675,7 +668,7 @@ const V3_SWAP_EXACT_OUT = 1;
675
668
  * `msg.sender` (the `execute` caller). Uniswap `Constants.MSG_SENDER`.
676
669
  */
677
670
  const MSG_SENDER = "0x0000000000000000000000000000000000000001";
678
- const UINT128_MAX$1 = (1n << 128n) - 1n;
671
+ const UINT128_MAX$4 = (1n << 128n) - 1n;
679
672
  const v3ExactInInputAbi = [
680
673
  {
681
674
  name: "recipient",
@@ -698,8 +691,8 @@ const v3ExactInInputAbi = [
698
691
  type: "bool"
699
692
  }
700
693
  ];
701
- function assertUint128(amount) {
702
- if (amount < 0n || amount > UINT128_MAX$1) throw new AmountExceedsUint128Error(amount);
694
+ function assertUint128$1(amount) {
695
+ if (amount < 0n || amount > UINT128_MAX$4) throw new AmountExceedsUint128Error(amount);
703
696
  }
704
697
  /**
705
698
  * Encode the packed v3 single-hop path `tokenIn ++ fee(uint24) ++ tokenOut`.
@@ -723,8 +716,8 @@ function encodeV3Path(tokenIn, fee, tokenOut) {
723
716
  function buildV3SwapExecuteArgs(args) {
724
717
  const { tokenIn, tokenOut, fee, amountIn, amountOutMinimum, deadline } = args;
725
718
  if (tokenIn === zeroAddress || tokenOut === zeroAddress) throw new PanopticError("native ETH is not supported by the v3 exact-in router builder");
726
- assertUint128(amountIn);
727
- assertUint128(amountOutMinimum);
719
+ assertUint128$1(amountIn);
720
+ assertUint128$1(amountOutMinimum);
728
721
  const path = encodeV3Path(tokenIn, fee, tokenOut);
729
722
  const input = encodeAbiParameters(v3ExactInInputAbi, [
730
723
  MSG_SENDER,
@@ -788,8 +781,8 @@ const v3ExactOutInputAbi = [
788
781
  function buildV3ExactOutSwapExecuteArgs(args) {
789
782
  const { tokenIn, tokenOut, fee, amountOut, amountInMaximum, deadline } = args;
790
783
  if (tokenIn === zeroAddress || tokenOut === zeroAddress) throw new PanopticError("native ETH is not supported by the v3 exact-out router builder");
791
- assertUint128(amountOut);
792
- assertUint128(amountInMaximum);
784
+ assertUint128$1(amountOut);
785
+ assertUint128$1(amountInMaximum);
793
786
  const path = encodeV3Path(tokenOut, fee, tokenIn);
794
787
  const input = encodeAbiParameters(v3ExactOutInputAbi, [
795
788
  MSG_SENDER,
@@ -930,8 +923,8 @@ const quoterV2Abi = [{
930
923
 
931
924
  //#endregion
932
925
  //#region src/uniswap/v3/router/quote.ts
933
- const BPS_DENOMINATOR = 10000n;
934
- const UINT128_MAX = (1n << 128n) - 1n;
926
+ const BPS_DENOMINATOR$2 = 10000n;
927
+ const UINT128_MAX$3 = (1n << 128n) - 1n;
935
928
  /**
936
929
  * Quote an exact-in v3 swap. Returns `null` when the quote reverts (e.g. the
937
930
  * pool does not exist / has no liquidity) so callers can skip that pool when
@@ -939,8 +932,8 @@ const UINT128_MAX = (1n << 128n) - 1n;
939
932
  */
940
933
  async function quoteV3ExactIn(params) {
941
934
  const { client, chainId, tokenIn, tokenOut, fee, amountIn, slippageBps, blockNumber } = params;
942
- if (amountIn < 0n || amountIn > UINT128_MAX) throw new PanopticError(`amountIn ${amountIn} exceeds uint128 maximum`);
943
- assertSlippageBps(slippageBps);
935
+ if (amountIn < 0n || amountIn > UINT128_MAX$3) throw new PanopticError(`amountIn ${amountIn} exceeds uint128 maximum`);
936
+ assertSlippageBps$1(slippageBps);
944
937
  const { quoterV2 } = getUniswapV3Addresses(chainId, params.addresses);
945
938
  try {
946
939
  const { result } = await client.simulateContract({
@@ -957,14 +950,14 @@ async function quoteV3ExactIn(params) {
957
950
  }]
958
951
  });
959
952
  const [amountOut, , , gasEstimate] = result;
960
- const amountOutMinimum = amountOut * (BPS_DENOMINATOR - slippageBps) / BPS_DENOMINATOR;
953
+ const amountOutMinimum = amountOut * (BPS_DENOMINATOR$2 - slippageBps) / BPS_DENOMINATOR$2;
961
954
  return {
962
955
  amountOut,
963
956
  amountOutMinimum,
964
957
  gasEstimate
965
958
  };
966
959
  } catch (err) {
967
- if (isRevert(err)) return null;
960
+ if (isRevert$1(err)) return null;
968
961
  throw err;
969
962
  }
970
963
  }
@@ -973,8 +966,8 @@ async function quoteV3ExactIn(params) {
973
966
  */
974
967
  async function quoteV3ExactOut(params) {
975
968
  const { client, chainId, tokenIn, tokenOut, fee, amountOut, slippageBps, blockNumber } = params;
976
- if (amountOut < 0n || amountOut > UINT128_MAX) throw new PanopticError(`amountOut ${amountOut} exceeds uint128 maximum`);
977
- assertSlippageBps(slippageBps);
969
+ if (amountOut < 0n || amountOut > UINT128_MAX$3) throw new PanopticError(`amountOut ${amountOut} exceeds uint128 maximum`);
970
+ assertSlippageBps$1(slippageBps);
978
971
  const { quoterV2 } = getUniswapV3Addresses(chainId, params.addresses);
979
972
  try {
980
973
  const { result } = await client.simulateContract({
@@ -991,21 +984,21 @@ async function quoteV3ExactOut(params) {
991
984
  }]
992
985
  });
993
986
  const [amountIn, , , gasEstimate] = result;
994
- const amountInMaximum = (amountIn * (BPS_DENOMINATOR + slippageBps) + BPS_DENOMINATOR - 1n) / BPS_DENOMINATOR;
987
+ const amountInMaximum = (amountIn * (BPS_DENOMINATOR$2 + slippageBps) + BPS_DENOMINATOR$2 - 1n) / BPS_DENOMINATOR$2;
995
988
  return {
996
989
  amountIn,
997
990
  amountInMaximum,
998
991
  gasEstimate
999
992
  };
1000
993
  } catch (err) {
1001
- if (isRevert(err)) return null;
994
+ if (isRevert$1(err)) return null;
1002
995
  throw err;
1003
996
  }
1004
997
  }
1005
- function assertSlippageBps(slippageBps) {
1006
- if (slippageBps < 0n || slippageBps > BPS_DENOMINATOR) throw new PanopticError(`invalid slippageBps ${slippageBps}, must be 0..10000`);
998
+ function assertSlippageBps$1(slippageBps) {
999
+ if (slippageBps < 0n || slippageBps > BPS_DENOMINATOR$2) throw new PanopticError(`invalid slippageBps ${slippageBps}, must be 0..10000`);
1007
1000
  }
1008
- function isRevert(err) {
1001
+ function isRevert$1(err) {
1009
1002
  return err instanceof BaseError && err.walk((e) => e instanceof ContractFunctionRevertedError) instanceof ContractFunctionRevertedError;
1010
1003
  }
1011
1004
 
@@ -1032,7 +1025,7 @@ async function resolveV3SwapRoute(params) {
1032
1025
 
1033
1026
  //#endregion
1034
1027
  //#region src/uniswap/v3/router/quoteViaRouter.ts
1035
- const FALLBACK_META = {
1028
+ const FALLBACK_META$1 = {
1036
1029
  blockNumber: 0n,
1037
1030
  blockTimestamp: 0n,
1038
1031
  blockHash: "0x0"
@@ -1080,7 +1073,7 @@ async function quoteSwapExactInViaV3Router(params) {
1080
1073
  return {
1081
1074
  success: false,
1082
1075
  error: error instanceof PanopticError ? error : new PanopticError(error instanceof Error ? error.message : "Quote failed", error instanceof Error ? error : void 0),
1083
- _meta: FALLBACK_META
1076
+ _meta: FALLBACK_META$1
1084
1077
  };
1085
1078
  }
1086
1079
  }
@@ -1127,7 +1120,7 @@ async function quoteSwapExactOutViaV3Router(params) {
1127
1120
  return {
1128
1121
  success: false,
1129
1122
  error: error instanceof PanopticError ? error : new PanopticError(error instanceof Error ? error.message : "Quote failed", error instanceof Error ? error : void 0),
1130
- _meta: FALLBACK_META
1123
+ _meta: FALLBACK_META$1
1131
1124
  };
1132
1125
  }
1133
1126
  }
@@ -1183,7 +1176,7 @@ function getUniswapV4Addresses(chainId, overrides) {
1183
1176
 
1184
1177
  //#endregion
1185
1178
  //#region src/uniswap/v3/router/swap.ts
1186
- const DEFAULT_DEADLINE_SECONDS = 1800n;
1179
+ const DEFAULT_DEADLINE_SECONDS$1 = 1800n;
1187
1180
  async function swapExactInViaV3Router(params) {
1188
1181
  const { client, walletClient, account, poolAddress, chainId, tokenIn, amountIn, slippageBps, deadline, recipient, txOverrides, addresses } = params;
1189
1182
  if (recipient !== void 0 && !isAddressEqual(recipient, account)) throw new PanopticError("Custom recipient is not supported yet; output goes to the sender");
@@ -1204,7 +1197,7 @@ async function swapExactInViaV3Router(params) {
1204
1197
  addresses
1205
1198
  });
1206
1199
  if (!quote) throw new PanopticError("v3 quote failed (pool may have no liquidity)");
1207
- const resolvedDeadline = deadline ?? (await getBlockMeta({ client })).blockTimestamp + DEFAULT_DEADLINE_SECONDS;
1200
+ const resolvedDeadline = deadline ?? (await getBlockMeta({ client })).blockTimestamp + DEFAULT_DEADLINE_SECONDS$1;
1208
1201
  const { args, value } = buildV3SwapExecuteArgs({
1209
1202
  tokenIn: route.tokenIn,
1210
1203
  tokenOut: route.tokenOut,
@@ -1245,7 +1238,7 @@ async function swapExactOutViaV3Router(params) {
1245
1238
  addresses
1246
1239
  });
1247
1240
  if (!quote) throw new PanopticError("v3 quote failed (pool may have no liquidity)");
1248
- const resolvedDeadline = deadline ?? (await getBlockMeta({ client })).blockTimestamp + DEFAULT_DEADLINE_SECONDS;
1241
+ const resolvedDeadline = deadline ?? (await getBlockMeta({ client })).blockTimestamp + DEFAULT_DEADLINE_SECONDS$1;
1249
1242
  const { args, value } = buildV3ExactOutSwapExecuteArgs({
1250
1243
  tokenIn: route.tokenIn,
1251
1244
  tokenOut: route.tokenOut,
@@ -1268,4 +1261,941 @@ async function swapExactOutViaV3Router(params) {
1268
1261
  }
1269
1262
 
1270
1263
  //#endregion
1271
- 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, calculatePositionVolatilityMetrics, encodeV3Path, feesFromFeeGrowthDelta, getLpDepositAmounts, getLpDepositBreakdown, getLpFundingDeposit, getLpPositionFunding, getMaxLpPositionSize, getPriceHistory, getUnhedgedLpRanges, getUniswapV3Addresses, getUniswapV3LpPositionState, getUniswapV4Addresses, getUniswapV4LpPositionState, prepareLiquidityGamma, preparePositionGamma, quoteSwapExactInViaV3Router, quoteSwapExactOutViaV3Router, quoteV3ExactIn, quoteV3ExactOut, resolveV3SwapRoute, swapExactInViaV3Router, swapExactOutViaV3Router, universalRouterAbi, valuePositionAccrual };
1264
+ //#region src/uniswap/v4/router/encodeSwap.ts
1265
+ /** Universal Router command byte for a v4 swap. */
1266
+ const V4_SWAP = 16;
1267
+ /** v4 action: exact-in single-hop swap. */
1268
+ const SWAP_EXACT_IN_SINGLE = 6;
1269
+ /** v4 action: pay all of the input currency owed. */
1270
+ const SETTLE_ALL = 12;
1271
+ /** v4 action: take all of the output currency owed. */
1272
+ const TAKE_ALL = 15;
1273
+ /** v4 action: take an output currency to an explicit recipient. */
1274
+ const TAKE = 14;
1275
+ /** v4 action: exact-out single-hop swap. */
1276
+ const SWAP_EXACT_OUT_SINGLE = 8;
1277
+ /** Universal Router command: sweep the router's token balance to a recipient. */
1278
+ const SWEEP = 4;
1279
+ /** v4 sentinel: take the full positive currency delta (used with TAKE). */
1280
+ const OPEN_DELTA = 0n;
1281
+ const UINT128_MAX$2 = (1n << 128n) - 1n;
1282
+ const poolKeyComponents = [
1283
+ {
1284
+ name: "currency0",
1285
+ type: "address"
1286
+ },
1287
+ {
1288
+ name: "currency1",
1289
+ type: "address"
1290
+ },
1291
+ {
1292
+ name: "fee",
1293
+ type: "uint24"
1294
+ },
1295
+ {
1296
+ name: "tickSpacing",
1297
+ type: "int24"
1298
+ },
1299
+ {
1300
+ name: "hooks",
1301
+ type: "address"
1302
+ }
1303
+ ];
1304
+ const exactInputSingleParamsAbi = [{
1305
+ type: "tuple",
1306
+ components: [
1307
+ {
1308
+ name: "poolKey",
1309
+ type: "tuple",
1310
+ components: poolKeyComponents
1311
+ },
1312
+ {
1313
+ name: "zeroForOne",
1314
+ type: "bool"
1315
+ },
1316
+ {
1317
+ name: "amountIn",
1318
+ type: "uint128"
1319
+ },
1320
+ {
1321
+ name: "amountOutMinimum",
1322
+ type: "uint128"
1323
+ },
1324
+ {
1325
+ name: "hookData",
1326
+ type: "bytes"
1327
+ }
1328
+ ]
1329
+ }];
1330
+ const exactOutputSingleParamsAbi = [{
1331
+ type: "tuple",
1332
+ components: [
1333
+ {
1334
+ name: "poolKey",
1335
+ type: "tuple",
1336
+ components: poolKeyComponents
1337
+ },
1338
+ {
1339
+ name: "zeroForOne",
1340
+ type: "bool"
1341
+ },
1342
+ {
1343
+ name: "amountOut",
1344
+ type: "uint128"
1345
+ },
1346
+ {
1347
+ name: "amountInMaximum",
1348
+ type: "uint128"
1349
+ },
1350
+ {
1351
+ name: "hookData",
1352
+ type: "bytes"
1353
+ }
1354
+ ]
1355
+ }];
1356
+ const currencyAmountAbi = [{
1357
+ name: "currency",
1358
+ type: "address"
1359
+ }, {
1360
+ name: "amount",
1361
+ type: "uint256"
1362
+ }];
1363
+ const takeToRecipientAbi = [
1364
+ {
1365
+ name: "currency",
1366
+ type: "address"
1367
+ },
1368
+ {
1369
+ name: "recipient",
1370
+ type: "address"
1371
+ },
1372
+ {
1373
+ name: "amount",
1374
+ type: "uint256"
1375
+ }
1376
+ ];
1377
+ const sweepParamsAbi = [
1378
+ {
1379
+ name: "token",
1380
+ type: "address"
1381
+ },
1382
+ {
1383
+ name: "recipient",
1384
+ type: "address"
1385
+ },
1386
+ {
1387
+ name: "amountMin",
1388
+ type: "uint256"
1389
+ }
1390
+ ];
1391
+ function assertUint128(amount) {
1392
+ if (amount < 0n || amount > UINT128_MAX$2) throw new AmountExceedsUint128Error(amount);
1393
+ }
1394
+ /**
1395
+ * Encode the input for a Universal Router `SWEEP` command that forwards the
1396
+ * router's full native-ETH balance (above `amountMin = 0`) to `recipient`.
1397
+ */
1398
+ function encodeEthSweepInput(recipient) {
1399
+ return encodeAbiParameters(sweepParamsAbi, [
1400
+ zeroAddress,
1401
+ recipient,
1402
+ 0n
1403
+ ]);
1404
+ }
1405
+ /**
1406
+ * Build the output-take v4 action byte + param.
1407
+ *
1408
+ * Native-ETH output is taken with the explicit-recipient `TAKE` action so the
1409
+ * router forwards the ETH straight to `recipient` (the implicit-`msgSender`
1410
+ * `TAKE_ALL` leaves native ETH held by the router). ERC20 output uses
1411
+ * `TAKE_ALL`, which already credits the caller.
1412
+ *
1413
+ * @param nativeAmount - TAKE amount for native-ETH output: `OPEN_DELTA` (0) to
1414
+ * take the full credit (exact-in, protected by the swap's min) or the exact
1415
+ * output (exact-out).
1416
+ * @param erc20Amount - TAKE_ALL min/amount for ERC20 output: `amountOutMinimum`
1417
+ * (exact-in) or the exact output (exact-out).
1418
+ */
1419
+ function buildOutputTake(tokenOut, recipient, nativeAmount, erc20Amount) {
1420
+ if (tokenOut === zeroAddress) {
1421
+ if (recipient === void 0) throw new MissingSweepRecipientError();
1422
+ return {
1423
+ action: TAKE,
1424
+ param: encodeAbiParameters(takeToRecipientAbi, [
1425
+ zeroAddress,
1426
+ recipient,
1427
+ nativeAmount
1428
+ ])
1429
+ };
1430
+ }
1431
+ return {
1432
+ action: TAKE_ALL,
1433
+ param: encodeAbiParameters(currencyAmountAbi, [tokenOut, erc20Amount])
1434
+ };
1435
+ }
1436
+ /**
1437
+ * Build the typed `execute(...)` args + msg.value for an exact-in single-hop v4
1438
+ * swap. Use this when submitting via viem (`writeContract` / `submitWrite`).
1439
+ *
1440
+ * When `tokenOut` is native ETH the explicit-recipient `TAKE` action forwards
1441
+ * the bought ETH to `recipient` (no trailing SWEEP is needed for exact-in).
1442
+ *
1443
+ * @returns `args` ready to spread into `execute` and the ETH `value` to send
1444
+ * (= `amountIn` for native-ETH input, otherwise `0n`).
1445
+ */
1446
+ function buildV4SwapExecuteArgs(args) {
1447
+ const { poolKey, zeroForOne, amountIn, amountOutMinimum, tokenIn, tokenOut, deadline, recipient, hookData = "0x" } = args;
1448
+ assertUint128(amountIn);
1449
+ assertUint128(amountOutMinimum);
1450
+ const swapParam = encodeAbiParameters(exactInputSingleParamsAbi, [{
1451
+ poolKey: {
1452
+ currency0: poolKey.currency0,
1453
+ currency1: poolKey.currency1,
1454
+ fee: Number(poolKey.fee),
1455
+ tickSpacing: Number(poolKey.tickSpacing),
1456
+ hooks: poolKey.hooks
1457
+ },
1458
+ zeroForOne,
1459
+ amountIn,
1460
+ amountOutMinimum,
1461
+ hookData
1462
+ }]);
1463
+ const settleParam = encodeAbiParameters(currencyAmountAbi, [tokenIn, amountIn]);
1464
+ const take = buildOutputTake(tokenOut, recipient, OPEN_DELTA, amountOutMinimum);
1465
+ const actions = encodePacked([
1466
+ "uint8",
1467
+ "uint8",
1468
+ "uint8"
1469
+ ], [
1470
+ SWAP_EXACT_IN_SINGLE,
1471
+ SETTLE_ALL,
1472
+ take.action
1473
+ ]);
1474
+ const v4Input = encodeAbiParameters([{
1475
+ name: "actions",
1476
+ type: "bytes"
1477
+ }, {
1478
+ name: "params",
1479
+ type: "bytes[]"
1480
+ }], [actions, [
1481
+ swapParam,
1482
+ settleParam,
1483
+ take.param
1484
+ ]]);
1485
+ const commands = encodePacked(["uint8"], [V4_SWAP]);
1486
+ const value = tokenIn === zeroAddress ? amountIn : 0n;
1487
+ return {
1488
+ args: [
1489
+ commands,
1490
+ [v4Input],
1491
+ deadline
1492
+ ],
1493
+ value
1494
+ };
1495
+ }
1496
+ /**
1497
+ * Build the `execute(commands, inputs, deadline)` calldata + msg.value for an
1498
+ * exact-in single-hop v4 swap.
1499
+ *
1500
+ * @returns The encoded calldata and the ETH `value` to send.
1501
+ */
1502
+ function buildV4SwapExecuteCalldata(args) {
1503
+ const { args: executeArgs, value } = buildV4SwapExecuteArgs(args);
1504
+ const data = encodeFunctionData({
1505
+ abi: universalRouterAbi,
1506
+ functionName: "execute",
1507
+ args: executeArgs
1508
+ });
1509
+ return {
1510
+ data,
1511
+ value
1512
+ };
1513
+ }
1514
+ /**
1515
+ * Build the typed `execute(...)` args + msg.value for an exact-out single-hop v4
1516
+ * swap. Use this when submitting via viem (`writeContract` / `submitWrite`).
1517
+ *
1518
+ * For native-ETH input, `value` is `amountInMaximum` (an overpay) and a trailing
1519
+ * `SWEEP` action refunds the unused ETH to `recipient`. For ERC20 input, Permit2
1520
+ * pulls only the settled amount, so no SWEEP is appended and `value` is `0n`.
1521
+ *
1522
+ * @returns `args` ready to spread into `execute` and the ETH `value` to send.
1523
+ */
1524
+ function buildV4ExactOutSwapExecuteArgs(args) {
1525
+ const { poolKey, zeroForOne, amountOut, amountInMaximum, tokenIn, tokenOut, deadline, recipient, hookData = "0x" } = args;
1526
+ assertUint128(amountOut);
1527
+ assertUint128(amountInMaximum);
1528
+ const swapParam = encodeAbiParameters(exactOutputSingleParamsAbi, [{
1529
+ poolKey: {
1530
+ currency0: poolKey.currency0,
1531
+ currency1: poolKey.currency1,
1532
+ fee: Number(poolKey.fee),
1533
+ tickSpacing: Number(poolKey.tickSpacing),
1534
+ hooks: poolKey.hooks
1535
+ },
1536
+ zeroForOne,
1537
+ amountOut,
1538
+ amountInMaximum,
1539
+ hookData
1540
+ }]);
1541
+ const settleParam = encodeAbiParameters(currencyAmountAbi, [tokenIn, amountInMaximum]);
1542
+ const take = buildOutputTake(tokenOut, recipient, amountOut, amountOut);
1543
+ const actions = encodePacked([
1544
+ "uint8",
1545
+ "uint8",
1546
+ "uint8"
1547
+ ], [
1548
+ SWAP_EXACT_OUT_SINGLE,
1549
+ SETTLE_ALL,
1550
+ take.action
1551
+ ]);
1552
+ const v4Input = encodeAbiParameters([{
1553
+ name: "actions",
1554
+ type: "bytes"
1555
+ }, {
1556
+ name: "params",
1557
+ type: "bytes[]"
1558
+ }], [actions, [
1559
+ swapParam,
1560
+ settleParam,
1561
+ take.param
1562
+ ]]);
1563
+ const isNativeIn = tokenIn === zeroAddress;
1564
+ const commandList = [V4_SWAP];
1565
+ const inputs = [v4Input];
1566
+ if (isNativeIn) {
1567
+ commandList.push(SWEEP);
1568
+ inputs.push(encodeEthSweepInput(recipient));
1569
+ }
1570
+ const commands = encodePacked(commandList.map(() => "uint8"), commandList);
1571
+ const value = isNativeIn ? amountInMaximum : 0n;
1572
+ return {
1573
+ args: [
1574
+ commands,
1575
+ inputs,
1576
+ deadline
1577
+ ],
1578
+ value
1579
+ };
1580
+ }
1581
+ /**
1582
+ * Build the `execute(commands, inputs, deadline)` calldata + msg.value for an
1583
+ * exact-out single-hop v4 swap.
1584
+ *
1585
+ * @returns The encoded calldata and the ETH `value` to send.
1586
+ */
1587
+ function buildV4ExactOutSwapExecuteCalldata(args) {
1588
+ const { args: executeArgs, value } = buildV4ExactOutSwapExecuteArgs(args);
1589
+ const data = encodeFunctionData({
1590
+ abi: universalRouterAbi,
1591
+ functionName: "execute",
1592
+ args: executeArgs
1593
+ });
1594
+ return {
1595
+ data,
1596
+ value
1597
+ };
1598
+ }
1599
+
1600
+ //#endregion
1601
+ //#region src/uniswap/v4/abis/permit2.ts
1602
+ /**
1603
+ * Minimal Permit2 ABI (`IAllowanceTransfer` subset: `approve` + `allowance`).
1604
+ * @module uniswap/v4/abis/permit2
1605
+ */
1606
+ const permit2Abi = [{
1607
+ type: "function",
1608
+ name: "approve",
1609
+ stateMutability: "nonpayable",
1610
+ inputs: [
1611
+ {
1612
+ name: "token",
1613
+ type: "address"
1614
+ },
1615
+ {
1616
+ name: "spender",
1617
+ type: "address"
1618
+ },
1619
+ {
1620
+ name: "amount",
1621
+ type: "uint160"
1622
+ },
1623
+ {
1624
+ name: "expiration",
1625
+ type: "uint48"
1626
+ }
1627
+ ],
1628
+ outputs: []
1629
+ }, {
1630
+ type: "function",
1631
+ name: "allowance",
1632
+ stateMutability: "view",
1633
+ inputs: [
1634
+ {
1635
+ name: "user",
1636
+ type: "address"
1637
+ },
1638
+ {
1639
+ name: "token",
1640
+ type: "address"
1641
+ },
1642
+ {
1643
+ name: "spender",
1644
+ type: "address"
1645
+ }
1646
+ ],
1647
+ outputs: [
1648
+ {
1649
+ name: "amount",
1650
+ type: "uint160"
1651
+ },
1652
+ {
1653
+ name: "expiration",
1654
+ type: "uint48"
1655
+ },
1656
+ {
1657
+ name: "nonce",
1658
+ type: "uint48"
1659
+ }
1660
+ ]
1661
+ }];
1662
+
1663
+ //#endregion
1664
+ //#region src/uniswap/v4/router/permit2.ts
1665
+ /** uint160 max — the largest Permit2 allowance amount. */
1666
+ const UINT160_MAX = (1n << 160n) - 1n;
1667
+ /** Default Permit2 allowance expiration window (30 days). */
1668
+ const DEFAULT_EXPIRATION_SECONDS = 2592000n;
1669
+ /** uint48 max — the largest Permit2 expiration. */
1670
+ const UINT48_MAX = (1n << 48n) - 1n;
1671
+ /**
1672
+ * Check whether the ERC20 → Permit2 → Universal Router allowance chain is
1673
+ * sufficient for an exact-in swap of `amount`.
1674
+ */
1675
+ async function checkRouterApproval(params) {
1676
+ const { client, chainId, tokenIn, owner, amount, addresses } = params;
1677
+ const { permit2, universalRouter } = getUniswapV4Addresses(chainId, addresses);
1678
+ const [erc20Allowance, permit2Allowance, blockMeta] = await Promise.all([
1679
+ client.readContract({
1680
+ address: tokenIn,
1681
+ abi: erc20Abi,
1682
+ functionName: "allowance",
1683
+ args: [owner, permit2]
1684
+ }),
1685
+ client.readContract({
1686
+ address: permit2,
1687
+ abi: permit2Abi,
1688
+ functionName: "allowance",
1689
+ args: [
1690
+ owner,
1691
+ tokenIn,
1692
+ universalRouter
1693
+ ]
1694
+ }),
1695
+ getBlockMeta({ client })
1696
+ ]);
1697
+ const [permit2Amount, permit2Expiration] = permit2Allowance;
1698
+ const needsErc20Approval = erc20Allowance < amount;
1699
+ const needsPermit2Approval = permit2Amount < amount || BigInt(permit2Expiration) <= blockMeta.blockTimestamp;
1700
+ return {
1701
+ needsErc20Approval,
1702
+ needsPermit2Approval,
1703
+ erc20Allowance,
1704
+ permit2Amount,
1705
+ permit2Expiration: BigInt(permit2Expiration)
1706
+ };
1707
+ }
1708
+ /**
1709
+ * Step 1: approve the ERC20 token to Permit2 (defaults to unlimited).
1710
+ */
1711
+ async function approveErc20ForPermit2(params) {
1712
+ const { client, walletClient, account, chainId, tokenIn, amount = maxUint256, txOverrides, addresses } = params;
1713
+ const { permit2 } = getUniswapV4Addresses(chainId, addresses);
1714
+ return submitWrite({
1715
+ client,
1716
+ walletClient,
1717
+ account,
1718
+ address: tokenIn,
1719
+ abi: erc20Abi,
1720
+ functionName: "approve",
1721
+ args: [permit2, amount],
1722
+ txOverrides
1723
+ });
1724
+ }
1725
+ /**
1726
+ * Step 1 (and wait): approve the ERC20 token to Permit2.
1727
+ */
1728
+ async function approveErc20ForPermit2AndWait(params) {
1729
+ const result = await approveErc20ForPermit2(params);
1730
+ return result.wait();
1731
+ }
1732
+ /**
1733
+ * Step 2: approve the Universal Router as a Permit2 spender for the token.
1734
+ */
1735
+ async function approveRouterViaPermit2(params) {
1736
+ const { client, walletClient, account, chainId, tokenIn, amount = UINT160_MAX, expiration, txOverrides, addresses } = params;
1737
+ const { permit2, universalRouter } = getUniswapV4Addresses(chainId, addresses);
1738
+ const resolvedExpiration = expiration ?? (await getBlockMeta({ client })).blockTimestamp + DEFAULT_EXPIRATION_SECONDS;
1739
+ const cappedExpiration = resolvedExpiration > UINT48_MAX ? UINT48_MAX : resolvedExpiration;
1740
+ return submitWrite({
1741
+ client,
1742
+ walletClient,
1743
+ account,
1744
+ address: permit2,
1745
+ abi: permit2Abi,
1746
+ functionName: "approve",
1747
+ args: [
1748
+ tokenIn,
1749
+ universalRouter,
1750
+ amount,
1751
+ cappedExpiration
1752
+ ],
1753
+ txOverrides
1754
+ });
1755
+ }
1756
+ /**
1757
+ * Step 2 (and wait): approve the Universal Router via Permit2.
1758
+ */
1759
+ async function approveRouterViaPermit2AndWait(params) {
1760
+ const result = await approveRouterViaPermit2(params);
1761
+ return result.wait();
1762
+ }
1763
+
1764
+ //#endregion
1765
+ //#region src/uniswap/v4/abis/v4Quoter.ts
1766
+ /**
1767
+ * Minimal V4Quoter ABI (`quoteExactInputSingle`, `quoteExactOutputSingle`).
1768
+ *
1769
+ * Note: neither function is `view` — they mutate state internally and are
1770
+ * intended to be called via `eth_call` / viem `simulateContract`, never
1771
+ * `readContract`.
1772
+ *
1773
+ * @module uniswap/v4/abis/v4Quoter
1774
+ */
1775
+ const quoteExactSingleParams = {
1776
+ name: "params",
1777
+ type: "tuple",
1778
+ components: [
1779
+ {
1780
+ name: "poolKey",
1781
+ type: "tuple",
1782
+ components: [
1783
+ {
1784
+ name: "currency0",
1785
+ type: "address"
1786
+ },
1787
+ {
1788
+ name: "currency1",
1789
+ type: "address"
1790
+ },
1791
+ {
1792
+ name: "fee",
1793
+ type: "uint24"
1794
+ },
1795
+ {
1796
+ name: "tickSpacing",
1797
+ type: "int24"
1798
+ },
1799
+ {
1800
+ name: "hooks",
1801
+ type: "address"
1802
+ }
1803
+ ]
1804
+ },
1805
+ {
1806
+ name: "zeroForOne",
1807
+ type: "bool"
1808
+ },
1809
+ {
1810
+ name: "exactAmount",
1811
+ type: "uint128"
1812
+ },
1813
+ {
1814
+ name: "hookData",
1815
+ type: "bytes"
1816
+ }
1817
+ ]
1818
+ };
1819
+ const v4QuoterAbi = [{
1820
+ type: "function",
1821
+ name: "quoteExactInputSingle",
1822
+ stateMutability: "nonpayable",
1823
+ inputs: [quoteExactSingleParams],
1824
+ outputs: [{
1825
+ name: "amountOut",
1826
+ type: "uint256"
1827
+ }, {
1828
+ name: "gasEstimate",
1829
+ type: "uint256"
1830
+ }]
1831
+ }, {
1832
+ type: "function",
1833
+ name: "quoteExactOutputSingle",
1834
+ stateMutability: "nonpayable",
1835
+ inputs: [quoteExactSingleParams],
1836
+ outputs: [{
1837
+ name: "amountIn",
1838
+ type: "uint256"
1839
+ }, {
1840
+ name: "gasEstimate",
1841
+ type: "uint256"
1842
+ }]
1843
+ }];
1844
+
1845
+ //#endregion
1846
+ //#region src/uniswap/v4/router/resolvePoolKey.ts
1847
+ /**
1848
+ * Resolve the PoolKey, swap direction, and output token metadata for a swap.
1849
+ *
1850
+ * @throws {InvalidSwapTokenError} when `tokenIn` is not part of the pool.
1851
+ */
1852
+ async function resolveSwapRoute(params) {
1853
+ const { client, poolAddress, chainId, tokenIn, blockNumber } = params;
1854
+ const pool = await getPool({
1855
+ client,
1856
+ poolAddress,
1857
+ chainId,
1858
+ blockNumber
1859
+ });
1860
+ const { poolKey } = pool;
1861
+ const isCurrency0 = isAddressEqual(tokenIn, poolKey.currency0);
1862
+ const isCurrency1 = isAddressEqual(tokenIn, poolKey.currency1);
1863
+ if (!isCurrency0 && !isCurrency1) throw new InvalidSwapTokenError(tokenIn, poolKey.currency0, poolKey.currency1);
1864
+ const zeroForOne = isCurrency0;
1865
+ const tokenOut = zeroForOne ? poolKey.currency1 : poolKey.currency0;
1866
+ const outTracker = zeroForOne ? pool.collateralTracker1 : pool.collateralTracker0;
1867
+ return {
1868
+ poolKey,
1869
+ zeroForOne,
1870
+ tokenOut,
1871
+ tokenOutDecimals: outTracker.decimals,
1872
+ tokenOutSymbol: outTracker.symbol
1873
+ };
1874
+ }
1875
+
1876
+ //#endregion
1877
+ //#region src/uniswap/v4/router/quote.ts
1878
+ const BPS_DENOMINATOR$1 = 10000n;
1879
+ const UINT128_MAX$1 = (1n << 128n) - 1n;
1880
+ /** Reject out-of-range slippage so the min/max amount math can't underflow/overflow. */
1881
+ function assertSlippageBps(slippageBps) {
1882
+ if (slippageBps < 0n || slippageBps > BPS_DENOMINATOR$1) throw new PanopticError(`invalid slippageBps ${slippageBps}, must be 0..10000`);
1883
+ }
1884
+ const FALLBACK_META = {
1885
+ blockNumber: 0n,
1886
+ blockTimestamp: 0n,
1887
+ blockHash: "0x0"
1888
+ };
1889
+ /**
1890
+ * Quote an exact-in spot swap against the underlying Uniswap v4 pool.
1891
+ *
1892
+ * Uses the V4Quoter `quoteExactInputSingle` via `eth_call` (the quoter is
1893
+ * state-mutating / revert-based, so it must be simulated, not read). Returns a
1894
+ * `SimulationResult` so failures carry a structured error rather than throwing.
1895
+ */
1896
+ async function quoteSwapExactInViaRouter(params) {
1897
+ const { client, poolAddress, chainId, tokenIn, amountIn, slippageBps, blockNumber, addresses } = params;
1898
+ try {
1899
+ if (amountIn < 0n || amountIn > UINT128_MAX$1) throw new PanopticError(`amountIn ${amountIn} exceeds uint128 maximum`);
1900
+ assertSlippageBps(slippageBps);
1901
+ const targetBlockNumber = blockNumber ?? await client.getBlockNumber();
1902
+ const metaPromise = getBlockMeta({
1903
+ client,
1904
+ blockNumber: targetBlockNumber
1905
+ });
1906
+ const { v4Quoter } = getUniswapV4Addresses(chainId, addresses);
1907
+ if (v4Quoter === zeroAddress) throw new QuoterUnavailableError(chainId);
1908
+ const route = await resolveSwapRoute({
1909
+ client,
1910
+ poolAddress,
1911
+ chainId,
1912
+ tokenIn,
1913
+ blockNumber: targetBlockNumber
1914
+ });
1915
+ const { result } = await client.simulateContract({
1916
+ address: v4Quoter,
1917
+ abi: v4QuoterAbi,
1918
+ functionName: "quoteExactInputSingle",
1919
+ blockNumber: targetBlockNumber,
1920
+ args: [{
1921
+ poolKey: {
1922
+ currency0: route.poolKey.currency0,
1923
+ currency1: route.poolKey.currency1,
1924
+ fee: Number(route.poolKey.fee),
1925
+ tickSpacing: Number(route.poolKey.tickSpacing),
1926
+ hooks: route.poolKey.hooks
1927
+ },
1928
+ zeroForOne: route.zeroForOne,
1929
+ exactAmount: amountIn,
1930
+ hookData: "0x"
1931
+ }]
1932
+ });
1933
+ const [amountOut, gasEstimate] = result;
1934
+ const amountOutMinimum = amountOut * (BPS_DENOMINATOR$1 - slippageBps) / BPS_DENOMINATOR$1;
1935
+ const _meta = await metaPromise;
1936
+ return {
1937
+ success: true,
1938
+ data: {
1939
+ amountOut,
1940
+ amountOutMinimum,
1941
+ zeroForOne: route.zeroForOne,
1942
+ tokenOut: route.tokenOut,
1943
+ poolKey: route.poolKey,
1944
+ gasEstimate
1945
+ },
1946
+ gasEstimate,
1947
+ _meta
1948
+ };
1949
+ } catch (error) {
1950
+ return {
1951
+ success: false,
1952
+ error: error instanceof PanopticError ? error : new PanopticError(error instanceof Error ? error.message : "Quote failed", error instanceof Error ? error : void 0),
1953
+ _meta: FALLBACK_META
1954
+ };
1955
+ }
1956
+ }
1957
+ /**
1958
+ * Quote an exact-out spot swap against the underlying Uniswap v4 pool.
1959
+ *
1960
+ * Uses the V4Quoter `quoteExactOutputSingle` via `eth_call` (the quoter is
1961
+ * state-mutating / revert-based, so it must be simulated, not read). Returns a
1962
+ * `SimulationResult` so failures carry a structured error rather than throwing.
1963
+ */
1964
+ async function quoteSwapExactOutViaRouter(params) {
1965
+ const { client, poolAddress, chainId, tokenIn, amountOut, slippageBps, blockNumber, addresses } = params;
1966
+ try {
1967
+ if (amountOut < 0n || amountOut > UINT128_MAX$1) throw new PanopticError(`amountOut ${amountOut} exceeds uint128 maximum`);
1968
+ assertSlippageBps(slippageBps);
1969
+ const targetBlockNumber = blockNumber ?? await client.getBlockNumber();
1970
+ const metaPromise = getBlockMeta({
1971
+ client,
1972
+ blockNumber: targetBlockNumber
1973
+ });
1974
+ const { v4Quoter } = getUniswapV4Addresses(chainId, addresses);
1975
+ if (v4Quoter === zeroAddress) throw new QuoterUnavailableError(chainId);
1976
+ const route = await resolveSwapRoute({
1977
+ client,
1978
+ poolAddress,
1979
+ chainId,
1980
+ tokenIn,
1981
+ blockNumber: targetBlockNumber
1982
+ });
1983
+ const { result } = await client.simulateContract({
1984
+ address: v4Quoter,
1985
+ abi: v4QuoterAbi,
1986
+ functionName: "quoteExactOutputSingle",
1987
+ blockNumber: targetBlockNumber,
1988
+ args: [{
1989
+ poolKey: {
1990
+ currency0: route.poolKey.currency0,
1991
+ currency1: route.poolKey.currency1,
1992
+ fee: Number(route.poolKey.fee),
1993
+ tickSpacing: Number(route.poolKey.tickSpacing),
1994
+ hooks: route.poolKey.hooks
1995
+ },
1996
+ zeroForOne: route.zeroForOne,
1997
+ exactAmount: amountOut,
1998
+ hookData: "0x"
1999
+ }]
2000
+ });
2001
+ const [amountIn, gasEstimate] = result;
2002
+ const amountInMaximum = (amountIn * (BPS_DENOMINATOR$1 + slippageBps) + BPS_DENOMINATOR$1 - 1n) / BPS_DENOMINATOR$1;
2003
+ const _meta = await metaPromise;
2004
+ return {
2005
+ success: true,
2006
+ data: {
2007
+ amountIn,
2008
+ amountInMaximum,
2009
+ zeroForOne: route.zeroForOne,
2010
+ tokenOut: route.tokenOut,
2011
+ poolKey: route.poolKey,
2012
+ gasEstimate
2013
+ },
2014
+ gasEstimate,
2015
+ _meta
2016
+ };
2017
+ } catch (error) {
2018
+ return {
2019
+ success: false,
2020
+ error: error instanceof PanopticError ? error : new PanopticError(error instanceof Error ? error.message : "Quote failed", error instanceof Error ? error : void 0),
2021
+ _meta: FALLBACK_META
2022
+ };
2023
+ }
2024
+ }
2025
+
2026
+ //#endregion
2027
+ //#region src/uniswap/v4/router/quoteByPoolKey.ts
2028
+ const BPS_DENOMINATOR = 10000n;
2029
+ const UINT128_MAX = (1n << 128n) - 1n;
2030
+ /**
2031
+ * Quote an exact-in v4 swap for a specific PoolKey. Returns `null` when the
2032
+ * quote reverts (pool missing / no liquidity) so callers can skip that pool when
2033
+ * ranking venues rather than aborting the cycle.
2034
+ */
2035
+ async function quoteV4ExactInByPoolKey(params) {
2036
+ const { client, chainId, poolKey, zeroForOne, amountIn, slippageBps, blockNumber } = params;
2037
+ if (amountIn < 0n || amountIn > UINT128_MAX) throw new PanopticError(`amountIn ${amountIn} exceeds uint128 maximum`);
2038
+ if (slippageBps < 0n || slippageBps > BPS_DENOMINATOR) throw new PanopticError(`invalid slippageBps ${slippageBps}, must be 0..10000`);
2039
+ const { v4Quoter } = getUniswapV4Addresses(chainId, params.addresses);
2040
+ try {
2041
+ const { result } = await client.simulateContract({
2042
+ address: v4Quoter,
2043
+ abi: v4QuoterAbi,
2044
+ functionName: "quoteExactInputSingle",
2045
+ blockNumber,
2046
+ args: [{
2047
+ poolKey: {
2048
+ currency0: poolKey.currency0,
2049
+ currency1: poolKey.currency1,
2050
+ fee: Number(poolKey.fee),
2051
+ tickSpacing: Number(poolKey.tickSpacing),
2052
+ hooks: poolKey.hooks
2053
+ },
2054
+ zeroForOne,
2055
+ exactAmount: amountIn,
2056
+ hookData: "0x"
2057
+ }]
2058
+ });
2059
+ const [amountOut, gasEstimate] = result;
2060
+ const amountOutMinimum = amountOut * (BPS_DENOMINATOR - slippageBps) / BPS_DENOMINATOR;
2061
+ return {
2062
+ amountOut,
2063
+ amountOutMinimum,
2064
+ gasEstimate
2065
+ };
2066
+ } catch (err) {
2067
+ if (isRevert(err)) return null;
2068
+ throw err;
2069
+ }
2070
+ }
2071
+ /** True only for genuine contract reverts (missing pool / no liquidity). */
2072
+ function isRevert(err) {
2073
+ return err instanceof BaseError && err.walk((e) => e instanceof ContractFunctionRevertedError) instanceof ContractFunctionRevertedError;
2074
+ }
2075
+
2076
+ //#endregion
2077
+ //#region src/uniswap/v4/router/swap.ts
2078
+ /** Default swap deadline window (30 minutes) when no deadline is provided. */
2079
+ const DEFAULT_DEADLINE_SECONDS = 1800n;
2080
+ /**
2081
+ * Execute an exact-in spot swap via the Universal Router.
2082
+ *
2083
+ * @param params - Swap parameters.
2084
+ * @returns TxResult with hash + wait().
2085
+ *
2086
+ * @example
2087
+ * ```typescript
2088
+ * const result = await swapExactInViaRouter({
2089
+ * client, walletClient, account, poolAddress,
2090
+ * chainId: 1n,
2091
+ * tokenIn: ZERO_ADDRESS, // native ETH
2092
+ * amountIn: 10n ** 17n, // 0.1 ETH
2093
+ * slippageBps: 50n, // 0.5%
2094
+ * })
2095
+ * await result.wait()
2096
+ * ```
2097
+ */
2098
+ async function swapExactInViaRouter(params) {
2099
+ const { client, walletClient, account, poolAddress, chainId, tokenIn, amountIn, slippageBps, deadline, recipient, txOverrides, addresses } = params;
2100
+ if (recipient !== void 0 && !isAddressEqual(recipient, account)) throw new PanopticError("Custom recipient is not supported yet; output goes to the sender");
2101
+ const resolved = getUniswapV4Addresses(chainId, addresses);
2102
+ const quote = await quoteSwapExactInViaRouter({
2103
+ client,
2104
+ poolAddress,
2105
+ chainId,
2106
+ tokenIn,
2107
+ amountIn,
2108
+ slippageBps,
2109
+ addresses
2110
+ });
2111
+ if (!quote.success) throw quote.error;
2112
+ const resolvedDeadline = deadline ?? (await getBlockMeta({ client })).blockTimestamp + DEFAULT_DEADLINE_SECONDS;
2113
+ if (!quote.data.poolKey) throw new PanopticError("v4 swap requires a poolKey from the quote");
2114
+ const { args, value } = buildV4SwapExecuteArgs({
2115
+ poolKey: quote.data.poolKey,
2116
+ zeroForOne: quote.data.zeroForOne,
2117
+ amountIn,
2118
+ amountOutMinimum: quote.data.amountOutMinimum,
2119
+ tokenIn,
2120
+ tokenOut: quote.data.tokenOut,
2121
+ deadline: resolvedDeadline,
2122
+ recipient: account
2123
+ });
2124
+ return submitWrite({
2125
+ client,
2126
+ walletClient,
2127
+ account,
2128
+ address: resolved.universalRouter,
2129
+ abi: universalRouterAbi,
2130
+ functionName: "execute",
2131
+ args,
2132
+ value,
2133
+ txOverrides
2134
+ });
2135
+ }
2136
+ /**
2137
+ * Execute an exact-in swap via the Universal Router and wait for confirmation.
2138
+ */
2139
+ async function swapExactInViaRouterAndWait(params) {
2140
+ const result = await swapExactInViaRouter(params);
2141
+ return result.wait();
2142
+ }
2143
+ /**
2144
+ * Execute an exact-out spot swap via the Universal Router.
2145
+ *
2146
+ * The caller specifies the exact `amountOut` to receive; the input (pay) amount
2147
+ * is quoted and capped at `amountInMaximum`. For native-ETH input the router is
2148
+ * funded with `amountInMaximum` and the unused surplus is swept back to the
2149
+ * sender.
2150
+ *
2151
+ * @param params - Swap parameters.
2152
+ * @returns TxResult with hash + wait().
2153
+ */
2154
+ async function swapExactOutViaRouter(params) {
2155
+ const { client, walletClient, account, poolAddress, chainId, tokenIn, amountOut, slippageBps, deadline, recipient, txOverrides, addresses } = params;
2156
+ if (recipient !== void 0 && !isAddressEqual(recipient, account)) throw new PanopticError("Custom recipient is not supported yet; output goes to the sender");
2157
+ const resolved = getUniswapV4Addresses(chainId, addresses);
2158
+ const quote = await quoteSwapExactOutViaRouter({
2159
+ client,
2160
+ poolAddress,
2161
+ chainId,
2162
+ tokenIn,
2163
+ amountOut,
2164
+ slippageBps,
2165
+ addresses
2166
+ });
2167
+ if (!quote.success) throw quote.error;
2168
+ const resolvedDeadline = deadline ?? (await getBlockMeta({ client })).blockTimestamp + DEFAULT_DEADLINE_SECONDS;
2169
+ if (!quote.data.poolKey) throw new PanopticError("v4 swap requires a poolKey from the quote");
2170
+ const { args, value } = buildV4ExactOutSwapExecuteArgs({
2171
+ poolKey: quote.data.poolKey,
2172
+ zeroForOne: quote.data.zeroForOne,
2173
+ amountOut,
2174
+ amountInMaximum: quote.data.amountInMaximum,
2175
+ tokenIn,
2176
+ tokenOut: quote.data.tokenOut,
2177
+ deadline: resolvedDeadline,
2178
+ recipient: account
2179
+ });
2180
+ return submitWrite({
2181
+ client,
2182
+ walletClient,
2183
+ account,
2184
+ address: resolved.universalRouter,
2185
+ abi: universalRouterAbi,
2186
+ functionName: "execute",
2187
+ args,
2188
+ value,
2189
+ txOverrides
2190
+ });
2191
+ }
2192
+ /**
2193
+ * Execute an exact-out swap via the Universal Router and wait for confirmation.
2194
+ */
2195
+ async function swapExactOutViaRouterAndWait(params) {
2196
+ const result = await swapExactOutViaRouter(params);
2197
+ return result.wait();
2198
+ }
2199
+
2200
+ //#endregion
2201
+ 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, approveErc20ForPermit2, approveErc20ForPermit2AndWait, approveRouterViaPermit2, approveRouterViaPermit2AndWait, buildV3ExactOutSwapExecuteArgs, buildV3ExactOutSwapExecuteCalldata, buildV3SwapExecuteArgs, buildV3SwapExecuteCalldata, buildV4ExactOutSwapExecuteArgs, buildV4ExactOutSwapExecuteCalldata, buildV4SwapExecuteArgs, buildV4SwapExecuteCalldata, calculatePositionVolatilityMetrics, checkRouterApproval, encodeV3Path, feesFromFeeGrowthDelta, getLpDepositAmounts, getLpDepositBreakdown, getLpFundingDeposit, getLpPositionFunding, getMaxLpPositionSize, getPriceHistory, getUnhedgedLpRanges, getUniswapV3Addresses, getUniswapV3LpPositionState, getUniswapV4Addresses, getUniswapV4LpPositionState, prepareLiquidityGamma, preparePositionGamma, quoteSwapExactInViaRouter, quoteSwapExactInViaV3Router, quoteSwapExactOutViaRouter, quoteSwapExactOutViaV3Router, quoteV3ExactIn, quoteV3ExactOut, quoteV4ExactInByPoolKey, resolveSwapRoute, resolveV3SwapRoute, swapExactInViaRouter, swapExactInViaRouterAndWait, swapExactInViaV3Router, swapExactOutViaRouter, swapExactOutViaRouterAndWait, swapExactOutViaV3Router, valuePositionAccrual };