@pafi-dev/trading 0.9.0 → 0.10.1

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/index.js CHANGED
@@ -3,6 +3,7 @@ import { getAddress } from "viem";
3
3
  import {
4
4
  buildPerpDepositWithGasDeduction,
5
5
  buildPerpDepositViaRelay,
6
+ buildErc20TransferUserOp,
6
7
  ORDERLY_RELAY_ABI,
7
8
  getContractAddresses,
8
9
  UNIVERSAL_ROUTER_ADDRESSES,
@@ -13,7 +14,8 @@ import {
13
14
  ValidationError,
14
15
  computeAccountId,
15
16
  quoteOperatorFeePt,
16
- quoteOperatorFeeUsdt
17
+ quoteOperatorFeeUsdt,
18
+ quoteOperatorFeeForTransfer
17
19
  } from "@pafi-dev/core";
18
20
 
19
21
  // src/quoting/routes.ts
@@ -1108,6 +1110,117 @@ var TradingHandlers = class {
1108
1110
  feeRecipient: pafiFeeRecipient
1109
1111
  };
1110
1112
  }
1113
+ // =========================================================================
1114
+ // POST /erc20-transfer — gasless ERC-20 send (USDC/USDT/active-PT)
1115
+ // =========================================================================
1116
+ /**
1117
+ * Build a sponsored ERC-20 transfer UserOp pair (sponsored + fallback).
1118
+ *
1119
+ * Sponsored variant: `[token.transfer(PAFI, fee), token.transfer(recipient, amount)]`.
1120
+ * Fallback variant: `[token.transfer(recipient, amount)]` (user pays ETH).
1121
+ *
1122
+ * Both calls hit the SAME token contract — fee currency equals the
1123
+ * token being sent, so the user needs only ONE token (≥ `amount + fee`)
1124
+ * to complete a gasless send.
1125
+ *
1126
+ * Allowlist enforced HERE (client-side fail-fast) **and** server-side
1127
+ * by sponsor-relayer's IntentValidator:
1128
+ * - USDC (`getContractAddresses(chainId).usdc`)
1129
+ * - USDT (`getContractAddresses(chainId).usdt`)
1130
+ * - active PointToken (caller responsibility to check
1131
+ * `IssuerRegistry.isActiveByPointToken` if not stable)
1132
+ *
1133
+ * Recipient rejected when equal to `pafiFeeRecipient` (self-fee abuse)
1134
+ * or `0x0` (use the burn scenario for that).
1135
+ *
1136
+ * See `docs/SPONSORED_ERC20_TRANSFER_SPEC.md` for the full call
1137
+ * pattern, error codes, and edge cases.
1138
+ */
1139
+ async handleErc20Transfer(authenticatedAddress, request) {
1140
+ if (getAddress(authenticatedAddress) !== getAddress(request.userAddress)) {
1141
+ throw new ValidationError(
1142
+ "USER_ADDRESS_MISMATCH",
1143
+ `handleErc20Transfer: authenticatedAddress (${authenticatedAddress}) does not match request.userAddress (${request.userAddress})`
1144
+ );
1145
+ }
1146
+ if (request.chainId !== this.chainId) {
1147
+ throw new ValidationError(
1148
+ "UNSUPPORTED_CHAIN_ID",
1149
+ `handleErc20Transfer: unsupported chainId ${request.chainId}`,
1150
+ { requested: request.chainId, supported: this.chainId }
1151
+ );
1152
+ }
1153
+ if (request.amount <= 0n) {
1154
+ throw new ValidationError(
1155
+ "ZERO_AMOUNT",
1156
+ "handleErc20Transfer: amount must be positive"
1157
+ );
1158
+ }
1159
+ const userAddress = getAddress(request.userAddress);
1160
+ const tokenAddress = getAddress(request.tokenAddress);
1161
+ const recipient = getAddress(request.recipient);
1162
+ const addrs = getContractAddresses(request.chainId);
1163
+ const pafiFeeRecipient = addrs.pafiFeeRecipient;
1164
+ if (recipient.toLowerCase() === pafiFeeRecipient.toLowerCase()) {
1165
+ throw new ValidationError(
1166
+ "SELF_FEE_ABUSE",
1167
+ "handleErc20Transfer: recipient cannot be the PAFI fee recipient",
1168
+ { recipient, pafiFeeRecipient }
1169
+ );
1170
+ }
1171
+ if (recipient === "0x0000000000000000000000000000000000000000") {
1172
+ throw new ValidationError(
1173
+ "USE_BURN_SCENARIO",
1174
+ "handleErc20Transfer: recipient cannot be the zero address \u2014 use the burn scenario instead"
1175
+ );
1176
+ }
1177
+ const isStable = addrs.usdc && tokenAddress.toLowerCase() === addrs.usdc.toLowerCase() || tokenAddress.toLowerCase() === addrs.usdt.toLowerCase();
1178
+ let feeAmount;
1179
+ if (request.feeAmount !== void 0) {
1180
+ feeAmount = request.feeAmount > 0n ? request.feeAmount : 0n;
1181
+ } else {
1182
+ feeAmount = await quoteOperatorFeeForTransfer({
1183
+ provider: this.provider,
1184
+ chainId: request.chainId,
1185
+ tokenAddress,
1186
+ allowStaleFallback: request.allowStaleFallback,
1187
+ fallbackEthPriceUsd: request.fallbackEthPriceUsd,
1188
+ fallbackPtPriceUsdt: request.fallbackPtPriceUsdt
1189
+ });
1190
+ }
1191
+ if (feeAmount > 0n && feeAmount >= request.amount) {
1192
+ throw new ValidationError(
1193
+ "INVALID_AMOUNT",
1194
+ `handleErc20Transfer: fee (${feeAmount}) must be strictly less than amount (${request.amount})`,
1195
+ { feeAmount, amount: request.amount }
1196
+ );
1197
+ }
1198
+ const userOp = buildErc20TransferUserOp({
1199
+ userAddress,
1200
+ aaNonce: request.aaNonce,
1201
+ tokenAddress,
1202
+ recipient,
1203
+ amount: request.amount,
1204
+ feeAmount: feeAmount > 0n ? feeAmount : void 0,
1205
+ feeRecipient: feeAmount > 0n ? pafiFeeRecipient : void 0
1206
+ });
1207
+ const userOpFallback = feeAmount > 0n ? buildErc20TransferUserOp({
1208
+ userAddress,
1209
+ aaNonce: request.aaNonce,
1210
+ tokenAddress,
1211
+ recipient,
1212
+ amount: request.amount
1213
+ // No feeAmount → 1-call variant (transfer only).
1214
+ }) : void 0;
1215
+ void isStable;
1216
+ return {
1217
+ userOp,
1218
+ userOpFallback,
1219
+ feeAmountUsed: feeAmount,
1220
+ feeRecipient: pafiFeeRecipient,
1221
+ tokenAddress
1222
+ };
1223
+ }
1111
1224
  };
1112
1225
  function isPlaceholderAddress(addr) {
1113
1226
  return /^0x0{36}[0-9a-fA-F]{4}$/i.test(addr);
@@ -1521,13 +1634,1922 @@ async function perpDepositDirect(params) {
1521
1634
  relayAddress
1522
1635
  };
1523
1636
  }
