@panoptic-eng/sdk 1.0.47 → 1.0.48

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.
@@ -1,5 +1,5 @@
1
- import { BatchValidationError, DEFAULT_VEGOID, InputListFailError, InvalidTickLimitsError, InvalidTokenIdParameterError, LEG_BITS, LEG_LIMITS, LEG_MASKS, LoanSlotExhaustedError, MaxRetriesExceededError, MissingPositionIdsError, NoLoanPositionsError, OracleRateLimitedError, PanopticError, PositionSnapshotNotFoundError, ProviderLagError, STRIKE_CONVERSION_FACTOR, SwapTokenMismatchError, SyncTimeoutError, TOKEN_ID_BITS, collateralTrackerV2Abi, createTxResult, decodeLeftRightUnsigned, decodeOracleTiming, getBlockMeta, getOpenPositionIds, getPool, getPoolMetaKey, getPoolMetadata, getPositionMetaKey, getPositions, getPositionsKey, getSyncCheckpointKey, getTrackedPositionIds, jsonSerializer, oracleEpochAt, panopticErrorsAbi, panopticFactoryV3Abi, panopticFactoryV4Abi, panopticPoolV2Abi, parsePanopticError, submitWrite } from "./getTrackedPositionIds-CwBHzOO2.js";
2
- import { REORG_DEPTH, calculatePositionGreeks, tickLimits } from "./greeks-9aTE-DwO.js";
1
+ import { BatchValidationError, DEFAULT_VEGOID, InputListFailError, InvalidTickLimitsError, InvalidTokenIdParameterError, LEG_BITS, LEG_LIMITS, LEG_MASKS, LoanSlotExhaustedError, MaxRetriesExceededError, MissingPositionIdsError, NoLoanPositionsError, OracleRateLimitedError, PanopticError, PanopticValidationError, PositionSnapshotNotFoundError, ProviderLagError, STANDARD_TICK_WIDTHS, STRIKE_CONVERSION_FACTOR, SwapTokenMismatchError, SyncTimeoutError, TOKEN_ID_BITS, UnsafePremiumSettlementError, collateralTrackerV2Abi, createTxResult, decodeLeftRightUnsigned, decodeOracleTiming, decodePosition, decodeTickSpacing, formatTokenAmount, getBlockMeta, getOpenPositionIds, getPool, getPoolMetaKey, getPoolMetadata, getPositionMetaKey, getPositions, getPositionsKey, getSyncCheckpointKey, getTrackedPositionIds, jsonSerializer, oracleEpochAt, panopticErrorsAbi, panopticFactoryV3Abi, panopticFactoryV4Abi, panopticPoolV2Abi, parsePanopticError, parseTokenAmount, submitWrite } from "./rates-5oQD-SuM.js";
2
+ import { MAX_TICK, MIN_TICK, REORG_DEPTH, calculatePositionGreeks, priceToTick, roundToTickSpacing, sqrtPriceX96ToTick, tickLimits, tickToPrice, tickToPriceDecimalScaled, tickToSqrtPriceX96 } from "./greeks-9aTE-DwO.js";
3
3
  import { decodeFunctionResult, encodeFunctionData, erc20Abi, getAbiItem } from "viem";
4
4
 
5
5
  //#region src/panoptic/v2/writes/broadcaster.ts
@@ -1572,350 +1572,525 @@ async function executeSettleSequenceAndWait(params) {
1572
1572
  }
1573
1573
 
1574
1574
  //#endregion
1575
- //#region src/panoptic/v2/reads/positionSizes.ts
1576
- const BIT_MASK_128 = (1n << 128n) - 1n;
1575
+ //#region src/panoptic/v2/formatters/percentage.ts
1577
1576
  /**
1578
- * Returns the current stored positionSize for each tokenId, in the same order
1579
- * as the input `positionIdList`. Reverts (via the contract) if any tokenId is
1580
- * not held by `account`.
1577
+ * Percentage and ratio formatters.
1578
+ *
1579
+ * All formatters require explicit precision - no hidden defaults.
1580
+ *
1581
+ * @module v2/formatters/percentage
1581
1582
  */
