@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.
- package/dist/cow/index.js +48 -4
- package/dist/cow/index.js.map +1 -1
- package/dist/{cow-DIYBU1j9.js → cow-BPcuMjYR.js} +1 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +203 -162
- package/dist/index.js.map +1 -1
- package/dist/{irm-CvLqxTAo.js → irm-C7QhFiwL.js} +7 -246
- package/dist/panoptic/v2/index.d.ts +167 -32
- package/dist/panoptic/v2/index.d.ts.map +1 -1
- package/dist/panoptic/v2/index.js +1562 -930
- package/dist/panoptic/v2/index.js.map +1 -1
- package/dist/panoptic/v2/react-public.d.ts +167 -32
- package/dist/panoptic/v2/react-public.d.ts.map +1 -1
- package/dist/panoptic/v2/react-public.js +1164 -528
- package/dist/panoptic/v2/react-public.js.map +1 -1
- package/dist/{getTrackedPositionIds-CwBHzOO2.js → rates-5oQD-SuM.js} +256 -4
- package/dist/{router-OcmdMkXx.js → router-C26q7i8w.js} +2 -2
- package/dist/{router-CUlQfzoc.js → router-ClhAkKjh.js} +1 -1
- package/dist/uniswap/index.js +59 -15
- package/dist/uniswap/index.js.map +1 -1
- package/dist/{v2-DeGgMN2a.js → v2-DVcophLf.js} +1379 -2671
- package/dist/{writes-BZW7UA4N.js → writes-DBHwfbDD.js} +2855 -944
- package/dist/zodiac/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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 "./
|
|
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/
|
|
1576
|
-
const BIT_MASK_128 = (1n << 128n) - 1n;
|
|
1575
|
+
//#region src/panoptic/v2/formatters/percentage.ts
|
|
1577
1576
|
/**
|
|
1578
|
-
*
|
|
1579
|
-
*
|
|
1580
|
-
*
|
|
1577
|
+
* Percentage and ratio formatters.
|
|
1578
|
+
*
|
|
1579
|
+
* All formatters require explicit precision - no hidden defaults.
|
|
1580
|
+
*
|
|
1581
|
+
* @module v2/formatters/percentage
|
|
1581
1582
|
*/
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
if (
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
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
|
-
*
|
|
1599
|
+
* Format basis points as a percentage string.
|
|
1600
|
+
* 100 bps = 1%
|
|
1603
1601
|
*
|
|
1604
|
-
*
|
|
1605
|
-
*
|
|
1602
|
+
* @param bps - Basis points value
|
|
1603
|
+
* @param precision - Number of decimal places to display
|
|
1604
|
+
* @returns Formatted percentage string
|
|
1606
1605
|
*
|
|
1607
|
-
* @
|
|
1608
|
-
*
|
|
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
|
-
*
|
|
1613
|
-
*
|
|
1614
|
-
*
|
|
1615
|
-
*
|
|
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
|
-
|
|
1623
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1662
|
-
const
|
|
1663
|
-
|
|
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/
|
|
1690
|
+
//#region src/panoptic/v2/formatters/poolFormatters.ts
|
|
1668
1691
|
/**
|
|
1669
|
-
*
|
|
1692
|
+
* Create pool-bound formatters that capture token decimals.
|
|
1670
1693
|
*
|
|
1671
|
-
*
|
|
1672
|
-
*
|
|
1694
|
+
* Use this factory when working with a single pool to avoid passing
|
|
1695
|
+
* decimals at every call site.
|
|
1673
1696
|
*
|
|
1674
|
-
* @param
|
|
1675
|
-
* @returns
|
|
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
|
-
*
|
|
1681
|
-
*
|
|
1682
|
-
*
|
|
1683
|
-
*
|
|
1684
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
1690
|
-
const {
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
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
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1720
|
-
|
|
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
|
-
*
|
|
1788
|
+
* Parse a token list ID back to chain ID and address.
|
|
1728
1789
|
*
|
|
1729
|
-
* @param
|
|
1730
|
-
* @returns
|
|
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
|
-
|
|
1733
|
-
const
|
|
1734
|
-
if (
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
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/
|
|
1781
|
-
|
|
1782
|
-
|
|
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
|
-
*
|
|
1785
|
-
*
|
|
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
|
|
1788
|
-
const
|
|
1789
|
-
|
|
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
|
-
*
|
|
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
|
|
1795
|
-
const
|
|
1796
|
-
|
|
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
|
-
*
|
|
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
|
|
1809
|
-
* @returns
|
|
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
|
-
* //
|
|
1815
|
-
*
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
*
|
|
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
|
-
*
|
|
1822
|
-
*
|
|
1823
|
-
*
|
|
1824
|
-
*
|
|
1825
|
-
*
|
|
1826
|
-
*
|
|
1827
|
-
*
|
|
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
|
-
|
|
1832
|
-
const
|
|
1833
|
-
|
|
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
|
-
*
|
|
1869
|
-
* with the same nonce and higher gas price.
|
|
1932
|
+
* Format a duration in milliseconds as a human-readable string.
|
|
1870
1933
|
*
|
|
1871
|
-
* @param
|
|
1872
|
-
* @returns
|
|
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
|
-
*
|
|
1878
|
-
*
|
|
1879
|
-
*
|
|
1880
|
-
*
|
|
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
|
-
|
|
1887
|
-
const
|
|
1888
|
-
const
|
|
1889
|
-
const
|
|
1890
|
-
if (
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
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
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
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/
|
|
2221
|
-
/** Maximum retry attempts on InputListFail */
|
|
2222
|
-
const MAX_RETRIES = 3;
|
|
2395
|
+
//#region src/panoptic/v2/tokenId/decode.ts
|
|
2223
2396
|
/**
|
|
2224
|
-
*
|
|
2225
|
-
*
|
|
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
|
|
2228
|
-
const
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
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
|
-
*
|
|
2248
|
-
*
|
|
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
|
|
2251
|
-
|
|
2252
|
-
|
|
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
|
-
*
|
|
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
|
|
2260
|
-
* @
|
|
2463
|
+
* @param tokenId - The TokenId to check
|
|
2464
|
+
* @returns True if any leg is long
|
|
2261
2465
|
*/
|
|
2262
|
-
function
|
|
2263
|
-
const
|
|
2264
|
-
|
|
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
|
-
*
|
|
2471
|
+
* Check if a TokenId represents a short-only position.
|
|
2300
2472
|
*
|
|
2301
|
-
* @param
|
|
2473
|
+
* @param tokenId - The TokenId to check
|
|
2474
|
+
* @returns True if all legs are short
|
|
2302
2475
|
*/
|
|
2303
|
-
function
|
|
2304
|
-
|
|
2476
|
+
function isShortOnly(tokenId) {
|
|
2477
|
+
const legs = decodeAllLegs(tokenId);
|
|
2478
|
+
return legs.length > 0 && legs.every((leg) => !leg.isLong);
|
|
2305
2479
|
}
|
|
2306
2480
|
/**
|
|
2307
|
-
*
|
|
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
|
|
2483
|
+
* @param tokenId - The TokenId to check
|
|
2484
|
+
* @returns True if any leg has a different risk partner
|
|
2315
2485
|
*/
|
|
2316
|
-
function
|
|
2317
|
-
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
2371
|
-
*
|
|
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
|
-
|
|
2383
|
-
const
|
|
2384
|
-
|
|
2385
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
2427
|
-
|
|
2428
|
-
return result.wait();
|
|
2509
|
+
function isLoanLeg(leg) {
|
|
2510
|
+
return leg.width === 0n && !leg.isLong;
|
|
2429
2511
|
}
|
|
2430
2512
|
/**
|
|
2431
|
-
*
|
|
2513
|
+
* Check if a leg is a credit (width=0 and isLong=true).
|
|
2432
2514
|
*
|
|
2433
|
-
*
|
|
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
|
|
2438
|
-
* @returns
|
|
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
|
-
* @
|
|
2441
|
-
*
|
|
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
|
-
|
|
2453
|
-
const
|
|
2454
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
2497
|
-
const
|
|
2498
|
-
return
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
2546
|
+
* @param tokenId - The TokenId to check
|
|
2547
|
+
* @returns True if any leg has width=0 (loan or credit)
|
|
2596
2548
|
*/
|
|
2597
|
-
|
|
2598
|
-
const
|
|
2599
|
-
return
|
|
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
|
-
*
|
|
2617
|
-
*
|
|
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
|
-
|
|
2625
|
-
|
|
2626
|
-
return
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
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
|
-
*
|
|
2672
|
-
*
|
|
2673
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
2697
|
-
const
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
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
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
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
|
-
*
|
|
2717
|
-
* no approval needed.
|
|
2683
|
+
* Derive a tokenId unique from `baseTokenId` for a partial reduction.
|
|
2718
2684
|
*
|
|
2719
|
-
*
|
|
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
|
-
|
|
2722
|
-
const {
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
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
|
-
/**
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
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/
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
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
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
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
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
*
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
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
|
-
|
|
2788
|
-
|
|
2789
|
-
vegoid,
|
|
2790
|
-
tickSpacing,
|
|
2791
|
-
legs,
|
|
2792
|
-
legCount: numLegs
|
|
2768
|
+
numerator,
|
|
2769
|
+
denominator
|
|
2793
2770
|
};
|
|
2794
2771
|
}
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
*
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
return
|
|
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
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
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
|
-
*
|
|
2818
|
-
*
|
|
2819
|
-
*
|
|
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
|
|
2823
|
-
|
|
2824
|
-
|
|
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
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
function
|
|
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
|
-
|
|
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
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
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
|
-
*
|
|
2849
|
-
*
|
|
2850
|
-
*
|
|
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
|
-
*
|
|
2853
|
-
*
|
|
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
|
|
2856
|
-
|
|
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
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
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
|
-
*
|
|
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
|
|
2876
|
-
const
|
|
2877
|
-
|
|
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
|
-
*
|
|
3168
|
+
* Build the atomic self-settlement dispatch that temporarily pokes each
|
|
3169
|
+
* affected short chunk before settling its source position.
|
|
2881
3170
|
*
|
|
2882
|
-
* @param
|
|
2883
|
-
* @returns
|
|
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
|
|
2886
|
-
|
|
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
|
-
*
|
|
2891
|
-
*
|
|
2892
|
-
*
|
|
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
|
|
2896
|
-
const
|
|
2897
|
-
|
|
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/
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2950
|
-
*
|
|
2951
|
-
*
|
|
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 -
|
|
2959
|
-
* @returns
|
|
3212
|
+
* @param params - The parameters
|
|
3213
|
+
* @returns Premia totals with block metadata
|
|
2960
3214
|
*
|
|
2961
3215
|
* @example
|
|
2962
3216
|
* ```typescript
|
|
2963
|
-
* const
|
|
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
|
-
*
|
|
2973
|
-
*
|
|
3220
|
+
* account,
|
|
3221
|
+
* tokenIds: [tokenId1, tokenId2],
|
|
3222
|
+
* includePendingPremium: true,
|
|
2974
3223
|
* })
|
|
2975
3224
|
*
|
|
2976
|
-
*
|
|
2977
|
-
*
|
|
2978
|
-
*
|
|
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
|
|
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 };
|