1637
+
1638
+ // src/direct/transferDirect.ts
1639
+ import {
1640
+ buildErc20TransferUserOp as buildErc20TransferUserOp2,
1641
+ getContractAddresses as getContractAddresses5,
1642
+ parseEip7702DelegatedAddress as parseEip7702DelegatedAddress4,
1643
+ detectDelegateImpl as detectDelegateImpl4,
1644
+ BATCH_EXECUTOR_7702_IMPL as BATCH_EXECUTOR_7702_IMPL4,
1645
+ SIMPLE_7702_IMPL_BASE_MAINNET as SIMPLE_7702_IMPL_BASE_MAINNET4
1646
+ } from "@pafi-dev/core";
1647
+
1648
+ // src/direct/addLiquidityDirect.ts
1649
+ import {
1650
+ decodeEventLog
1651
+ } from "viem";
1652
+ import {
1653
+ parseEip7702DelegatedAddress as parseEip7702DelegatedAddress5,
1654
+ buildPartialUserOperation as buildPartialUserOperation2,
1655
+ computeV3PoolAddress,
1656
+ V3_FACTORY_ADDRESSES,
1657
+ V3_POOL_INIT_CODE_HASH
1658
+ } from "@pafi-dev/core";
1659
+
1660
+ // src/liquidity/abi/nonfungiblePositionManager.ts
1661
+ var nonfungiblePositionManagerAbi = [
1662
+ // ─── Write methods ───────────────────────────────────────────────────────
1663
+ {
1664
+ type: "function",
1665
+ name: "mint",
1666
+ stateMutability: "payable",
1667
+ inputs: [
1668
+ {
1669
+ name: "params",
1670
+ type: "tuple",
1671
+ components: [
1672
+ { name: "token0", type: "address" },
1673
+ { name: "token1", type: "address" },
1674
+ { name: "fee", type: "uint24" },
1675
+ { name: "tickLower", type: "int24" },
1676
+ { name: "tickUpper", type: "int24" },
1677
+ { name: "amount0Desired", type: "uint256" },
1678
+ { name: "amount1Desired", type: "uint256" },
1679
+ { name: "amount0Min", type: "uint256" },
1680
+ { name: "amount1Min", type: "uint256" },
1681
+ { name: "recipient", type: "address" },
1682
+ { name: "deadline", type: "uint256" }
1683
+ ]
1684
+ }
1685
+ ],
1686
+ outputs: [
1687
+ { name: "tokenId", type: "uint256" },
1688
+ { name: "liquidity", type: "uint128" },
1689
+ { name: "amount0", type: "uint256" },
1690
+ { name: "amount1", type: "uint256" }
1691
+ ]
1692
+ },
1693
+ {
1694
+ type: "function",
1695
+ name: "increaseLiquidity",
1696
+ stateMutability: "payable",
1697
+ inputs: [
1698
+ {
1699
+ name: "params",
1700
+ type: "tuple",
1701
+ components: [
1702
+ { name: "tokenId", type: "uint256" },
1703
+ { name: "amount0Desired", type: "uint256" },
1704
+ { name: "amount1Desired", type: "uint256" },
1705
+ { name: "amount0Min", type: "uint256" },
1706
+ { name: "amount1Min", type: "uint256" },
1707
+ { name: "deadline", type: "uint256" }
1708
+ ]
1709
+ }
1710
+ ],
1711
+ outputs: [
1712
+ { name: "liquidity", type: "uint128" },
1713
+ { name: "amount0", type: "uint256" },
1714
+ { name: "amount1", type: "uint256" }
1715
+ ]
1716
+ },
1717
+ {
1718
+ type: "function",
1719
+ name: "decreaseLiquidity",
1720
+ stateMutability: "payable",
1721
+ inputs: [
1722
+ {
1723
+ name: "params",
1724
+ type: "tuple",
1725
+ components: [
1726
+ { name: "tokenId", type: "uint256" },
1727
+ { name: "liquidity", type: "uint128" },
1728
+ { name: "amount0Min", type: "uint256" },
1729
+ { name: "amount1Min", type: "uint256" },
1730
+ { name: "deadline", type: "uint256" }
1731
+ ]
1732
+ }
1733
+ ],
1734
+ outputs: [
1735
+ { name: "amount0", type: "uint256" },
1736
+ { name: "amount1", type: "uint256" }
1737
+ ]
1738
+ },
1739
+ {
1740
+ type: "function",
1741
+ name: "collect",
1742
+ stateMutability: "payable",
1743
+ inputs: [
1744
+ {
1745
+ name: "params",
1746
+ type: "tuple",
1747
+ components: [
1748
+ { name: "tokenId", type: "uint256" },
1749
+ { name: "recipient", type: "address" },
1750
+ { name: "amount0Max", type: "uint128" },
1751
+ { name: "amount1Max", type: "uint128" }
1752
+ ]
1753
+ }
1754
+ ],
1755
+ outputs: [
1756
+ { name: "amount0", type: "uint256" },
1757
+ { name: "amount1", type: "uint256" }
1758
+ ]
1759
+ },
1760
+ {
1761
+ type: "function",
1762
+ name: "burn",
1763
+ stateMutability: "payable",
1764
+ inputs: [{ name: "tokenId", type: "uint256" }],
1765
+ outputs: []
1766
+ },
1767
+ // ─── Read methods ────────────────────────────────────────────────────────
1768
+ // Returns the legacy V3 12-tuple. `readPosition` wraps this into a struct.
1769
+ {
1770
+ type: "function",
1771
+ name: "positions",
1772
+ stateMutability: "view",
1773
+ inputs: [{ name: "tokenId", type: "uint256" }],
1774
+ outputs: [
1775
+ { name: "nonce", type: "uint96" },
1776
+ { name: "operator", type: "address" },
1777
+ { name: "token0", type: "address" },
1778
+ { name: "token1", type: "address" },
1779
+ { name: "fee", type: "uint24" },
1780
+ { name: "tickLower", type: "int24" },
1781
+ { name: "tickUpper", type: "int24" },
1782
+ { name: "liquidity", type: "uint128" },
1783
+ { name: "feeGrowthInside0LastX128", type: "uint256" },
1784
+ { name: "feeGrowthInside1LastX128", type: "uint256" },
1785
+ { name: "tokensOwed0", type: "uint128" },
1786
+ { name: "tokensOwed1", type: "uint128" }
1787
+ ]
1788
+ },
1789
+ // Used by the ABI smoke test to verify NPM is wired to the expected
1790
+ // PafiV3Factory.
1791
+ {
1792
+ type: "function",
1793
+ name: "factory",
1794
+ stateMutability: "view",
1795
+ inputs: [],
1796
+ outputs: [{ name: "", type: "address" }]
1797
+ },
1798
+ // ─── Events (for receipt parsing) ────────────────────────────────────────
1799
+ // Emitted from address(0) on mint; addLiquidityDirect parses this to
1800
+ // extract the newly-minted tokenId.
1801
+ {
1802
+ type: "event",
1803
+ name: "Transfer",
1804
+ inputs: [
1805
+ { name: "from", type: "address", indexed: true },
1806
+ { name: "to", type: "address", indexed: true },
1807
+ { name: "tokenId", type: "uint256", indexed: true }
1808
+ ]
1809
+ },
1810
+ {
1811
+ type: "event",
1812
+ name: "IncreaseLiquidity",
1813
+ inputs: [
1814
+ { name: "tokenId", type: "uint256", indexed: true },
1815
+ { name: "liquidity", type: "uint128", indexed: false },
1816
+ { name: "amount0", type: "uint256", indexed: false },
1817
+ { name: "amount1", type: "uint256", indexed: false }
1818
+ ]
1819
+ },
1820
+ {
1821
+ type: "event",
1822
+ name: "DecreaseLiquidity",
1823
+ inputs: [
1824
+ { name: "tokenId", type: "uint256", indexed: true },
1825
+ { name: "liquidity", type: "uint128", indexed: false },
1826
+ { name: "amount0", type: "uint256", indexed: false },
1827
+ { name: "amount1", type: "uint256", indexed: false }
1828
+ ]
1829
+ },
1830
+ {
1831
+ type: "event",
1832
+ name: "Collect",
1833
+ inputs: [
1834
+ { name: "tokenId", type: "uint256", indexed: true },
1835
+ { name: "recipient", type: "address", indexed: false },
1836
+ { name: "amount0", type: "uint256", indexed: false },
1837
+ { name: "amount1", type: "uint256", indexed: false }
1838
+ ]
1839
+ }
1840
+ ];
1841
+
1842
+ // src/liquidity/abi/v3Pool.ts
1843
+ var v3PoolAbi = [
1844
+ // Pool's underlying tokens. Read on-chain to verify the
1845
+ // `computeV3PoolAddress(factory, token0, token1, fee)` derivation matches
1846
+ // an already-deployed pool — and useful generally for consumers that
1847
+ // discovered a pool by address (e.g. from a Transfer log) and need the
1848
+ // tokens.
1849
+ {
1850
+ type: "function",
1851
+ name: "token0",
1852
+ stateMutability: "view",
1853
+ inputs: [],
1854
+ outputs: [{ name: "", type: "address" }]
1855
+ },
1856
+ {
1857
+ type: "function",
1858
+ name: "token1",
1859
+ stateMutability: "view",
1860
+ inputs: [],
1861
+ outputs: [{ name: "", type: "address" }]
1862
+ },
1863
+ // (sqrtPriceX96, tick, observationIndex, observationCardinality,
1864
+ // observationCardinalityNext, feeProtocol, unlocked)
1865
+ {
1866
+ type: "function",
1867
+ name: "slot0",
1868
+ stateMutability: "view",
1869
+ inputs: [],
1870
+ outputs: [
1871
+ { name: "sqrtPriceX96", type: "uint160" },
1872
+ { name: "tick", type: "int24" },
1873
+ { name: "observationIndex", type: "uint16" },
1874
+ { name: "observationCardinality", type: "uint16" },
1875
+ { name: "observationCardinalityNext", type: "uint16" },
1876
+ { name: "feeProtocol", type: "uint8" },
1877
+ { name: "unlocked", type: "bool" }
1878
+ ]
1879
+ },
1880
+ {
1881
+ type: "function",
1882
+ name: "liquidity",
1883
+ stateMutability: "view",
1884
+ inputs: [],
1885
+ outputs: [{ name: "", type: "uint128" }]
1886
+ },
1887
+ {
1888
+ type: "function",
1889
+ name: "tickSpacing",
1890
+ stateMutability: "view",
1891
+ inputs: [],
1892
+ outputs: [{ name: "", type: "int24" }]
1893
+ },
1894
+ {
1895
+ type: "function",
1896
+ name: "fee",
1897
+ stateMutability: "view",
1898
+ inputs: [],
1899
+ outputs: [{ name: "", type: "uint24" }]
1900
+ },
1901
+ // (Token-0 / token-1 cumulative fee growth per unit of liquidity, in
1902
+ // Q128.128.) Used with `ticks(...)` to derive the pending-fee estimate
1903
+ // for collectFeesDirect.
1904
+ {
1905
+ type: "function",
1906
+ name: "feeGrowthGlobal0X128",
1907
+ stateMutability: "view",
1908
+ inputs: [],
1909
+ outputs: [{ name: "", type: "uint256" }]
1910
+ },
1911
+ {
1912
+ type: "function",
1913
+ name: "feeGrowthGlobal1X128",
1914
+ stateMutability: "view",
1915
+ inputs: [],
1916
+ outputs: [{ name: "", type: "uint256" }]
1917
+ },
1918
+ // Per-tick state. The SDK reads `ticks(tickLower)` + `ticks(tickUpper)`
1919
+ // for a position to compute its `feeGrowthInside0/1` in the canonical
1920
+ // V3 formula (current global - outside lower - outside upper, branched
1921
+ // on whether `slot0.tick` is below / inside / above the range).
1922
+ {
1923
+ type: "function",
1924
+ name: "ticks",
1925
+ stateMutability: "view",
1926
+ inputs: [{ name: "tick", type: "int24" }],
1927
+ outputs: [
1928
+ { name: "liquidityGross", type: "uint128" },
1929
+ { name: "liquidityNet", type: "int128" },
1930
+ { name: "feeGrowthOutside0X128", type: "uint256" },
1931
+ { name: "feeGrowthOutside1X128", type: "uint256" },
1932
+ { name: "tickCumulativeOutside", type: "int56" },
1933
+ { name: "secondsPerLiquidityOutsideX128", type: "uint160" },
1934
+ { name: "secondsOutside", type: "uint56" },
1935
+ { name: "initialized", type: "bool" }
1936
+ ]
1937
+ },
1938
+ // Per-word initialized-tick bitset. Each uint256 word covers 256
1939
+ // compressed tick positions (compressed = tick / tickSpacing). The
1940
+ // off-chain liquidity-curve scanner reads every word in
1941
+ // `[wordOf(minUsableTick), wordOf(maxUsableTick)]` then decodes set
1942
+ // bits into absolute tick indices via `(wordPos * 256 + bit) * spacing`.
1943
+ // See `fetchAllInitializedTicks.ts`.
1944
+ {
1945
+ type: "function",
1946
+ name: "tickBitmap",
1947
+ stateMutability: "view",
1948
+ inputs: [{ name: "wordPosition", type: "int16" }],
1949
+ outputs: [{ name: "", type: "uint256" }]
1950
+ }
1951
+ ];
1952
+
1953
+ // src/liquidity/constants.ts
1954
+ var V3_NPM_ADDRESSES = {
1955
+ // Base mainnet — PAFI's V3 NPM deployment.
1956
+ 8453: "0xD7db6931B5EbeaE033605db0781DE6C91F05CCdf"
1957
+ };
1958
+ var UINT128_MAX2 = (1n << 128n) - 1n;
1959
+
1960
+ // src/liquidity/math.ts
1961
+ var MIN_TICK = -887272;
1962
+ var MAX_TICK = 887272;
1963
+ var MIN_SQRT_RATIO = 4295128739n;
1964
+ var MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342n;
1965
+ var Q96 = 1n << 96n;
1966
+ var Q192 = 1n << 192n;
1967
+ function mulShift(val, mulBy) {
1968
+ return val * mulBy >> 128n;
1969
+ }
1970
+ function mulDivFloor(a, b, denominator) {
1971
+ return a * b / denominator;
1972
+ }
1973
+ function mulDivCeil(a, b, denominator) {
1974
+ const product = a * b;
1975
+ return product % denominator === 0n ? product / denominator : product / denominator + 1n;
1976
+ }
1977
+ function tickToSqrtPriceX96(tick) {
1978
+ if (!Number.isInteger(tick)) {
1979
+ throw new Error(`tickToSqrtPriceX96: tick must be an integer, got ${tick}`);
1980
+ }
1981
+ if (tick < MIN_TICK || tick > MAX_TICK) {
1982
+ throw new Error(
1983
+ `tickToSqrtPriceX96: tick ${tick} out of range [${MIN_TICK}, ${MAX_TICK}]`
1984
+ );
1985
+ }
1986
+ const absTick = BigInt(tick < 0 ? -tick : tick);
1987
+ let ratio = (absTick & 0x1n) !== 0n ? 0xfffcb933bd6fad37aa2d162d1a594001n : 0x100000000000000000000000000000000n;
1988
+ if ((absTick & 0x2n) !== 0n)
1989
+ ratio = mulShift(ratio, 0xfff97272373d413259a46990580e213an);
1990
+ if ((absTick & 0x4n) !== 0n)
1991
+ ratio = mulShift(ratio, 0xfff2e50f5f656932ef12357cf3c7fdccn);
1992
+ if ((absTick & 0x8n) !== 0n)
1993
+ ratio = mulShift(ratio, 0xffe5caca7e10e4e61c3624eaa0941cd0n);
1994
+ if ((absTick & 0x10n) !== 0n)
1995
+ ratio = mulShift(ratio, 0xffcb9843d60f6159c9db58835c926644n);
1996
+ if ((absTick & 0x20n) !== 0n)
1997
+ ratio = mulShift(ratio, 0xff973b41fa98c081472e6896dfb254c0n);
1998
+ if ((absTick & 0x40n) !== 0n)
1999
+ ratio = mulShift(ratio, 0xff2ea16466c96a3843ec78b326b52861n);
2000
+ if ((absTick & 0x80n) !== 0n)
2001
+ ratio = mulShift(ratio, 0xfe5dee046a99a2a811c461f1969c3053n);
2002
+ if ((absTick & 0x100n) !== 0n)
2003
+ ratio = mulShift(ratio, 0xfcbe86c7900a88aedcffc83b479aa3a4n);
2004
+ if ((absTick & 0x200n) !== 0n)
2005
+ ratio = mulShift(ratio, 0xf987a7253ac413176f2b074cf7815e54n);
2006
+ if ((absTick & 0x400n) !== 0n)
2007
+ ratio = mulShift(ratio, 0xf3392b0822b70005940c7a398e4b70f3n);
2008
+ if ((absTick & 0x800n) !== 0n)
2009
+ ratio = mulShift(ratio, 0xe7159475a2c29b7443b29c7fa6e889d9n);
2010
+ if ((absTick & 0x1000n) !== 0n)
2011
+ ratio = mulShift(ratio, 0xd097f3bdfd2022b8845ad8f792aa5825n);
2012
+ if ((absTick & 0x2000n) !== 0n)
2013
+ ratio = mulShift(ratio, 0xa9f746462d870fdf8a65dc1f90e061e5n);
2014
+ if ((absTick & 0x4000n) !== 0n)
2015
+ ratio = mulShift(ratio, 0x70d869a156d2a1b890bb3df62baf32f7n);
2016
+ if ((absTick & 0x8000n) !== 0n)
2017
+ ratio = mulShift(ratio, 0x31be135f97d08fd981231505542fcfa6n);
2018
+ if ((absTick & 0x10000n) !== 0n)
2019
+ ratio = mulShift(ratio, 0x9aa508b5b7a84e1c677de54f3e99bc9n);
2020
+ if ((absTick & 0x20000n) !== 0n)
2021
+ ratio = mulShift(ratio, 0x5d6af8dedb81196699c329225ee604n);
2022
+ if ((absTick & 0x40000n) !== 0n)
2023
+ ratio = mulShift(ratio, 0x2216e584f5fa1ea926041bedfe98n);
2024
+ if ((absTick & 0x80000n) !== 0n)
2025
+ ratio = mulShift(ratio, 0x48a170391f7dc42444e8fa2n);
2026
+ if (tick > 0) {
2027
+ const max256 = (1n << 256n) - 1n;
2028
+ ratio = max256 / ratio;
2029
+ }
2030
+ return (ratio >> 32n) + (ratio % (1n << 32n) === 0n ? 0n : 1n);
2031
+ }
2032
+ function sqrtPriceX96ToTick(sqrtPriceX96) {
2033
+ if (sqrtPriceX96 < MIN_SQRT_RATIO || sqrtPriceX96 >= MAX_SQRT_RATIO) {
2034
+ throw new Error(
2035
+ `sqrtPriceX96ToTick: sqrtPriceX96 ${sqrtPriceX96} out of range [${MIN_SQRT_RATIO}, ${MAX_SQRT_RATIO})`
2036
+ );
2037
+ }
2038
+ const ratio = sqrtPriceX96 << 32n;
2039
+ let r = ratio;
2040
+ let msb = 0n;
2041
+ for (const [shift, value] of [
2042
+ [128n, 0xffffffffffffffffffffffffffffffffn],
2043
+ [64n, 0xffffffffffffffffn],
2044
+ [32n, 0xffffffffn],
2045
+ [16n, 0xffffn],
2046
+ [8n, 0xffn],
2047
+ [4n, 0xfn],
2048
+ [2n, 0x3n],
2049
+ [1n, 0x1n]
2050
+ ]) {
2051
+ const f = r > value ? shift : 0n;
2052
+ msb |= f;
2053
+ r >>= f;
2054
+ }
2055
+ r = msb >= 128n ? ratio >> msb - 127n : ratio << 127n - msb;
2056
+ let log2 = msb - 128n << 64n;
2057
+ for (let i = 0; i < 14; i++) {
2058
+ r = r * r >> 127n;
2059
+ const f = r >> 128n;
2060
+ log2 |= f << BigInt(63 - i);
2061
+ r >>= f;
2062
+ }
2063
+ const logSqrt10001 = log2 * 255738958999603826347141n;
2064
+ const tickLow = Number(
2065
+ logSqrt10001 - 3402992956809132418596140100660247210n >> 128n
2066
+ );
2067
+ const tickHigh = Number(
2068
+ logSqrt10001 + 291339464771989622907027621153398088495n >> 128n
2069
+ );
2070
+ if (tickLow === tickHigh) return tickLow;
2071
+ return tickToSqrtPriceX96(tickHigh) <= sqrtPriceX96 ? tickHigh : tickLow;
2072
+ }
2073
+ function nearestUsableTick(tick, tickSpacing) {
2074
+ if (!Number.isInteger(tick) || !Number.isInteger(tickSpacing)) {
2075
+ throw new Error("nearestUsableTick: both arguments must be integers");
2076
+ }
2077
+ if (tickSpacing <= 0) {
2078
+ throw new Error(
2079
+ `nearestUsableTick: tickSpacing must be positive, got ${tickSpacing}`
2080
+ );
2081
+ }
2082
+ const rounded = Math.round(tick / tickSpacing) * tickSpacing;
2083
+ if (rounded < MIN_TICK) return rounded + tickSpacing;
2084
+ if (rounded > MAX_TICK) return rounded - tickSpacing;
2085
+ return rounded;
2086
+ }
2087
+ function minUsableTick(tickSpacing) {
2088
+ return Math.ceil(MIN_TICK / tickSpacing) * tickSpacing;
2089
+ }
2090
+ function maxUsableTick(tickSpacing) {
2091
+ return Math.floor(MAX_TICK / tickSpacing) * tickSpacing;
2092
+ }
2093
+ function tickSpacingForFee(fee) {
2094
+ switch (fee) {
2095
+ case 100:
2096
+ return 1;
2097
+ case 500:
2098
+ return 10;
2099
+ case 3e3:
2100
+ return 60;
2101
+ case 1e4:
2102
+ return 200;
2103
+ default:
2104
+ return void 0;
2105
+ }
2106
+ }
2107
+ function priceToTick(params) {
2108
+ const { price, token0Decimals, token1Decimals } = params;
2109
+ if (price <= 0 || !Number.isFinite(price)) {
2110
+ throw new Error(`priceToTick: price must be a positive finite number, got ${price}`);
2111
+ }
2112
+ const rawPrice = price * 10 ** (token1Decimals - token0Decimals);
2113
+ return Math.floor(Math.log(rawPrice) / Math.log(1.0001));
2114
+ }
2115
+ function tickToPrice(params) {
2116
+ const { tick, token0Decimals, token1Decimals } = params;
2117
+ if (!Number.isInteger(tick)) {
2118
+ throw new Error(`tickToPrice: tick must be an integer, got ${tick}`);
2119
+ }
2120
+ const rawPrice = Math.pow(1.0001, tick);
2121
+ return rawPrice / 10 ** (token1Decimals - token0Decimals);
2122
+ }
2123
+ function getAmount0ForLiquidity(sqrtPriceX96Lower, sqrtPriceX96Upper, liquidity) {
2124
+ if (sqrtPriceX96Lower > sqrtPriceX96Upper) {
2125
+ [sqrtPriceX96Lower, sqrtPriceX96Upper] = [
2126
+ sqrtPriceX96Upper,
2127
+ sqrtPriceX96Lower
2128
+ ];
2129
+ }
2130
+ if (liquidity <= 0n) return 0n;
2131
+ const numerator = liquidity << 96n;
2132
+ const diff = sqrtPriceX96Upper - sqrtPriceX96Lower;
2133
+ return mulDivCeil(numerator * diff, 1n, sqrtPriceX96Upper * sqrtPriceX96Lower);
2134
+ }
2135
+ function getAmount1ForLiquidity(sqrtPriceX96Lower, sqrtPriceX96Upper, liquidity) {
2136
+ if (sqrtPriceX96Lower > sqrtPriceX96Upper) {
2137
+ [sqrtPriceX96Lower, sqrtPriceX96Upper] = [
2138
+ sqrtPriceX96Upper,
2139
+ sqrtPriceX96Lower
2140
+ ];
2141
+ }
2142
+ if (liquidity <= 0n) return 0n;
2143
+ return mulDivCeil(liquidity, sqrtPriceX96Upper - sqrtPriceX96Lower, Q96);
2144
+ }
2145
+ function getAmountsForLiquidity(params) {
2146
+ let { sqrtPriceX96Lower, sqrtPriceX96Upper } = params;
2147
+ const { sqrtPriceX96Current, liquidity } = params;
2148
+ if (sqrtPriceX96Lower > sqrtPriceX96Upper) {
2149
+ [sqrtPriceX96Lower, sqrtPriceX96Upper] = [
2150
+ sqrtPriceX96Upper,
2151
+ sqrtPriceX96Lower
2152
+ ];
2153
+ }
2154
+ if (sqrtPriceX96Current <= sqrtPriceX96Lower) {
2155
+ return {
2156
+ amount0: getAmount0ForLiquidity(
2157
+ sqrtPriceX96Lower,
2158
+ sqrtPriceX96Upper,
2159
+ liquidity
2160
+ ),
2161
+ amount1: 0n
2162
+ };
2163
+ } else if (sqrtPriceX96Current < sqrtPriceX96Upper) {
2164
+ return {
2165
+ amount0: getAmount0ForLiquidity(
2166
+ sqrtPriceX96Current,
2167
+ sqrtPriceX96Upper,
2168
+ liquidity
2169
+ ),
2170
+ amount1: getAmount1ForLiquidity(
2171
+ sqrtPriceX96Lower,
2172
+ sqrtPriceX96Current,
2173
+ liquidity
2174
+ )
2175
+ };
2176
+ } else {
2177
+ return {
2178
+ amount0: 0n,
2179
+ amount1: getAmount1ForLiquidity(
2180
+ sqrtPriceX96Lower,
2181
+ sqrtPriceX96Upper,
2182
+ liquidity
2183
+ )
2184
+ };
2185
+ }
2186
+ }
2187
+ function getLiquidityForAmount0(sqrtPriceX96Lower, sqrtPriceX96Upper, amount0) {
2188
+ if (sqrtPriceX96Lower > sqrtPriceX96Upper) {
2189
+ [sqrtPriceX96Lower, sqrtPriceX96Upper] = [
2190
+ sqrtPriceX96Upper,
2191
+ sqrtPriceX96Lower
2192
+ ];
2193
+ }
2194
+ if (amount0 <= 0n) return 0n;
2195
+ const intermediate = mulDivFloor(sqrtPriceX96Lower, sqrtPriceX96Upper, Q96);
2196
+ return mulDivFloor(amount0, intermediate, sqrtPriceX96Upper - sqrtPriceX96Lower);
2197
+ }
2198
+ function getLiquidityForAmount1(sqrtPriceX96Lower, sqrtPriceX96Upper, amount1) {
2199
+ if (sqrtPriceX96Lower > sqrtPriceX96Upper) {
2200
+ [sqrtPriceX96Lower, sqrtPriceX96Upper] = [
2201
+ sqrtPriceX96Upper,
2202
+ sqrtPriceX96Lower
2203
+ ];
2204
+ }
2205
+ if (amount1 <= 0n) return 0n;
2206
+ return mulDivFloor(amount1, Q96, sqrtPriceX96Upper - sqrtPriceX96Lower);
2207
+ }
2208
+ function estimateLiquidityFromOneSide(params) {
2209
+ let { sqrtPriceX96Lower, sqrtPriceX96Upper } = params;
2210
+ const {
2211
+ sqrtPriceX96Current,
2212
+ amount0Desired,
2213
+ amount1Desired
2214
+ } = params;
2215
+ if (sqrtPriceX96Lower > sqrtPriceX96Upper) {
2216
+ [sqrtPriceX96Lower, sqrtPriceX96Upper] = [
2217
+ sqrtPriceX96Upper,
2218
+ sqrtPriceX96Lower
2219
+ ];
2220
+ }
2221
+ const has0 = amount0Desired !== void 0 && amount0Desired > 0n;
2222
+ const has1 = amount1Desired !== void 0 && amount1Desired > 0n;
2223
+ if (has0 === has1) {
2224
+ throw new Error(
2225
+ "estimateLiquidityFromOneSide: exactly one of amount0Desired / amount1Desired must be a positive bigint"
2226
+ );
2227
+ }
2228
+ if (sqrtPriceX96Current <= sqrtPriceX96Lower) {
2229
+ if (!has0) {
2230
+ throw new Error(
2231
+ "estimateLiquidityFromOneSide: spot is below range; pin amount0Desired (token1 isn't required)"
2232
+ );
2233
+ }
2234
+ const liquidity2 = getLiquidityForAmount0(
2235
+ sqrtPriceX96Lower,
2236
+ sqrtPriceX96Upper,
2237
+ amount0Desired
2238
+ );
2239
+ return { liquidity: liquidity2, amount0: amount0Desired, amount1: 0n };
2240
+ }
2241
+ if (sqrtPriceX96Current >= sqrtPriceX96Upper) {
2242
+ if (!has1) {
2243
+ throw new Error(
2244
+ "estimateLiquidityFromOneSide: spot is above range; pin amount1Desired (token0 isn't required)"
2245
+ );
2246
+ }
2247
+ const liquidity2 = getLiquidityForAmount1(
2248
+ sqrtPriceX96Lower,
2249
+ sqrtPriceX96Upper,
2250
+ amount1Desired
2251
+ );
2252
+ return { liquidity: liquidity2, amount0: 0n, amount1: amount1Desired };
2253
+ }
2254
+ let liquidity;
2255
+ if (has0) {
2256
+ liquidity = getLiquidityForAmount0(
2257
+ sqrtPriceX96Current,
2258
+ sqrtPriceX96Upper,
2259
+ amount0Desired
2260
+ );
2261
+ } else {
2262
+ liquidity = getLiquidityForAmount1(
2263
+ sqrtPriceX96Lower,
2264
+ sqrtPriceX96Current,
2265
+ amount1Desired
2266
+ );
2267
+ }
2268
+ const amounts = getAmountsForLiquidity({
2269
+ sqrtPriceX96Current,
2270
+ sqrtPriceX96Lower,
2271
+ sqrtPriceX96Upper,
2272
+ liquidity
2273
+ });
2274
+ return {
2275
+ liquidity,
2276
+ amount0: has0 ? amount0Desired : amounts.amount0,
2277
+ amount1: has1 ? amount1Desired : amounts.amount1
2278
+ };
2279
+ }
2280
+ function applyMintSlippage(params) {
2281
+ const { amount0Desired, amount1Desired, slippageBps } = params;
2282
+ if (!Number.isInteger(slippageBps) || slippageBps < 0 || slippageBps > 1e4) {
2283
+ throw new Error(
2284
+ `applyMintSlippage: slippageBps must be integer in [0, 10000], got ${slippageBps}`
2285
+ );
2286
+ }
2287
+ const num = BigInt(1e4 - slippageBps);
2288
+ return {
2289
+ amount0Min: amount0Desired * num / 10000n,
2290
+ amount1Min: amount1Desired * num / 10000n
2291
+ };
2292
+ }
2293
+ function applyRemoveSlippage(params) {
2294
+ const { amount0Expected, amount1Expected, slippageBps } = params;
2295
+ if (!Number.isInteger(slippageBps) || slippageBps < 0 || slippageBps > 1e4) {
2296
+ throw new Error(
2297
+ `applyRemoveSlippage: slippageBps must be integer in [0, 10000], got ${slippageBps}`
2298
+ );
2299
+ }
2300
+ const num = BigInt(1e4 - slippageBps);
2301
+ return {
2302
+ amount0Min: amount0Expected * num / 10000n,
2303
+ amount1Min: amount1Expected * num / 10000n
2304
+ };
2305
+ }
2306
+
2307
+ // src/liquidity/readPosition.ts
2308
+ async function readPosition(client, npm, tokenId) {
2309
+ const result = await client.readContract({
2310
+ address: npm,
2311
+ abi: nonfungiblePositionManagerAbi,
2312
+ functionName: "positions",
2313
+ args: [tokenId]
2314
+ });
2315
+ return {
2316
+ nonce: result[0],
2317
+ operator: result[1],
2318
+ token0: result[2],
2319
+ token1: result[3],
2320
+ fee: result[4],
2321
+ tickLower: result[5],
2322
+ tickUpper: result[6],
2323
+ liquidity: result[7],
2324
+ feeGrowthInside0LastX128: result[8],
2325
+ feeGrowthInside1LastX128: result[9],
2326
+ tokensOwed0: result[10],
2327
+ tokensOwed1: result[11]
2328
+ };
2329
+ }
2330
+
2331
+ // src/liquidity/readPoolState.ts
2332
+ async function readPoolState(client, pool, blockNumber) {
2333
+ const results = await client.multicall({
2334
+ contracts: [
2335
+ { address: pool, abi: v3PoolAbi, functionName: "slot0" },
2336
+ { address: pool, abi: v3PoolAbi, functionName: "liquidity" },
2337
+ { address: pool, abi: v3PoolAbi, functionName: "tickSpacing" },
2338
+ { address: pool, abi: v3PoolAbi, functionName: "fee" }
2339
+ ],
2340
+ allowFailure: false,
2341
+ blockNumber
2342
+ });
2343
+ const slot0 = results[0];
2344
+ return {
2345
+ sqrtPriceX96: slot0[0],
2346
+ tick: slot0[1],
2347
+ liquidity: results[1],
2348
+ tickSpacing: results[2],
2349
+ fee: results[3]
2350
+ };
2351
+ }
2352
+ async function readTicksAt(client, pool, tickLower, tickUpper) {
2353
+ const results = await client.multicall({
2354
+ contracts: [
2355
+ { address: pool, abi: v3PoolAbi, functionName: "ticks", args: [tickLower] },
2356
+ { address: pool, abi: v3PoolAbi, functionName: "ticks", args: [tickUpper] }
2357
+ ],
2358
+ allowFailure: false
2359
+ });
2360
+ const toTickInfo = (r) => {
2361
+ const tuple = r;
2362
+ return {
2363
+ liquidityGross: tuple[0],
2364
+ liquidityNet: tuple[1],
2365
+ feeGrowthOutside0X128: tuple[2],
2366
+ feeGrowthOutside1X128: tuple[3],
2367
+ initialized: tuple[7]
2368
+ };
2369
+ };
2370
+ return {
2371
+ lower: toTickInfo(results[0]),
2372
+ upper: toTickInfo(results[1])
2373
+ };
2374
+ }
2375
+ async function readFeeGrowthGlobals(client, pool) {
2376
+ const results = await client.multicall({
2377
+ contracts: [
2378
+ { address: pool, abi: v3PoolAbi, functionName: "feeGrowthGlobal0X128" },
2379
+ { address: pool, abi: v3PoolAbi, functionName: "feeGrowthGlobal1X128" }
2380
+ ],
2381
+ allowFailure: false
2382
+ });
2383
+ return {
2384
+ feeGrowthGlobal0X128: results[0],
2385
+ feeGrowthGlobal1X128: results[1]
2386
+ };
2387
+ }
2388
+
2389
+ // src/liquidity/buildMint.ts
2390
+ import { encodeFunctionData as encodeFunctionData3 } from "viem";
2391
+ import { erc20ApproveOp as erc20ApproveOp2 } from "@pafi-dev/core";
2392
+ function buildMint(params) {
2393
+ return [
2394
+ erc20ApproveOp2(params.token0, params.npm, params.amount0Desired),
2395
+ erc20ApproveOp2(params.token1, params.npm, params.amount1Desired),
2396
+ {
2397
+ target: params.npm,
2398
+ value: 0n,
2399
+ data: encodeFunctionData3({
2400
+ abi: nonfungiblePositionManagerAbi,
2401
+ functionName: "mint",
2402
+ args: [
2403
+ {
2404
+ token0: params.token0,
2405
+ token1: params.token1,
2406
+ fee: params.fee,
2407
+ tickLower: params.tickLower,
2408
+ tickUpper: params.tickUpper,
2409
+ amount0Desired: params.amount0Desired,
2410
+ amount1Desired: params.amount1Desired,
2411
+ amount0Min: params.amount0Min,
2412
+ amount1Min: params.amount1Min,
2413
+ recipient: params.recipient,
2414
+ deadline: params.deadline
2415
+ }
2416
+ ]
2417
+ })
2418
+ }
2419
+ ];
2420
+ }
2421
+
2422
+ // src/liquidity/buildIncreaseLiquidity.ts
2423
+ import { encodeFunctionData as encodeFunctionData4 } from "viem";
2424
+ import { erc20ApproveOp as erc20ApproveOp3 } from "@pafi-dev/core";
2425
+ function buildIncreaseLiquidity(params) {
2426
+ return [
2427
+ erc20ApproveOp3(params.token0, params.npm, params.amount0Desired),
2428
+ erc20ApproveOp3(params.token1, params.npm, params.amount1Desired),
2429
+ {
2430
+ target: params.npm,
2431
+ value: 0n,
2432
+ data: encodeFunctionData4({
2433
+ abi: nonfungiblePositionManagerAbi,
2434
+ functionName: "increaseLiquidity",
2435
+ args: [
2436
+ {
2437
+ tokenId: params.tokenId,
2438
+ amount0Desired: params.amount0Desired,
2439
+ amount1Desired: params.amount1Desired,
2440
+ amount0Min: params.amount0Min,
2441
+ amount1Min: params.amount1Min,
2442
+ deadline: params.deadline
2443
+ }
2444
+ ]
2445
+ })
2446
+ }
2447
+ ];
2448
+ }
2449
+
2450
+ // src/liquidity/buildDecreaseLiquidity.ts
2451
+ import { encodeFunctionData as encodeFunctionData5 } from "viem";
2452
+ function buildDecreaseLiquidity(params) {
2453
+ return {
2454
+ target: params.npm,
2455
+ value: 0n,
2456
+ data: encodeFunctionData5({
2457
+ abi: nonfungiblePositionManagerAbi,
2458
+ functionName: "decreaseLiquidity",
2459
+ args: [
2460
+ {
2461
+ tokenId: params.tokenId,
2462
+ liquidity: params.liquidity,
2463
+ amount0Min: params.amount0Min,
2464
+ amount1Min: params.amount1Min,
2465
+ deadline: params.deadline
2466
+ }
2467
+ ]
2468
+ })
2469
+ };
2470
+ }
2471
+
2472
+ // src/liquidity/buildCollect.ts
2473
+ import { encodeFunctionData as encodeFunctionData6 } from "viem";
2474
+ function buildCollect(params) {
2475
+ return {
2476
+ target: params.npm,
2477
+ value: 0n,
2478
+ data: encodeFunctionData6({
2479
+ abi: nonfungiblePositionManagerAbi,
2480
+ functionName: "collect",
2481
+ args: [
2482
+ {
2483
+ tokenId: params.tokenId,
2484
+ recipient: params.recipient,
2485
+ amount0Max: params.amount0Max ?? UINT128_MAX2,
2486
+ amount1Max: params.amount1Max ?? UINT128_MAX2
2487
+ }
2488
+ ]
2489
+ })
2490
+ };
2491
+ }
2492
+
2493
+ // src/liquidity/buildBurn.ts
2494
+ import { encodeFunctionData as encodeFunctionData7 } from "viem";
2495
+ function buildBurn(params) {
2496
+ return {
2497
+ target: params.npm,
2498
+ value: 0n,
2499
+ data: encodeFunctionData7({
2500
+ abi: nonfungiblePositionManagerAbi,
2501
+ functionName: "burn",
2502
+ args: [params.tokenId]
2503
+ })
2504
+ };
2505
+ }
2506
+
2507
+ // src/liquidity/buildClosePosition.ts
2508
+ function buildClosePosition(params) {
2509
+ const ops = [];
2510
+ if (params.liquidity > 0n) {
2511
+ ops.push(
2512
+ buildDecreaseLiquidity({
2513
+ npm: params.npm,
2514
+ tokenId: params.tokenId,
2515
+ liquidity: params.liquidity,
2516
+ amount0Min: params.amount0Min,
2517
+ amount1Min: params.amount1Min,
2518
+ deadline: params.deadline
2519
+ })
2520
+ );
2521
+ }
2522
+ if (params.liquidity > 0n || params.hasOutstandingFees) {
2523
+ ops.push(
2524
+ buildCollect({
2525
+ npm: params.npm,
2526
+ tokenId: params.tokenId,
2527
+ recipient: params.recipient
2528
+ })
2529
+ );
2530
+ }
2531
+ ops.push(buildBurn({ npm: params.npm, tokenId: params.tokenId }));
2532
+ return ops;
2533
+ }
2534
+
2535
+ // src/liquidity/readTickBitmap.ts
2536
+ function tickToBitmapPosition(tick, tickSpacing) {
2537
+ if (!Number.isInteger(tick)) {
2538
+ throw new Error(`tickToBitmapPosition: tick (${tick}) must be an integer`);
2539
+ }
2540
+ if (!Number.isInteger(tickSpacing) || tickSpacing <= 0) {
2541
+ throw new Error(
2542
+ `tickToBitmapPosition: tickSpacing (${tickSpacing}) must be a positive integer`
2543
+ );
2544
+ }
2545
+ if (tick % tickSpacing !== 0) {
2546
+ throw new Error(
2547
+ `tickToBitmapPosition: tick (${tick}) must be a multiple of tickSpacing ${tickSpacing}`
2548
+ );
2549
+ }
2550
+ const compressed = Math.floor(tick / tickSpacing);
2551
+ const word = compressed >> 8;
2552
+ const bit = (compressed % 256 + 256) % 256;
2553
+ return { word, bit };
2554
+ }
2555
+ function decodeBitmapWord(bitmap, wordPos, tickSpacing) {
2556
+ if (bitmap === 0n) return [];
2557
+ const out = [];
2558
+ for (let bit = 0; bit < 256; bit++) {
2559
+ if ((bitmap & 1n << BigInt(bit)) !== 0n) {
2560
+ out.push((wordPos * 256 + bit) * tickSpacing);
2561
+ }
2562
+ }
2563
+ return out;
2564
+ }
2565
+ function bitmapWordRange(tickSpacing) {
2566
+ const lo = minUsableTick(tickSpacing);
2567
+ const hi = maxUsableTick(tickSpacing);
2568
+ return {
2569
+ min: Math.floor(lo / tickSpacing) >> 8,
2570
+ max: Math.floor(hi / tickSpacing) >> 8
2571
+ };
2572
+ }
2573
+
2574
+ // src/liquidity/buildLiquidityCurve.ts
2575
+ var LiquidityCurveErrorCode = /* @__PURE__ */ ((LiquidityCurveErrorCode2) => {
2576
+ LiquidityCurveErrorCode2["TOO_MANY_TICKS"] = "TOO_MANY_TICKS";
2577
+ LiquidityCurveErrorCode2["ATOMICITY_VIOLATION"] = "ATOMICITY_VIOLATION";
2578
+ LiquidityCurveErrorCode2["CURVE_SUM_NONZERO"] = "CURVE_SUM_NONZERO";
2579
+ LiquidityCurveErrorCode2["CURVE_NEGATIVE_L"] = "CURVE_NEGATIVE_L";
2580
+ LiquidityCurveErrorCode2["CURVE_EDGE_NONZERO"] = "CURVE_EDGE_NONZERO";
2581
+ return LiquidityCurveErrorCode2;
2582
+ })(LiquidityCurveErrorCode || {});
2583
+ var LiquidityCurveError = class extends Error {
2584
+ code;
2585
+ constructor(code, message) {
2586
+ super(message);
2587
+ this.name = "LiquidityCurveError";
2588
+ this.code = code;
2589
+ }
2590
+ };
2591
+ function buildLiquidityCurve(params) {
2592
+ const { ticks, currentTick, currentLiquidity, tickSpacing } = params;
2593
+ const TICK_LO = minUsableTick(tickSpacing);
2594
+ const TICK_HI = maxUsableTick(tickSpacing);
2595
+ const N = ticks.length;
2596
+ const sumNet = ticks.reduce((acc, t) => acc + t.liquidityNet, 0n);
2597
+ if (sumNet !== 0n) {
2598
+ throw new LiquidityCurveError(
2599
+ "CURVE_SUM_NONZERO" /* CURVE_SUM_NONZERO */,
2600
+ `buildLiquidityCurve: sum(liquidityNet) = ${sumNet}, expected 0. Tick set is incomplete or fetched at a different block.`
2601
+ );
2602
+ }
2603
+ if (N === 0) {
2604
+ return [
2605
+ {
2606
+ tickLower: TICK_LO,
2607
+ tickUpper: TICK_HI,
2608
+ sqrtPriceLowerX96: tickToSqrtPriceX96(TICK_LO),
2609
+ sqrtPriceUpperX96: tickToSqrtPriceX96(TICK_HI),
2610
+ liquidityActive: currentLiquidity
2611
+ }
2612
+ ];
2613
+ }
2614
+ let lo = 0;
2615
+ let hi = N;
2616
+ while (lo < hi) {
2617
+ const mid = lo + hi >>> 1;
2618
+ if (ticks[mid].tick > currentTick) hi = mid;
2619
+ else lo = mid + 1;
2620
+ }
2621
+ const i_above = lo;
2622
+ const i_below = i_above - 1;
2623
+ const segments = new Array(N + 1);
2624
+ const anchorLower = i_below >= 0 ? ticks[i_below].tick : TICK_LO;
2625
+ const anchorUpper = i_above < N ? ticks[i_above].tick : TICK_HI;
2626
+ segments[i_above] = {
2627
+ tickLower: anchorLower,
2628
+ tickUpper: anchorUpper,
2629
+ sqrtPriceLowerX96: 0n,
2630
+ // filled below
2631
+ sqrtPriceUpperX96: 0n,
2632
+ liquidityActive: currentLiquidity
2633
+ };
2634
+ let L = currentLiquidity;
2635
+ for (let k = i_below; k >= 0; k--) {
2636
+ L = L - ticks[k].liquidityNet;
2637
+ if (L < 0n) {
2638
+ throw new LiquidityCurveError(
2639
+ "CURVE_NEGATIVE_L" /* CURVE_NEGATIVE_L */,
2640
+ `buildLiquidityCurve: active liquidity went negative at tick ${ticks[k].tick} during left walk. Indicates inconsistent input.`
2641
+ );
2642
+ }
2643
+ const lower = k - 1 >= 0 ? ticks[k - 1].tick : TICK_LO;
2644
+ segments[k] = {
2645
+ tickLower: lower,
2646
+ tickUpper: ticks[k].tick,
2647
+ sqrtPriceLowerX96: 0n,
2648
+ sqrtPriceUpperX96: 0n,
2649
+ liquidityActive: L
2650
+ };
2651
+ }
2652
+ L = currentLiquidity;
2653
+ for (let j = i_above; j < N; j++) {
2654
+ L = L + ticks[j].liquidityNet;
2655
+ if (L < 0n) {
2656
+ throw new LiquidityCurveError(
2657
+ "CURVE_NEGATIVE_L" /* CURVE_NEGATIVE_L */,
2658
+ `buildLiquidityCurve: active liquidity went negative at tick ${ticks[j].tick} during right walk.`
2659
+ );
2660
+ }
2661
+ const upper = j + 1 < N ? ticks[j + 1].tick : TICK_HI;
2662
+ segments[j + 1] = {
2663
+ tickLower: ticks[j].tick,
2664
+ tickUpper: upper,
2665
+ sqrtPriceLowerX96: 0n,
2666
+ sqrtPriceUpperX96: 0n,
2667
+ liquidityActive: L
2668
+ };
2669
+ }
2670
+ if (segments[0].liquidityActive !== 0n) {
2671
+ throw new LiquidityCurveError(
2672
+ "CURVE_EDGE_NONZERO" /* CURVE_EDGE_NONZERO */,
2673
+ `buildLiquidityCurve: leftmost L = ${segments[0].liquidityActive}, expected 0. currentLiquidity inconsistent with tick set.`
2674
+ );
2675
+ }
2676
+ if (segments[N].liquidityActive !== 0n) {
2677
+ throw new LiquidityCurveError(
2678
+ "CURVE_EDGE_NONZERO" /* CURVE_EDGE_NONZERO */,
2679
+ `buildLiquidityCurve: rightmost L = ${segments[N].liquidityActive}, expected 0.`
2680
+ );
2681
+ }
2682
+ for (const seg of segments) {
2683
+ seg.sqrtPriceLowerX96 = tickToSqrtPriceX96(seg.tickLower);
2684
+ seg.sqrtPriceUpperX96 = tickToSqrtPriceX96(seg.tickUpper);
2685
+ }
2686
+ return segments;
2687
+ }
2688
+ function clipCurveToTickRange(curve, tickLo, tickHi) {
2689
+ if (curve.length === 0 || tickHi <= tickLo) return [];
2690
+ let lo = 0;
2691
+ let hi = curve.length;
2692
+ while (lo < hi) {
2693
+ const mid = lo + hi >>> 1;
2694
+ if (curve[mid].tickUpper > tickLo) hi = mid;
2695
+ else lo = mid + 1;
2696
+ }
2697
+ const startIdx = lo;
2698
+ lo = 0;
2699
+ hi = curve.length;
2700
+ while (lo < hi) {
2701
+ const mid = lo + hi >>> 1;
2702
+ if (curve[mid].tickLower >= tickHi) hi = mid;
2703
+ else lo = mid + 1;
2704
+ }
2705
+ const endIdx = lo;
2706
+ if (startIdx >= endIdx) return [];
2707
+ const out = [];
2708
+ for (let i = startIdx; i < endIdx; i++) {
2709
+ const seg = curve[i];
2710
+ const newLower = Math.max(seg.tickLower, tickLo);
2711
+ const newUpper = Math.min(seg.tickUpper, tickHi);
2712
+ if (newLower === seg.tickLower && newUpper === seg.tickUpper) {
2713
+ out.push(seg);
2714
+ } else {
2715
+ out.push({
2716
+ tickLower: newLower,
2717
+ tickUpper: newUpper,
2718
+ sqrtPriceLowerX96: tickToSqrtPriceX96(newLower),
2719
+ sqrtPriceUpperX96: tickToSqrtPriceX96(newUpper),
2720
+ liquidityActive: seg.liquidityActive
2721
+ });
2722
+ }
2723
+ }
2724
+ return out;
2725
+ }
2726
+
2727
+ // src/liquidity/fetchAllInitializedTicks.ts
2728
+ var CALLDATA_BYTES_PER_CALL = 36;
2729
+ var DEFAULT_CALLS_PER_BATCH = 250;
2730
+ var DEFAULT_MAX_TICKS = 5e4;
2731
+ async function fetchAllInitializedTicks(params) {
2732
+ const callsPerBatch = params.callsPerBatch ?? DEFAULT_CALLS_PER_BATCH;
2733
+ const maxTicks = params.maxTicks ?? DEFAULT_MAX_TICKS;
2734
+ const batchSizeBytes = callsPerBatch * CALLDATA_BYTES_PER_CALL;
2735
+ const blockNumber = params.blockNumber ?? await params.client.getBlockNumber();
2736
+ let tickSpacing = params.tickSpacing;
2737
+ if (tickSpacing === void 0) {
2738
+ const [resolved] = await params.client.multicall({
2739
+ contracts: [
2740
+ {
2741
+ address: params.pool,
2742
+ abi: v3PoolAbi,
2743
+ functionName: "tickSpacing"
2744
+ }
2745
+ ],
2746
+ allowFailure: false,
2747
+ blockNumber,
2748
+ batchSize: batchSizeBytes
2749
+ });
2750
+ tickSpacing = Number(resolved);
2751
+ }
2752
+ const { min: wordLo, max: wordHi } = bitmapWordRange(tickSpacing);
2753
+ const bitmapContracts = [];
2754
+ for (let w = wordLo; w <= wordHi; w++) {
2755
+ bitmapContracts.push({
2756
+ address: params.pool,
2757
+ abi: v3PoolAbi,
2758
+ functionName: "tickBitmap",
2759
+ args: [w]
2760
+ });
2761
+ }
2762
+ const bitmaps = await params.client.multicall({
2763
+ contracts: bitmapContracts,
2764
+ allowFailure: false,
2765
+ blockNumber,
2766
+ batchSize: batchSizeBytes
2767
+ });
2768
+ const initTicks = [];
2769
+ for (let i = 0; i < bitmaps.length; i++) {
2770
+ const word = bitmaps[i];
2771
+ if (word === 0n) continue;
2772
+ const wordPos = wordLo + i;
2773
+ const decoded = decodeBitmapWord(word, wordPos, tickSpacing);
2774
+ for (const t of decoded) initTicks.push(t);
2775
+ }
2776
+ if (initTicks.length > maxTicks) {
2777
+ throw new LiquidityCurveError(
2778
+ "TOO_MANY_TICKS" /* TOO_MANY_TICKS */,
2779
+ `fetchAllInitializedTicks: pool ${params.pool} has ${initTicks.length} initialized ticks, exceeds maxTicks=${maxTicks}. Pass maxTicks explicitly if you want the full set.`
2780
+ );
2781
+ }
2782
+ if (initTicks.length === 0) {
2783
+ return { blockNumber, tickSpacing, ticks: [] };
2784
+ }
2785
+ const ticksContracts = initTicks.map(
2786
+ (tick) => ({
2787
+ address: params.pool,
2788
+ abi: v3PoolAbi,
2789
+ functionName: "ticks",
2790
+ args: [tick]
2791
+ })
2792
+ );
2793
+ const ticksTuples = await params.client.multicall({
2794
+ contracts: ticksContracts,
2795
+ allowFailure: false,
2796
+ blockNumber,
2797
+ batchSize: batchSizeBytes
2798
+ });
2799
+ const ticks = ticksTuples.map((tuple, i) => ({
2800
+ tick: initTicks[i],
2801
+ liquidityGross: tuple[0],
2802
+ liquidityNet: tuple[1],
2803
+ feeGrowthOutside0X128: tuple[2],
2804
+ feeGrowthOutside1X128: tuple[3],
2805
+ initialized: tuple[7]
2806
+ }));
2807
+ for (const t of ticks) {
2808
+ if (!t.initialized) {
2809
+ throw new LiquidityCurveError(
2810
+ "ATOMICITY_VIOLATION" /* ATOMICITY_VIOLATION */,
2811
+ `fetchAllInitializedTicks: tick ${t.tick} bitmap-set but ticks().initialized=false at block ${blockNumber}. Snapshot is torn \u2014 RPC node likely served different blocks across batches.`
2812
+ );
2813
+ }
2814
+ }
2815
+ return { blockNumber, tickSpacing, ticks };
2816
+ }
2817
+
2818
+ // src/liquidity/fetchLiquidityCurve.ts
2819
+ async function fetchLiquidityCurve(params) {
2820
+ const blockNumber = params.blockNumber ?? await params.client.getBlockNumber();
2821
+ const poolState = params.poolState ?? await readPoolState(params.client, params.pool, blockNumber);
2822
+ const ticksResult = await fetchAllInitializedTicks({
2823
+ client: params.client,
2824
+ pool: params.pool,
2825
+ blockNumber,
2826
+ tickSpacing: poolState.tickSpacing,
2827
+ callsPerBatch: params.callsPerBatch,
2828
+ maxTicks: params.maxTicks
2829
+ });
2830
+ const curve = buildLiquidityCurve({
2831
+ ticks: ticksResult.ticks,
2832
+ currentTick: poolState.tick,
2833
+ currentLiquidity: poolState.liquidity,
2834
+ tickSpacing: poolState.tickSpacing
2835
+ });
2836
+ let lo = 0;
2837
+ let hi = curve.length;
2838
+ while (lo < hi) {
2839
+ const mid = lo + hi >>> 1;
2840
+ if (curve[mid].tickUpper > poolState.tick) hi = mid;
2841
+ else lo = mid + 1;
2842
+ }
2843
+ const currentSegmentIndex = lo;
2844
+ return {
2845
+ blockNumber,
2846
+ poolState,
2847
+ ticks: ticksResult.ticks,
2848
+ curve,
2849
+ currentSegmentIndex
2850
+ };
2851
+ }
2852
+
2853
+ // src/direct/addLiquidityDirect.ts
2854
+ async function addLiquidityDirect(params) {
2855
+ const npm = V3_NPM_ADDRESSES[params.chainId];
2856
+ if (!npm) {
2857
+ throw new Error(
2858
+ `addLiquidityDirect: no NonfungiblePositionManager for chainId ${params.chainId}`
2859
+ );
2860
+ }
2861
+ const factory = V3_FACTORY_ADDRESSES[params.chainId];
2862
+ if (!factory) {
2863
+ throw new Error(
2864
+ `addLiquidityDirect: no V3 factory for chainId ${params.chainId}`
2865
+ );
2866
+ }
2867
+ if (params.tickLower >= params.tickUpper) {
2868
+ throw new Error(
2869
+ `addLiquidityDirect: tickLower (${params.tickLower}) must be strictly less than tickUpper (${params.tickUpper})`
2870
+ );
2871
+ }
2872
+ const ts = tickSpacingForFee(params.fee);
2873
+ if (ts !== void 0) {
2874
+ if (params.tickLower % ts !== 0 || params.tickUpper % ts !== 0) {
2875
+ throw new Error(
2876
+ `addLiquidityDirect: ticks must be multiples of tickSpacing ${ts} for fee ${params.fee}`
2877
+ );
2878
+ }
2879
+ }
2880
+ const has0 = params.amount0Desired !== void 0 && params.amount0Desired > 0n;
2881
+ const has1 = params.amount1Desired !== void 0 && params.amount1Desired > 0n;
2882
+ if (has0 === has1) {
2883
+ throw new Error(
2884
+ "addLiquidityDirect: exactly one of amount0Desired / amount1Desired must be a positive bigint"
2885
+ );
2886
+ }
2887
+ const slippageBps = params.slippageBps ?? 50;
2888
+ if (!Number.isInteger(slippageBps) || slippageBps < 0 || slippageBps > 1e4) {
2889
+ throw new Error(
2890
+ `addLiquidityDirect: slippageBps (${slippageBps}) must be an integer in [0, 10000]`
2891
+ );
2892
+ }
2893
+ const account = params.walletClient.account;
2894
+ if (!account) {
2895
+ throw new Error(
2896
+ "addLiquidityDirect: walletClient has no account attached \u2014 cannot send tx"
2897
+ );
2898
+ }
2899
+ if (account.address.toLowerCase() !== params.userAddress.toLowerCase()) {
2900
+ throw new Error(
2901
+ `addLiquidityDirect: walletClient.account.address (${account.address}) must equal userAddress (${params.userAddress})`
2902
+ );
2903
+ }
2904
+ const code = await params.publicClient.getCode({ address: params.userAddress });
2905
+ const delegate = parseEip7702DelegatedAddress5(code);
2906
+ if (!delegate) {
2907
+ throw new Error(
2908
+ `addLiquidityDirect: user ${params.userAddress} is not EIP-7702 delegated. Run \`delegateDirect()\` first or use the AA path.`
2909
+ );
2910
+ }
2911
+ const pool = computeV3PoolAddress({
2912
+ factory,
2913
+ tokenA: params.token0,
2914
+ tokenB: params.token1,
2915
+ fee: params.fee,
2916
+ initCodeHash: V3_POOL_INIT_CODE_HASH
2917
+ });
2918
+ const poolState = await readPoolState(params.publicClient, pool);
2919
+ const sqrtL = tickToSqrtPriceX96(params.tickLower);
2920
+ const sqrtU = tickToSqrtPriceX96(params.tickUpper);
2921
+ const { liquidity, amount0, amount1 } = estimateLiquidityFromOneSide({
2922
+ sqrtPriceX96Current: poolState.sqrtPriceX96,
2923
+ sqrtPriceX96Lower: sqrtL,
2924
+ sqrtPriceX96Upper: sqrtU,
2925
+ amount0Desired: params.amount0Desired,
2926
+ amount1Desired: params.amount1Desired
2927
+ });
2928
+ const { amount0Min, amount1Min } = applyMintSlippage({
2929
+ amount0Desired: amount0,
2930
+ amount1Desired: amount1,
2931
+ slippageBps
2932
+ });
2933
+ const deadline = params.deadline ?? BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
2934
+ const operations = buildMint({
2935
+ npm,
2936
+ token0: params.token0,
2937
+ token1: params.token1,
2938
+ fee: params.fee,
2939
+ tickLower: params.tickLower,
2940
+ tickUpper: params.tickUpper,
2941
+ amount0Desired: amount0,
2942
+ amount1Desired: amount1,
2943
+ amount0Min,
2944
+ amount1Min,
2945
+ recipient: params.userAddress,
2946
+ deadline
2947
+ });
2948
+ const partial = buildPartialUserOperation2({
2949
+ sender: params.userAddress,
2950
+ nonce: 0n,
2951
+ operations
2952
+ });
2953
+ const txHash = await params.walletClient.sendTransaction({
2954
+ account,
2955
+ chain: params.walletClient.chain,
2956
+ to: params.userAddress,
2957
+ value: 0n,
2958
+ data: partial.callData
2959
+ });
2960
+ let receipt;
2961
+ let tokenId;
2962
+ if (params.waitForReceipt !== false) {
2963
+ try {
2964
+ receipt = await params.publicClient.waitForTransactionReceipt({ hash: txHash });
2965
+ tokenId = parseMintedTokenId(receipt, npm, params.userAddress);
2966
+ } catch (err) {
2967
+ params.onWarning?.(
2968
+ `addLiquidityDirect: tx ${txHash} sent but receipt fetch failed: ${err instanceof Error ? err.message : String(err)}`
2969
+ );
2970
+ }
2971
+ }
2972
+ return {
2973
+ txHash,
2974
+ receipt,
2975
+ tokenId,
2976
+ amount0Desired: amount0,
2977
+ amount1Desired: amount1,
2978
+ amount0Min,
2979
+ amount1Min,
2980
+ liquidity,
2981
+ deadline
2982
+ };
2983
+ }
2984
+ function parseMintedTokenId(receipt, npm, user) {
2985
+ const userLower = user.toLowerCase();
2986
+ const zero = "0x0000000000000000000000000000000000000000";
2987
+ for (const log of receipt.logs) {
2988
+ if (log.address.toLowerCase() !== npm.toLowerCase()) continue;
2989
+ try {
2990
+ const decoded = decodeEventLog({
2991
+ abi: nonfungiblePositionManagerAbi,
2992
+ data: log.data,
2993
+ topics: log.topics
2994
+ });
2995
+ if (decoded.eventName !== "Transfer") continue;
2996
+ const args = decoded.args;
2997
+ if (args.from.toLowerCase() === zero && args.to.toLowerCase() === userLower) {
2998
+ return args.tokenId;
2999
+ }
3000
+ } catch {
3001
+ }
3002
+ }
3003
+ return void 0;
3004
+ }
3005
+
3006
+ // src/direct/increaseLiquidityDirect.ts
3007
+ import {
3008
+ parseEip7702DelegatedAddress as parseEip7702DelegatedAddress6,
3009
+ buildPartialUserOperation as buildPartialUserOperation3,
3010
+ computeV3PoolAddress as computeV3PoolAddress2,
3011
+ V3_FACTORY_ADDRESSES as V3_FACTORY_ADDRESSES2,
3012
+ V3_POOL_INIT_CODE_HASH as V3_POOL_INIT_CODE_HASH2
3013
+ } from "@pafi-dev/core";
3014
+ async function increaseLiquidityDirect(params) {
3015
+ const npm = V3_NPM_ADDRESSES[params.chainId];
3016
+ if (!npm) {
3017
+ throw new Error(
3018
+ `increaseLiquidityDirect: no NonfungiblePositionManager for chainId ${params.chainId}`
3019
+ );
3020
+ }
3021
+ const factory = V3_FACTORY_ADDRESSES2[params.chainId];
3022
+ if (!factory) {
3023
+ throw new Error(
3024
+ `increaseLiquidityDirect: no V3 factory for chainId ${params.chainId}`
3025
+ );
3026
+ }
3027
+ if (params.tokenId <= 0n) {
3028
+ throw new Error("increaseLiquidityDirect: tokenId must be positive");
3029
+ }
3030
+ const has0 = params.amount0Desired !== void 0 && params.amount0Desired > 0n;
3031
+ const has1 = params.amount1Desired !== void 0 && params.amount1Desired > 0n;
3032
+ if (has0 === has1) {
3033
+ throw new Error(
3034
+ "increaseLiquidityDirect: exactly one of amount0Desired / amount1Desired must be a positive bigint"
3035
+ );
3036
+ }
3037
+ const slippageBps = params.slippageBps ?? 50;
3038
+ if (!Number.isInteger(slippageBps) || slippageBps < 0 || slippageBps > 1e4) {
3039
+ throw new Error(
3040
+ `increaseLiquidityDirect: slippageBps (${slippageBps}) must be an integer in [0, 10000]`
3041
+ );
3042
+ }
3043
+ const account = params.walletClient.account;
3044
+ if (!account) {
3045
+ throw new Error("increaseLiquidityDirect: walletClient has no account attached");
3046
+ }
3047
+ if (account.address.toLowerCase() !== params.userAddress.toLowerCase()) {
3048
+ throw new Error(
3049
+ `increaseLiquidityDirect: walletClient.account.address (${account.address}) must equal userAddress (${params.userAddress})`
3050
+ );
3051
+ }
3052
+ const code = await params.publicClient.getCode({ address: params.userAddress });
3053
+ const delegate = parseEip7702DelegatedAddress6(code);
3054
+ if (!delegate) {
3055
+ throw new Error(
3056
+ `increaseLiquidityDirect: user ${params.userAddress} is not EIP-7702 delegated.`
3057
+ );
3058
+ }
3059
+ const position = await readPosition(params.publicClient, npm, params.tokenId);
3060
+ const pool = computeV3PoolAddress2({
3061
+ factory,
3062
+ tokenA: position.token0,
3063
+ tokenB: position.token1,
3064
+ fee: position.fee,
3065
+ initCodeHash: V3_POOL_INIT_CODE_HASH2
3066
+ });
3067
+ const poolState = await readPoolState(params.publicClient, pool);
3068
+ const sqrtL = tickToSqrtPriceX96(position.tickLower);
3069
+ const sqrtU = tickToSqrtPriceX96(position.tickUpper);
3070
+ const { liquidity, amount0, amount1 } = estimateLiquidityFromOneSide({
3071
+ sqrtPriceX96Current: poolState.sqrtPriceX96,
3072
+ sqrtPriceX96Lower: sqrtL,
3073
+ sqrtPriceX96Upper: sqrtU,
3074
+ amount0Desired: params.amount0Desired,
3075
+ amount1Desired: params.amount1Desired
3076
+ });
3077
+ const { amount0Min, amount1Min } = applyMintSlippage({
3078
+ amount0Desired: amount0,
3079
+ amount1Desired: amount1,
3080
+ slippageBps
3081
+ });
3082
+ const deadline = params.deadline ?? BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
3083
+ const operations = buildIncreaseLiquidity({
3084
+ npm,
3085
+ token0: position.token0,
3086
+ token1: position.token1,
3087
+ tokenId: params.tokenId,
3088
+ amount0Desired: amount0,
3089
+ amount1Desired: amount1,
3090
+ amount0Min,
3091
+ amount1Min,
3092
+ deadline
3093
+ });
3094
+ const partial = buildPartialUserOperation3({
3095
+ sender: params.userAddress,
3096
+ nonce: 0n,
3097
+ operations
3098
+ });
3099
+ const txHash = await params.walletClient.sendTransaction({
3100
+ account,
3101
+ chain: params.walletClient.chain,
3102
+ to: params.userAddress,
3103
+ value: 0n,
3104
+ data: partial.callData
3105
+ });
3106
+ let receipt;
3107
+ if (params.waitForReceipt !== false) {
3108
+ try {
3109
+ receipt = await params.publicClient.waitForTransactionReceipt({ hash: txHash });
3110
+ } catch (err) {
3111
+ params.onWarning?.(
3112
+ `increaseLiquidityDirect: tx ${txHash} sent but receipt fetch failed: ${err instanceof Error ? err.message : String(err)}`
3113
+ );
3114
+ }
3115
+ }
3116
+ return {
3117
+ txHash,
3118
+ receipt,
3119
+ amount0Desired: amount0,
3120
+ amount1Desired: amount1,
3121
+ amount0Min,
3122
+ amount1Min,
3123
+ liquidityAdded: liquidity,
3124
+ deadline
3125
+ };
3126
+ }
3127
+
3128
+ // src/direct/removeLiquidityDirect.ts
3129
+ import {
3130
+ parseEip7702DelegatedAddress as parseEip7702DelegatedAddress7,
3131
+ buildPartialUserOperation as buildPartialUserOperation4,
3132
+ computeV3PoolAddress as computeV3PoolAddress3,
3133
+ V3_FACTORY_ADDRESSES as V3_FACTORY_ADDRESSES3,
3134
+ V3_POOL_INIT_CODE_HASH as V3_POOL_INIT_CODE_HASH3
3135
+ } from "@pafi-dev/core";
3136
+ async function removeLiquidityDirect(params) {
3137
+ const npm = V3_NPM_ADDRESSES[params.chainId];
3138
+ if (!npm) {
3139
+ throw new Error(
3140
+ `removeLiquidityDirect: no NonfungiblePositionManager for chainId ${params.chainId}`
3141
+ );
3142
+ }
3143
+ const factory = V3_FACTORY_ADDRESSES3[params.chainId];
3144
+ if (!factory) {
3145
+ throw new Error(`removeLiquidityDirect: no V3 factory for chainId ${params.chainId}`);
3146
+ }
3147
+ if (params.tokenId <= 0n) {
3148
+ throw new Error("removeLiquidityDirect: tokenId must be positive");
3149
+ }
3150
+ const hasL = params.liquidity !== void 0 && params.liquidity > 0n;
3151
+ const hasBps = params.liquidityBps !== void 0 && params.liquidityBps > 0 && params.liquidityBps <= 1e4;
3152
+ if (hasL === hasBps) {
3153
+ throw new Error(
3154
+ "removeLiquidityDirect: exactly one of liquidity (>0) or liquidityBps (1..10000) must be set"
3155
+ );
3156
+ }
3157
+ const slippageBps = params.slippageBps ?? 50;
3158
+ if (!Number.isInteger(slippageBps) || slippageBps < 0 || slippageBps > 1e4) {
3159
+ throw new Error(
3160
+ `removeLiquidityDirect: slippageBps (${slippageBps}) must be an integer in [0, 10000]`
3161
+ );
3162
+ }
3163
+ const account = params.walletClient.account;
3164
+ if (!account) {
3165
+ throw new Error("removeLiquidityDirect: walletClient has no account attached");
3166
+ }
3167
+ if (account.address.toLowerCase() !== params.userAddress.toLowerCase()) {
3168
+ throw new Error(
3169
+ `removeLiquidityDirect: walletClient.account.address (${account.address}) must equal userAddress (${params.userAddress})`
3170
+ );
3171
+ }
3172
+ const code = await params.publicClient.getCode({ address: params.userAddress });
3173
+ if (!parseEip7702DelegatedAddress7(code)) {
3174
+ throw new Error(
3175
+ `removeLiquidityDirect: user ${params.userAddress} is not EIP-7702 delegated.`
3176
+ );
3177
+ }
3178
+ const position = await readPosition(params.publicClient, npm, params.tokenId);
3179
+ if (position.liquidity === 0n) {
3180
+ throw new Error(
3181
+ `removeLiquidityDirect: position ${params.tokenId} has zero liquidity \u2014 nothing to remove`
3182
+ );
3183
+ }
3184
+ const liquidityToRemove = hasL ? params.liquidity : position.liquidity * BigInt(params.liquidityBps) / 10000n;
3185
+ if (liquidityToRemove === 0n) {
3186
+ throw new Error(
3187
+ "removeLiquidityDirect: computed liquidity to remove is zero (bps too small for position size)"
3188
+ );
3189
+ }
3190
+ if (liquidityToRemove > position.liquidity) {
3191
+ throw new Error(
3192
+ `removeLiquidityDirect: liquidity to remove (${liquidityToRemove}) exceeds position liquidity (${position.liquidity})`
3193
+ );
3194
+ }
3195
+ const pool = computeV3PoolAddress3({
3196
+ factory,
3197
+ tokenA: position.token0,
3198
+ tokenB: position.token1,
3199
+ fee: position.fee,
3200
+ initCodeHash: V3_POOL_INIT_CODE_HASH3
3201
+ });
3202
+ const poolState = await readPoolState(params.publicClient, pool);
3203
+ const { amount0: amount0Expected, amount1: amount1Expected } = getAmountsForLiquidity({
3204
+ sqrtPriceX96Current: poolState.sqrtPriceX96,
3205
+ sqrtPriceX96Lower: tickToSqrtPriceX96(position.tickLower),
3206
+ sqrtPriceX96Upper: tickToSqrtPriceX96(position.tickUpper),
3207
+ liquidity: liquidityToRemove
3208
+ });
3209
+ const { amount0Min, amount1Min } = applyRemoveSlippage({
3210
+ amount0Expected,
3211
+ amount1Expected,
3212
+ slippageBps
3213
+ });
3214
+ const deadline = params.deadline ?? BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
3215
+ const operations = [
3216
+ buildDecreaseLiquidity({
3217
+ npm,
3218
+ tokenId: params.tokenId,
3219
+ liquidity: liquidityToRemove,
3220
+ amount0Min,
3221
+ amount1Min,
3222
+ deadline
3223
+ }),
3224
+ buildCollect({
3225
+ npm,
3226
+ tokenId: params.tokenId,
3227
+ recipient: params.userAddress
3228
+ })
3229
+ ];
3230
+ const partial = buildPartialUserOperation4({
3231
+ sender: params.userAddress,
3232
+ nonce: 0n,
3233
+ operations
3234
+ });
3235
+ const txHash = await params.walletClient.sendTransaction({
3236
+ account,
3237
+ chain: params.walletClient.chain,
3238
+ to: params.userAddress,
3239
+ value: 0n,
3240
+ data: partial.callData
3241
+ });
3242
+ let receipt;
3243
+ if (params.waitForReceipt !== false) {
3244
+ try {
3245
+ receipt = await params.publicClient.waitForTransactionReceipt({ hash: txHash });
3246
+ } catch (err) {
3247
+ params.onWarning?.(
3248
+ `removeLiquidityDirect: tx ${txHash} sent but receipt fetch failed: ${err instanceof Error ? err.message : String(err)}`
3249
+ );
3250
+ }
3251
+ }
3252
+ return {
3253
+ txHash,
3254
+ receipt,
3255
+ liquidityRemoved: liquidityToRemove,
3256
+ amount0Expected,
3257
+ amount1Expected,
3258
+ amount0Min,
3259
+ amount1Min,
3260
+ deadline
3261
+ };
3262
+ }
3263
+
3264
+ // src/direct/closePositionDirect.ts
3265
+ import {
3266
+ parseEip7702DelegatedAddress as parseEip7702DelegatedAddress8,
3267
+ buildPartialUserOperation as buildPartialUserOperation5,
3268
+ computeV3PoolAddress as computeV3PoolAddress4,
3269
+ V3_FACTORY_ADDRESSES as V3_FACTORY_ADDRESSES4,
3270
+ V3_POOL_INIT_CODE_HASH as V3_POOL_INIT_CODE_HASH4
3271
+ } from "@pafi-dev/core";
3272
+ async function closePositionDirect(params) {
3273
+ const npm = V3_NPM_ADDRESSES[params.chainId];
3274
+ if (!npm) {
3275
+ throw new Error(
3276
+ `closePositionDirect: no NonfungiblePositionManager for chainId ${params.chainId}`
3277
+ );
3278
+ }
3279
+ const factory = V3_FACTORY_ADDRESSES4[params.chainId];
3280
+ if (!factory) {
3281
+ throw new Error(`closePositionDirect: no V3 factory for chainId ${params.chainId}`);
3282
+ }
3283
+ if (params.tokenId <= 0n) {
3284
+ throw new Error("closePositionDirect: tokenId must be positive");
3285
+ }
3286
+ const slippageBps = params.slippageBps ?? 50;
3287
+ if (!Number.isInteger(slippageBps) || slippageBps < 0 || slippageBps > 1e4) {
3288
+ throw new Error(
3289
+ `closePositionDirect: slippageBps (${slippageBps}) must be an integer in [0, 10000]`
3290
+ );
3291
+ }
3292
+ const account = params.walletClient.account;
3293
+ if (!account) {
3294
+ throw new Error("closePositionDirect: walletClient has no account attached");
3295
+ }
3296
+ if (account.address.toLowerCase() !== params.userAddress.toLowerCase()) {
3297
+ throw new Error(
3298
+ `closePositionDirect: walletClient.account.address (${account.address}) must equal userAddress (${params.userAddress})`
3299
+ );
3300
+ }
3301
+ const code = await params.publicClient.getCode({ address: params.userAddress });
3302
+ if (!parseEip7702DelegatedAddress8(code)) {
3303
+ throw new Error(
3304
+ `closePositionDirect: user ${params.userAddress} is not EIP-7702 delegated.`
3305
+ );
3306
+ }
3307
+ const position = await readPosition(params.publicClient, npm, params.tokenId);
3308
+ let amount0Expected = 0n;
3309
+ let amount1Expected = 0n;
3310
+ let amount0Min = 0n;
3311
+ let amount1Min = 0n;
3312
+ if (position.liquidity > 0n) {
3313
+ const pool = computeV3PoolAddress4({
3314
+ factory,
3315
+ tokenA: position.token0,
3316
+ tokenB: position.token1,
3317
+ fee: position.fee,
3318
+ initCodeHash: V3_POOL_INIT_CODE_HASH4
3319
+ });
3320
+ const poolState = await readPoolState(params.publicClient, pool);
3321
+ const amounts = getAmountsForLiquidity({
3322
+ sqrtPriceX96Current: poolState.sqrtPriceX96,
3323
+ sqrtPriceX96Lower: tickToSqrtPriceX96(position.tickLower),
3324
+ sqrtPriceX96Upper: tickToSqrtPriceX96(position.tickUpper),
3325
+ liquidity: position.liquidity
3326
+ });
3327
+ amount0Expected = amounts.amount0;
3328
+ amount1Expected = amounts.amount1;
3329
+ const min = applyRemoveSlippage({
3330
+ amount0Expected,
3331
+ amount1Expected,
3332
+ slippageBps
3333
+ });
3334
+ amount0Min = min.amount0Min;
3335
+ amount1Min = min.amount1Min;
3336
+ }
3337
+ const deadline = params.deadline ?? BigInt(Math.floor(Date.now() / 1e3) + 5 * 60);
3338
+ const operations = buildClosePosition({
3339
+ npm,
3340
+ tokenId: params.tokenId,
3341
+ liquidity: position.liquidity,
3342
+ hasOutstandingFees: position.tokensOwed0 + position.tokensOwed1 > 0n,
3343
+ recipient: params.userAddress,
3344
+ amount0Min,
3345
+ amount1Min,
3346
+ deadline
3347
+ });
3348
+ const partial = buildPartialUserOperation5({
3349
+ sender: params.userAddress,
3350
+ nonce: 0n,
3351
+ operations
3352
+ });
3353
+ const txHash = await params.walletClient.sendTransaction({
3354
+ account,
3355
+ chain: params.walletClient.chain,
3356
+ to: params.userAddress,
3357
+ value: 0n,
3358
+ data: partial.callData
3359
+ });
3360
+ let receipt;
3361
+ if (params.waitForReceipt !== false) {
3362
+ try {
3363
+ receipt = await params.publicClient.waitForTransactionReceipt({ hash: txHash });
3364
+ } catch (err) {
3365
+ params.onWarning?.(
3366
+ `closePositionDirect: tx ${txHash} sent but receipt fetch failed: ${err instanceof Error ? err.message : String(err)}`
3367
+ );
3368
+ }
3369
+ }
3370
+ return {
3371
+ txHash,
3372
+ receipt,
3373
+ liquidityRemoved: position.liquidity,
3374
+ amount0Expected,
3375
+ amount1Expected,
3376
+ amount0Min,
3377
+ amount1Min,
3378
+ deadline
3379
+ };
3380
+ }
3381
+
3382
+ // src/direct/collectFeesDirect.ts
3383
+ import {
3384
+ parseEip7702DelegatedAddress as parseEip7702DelegatedAddress9,
3385
+ buildPartialUserOperation as buildPartialUserOperation6,
3386
+ computeV3PoolAddress as computeV3PoolAddress5,
3387
+ V3_FACTORY_ADDRESSES as V3_FACTORY_ADDRESSES5,
3388
+ V3_POOL_INIT_CODE_HASH as V3_POOL_INIT_CODE_HASH5
3389
+ } from "@pafi-dev/core";
3390
+ async function collectFeesDirect(params) {
3391
+ const npm = V3_NPM_ADDRESSES[params.chainId];
3392
+ if (!npm) {
3393
+ throw new Error(
3394
+ `collectFeesDirect: no NonfungiblePositionManager for chainId ${params.chainId}`
3395
+ );
3396
+ }
3397
+ const factory = V3_FACTORY_ADDRESSES5[params.chainId];
3398
+ if (!factory) {
3399
+ throw new Error(`collectFeesDirect: no V3 factory for chainId ${params.chainId}`);
3400
+ }
3401
+ if (params.tokenId <= 0n) {
3402
+ throw new Error("collectFeesDirect: tokenId must be positive");
3403
+ }
3404
+ const account = params.walletClient.account;
3405
+ if (!account) {
3406
+ throw new Error("collectFeesDirect: walletClient has no account attached");
3407
+ }
3408
+ if (account.address.toLowerCase() !== params.userAddress.toLowerCase()) {
3409
+ throw new Error(
3410
+ `collectFeesDirect: walletClient.account.address (${account.address}) must equal userAddress (${params.userAddress})`
3411
+ );
3412
+ }
3413
+ const code = await params.publicClient.getCode({ address: params.userAddress });
3414
+ if (!parseEip7702DelegatedAddress9(code)) {
3415
+ throw new Error(
3416
+ `collectFeesDirect: user ${params.userAddress} is not EIP-7702 delegated.`
3417
+ );
3418
+ }
3419
+ const position = await readPosition(params.publicClient, npm, params.tokenId);
3420
+ let amount0Expected = position.tokensOwed0;
3421
+ let amount1Expected = position.tokensOwed1;
3422
+ if (position.liquidity > 0n) {
3423
+ const pool = computeV3PoolAddress5({
3424
+ factory,
3425
+ tokenA: position.token0,
3426
+ tokenB: position.token1,
3427
+ fee: position.fee,
3428
+ initCodeHash: V3_POOL_INIT_CODE_HASH5
3429
+ });
3430
+ const [poolState, ticks, globals] = await Promise.all([
3431
+ readPoolState(params.publicClient, pool),
3432
+ readTicksAt(params.publicClient, pool, position.tickLower, position.tickUpper),
3433
+ readFeeGrowthGlobals(params.publicClient, pool)
3434
+ ]);
3435
+ const feeGrowthInside0 = computeFeeGrowthInside({
3436
+ currentTick: poolState.tick,
3437
+ tickLower: position.tickLower,
3438
+ tickUpper: position.tickUpper,
3439
+ feeGrowthGlobalX128: globals.feeGrowthGlobal0X128,
3440
+ feeGrowthOutsideLowerX128: ticks.lower.feeGrowthOutside0X128,
3441
+ feeGrowthOutsideUpperX128: ticks.upper.feeGrowthOutside0X128
3442
+ });
3443
+ const feeGrowthInside1 = computeFeeGrowthInside({
3444
+ currentTick: poolState.tick,
3445
+ tickLower: position.tickLower,
3446
+ tickUpper: position.tickUpper,
3447
+ feeGrowthGlobalX128: globals.feeGrowthGlobal1X128,
3448
+ feeGrowthOutsideLowerX128: ticks.lower.feeGrowthOutside1X128,
3449
+ feeGrowthOutsideUpperX128: ticks.upper.feeGrowthOutside1X128
3450
+ });
3451
+ const pending0 = wrappingDelta(
3452
+ feeGrowthInside0,
3453
+ position.feeGrowthInside0LastX128
3454
+ ) * position.liquidity / (1n << 128n);
3455
+ const pending1 = wrappingDelta(
3456
+ feeGrowthInside1,
3457
+ position.feeGrowthInside1LastX128
3458
+ ) * position.liquidity / (1n << 128n);
3459
+ amount0Expected += pending0;
3460
+ amount1Expected += pending1;
3461
+ }
3462
+ const recipient = params.recipient ?? params.userAddress;
3463
+ const operations = [buildCollect({ npm, tokenId: params.tokenId, recipient })];
3464
+ const partial = buildPartialUserOperation6({
3465
+ sender: params.userAddress,
3466
+ nonce: 0n,
3467
+ operations
3468
+ });
3469
+ const txHash = await params.walletClient.sendTransaction({
3470
+ account,
3471
+ chain: params.walletClient.chain,
3472
+ to: params.userAddress,
3473
+ value: 0n,
3474
+ data: partial.callData
3475
+ });
3476
+ let receipt;
3477
+ if (params.waitForReceipt !== false) {
3478
+ try {
3479
+ receipt = await params.publicClient.waitForTransactionReceipt({ hash: txHash });
3480
+ } catch (err) {
3481
+ params.onWarning?.(
3482
+ `collectFeesDirect: tx ${txHash} sent but receipt fetch failed: ${err instanceof Error ? err.message : String(err)}`
3483
+ );
3484
+ }
3485
+ }
3486
+ return {
3487
+ txHash,
3488
+ receipt,
3489
+ amount0Expected,
3490
+ amount1Expected,
3491
+ recipient
3492
+ };
3493
+ }
3494
+ function computeFeeGrowthInside(params) {
3495
+ const {
3496
+ currentTick,
3497
+ tickLower,
3498
+ tickUpper,
3499
+ feeGrowthGlobalX128,
3500
+ feeGrowthOutsideLowerX128,
3501
+ feeGrowthOutsideUpperX128
3502
+ } = params;
3503
+ let feeGrowthBelow;
3504
+ if (currentTick >= tickLower) {
3505
+ feeGrowthBelow = feeGrowthOutsideLowerX128;
3506
+ } else {
3507
+ feeGrowthBelow = wrappingDelta(feeGrowthGlobalX128, feeGrowthOutsideLowerX128);
3508
+ }
3509
+ let feeGrowthAbove;
3510
+ if (currentTick < tickUpper) {
3511
+ feeGrowthAbove = feeGrowthOutsideUpperX128;
3512
+ } else {
3513
+ feeGrowthAbove = wrappingDelta(feeGrowthGlobalX128, feeGrowthOutsideUpperX128);
3514
+ }
3515
+ return wrappingDelta(
3516
+ wrappingDelta(feeGrowthGlobalX128, feeGrowthBelow),
3517
+ feeGrowthAbove
3518
+ );
3519
+ }
3520
+ function wrappingDelta(a, b) {
3521
+ const TWO_256 = 1n << 256n;
3522
+ return (a - b + TWO_256) % TWO_256;
3523
+ }
1524
3524
  export {
3525
+ CALLDATA_BYTES_PER_CALL,
3526
+ LiquidityCurveError,
3527
+ LiquidityCurveErrorCode,
3528
+ MAX_SQRT_RATIO,
3529
+ MAX_TICK,
3530
+ MIN_SQRT_RATIO,
3531
+ MIN_TICK,
1525
3532
  PAFI_SUBGRAPH_URL,
3533
+ Q192,
3534
+ Q96,
1526
3535
  TradingHandlers,
3536
+ UINT128_MAX2 as UINT128_MAX,
3537
+ V3_NPM_ADDRESSES,
1527
3538
  V3_SWAP_EXACT_IN,
1528
3539
  V3_SWAP_EXACT_OUT,
3540
+ addLiquidityDirect,
3541
+ applyMintSlippage,
3542
+ applyRemoveSlippage,
3543
+ bitmapWordRange,
1529
3544
  buildAllPaths,
3545
+ buildBurn,
3546
+ buildClosePosition,
3547
+ buildCollect,
3548
+ buildDecreaseLiquidity,
1530
3549
  buildErc20ApprovalCalldata,
3550
+ buildIncreaseLiquidity,
3551
+ buildLiquidityCurve,
3552
+ buildMint,
1531
3553
  buildPermit2ApprovalCalldata,
1532
3554
  buildSwapUserOp,
1533
3555
  buildSwapUserOpExactOut,
@@ -1536,19 +3558,48 @@ export {
1536
3558
  buildV3SwapInputExactIn,
1537
3559
  buildV3SwapInputExactOut,
1538
3560
  checkAllowance,
3561
+ clipCurveToTickRange,
3562
+ closePositionDirect,
3563
+ collectFeesDirect,
1539
3564
  combineRoutes,
3565
+ decodeBitmapWord,
3566
+ estimateLiquidityFromOneSide,
3567
+ fetchAllInitializedTicks,
3568
+ fetchLiquidityCurve,
1540
3569
  fetchPafiPools,
1541
3570
  findBestQuote,
1542
3571
  findBestQuoteExactOut,
3572
+ getAmount0ForLiquidity,
3573
+ getAmount1ForLiquidity,
3574
+ getAmountsForLiquidity,
3575
+ getLiquidityForAmount0,
3576
+ getLiquidityForAmount1,
3577
+ increaseLiquidityDirect,
3578
+ maxUsableTick,
3579
+ minUsableTick,
3580
+ nearestUsableTick,
3581
+ nonfungiblePositionManagerAbi,
1543
3582
  perpDepositDirect,
3583
+ priceToTick,
1544
3584
  quoteBestRoute,
1545
3585
  quoteBestRouteExactOut,
1546
3586
  quoteExactInput,
1547
3587
  quoteExactInputSingle,
1548
3588
  quoteExactOutput,
1549
3589
  quoteExactOutputSingle,
3590
+ readFeeGrowthGlobals,
3591
+ readPoolState,
3592
+ readPosition,
3593
+ readTicksAt,
3594
+ removeLiquidityDirect,
1550
3595
  simulateSwap,
3596
+ sqrtPriceX96ToTick,
1551
3597
  swapDirect,
1552
- swapDirectExactOut
3598
+ swapDirectExactOut,
3599
+ tickSpacingForFee,
3600
+ tickToBitmapPosition,
3601
+ tickToPrice,
3602
+ tickToSqrtPriceX96,
3603
+ v3PoolAbi
1553
3604
  };
1554
3605
  //# sourceMappingURL=index.js.map