1582
- async function getCurrentPositionSizes(params) {
1583
- const { client, poolAddress, account, positionIdList, blockNumber } = params;
1584
- if (positionIdList.length === 0) return [];
1585
- const [, , positionBalances] = await client.readContract({
1586
- address: poolAddress,
1587
- abi: panopticPoolV2Abi,
1588
- functionName: "getFullPositionsData",
1589
- args: [
1590
- account,
1591
- false,
1592
- positionIdList
1593
- ],
1594
- blockNumber
1595
- });
1596
- return positionBalances.map((packed) => packed & BIT_MASK_128);
1583
+ const TEN$2 = 10n;
1584
+ function formatRatio$2(numerator, denominator, precision) {
1585
+ if (precision < 0n) throw new RangeError("Precision must be non-negative");
1586
+ if (denominator === 0n) return "0";
1587
+ const isNegative = numerator < 0n !== denominator < 0n;
1588
+ const absNumerator = numerator < 0n ? -numerator : numerator;
1589
+ const absDenominator = denominator < 0n ? -denominator : denominator;
1590
+ const scale = TEN$2 ** precision;
1591
+ const scaled = (absNumerator * scale + absDenominator / 2n) / absDenominator;
1592
+ const integerPart = scaled / scale;
1593
+ const fractionalPart = scaled % scale;
1594
+ const sign = isNegative ? "-" : "";
1595
+ if (precision === 0n) return `${sign}${integerPart}`;
1596
+ return `${sign}${integerPart}.${fractionalPart.toString().padStart(Number(precision), "0")}`;
1597
1597
  }
1598
-
1599
- //#endregion
1600
- //#region src/panoptic/v2/writes/settle.ts
1601
1598
  /**
1602
- * Settle accumulated premia on existing positions.
1599
+ * Format basis points as a percentage string.
1600
+ * 100 bps = 1%
1603
1601
  *
1604
- * This function triggers premium collection without changing position size.
1605
- * It calls dispatch with unchanged position lists.
1602
+ * @param bps - Basis points value
1603
+ * @param precision - Number of decimal places to display
1604
+ * @returns Formatted percentage string
1606
1605
  *
1607
- * @param params - Settlement parameters
1608
- * @returns TxResult
1606
+ * @example
1607
+ * ```typescript
1608
+ * formatBps(50n, 2n) // "0.50%"
1609
+ * formatBps(50n, 1n) // "0.5%"
1610
+ * formatBps(100n, 2n) // "1.00%"
1611
+ * formatBps(1500n, 2n) // "15.00%"
1612
+ * formatBps(-50n, 2n) // "-0.50%"
1613
+ * ```
1614
+ */
1615
+ function formatBps(bps, precision) {
1616
+ const isNegative = bps < 0n;
1617
+ const absBps = isNegative ? -bps : bps;
1618
+ const scaleFactor = 10n ** precision;
1619
+ const scaled = absBps * scaleFactor / 100n;
1620
+ const integerPart = scaled / scaleFactor;
1621
+ const fractionalPart = scaled % scaleFactor;
1622
+ const fractionalStr = fractionalPart.toString().padStart(Number(precision), "0");
1623
+ const sign = isNegative ? "-" : "";
1624
+ return precision > 0n ? `${sign}${integerPart}.${fractionalStr}%` : `${sign}${integerPart}%`;
1625
+ }
1626
+ /**
1627
+ * Format utilization as a percentage string.
1628
+ * Utilization is stored as 0n-10000n, where 10000n = 100%.
1629
+ *
1630
+ * @param util - Utilization value (0-10000)
1631
+ * @param precision - Number of decimal places to display
1632
+ * @returns Formatted percentage string
1609
1633
  *
1610
1634
  * @example
1611
1635
  * ```typescript
1612
- * const result = await settleAccumulatedPremia({
1613
- * client,
1614
- * walletClient,
1615
- * account,
1616
- * poolAddress,
1617
- * positionIdList: existingPositions,
1618
- * })
1619
- * const receipt = await result.wait()
1636
+ * formatUtilization(7500n, 2n) // "75.00%"
1637
+ * formatUtilization(7500n, 0n) // "75%"
1638
+ * formatUtilization(10000n, 2n) // "100.00%"
1639
+ * formatUtilization(123n, 2n) // "1.23%"
1620
1640
  * ```
1621
1641
  */
1622
- async function settleAccumulatedPremia(params) {
1623
- const { client, walletClient, account, poolAddress, positionIdList, finalPositionIdList, positionSizes: providedSizes, usePremiaAsCollateral = false, builderCode = 0n, txOverrides } = params;
1624
- if (providedSizes && providedSizes.length !== positionIdList.length) throw new PanopticError("settleAccumulatedPremia: positionSizes length must match positionIdList");
1625
- const positionSizes = providedSizes ?? await getCurrentPositionSizes({
1626
- client,
1627
- poolAddress,
1628
- account,
1629
- positionIdList
1630
- });
1631
- const tickAndSpreadLimits = positionIdList.map(() => [
1632
- -887272n,
1633
- 887272n,
1634
- 0n
1635
- ]);
1636
- return submitWrite({
1637
- client,
1638
- walletClient,
1639
- account,
1640
- address: poolAddress,
1641
- abi: panopticPoolV2Abi,
1642
- functionName: "dispatch",
1643
- args: [
1644
- positionIdList,
1645
- finalPositionIdList ?? positionIdList,
1646
- positionSizes.map((s) => BigInt(s)),
1647
- tickAndSpreadLimits.map((t) => [
1648
- Number(t[0]),
1649
- Number(t[1]),
1650
- Number(t[2])
1651
- ]),
1652
- usePremiaAsCollateral,
1653
- builderCode
1654
- ],
1655
- txOverrides
1656
- });
1642
+ function formatUtilization(util, precision) {
1643
+ return formatBps(util, precision);
1657
1644
  }
1658
1645
  /**
1659
- * Settle premia and wait for confirmation.
1646
+ * Parse a percentage string to basis points.
1647
+ *
1648
+ * @param percent - Percentage string (e.g., "1.5%" or "1.5")
1649
+ * @returns Basis points value
1650
+ *
1651
+ * @example
1652
+ * ```typescript
1653
+ * parseBps("1.5%") // 150n
1654
+ * parseBps("1.5") // 150n
1655
+ * parseBps("100%") // 10000n
1656
+ * parseBps("0.5%") // 50n
1657
+ * ```
1660
1658
  */
1661
- async function settleAccumulatedPremiaAndWait(params) {
1662
- const result = await settleAccumulatedPremia(params);
1663
- return result.wait();
1659
+ function parseBps(percent) {
1660
+ const cleaned = percent.trim().replace(/%$/, "");
1661
+ const isNegative = cleaned.startsWith("-");
1662
+ const absValue = isNegative ? cleaned.slice(1) : cleaned;
1663
+ const [integerStr, fractionalStr = ""] = absValue.split(".");
1664
+ const paddedFractional = fractionalStr.padEnd(2, "0").slice(0, 2);
1665
+ const integerPart = BigInt(integerStr || "0") * 100n;
1666
+ const fractionalPart = BigInt(paddedFractional || "0");
1667
+ const result = integerPart + fractionalPart;
1668
+ return isNegative ? -result : result;
1669
+ }
1670
+ /**
1671
+ * Format a ratio as a percentage string.
1672
+ *
1673
+ * @param numerator - Numerator of the ratio
1674
+ * @param denominator - Denominator of the ratio
1675
+ * @param precision - Number of decimal places to display
1676
+ * @returns Formatted percentage string
1677
+ *
1678
+ * @example
1679
+ * ```typescript
1680
+ * formatRatioPercent(1n, 4n, 1n) // "25.0%"
1681
+ * formatRatioPercent(3n, 4n, 2n) // "75.00%"
1682
+ * ```
1683
+ */
1684
+ function formatRatioPercent(numerator, denominator, precision) {
1685
+ const scaledNumerator = numerator * 100n;
1686
+ return `${formatRatio$2(scaledNumerator, denominator, precision)}%`;
1664
1687
  }
1665
1688
 
1666
1689
  //#endregion
1667
- //#region src/panoptic/v2/writes/pokeOracle.ts
1690
+ //#region src/panoptic/v2/formatters/poolFormatters.ts
1668
1691
  /**
1669
- * Poke the oracle to update its state.
1692
+ * Create pool-bound formatters that capture token decimals.
1670
1693
  *
1671
- * This function can be called to advance the oracle epoch.
1672
- * Note: The oracle can only be poked once per epoch (64 seconds).
1694
+ * Use this factory when working with a single pool to avoid passing
1695
+ * decimals at every call site.
1673
1696
  *
1674
- * @param params - Poke oracle parameters
1675
- * @returns TxResult
1676
- * @throws OracleRateLimitedError if checkRateLimit is true and oracle was recently poked
1697
+ * @param config - Pool configuration with token decimals
1698
+ * @returns Pool-bound formatter functions
1677
1699
  *
1678
1700
  * @example
1679
1701
  * ```typescript
1680
- * const result = await pokeOracle({
1681
- * client,
1682
- * walletClient,
1683
- * account,
1684
- * poolAddress,
1702
+ * // Get pool data
1703
+ * const pool = await getPool({ client, poolAddress })
1704
+ *
1705
+ * // Create formatters bound to this pool
1706
+ * const fmt = createPoolFormatters({
1707
+ * decimals0: pool.token0Decimals,
1708
+ * decimals1: pool.token1Decimals,
1685
1709
  * })
1686
- * const receipt = await result.wait()
1710
+ *
1711
+ * // Now use without passing decimals each time
1712
+ * const priceStr = fmt.tickToPriceScaled(position.currentTick, 4n)
1713
+ * const amount0Str = fmt.formatAmount0(collateral.assets, 4n)
1714
+ * const amount1Str = fmt.formatAmount1(premia.token1, 2n)
1715
+ *
1716
+ * // Parse user input
1717
+ * const rawAmount0 = fmt.parseAmount0("1.5")
1718
+ * const rawAmount1 = fmt.parseAmount1("3000")
1687
1719
  * ```
1688
1720
  */
1689
- async function pokeOracle(params) {
1690
- const { client, walletClient, account, poolAddress, checkRateLimit = false, txOverrides } = params;
1691
- if (checkRateLimit) {
1692
- const [oracleData, block] = await Promise.all([client.readContract({
1693
- address: poolAddress,
1694
- abi: panopticPoolV2Abi,
1695
- functionName: "getOracleTicks"
1696
- }), client.getBlock()]);
1697
- const oraclePack = oracleData[4];
1698
- const { epoch } = decodeOracleTiming(oraclePack, block.timestamp);
1699
- const currentEpoch = oracleEpochAt(block.timestamp);
1700
- if (currentEpoch === epoch) {
1701
- const lastUpdate = epoch << 6n;
1702
- throw new OracleRateLimitedError(lastUpdate, block.timestamp);
1721
+ function createPoolFormatters(config) {
1722
+ const { decimals0, decimals1 } = config;
1723
+ return {
1724
+ decimals0,
1725
+ decimals1,
1726
+ tickToPrice(tick) {
1727
+ return tickToPrice(tick);
1728
+ },
1729
+ tickToPriceScaled(tick, precision) {
1730
+ return tickToPriceDecimalScaled(tick, decimals0, decimals1, precision);
1731
+ },
1732
+ tickToInversePriceScaled(tick, precision) {
1733
+ return tickToPriceDecimalScaled(tick, decimals1, decimals0, precision);
1734
+ },
1735
+ priceToTick(price) {
1736
+ return priceToTick(price, decimals0, decimals1);
1737
+ },
1738
+ formatAmount0(amount, precision) {
1739
+ return formatTokenAmount(amount, decimals0, precision);
1740
+ },
1741
+ formatAmount1(amount, precision) {
1742
+ return formatTokenAmount(amount, decimals1, precision);
1743
+ },
1744
+ parseAmount0(amount) {
1745
+ return parseTokenAmount(amount, decimals0);
1746
+ },
1747
+ parseAmount1(amount) {
1748
+ return parseTokenAmount(amount, decimals1);
1703
1749
  }
1704
- }
1705
- return submitWrite({
1706
- client,
1707
- walletClient,
1708
- account,
1709
- address: poolAddress,
1710
- abi: panopticPoolV2Abi,
1711
- functionName: "pokeOracle",
1712
- args: [],
1713
- txOverrides
1714
- });
1750
+ };
1751
+ }
1752
+
1753
+ //#endregion
1754
+ //#region src/panoptic/v2/formatters/tokenList.ts
1755
+ const TEN$1 = 10n;
1756
+ function formatRatio$1(numerator, denominator, precision) {
1757
+ if (precision < 0n) throw new RangeError("Precision must be non-negative");
1758
+ const scale = TEN$1 ** precision;
1759
+ const scaled = (numerator * scale + denominator / 2n) / denominator;
1760
+ const integerPart = scaled / scale;
1761
+ const fractionalPart = scaled % scale;
1762
+ if (precision === 0n) return integerPart.toString();
1763
+ return `${integerPart}.${fractionalPart.toString().padStart(Number(precision), "0")}`;
1715
1764
  }
1716
1765
  /**
1717
- * Poke oracle and wait for confirmation.
1766
+ * Generate a token list ID for external token list integration.
1767
+ *
1768
+ * Token lists use a standardized format: `chainId:address`
1769
+ * This is compatible with most token list standards.
1770
+ *
1771
+ * @param chainId - The chain ID
1772
+ * @param address - The token address
1773
+ * @returns Token list ID string
1774
+ *
1775
+ * @example
1776
+ * ```typescript
1777
+ * getTokenListId(1n, '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2')
1778
+ * // "1:0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
1779
+ *
1780
+ * getTokenListId(11155111n, '0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14')
1781
+ * // "11155111:0xfff9976782d46cc05630d1f6ebab18b2324d6b14"
1782
+ * ```
1718
1783
  */
1719
- async function pokeOracleAndWait(params) {
1720
- const result = await pokeOracle(params);
1721
- return result.wait();
1784
+ function getTokenListId(chainId, address) {
1785
+ return `${chainId}:${address.toLowerCase()}`;
1722
1786
  }
1723
-
1724
- //#endregion
1725
- //#region src/panoptic/v2/writes/factory.ts
1726
1787
  /**
1727
- * Deploy a new Panoptic pool via the factory.
1788
+ * Parse a token list ID back to chain ID and address.
1728
1789
  *
1729
- * @param params - Deployment parameters (versioned: 'v3' or 'v4')
1730
- * @returns Transaction result with hash and wait function
1790
+ * @param tokenListId - The token list ID string
1791
+ * @returns Object with chainId and address
1792
+ *
1793
+ * @example
1794
+ * ```typescript
1795
+ * parseTokenListId("1:0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")
1796
+ * // { chainId: 1n, address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" }
1797
+ * ```
1731
1798
  */
1732
- async function deployNewPool(params) {
1733
- const { client, walletClient, account, factoryAddress, riskEngine, salt, txOverrides } = params;
1734
- if (params.version === "v3") return submitWrite({
1735
- client,
1736
- walletClient,
1737
- account,
1738
- address: factoryAddress,
1739
- abi: panopticFactoryV3Abi,
1740
- functionName: "deployNewPool",
1741
- args: [
1742
- params.token0,
1743
- params.token1,
1744
- params.fee,
1745
- riskEngine,
1746
- salt
1747
- ],
1748
- txOverrides
1749
- });
1750
- return submitWrite({
1751
- client,
1752
- walletClient,
1753
- account,
1754
- address: factoryAddress,
1755
- abi: panopticFactoryV4Abi,
1756
- functionName: "deployNewPool",
1757
- args: [
1758
- {
1759
- currency0: params.poolKey.currency0,
1760
- currency1: params.poolKey.currency1,
1761
- fee: Number(params.poolKey.fee),
1762
- tickSpacing: Number(params.poolKey.tickSpacing),
1763
- hooks: params.poolKey.hooks
1764
- },
1765
- riskEngine,
1766
- salt
1767
- ],
1768
- txOverrides
1769
- });
1799
+ function parseTokenListId(tokenListId) {
1800
+ const [chainIdStr, address] = tokenListId.split(":");
1801
+ if (!chainIdStr || !address) throw new Error(`Invalid token list ID: ${tokenListId}`);
1802
+ return {
1803
+ chainId: BigInt(chainIdStr),
1804
+ address
1805
+ };
1770
1806
  }
1771
1807
  /**
1772
- * Deploy a new Panoptic pool and wait for confirmation.
1808
+ * Generate a pool ID string for display purposes.
1809
+ *
1810
+ * @param token0Symbol - Symbol of token0
1811
+ * @param token1Symbol - Symbol of token1
1812
+ * @param feeBps - Fee in basis points (e.g., 500n for 0.05%)
1813
+ * @returns Pool ID string
1814
+ *
1815
+ * @example
1816
+ * ```typescript
1817
+ * getPoolDisplayId('WETH', 'USDC', 500n)
1818
+ * // "WETH/USDC 0.05%"
1819
+ *
1820
+ * getPoolDisplayId('WBTC', 'ETH', 3000n)
1821
+ * // "WBTC/ETH 0.30%"
1822
+ * ```
1773
1823
  */
1774
- async function deployNewPoolAndWait(params) {
1775
- const result = await deployNewPool(params);
1776
- return result.wait();
1824
+ function getPoolDisplayId(token0Symbol, token1Symbol, feeBps) {
1825
+ return `${token0Symbol}/${token1Symbol} ${formatFeeTier(feeBps)}`;
1826
+ }
1827
+ /**
1828
+ * Format a fee tier for display.
1829
+ *
1830
+ * @param feeBps - Fee in basis points (e.g., 500n for 0.05%)
1831
+ * @returns Fee tier string
1832
+ *
1833
+ * @example
1834
+ * ```typescript
1835
+ * formatFeeTier(500n) // "0.05%"
1836
+ * formatFeeTier(3000n) // "0.30%"
1837
+ * formatFeeTier(10000n) // "1.0%"
1838
+ * ```
1839
+ */
1840
+ function formatFeeTier(feeBps) {
1841
+ const precision = feeBps < 10000n ? 2n : 1n;
1842
+ const feeStr = formatRatio$1(feeBps, 10000n, precision);
1843
+ return `${feeStr}%`;
1777
1844
  }
1778
1845
 
1779
1846
  //#endregion
1780
- //#region src/panoptic/v2/writes/txManagement.ts
1781
- /** Default gas price multiplier for replacement (12.5% bump = minimum for replacement) */
1782
- const DEFAULT_GAS_PRICE_MULTIPLIER = 1.125;
1847
+ //#region src/panoptic/v2/formatters/display.ts
1848
+ const TEN = 10n;
1849
+ function formatRatio(numerator, denominator, precision) {
1850
+ if (precision < 0n) throw new RangeError("Precision must be non-negative");
1851
+ const scale = TEN ** precision;
1852
+ const scaled = (numerator * scale + denominator / 2n) / denominator;
1853
+ const integerPart = scaled / scale;
1854
+ const fractionalPart = scaled % scale;
1855
+ if (precision === 0n) return integerPart.toString();
1856
+ return `${integerPart}.${fractionalPart.toString().padStart(Number(precision), "0")}`;
1857
+ }
1783
1858
  /**
1784
- * Apply a multiplier to a bigint gas value.
1785
- * Uses integer arithmetic to avoid floating point issues.
1859
+ * Truncate an address for display.
1860
+ *
1861
+ * @param address - Full address
1862
+ * @param chars - Characters to show on each side (default: 4)
1863
+ * @returns Truncated address like "0x1234...5678"
1864
+ *
1865
+ * @example
1866
+ * ```typescript
1867
+ * truncateAddress('0x1234567890abcdef1234567890abcdef12345678')
1868
+ * // "0x1234...5678"
1869
+ *
1870
+ * truncateAddress('0x1234567890abcdef1234567890abcdef12345678', 6)
1871
+ * // "0x123456...345678"
1872
+ * ```
1786
1873
  */
1787
- function applyMultiplier(value, multiplier) {
1788
- const bps = BigInt(Math.ceil(multiplier * 1e4));
1789
- return value * bps / 10000n;
1874
+ function truncateAddress(address, chars = 4) {
1875
+ const charsBig = BigInt(chars);
1876
+ const minLength = charsBig * 2n + 4n;
1877
+ if (BigInt(address.length) <= minLength) return address;
1878
+ return `${address.slice(0, Number(charsBig + 2n))}...${address.slice(-Number(charsBig))}`;
1790
1879
  }
1791
1880
  /**
1792
- * Compute bumped gas parameters from the original transaction.
1881
+ * Format a Unix timestamp as an ISO date string (YYYY-MM-DD).
1882
+ *
1883
+ * @param timestamp - Unix timestamp in seconds
1884
+ * @returns ISO date string
1885
+ *
1886
+ * @example
1887
+ * ```typescript
1888
+ * formatTimestamp(1700000000n) // "2023-11-14"
1889
+ * ```
1793
1890
  */
1794
- function computeBumpedGas(originalMaxFeePerGas, originalMaxPriorityFeePerGas, explicitMaxFeePerGas, explicitMaxPriorityFeePerGas, multiplier) {
1795
- const maxFeePerGas = explicitMaxFeePerGas ?? applyMultiplier(originalMaxFeePerGas ?? 0n, multiplier);
1796
- const maxPriorityFeePerGas = explicitMaxPriorityFeePerGas ?? applyMultiplier(originalMaxPriorityFeePerGas ?? 0n, multiplier);
1797
- return {
1798
- maxFeePerGas,
1799
- maxPriorityFeePerGas
1800
- };
1891
+ function formatTimestamp(timestamp) {
1892
+ const ms = timestamp * 1000n;
1893
+ return new Date(Number(ms)).toISOString().split("T")[0];
1801
1894
  }
1802
1895
  /**
1803
- * Speed up a pending transaction by resubmitting with higher gas.
1804
- *
1805
- * Fetches the original transaction, extracts its parameters,
1806
- * bumps the gas price, and resubmits with the same nonce.
1896
+ * Format a Unix timestamp as an ISO datetime string.
1807
1897
  *
1808
- * @param params - Speed up parameters
1809
- * @returns TxResult for the replacement transaction
1810
- * @throws Error if the original transaction is not found
1898
+ * @param timestamp - Unix timestamp in seconds
1899
+ * @returns ISO datetime string
1811
1900
  *
1812
1901
  * @example
1813
1902
  * ```typescript
1814
- * // Speed up with default 12.5% bump
1815
- * const result = await speedUpTransaction({
1816
- * client,
1817
- * walletClient,
1818
- * hash: pendingTxHash,
1819
- * })
1903
+ * formatDatetime(1700000000n) // "2023-11-14T22:13:20.000Z"
1904
+ * ```
1905
+ */
1906
+ function formatDatetime(timestamp) {
1907
+ const ms = timestamp * 1000n;
1908
+ return new Date(Number(ms)).toISOString();
1909
+ }
1910
+ /**
1911
+ * Format a Unix timestamp as a locale-aware date string.
1820
1912
  *
1821
- * // Speed up with explicit gas prices
1822
- * const result = await speedUpTransaction({
1823
- * client,
1824
- * walletClient,
1825
- * hash: pendingTxHash,
1826
- * maxFeePerGas: 50_000_000_000n, // 50 gwei
1827
- * maxPriorityFeePerGas: 3_000_000_000n, // 3 gwei
1828
- * })
1913
+ * @param timestamp - Unix timestamp in seconds
1914
+ * @param locale - Locale string (default: system locale)
1915
+ * @param options - Intl.DateTimeFormat options
1916
+ * @returns Formatted date string
1917
+ *
1918
+ * @example
1919
+ * ```typescript
1920
+ * formatTimestampLocale(1700000000n)
1921
+ * // "11/14/2023" (US locale)
1922
+ *
1923
+ * formatTimestampLocale(1700000000n, 'de-DE')
1924
+ * // "14.11.2023" (German locale)
1829
1925
  * ```
1830
1926
  */
1831
- async function speedUpTransaction(params) {
1832
- const { client, walletClient, hash, maxFeePerGas: explicitMaxFee, maxPriorityFeePerGas: explicitMaxPriority, gasPriceMultiplier = DEFAULT_GAS_PRICE_MULTIPLIER, broadcaster } = params;
1833
- const tx = await client.getTransaction({ hash });
1834
- const { maxFeePerGas, maxPriorityFeePerGas } = computeBumpedGas(tx.maxFeePerGas ?? void 0, tx.maxPriorityFeePerGas ?? void 0, explicitMaxFee, explicitMaxPriority, gasPriceMultiplier);
1835
- if (broadcaster) {
1836
- const request = await walletClient.prepareTransactionRequest({
1837
- account: tx.from,
1838
- to: tx.to ?? void 0,
1839
- data: tx.input,
1840
- value: tx.value,
1841
- nonce: tx.nonce,
1842
- gas: tx.gas,
1843
- maxFeePerGas,
1844
- maxPriorityFeePerGas,
1845
- chain: walletClient.chain
1846
- });
1847
- const signedTx = await walletClient.signTransaction({
1848
- ...request,
1849
- account: tx.from
1850
- });
1851
- const replacementHash$1 = await broadcaster.broadcast(signedTx);
1852
- return createTxResult(client, replacementHash$1);
1853
- }
1854
- const replacementHash = await walletClient.sendTransaction({
1855
- account: tx.from,
1856
- to: tx.to ?? void 0,
1857
- data: tx.input,
1858
- value: tx.value,
1859
- nonce: tx.nonce,
1860
- gas: tx.gas,
1861
- maxFeePerGas,
1862
- maxPriorityFeePerGas,
1863
- chain: walletClient.chain
1864
- });
1865
- return createTxResult(client, replacementHash);
1927
+ function formatTimestampLocale(timestamp, locale, options) {
1928
+ const ms = timestamp * 1000n;
1929
+ return new Date(Number(ms)).toLocaleDateString(locale, options);
1866
1930
  }
1867
1931
  /**
1868
- * Cancel a pending transaction by sending a 0-value self-transfer
1869
- * with the same nonce and higher gas price.
1932
+ * Format a duration in milliseconds as a human-readable string.
1870
1933
  *
1871
- * @param params - Cancel parameters
1872
- * @returns TxResult for the cancellation transaction
1873
- * @throws Error if the original transaction is not found
1934
+ * @param ms - Duration in milliseconds
1935
+ * @returns Formatted duration string
1874
1936
  *
1875
1937
  * @example
1876
1938
  * ```typescript
1877
- * const result = await cancelTransaction({
1878
- * client,
1879
- * walletClient,
1880
- * account,
1881
- * hash: pendingTxHash,
1882
- * })
1883
- * await result.wait()
1939
+ * formatDuration(1500n) // "1.5s"
1940
+ * formatDuration(150n) // "150ms"
1941
+ * formatDuration(90000n) // "1m 30s"
1942
+ * formatDuration(3661000n) // "1h 1m"
1884
1943
  * ```
1885
1944
  */
1886
- async function cancelTransaction(params) {
1887
- const { client, walletClient, account, hash, maxFeePerGas: explicitMaxFee, maxPriorityFeePerGas: explicitMaxPriority, gasPriceMultiplier = DEFAULT_GAS_PRICE_MULTIPLIER, broadcaster } = params;
1888
- const tx = await client.getTransaction({ hash });
1889
- const { maxFeePerGas, maxPriorityFeePerGas } = computeBumpedGas(tx.maxFeePerGas ?? void 0, tx.maxPriorityFeePerGas ?? void 0, explicitMaxFee, explicitMaxPriority, gasPriceMultiplier);
1890
- if (broadcaster) {
1891
- const request = await walletClient.prepareTransactionRequest({
1892
- account,
1893
- to: account,
1894
- value: 0n,
1895
- nonce: tx.nonce,
1896
- gas: 21000n,
1897
- maxFeePerGas,
1898
- maxPriorityFeePerGas,
1899
- chain: walletClient.chain
1900
- });
1901
- const signedTx = await walletClient.signTransaction({
1902
- ...request,
1903
- account
1904
- });
1905
- const cancelHash$1 = await broadcaster.broadcast(signedTx);
1906
- return createTxResult(client, cancelHash$1);
1945
+ function formatDuration(ms) {
1946
+ const isNegative = ms < 0n;
1947
+ const absMs = isNegative ? -ms : ms;
1948
+ const sign = isNegative ? "-" : "";
1949
+ if (absMs < 1000n) return `${sign}${absMs}ms`;
1950
+ if (absMs < 60000n) {
1951
+ const tenthsTotal = (absMs + 50n) / 100n;
1952
+ const seconds = tenthsTotal / 10n;
1953
+ const tenths = tenthsTotal % 10n;
1954
+ return `${sign}${seconds}.${tenths}s`;
1907
1955
  }
1908
- const cancelHash = await walletClient.sendTransaction({
1909
- account,
1910
- to: account,
1911
- value: 0n,
1912
- nonce: tx.nonce,
1913
- gas: 21000n,
1914
- maxFeePerGas,
1915
- maxPriorityFeePerGas,
1916
- chain: walletClient.chain
1917
- });
1918
- return createTxResult(client, cancelHash);
1956
+ if (absMs < 3600000n) {
1957
+ const minutes$1 = absMs / 60000n;
1958
+ const seconds = (absMs % 60000n + 500n) / 1000n;
1959
+ return seconds > 0n ? `${sign}${minutes$1}m ${seconds}s` : `${sign}${minutes$1}m`;
1960
+ }
1961
+ const hours = absMs / 3600000n;
1962
+ const minutes = (absMs % 3600000n + 30000n) / 60000n;
1963
+ return minutes > 0n ? `${sign}${hours}h ${minutes}m` : `${sign}${hours}h`;
1964
+ }
1965
+ /**
1966
+ * Format a duration in seconds as a human-readable string.
1967
+ *
1968
+ * @param seconds - Duration in seconds
1969
+ * @returns Formatted duration string
1970
+ */
1971
+ function formatDurationSeconds(seconds) {
1972
+ return formatDuration(seconds * 1000n);
1973
+ }
1974
+ /**
1975
+ * Format a block number for display.
1976
+ *
1977
+ * @param blockNumber - The block number
1978
+ * @returns Formatted block number with commas
1979
+ *
1980
+ * @example
1981
+ * ```typescript
1982
+ * formatBlockNumber(18000000n) // "18,000,000"
1983
+ * ```
1984
+ */
1985
+ function formatBlockNumber(blockNumber) {
1986
+ return blockNumber.toLocaleString();
1987
+ }
1988
+ /**
1989
+ * Format a gas amount for display.
1990
+ *
1991
+ * @param gas - Gas units
1992
+ * @returns Formatted gas string
1993
+ */
1994
+ function formatGas(gas) {
1995
+ return gas.toLocaleString();
1996
+ }
1997
+ /**
1998
+ * Format a transaction hash for display (truncated).
1999
+ *
2000
+ * @param hash - Full transaction hash
2001
+ * @param chars - Characters to show on each side (default: 6)
2002
+ * @returns Truncated hash like "0x123456...abcdef"
2003
+ *
2004
+ * @example
2005
+ * ```typescript
2006
+ * formatTxHash('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef')
2007
+ * // "0x123456...abcdef"
2008
+ * ```
2009
+ */
2010
+ function formatTxHash(hash, chars = 6) {
2011
+ const charsBig = BigInt(chars);
2012
+ const minLength = charsBig * 2n + 4n;
2013
+ if (BigInt(hash.length) <= minLength) return hash;
2014
+ return `${hash.slice(0, Number(charsBig + 2n))}...${hash.slice(-Number(charsBig))}`;
2015
+ }
2016
+ function formatHex(value) {
2017
+ return `0x${value.toString(16)}`;
2018
+ }
2019
+ /**
2020
+ * Format a TokenId as a hex string.
2021
+ *
2022
+ * @param tokenId - TokenId value
2023
+ * @returns Hex string representation
2024
+ */
2025
+ function formatTokenIdHex(tokenId) {
2026
+ return formatHex(tokenId);
2027
+ }
2028
+ /**
2029
+ * Format a PoolId as a hex string.
2030
+ *
2031
+ * @param poolId - PoolId value
2032
+ * @returns Hex string representation
2033
+ */
2034
+ function formatPoolIdHex(poolId) {
2035
+ return formatHex(poolId);
2036
+ }
2037
+ /**
2038
+ * Format a TokenId as a shortened hex string.
2039
+ *
2040
+ * @param tokenId - TokenId value
2041
+ * @param chars - Characters to show on each side (default: 4)
2042
+ * @returns Truncated hex string like "0x1234...abcd"
2043
+ */
2044
+ function formatTokenIdShort(tokenId, chars = 4) {
2045
+ const hex = formatHex(tokenId);
2046
+ const charsBig = BigInt(chars);
2047
+ const minLength = charsBig * 2n + 4n;
2048
+ if (BigInt(hex.length) <= minLength) return hex;
2049
+ return `${hex.slice(0, Number(charsBig + 2n))}...${hex.slice(-Number(charsBig))}`;
2050
+ }
2051
+ /**
2052
+ * Format a large number with K/M/B suffixes.
2053
+ *
2054
+ * @param value - The numeric value
2055
+ * @param precision - Number of decimal places (default: 1n)
2056
+ * @returns Formatted string with suffix
2057
+ *
2058
+ * @example
2059
+ * ```typescript
2060
+ * formatCompact(1234n) // "1.2K"
2061
+ * formatCompact(1234567n) // "1.2M"
2062
+ * formatCompact(1234567890n) // "1.2B"
2063
+ * formatCompact(999n) // "999"
2064
+ * ```
2065
+ */
2066
+ function formatCompact(value, precision = 1n) {
2067
+ const isNegative = value < 0n;
2068
+ const absValue = isNegative ? -value : value;
2069
+ let formatted;
2070
+ if (absValue < 1000n) formatted = absValue.toString();
2071
+ else if (absValue < 1000000n) formatted = `${formatRatio(absValue, 1000n, precision)}K`;
2072
+ else if (absValue < 1000000000n) formatted = `${formatRatio(absValue, 1000000n, precision)}M`;
2073
+ else formatted = `${formatRatio(absValue, 1000000000n, precision)}B`;
2074
+ return isNegative ? `-${formatted}` : formatted;
2075
+ }
2076
+ /**
2077
+ * Format a wei amount as a display string.
2078
+ *
2079
+ * @param wei - Amount in wei
2080
+ * @returns Formatted string with unit
2081
+ */
2082
+ function formatWei(wei) {
2083
+ return `${wei} wei`;
2084
+ }
2085
+ /**
2086
+ * Format a wei amount as gwei.
2087
+ *
2088
+ * @param wei - Amount in wei
2089
+ * @param precision - Number of decimal places to display
2090
+ * @returns Formatted string with unit
2091
+ */
2092
+ function formatGwei(wei, precision) {
2093
+ return `${formatTokenAmount(wei, 9n, precision)} gwei`;
1919
2094
  }
1920
2095
 
1921
2096
  //#endregion
@@ -2006,7 +2181,7 @@ function decodeVegoid(tokenId) {
2006
2181
  * @param tokenId - The TokenId to decode
2007
2182
  * @returns The tick spacing
2008
2183
  */
2009
- function decodeTickSpacing(tokenId) {
2184
+ function decodeTickSpacing$1(tokenId) {
2010
2185
  const poolId = tokenId & (1n << TOKEN_ID_BITS.POOL_ID_SIZE) - 1n;
2011
2186
  return poolId >> TOKEN_ID_BITS.TICK_SPACING_STARTING_BIT;
2012
2187
  }
@@ -2217,766 +2392,1149 @@ function createTokenIdBuilder(poolId) {
2217
2392
  }
2218
2393
 
2219
2394
  //#endregion
2220
- //#region src/panoptic/v2/writes/loanUtils.ts
2221
- /** Maximum retry attempts on InputListFail */
2222
- const MAX_RETRIES = 3;
2395
+ //#region src/panoptic/v2/tokenId/decode.ts
2223
2396
  /**
2224
- * Resolve token index (0 or 1) for the given token address against the pool.
2225
- * Throws SwapTokenMismatchError if the token isn't in the pool.
2397
+ * Convert a DecodedLeg to TokenIdLeg format.
2398
+ *
2399
+ * @param leg - The decoded leg from encoding utils
2400
+ * @param tickSpacing - The tick spacing for calculating tick bounds
2401
+ * @returns The TokenIdLeg format
2226
2402
  */
2227
- function resolveTokenIndex(tokenAddress, token0, token1) {
2228
- const lower = tokenAddress.toLowerCase();
2229
- if (lower === token0.toLowerCase()) return 0n;
2230
- if (lower === token1.toLowerCase()) return 1n;
2231
- throw new SwapTokenMismatchError(tokenAddress, token0, token1);
2403
+ function convertToTokenIdLeg(leg, tickSpacing) {
2404
+ const halfWidth = leg.width * tickSpacing / 2n;
2405
+ const tickLower = leg.strike - halfWidth;
2406
+ const tickUpper = leg.strike + halfWidth;
2407
+ return {
2408
+ index: leg.index,
2409
+ asset: leg.asset,
2410
+ optionRatio: leg.optionRatio,
2411
+ isLong: leg.isLong,
2412
+ tokenType: leg.tokenType,
2413
+ riskPartner: leg.riskPartner,
2414
+ strike: leg.strike,
2415
+ width: leg.width,
2416
+ tickLower,
2417
+ tickUpper
2418
+ };
2232
2419
  }
2233
2420
  /**
2234
- * Resolve existing position IDs from explicit param or storage.
2421
+ * Decode a TokenId into its component parts.
2422
+ *
2423
+ * @param tokenId - The TokenId to decode
2424
+ * @returns The decoded TokenId data
2425
+ *
2426
+ * @example
2427
+ * ```typescript
2428
+ * const decoded = decodeTokenId(tokenId)
2429
+ * console.log(decoded.legs) // Array of legs
2430
+ * console.log(decoded.tickSpacing) // Tick spacing
2431
+ * ```
2235
2432
  */
2236
- async function resolvePositionIds(explicit, storage, chainId, poolAddress, account) {
2237
- if (explicit !== void 0) return explicit;
2238
- if (storage) return getTrackedPositionIds({
2239
- chainId,
2240
- poolAddress,
2241
- account,
2242
- storage
2243
- });
2244
- throw new MissingPositionIdsError();
2433
+ function decodeTokenId(tokenId) {
2434
+ const poolId = decodePoolId(tokenId);
2435
+ const vegoid = decodeVegoid(tokenId);
2436
+ const tickSpacing = decodeTickSpacing$1(tokenId);
2437
+ const rawLegs = decodeAllLegs(tokenId);
2438
+ const numLegs = countLegs(tokenId);
2439
+ const legs = rawLegs.map((leg) => convertToTokenIdLeg(leg, tickSpacing));
2440
+ return {
2441
+ tokenId,
2442
+ poolId,
2443
+ vegoid,
2444
+ tickSpacing,
2445
+ legs,
2446
+ legCount: numLegs
2447
+ };
2245
2448
  }
2246
2449
  /**
2247
- * Check if a caught error is an InputListFail contract revert.
2248
- * Works with both raw viem errors and parsed PanopticError instances.
2450
+ * Validate that a TokenId has the expected pool ID.
2451
+ *
2452
+ * @param tokenId - The TokenId to validate
2453
+ * @param expectedPoolId - The expected pool ID
2454
+ * @returns True if the pool IDs match
2249
2455
  */
2250
- function isInputListFailError(error) {
2251
- if (error instanceof InputListFailError) return true;
2252
- const parsed = parsePanopticError(error);
2253
- return parsed?.errorName === "InputListFail";
2456
+ function validatePoolId(tokenId, expectedPoolId) {
2457
+ const actualPoolId = tokenId & (1n << 64n) - 1n;
2458
+ return actualPoolId === expectedPoolId;
2254
2459
  }
2255
2460
  /**
2256
- * Build a unique width=0 tokenId that doesn't collide with existing positions.
2257
- * Bumps optionRatio (1-127) and returns adjusted size to maintain equivalent exposure.
2461
+ * Check if a TokenId represents a long position (any leg is long).
2258
2462
  *
2259
- * @param kind - Whether to build a loan (borrow) or a credit (pay-in) leg.
2260
- * @param asset - Which token denominates the positionSize (0 or 1).
2463
+ * @param tokenId - The TokenId to check
2464
+ * @returns True if any leg is long
2261
2465
  */
2262
- function buildUniqueWidthZeroLeg(kind, poolId, asset, tokenType, currentTick, tickSpacing, existingPositionIds, positionSize) {
2263
- const addLeg = (strike$1, optionRatio) => {
2264
- const builder = createTokenIdBuilder(poolId);
2265
- const config = {
2266
- asset,
2267
- tokenType,
2268
- strike: strike$1,
2269
- optionRatio
2270
- };
2271
- return (kind === "loan" ? builder.addLoan(config) : builder.addCredit(config)).build();
2272
- };
2273
- const mod = currentTick % tickSpacing;
2274
- let strike = currentTick - (mod + tickSpacing) % tickSpacing;
2275
- for (let ratio = 1n; ratio <= 127n; ratio++) {
2276
- const tokenId = addLeg(strike, ratio);
2277
- if (!existingPositionIds.includes(tokenId)) {
2278
- const adjustedSize = positionSize / ratio;
2279
- if (adjustedSize === 0n || positionSize % ratio !== 0n) continue;
2280
- return {
2281
- tokenId,
2282
- adjustedSize
2283
- };
2284
- }
2285
- }
2286
- const mod2 = currentTick % tickSpacing;
2287
- strike = currentTick - (mod2 + tickSpacing) % tickSpacing + tickSpacing;
2288
- for (let i = 0; i < 100; i++) {
2289
- const tokenId = addLeg(strike);
2290
- if (!existingPositionIds.includes(tokenId)) return {
2291
- tokenId,
2292
- adjustedSize: positionSize
2293
- };
2294
- strike += tickSpacing;
2295
- }
2296
- throw new LoanSlotExhaustedError();
2466
+ function hasLongLeg(tokenId) {
2467
+ const legs = decodeAllLegs(tokenId);
2468
+ return legs.some((leg) => leg.isLong);
2297
2469
  }
2298
2470
  /**
2299
- * Build a unique loan (borrow) tokenId that doesn't collide with existing positions.
2471
+ * Check if a TokenId represents a short-only position.
2300
2472
  *
2301
- * @param asset - Which token denominates the positionSize (0 or 1).
2473
+ * @param tokenId - The TokenId to check
2474
+ * @returns True if all legs are short
2302
2475
  */
2303
- function buildUniqueLoan(poolId, asset, tokenType, currentTick, tickSpacing, existingPositionIds, positionSize) {
2304
- return buildUniqueWidthZeroLeg("loan", poolId, asset, tokenType, currentTick, tickSpacing, existingPositionIds, positionSize);
2476
+ function isShortOnly(tokenId) {
2477
+ const legs = decodeAllLegs(tokenId);
2478
+ return legs.length > 0 && legs.every((leg) => !leg.isLong);
2305
2479
  }
2306
2480
  /**
2307
- * Build a unique credit (pay-in) tokenId that doesn't collide with existing positions.
2308
- *
2309
- * Unlike a loan, a credit never borrows from the pool: it requires **zero** buying
2310
- * power, leaves `s_assetsInAMM` (and therefore utilization and the borrow rate)
2311
- * untouched, and accrues no interest. This is what lets a credit-based swap run
2312
- * against a fully-utilized collateral tracker.
2481
+ * Check if a TokenId represents a spread (legs with different risk partners).
2313
2482
  *
2314
- * @param asset - Which token denominates the positionSize (0 or 1).
2483
+ * @param tokenId - The TokenId to check
2484
+ * @returns True if any leg has a different risk partner
2315
2485
  */
2316
- function buildUniqueCredit(poolId, asset, tokenType, currentTick, tickSpacing, existingPositionIds, positionSize) {
2317
- return buildUniqueWidthZeroLeg("credit", poolId, asset, tokenType, currentTick, tickSpacing, existingPositionIds, positionSize);
2318
- }
2319
-
2320
- //#endregion
2321
- //#region src/panoptic/v2/writes/swap.ts
2322
- /** Build an atomic credit mint/burn swap without fetching or sending anything. */
2323
- function buildCreditSwapCall(params) {
2324
- const amount = params.kind === "exactIn" ? params.amountIn : params.amountOut;
2325
- if (amount <= 0n) throw new Error("credit swap amount must be positive");
2326
- if (params.tokenIndex !== 0n && params.tokenIndex !== 1n) throw new Error("credit swap tokenIndex must be 0 or 1");
2327
- const { low, high } = tickLimits(params.currentTick, params.slippageBps);
2328
- const { tokenId: creditTokenId, adjustedSize } = buildUniqueCredit(params.poolId, params.tokenIndex, params.tokenIndex, params.currentTick, params.tickSpacing, params.existingPositionIds, amount);
2329
- const ascending = [
2330
- Number(low),
2331
- Number(high),
2332
- 0
2333
- ];
2334
- const descending = [
2335
- Number(high),
2336
- Number(low),
2337
- 0
2338
- ];
2339
- const limits = params.kind === "exactIn" ? [ascending, descending] : [descending, ascending];
2340
- const args = [
2341
- [creditTokenId, creditTokenId],
2342
- [...params.existingPositionIds],
2343
- [adjustedSize, 0n],
2344
- limits,
2345
- false,
2346
- params.builderCode ?? 0n
2347
- ];
2348
- return {
2349
- to: params.poolAddress,
2350
- data: encodeFunctionData({
2351
- abi: panopticPoolV2Abi,
2352
- functionName: "dispatch",
2353
- args
2354
- }),
2355
- creditTokenId,
2356
- adjustedSize,
2357
- args
2358
- };
2486
+ function isSpread(tokenId) {
2487
+ const legs = decodeAllLegs(tokenId);
2488
+ return legs.some((leg) => leg.riskPartner !== leg.index);
2359
2489
  }
2360
2490
  /**
2361
- * Swap tokens using Panoptic's exact-output mechanism.
2362
- *
2363
- * Opens a credit in `tokenOut` with `swapAtMint=true` (paying a swapped amount of
2364
- * the other token), then burns it with `swapAtMint=false` to receive exactly
2365
- * `amountOut` of `tokenOut`.
2366
- *
2367
- * @param params - Swap parameters
2368
- * @returns TxResult
2491
+ * Get the asset index for a TokenId (from the first leg).
2369
2492
  *
2370
- * @example
2371
- * ```typescript
2372
- * const result = await swapExactOut({
2373
- * client, walletClient, account, poolAddress,
2374
- * chainId: 11155111n,
2375
- * tokenOut: WETH_ADDRESS,
2376
- * amountOut: 5n * 10n**16n, // 0.05 WETH
2377
- * slippageBps: 500n, // 5% slippage
2378
- * })
2379
- * const receipt = await result.wait()
2380
- * ```
2493
+ * @param tokenId - The TokenId
2494
+ * @returns The asset index (0 or 1), or undefined if no legs
2381
2495
  */
2382
- async function swapExactOut(params) {
2383
- const { client, walletClient, account, poolAddress, chainId, tokenOut, amountOut, slippageBps, existingPositionIds: explicitIds, storage, builderCode = 0n, txOverrides } = params;
2384
- for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
2385
- const [pool, positionIds] = await Promise.all([getPool({
2386
- client,
2387
- poolAddress,
2388
- chainId
2389
- }), resolvePositionIds(explicitIds, storage, chainId, poolAddress, account)]);
2390
- const token0 = pool.collateralTracker0.token;
2391
- const token1 = pool.collateralTracker1.token;
2392
- const tokenOutIndex = resolveTokenIndex(tokenOut, token0, token1);
2393
- const call = buildCreditSwapCall({
2394
- kind: "exactOut",
2395
- poolAddress,
2396
- poolId: pool.poolId,
2397
- currentTick: pool.currentTick,
2398
- tickSpacing: pool.tickSpacing,
2399
- existingPositionIds: positionIds,
2400
- tokenIndex: tokenOutIndex,
2401
- amountOut,
2402
- slippageBps,
2403
- builderCode
2404
- });
2405
- try {
2406
- return await submitWrite({
2407
- client,
2408
- walletClient,
2409
- account,
2410
- address: poolAddress,
2411
- abi: panopticPoolV2Abi,
2412
- functionName: "dispatch",
2413
- args: call.args,
2414
- txOverrides
2415
- });
2416
- } catch (error) {
2417
- if (isInputListFailError(error) && attempt < MAX_RETRIES - 1) continue;
2418
- throw error;
2419
- }
2420
- }
2421
- throw new MaxRetriesExceededError("swapExactOut");
2496
+ function getAssetIndex(tokenId) {
2497
+ const legs = decodeAllLegs(tokenId);
2498
+ if (legs.length === 0) return void 0;
2499
+ return legs[0].asset;
2422
2500
  }
2423
2501
  /**
2424
- * Swap exact output and wait for confirmation.
2502
+ * Check if a leg is a loan (width=0 and isLong=false).
2503
+ *
2504
+ * Loans borrow liquidity from the pool at a specific strike price.
2505
+ *
2506
+ * @param leg - The decoded leg to check
2507
+ * @returns True if the leg is a loan
2425
2508
  */
2426
- async function swapExactOutAndWait(params) {
2427
- const result = await swapExactOut(params);
2428
- return result.wait();
2509
+ function isLoanLeg(leg) {
2510
+ return leg.width === 0n && !leg.isLong;
2429
2511
  }
2430
2512
  /**
2431
- * Swap tokens using Panoptic's exact-input mechanism.
2513
+ * Check if a leg is a credit (width=0 and isLong=true).
2432
2514
  *
2433
- * Opens a credit in `tokenIn` with `swapAtMint=false` (paying exactly `amountIn`),
2434
- * then burns it with `swapAtMint=true` to receive the swapped amount of the other
2435
- * token. The user spends exactly `amountIn` of `tokenIn`.
2515
+ * Credits lend liquidity to the pool at a specific strike price.
2436
2516
  *
2437
- * @param params - Swap parameters
2438
- * @returns TxResult
2517
+ * @param leg - The decoded leg to check
2518
+ * @returns True if the leg is a credit
2519
+ */
2520
+ function isCreditLeg(leg) {
2521
+ return leg.width === 0n && leg.isLong;
2522
+ }
2523
+ /**
2524
+ * Check if a TokenId is a pure loan (all legs are loans).
2439
2525
  *
2440
- * @example
2441
- * ```typescript
2442
- * const result = await swapExactIn({
2443
- * client, walletClient, account, poolAddress,
2444
- * chainId: 11155111n,
2445
- * tokenIn: USDC_ADDRESS,
2446
- * amountIn: 1000n * 10n**6n, // 1000 USDC
2447
- * slippageBps: 500n,
2448
- * })
2449
- * const receipt = await result.wait()
2450
- * ```
2526
+ * @param tokenId - The TokenId to check
2527
+ * @returns True if all legs are loans
2451
2528
  */
2452
- async function swapExactIn(params) {
2453
- const { client, walletClient, account, poolAddress, chainId, tokenIn, amountIn, slippageBps, existingPositionIds: explicitIds, storage, builderCode = 0n, txOverrides } = params;
2454
- for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
2455
- const [pool, positionIds] = await Promise.all([getPool({
2456
- client,
2457
- poolAddress,
2458
- chainId
2459
- }), resolvePositionIds(explicitIds, storage, chainId, poolAddress, account)]);
2460
- const token0 = pool.collateralTracker0.token;
2461
- const token1 = pool.collateralTracker1.token;
2462
- const tokenInIndex = resolveTokenIndex(tokenIn, token0, token1);
2463
- const call = buildCreditSwapCall({
2464
- kind: "exactIn",
2465
- poolAddress,
2466
- poolId: pool.poolId,
2467
- currentTick: pool.currentTick,
2468
- tickSpacing: pool.tickSpacing,
2469
- existingPositionIds: positionIds,
2470
- tokenIndex: tokenInIndex,
2471
- amountIn,
2472
- slippageBps,
2473
- builderCode
2474
- });
2475
- try {
2476
- return await submitWrite({
2477
- client,
2478
- walletClient,
2479
- account,
2480
- address: poolAddress,
2481
- abi: panopticPoolV2Abi,
2482
- functionName: "dispatch",
2483
- args: call.args,
2484
- txOverrides
2485
- });
2486
- } catch (error) {
2487
- if (isInputListFailError(error) && attempt < MAX_RETRIES - 1) continue;
2488
- throw error;
2489
- }
2490
- }
2491
- throw new MaxRetriesExceededError("swapExactIn");
2529
+ function isLoan(tokenId) {
2530
+ const legs = decodeAllLegs(tokenId);
2531
+ return legs.length > 0 && legs.every(isLoanLeg);
2492
2532
  }
2493
2533
  /**
2494
- * Swap exact input and wait for confirmation.
2534
+ * Check if a TokenId is a pure credit (all legs are credits).
2535
+ *
2536
+ * @param tokenId - The TokenId to check
2537
+ * @returns True if all legs are credits
2495
2538
  */
2496
- async function swapExactInAndWait(params) {
2497
- const result = await swapExactIn(params);
2498
- return result.wait();
2539
+ function isCredit(tokenId) {
2540
+ const legs = decodeAllLegs(tokenId);
2541
+ return legs.length > 0 && legs.every(isCreditLeg);
2499
2542
  }
2500
-
2501
- //#endregion
2502
- //#region src/panoptic/v2/writes/wrap.ts
2503
- /** Minimal ERC4626 surface used by the xStock wrappers. */
2504
- const xstockWrapperAbi = [
2505
- {
2506
- type: "function",
2507
- name: "deposit",
2508
- stateMutability: "nonpayable",
2509
- inputs: [{
2510
- name: "assets",
2511
- type: "uint256"
2512
- }, {
2513
- name: "receiver",
2514
- type: "address"
2515
- }],
2516
- outputs: [{
2517
- name: "shares",
2518
- type: "uint256"
2519
- }]
2520
- },
2521
- {
2522
- type: "function",
2523
- name: "redeem",
2524
- stateMutability: "nonpayable",
2525
- inputs: [
2526
- {
2527
- name: "shares",
2528
- type: "uint256"
2529
- },
2530
- {
2531
- name: "receiver",
2532
- type: "address"
2533
- },
2534
- {
2535
- name: "owner",
2536
- type: "address"
2537
- }
2538
- ],
2539
- outputs: [{
2540
- name: "assets",
2541
- type: "uint256"
2542
- }]
2543
- },
2544
- {
2545
- type: "function",
2546
- name: "asset",
2547
- stateMutability: "view",
2548
- inputs: [],
2549
- outputs: [{ type: "address" }]
2550
- },
2551
- {
2552
- type: "function",
2553
- name: "previewDeposit",
2554
- stateMutability: "view",
2555
- inputs: [{
2556
- name: "assets",
2557
- type: "uint256"
2558
- }],
2559
- outputs: [{
2560
- name: "shares",
2561
- type: "uint256"
2562
- }]
2563
- },
2564
- {
2565
- type: "function",
2566
- name: "previewRedeem",
2567
- stateMutability: "view",
2568
- inputs: [{
2569
- name: "shares",
2570
- type: "uint256"
2571
- }],
2572
- outputs: [{
2573
- name: "assets",
2574
- type: "uint256"
2575
- }]
2576
- },
2577
- {
2578
- type: "function",
2579
- name: "maxRedeem",
2580
- stateMutability: "view",
2581
- inputs: [{
2582
- name: "owner",
2583
- type: "address"
2584
- }],
2585
- outputs: [{
2586
- name: "maxShares",
2587
- type: "uint256"
2588
- }]
2589
- }
2590
- ];
2591
2543
  /**
2592
- * Wrap an underlying xStock into its ERC4626 wrapper (`deposit`). Requires a
2593
- * prior ERC20 approval of the underlying to the wrapper address.
2544
+ * Check if a TokenId contains loan or credit legs (width=0).
2594
2545
  *
2595
- * @returns TxResult with hash and wait function
2546
+ * @param tokenId - The TokenId to check
2547
+ * @returns True if any leg has width=0 (loan or credit)
2596
2548
  */
2597
- async function wrapXstock(params) {
2598
- const { client, walletClient, account, wrapper, assets, receiver = account, txOverrides } = params;
2599
- return submitWrite({
2600
- client,
2601
- walletClient,
2602
- account,
2603
- address: wrapper,
2604
- abi: xstockWrapperAbi,
2605
- functionName: "deposit",
2606
- args: [assets, receiver],
2607
- txOverrides
2608
- });
2609
- }
2610
- /** Wrap and wait for confirmation. */
2611
- async function wrapXstockAndWait(params) {
2612
- const result = await wrapXstock(params);
2613
- return result.wait();
2549
+ function hasLoanOrCredit(tokenId) {
2550
+ const legs = decodeAllLegs(tokenId);
2551
+ return legs.some((leg) => leg.width === 0n);
2614
2552
  }
2553
+
2554
+ //#endregion
2555
+ //#region src/panoptic/v2/tokenId/deriveUniqueTokenId.ts
2556
+ const POOL_ID_MASK$2 = (1n << 64n) - 1n;
2557
+ const MAX_LEGS = TOKEN_ID_BITS.MAX_LEGS;
2558
+ const MAX_OPTION_RATIO = LEG_LIMITS.MAX_RATIO;
2559
+ const Q192$1 = 1n << 192n;
2615
2560
  /**
2616
- * Unwrap wrapper shares back into the underlying xStock (`redeem`). When
2617
- * `owner` defaults to (or equals) `account` the caller burns their own shares
2618
- * and no approval is needed. Passing a different `owner` redeems on its behalf
2619
- * and requires that owner to have granted the caller an ERC-4626 share
2620
- * allowance (`approve`), or the `redeem` call reverts.
2621
- *
2622
- * @returns TxResult with hash and wait function
2561
+ * Floor integer square root for bigints (Newton's method). Mirrors the isqrt
2562
+ * used in reads/collateralEstimate.ts (buildNeutralLeg).
2623
2563
  */
2624
- async function unwrapXstock(params) {
2625
- const { client, walletClient, account, wrapper, shares, receiver = account, owner = account, txOverrides } = params;
2626
- return submitWrite({
2627
- client,
2628
- walletClient,
2629
- account,
2630
- address: wrapper,
2631
- abi: xstockWrapperAbi,
2632
- functionName: "redeem",
2633
- args: [
2634
- shares,
2635
- receiver,
2636
- owner
2637
- ],
2638
- txOverrides
2639
- });
2640
- }
2641
- /** Unwrap and wait for confirmation. */
2642
- async function unwrapXstockAndWait(params) {
2643
- const result = await unwrapXstock(params);
2644
- return result.wait();
2564
+ function isqrt(value) {
2565
+ if (value < 0n) throw new PanopticError("isqrt of negative number");
2566
+ if (value < 2n) return value;
2567
+ let x = value;
2568
+ let y = x + 1n >> 1n;
2569
+ while (y < x) {
2570
+ x = y;
2571
+ y = x + value / x >> 1n;
2572
+ }
2573
+ return x;
2645
2574
  }
2575
+ const DEFAULT_TICK_SPACING = 200n;
2646
2576
  /**
2647
- * Preview the wrapper shares minted for a given amount of underlying xStock.
2577
+ * Target notional (in wei of the tokenType-side asset) for the appended
2578
+ * tiny credit leg. Chosen at 10 wei — small enough to be economically
2579
+ * meaningless against any real position size, large enough to safely
2580
+ * clear rounding on the SFPM's width-2 internal chunk math without any
2581
+ * risk of underflowing to zero (which would revert ChunkHasZeroLiquidity).
2648
2582
  */
2649
- async function previewWrap(params) {
2650
- const { client, wrapper, amount } = params;
2651
- return client.readContract({
2652
- address: wrapper,
2653
- abi: xstockWrapperAbi,
2654
- functionName: "previewDeposit",
2655
- args: [amount]
2656
- });
2657
- }
2583
+ const TINY_CREDIT_TARGET_NOTIONAL_WEI = 10n;
2658
2584
  /**
2659
- * Preview the underlying xStock returned for a given amount of wrapper shares.
2585
+ * Pick a signedStrike for the appended width=0 credit leg such that:
2586
+ * 1. positionSize · 1.0001^signedStrike ≈ TINY_CREDIT_TARGET_NOTIONAL_WEI
2587
+ * (~10 wei — economically meaningless but comfortably above any
2588
+ * SFPM width-2 chunk rounding);
2589
+ * 2. the leg's tick range (SFPM internally treats width=0 as width=2, so
2590
+ * the range is `strike ± tickSpacing`) stays strictly inside
2591
+ * `[MIN_POOL_TICK, MAX_POOL_TICK]` — otherwise the getSqrtRatioAtTick
2592
+ * call reverts with `InvalidTick`.
2593
+ *
2594
+ * Closed form: `1.0001^signedStrike = target/positionSize`, so
2595
+ * `sqrtKrawX96 = isqrt(target · 2^192 / positionSize)` and
2596
+ * `signedStrike = sqrtPriceX96ToTick(sqrtKrawX96)`. If the notional-optimal
2597
+ * strike falls outside the safe range, we clamp inward. Clamping raises the
2598
+ * notional but keeps it << position size for any reasonable strike.
2660
2599
  */
2661
- async function previewUnwrap(params) {
2662
- const { client, wrapper, amount } = params;
2663
- return client.readContract({
2664
- address: wrapper,
2665
- abi: xstockWrapperAbi,
2666
- functionName: "previewRedeem",
2667
- args: [amount]
2668
- });
2600
+ function computeTinyCreditSignedStrike(positionSize, tickSpacing) {
2601
+ if (positionSize <= 0n) throw new PanopticError("computeTinyCreditSignedStrike: positionSize must be > 0");
2602
+ const minSafe = MIN_TICK + tickSpacing + 1n;
2603
+ const maxSafe = MAX_TICK - tickSpacing - 1n;
2604
+ let signedStrike;
2605
+ try {
2606
+ const sqrtKrawX96 = isqrt(TINY_CREDIT_TARGET_NOTIONAL_WEI * Q192$1 / positionSize);
2607
+ signedStrike = sqrtPriceX96ToTick(sqrtKrawX96);
2608
+ } catch {
2609
+ signedStrike = minSafe;
2610
+ }
2611
+ if (signedStrike < minSafe) return minSafe;
2612
+ if (signedStrike > maxSafe) return maxSafe;
2613
+ return signedStrike;
2669
2614
  }
2670
2615
  /**
2671
- * Minimal canonical WETH9 surface used for ETH<->WETH wrapping. Unlike the
2672
- * ERC4626 xStock wrapper, WETH is a 1:1 wrapper: `deposit` is payable and wraps
2673
- * `msg.value`, `withdraw` unwraps an exact amount back to native ETH.
2674
- */
2675
- const wethWrapAbi = [{
2676
- type: "function",
2677
- name: "deposit",
2678
- stateMutability: "payable",
2679
- inputs: [],
2680
- outputs: []
2681
- }, {
2682
- type: "function",
2683
- name: "withdraw",
2684
- stateMutability: "nonpayable",
2685
- inputs: [{
2686
- name: "wad",
2687
- type: "uint256"
2688
- }],
2689
- outputs: []
2690
- }];
2691
- /**
2692
- * Wrap native ETH into WETH (`deposit` payable). No approval needed.
2616
+ * Assemble the tiny credit leg struct, picking a strike that avoids
2617
+ * colliding with any existing width=0 credit leg on the same (asset,
2618
+ * tokenType) pair.
2693
2619
  *
2694
- * @returns TxResult with hash and wait function
2620
+ * The encoded strike stored in the tokenId is
2621
+ * `asset === 0 ? signedStrike : -signedStrike` (mirrors
2622
+ * {@link buildNeutralLeg} in reads/collateralEstimate.ts).
2695
2623
  */
2696
- async function wrapEth(params) {
2697
- const { client, walletClient, account, weth, amount, txOverrides } = params;
2698
- return submitWrite({
2699
- client,
2700
- walletClient,
2701
- account,
2702
- address: weth,
2703
- abi: wethWrapAbi,
2704
- functionName: "deposit",
2705
- args: [],
2706
- value: amount,
2707
- txOverrides
2708
- });
2624
+ function pickUniqueTinyCreditLeg(baseTokenId, legIndex, positionSize, tickSpacing) {
2625
+ const asset = 1n;
2626
+ const tokenType = 0n;
2627
+ const existingLegs = decodeAllLegs(baseTokenId);
2628
+ const maxSafe = MAX_TICK - tickSpacing - 1n;
2629
+ let signedStrike = computeTinyCreditSignedStrike(positionSize, tickSpacing);
2630
+ while (signedStrike <= maxSafe) {
2631
+ const candidate = asset === 0n ? signedStrike : -signedStrike;
2632
+ let collides = false;
2633
+ for (const leg of existingLegs) if (leg.width === 0n && leg.isLong && leg.asset === asset && leg.tokenType === tokenType && leg.strike === candidate) {
2634
+ collides = true;
2635
+ break;
2636
+ }
2637
+ if (!collides) break;
2638
+ signedStrike += 1n;
2639
+ }
2640
+ if (signedStrike > maxSafe) throw new PanopticError("deriveUniqueTokenId: exhausted strike space picking a unique tiny credit leg");
2641
+ const encodedStrike = asset === 0n ? signedStrike : -signedStrike;
2642
+ return {
2643
+ index: legIndex,
2644
+ asset,
2645
+ tokenType,
2646
+ optionRatio: 1n,
2647
+ isLong: 1n,
2648
+ riskPartner: legIndex,
2649
+ strike: encodedStrike,
2650
+ width: 0n
2651
+ };
2709
2652
  }
2710
- /** Wrap ETH and wait for confirmation. */
2711
- async function wrapEthAndWait(params) {
2712
- const result = await wrapEth(params);
2713
- return result.wait();
2653
+ function appendTinyCreditLeg(baseTokenId, baseLegCount, positionSize, tickSpacing) {
2654
+ const newLeg = pickUniqueTinyCreditLeg(baseTokenId, baseLegCount, positionSize, tickSpacing);
2655
+ return addLegToTokenId(baseTokenId, newLeg);
2656
+ }
2657
+ function scaleRatios(baseTokenId, targetPositionSize) {
2658
+ const legs = decodeAllLegs(baseTokenId);
2659
+ const maxRatio = legs.reduce((m, leg) => leg.optionRatio > m ? leg.optionRatio : m, 0n);
2660
+ const N = MAX_OPTION_RATIO / maxRatio;
2661
+ if (N < 2n) throw new PanopticError("deriveUniqueTokenId: cannot derive a unique tokenId — all 4 leg slots used and optionRatios already near the 127 ceiling");
2662
+ const newPositionSize = (targetPositionSize + N - 1n) / N;
2663
+ const poolId = baseTokenId & POOL_ID_MASK$2;
2664
+ let out = poolId;
2665
+ for (const leg of legs) out = addLegToTokenId(out, {
2666
+ index: leg.index,
2667
+ asset: leg.asset,
2668
+ tokenType: leg.tokenType,
2669
+ optionRatio: leg.optionRatio * N,
2670
+ isLong: leg.isLong ? 1n : 0n,
2671
+ riskPartner: leg.riskPartner,
2672
+ strike: leg.strike,
2673
+ width: leg.width
2674
+ });
2675
+ return {
2676
+ newTokenId: out,
2677
+ newPositionSize,
2678
+ effectivePositionSize: newPositionSize * N,
2679
+ strategy: "ratio-scale"
2680
+ };
2714
2681
  }
2715
2682
  /**
2716
- * Unwrap WETH back into native ETH (`withdraw`). Burns the caller's own WETH —
2717
- * no approval needed.
2683
+ * Derive a tokenId unique from `baseTokenId` for a partial reduction.
2718
2684
  *
2719
- * @returns TxResult with hash and wait function
2685
+ * Prefers a tiny-credit-leg extension (arbitrary new size). Falls back to
2686
+ * optionRatio scaling only when the base tokenId already occupies all 4 leg
2687
+ * slots.
2720
2688
  */
2721
- async function unwrapWeth(params) {
2722
- const { client, walletClient, account, weth, amount, txOverrides } = params;
2723
- return submitWrite({
2724
- client,
2725
- walletClient,
2726
- account,
2727
- address: weth,
2728
- abi: wethWrapAbi,
2729
- functionName: "withdraw",
2730
- args: [amount],
2731
- txOverrides
2732
- });
2689
+ function deriveUniqueTokenId(params) {
2690
+ const { baseTokenId, targetPositionSize, tickSpacing = DEFAULT_TICK_SPACING } = params;
2691
+ if (targetPositionSize <= 0n) throw new PanopticError("deriveUniqueTokenId: targetPositionSize must be > 0");
2692
+ const legCount = countLegs(baseTokenId);
2693
+ if (legCount === 0n) throw new PanopticError("deriveUniqueTokenId: baseTokenId has no legs");
2694
+ if (legCount < MAX_LEGS) {
2695
+ const newTokenId = appendTinyCreditLeg(baseTokenId, legCount, targetPositionSize, tickSpacing);
2696
+ return {
2697
+ newTokenId,
2698
+ newPositionSize: targetPositionSize,
2699
+ effectivePositionSize: targetPositionSize,
2700
+ strategy: "tiny-credit"
2701
+ };
2702
+ }
2703
+ return scaleRatios(baseTokenId, targetPositionSize);
2733
2704
  }
2734
- /** Unwrap WETH and wait for confirmation. */
2735
- async function unwrapWethAndWait(params) {
2736
- const result = await unwrapWeth(params);
2737
- return result.wait();
2705
+ /**
2706
+ * Re-export decoded leg count so callers can gate UI on the strategy that
2707
+ * would be chosen (e.g. show a divisibility hint on ratio-scale positions).
2708
+ */
2709
+ function planDeriveStrategy(baseTokenId) {
2710
+ return countLegs(baseTokenId) < MAX_LEGS ? "tiny-credit" : "ratio-scale";
2738
2711
  }
2739
2712
 
2740
2713
  //#endregion
2741
- //#region src/panoptic/v2/tokenId/decode.ts
2742
- /**
2743
- * Convert a DecodedLeg to TokenIdLeg format.
2744
- *
2745
- * @param leg - The decoded leg from encoding utils
2746
- * @param tickSpacing - The tick spacing for calculating tick bounds
2747
- * @returns The TokenIdLeg format
2748
- */
2749
- function convertToTokenIdLeg(leg, tickSpacing) {
2750
- const halfWidth = leg.width * tickSpacing / 2n;
2751
- const tickLower = leg.strike - halfWidth;
2752
- const tickUpper = leg.strike + halfWidth;
2714
+ //#region src/panoptic/v2/tokenId/generateOverlapping.ts
2715
+ const MAX_UINT64 = (1n << 64n) - 1n;
2716
+ const MAX_UINT128$2 = (1n << 128n) - 1n;
2717
+ const Q192 = 1n << 192n;
2718
+ const MAX_TOKEN_DECIMALS = 255n;
2719
+ const MAX_DECIMAL_EXPONENT = 512n;
2720
+ function invalid$1(message) {
2721
+ throw new PanopticValidationError(`generateOverlappingTokenIds: ${message}`);
2722
+ }
2723
+ function pow10(exponent) {
2724
+ return 10n ** exponent;
2725
+ }
2726
+ function parsePriceSpacing(value, quoteDecimals) {
2727
+ const match = /^\+?(?:(\d+)(?:\.(\d*))?|\.(\d+))(?:e([+-]?\d+))?$/i.exec(value.trim());
2728
+ if (match === null) invalid$1("strikePriceSpacing must be a positive decimal string");
2729
+ const integerPart = match[1] ?? "0";
2730
+ const fractionalPart = match[2] ?? match[3] ?? "";
2731
+ const exponent = BigInt(match[4] ?? "0");
2732
+ if (exponent < -MAX_DECIMAL_EXPONENT || exponent > MAX_DECIMAL_EXPONENT) invalid$1("strikePriceSpacing exponent is too large");
2733
+ let units = BigInt(`${integerPart}${fractionalPart}`);
2734
+ let scale = BigInt(fractionalPart.length) - exponent;
2735
+ if (scale < 0n) {
2736
+ units *= pow10(-scale);
2737
+ scale = 0n;
2738
+ }
2739
+ while (scale > 0n && units % 10n === 0n) {
2740
+ units /= 10n;
2741
+ scale -= 1n;
2742
+ }
2743
+ if (units <= 0n) invalid$1("strikePriceSpacing must be positive");
2744
+ if (scale > quoteDecimals) invalid$1("strikePriceSpacing exceeds the quote token decimal precision");
2753
2745
  return {
2754
- index: leg.index,
2755
- asset: leg.asset,
2756
- optionRatio: leg.optionRatio,
2757
- isLong: leg.isLong,
2758
- tokenType: leg.tokenType,
2759
- riskPartner: leg.riskPartner,
2760
- strike: leg.strike,
2761
- width: leg.width,
2762
- tickLower,
2763
- tickUpper
2746
+ units,
2747
+ scale,
2748
+ denominator: pow10(scale)
2764
2749
  };
2765
2750
  }
2766
- /**
2767
- * Decode a TokenId into its component parts.
2768
- *
2769
- * @param tokenId - The TokenId to decode
2770
- * @returns The decoded TokenId data
2771
- *
2772
- * @example
2773
- * ```typescript
2774
- * const decoded = decodeTokenId(tokenId)
2775
- * console.log(decoded.legs) // Array of legs
2776
- * console.log(decoded.tickSpacing) // Tick spacing
2777
- * ```
2778
- */
2779
- function decodeTokenId(tokenId) {
2780
- const poolId = decodePoolId(tokenId);
2781
- const vegoid = decodeVegoid(tokenId);
2782
- const tickSpacing = decodeTickSpacing(tokenId);
2783
- const rawLegs = decodeAllLegs(tokenId);
2784
- const numLegs = countLegs(tokenId);
2785
- const legs = rawLegs.map((leg) => convertToTokenIdLeg(leg, tickSpacing));
2751
+ function formatGridPrice(multiple, spacing) {
2752
+ const scaledPrice = multiple * spacing.units;
2753
+ if (spacing.scale === 0n) return scaledPrice.toString();
2754
+ const scale = Number(spacing.scale);
2755
+ const digits = scaledPrice.toString().padStart(scale + 1, "0");
2756
+ const decimalIndex = digits.length - scale;
2757
+ return `${digits.slice(0, decimalIndex)}.${digits.slice(decimalIndex)}`;
2758
+ }
2759
+ function quotePriceAtTick(tick, asset, assetDecimals, quoteDecimals) {
2760
+ const orientedTick = asset === 0n ? tick : -tick;
2761
+ const sqrtPriceX96 = tickToSqrtPriceX96(orientedTick);
2762
+ let numerator = sqrtPriceX96 * sqrtPriceX96;
2763
+ let denominator = Q192;
2764
+ const decimalDifference = assetDecimals - quoteDecimals;
2765
+ if (decimalDifference > 0n) numerator *= pow10(decimalDifference);
2766
+ else if (decimalDifference < 0n) denominator *= pow10(-decimalDifference);
2786
2767
  return {
2787
- tokenId,
2788
- poolId,
2789
- vegoid,
2790
- tickSpacing,
2791
- legs,
2792
- legCount: numLegs
2768
+ numerator,
2769
+ denominator
2793
2770
  };
2794
2771
  }
2795
- /**
2796
- * Validate that a TokenId has the expected pool ID.
2797
- *
2798
- * @param tokenId - The TokenId to validate
2799
- * @param expectedPoolId - The expected pool ID
2800
- * @returns True if the pool IDs match
2801
- */
2802
- function validatePoolId(tokenId, expectedPoolId) {
2803
- const actualPoolId = tokenId & (1n << 64n) - 1n;
2804
- return actualPoolId === expectedPoolId;
2772
+ function isPriceGridStrike(strike, params, spacing, tickSpacing) {
2773
+ const price = quotePriceAtTick(strike, params.asset, params.assetDecimals, params.quoteDecimals);
2774
+ const floorMultiple = price.numerator * spacing.denominator / (price.denominator * spacing.units);
2775
+ for (const multiple of [floorMultiple, floorMultiple + 1n]) {
2776
+ if (multiple <= 0n) continue;
2777
+ const orientedTick = priceToTick(formatGridPrice(multiple, spacing), params.assetDecimals, params.quoteDecimals);
2778
+ const poolTick = params.asset === 0n ? orientedTick : -orientedTick;
2779
+ if (roundToTickSpacing(poolTick, tickSpacing) === strike) return true;
2780
+ }
2781
+ return false;
2782
+ }
2783
+ function ceilDiv$1(value, divisor) {
2784
+ const quotient = value / divisor;
2785
+ const remainder = value % divisor;
2786
+ return remainder > 0n ? quotient + 1n : quotient;
2787
+ }
2788
+ function floorDiv(value, divisor) {
2789
+ const quotient = value / divisor;
2790
+ const remainder = value % divisor;
2791
+ return remainder < 0n ? quotient - 1n : quotient;
2792
+ }
2793
+ function resolveLegs(legs) {
2794
+ if (legs.length === 0 || legs.length > Number(TOKEN_ID_BITS.MAX_LEGS)) invalid$1("legs must contain between 1 and 4 entries");
2795
+ const resolved = legs.map((leg, index) => {
2796
+ if (leg.optionType !== "call" && leg.optionType !== "put") invalid$1(`leg ${index} has an invalid optionType`);
2797
+ if (typeof leg.isLong !== "boolean") invalid$1(`leg ${index} has an invalid isLong value`);
2798
+ if (leg.optionRatio < 1n || leg.optionRatio > LEG_LIMITS.MAX_RATIO) invalid$1(`leg ${index} optionRatio must be between 1 and ${LEG_LIMITS.MAX_RATIO}`);
2799
+ const riskPartner = leg.riskPartner ?? BigInt(index);
2800
+ if (riskPartner < 0n || riskPartner >= BigInt(legs.length)) invalid$1(`leg ${index} references an inactive risk partner`);
2801
+ return {
2802
+ ...leg,
2803
+ riskPartner
2804
+ };
2805
+ });
2806
+ for (const [index, leg] of resolved.entries()) {
2807
+ if (leg.riskPartner === BigInt(index)) continue;
2808
+ const partner = resolved[Number(leg.riskPartner)];
2809
+ if (partner === void 0 || partner.riskPartner !== BigInt(index)) invalid$1(`leg ${index} has a non-mutual risk partner`);
2810
+ }
2811
+ return resolved;
2812
+ }
2813
+ function getCandidateStrikes(params, spacing, tickSpacing, halfWidth) {
2814
+ const lower = MIN_TICK + halfWidth > params.currentTick - halfWidth + 1n ? MIN_TICK + halfWidth : params.currentTick - halfWidth + 1n;
2815
+ const upper = MAX_TICK - halfWidth < params.currentTick + halfWidth - 1n ? MAX_TICK - halfWidth : params.currentTick + halfWidth - 1n;
2816
+ const firstStrike = ceilDiv$1(lower, tickSpacing) * tickSpacing;
2817
+ const lastStrike = floorDiv(upper, tickSpacing) * tickSpacing;
2818
+ const strikes = [];
2819
+ for (let strike = firstStrike; strike <= lastStrike; strike += tickSpacing) if (isPriceGridStrike(strike, params, spacing, tickSpacing)) strikes.push(strike);
2820
+ if (params.asset === 1n) strikes.reverse();
2821
+ return strikes;
2822
+ }
2823
+ function buildTokenId$1(poolId, strike, width, asset, legs) {
2824
+ const builder = createTokenIdBuilder(poolId);
2825
+ for (const leg of legs) {
2826
+ const config = {
2827
+ strike,
2828
+ width,
2829
+ optionRatio: leg.optionRatio,
2830
+ isLong: leg.isLong,
2831
+ riskPartner: leg.riskPartner,
2832
+ asset
2833
+ };
2834
+ if (leg.optionType === "call") builder.addCall(config);
2835
+ else builder.addPut(config);
2836
+ }
2837
+ return builder.build();
2805
2838
  }
2806
- /**
2807
- * Check if a TokenId represents a long position (any leg is long).
2808
- *
2809
- * @param tokenId - The TokenId to check
2810
- * @returns True if any leg is long
2811
- */
2812
- function hasLongLeg(tokenId) {
2813
- const legs = decodeAllLegs(tokenId);
2814
- return legs.some((leg) => leg.isLong);
2839
+ function dividePositionSize$1(positionSize, count) {
2840
+ if (positionSize < count) invalid$1(`positionSize ${positionSize} is too small for ${count} nonzero positions`);
2841
+ const quotient = positionSize / count;
2842
+ const remainder = positionSize % count;
2843
+ const sizes = [];
2844
+ for (let index = 0n; index < count; index += 1n) sizes.push(quotient + (index < remainder ? 1n : 0n));
2845
+ return sizes;
2815
2846
  }
2816
2847
  /**
2817
- * Check if a TokenId represents a short-only position.
2818
- *
2819
- * @param tokenId - The TokenId to check
2820
- * @returns True if all legs are short
2848
+ * Generate a fixed quote-price lattice of co-strike TokenIds whose liquidity
2849
+ * ranges strictly contain the current tick. Returned arrays are ordered by
2850
+ * ascending quote strike price and are ready for `dispatch()`.
2821
2851
  */
2822
- function isShortOnly(tokenId) {
2823
- const legs = decodeAllLegs(tokenId);
2824
- return legs.length > 0 && legs.every((leg) => !leg.isLong);
2852
+ function generateOverlappingTokenIds(params) {
2853
+ if (params.poolId < 0n || params.poolId > MAX_UINT64) invalid$1("poolId must fit in uint64");
2854
+ if (params.currentTick < MIN_TICK || params.currentTick > MAX_TICK) invalid$1(`currentTick must be between ${MIN_TICK} and ${MAX_TICK}`);
2855
+ if (params.asset !== 0n && params.asset !== 1n) invalid$1("asset must be 0 or 1");
2856
+ if (params.assetDecimals < 0n || params.assetDecimals > MAX_TOKEN_DECIMALS || params.quoteDecimals < 0n || params.quoteDecimals > MAX_TOKEN_DECIMALS) invalid$1("assetDecimals and quoteDecimals must be between 0 and 255");
2857
+ if (params.positionSize <= 0n || params.positionSize > MAX_UINT128$2) invalid$1("positionSize must be between 1 and uint128.max");
2858
+ const tickSpacing = decodeTickSpacing$1(params.poolId);
2859
+ if (tickSpacing <= 0n) invalid$1("poolId tick spacing must be positive");
2860
+ const standardTickWidth = STANDARD_TICK_WIDTHS[params.timescale];
2861
+ if (standardTickWidth === void 0) invalid$1(`unknown timescale ${String(params.timescale)}`);
2862
+ const width = (standardTickWidth + tickSpacing - 1n) / tickSpacing;
2863
+ if (width <= 0n || width > LEG_LIMITS.MAX_WIDTH) invalid$1(`timescale width does not fit in the TokenId width field for tick spacing ${tickSpacing}`);
2864
+ const halfWidth = width * tickSpacing / 2n;
2865
+ if (halfWidth <= 0n) invalid$1("timescale width must span at least two ticks");
2866
+ const spacing = parsePriceSpacing(params.strikePriceSpacing, params.quoteDecimals);
2867
+ const legs = resolveLegs(params.legs);
2868
+ const strikes = getCandidateStrikes(params, spacing, tickSpacing, halfWidth);
2869
+ if (strikes.length === 0) invalid$1("no price-grid strikes overlap the current tick");
2870
+ return {
2871
+ positionIdList: strikes.map((strike) => buildTokenId$1(params.poolId, strike, width, params.asset, legs)),
2872
+ positionSizes: dividePositionSize$1(params.positionSize, BigInt(strikes.length))
2873
+ };
2825
2874
  }
2826
- /**
2827
- * Check if a TokenId represents a spread (legs with different risk partners).
2828
- *
2829
- * @param tokenId - The TokenId to check
2830
- * @returns True if any leg has a different risk partner
2831
- */
2832
- function isSpread(tokenId) {
2875
+
2876
+ //#endregion
2877
+ //#region src/panoptic/v2/tokenId/split.ts
2878
+ const POOL_ID_MASK$1 = (1n << TOKEN_ID_BITS.POOL_ID_SIZE) - 1n;
2879
+ const MAX_UINT128$1 = (1n << 128n) - 1n;
2880
+ const MAX_UINT256 = (1n << 256n) - 1n;
2881
+ function invalid(message) {
2882
+ throw new PanopticValidationError(`splitTokenIdByTimescale: ${message}`);
2883
+ }
2884
+ function validateInput(tokenId, positionSize) {
2885
+ if (tokenId < 0n || tokenId > MAX_UINT256) invalid("tokenId must fit in uint256");
2886
+ if (positionSize <= 0n || positionSize > MAX_UINT128$1) invalid("positionSize must be between 1 and uint128.max");
2833
2887
  const legs = decodeAllLegs(tokenId);
2834
- return legs.some((leg) => leg.riskPartner !== leg.index);
2888
+ if (legs.length === 0) invalid("tokenId must contain at least one active leg");
2889
+ for (const [index, leg] of legs.entries()) if (leg.index !== BigInt(index)) invalid("active legs must be contiguous from index 0");
2890
+ const firstUnusedBit = TOKEN_ID_BITS.POOL_ID_SIZE + BigInt(legs.length) * TOKEN_ID_BITS.LEG_SIZE;
2891
+ if (tokenId >> firstUnusedBit !== 0n) invalid("inactive leg slots must be empty");
2892
+ const byIndex = new Map(legs.map((leg) => [leg.index, leg]));
2893
+ for (const leg of legs) {
2894
+ const partner = byIndex.get(leg.riskPartner);
2895
+ if (partner === void 0) invalid(`leg ${leg.index} references an inactive risk partner`);
2896
+ if (partner.index !== leg.index && partner.riskPartner !== leg.index) invalid(`leg ${leg.index} has a non-mutual risk partner`);
2897
+ }
2898
+ return legs;
2835
2899
  }
2836
- /**
2837
- * Get the asset index for a TokenId (from the first leg).
2838
- *
2839
- * @param tokenId - The TokenId
2840
- * @returns The asset index (0 or 1), or undefined if no legs
2841
- */
2842
- function getAssetIndex(tokenId) {
2843
- const legs = decodeAllLegs(tokenId);
2844
- if (legs.length === 0) return void 0;
2845
- return legs[0].asset;
2900
+ function isCall(leg) {
2901
+ return leg.tokenType === leg.asset;
2902
+ }
2903
+ function isEconomicStraddlePair(left, right) {
2904
+ return isCall(left) !== isCall(right) && left.asset === right.asset && left.optionRatio === right.optionRatio && left.isLong === right.isLong && left.strike === right.strike && left.width === right.width;
2905
+ }
2906
+ /** Preserve mutual pairs, then pair self-partnered economic straddles in source order. */
2907
+ function groupOptionLegs(optionLegs) {
2908
+ const byIndex = new Map(optionLegs.map((leg) => [leg.index, leg]));
2909
+ const consumed = new Set();
2910
+ const groups = [];
2911
+ for (const leg of optionLegs) {
2912
+ if (consumed.has(leg.index)) continue;
2913
+ if (leg.riskPartner !== leg.index) {
2914
+ const riskPartner = byIndex.get(leg.riskPartner);
2915
+ if (riskPartner === void 0) invalid(`mutual risk pair containing leg ${leg.index} cannot be separated`);
2916
+ consumed.add(leg.index);
2917
+ consumed.add(riskPartner.index);
2918
+ groups.push([leg, riskPartner]);
2919
+ continue;
2920
+ }
2921
+ const partner = optionLegs.find((candidate) => candidate.index > leg.index && !consumed.has(candidate.index) && candidate.riskPartner === candidate.index && isEconomicStraddlePair(leg, candidate));
2922
+ consumed.add(leg.index);
2923
+ if (partner === void 0) {
2924
+ groups.push([leg]);
2925
+ continue;
2926
+ }
2927
+ consumed.add(partner.index);
2928
+ groups.push([leg, partner]);
2929
+ }
2930
+ return groups;
2931
+ }
2932
+ function remapRiskPartner(leg, indexBySource) {
2933
+ const riskPartner = indexBySource.get(leg.riskPartner);
2934
+ if (riskPartner === void 0) invalid(`risk partner for leg ${leg.index} is missing from its output group`);
2935
+ return riskPartner;
2936
+ }
2937
+ function buildTokenId(poolId, legs, transform) {
2938
+ const indexBySource = new Map(legs.map((leg, index) => [leg.index, BigInt(index)]));
2939
+ let output = poolId;
2940
+ for (const [index, leg] of legs.entries()) {
2941
+ const newIndex = BigInt(index);
2942
+ const { strike, width } = transform(leg);
2943
+ if (strike < LEG_LIMITS.MIN_STRIKE || strike > LEG_LIMITS.MAX_STRIKE) invalid(`generated strike ${strike} does not fit in int24`);
2944
+ if (width < 0n || width > LEG_LIMITS.MAX_WIDTH) invalid(`generated width ${width} does not fit in the TokenId width field`);
2945
+ output = addLegToTokenId(output, {
2946
+ index: newIndex,
2947
+ asset: leg.asset,
2948
+ optionRatio: leg.optionRatio,
2949
+ isLong: leg.isLong ? 1n : 0n,
2950
+ tokenType: leg.tokenType,
2951
+ riskPartner: remapRiskPartner(leg, indexBySource),
2952
+ strike,
2953
+ width
2954
+ });
2955
+ }
2956
+ return output;
2957
+ }
2958
+ function childWidths(sourceWidth, targetWidth) {
2959
+ if (sourceWidth <= targetWidth) return [sourceWidth];
2960
+ const fullWidthCount = sourceWidth / targetWidth;
2961
+ const remainder = sourceWidth % targetWidth;
2962
+ const widths = [];
2963
+ for (let remaining = fullWidthCount; remaining > 0n; remaining -= 1n) widths.push(targetWidth);
2964
+ if (remainder > 0n) widths.push(remainder);
2965
+ return widths;
2966
+ }
2967
+ function dividePositionSize(positionSize, count) {
2968
+ const countBigInt = BigInt(count);
2969
+ if (positionSize < countBigInt) invalid(`positionSize ${positionSize} is too small for ${count} nonzero child positions`);
2970
+ const quotient = positionSize / countBigInt;
2971
+ const remainder = positionSize % countBigInt;
2972
+ return Array.from({ length: count }, (_, index) => quotient + (BigInt(index) < remainder ? 1n : 0n));
2973
+ }
2974
+ function splitGroup(poolId, group, tickSpacing, targetWidth, positionSize) {
2975
+ const sourceWidth = group[0]?.width;
2976
+ if (sourceWidth === void 0 || sourceWidth === 0n) invalid("option group must have nonzero width");
2977
+ if (group.some((leg) => leg.width !== sourceWidth)) invalid("all legs in an option group must have the same width");
2978
+ const widths = childWidths(sourceWidth, targetWidth);
2979
+ const positionSizes = dividePositionSize(positionSize, widths.length);
2980
+ let consumedWidth = 0n;
2981
+ const positionIdList = widths.map((width) => {
2982
+ const offset = consumedWidth;
2983
+ consumedWidth += width;
2984
+ return buildTokenId(poolId, group, (leg) => {
2985
+ const sourceLower = leg.strike - sourceWidth * tickSpacing / 2n;
2986
+ const childLower = sourceLower + offset * tickSpacing;
2987
+ const strike = childLower + width * tickSpacing / 2n;
2988
+ return {
2989
+ strike,
2990
+ width
2991
+ };
2992
+ });
2993
+ });
2994
+ return {
2995
+ positionIdList,
2996
+ positionSizes
2997
+ };
2846
2998
  }
2847
2999
  /**
2848
- * Check if a leg is a loan (width=0 and isLong=false).
2849
- *
2850
- * Loans borrow liquidity from the pool at a specific strike price.
3000
+ * Split every standalone option, economic straddle, or mutual risk pair in a
3001
+ * TokenId into a shorter standard timescale. Width-zero credit/loan legs are
3002
+ * emitted together as one final position. Returned arrays are parallel and
3003
+ * ready for `dispatch()`.
2851
3004
  *
2852
- * @param leg - The decoded leg to check
2853
- * @returns True if the leg is a loan
3005
+ * Each option group divides `positionSize` independently across its children.
3006
+ * Integer remainder units are assigned to the lower children first.
2854
3007
  */
2855
- function isLoanLeg(leg) {
2856
- return leg.width === 0n && !leg.isLong;
3008
+ function splitTokenIdByTimescale(tokenId, positionSize, targetTimescale) {
3009
+ const legs = validateInput(tokenId, positionSize);
3010
+ const tickSpacing = decodeTickSpacing$1(tokenId);
3011
+ if (tickSpacing <= 0n) invalid("tokenId tick spacing must be positive");
3012
+ const targetTickWidth = STANDARD_TICK_WIDTHS[targetTimescale];
3013
+ if (targetTickWidth === void 0) invalid(`unknown target timescale ${String(targetTimescale)}`);
3014
+ const targetWidth = (targetTickWidth + tickSpacing - 1n) / tickSpacing;
3015
+ const poolId = tokenId & POOL_ID_MASK$1;
3016
+ const optionLegs = legs.filter((leg) => leg.width > 0n);
3017
+ const fundingLegs = legs.filter((leg) => leg.width === 0n);
3018
+ const positionIdList = [];
3019
+ const positionSizes = [];
3020
+ for (const group of groupOptionLegs(optionLegs)) {
3021
+ const split = splitGroup(poolId, group, tickSpacing, targetWidth, positionSize);
3022
+ positionIdList.push(...split.positionIdList);
3023
+ positionSizes.push(...split.positionSizes);
3024
+ }
3025
+ if (fundingLegs.length > 0) {
3026
+ positionIdList.push(buildTokenId(poolId, fundingLegs, (leg) => ({
3027
+ strike: leg.strike,
3028
+ width: leg.width
3029
+ })));
3030
+ positionSizes.push(positionSize);
3031
+ }
3032
+ return {
3033
+ positionIdList,
3034
+ positionSizes
3035
+ };
2857
3036
  }
2858
- /**
2859
- * Check if a leg is a credit (width=0 and isLong=true).
2860
- *
2861
- * Credits lend liquidity to the pool at a specific strike price.
2862
- *
2863
- * @param leg - The decoded leg to check
2864
- * @returns True if the leg is a credit
2865
- */
2866
- function isCreditLeg(leg) {
2867
- return leg.width === 0n && leg.isLong;
3037
+
3038
+ //#endregion
3039
+ //#region src/panoptic/v2/writes/protectedSettle.ts
3040
+ const POOL_ID_MASK = (1n << 64n) - 1n;
3041
+ const Q96 = 1n << 96n;
3042
+ const MAX_UINT128 = (1n << 128n) - 1n;
3043
+ const SETTLE_LIMITS = [
3044
+ -887272n,
3045
+ 887272n,
3046
+ 0n
3047
+ ];
3048
+ function ceilDiv(numerator, denominator) {
3049
+ return (numerator + denominator - 1n) / denominator;
3050
+ }
3051
+ /** Smallest position size that creates non-zero liquidity on every poke leg. */
3052
+ function minimumPokeSize(legs, tickSpacing) {
3053
+ let requiredSize = 1n;
3054
+ for (const leg of legs) {
3055
+ const widthInTicks = leg.width * tickSpacing;
3056
+ const lowerTick = leg.strike - widthInTicks / 2n;
3057
+ const upperTick = leg.strike + (widthInTicks + 1n) / 2n;
3058
+ const sqrtLower = tickToSqrtPriceX96(lowerTick);
3059
+ const sqrtUpper = tickToSqrtPriceX96(upperTick);
3060
+ const delta = sqrtUpper - sqrtLower;
3061
+ const liquidityFactor = leg.asset === 0n ? sqrtLower * sqrtUpper / Q96 : Q96;
3062
+ if (liquidityFactor === 0n) return void 0;
3063
+ const requiredAmount = ceilDiv(delta, liquidityFactor);
3064
+ const legSize = ceilDiv(requiredAmount, leg.optionRatio);
3065
+ if (legSize > requiredSize) requiredSize = legSize;
3066
+ }
3067
+ if (requiredSize > MAX_UINT128) return void 0;
3068
+ return requiredSize;
3069
+ }
3070
+ function encodePokeToken(sourceTokenId, legs, firstRatio, assetMask) {
3071
+ let tokenId = sourceTokenId & POOL_ID_MASK;
3072
+ legs.forEach((leg, index) => {
3073
+ const encodedIndex = BigInt(index);
3074
+ tokenId = addLegToTokenId(tokenId, {
3075
+ index: encodedIndex,
3076
+ asset: assetMask >> encodedIndex & 1n,
3077
+ optionRatio: index === 0 ? firstRatio : leg.optionRatio,
3078
+ isLong: 0n,
3079
+ tokenType: leg.tokenType,
3080
+ riskPartner: encodedIndex,
3081
+ strike: leg.strike,
3082
+ width: leg.width
3083
+ });
3084
+ });
3085
+ return tokenId;
3086
+ }
3087
+ function buildUniquePoke(sourceTokenId, occupied) {
3088
+ const seenChunks = new Set();
3089
+ const shortChunkLegs = decodeAllLegs(sourceTokenId).filter((leg) => {
3090
+ if (leg.isLong || leg.width === 0n) return false;
3091
+ const chunkKey = `${leg.strike}:${leg.width}:${leg.tokenType}`;
3092
+ if (seenChunks.has(chunkKey)) return false;
3093
+ seenChunks.add(chunkKey);
3094
+ return true;
3095
+ });
3096
+ if (shortChunkLegs.length === 0) return void 0;
3097
+ const originalRatio = shortChunkLegs[0].optionRatio;
3098
+ for (let ratioOffset = 0n; ratioOffset < 127n; ratioOffset += 1n) {
3099
+ const firstRatio = (originalRatio - 1n + ratioOffset) % 127n + 1n;
3100
+ const assetVariants = 1n << BigInt(shortChunkLegs.length);
3101
+ for (let assetMask = 0n; assetMask < assetVariants; assetMask += 1n) {
3102
+ const tokenId = encodePokeToken(sourceTokenId, shortChunkLegs, firstRatio, assetMask);
3103
+ if (!occupied.has(tokenId)) {
3104
+ const size = minimumPokeSize(decodeAllLegs(tokenId), decodeTickSpacing$1(sourceTokenId));
3105
+ if (size !== void 0) return {
3106
+ tokenId,
3107
+ size
3108
+ };
3109
+ }
3110
+ }
3111
+ }
3112
+ throw new PanopticError("Unable to derive a collision-free protected settlement poke");
2868
3113
  }
2869
3114
  /**
2870
- * Check if a TokenId is a pure loan (all legs are loans).
2871
- *
2872
- * @param tokenId - The TokenId to check
2873
- * @returns True if all legs are loans
3115
+ * Build `[poke, settle, poke]` for every target containing a width>0 short.
3116
+ * Pure-long and width-zero positions are settled directly.
2874
3117
  */
2875
- function isLoan(tokenId) {
2876
- const legs = decodeAllLegs(tokenId);
2877
- return legs.length > 0 && legs.every(isLoanLeg);
3118
+ function buildProtectedSettlePlan(params) {
3119
+ const { positionIdList, finalPositionIdList, positionSizes, usePremiaAsCollateral = false, builderCode = 0n } = params;
3120
+ if (positionIdList.length !== positionSizes.length) throw new PanopticError("Protected settlement: positionSizes length must match positionIdList");
3121
+ const occupied = new Set(finalPositionIdList);
3122
+ const actionIds = [];
3123
+ const actionSizes = [];
3124
+ const actionLimits = [];
3125
+ const collectionIds = [];
3126
+ const collectionSizes = [];
3127
+ const collectionLimits = [];
3128
+ const pokingTokenIds = [];
3129
+ positionIdList.forEach((tokenId, index) => {
3130
+ const poke = buildUniquePoke(tokenId, occupied);
3131
+ if (poke === void 0) {
3132
+ actionIds.push(tokenId);
3133
+ actionSizes.push(positionSizes[index]);
3134
+ actionLimits.push(SETTLE_LIMITS);
3135
+ return;
3136
+ }
3137
+ occupied.add(poke.tokenId);
3138
+ pokingTokenIds.push(poke.tokenId);
3139
+ actionIds.push(poke.tokenId, tokenId, poke.tokenId);
3140
+ actionSizes.push(poke.size, positionSizes[index], 0n);
3141
+ actionLimits.push(SETTLE_LIMITS, SETTLE_LIMITS, SETTLE_LIMITS);
3142
+ collectionIds.push(poke.tokenId, poke.tokenId);
3143
+ collectionSizes.push(poke.size, 0n);
3144
+ collectionLimits.push(SETTLE_LIMITS, SETTLE_LIMITS);
3145
+ });
3146
+ const base = {
3147
+ finalPositionIdList: [...finalPositionIdList],
3148
+ usePremiaAsCollateral,
3149
+ builderCode
3150
+ };
3151
+ return {
3152
+ dispatch: {
3153
+ ...base,
3154
+ positionIdList: actionIds,
3155
+ positionSizes: actionSizes,
3156
+ tickAndSpreadLimits: actionLimits
3157
+ },
3158
+ collectionDispatch: collectionIds.length === 0 ? void 0 : {
3159
+ ...base,
3160
+ positionIdList: collectionIds,
3161
+ positionSizes: collectionSizes,
3162
+ tickAndSpreadLimits: collectionLimits
3163
+ },
3164
+ pokingTokenIds
3165
+ };
2878
3166
  }
2879
3167
  /**
2880
- * Check if a TokenId is a pure credit (all legs are credits).
3168
+ * Build the atomic self-settlement dispatch that temporarily pokes each
3169
+ * affected short chunk before settling its source position.
2881
3170
  *
2882
- * @param tokenId - The TokenId to check
2883
- * @returns True if all legs are credits
3171
+ * @param params - Positions, current sizes, and complete held-position list.
3172
+ * @returns A dispatch intent ready for simulation or submission.
2884
3173
  */
2885
- function isCredit(tokenId) {
2886
- const legs = decodeAllLegs(tokenId);
2887
- return legs.length > 0 && legs.every(isCreditLeg);
3174
+ function buildProtectedSettleDispatch(params) {
3175
+ return buildProtectedSettlePlan(params).dispatch;
2888
3176
  }
3177
+
3178
+ //#endregion
3179
+ //#region src/panoptic/v2/reads/positionSizes.ts
3180
+ const BIT_MASK_128$1 = (1n << 128n) - 1n;
2889
3181
  /**
2890
- * Check if a TokenId contains loan or credit legs (width=0).
2891
- *
2892
- * @param tokenId - The TokenId to check
2893
- * @returns True if any leg has width=0 (loan or credit)
3182
+ * Returns the current stored positionSize for each tokenId, in the same order
3183
+ * as the input `positionIdList`. Reverts (via the contract) if any tokenId is
3184
+ * not held by `account`.
2894
3185
  */
2895
- function hasLoanOrCredit(tokenId) {
2896
- const legs = decodeAllLegs(tokenId);
2897
- return legs.some((leg) => leg.width === 0n);
3186
+ async function getCurrentPositionSizes(params) {
3187
+ const { client, poolAddress, account, positionIdList, blockNumber } = params;
3188
+ if (positionIdList.length === 0) return [];
3189
+ const [, , positionBalances] = await client.readContract({
3190
+ address: poolAddress,
3191
+ abi: panopticPoolV2Abi,
3192
+ functionName: "getFullPositionsData",
3193
+ args: [
3194
+ account,
3195
+ false,
3196
+ positionIdList
3197
+ ],
3198
+ blockNumber
3199
+ });
3200
+ return positionBalances.map((packed) => packed & BIT_MASK_128$1);
2898
3201
  }
2899
3202
 
2900
3203
  //#endregion
2901
- //#region src/panoptic/v2/simulations/tokenFlow.ts
2902
- /**
2903
- * PanopticPool getAssetsOf ABI.
2904
- * Returns collateral assets (shares converted to underlying) for an account.
2905
- */
2906
- const getAssetsOfAbi = [{
2907
- type: "function",
2908
- name: "getAssetsOf",
2909
- inputs: [{
2910
- name: "account",
2911
- type: "address"
2912
- }],
2913
- outputs: [{
2914
- name: "assets0",
2915
- type: "uint256"
2916
- }, {
2917
- name: "assets1",
2918
- type: "uint256"
2919
- }],
2920
- stateMutability: "view"
2921
- }];
2922
- /**
2923
- * PanopticPool multicall ABI (inherited from Uniswap).
2924
- * Uses delegatecall, preserving msg.sender throughout the chain.
2925
- */
2926
- const multicallAbi = [{
2927
- type: "function",
2928
- name: "multicall",
2929
- inputs: [{
2930
- name: "data",
2931
- type: "bytes[]"
2932
- }],
2933
- outputs: [{
2934
- name: "results",
2935
- type: "bytes[]"
2936
- }],
2937
- stateMutability: "nonpayable"
2938
- }, ...panopticErrorsAbi];
3204
+ //#region src/panoptic/v2/reads/premia.ts
2939
3205
  /**
2940
- * Simulate a contract call and measure token flow using PanopticPool.multicall.
2941
- *
2942
- * This function uses PanopticPool's inherited multicall (delegatecall-based) to chain:
2943
- * 1. getAssetsOf(user) - read collateral assets before
2944
- * 2. getCurrentTick() - read pool tick before
2945
- * 3. Execute the target call (e.g., dispatch)
2946
- * 4. getCurrentTick() - read pool tick after
2947
- * 5. getAssetsOf(user) - read collateral assets after
3206
+ * Get premia totals for an account.
2948
3207
  *
2949
- * ## Why PanopticPool.multicall instead of Multicall3?
2950
- * - Measures **collateral assets** (shares → underlying), not raw wallet balances
2951
- * - Uses **delegatecall**, preserving msg.sender throughout the chain
2952
- * - Single contract interaction with PanopticPool
2953
- * - Correctly reflects what happens during position operations
2954
- *
2955
- * ## Same-Block Guarantee
2956
- * All operations execute within a single eth_call, ensuring atomic consistency.
3208
+ * Returns the total short and long premium across all positions.
3209
+ * Short premium is owed TO the account (earned from selling options).
3210
+ * Long premium is owed BY the account (paid for buying options).
2957
3211
  *
2958
- * @param params - Simulation parameters
2959
- * @returns Token flow result
3212
+ * @param params - The parameters
3213
+ * @returns Premia totals with block metadata
2960
3214
  *
2961
3215
  * @example
2962
3216
  * ```typescript
2963
- * const callData = encodeFunctionData({
2964
- * abi: panopticPoolV2Abi,
2965
- * functionName: 'dispatch',
2966
- * args: [positionIdList, finalPositionIdList, positionSizes, tickAndSpreadLimits, false, 0n],
2967
- * })
2968
- *
2969
- * const result = await simulateWithTokenFlow({
3217
+ * const premia = await getAccountPremia({
2970
3218
  * client,
2971
3219
  * poolAddress,
2972
- * user: userAddress,
2973
- * callData,
3220
+ * account,
3221
+ * tokenIds: [tokenId1, tokenId2],
3222
+ * includePendingPremium: true,
2974
3223
  * })
2975
3224
  *
2976
- * if (result.success) {
2977
- * console.log('Token 0 change:', result.tokenFlow.delta0)
2978
- * console.log('Token 1 change:', result.tokenFlow.delta1)
2979
- * }
3225
+ * console.log('Short premium earned:', premia.shortPremium0, premia.shortPremium1)
3226
+ * console.log('Long premium owed:', premia.longPremium0, premia.longPremium1)
3227
+ * ```
3228
+ */
3229
+ async function getAccountPremia(params) {
3230
+ const { client, poolAddress, account, tokenIds, includePendingPremium = true, blockNumber } = params;
3231
+ const targetBlockNumber = blockNumber ?? params._meta?.blockNumber ?? await client.getBlockNumber();
3232
+ if (tokenIds.length === 0) {
3233
+ const _meta$1 = params._meta ?? await getBlockMeta({
3234
+ client,
3235
+ blockNumber: targetBlockNumber
3236
+ });
3237
+ return {
3238
+ shortPremium0: 0n,
3239
+ shortPremium1: 0n,
3240
+ longPremium0: 0n,
3241
+ longPremium1: 0n,
3242
+ includePendingPremium,
3243
+ _meta: _meta$1
3244
+ };
3245
+ }
3246
+ const [[shortPremiumPacked, longPremiumPacked], _meta] = await Promise.all([client.readContract({
3247
+ address: poolAddress,
3248
+ abi: panopticPoolV2Abi,
3249
+ functionName: "getFullPositionsData",
3250
+ args: [
3251
+ account,
3252
+ includePendingPremium,
3253
+ tokenIds
3254
+ ],
3255
+ blockNumber: targetBlockNumber
3256
+ }), params._meta ?? getBlockMeta({
3257
+ client,
3258
+ blockNumber: targetBlockNumber
3259
+ })]);
3260
+ const shortPremium = decodeLeftRightUnsigned(shortPremiumPacked);
3261
+ const longPremium = decodeLeftRightUnsigned(longPremiumPacked);
3262
+ return {
3263
+ shortPremium0: shortPremium.right,
3264
+ shortPremium1: shortPremium.left,
3265
+ longPremium0: longPremium.right,
3266
+ longPremium1: longPremium.left,
3267
+ includePendingPremium,
3268
+ _meta
3269
+ };
3270
+ }
3271
+ /**
3272
+ * Get positions with per-position premia data.
3273
+ *
3274
+ * Uses multicall to batch individual getFullPositionsData calls
3275
+ * for each position, giving us per-position premia in a single RPC request.
3276
+ *
3277
+ * @param params - The parameters
3278
+ * @returns Positions with premia and totals with block metadata
3279
+ *
3280
+ * @example
3281
+ * ```typescript
3282
+ * const result = await getPositionsWithPremia({
3283
+ * client,
3284
+ * poolAddress,
3285
+ * account,
3286
+ * tokenIds: [tokenId1, tokenId2],
3287
+ * })
3288
+ *
3289
+ * for (const position of result.positions) {
3290
+ * console.log('Position:', position.tokenId)
3291
+ * console.log('Premia:', position.premiaOwed0, position.premiaOwed1)
3292
+ * }
3293
+ * console.log('Total short premium:', result.shortPremium0, result.shortPremium1)
3294
+ * ```
3295
+ */
3296
+ async function getPositionsWithPremia(params) {
3297
+ const { client, poolAddress, account, tokenIds, includePendingPremium = true, blockNumber } = params;
3298
+ const targetBlockNumber = blockNumber ?? params._meta?.blockNumber ?? await client.getBlockNumber();
3299
+ if (tokenIds.length === 0) {
3300
+ const _meta$1 = params._meta ?? await getBlockMeta({
3301
+ client,
3302
+ blockNumber: targetBlockNumber
3303
+ });
3304
+ return {
3305
+ positions: [],
3306
+ shortPremium0: 0n,
3307
+ shortPremium1: 0n,
3308
+ longPremium0: 0n,
3309
+ longPremium1: 0n,
3310
+ includePendingPremium,
3311
+ _meta: _meta$1
3312
+ };
3313
+ }
3314
+ const contracts = tokenIds.map((tokenId) => ({
3315
+ address: poolAddress,
3316
+ abi: panopticPoolV2Abi,
3317
+ functionName: "getFullPositionsData",
3318
+ args: [
3319
+ account,
3320
+ includePendingPremium,
3321
+ [tokenId]
3322
+ ]
3323
+ }));
3324
+ const [multicallResults, _meta] = await Promise.all([client.multicall({
3325
+ contracts,
3326
+ blockNumber: targetBlockNumber,
3327
+ allowFailure: true
3328
+ }), params._meta ?? getBlockMeta({
3329
+ client,
3330
+ blockNumber: targetBlockNumber
3331
+ })]);
3332
+ const positions = [];
3333
+ let totalShortPremium0 = 0n;
3334
+ let totalShortPremium1 = 0n;
3335
+ let totalLongPremium0 = 0n;
3336
+ let totalLongPremium1 = 0n;
3337
+ for (let i = 0; i < tokenIds.length; i++) {
3338
+ const tokenId = tokenIds[i];
3339
+ const result = multicallResults[i];
3340
+ if (result.status !== "success") continue;
3341
+ const [shortPremiumPacked, longPremiumPacked, balances] = result.result;
3342
+ const shortPremium = decodeLeftRightUnsigned(shortPremiumPacked);
3343
+ const longPremium = decodeLeftRightUnsigned(longPremiumPacked);
3344
+ totalShortPremium0 += shortPremium.right;
3345
+ totalShortPremium1 += shortPremium.left;
3346
+ totalLongPremium0 += longPremium.right;
3347
+ totalLongPremium1 += longPremium.left;
3348
+ const balanceData = balances[0];
3349
+ if (balanceData === void 0) continue;
3350
+ const positionSize = balanceData & (1n << 128n) - 1n;
3351
+ if (positionSize === 0n) continue;
3352
+ const poolUtilization0 = balanceData >> 128n & 0xffffn;
3353
+ const poolUtilization1 = balanceData >> 144n & 0xffffn;
3354
+ let tickAtMint = balanceData >> 160n & 0xffffffn;
3355
+ if (tickAtMint > 0x7fffffn) tickAtMint = tickAtMint - 0x1000000n;
3356
+ const timestampAtMint = balanceData >> 184n & 0xffffffffn;
3357
+ const blockAtMint = balanceData >> 216n & (1n << 39n) - 1n;
3358
+ const swapAtMint = balanceData >> 255n === 1n;
3359
+ const decoded = decodePosition(tokenId);
3360
+ const tickSpacing = decodeTickSpacing(tokenId);
3361
+ const legs = decoded.legs.map((leg) => {
3362
+ const width = leg.width;
3363
+ const strike = leg.strike;
3364
+ const tickLower = strike - width * tickSpacing / 2n;
3365
+ const tickUpper = strike + width * tickSpacing / 2n;
3366
+ return {
3367
+ index: BigInt(leg.index),
3368
+ asset: leg.asset,
3369
+ optionRatio: leg.optionRatio,
3370
+ isLong: leg.isLong === 1n,
3371
+ tokenType: leg.tokenType,
3372
+ riskPartner: leg.riskPartner,
3373
+ strike,
3374
+ width,
3375
+ tickLower,
3376
+ tickUpper
3377
+ };
3378
+ });
3379
+ const premiaOwed0 = shortPremium.right - longPremium.right;
3380
+ const premiaOwed1 = shortPremium.left - longPremium.left;
3381
+ positions.push({
3382
+ tokenId,
3383
+ positionSize,
3384
+ owner: account,
3385
+ poolAddress,
3386
+ legs,
3387
+ poolUtilization0AtMint: poolUtilization0,
3388
+ poolUtilization1AtMint: poolUtilization1,
3389
+ tickAtMint,
3390
+ timestampAtMint,
3391
+ blockNumberAtMint: blockAtMint,
3392
+ swapAtMint,
3393
+ premiaOwed0,
3394
+ premiaOwed1,
3395
+ assetIndex: legs.length > 0 ? legs[0].asset : 0n,
3396
+ _meta
3397
+ });
3398
+ }
3399
+ return {
3400
+ positions,
3401
+ shortPremium0: totalShortPremium0,
3402
+ shortPremium1: totalShortPremium1,
3403
+ longPremium0: totalLongPremium0,
3404
+ longPremium1: totalLongPremium1,
3405
+ includePendingPremium,
3406
+ _meta
3407
+ };
3408
+ }
3409
+ /**
3410
+ * Get the unsettled short premium an account would forfeit by closing now.
3411
+ *
3412
+ * Calls `getFullPositionsData` twice in one multicall — once with
3413
+ * `includePendingPremium = true` (everything owed to the short legs) and once
3414
+ * with `false` (only what is available to collect). The difference is the
3415
+ * premium still owed by buyers that has not been settled; closing before it
3416
+ * settles forfeits it. Settling buyers first (see `settlePremiumFrom`) moves
3417
+ * that premium into the available bucket.
3418
+ *
3419
+ * @param params - The parameters
3420
+ * @returns Owed, available, and forfeitable premium with block metadata
3421
+ */
3422
+ async function getForfeitablePremium(params) {
3423
+ const { client, poolAddress, account, tokenIds, blockNumber } = params;
3424
+ const targetBlockNumber = blockNumber ?? await client.getBlockNumber();
3425
+ const contracts = [true, false].map((includePendingPremium) => ({
3426
+ address: poolAddress,
3427
+ abi: panopticPoolV2Abi,
3428
+ functionName: "getFullPositionsData",
3429
+ args: [
3430
+ account,
3431
+ includePendingPremium,
3432
+ tokenIds
3433
+ ]
3434
+ }));
3435
+ const [multicallResults, _meta] = await Promise.all([client.multicall({
3436
+ contracts,
3437
+ blockNumber: targetBlockNumber,
3438
+ allowFailure: false
3439
+ }), getBlockMeta({
3440
+ client,
3441
+ blockNumber: targetBlockNumber
3442
+ })]);
3443
+ const owed = decodeLeftRightUnsigned(multicallResults[0][0]);
3444
+ const available = decodeLeftRightUnsigned(multicallResults[1][0]);
3445
+ const forfeit0 = owed.right > available.right ? owed.right - available.right : 0n;
3446
+ const forfeit1 = owed.left > available.left ? owed.left - available.left : 0n;
3447
+ return {
3448
+ owed0: owed.right,
3449
+ owed1: owed.left,
3450
+ available0: available.right,
3451
+ available1: available.left,
3452
+ forfeit0,
3453
+ forfeit1,
3454
+ _meta
3455
+ };
3456
+ }
3457
+
3458
+ //#endregion
3459
+ //#region src/panoptic/v2/simulations/tokenFlow.ts
3460
+ /**
3461
+ * PanopticPool getAssetsOf ABI.
3462
+ * Returns collateral assets (shares converted to underlying) for an account.
3463
+ */
3464
+ const getAssetsOfAbi = [{
3465
+ type: "function",
3466
+ name: "getAssetsOf",
3467
+ inputs: [{
3468
+ name: "account",
3469
+ type: "address"
3470
+ }],
3471
+ outputs: [{
3472
+ name: "assets0",
3473
+ type: "uint256"
3474
+ }, {
3475
+ name: "assets1",
3476
+ type: "uint256"
3477
+ }],
3478
+ stateMutability: "view"
3479
+ }];
3480
+ /**
3481
+ * PanopticPool multicall ABI (inherited from Uniswap).
3482
+ * Uses delegatecall, preserving msg.sender throughout the chain.
3483
+ */
3484
+ const multicallAbi$1 = [{
3485
+ type: "function",
3486
+ name: "multicall",
3487
+ inputs: [{
3488
+ name: "data",
3489
+ type: "bytes[]"
3490
+ }],
3491
+ outputs: [{
3492
+ name: "results",
3493
+ type: "bytes[]"
3494
+ }],
3495
+ stateMutability: "nonpayable"
3496
+ }, ...panopticErrorsAbi];
3497
+ /**
3498
+ * Simulate a contract call and measure token flow using PanopticPool.multicall.
3499
+ *
3500
+ * This function uses PanopticPool's inherited multicall (delegatecall-based) to chain:
3501
+ * 1. getAssetsOf(user) - read collateral assets before
3502
+ * 2. getCurrentTick() - read pool tick before
3503
+ * 3. Execute the target call (e.g., dispatch)
3504
+ * 4. getCurrentTick() - read pool tick after
3505
+ * 5. getAssetsOf(user) - read collateral assets after
3506
+ *
3507
+ * ## Why PanopticPool.multicall instead of Multicall3?
3508
+ * - Measures **collateral assets** (shares → underlying), not raw wallet balances
3509
+ * - Uses **delegatecall**, preserving msg.sender throughout the chain
3510
+ * - Single contract interaction with PanopticPool
3511
+ * - Correctly reflects what happens during position operations
3512
+ *
3513
+ * ## Same-Block Guarantee
3514
+ * All operations execute within a single eth_call, ensuring atomic consistency.
3515
+ *
3516
+ * @param params - Simulation parameters
3517
+ * @returns Token flow result
3518
+ *
3519
+ * @example
3520
+ * ```typescript
3521
+ * const callData = encodeFunctionData({
3522
+ * abi: panopticPoolV2Abi,
3523
+ * functionName: 'dispatch',
3524
+ * args: [positionIdList, finalPositionIdList, positionSizes, tickAndSpreadLimits, false, 0n],
3525
+ * })
3526
+ *
3527
+ * const result = await simulateWithTokenFlow({
3528
+ * client,
3529
+ * poolAddress,
3530
+ * user: userAddress,
3531
+ * callData,
3532
+ * })
3533
+ *
3534
+ * if (result.success) {
3535
+ * console.log('Token 0 change:', result.tokenFlow.delta0)
3536
+ * console.log('Token 1 change:', result.tokenFlow.delta1)
3537
+ * }
2980
3538
  * ```
2981
3539
  */
2982
3540
  async function simulateWithTokenFlow(params) {
@@ -3003,7 +3561,7 @@ async function simulateWithTokenFlow(params) {
3003
3561
  try {
3004
3562
  const { result } = await client.simulateContract({
3005
3563
  address: poolAddress,
3006
- abi: multicallAbi,
3564
+ abi: multicallAbi$1,
3007
3565
  functionName: "multicall",
3008
3566
  args: [multicallData],
3009
3567
  account: user,
@@ -3131,6 +3689,1359 @@ async function getPoolTokensForSimulation(params) {
3131
3689
  };
3132
3690
  }
3133
3691
 
3692
+ //#endregion
3693
+ //#region src/panoptic/v2/simulations/simulateSettlePremiumFrom.ts
3694
+ /** Soft-failure revert markers for settle premium (target-state issues, not caller errors). */
3695
+ const SOFT_FAILURES = [
3696
+ {
3697
+ marker: "AccountInsolvent",
3698
+ reason: "Target account is insolvent; premium cannot be settled"
3699
+ },
3700
+ {
3701
+ marker: "PositionNotOwned",
3702
+ reason: "Target account no longer owns the position"
3703
+ },
3704
+ {
3705
+ marker: "StaleOracle",
3706
+ reason: "Oracle price is stale; settlement temporarily unavailable"
3707
+ },
3708
+ {
3709
+ marker: "InputListFail",
3710
+ reason: "Position list is stale (target positions changed)"
3711
+ }
3712
+ ];
3713
+ /**
3714
+ * Simulate settling another account's accumulated long premium via `dispatchFrom`
3715
+ * (equal-length `positionIdListTo`/`positionIdListToFinal` selects the settle mode).
3716
+ *
3717
+ * The measured token flow is the CALLER's collateral delta — i.e. the premium
3718
+ * the caller receives from the settlement (for chunks they sold).
3719
+ *
3720
+ * @param params - Simulation parameters
3721
+ * @returns Simulation result with settled premium data or error
3722
+ */
3723
+ async function simulateSettlePremiumFrom(params) {
3724
+ const { client, poolAddress, account, user, positionIdListFrom, positionIdList, tokenId, blockNumber } = params;
3725
+ const targetBlockNumber = blockNumber ?? await client.getBlockNumber();
3726
+ const metaPromise = getBlockMeta({
3727
+ client,
3728
+ blockNumber: targetBlockNumber
3729
+ });
3730
+ const emptyTokenFlow = {
3731
+ delta0: 0n,
3732
+ delta1: 0n,
3733
+ balanceBefore0: 0n,
3734
+ balanceBefore1: 0n,
3735
+ balanceAfter0: 0n,
3736
+ balanceAfter1: 0n,
3737
+ tickBefore: null,
3738
+ tickAfter: null
3739
+ };
3740
+ const softFailure = (errorMessage) => {
3741
+ const match = SOFT_FAILURES.find(({ marker }) => errorMessage.includes(marker));
3742
+ return match ? {
3743
+ premium0: 0n,
3744
+ premium1: 0n,
3745
+ settled0: 0n,
3746
+ settled1: 0n,
3747
+ canSettle: false,
3748
+ reason: match.reason
3749
+ } : null;
3750
+ };
3751
+ try {
3752
+ const orderedList = tokenId !== void 0 ? orderListForSettle(positionIdList, tokenId) : positionIdList;
3753
+ const callData = encodeFunctionData({
3754
+ abi: panopticPoolV2Abi,
3755
+ functionName: "dispatchFrom",
3756
+ args: [
3757
+ positionIdListFrom,
3758
+ user,
3759
+ orderedList,
3760
+ orderedList,
3761
+ 0n
3762
+ ]
3763
+ });
3764
+ const availablePremiumCallData = encodeFunctionData({
3765
+ abi: panopticPoolV2Abi,
3766
+ functionName: "getFullPositionsData",
3767
+ args: [
3768
+ account,
3769
+ false,
3770
+ positionIdListFrom
3771
+ ]
3772
+ });
3773
+ const buyerOwedCallData = encodeFunctionData({
3774
+ abi: panopticPoolV2Abi,
3775
+ functionName: "getFullPositionsData",
3776
+ args: [
3777
+ user,
3778
+ true,
3779
+ orderedList
3780
+ ]
3781
+ });
3782
+ const flowResult = await simulateWithTokenFlow({
3783
+ client,
3784
+ poolAddress,
3785
+ user: account,
3786
+ callData,
3787
+ blockNumber: targetBlockNumber,
3788
+ preCallData: [availablePremiumCallData, buyerOwedCallData],
3789
+ postCallData: [availablePremiumCallData, buyerOwedCallData]
3790
+ });
3791
+ if (!flowResult.success || !flowResult.tokenFlow) {
3792
+ const errorMessage = flowResult.error || "Simulation failed";
3793
+ const soft = softFailure(errorMessage);
3794
+ if (soft) {
3795
+ const _meta$1 = await metaPromise;
3796
+ return {
3797
+ success: true,
3798
+ data: soft,
3799
+ gasEstimate: 0n,
3800
+ tokenFlow: emptyTokenFlow,
3801
+ _meta: _meta$1
3802
+ };
3803
+ }
3804
+ throw new PanopticError(errorMessage);
3805
+ }
3806
+ const _meta = await metaPromise;
3807
+ const tokenFlow = flowResult.tokenFlow;
3808
+ const decodePremia = (raw) => {
3809
+ const [shortPremiumPacked, longPremiumPacked] = decodeFunctionResult({
3810
+ abi: panopticPoolV2Abi,
3811
+ functionName: "getFullPositionsData",
3812
+ data: raw
3813
+ });
3814
+ const short = decodeLeftRightUnsigned(shortPremiumPacked);
3815
+ const long = decodeLeftRightUnsigned(longPremiumPacked);
3816
+ return {
3817
+ short0: short.right,
3818
+ short1: short.left,
3819
+ long0: long.right,
3820
+ long1: long.left
3821
+ };
3822
+ };
3823
+ let premium0 = 0n;
3824
+ let premium1 = 0n;
3825
+ const preRaw = flowResult.preCallResults?.[0];
3826
+ const postRaw = flowResult.postCallResults?.[0];
3827
+ if (preRaw !== void 0 && postRaw !== void 0) {
3828
+ const pre = decodePremia(preRaw);
3829
+ const post = decodePremia(postRaw);
3830
+ premium0 = post.short0 > pre.short0 ? post.short0 - pre.short0 : 0n;
3831
+ premium1 = post.short1 > pre.short1 ? post.short1 - pre.short1 : 0n;
3832
+ }
3833
+ let settled0 = 0n;
3834
+ let settled1 = 0n;
3835
+ const preBuyerRaw = flowResult.preCallResults?.[1];
3836
+ const postBuyerRaw = flowResult.postCallResults?.[1];
3837
+ if (preBuyerRaw !== void 0 && postBuyerRaw !== void 0) {
3838
+ const pre = decodePremia(preBuyerRaw);
3839
+ const post = decodePremia(postBuyerRaw);
3840
+ settled0 = pre.long0 > post.long0 ? pre.long0 - post.long0 : 0n;
3841
+ settled1 = pre.long1 > post.long1 ? pre.long1 - post.long1 : 0n;
3842
+ }
3843
+ const data = {
3844
+ premium0,
3845
+ premium1,
3846
+ settled0,
3847
+ settled1,
3848
+ canSettle: true
3849
+ };
3850
+ return {
3851
+ success: true,
3852
+ data,
3853
+ gasEstimate: flowResult.gasEstimate,
3854
+ tokenFlow,
3855
+ _meta
3856
+ };
3857
+ } catch (error) {
3858
+ const _meta = await metaPromise;
3859
+ const errorMessage = error instanceof Error ? error.message : "Simulation failed";
3860
+ const soft = softFailure(errorMessage);
3861
+ if (soft) return {
3862
+ success: true,
3863
+ data: soft,
3864
+ gasEstimate: 0n,
3865
+ tokenFlow: emptyTokenFlow,
3866
+ _meta
3867
+ };
3868
+ return {
3869
+ success: false,
3870
+ error: error instanceof PanopticError ? error : new PanopticError(errorMessage, error instanceof Error ? error : void 0),
3871
+ _meta
3872
+ };
3873
+ }
3874
+ }
3875
+
3876
+ //#endregion
3877
+ //#region src/panoptic/v2/simulations/simulateSettlePremiumBatch.ts
3878
+ /**
3879
+ * Simulate settling each target buyer's owed long premium, all at one block.
3880
+ *
3881
+ * Individual failures (insolvent buyer, stale list, …) are soft: the target
3882
+ * lands in the unsettleable partition instead of failing the batch. Only
3883
+ * unexpected errors reject.
3884
+ *
3885
+ * @param params - Simulation parameters
3886
+ * @returns Partitioned targets with per-target premium and totals
3887
+ */
3888
+ async function simulateSettlePremiumBatch(params) {
3889
+ const { client, poolAddress, account, positionIdListFrom, targets, blockNumber } = params;
3890
+ const targetBlockNumber = blockNumber ?? await client.getBlockNumber();
3891
+ const metaPromise = getBlockMeta({
3892
+ client,
3893
+ blockNumber: targetBlockNumber
3894
+ });
3895
+ const simulations = await Promise.all(targets.map((target) => simulateSettlePremiumFrom({
3896
+ client,
3897
+ poolAddress,
3898
+ account,
3899
+ user: target.user,
3900
+ positionIdListFrom,
3901
+ positionIdList: target.positionIdList,
3902
+ tokenId: target.tokenId,
3903
+ blockNumber: targetBlockNumber
3904
+ })));
3905
+ const results = [];
3906
+ const settleable = [];
3907
+ let unsettleableCount = 0;
3908
+ let premium0 = 0n;
3909
+ let premium1 = 0n;
3910
+ simulations.forEach((sim, i) => {
3911
+ const simulation = sim.success ? sim.data : {
3912
+ premium0: 0n,
3913
+ premium1: 0n,
3914
+ settled0: 0n,
3915
+ settled1: 0n,
3916
+ canSettle: false,
3917
+ reason: sim.error.message
3918
+ };
3919
+ results.push({
3920
+ target: targets[i],
3921
+ simulation
3922
+ });
3923
+ if (simulation.canSettle) {
3924
+ settleable.push(targets[i]);
3925
+ premium0 += simulation.premium0;
3926
+ premium1 += simulation.premium1;
3927
+ } else unsettleableCount += 1;
3928
+ });
3929
+ const _meta = await metaPromise;
3930
+ return {
3931
+ results,
3932
+ settleable,
3933
+ unsettleableCount,
3934
+ premium0,
3935
+ premium1,
3936
+ _meta
3937
+ };
3938
+ }
3939
+ /**
3940
+ * Simulate a full settle sequence (all settles + optional close) as the one
3941
+ * multicall that `executeSettleSequence` submits, measuring the caller's
3942
+ * total token flow and gas.
3943
+ *
3944
+ * @param params - Simulation parameters
3945
+ * @returns Simulation result with the caller's net flow, or error
3946
+ */
3947
+ async function simulateSettleSequence(params) {
3948
+ const { client, poolAddress, account, blockNumber } = params;
3949
+ const targetBlockNumber = blockNumber ?? await client.getBlockNumber();
3950
+ const metaPromise = getBlockMeta({
3951
+ client,
3952
+ blockNumber: targetBlockNumber
3953
+ });
3954
+ try {
3955
+ const calls = buildSettleSequenceCalls(params);
3956
+ const callData = encodeFunctionData({
3957
+ abi: panopticPoolV2Abi,
3958
+ functionName: "multicall",
3959
+ args: [calls]
3960
+ });
3961
+ const flowResult = await simulateWithTokenFlow({
3962
+ client,
3963
+ poolAddress,
3964
+ user: account,
3965
+ callData,
3966
+ blockNumber: targetBlockNumber
3967
+ });
3968
+ if (!flowResult.success || !flowResult.tokenFlow) throw new PanopticError(flowResult.error || "Simulation failed");
3969
+ const _meta = await metaPromise;
3970
+ const tokenFlow = flowResult.tokenFlow;
3971
+ return {
3972
+ success: true,
3973
+ data: {
3974
+ delta0: tokenFlow.delta0,
3975
+ delta1: tokenFlow.delta1
3976
+ },
3977
+ gasEstimate: flowResult.gasEstimate,
3978
+ tokenFlow,
3979
+ _meta
3980
+ };
3981
+ } catch (error) {
3982
+ const _meta = await metaPromise;
3983
+ const errorMessage = error instanceof Error ? error.message : "Simulation failed";
3984
+ return {
3985
+ success: false,
3986
+ error: error instanceof PanopticError ? error : new PanopticError(errorMessage, error instanceof Error ? error : void 0),
3987
+ _meta
3988
+ };
3989
+ }
3990
+ }
3991
+
3992
+ //#endregion
3993
+ //#region src/panoptic/v2/simulations/simulateSettle.ts
3994
+ const BIT_MASK_128 = (1n << 128n) - 1n;
3995
+ const multicallAbi = [{
3996
+ type: "function",
3997
+ name: "multicall",
3998
+ inputs: [{
3999
+ name: "data",
4000
+ type: "bytes[]"
4001
+ }],
4002
+ outputs: [{
4003
+ name: "results",
4004
+ type: "bytes[]"
4005
+ }],
4006
+ stateMutability: "nonpayable"
4007
+ }];
4008
+ function encodeDispatch(plan) {
4009
+ const dispatch$1 = plan.dispatch;
4010
+ return encodeFunctionData({
4011
+ abi: panopticPoolV2Abi,
4012
+ functionName: "dispatch",
4013
+ args: [
4014
+ dispatch$1.positionIdList,
4015
+ dispatch$1.finalPositionIdList,
4016
+ dispatch$1.positionSizes,
4017
+ dispatch$1.tickAndSpreadLimits.map((limits) => [
4018
+ Number(limits[0]),
4019
+ Number(limits[1]),
4020
+ Number(limits[2])
4021
+ ]),
4022
+ dispatch$1.usePremiaAsCollateral,
4023
+ dispatch$1.builderCode
4024
+ ]
4025
+ });
4026
+ }
4027
+ function decodeShortPremium(data) {
4028
+ const packed = decodeFunctionResult({
4029
+ abi: panopticPoolV2Abi,
4030
+ functionName: "getFullPositionsData",
4031
+ data
4032
+ })[0];
4033
+ return [packed & BIT_MASK_128, packed >> 128n];
4034
+ }
4035
+ async function remainingForfeitAfterProtection(params) {
4036
+ const { client, poolAddress, account, positionIdList, targets, plan, blockNumber, initial } = params;
4037
+ if (targets.length === 0 && plan.collectionDispatch === void 0) return [...initial];
4038
+ const protectionCalls = buildSettleSequenceCalls({
4039
+ positionIdListFrom: params.finalPositionIdList,
4040
+ targets,
4041
+ dispatch: plan.collectionDispatch
4042
+ });
4043
+ const availableCall = encodeFunctionData({
4044
+ abi: panopticPoolV2Abi,
4045
+ functionName: "getFullPositionsData",
4046
+ args: [
4047
+ account,
4048
+ false,
4049
+ positionIdList
4050
+ ]
4051
+ });
4052
+ const totalCall = encodeFunctionData({
4053
+ abi: panopticPoolV2Abi,
4054
+ functionName: "getFullPositionsData",
4055
+ args: [
4056
+ account,
4057
+ true,
4058
+ positionIdList
4059
+ ]
4060
+ });
4061
+ const { result } = await client.simulateContract({
4062
+ address: poolAddress,
4063
+ abi: multicallAbi,
4064
+ functionName: "multicall",
4065
+ args: [[
4066
+ ...protectionCalls,
4067
+ availableCall,
4068
+ totalCall
4069
+ ]],
4070
+ account,
4071
+ blockNumber
4072
+ });
4073
+ const available = decodeShortPremium(result[result.length - 2]);
4074
+ const total = decodeShortPremium(result[result.length - 1]);
4075
+ return [total[0] > available[0] ? total[0] - available[0] : 0n, total[1] > available[1] ? total[1] - available[1] : 0n];
4076
+ }
4077
+ async function simulateSettle(params) {
4078
+ const { client, poolAddress, account, positionIdList, finalPositionIdList = positionIdList, positionSizes: providedSizes, targets = [], usePremiaAsCollateral = false, builderCode = 0n, allowForfeit = false, blockNumber } = params;
4079
+ const targetBlockNumber = blockNumber ?? await client.getBlockNumber();
4080
+ const metaPromise = getBlockMeta({
4081
+ client,
4082
+ blockNumber: targetBlockNumber
4083
+ });
4084
+ try {
4085
+ if (providedSizes && providedSizes.length !== positionIdList.length) throw new PanopticError("simulateSettle: positionSizes length must match positionIdList");
4086
+ const positionSizes = providedSizes ?? await getCurrentPositionSizes({
4087
+ client,
4088
+ poolAddress,
4089
+ account,
4090
+ positionIdList,
4091
+ blockNumber: targetBlockNumber
4092
+ });
4093
+ const plan = buildProtectedSettlePlan({
4094
+ positionIdList,
4095
+ finalPositionIdList,
4096
+ positionSizes,
4097
+ usePremiaAsCollateral,
4098
+ builderCode
4099
+ });
4100
+ const initialForfeit = await getForfeitablePremium({
4101
+ client,
4102
+ poolAddress,
4103
+ account,
4104
+ tokenIds: positionIdList,
4105
+ blockNumber: targetBlockNumber
4106
+ });
4107
+ const initial = [initialForfeit.forfeit0, initialForfeit.forfeit1];
4108
+ if (targets.length > 0) {
4109
+ const buyers = await simulateSettlePremiumBatch({
4110
+ client,
4111
+ poolAddress,
4112
+ account,
4113
+ positionIdListFrom: finalPositionIdList,
4114
+ targets,
4115
+ blockNumber: targetBlockNumber
4116
+ });
4117
+ if (buyers.unsettleableCount > 0) throw new UnsafePremiumSettlementError(initial, buyers.unsettleableCount);
4118
+ }
4119
+ const remainingForfeit = await remainingForfeitAfterProtection({
4120
+ client,
4121
+ poolAddress,
4122
+ account,
4123
+ positionIdList,
4124
+ finalPositionIdList,
4125
+ targets,
4126
+ plan,
4127
+ blockNumber: targetBlockNumber,
4128
+ initial
4129
+ });
4130
+ if ((remainingForfeit[0] > 0n || remainingForfeit[1] > 0n) && !allowForfeit) throw new UnsafePremiumSettlementError(remainingForfeit, 0);
4131
+ const callData = targets.length === 0 ? encodeDispatch(plan) : encodeFunctionData({
4132
+ abi: panopticPoolV2Abi,
4133
+ functionName: "multicall",
4134
+ args: [buildSettleSequenceCalls({
4135
+ positionIdListFrom: finalPositionIdList,
4136
+ targets,
4137
+ dispatch: plan.dispatch
4138
+ })]
4139
+ });
4140
+ const flowResult = await simulateWithTokenFlow({
4141
+ client,
4142
+ poolAddress,
4143
+ user: account,
4144
+ callData,
4145
+ blockNumber: targetBlockNumber
4146
+ });
4147
+ if (!flowResult.success || !flowResult.tokenFlow) throw flowResult.rawError ?? new PanopticError(flowResult.error || "Simulation failed");
4148
+ const tokenFlow = flowResult.tokenFlow;
4149
+ return {
4150
+ success: true,
4151
+ data: {
4152
+ premiaReceived0: tokenFlow.delta0,
4153
+ premiaReceived1: tokenFlow.delta1,
4154
+ postCollateral0: tokenFlow.balanceAfter0,
4155
+ postCollateral1: tokenFlow.balanceAfter1,
4156
+ premiumProtected: [initial[0] - remainingForfeit[0], initial[1] - remainingForfeit[1]],
4157
+ remainingForfeit,
4158
+ usesPoke: plan.pokingTokenIds.length > 0,
4159
+ settledBuyerCount: targets.length
4160
+ },
4161
+ gasEstimate: flowResult.gasEstimate,
4162
+ tokenFlow,
4163
+ _meta: await metaPromise
4164
+ };
4165
+ } catch (error) {
4166
+ return {
4167
+ success: false,
4168
+ error: error instanceof PanopticError ? error : new PanopticError(error instanceof Error ? error.message : "Simulation failed", error instanceof Error ? error : void 0),
4169
+ _meta: await metaPromise
4170
+ };
4171
+ }
4172
+ }
4173
+
4174
+ //#endregion
4175
+ //#region src/panoptic/v2/writes/settle.ts
4176
+ /**
4177
+ * Settle accumulated premia on existing positions.
4178
+ *
4179
+ * This function triggers premium collection without changing position size.
4180
+ * It calls dispatch with unchanged position lists.
4181
+ *
4182
+ * @param params - Settlement parameters
4183
+ * @returns TxResult
4184
+ *
4185
+ * @example
4186
+ * ```typescript
4187
+ * const result = await settleAccumulatedPremia({
4188
+ * client,
4189
+ * walletClient,
4190
+ * account,
4191
+ * poolAddress,
4192
+ * positionIdList: existingPositions,
4193
+ * })
4194
+ * const receipt = await result.wait()
4195
+ * ```
4196
+ */
4197
+ async function settleAccumulatedPremia(params) {
4198
+ const { client, walletClient, account, poolAddress, positionIdList, finalPositionIdList, positionSizes: providedSizes, targets = [], skipPreflight = false, allowForfeit = false, usePremiaAsCollateral = false, builderCode = 0n, txOverrides } = params;
4199
+ if (providedSizes && providedSizes.length !== positionIdList.length) throw new PanopticError("settleAccumulatedPremia: positionSizes length must match positionIdList");
4200
+ const positionSizes = providedSizes ?? await getCurrentPositionSizes({
4201
+ client,
4202
+ poolAddress,
4203
+ account,
4204
+ positionIdList
4205
+ });
4206
+ const heldPositions = finalPositionIdList ?? positionIdList;
4207
+ if (!skipPreflight) {
4208
+ const simulation = await simulateSettle({
4209
+ client,
4210
+ poolAddress,
4211
+ account,
4212
+ positionIdList,
4213
+ finalPositionIdList: heldPositions,
4214
+ positionSizes,
4215
+ targets,
4216
+ usePremiaAsCollateral,
4217
+ builderCode,
4218
+ allowForfeit
4219
+ });
4220
+ if (!simulation.success) throw simulation.error;
4221
+ }
4222
+ const { dispatch: dispatch$1 } = buildProtectedSettlePlan({
4223
+ positionIdList,
4224
+ finalPositionIdList: heldPositions,
4225
+ positionSizes,
4226
+ usePremiaAsCollateral,
4227
+ builderCode
4228
+ });
4229
+ if (targets.length > 0) return executeSettleSequence({
4230
+ client,
4231
+ walletClient,
4232
+ account,
4233
+ poolAddress,
4234
+ positionIdListFrom: heldPositions,
4235
+ targets,
4236
+ dispatch: dispatch$1,
4237
+ txOverrides
4238
+ });
4239
+ return submitWrite({
4240
+ client,
4241
+ walletClient,
4242
+ account,
4243
+ address: poolAddress,
4244
+ abi: panopticPoolV2Abi,
4245
+ functionName: "dispatch",
4246
+ args: [
4247
+ dispatch$1.positionIdList,
4248
+ dispatch$1.finalPositionIdList,
4249
+ dispatch$1.positionSizes,
4250
+ dispatch$1.tickAndSpreadLimits.map((t) => [
4251
+ Number(t[0]),
4252
+ Number(t[1]),
4253
+ Number(t[2])
4254
+ ]),
4255
+ dispatch$1.usePremiaAsCollateral,
4256
+ dispatch$1.builderCode
4257
+ ],
4258
+ txOverrides
4259
+ });
4260
+ }
4261
+ /**
4262
+ * Settle premia and wait for confirmation.
4263
+ */
4264
+ async function settleAccumulatedPremiaAndWait(params) {
4265
+ const result = await settleAccumulatedPremia(params);
4266
+ return result.wait();
4267
+ }
4268
+
4269
+ //#endregion
4270
+ //#region src/panoptic/v2/writes/pokeOracle.ts
4271
+ /**
4272
+ * Poke the oracle to update its state.
4273
+ *
4274
+ * This function can be called to advance the oracle epoch.
4275
+ * Note: The oracle can only be poked once per epoch (64 seconds).
4276
+ *
4277
+ * @param params - Poke oracle parameters
4278
+ * @returns TxResult
4279
+ * @throws OracleRateLimitedError if checkRateLimit is true and oracle was recently poked
4280
+ *
4281
+ * @example
4282
+ * ```typescript
4283
+ * const result = await pokeOracle({
4284
+ * client,
4285
+ * walletClient,
4286
+ * account,
4287
+ * poolAddress,
4288
+ * })
4289
+ * const receipt = await result.wait()
4290
+ * ```
4291
+ */
4292
+ async function pokeOracle(params) {
4293
+ const { client, walletClient, account, poolAddress, checkRateLimit = false, txOverrides } = params;
4294
+ if (checkRateLimit) {
4295
+ const [oracleData, block] = await Promise.all([client.readContract({
4296
+ address: poolAddress,
4297
+ abi: panopticPoolV2Abi,
4298
+ functionName: "getOracleTicks"
4299
+ }), client.getBlock()]);
4300
+ const oraclePack = oracleData[4];
4301
+ const { epoch } = decodeOracleTiming(oraclePack, block.timestamp);
4302
+ const currentEpoch = oracleEpochAt(block.timestamp);
4303
+ if (currentEpoch === epoch) {
4304
+ const lastUpdate = epoch << 6n;
4305
+ throw new OracleRateLimitedError(lastUpdate, block.timestamp);
4306
+ }
4307
+ }
4308
+ return submitWrite({
4309
+ client,
4310
+ walletClient,
4311
+ account,
4312
+ address: poolAddress,
4313
+ abi: panopticPoolV2Abi,
4314
+ functionName: "pokeOracle",
4315
+ args: [],
4316
+ txOverrides
4317
+ });
4318
+ }
4319
+ /**
4320
+ * Poke oracle and wait for confirmation.
4321
+ */
4322
+ async function pokeOracleAndWait(params) {
4323
+ const result = await pokeOracle(params);
4324
+ return result.wait();
4325
+ }
4326
+
4327
+ //#endregion
4328
+ //#region src/panoptic/v2/writes/factory.ts
4329
+ /**
4330
+ * Deploy a new Panoptic pool via the factory.
4331
+ *
4332
+ * @param params - Deployment parameters (versioned: 'v3' or 'v4')
4333
+ * @returns Transaction result with hash and wait function
4334
+ */
4335
+ async function deployNewPool(params) {
4336
+ const { client, walletClient, account, factoryAddress, riskEngine, salt, txOverrides } = params;
4337
+ if (params.version === "v3") return submitWrite({
4338
+ client,
4339
+ walletClient,
4340
+ account,
4341
+ address: factoryAddress,
4342
+ abi: panopticFactoryV3Abi,
4343
+ functionName: "deployNewPool",
4344
+ args: [
4345
+ params.token0,
4346
+ params.token1,
4347
+ params.fee,
4348
+ riskEngine,
4349
+ salt
4350
+ ],
4351
+ txOverrides
4352
+ });
4353
+ return submitWrite({
4354
+ client,
4355
+ walletClient,
4356
+ account,
4357
+ address: factoryAddress,
4358
+ abi: panopticFactoryV4Abi,
4359
+ functionName: "deployNewPool",
4360
+ args: [
4361
+ {
4362
+ currency0: params.poolKey.currency0,
4363
+ currency1: params.poolKey.currency1,
4364
+ fee: Number(params.poolKey.fee),
4365
+ tickSpacing: Number(params.poolKey.tickSpacing),
4366
+ hooks: params.poolKey.hooks
4367
+ },
4368
+ riskEngine,
4369
+ salt
4370
+ ],
4371
+ txOverrides
4372
+ });
4373
+ }
4374
+ /**
4375
+ * Deploy a new Panoptic pool and wait for confirmation.
4376
+ */
4377
+ async function deployNewPoolAndWait(params) {
4378
+ const result = await deployNewPool(params);
4379
+ return result.wait();
4380
+ }
4381
+
4382
+ //#endregion
4383
+ //#region src/panoptic/v2/writes/txManagement.ts
4384
+ /** Default gas price multiplier for replacement (12.5% bump = minimum for replacement) */
4385
+ const DEFAULT_GAS_PRICE_MULTIPLIER = 1.125;
4386
+ /**
4387
+ * Apply a multiplier to a bigint gas value.
4388
+ * Uses integer arithmetic to avoid floating point issues.
4389
+ */
4390
+ function applyMultiplier(value, multiplier) {
4391
+ const bps = BigInt(Math.ceil(multiplier * 1e4));
4392
+ return value * bps / 10000n;
4393
+ }
4394
+ /**
4395
+ * Compute bumped gas parameters from the original transaction.
4396
+ */
4397
+ function computeBumpedGas(originalMaxFeePerGas, originalMaxPriorityFeePerGas, explicitMaxFeePerGas, explicitMaxPriorityFeePerGas, multiplier) {
4398
+ const maxFeePerGas = explicitMaxFeePerGas ?? applyMultiplier(originalMaxFeePerGas ?? 0n, multiplier);
4399
+ const maxPriorityFeePerGas = explicitMaxPriorityFeePerGas ?? applyMultiplier(originalMaxPriorityFeePerGas ?? 0n, multiplier);
4400
+ return {
4401
+ maxFeePerGas,
4402
+ maxPriorityFeePerGas
4403
+ };
4404
+ }
4405
+ /**
4406
+ * Speed up a pending transaction by resubmitting with higher gas.
4407
+ *
4408
+ * Fetches the original transaction, extracts its parameters,
4409
+ * bumps the gas price, and resubmits with the same nonce.
4410
+ *
4411
+ * @param params - Speed up parameters
4412
+ * @returns TxResult for the replacement transaction
4413
+ * @throws Error if the original transaction is not found
4414
+ *
4415
+ * @example
4416
+ * ```typescript
4417
+ * // Speed up with default 12.5% bump
4418
+ * const result = await speedUpTransaction({
4419
+ * client,
4420
+ * walletClient,
4421
+ * hash: pendingTxHash,
4422
+ * })
4423
+ *
4424
+ * // Speed up with explicit gas prices
4425
+ * const result = await speedUpTransaction({
4426
+ * client,
4427
+ * walletClient,
4428
+ * hash: pendingTxHash,
4429
+ * maxFeePerGas: 50_000_000_000n, // 50 gwei
4430
+ * maxPriorityFeePerGas: 3_000_000_000n, // 3 gwei
4431
+ * })
4432
+ * ```
4433
+ */
4434
+ async function speedUpTransaction(params) {
4435
+ const { client, walletClient, hash, maxFeePerGas: explicitMaxFee, maxPriorityFeePerGas: explicitMaxPriority, gasPriceMultiplier = DEFAULT_GAS_PRICE_MULTIPLIER, broadcaster } = params;
4436
+ const tx = await client.getTransaction({ hash });
4437
+ const { maxFeePerGas, maxPriorityFeePerGas } = computeBumpedGas(tx.maxFeePerGas ?? void 0, tx.maxPriorityFeePerGas ?? void 0, explicitMaxFee, explicitMaxPriority, gasPriceMultiplier);
4438
+ if (broadcaster) {
4439
+ const request = await walletClient.prepareTransactionRequest({
4440
+ account: tx.from,
4441
+ to: tx.to ?? void 0,
4442
+ data: tx.input,
4443
+ value: tx.value,
4444
+ nonce: tx.nonce,
4445
+ gas: tx.gas,
4446
+ maxFeePerGas,
4447
+ maxPriorityFeePerGas,
4448
+ chain: walletClient.chain
4449
+ });
4450
+ const signedTx = await walletClient.signTransaction({
4451
+ ...request,
4452
+ account: tx.from
4453
+ });
4454
+ const replacementHash$1 = await broadcaster.broadcast(signedTx);
4455
+ return createTxResult(client, replacementHash$1);
4456
+ }
4457
+ const replacementHash = await walletClient.sendTransaction({
4458
+ account: tx.from,
4459
+ to: tx.to ?? void 0,
4460
+ data: tx.input,
4461
+ value: tx.value,
4462
+ nonce: tx.nonce,
4463
+ gas: tx.gas,
4464
+ maxFeePerGas,
4465
+ maxPriorityFeePerGas,
4466
+ chain: walletClient.chain
4467
+ });
4468
+ return createTxResult(client, replacementHash);
4469
+ }
4470
+ /**
4471
+ * Cancel a pending transaction by sending a 0-value self-transfer
4472
+ * with the same nonce and higher gas price.
4473
+ *
4474
+ * @param params - Cancel parameters
4475
+ * @returns TxResult for the cancellation transaction
4476
+ * @throws Error if the original transaction is not found
4477
+ *
4478
+ * @example
4479
+ * ```typescript
4480
+ * const result = await cancelTransaction({
4481
+ * client,
4482
+ * walletClient,
4483
+ * account,
4484
+ * hash: pendingTxHash,
4485
+ * })
4486
+ * await result.wait()
4487
+ * ```
4488
+ */
4489
+ async function cancelTransaction(params) {
4490
+ const { client, walletClient, account, hash, maxFeePerGas: explicitMaxFee, maxPriorityFeePerGas: explicitMaxPriority, gasPriceMultiplier = DEFAULT_GAS_PRICE_MULTIPLIER, broadcaster } = params;
4491
+ const tx = await client.getTransaction({ hash });
4492
+ const { maxFeePerGas, maxPriorityFeePerGas } = computeBumpedGas(tx.maxFeePerGas ?? void 0, tx.maxPriorityFeePerGas ?? void 0, explicitMaxFee, explicitMaxPriority, gasPriceMultiplier);
4493
+ if (broadcaster) {
4494
+ const request = await walletClient.prepareTransactionRequest({
4495
+ account,
4496
+ to: account,
4497
+ value: 0n,
4498
+ nonce: tx.nonce,
4499
+ gas: 21000n,
4500
+ maxFeePerGas,
4501
+ maxPriorityFeePerGas,
4502
+ chain: walletClient.chain
4503
+ });
4504
+ const signedTx = await walletClient.signTransaction({
4505
+ ...request,
4506
+ account
4507
+ });
4508
+ const cancelHash$1 = await broadcaster.broadcast(signedTx);
4509
+ return createTxResult(client, cancelHash$1);
4510
+ }
4511
+ const cancelHash = await walletClient.sendTransaction({
4512
+ account,
4513
+ to: account,
4514
+ value: 0n,
4515
+ nonce: tx.nonce,
4516
+ gas: 21000n,
4517
+ maxFeePerGas,
4518
+ maxPriorityFeePerGas,
4519
+ chain: walletClient.chain
4520
+ });
4521
+ return createTxResult(client, cancelHash);
4522
+ }
4523
+
4524
+ //#endregion
4525
+ //#region src/panoptic/v2/writes/loanUtils.ts
4526
+ /** Maximum retry attempts on InputListFail */
4527
+ const MAX_RETRIES = 3;
4528
+ /**
4529
+ * Resolve token index (0 or 1) for the given token address against the pool.
4530
+ * Throws SwapTokenMismatchError if the token isn't in the pool.
4531
+ */
4532
+ function resolveTokenIndex(tokenAddress, token0, token1) {
4533
+ const lower = tokenAddress.toLowerCase();
4534
+ if (lower === token0.toLowerCase()) return 0n;
4535
+ if (lower === token1.toLowerCase()) return 1n;
4536
+ throw new SwapTokenMismatchError(tokenAddress, token0, token1);
4537
+ }
4538
+ /**
4539
+ * Resolve existing position IDs from explicit param or storage.
4540
+ */
4541
+ async function resolvePositionIds(explicit, storage, chainId, poolAddress, account) {
4542
+ if (explicit !== void 0) return explicit;
4543
+ if (storage) return getTrackedPositionIds({
4544
+ chainId,
4545
+ poolAddress,
4546
+ account,
4547
+ storage
4548
+ });
4549
+ throw new MissingPositionIdsError();
4550
+ }
4551
+ /**
4552
+ * Check if a caught error is an InputListFail contract revert.
4553
+ * Works with both raw viem errors and parsed PanopticError instances.
4554
+ */
4555
+ function isInputListFailError(error) {
4556
+ if (error instanceof InputListFailError) return true;
4557
+ const parsed = parsePanopticError(error);
4558
+ return parsed?.errorName === "InputListFail";
4559
+ }
4560
+ /**
4561
+ * Build a unique width=0 tokenId that doesn't collide with existing positions.
4562
+ * Bumps optionRatio (1-127) and returns adjusted size to maintain equivalent exposure.
4563
+ *
4564
+ * @param kind - Whether to build a loan (borrow) or a credit (pay-in) leg.
4565
+ * @param asset - Which token denominates the positionSize (0 or 1).
4566
+ */
4567
+ function buildUniqueWidthZeroLeg(kind, poolId, asset, tokenType, currentTick, tickSpacing, existingPositionIds, positionSize) {
4568
+ const addLeg = (strike$1, optionRatio) => {
4569
+ const builder = createTokenIdBuilder(poolId);
4570
+ const config = {
4571
+ asset,
4572
+ tokenType,
4573
+ strike: strike$1,
4574
+ optionRatio
4575
+ };
4576
+ return (kind === "loan" ? builder.addLoan(config) : builder.addCredit(config)).build();
4577
+ };
4578
+ const mod = currentTick % tickSpacing;
4579
+ let strike = currentTick - (mod + tickSpacing) % tickSpacing;
4580
+ for (let ratio = 1n; ratio <= 127n; ratio++) {
4581
+ const tokenId = addLeg(strike, ratio);
4582
+ if (!existingPositionIds.includes(tokenId)) {
4583
+ const adjustedSize = positionSize / ratio;
4584
+ if (adjustedSize === 0n || positionSize % ratio !== 0n) continue;
4585
+ return {
4586
+ tokenId,
4587
+ adjustedSize
4588
+ };
4589
+ }
4590
+ }
4591
+ const mod2 = currentTick % tickSpacing;
4592
+ strike = currentTick - (mod2 + tickSpacing) % tickSpacing + tickSpacing;
4593
+ for (let i = 0; i < 100; i++) {
4594
+ const tokenId = addLeg(strike);
4595
+ if (!existingPositionIds.includes(tokenId)) return {
4596
+ tokenId,
4597
+ adjustedSize: positionSize
4598
+ };
4599
+ strike += tickSpacing;
4600
+ }
4601
+ throw new LoanSlotExhaustedError();
4602
+ }
4603
+ /**
4604
+ * Build a unique loan (borrow) tokenId that doesn't collide with existing positions.
4605
+ *
4606
+ * @param asset - Which token denominates the positionSize (0 or 1).
4607
+ */
4608
+ function buildUniqueLoan(poolId, asset, tokenType, currentTick, tickSpacing, existingPositionIds, positionSize) {
4609
+ return buildUniqueWidthZeroLeg("loan", poolId, asset, tokenType, currentTick, tickSpacing, existingPositionIds, positionSize);
4610
+ }
4611
+ /**
4612
+ * Build a unique credit (pay-in) tokenId that doesn't collide with existing positions.
4613
+ *
4614
+ * Unlike a loan, a credit never borrows from the pool: it requires **zero** buying
4615
+ * power, leaves `s_assetsInAMM` (and therefore utilization and the borrow rate)
4616
+ * untouched, and accrues no interest. This is what lets a credit-based swap run
4617
+ * against a fully-utilized collateral tracker.
4618
+ *
4619
+ * @param asset - Which token denominates the positionSize (0 or 1).
4620
+ */
4621
+ function buildUniqueCredit(poolId, asset, tokenType, currentTick, tickSpacing, existingPositionIds, positionSize) {
4622
+ return buildUniqueWidthZeroLeg("credit", poolId, asset, tokenType, currentTick, tickSpacing, existingPositionIds, positionSize);
4623
+ }
4624
+
4625
+ //#endregion
4626
+ //#region src/panoptic/v2/writes/swap.ts
4627
+ /** Build an atomic credit mint/burn swap without fetching or sending anything. */
4628
+ function buildCreditSwapCall(params) {
4629
+ const amount = params.kind === "exactIn" ? params.amountIn : params.amountOut;
4630
+ if (amount <= 0n) throw new Error("credit swap amount must be positive");
4631
+ if (params.tokenIndex !== 0n && params.tokenIndex !== 1n) throw new Error("credit swap tokenIndex must be 0 or 1");
4632
+ const { low, high } = tickLimits(params.currentTick, params.slippageBps);
4633
+ const { tokenId: creditTokenId, adjustedSize } = buildUniqueCredit(params.poolId, params.tokenIndex, params.tokenIndex, params.currentTick, params.tickSpacing, params.existingPositionIds, amount);
4634
+ const ascending = [
4635
+ Number(low),
4636
+ Number(high),
4637
+ 0
4638
+ ];
4639
+ const descending = [
4640
+ Number(high),
4641
+ Number(low),
4642
+ 0
4643
+ ];
4644
+ const limits = params.kind === "exactIn" ? [ascending, descending] : [descending, ascending];
4645
+ const args = [
4646
+ [creditTokenId, creditTokenId],
4647
+ [...params.existingPositionIds],
4648
+ [adjustedSize, 0n],
4649
+ limits,
4650
+ false,
4651
+ params.builderCode ?? 0n
4652
+ ];
4653
+ return {
4654
+ to: params.poolAddress,
4655
+ data: encodeFunctionData({
4656
+ abi: panopticPoolV2Abi,
4657
+ functionName: "dispatch",
4658
+ args
4659
+ }),
4660
+ creditTokenId,
4661
+ adjustedSize,
4662
+ args
4663
+ };
4664
+ }
4665
+ /**
4666
+ * Swap tokens using Panoptic's exact-output mechanism.
4667
+ *
4668
+ * Opens a credit in `tokenOut` with `swapAtMint=true` (paying a swapped amount of
4669
+ * the other token), then burns it with `swapAtMint=false` to receive exactly
4670
+ * `amountOut` of `tokenOut`.
4671
+ *
4672
+ * @param params - Swap parameters
4673
+ * @returns TxResult
4674
+ *
4675
+ * @example
4676
+ * ```typescript
4677
+ * const result = await swapExactOut({
4678
+ * client, walletClient, account, poolAddress,
4679
+ * chainId: 11155111n,
4680
+ * tokenOut: WETH_ADDRESS,
4681
+ * amountOut: 5n * 10n**16n, // 0.05 WETH
4682
+ * slippageBps: 500n, // 5% slippage
4683
+ * })
4684
+ * const receipt = await result.wait()
4685
+ * ```
4686
+ */
4687
+ async function swapExactOut(params) {
4688
+ const { client, walletClient, account, poolAddress, chainId, tokenOut, amountOut, slippageBps, existingPositionIds: explicitIds, storage, builderCode = 0n, txOverrides } = params;
4689
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
4690
+ const [pool, positionIds] = await Promise.all([getPool({
4691
+ client,
4692
+ poolAddress,
4693
+ chainId
4694
+ }), resolvePositionIds(explicitIds, storage, chainId, poolAddress, account)]);
4695
+ const token0 = pool.collateralTracker0.token;
4696
+ const token1 = pool.collateralTracker1.token;
4697
+ const tokenOutIndex = resolveTokenIndex(tokenOut, token0, token1);
4698
+ const call = buildCreditSwapCall({
4699
+ kind: "exactOut",
4700
+ poolAddress,
4701
+ poolId: pool.poolId,
4702
+ currentTick: pool.currentTick,
4703
+ tickSpacing: pool.tickSpacing,
4704
+ existingPositionIds: positionIds,
4705
+ tokenIndex: tokenOutIndex,
4706
+ amountOut,
4707
+ slippageBps,
4708
+ builderCode
4709
+ });
4710
+ try {
4711
+ return await submitWrite({
4712
+ client,
4713
+ walletClient,
4714
+ account,
4715
+ address: poolAddress,
4716
+ abi: panopticPoolV2Abi,
4717
+ functionName: "dispatch",
4718
+ args: call.args,
4719
+ txOverrides
4720
+ });
4721
+ } catch (error) {
4722
+ if (isInputListFailError(error) && attempt < MAX_RETRIES - 1) continue;
4723
+ throw error;
4724
+ }
4725
+ }
4726
+ throw new MaxRetriesExceededError("swapExactOut");
4727
+ }
4728
+ /**
4729
+ * Swap exact output and wait for confirmation.
4730
+ */
4731
+ async function swapExactOutAndWait(params) {
4732
+ const result = await swapExactOut(params);
4733
+ return result.wait();
4734
+ }
4735
+ /**
4736
+ * Swap tokens using Panoptic's exact-input mechanism.
4737
+ *
4738
+ * Opens a credit in `tokenIn` with `swapAtMint=false` (paying exactly `amountIn`),
4739
+ * then burns it with `swapAtMint=true` to receive the swapped amount of the other
4740
+ * token. The user spends exactly `amountIn` of `tokenIn`.
4741
+ *
4742
+ * @param params - Swap parameters
4743
+ * @returns TxResult
4744
+ *
4745
+ * @example
4746
+ * ```typescript
4747
+ * const result = await swapExactIn({
4748
+ * client, walletClient, account, poolAddress,
4749
+ * chainId: 11155111n,
4750
+ * tokenIn: USDC_ADDRESS,
4751
+ * amountIn: 1000n * 10n**6n, // 1000 USDC
4752
+ * slippageBps: 500n,
4753
+ * })
4754
+ * const receipt = await result.wait()
4755
+ * ```
4756
+ */
4757
+ async function swapExactIn(params) {
4758
+ const { client, walletClient, account, poolAddress, chainId, tokenIn, amountIn, slippageBps, existingPositionIds: explicitIds, storage, builderCode = 0n, txOverrides } = params;
4759
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
4760
+ const [pool, positionIds] = await Promise.all([getPool({
4761
+ client,
4762
+ poolAddress,
4763
+ chainId
4764
+ }), resolvePositionIds(explicitIds, storage, chainId, poolAddress, account)]);
4765
+ const token0 = pool.collateralTracker0.token;
4766
+ const token1 = pool.collateralTracker1.token;
4767
+ const tokenInIndex = resolveTokenIndex(tokenIn, token0, token1);
4768
+ const call = buildCreditSwapCall({
4769
+ kind: "exactIn",
4770
+ poolAddress,
4771
+ poolId: pool.poolId,
4772
+ currentTick: pool.currentTick,
4773
+ tickSpacing: pool.tickSpacing,
4774
+ existingPositionIds: positionIds,
4775
+ tokenIndex: tokenInIndex,
4776
+ amountIn,
4777
+ slippageBps,
4778
+ builderCode
4779
+ });
4780
+ try {
4781
+ return await submitWrite({
4782
+ client,
4783
+ walletClient,
4784
+ account,
4785
+ address: poolAddress,
4786
+ abi: panopticPoolV2Abi,
4787
+ functionName: "dispatch",
4788
+ args: call.args,
4789
+ txOverrides
4790
+ });
4791
+ } catch (error) {
4792
+ if (isInputListFailError(error) && attempt < MAX_RETRIES - 1) continue;
4793
+ throw error;
4794
+ }
4795
+ }
4796
+ throw new MaxRetriesExceededError("swapExactIn");
4797
+ }
4798
+ /**
4799
+ * Swap exact input and wait for confirmation.
4800
+ */
4801
+ async function swapExactInAndWait(params) {
4802
+ const result = await swapExactIn(params);
4803
+ return result.wait();
4804
+ }
4805
+
4806
+ //#endregion
4807
+ //#region src/panoptic/v2/writes/wrap.ts
4808
+ /** Minimal ERC4626 surface used by the xStock wrappers. */
4809
+ const xstockWrapperAbi = [
4810
+ {
4811
+ type: "function",
4812
+ name: "deposit",
4813
+ stateMutability: "nonpayable",
4814
+ inputs: [{
4815
+ name: "assets",
4816
+ type: "uint256"
4817
+ }, {
4818
+ name: "receiver",
4819
+ type: "address"
4820
+ }],
4821
+ outputs: [{
4822
+ name: "shares",
4823
+ type: "uint256"
4824
+ }]
4825
+ },
4826
+ {
4827
+ type: "function",
4828
+ name: "redeem",
4829
+ stateMutability: "nonpayable",
4830
+ inputs: [
4831
+ {
4832
+ name: "shares",
4833
+ type: "uint256"
4834
+ },
4835
+ {
4836
+ name: "receiver",
4837
+ type: "address"
4838
+ },
4839
+ {
4840
+ name: "owner",
4841
+ type: "address"
4842
+ }
4843
+ ],
4844
+ outputs: [{
4845
+ name: "assets",
4846
+ type: "uint256"
4847
+ }]
4848
+ },
4849
+ {
4850
+ type: "function",
4851
+ name: "asset",
4852
+ stateMutability: "view",
4853
+ inputs: [],
4854
+ outputs: [{ type: "address" }]
4855
+ },
4856
+ {
4857
+ type: "function",
4858
+ name: "previewDeposit",
4859
+ stateMutability: "view",
4860
+ inputs: [{
4861
+ name: "assets",
4862
+ type: "uint256"
4863
+ }],
4864
+ outputs: [{
4865
+ name: "shares",
4866
+ type: "uint256"
4867
+ }]
4868
+ },
4869
+ {
4870
+ type: "function",
4871
+ name: "previewRedeem",
4872
+ stateMutability: "view",
4873
+ inputs: [{
4874
+ name: "shares",
4875
+ type: "uint256"
4876
+ }],
4877
+ outputs: [{
4878
+ name: "assets",
4879
+ type: "uint256"
4880
+ }]
4881
+ },
4882
+ {
4883
+ type: "function",
4884
+ name: "maxRedeem",
4885
+ stateMutability: "view",
4886
+ inputs: [{
4887
+ name: "owner",
4888
+ type: "address"
4889
+ }],
4890
+ outputs: [{
4891
+ name: "maxShares",
4892
+ type: "uint256"
4893
+ }]
4894
+ }
4895
+ ];
4896
+ /**
4897
+ * Wrap an underlying xStock into its ERC4626 wrapper (`deposit`). Requires a
4898
+ * prior ERC20 approval of the underlying to the wrapper address.
4899
+ *
4900
+ * @returns TxResult with hash and wait function
4901
+ */
4902
+ async function wrapXstock(params) {
4903
+ const { client, walletClient, account, wrapper, assets, receiver = account, txOverrides } = params;
4904
+ return submitWrite({
4905
+ client,
4906
+ walletClient,
4907
+ account,
4908
+ address: wrapper,
4909
+ abi: xstockWrapperAbi,
4910
+ functionName: "deposit",
4911
+ args: [assets, receiver],
4912
+ txOverrides
4913
+ });
4914
+ }
4915
+ /** Wrap and wait for confirmation. */
4916
+ async function wrapXstockAndWait(params) {
4917
+ const result = await wrapXstock(params);
4918
+ return result.wait();
4919
+ }
4920
+ /**
4921
+ * Unwrap wrapper shares back into the underlying xStock (`redeem`). When
4922
+ * `owner` defaults to (or equals) `account` the caller burns their own shares
4923
+ * and no approval is needed. Passing a different `owner` redeems on its behalf
4924
+ * and requires that owner to have granted the caller an ERC-4626 share
4925
+ * allowance (`approve`), or the `redeem` call reverts.
4926
+ *
4927
+ * @returns TxResult with hash and wait function
4928
+ */
4929
+ async function unwrapXstock(params) {
4930
+ const { client, walletClient, account, wrapper, shares, receiver = account, owner = account, txOverrides } = params;
4931
+ return submitWrite({
4932
+ client,
4933
+ walletClient,
4934
+ account,
4935
+ address: wrapper,
4936
+ abi: xstockWrapperAbi,
4937
+ functionName: "redeem",
4938
+ args: [
4939
+ shares,
4940
+ receiver,
4941
+ owner
4942
+ ],
4943
+ txOverrides
4944
+ });
4945
+ }
4946
+ /** Unwrap and wait for confirmation. */
4947
+ async function unwrapXstockAndWait(params) {
4948
+ const result = await unwrapXstock(params);
4949
+ return result.wait();
4950
+ }
4951
+ /**
4952
+ * Preview the wrapper shares minted for a given amount of underlying xStock.
4953
+ */
4954
+ async function previewWrap(params) {
4955
+ const { client, wrapper, amount } = params;
4956
+ return client.readContract({
4957
+ address: wrapper,
4958
+ abi: xstockWrapperAbi,
4959
+ functionName: "previewDeposit",
4960
+ args: [amount]
4961
+ });
4962
+ }
4963
+ /**
4964
+ * Preview the underlying xStock returned for a given amount of wrapper shares.
4965
+ */
4966
+ async function previewUnwrap(params) {
4967
+ const { client, wrapper, amount } = params;
4968
+ return client.readContract({
4969
+ address: wrapper,
4970
+ abi: xstockWrapperAbi,
4971
+ functionName: "previewRedeem",
4972
+ args: [amount]
4973
+ });
4974
+ }
4975
+ /**
4976
+ * Minimal canonical WETH9 surface used for ETH<->WETH wrapping. Unlike the
4977
+ * ERC4626 xStock wrapper, WETH is a 1:1 wrapper: `deposit` is payable and wraps
4978
+ * `msg.value`, `withdraw` unwraps an exact amount back to native ETH.
4979
+ */
4980
+ const wethWrapAbi = [{
4981
+ type: "function",
4982
+ name: "deposit",
4983
+ stateMutability: "payable",
4984
+ inputs: [],
4985
+ outputs: []
4986
+ }, {
4987
+ type: "function",
4988
+ name: "withdraw",
4989
+ stateMutability: "nonpayable",
4990
+ inputs: [{
4991
+ name: "wad",
4992
+ type: "uint256"
4993
+ }],
4994
+ outputs: []
4995
+ }];
4996
+ /**
4997
+ * Wrap native ETH into WETH (`deposit` payable). No approval needed.
4998
+ *
4999
+ * @returns TxResult with hash and wait function
5000
+ */
5001
+ async function wrapEth(params) {
5002
+ const { client, walletClient, account, weth, amount, txOverrides } = params;
5003
+ return submitWrite({
5004
+ client,
5005
+ walletClient,
5006
+ account,
5007
+ address: weth,
5008
+ abi: wethWrapAbi,
5009
+ functionName: "deposit",
5010
+ args: [],
5011
+ value: amount,
5012
+ txOverrides
5013
+ });
5014
+ }
5015
+ /** Wrap ETH and wait for confirmation. */
5016
+ async function wrapEthAndWait(params) {
5017
+ const result = await wrapEth(params);
5018
+ return result.wait();
5019
+ }
5020
+ /**
5021
+ * Unwrap WETH back into native ETH (`withdraw`). Burns the caller's own WETH —
5022
+ * no approval needed.
5023
+ *
5024
+ * @returns TxResult with hash and wait function
5025
+ */
5026
+ async function unwrapWeth(params) {
5027
+ const { client, walletClient, account, weth, amount, txOverrides } = params;
5028
+ return submitWrite({
5029
+ client,
5030
+ walletClient,
5031
+ account,
5032
+ address: weth,
5033
+ abi: wethWrapAbi,
5034
+ functionName: "withdraw",
5035
+ args: [amount],
5036
+ txOverrides
5037
+ });
5038
+ }
5039
+ /** Unwrap WETH and wait for confirmation. */
5040
+ async function unwrapWethAndWait(params) {
5041
+ const result = await unwrapWeth(params);
5042
+ return result.wait();
5043
+ }
5044
+
3134
5045
  //#endregion
3135
5046
  //#region src/panoptic/v2/simulations/simulateOpenPosition.ts
3136
5047
  /**
@@ -3829,4 +5740,4 @@ async function smartRepayAndWait(params) {
3829
5740
  }
3830
5741
 
3831
5742
  //#endregion
3832
- export { addLegToTokenId, approve, approveAndWait, approvePool, borrow, borrowAndWait, buildBatchDispatchArgs, buildCreditSwapCall, buildOpenPositionCalldata, buildSettleSequenceCalls, buildUniqueCredit, buildUniqueLoan, buildUniqueWidthZeroLeg, calculateResyncBlock, cancelTransaction, checkApproval, clearCheckpoint, closePosition, closePositionAndWait, countLegs, createNonceManager, createTokenIdBuilder, decodeAllLegs, decodeLeg, decodePoolId, decodeTickSpacing as decodeTickSpacing$1, decodeTokenId, decodeVegoid, deployNewPool, deployNewPoolAndWait, deposit, depositAndWait, detectReorg, dispatch, dispatchAndWait, encodeLeg, encodePoolId, encodeV4PoolId, executeBatchDispatch, executeBatchDispatchAndWait, executeSettleSequence, executeSettleSequenceAndWait, forceExercise, forceExerciseAndWait, getAssetIndex, getCurrentPositionSizes, getPoolDeploymentBlock, getPoolTokensForSimulation, hasLoanOrCredit, hasLongLeg, isCredit, isCreditLeg, isInputListFailError, isLoan, isLoanLeg, isShortOnly, isSpread, liquidate, liquidateAndWait, loadCheckpoint, mint, mintAndWait, openPosition, openPositionAndWait, orderListForSettle, pokeOracle, pokeOracleAndWait, previewBorrow, previewUnwrap, previewWrap, publicBroadcaster, reconstructFromEvents, redeem, redeemAndWait, repay, repayAndWait, resolveTokenIndex, rollPosition, rollPositionAndWait, saveCheckpoint, settleAccumulatedPremia, settleAccumulatedPremiaAndWait, settlePremiumFrom, settlePremiumFromAndWait, simulateOpenPosition, simulateWithTokenFlow, smartRepay, smartRepayAndWait, speedUpTransaction, supply, supplyAndWait, swapExactIn, swapExactInAndWait, swapExactOut, swapExactOutAndWait, syncPositions, unsupply, unsupplyAndWait, unwrapWeth, unwrapWethAndWait, unwrapXstock, unwrapXstockAndWait, validateBatch, validatePoolId, verifyBlockContinuity, wethWrapAbi, withdraw, withdrawAndWait, withdrawWithPositions, withdrawWithPositionsAndWait, wrapEth, wrapEthAndWait, wrapXstock, wrapXstockAndWait, xstockWrapperAbi };
5743
+ export { addLegToTokenId, approve, approveAndWait, approvePool, borrow, borrowAndWait, buildBatchDispatchArgs, buildCreditSwapCall, buildOpenPositionCalldata, buildProtectedSettleDispatch, buildProtectedSettlePlan, buildSettleSequenceCalls, buildUniqueCredit, buildUniqueLoan, buildUniqueWidthZeroLeg, calculateResyncBlock, cancelTransaction, checkApproval, clearCheckpoint, closePosition, closePositionAndWait, countLegs, createNonceManager, createPoolFormatters, createTokenIdBuilder, decodeAllLegs, decodeLeg, decodePoolId, decodeTickSpacing$1, decodeTokenId, decodeVegoid, deployNewPool, deployNewPoolAndWait, deposit, depositAndWait, deriveUniqueTokenId, detectReorg, dispatch, dispatchAndWait, encodeLeg, encodePoolId, encodeV4PoolId, executeBatchDispatch, executeBatchDispatchAndWait, executeSettleSequence, executeSettleSequenceAndWait, forceExercise, forceExerciseAndWait, formatBlockNumber, formatBps, formatCompact, formatDatetime, formatDuration, formatDurationSeconds, formatFeeTier, formatGas, formatGwei, formatPoolIdHex, formatRatioPercent, formatTimestamp, formatTimestampLocale, formatTokenIdHex, formatTokenIdShort, formatTxHash, formatUtilization, formatWei, generateOverlappingTokenIds, getAccountPremia, getAssetIndex, getForfeitablePremium, getPoolDeploymentBlock, getPoolDisplayId, getPoolTokensForSimulation, getPositionsWithPremia, getTokenListId, hasLoanOrCredit, hasLongLeg, isCredit, isCreditLeg, isInputListFailError, isLoan, isLoanLeg, isShortOnly, isSpread, liquidate, liquidateAndWait, loadCheckpoint, mint, mintAndWait, openPosition, openPositionAndWait, orderListForSettle, parseBps, parseTokenListId, planDeriveStrategy, pokeOracle, pokeOracleAndWait, previewBorrow, previewUnwrap, previewWrap, publicBroadcaster, reconstructFromEvents, redeem, redeemAndWait, repay, repayAndWait, resolveTokenIndex, rollPosition, rollPositionAndWait, saveCheckpoint, settleAccumulatedPremia, settleAccumulatedPremiaAndWait, settlePremiumFrom, settlePremiumFromAndWait, simulateOpenPosition, simulateSettle, simulateSettlePremiumBatch, simulateSettlePremiumFrom, simulateSettleSequence, simulateWithTokenFlow, smartRepay, smartRepayAndWait, speedUpTransaction, splitTokenIdByTimescale, supply, supplyAndWait, swapExactIn, swapExactInAndWait, swapExactOut, swapExactOutAndWait, syncPositions, truncateAddress, unsupply, unsupplyAndWait, unwrapWeth, unwrapWethAndWait, unwrapXstock, unwrapXstockAndWait, validateBatch, validatePoolId, verifyBlockContinuity, wethWrapAbi, withdraw, withdrawAndWait, withdrawWithPositions, withdrawWithPositionsAndWait, wrapEth, wrapEthAndWait, wrapXstock, wrapXstockAndWait, xstockWrapperAbi };