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