lpsignal 0.9.0 → 0.11.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 CHANGED
@@ -104,6 +104,85 @@ app.post('/lpsignal', express.raw({ type: 'application/json' }), (req, res) => {
104
104
  Pass the raw body (a `Buffer` or string), never re-serialised JSON. Deliveries older than 5 minutes are rejected
105
105
  (`toleranceSec`); every retry is signed afresh.
106
106
 
107
+ ## Adding and removing liquidity
108
+
109
+ `lpsignal/liquidity` builds the transactions to add liquidity to a pool (in the range you choose) and to remove it,
110
+ on the pools' official position managers — Uniswap v3, PancakeSwap v3, Aerodrome and Velodrome Slipstream. You sign
111
+ and send them with your own [viem](https://viem.sh) wallet: your keys never reach the SDK, the position is always minted
112
+ to and collected by your own address, and there is no LPSignal contract or fee in between. Uniswap v4 pools are not
113
+ supported here (use the Uniswap app). Install viem next to the SDK: `npm i lpsignal viem`.
114
+
115
+ ```js
116
+ import { LPSignal } from 'lpsignal';
117
+ import { planAddLiquidity, planRemoveLiquidity, positions, sendPlan } from 'lpsignal/liquidity';
118
+ import { createPublicClient, createWalletClient, http, parseEther } from 'viem';
119
+ import { privateKeyToAccount } from 'viem/accounts';
120
+ import { base } from 'viem/chains';
121
+
122
+ const account = privateKeyToAccount(process.env.PRIVATE_KEY);
123
+ const publicClient = createPublicClient({ chain: base, transport: http() });
124
+ const wallet = createWalletClient({ account, chain: base, transport: http() });
125
+
126
+ // the pool's tokens, decimals, fee and tick spacing
127
+ const { pool } = await new LPSignal().pool('base', '0x6c561b446416e1a00e8e93e221854d6ea4171372');
128
+ // ±5% around the current price, 1 WETH in, paid in ETH; the USDC side is computed
129
+ const plan = await planAddLiquidity(publicClient, pool, { owner: account.address, amount0: parseEther('1'), rangeBp: 500, nativeSide: 0 });
130
+ await sendPlan(wallet, publicClient, [...plan.approvals, plan.mint]); // exact approvals, then the mint
131
+
132
+ // later: your positions, then take half of one out (principal + fees, to you)
133
+ const [pos] = (await positions(publicClient, 'base', account.address)).filter((p) => p.pool === pool.address);
134
+ const half = await planRemoveLiquidity(publicClient, pos, { owner: account.address, shareBps: 5000 });
135
+ // finalized: wait until that block is final before taking more from the same position (a reorg could otherwise drop
136
+ // this removal while the next one reads the old liquidity, and both land)
137
+ const [{ blockNumber }] = await sendPlan(wallet, publicClient, [half.call], { finalized: true });
138
+ // the rest: read at a block no older than that removal
139
+ const rest = await planRemoveLiquidity(publicClient, pos, { owner: account.address, shareBps: 10000, minBlock: blockNumber });
140
+ await sendPlan(wallet, publicClient, [rest.call]);
141
+ ```
142
+
143
+ - Minimum amounts follow the Uniswap SDK's rule for a price move of up to `slippageBps` (default 0.5%); transactions
144
+ expire after `deadlineS` (default 20 minutes).
145
+ - Every planned call is bound to its chain and owner: `sendPlan` refuses to send it from another account or chain.
146
+ - `sendPlan` waits for each receipt. If one is not seen in time it throws `TxPending` with the hash: **do not send the
147
+ same mint or partial removal again until you know what became of it** — a second one would also go through. A
148
+ transaction cancelled or replaced in the wallet throws `TxReplaced` and stops the plan (a speed-up is fine). A send
149
+ that fails without a hash throws `TxUnknown` with the account and the nonce it was sent with: check whether that nonce
150
+ was used first (with a nonce manager or a wallet that picks nonces itself, check its history). Plans of one account
151
+ are sent one after another within a process; do not send from the same account elsewhere at the same time. After a
152
+ `TxUnknown`, `TxPending` or `TxReplaced`, every further send from that account on that chain throws `AccountBlocked` until you have
153
+ checked the transaction and call `unblock(chainId, account)`.
154
+ - Minimum amounts are what the position manager would take at the edges of the `slippage` band. With a range narrower
155
+ than that band (e.g. ±0.05% on a stable pair at 0.5% slippage) both minimums can be 0: the mint then has no on-chain
156
+ price bound, but a price pushed outside your range only makes the deposit single-sided (a mint never trades), and
157
+ coming back it converts at prices inside your range — the loss is bounded by the range's width.
158
+ - Positions staked in an Aerodrome / Velodrome gauge belong to the gauge and are not listed by `positions`.
159
+ - Not financial advice: a range that paid well can lose money if the price leaves it.
160
+
161
+ ## Swapping
162
+
163
+ `planSwap` swaps one of a pool's tokens for the other (e.g. the side you are short of before adding) through the
164
+ [KyberSwap](https://kyberswap.com) aggregator, with **LPSignal's fee: 0.25% of the input (0.05% in stable pools)**,
165
+ sent by the aggregator's router to LPSignal's address. The aggregator's answers are checked, never trusted: the quote
166
+ must be close to the pool's own on-chain price, and the transaction it builds is decoded — the router, the tokens and
167
+ amount, the recipient (your own address), exactly LPSignal's fee and no other, no permit, and a guaranteed minimum out
168
+ no lower than your slippage allows — then simulated. The router pays at least `minReturn` or the swap reverts.
169
+
170
+ ```js
171
+ import { planSwap, sendPlan } from 'lpsignal/liquidity';
172
+ // 0.1 ETH (paid as the native coin) for USDC in the WETH/USDC pool
173
+ const swap = await planSwap(publicClient, pool, { owner: account.address, fromSide: 0, fromNative: true, amountIn: parseEther('0.1') });
174
+ console.log(swap.quoteOut, swap.minReturn, swap.feeBps);
175
+ await sendPlan(wallet, publicClient, [...swap.approvals, swap.swap]); // an exact approval if needed, then the swap
176
+ ```
177
+
178
+ - `minOut`: the least the swap must deliver (e.g. what you are short of); refused (`SwapRefused` `moved`) if the quote
179
+ less the slippage no longer covers it. Other refusals: `impact` (the quote is too far under the pool price),
180
+ `quote` / `calldata` (the aggregator's answer did not match), `simulation` (it would revert now).
181
+ - Send the plan right away (quotes move; it expires after `deadlineS`, default 10 minutes). A swap's deadline sits in
182
+ calldata nobody can check, so after a `TxUnknown` / `TxPending` find out what became of that very transaction before
183
+ swapping again (`sendPlan` blocks the account meanwhile).
184
+ - Uniswap v4 pools are not supported. The aggregator refuses some addresses (e.g. well-known test keys).
185
+
107
186
  ## License
108
187
 
109
188
  MIT
package/README.zh.md CHANGED
@@ -71,6 +71,33 @@ await lps.createRule({ kind: 'depeg', name: 'early depeg', minDeviation: 0.003 }
71
71
  await lps.setSubscriptions(['net_apr', 'burst', 'depeg']); // 推送哪些类型(规则命中总会推送)
72
72
  ```
73
73
 
74
+ ## 添加和移除流动性
75
+
76
+ `lpsignal/liquidity` 负责构造交易:在你选的价格区间内添加流动性,以及移除流动性。交易发到池子的官方仓位合约(Uniswap v3、PancakeSwap v3、Aerodrome 和 Velodrome Slipstream)。签名和发送都用你自己的 [viem](https://viem.sh) 钱包:私钥不经过 SDK;仓位只会创建到你自己的地址,资金也只领回到你自己的地址;中间没有 LPSignal 的合约,也不收费。这里不支持 Uniswap v4 池子(请用 Uniswap 官方应用)。需要和 SDK 一起安装 viem:`npm i lpsignal viem`。
77
+
78
+ 用法见英文 README 的示例:`planAddLiquidity` → `sendPlan`,`positions` → `planRemoveLiquidity` → `sendPlan`。
79
+
80
+ - 最低成交量按 Uniswap SDK 的规则计算,允许的价格变动由 `slippageBps` 指定(默认 0.5%);交易在 `deadlineS` 之后失效(默认 20 分钟)。
81
+ - `sendPlan` 会等每一笔的回执。如果规定时间内没等到,会抛出带交易哈希的 `TxPending`:**在弄清楚这笔交易的结果之前,不要重发同一笔创建仓位或部分移除的交易**,否则第二笔也会成交。
82
+ - 每笔计划好的交易都绑定了所属的链和账户:从别的账户或别的链发送,`sendPlan` 会拒绝。在钱包里被取消或替换成别的交易时,会抛出 `TxReplaced` 并停止整个计划(只是加速则没关系)。
83
+ - 发送时出错且拿不到交易哈希(节点可能已经收下了这笔交易)会抛出 `TxUnknown`,带上账户和 nonce:请先确认这个 nonce 有没有被用掉,再决定是否重发。
84
+ - 出现 `TxUnknown`、`TxPending` 或 `TxReplaced` 后,这个账户在这条链上的后续发送都会抛出 `AccountBlocked`,直到你核实那笔交易后调用 `unblock(chainId, account)`。
85
+ - 对同一个仓位做下一次部分移除前,给 `sendPlan` 传 `{ finalized: true }`,等那个区块最终确认后再继续,避免链重组导致多取。
86
+ - 对同一个仓位做下一次移除时,把上一次 `sendPlan` 返回的区块号作为 `minBlock` 传入,这样 SDK 只会在不旧于那个区块的数据上计算,不会因为 RPC 节点落后而多取。
87
+ - 最低成交量是价格在滑点范围两端时、仓位合约实际会收取的数量。如果区间比滑点范围还窄(例如稳定币池 ±0.05% 区间配 0.5% 滑点),两个最低值都可能是 0,这时交易在链上没有价格限制。但价格被推出你的区间时,添加只会变成存入单一代币(添加本身不做兑换);价格回来时,转换只发生在你的区间内,所以损失上限是区间的宽度。
88
+ - 质押在 Aerodrome / Velodrome gauge 里的仓位属于 gauge,`positions` 不会列出。
89
+ - 不构成投资建议:过去收益好的区间,价格离开后也可能亏损。
90
+
91
+ ## 兑换
92
+
93
+ `planSwap` 通过 [KyberSwap](https://kyberswap.com) 聚合器,把池子里的一种代币兑换成另一种(例如添加前补足不够的那一边)。**LPSignal 收取输入金额的 0.25%(稳定型池 0.05%)作为手续费**,由聚合器路由合约直接转到 LPSignal 的地址。聚合器的返回不会被直接信任:报价必须接近池子自身的链上价格;它构造的交易会被解码核对,包括路由合约、代币和数量、收款人(你自己的地址)、手续费恰好是 LPSignal 的且没有其他费用、没有 permit、保证的最少到账不低于你的滑点允许值,然后再模拟一次。路由合约保证至少到账 `minReturn`,否则交易回滚。
94
+
95
+ 用法:`planSwap(publicClient, pool, { owner, fromSide, amountIn, fromNative?, toNative?, slippageBps?, minOut? })` → `sendPlan(wallet, publicClient, [...plan.approvals, plan.swap])`。
96
+
97
+ - `minOut`:这次兑换至少要到账的数量(例如你缺的数量);报价扣除滑点后不够时会拒绝(`SwapRefused` `moved`)。其他拒绝原因:`impact`(报价比池子价格低太多)、`quote` / `calldata`(聚合器返回与请求不符)、`simulation`(现在发送会失败)。
98
+ - 计划生成后请立即发送(报价会变;`deadlineS` 后失效,默认 10 分钟)。兑换的截止时间写在无法核对的 calldata 里,所以遇到 `TxUnknown` / `TxPending` 后,请先弄清那笔交易本身的结果再兑换(期间 `sendPlan` 会锁住该账户)。
99
+ - 不支持 Uniswap v4 池子。聚合器会拒绝部分地址(例如公开的测试私钥)。
100
+
74
101
  ## 开发
75
102
 
76
103
  ```bash
@@ -0,0 +1,361 @@
1
+ /**
2
+ * Concentrated-liquidity math and the official position-manager calls (Uniswap v3 / PancakeSwap v3
3
+ * NonfungiblePositionManager, Aerodrome / Velodrome Slipstream) — the same code the lpsignal.app web app runs, kept in
4
+ * step with it. Pure: nothing here signs or sends; recipients are always the caller's own address.
5
+ *
6
+ * Math is the pools' own, in integers: TickMath.getSqrtRatioAtTick, LiquidityAmounts, SqrtPriceMath (rounding as the
7
+ * contracts do), and the Uniswap SDK's slippage rule for the minimum amounts.
8
+ */
9
+ import { type Address, type Hex } from 'viem';
10
+ export type LpDex = 'uniswap_v3' | 'pancake_v3' | 'aerodrome_cl' | 'velodrome_cl';
11
+ /** position managers per chain and dex — the ones whose factory created the pools we track (src/chains.ts `npms`) */
12
+ export declare const NPM: Record<string, Partial<Record<LpDex, Address>>>;
13
+ export declare const CHAIN_ID: Record<string, number>;
14
+ /** the wrapped native coin the position managers wrap msg.value into (their WETH9()) */
15
+ export declare const WRAPPED: Record<string, Address>;
16
+ export declare const EXPLORER: Record<string, string>;
17
+ export declare const NATIVE_SYMBOL: Record<string, string>;
18
+ /** USDT on Ethereum refuses to change a non-zero allowance to another non-zero one: reset it to 0 first */
19
+ export declare const ZERO_FIRST: Record<string, Address[]>;
20
+ export declare const isSlipstream: (dex: string) => boolean;
21
+ export declare function npmOf(chain: string, dex: string): Address | null;
22
+ export declare const MIN_TICK = -887272;
23
+ export declare const MAX_TICK = 887272;
24
+ export declare const MIN_SQRT_RATIO = 4295128739n;
25
+ export declare const MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342n;
26
+ export declare function sqrtRatioAtTick(tick: number): bigint;
27
+ /** the full range: the extreme ticks rounded inward to the spacing (the widest legal position) */
28
+ export declare function fullRange(tickSpacing: number): [number, number];
29
+ /** ±rangeBp of price around `tick`, widened outward to the tick grid, always containing the price (0 = full range) — as the backtests */
30
+ export declare function rangeTicks(tick: number, rangeBp: number, tickSpacing: number): [number, number];
31
+ /** the liquidity the position manager mints for these desired amounts (LiquidityAmounts.getLiquidityForAmounts) */
32
+ export declare function liquidityForAmounts(sp: bigint, sa: bigint, sb: bigint, amount0: bigint, amount1: bigint): bigint;
33
+ /** the token amounts liquidity `l` takes at price `sp` (rounded up = what a mint pulls; down = what it is worth) */
34
+ export declare function amountsForLiquidity(sp: bigint, sa: bigint, sb: bigint, l: bigint, up: boolean): [bigint, bigint];
35
+ /**
36
+ * Given one side's amount, the other side the range needs at the current price (0 when the range takes only one
37
+ * token at this price; null when the given side is not used at all there).
38
+ */
39
+ export declare function otherAmount(side: 0 | 1, amount: bigint, sp: bigint, tickLower: number, tickUpper: number): bigint | null;
40
+ /** which tokens a range takes at this price */
41
+ export declare function sidesUsed(sp: bigint, tickLower: number, tickUpper: number): {
42
+ token0: boolean;
43
+ token1: boolean;
44
+ };
45
+ export interface MintAmounts {
46
+ liquidity: bigint;
47
+ amount0Desired: bigint;
48
+ amount1Desired: bigint;
49
+ amount0Min: bigint;
50
+ amount1Min: bigint;
51
+ }
52
+ /**
53
+ * The amounts to send for these desired amounts, and the minimums under a price move of up to `slippageBps` before the
54
+ * transaction lands. The position manager re-derives the liquidity from the desired amounts AT THE PRICE IT MEETS, so
55
+ * the minimum of each token is what it would actually take at the worse end of the band — token0 at the upper price,
56
+ * token1 at the lower (each is monotonic in the price) — never the liquidity fixed now valued there (that is higher
57
+ * than what a moved price takes, and would revert a move well inside the tolerance).
58
+ */
59
+ export declare function mintAmounts(sp: bigint, tickLower: number, tickUpper: number, desired0: bigint, desired1: bigint, slippageBps: number): MintAmounts;
60
+ export declare const ERC20: readonly [{
61
+ readonly type: "function";
62
+ readonly name: "approve";
63
+ readonly stateMutability: "nonpayable";
64
+ readonly inputs: readonly [{
65
+ readonly name: "spender";
66
+ readonly type: "address";
67
+ }, {
68
+ readonly name: "amount";
69
+ readonly type: "uint256";
70
+ }];
71
+ readonly outputs: readonly [{
72
+ readonly type: "bool";
73
+ }];
74
+ }, {
75
+ readonly type: "function";
76
+ readonly name: "allowance";
77
+ readonly stateMutability: "view";
78
+ readonly inputs: readonly [{
79
+ readonly name: "owner";
80
+ readonly type: "address";
81
+ }, {
82
+ readonly name: "spender";
83
+ readonly type: "address";
84
+ }];
85
+ readonly outputs: readonly [{
86
+ readonly type: "uint256";
87
+ }];
88
+ }, {
89
+ readonly type: "function";
90
+ readonly name: "balanceOf";
91
+ readonly stateMutability: "view";
92
+ readonly inputs: readonly [{
93
+ readonly name: "owner";
94
+ readonly type: "address";
95
+ }];
96
+ readonly outputs: readonly [{
97
+ readonly type: "uint256";
98
+ }];
99
+ }];
100
+ /** the first two fields of slot0 — the same on Uniswap v3, PancakeSwap v3 and Slipstream (the rest differ) */
101
+ export declare const SLOT0: readonly [{
102
+ readonly type: "function";
103
+ readonly name: "slot0";
104
+ readonly stateMutability: "view";
105
+ readonly inputs: readonly [];
106
+ readonly outputs: readonly [{
107
+ readonly name: "sqrtPriceX96";
108
+ readonly type: "uint160";
109
+ }, {
110
+ readonly name: "tick";
111
+ readonly type: "int24";
112
+ }];
113
+ }];
114
+ export interface Call {
115
+ to: Address;
116
+ data: Hex;
117
+ value: bigint;
118
+ }
119
+ export interface MintInput {
120
+ chain: string;
121
+ dex: string;
122
+ token0: Address;
123
+ token1: Address;
124
+ fee: number;
125
+ tickSpacing: number;
126
+ tickLower: number;
127
+ tickUpper: number;
128
+ amounts: MintAmounts;
129
+ recipient: Address;
130
+ deadline: bigint;
131
+ /** pay this side in the native coin (it must be the wrapped native token): sent as value, the change refunded */
132
+ nativeSide: 0 | 1 | null;
133
+ }
134
+ /** the mint transaction (with the native coin: multicall(mint, refundETH) carrying the value) */
135
+ export declare function mintCall(m: MintInput): Call;
136
+ export declare function approveCall(token: Address, spender: Address, amount: bigint): Call;
137
+ /** the approvals a mint needs, given the current allowances (exact amounts, never unlimited) */
138
+ export declare function approvalsNeeded(chain: string, npm: Address, needs: {
139
+ token: Address;
140
+ amount: bigint;
141
+ allowance: bigint;
142
+ }[]): Call[];
143
+ /** token units ⇄ text, exact (no floats) */
144
+ export declare function parseUnits(text: string, decimals: number): bigint | null;
145
+ export declare function formatUnits(v: bigint, decimals: number, maxFrac?: number): string;
146
+ /** human price of token0 in token1 at a tick */
147
+ export declare const priceAtTick: (tick: number, decimals0: number, decimals1: number) => number;
148
+ export interface LpPool {
149
+ chain: string;
150
+ dex: string;
151
+ address: Address;
152
+ token0: Address;
153
+ token1: Address;
154
+ decimals0: number;
155
+ decimals1: number;
156
+ fee: number;
157
+ tickSpacing: number;
158
+ }
159
+ export interface MintPlan {
160
+ tickLower: number;
161
+ tickUpper: number;
162
+ amounts: MintAmounts;
163
+ approvals: Call[];
164
+ mint: Call;
165
+ }
166
+ /**
167
+ * Everything a mint needs, from the pool's live price and what the user wants to put in: the range (±rangeBp around
168
+ * the price, on the grid), the amounts and minimums, the approvals still missing (for the amounts the user entered,
169
+ * so a re-plan at a fresh price before the mint never needs another one), and the mint itself.
170
+ */
171
+ export declare function planMint(pool: LpPool, slot0: {
172
+ sqrtPriceX96: bigint;
173
+ tick: number;
174
+ }, input: {
175
+ rangeBp: number;
176
+ desired0: bigint;
177
+ desired1: bigint;
178
+ slippageBps: number;
179
+ nativeSide: 0 | 1 | null;
180
+ recipient: Address;
181
+ deadline: bigint;
182
+ allowance0: bigint;
183
+ allowance1: bigint;
184
+ }): MintPlan;
185
+ /** the position-manager calls for reading and removing a position (the same on Uniswap v3, PancakeSwap v3, Slipstream) */
186
+ export declare const NPM_READ: readonly [{
187
+ readonly type: "function";
188
+ readonly name: "balanceOf";
189
+ readonly stateMutability: "view";
190
+ readonly inputs: readonly [{
191
+ readonly name: "owner";
192
+ readonly type: "address";
193
+ }];
194
+ readonly outputs: readonly [{
195
+ readonly type: "uint256";
196
+ }];
197
+ }, {
198
+ readonly type: "function";
199
+ readonly name: "tokenOfOwnerByIndex";
200
+ readonly stateMutability: "view";
201
+ readonly inputs: readonly [{
202
+ readonly name: "owner";
203
+ readonly type: "address";
204
+ }, {
205
+ readonly name: "index";
206
+ readonly type: "uint256";
207
+ }];
208
+ readonly outputs: readonly [{
209
+ readonly type: "uint256";
210
+ }];
211
+ }, {
212
+ readonly type: "function";
213
+ readonly name: "positions";
214
+ readonly stateMutability: "view";
215
+ readonly inputs: readonly [{
216
+ readonly name: "tokenId";
217
+ readonly type: "uint256";
218
+ }];
219
+ readonly outputs: readonly [{
220
+ readonly name: "nonce";
221
+ readonly type: "uint96";
222
+ }, {
223
+ readonly name: "operator";
224
+ readonly type: "address";
225
+ }, {
226
+ readonly name: "token0";
227
+ readonly type: "address";
228
+ }, {
229
+ readonly name: "token1";
230
+ readonly type: "address";
231
+ }, {
232
+ readonly name: "feeOrSpacing";
233
+ readonly type: "int24";
234
+ }, {
235
+ readonly name: "tickLower";
236
+ readonly type: "int24";
237
+ }, {
238
+ readonly name: "tickUpper";
239
+ readonly type: "int24";
240
+ }, {
241
+ readonly name: "liquidity";
242
+ readonly type: "uint128";
243
+ }, {
244
+ readonly name: "feeGrowthInside0LastX128";
245
+ readonly type: "uint256";
246
+ }, {
247
+ readonly name: "feeGrowthInside1LastX128";
248
+ readonly type: "uint256";
249
+ }, {
250
+ readonly name: "tokensOwed0";
251
+ readonly type: "uint128";
252
+ }, {
253
+ readonly name: "tokensOwed1";
254
+ readonly type: "uint128";
255
+ }];
256
+ }];
257
+ export declare const COLLECT_ABI: readonly [{
258
+ readonly type: "function";
259
+ readonly name: "decreaseLiquidity";
260
+ readonly stateMutability: "payable";
261
+ readonly inputs: readonly [{
262
+ readonly name: "params";
263
+ readonly type: "tuple";
264
+ readonly components: readonly [{
265
+ readonly name: "tokenId";
266
+ readonly type: "uint256";
267
+ }, {
268
+ readonly name: "liquidity";
269
+ readonly type: "uint128";
270
+ }, {
271
+ readonly name: "amount0Min";
272
+ readonly type: "uint256";
273
+ }, {
274
+ readonly name: "amount1Min";
275
+ readonly type: "uint256";
276
+ }, {
277
+ readonly name: "deadline";
278
+ readonly type: "uint256";
279
+ }];
280
+ }];
281
+ readonly outputs: readonly [{
282
+ readonly name: "amount0";
283
+ readonly type: "uint256";
284
+ }, {
285
+ readonly name: "amount1";
286
+ readonly type: "uint256";
287
+ }];
288
+ }, {
289
+ readonly type: "function";
290
+ readonly name: "collect";
291
+ readonly stateMutability: "payable";
292
+ readonly inputs: readonly [{
293
+ readonly name: "params";
294
+ readonly type: "tuple";
295
+ readonly components: readonly [{
296
+ readonly name: "tokenId";
297
+ readonly type: "uint256";
298
+ }, {
299
+ readonly name: "recipient";
300
+ readonly type: "address";
301
+ }, {
302
+ readonly name: "amount0Max";
303
+ readonly type: "uint128";
304
+ }, {
305
+ readonly name: "amount1Max";
306
+ readonly type: "uint128";
307
+ }];
308
+ }];
309
+ readonly outputs: readonly [{
310
+ readonly name: "amount0";
311
+ readonly type: "uint256";
312
+ }, {
313
+ readonly name: "amount1";
314
+ readonly type: "uint256";
315
+ }];
316
+ }, {
317
+ readonly type: "function";
318
+ readonly name: "multicall";
319
+ readonly stateMutability: "payable";
320
+ readonly inputs: readonly [{
321
+ readonly name: "data";
322
+ readonly type: "bytes[]";
323
+ }];
324
+ readonly outputs: readonly [{
325
+ readonly name: "results";
326
+ readonly type: "bytes[]";
327
+ }];
328
+ }];
329
+ export interface Position {
330
+ tokenId: bigint;
331
+ token0: Address;
332
+ token1: Address;
333
+ feeOrSpacing: number;
334
+ tickLower: number;
335
+ tickUpper: number;
336
+ liquidity: bigint;
337
+ }
338
+ /** whether a position belongs to this pool (same tokens and fee — Slipstream: tick spacing) */
339
+ export declare function inPool(pos: Position, pool: LpPool): boolean;
340
+ /**
341
+ * Taking `share` (basis points of the position, 1..10000) out: the liquidity, what it is worth now, and the minimums
342
+ * under a price move of up to `slippageBps` (each token at its worse price, as the Uniswap SDK's burn rule).
343
+ */
344
+ export declare function removeAmounts(sp: bigint, pos: Position, shareBps: number, slippageBps: number): {
345
+ liquidity: bigint;
346
+ amount0: bigint;
347
+ amount1: bigint;
348
+ amount0Min: bigint;
349
+ amount1Min: bigint;
350
+ };
351
+ /** collect everything owed (fees + what was decreased) to the wallet — the same call simulated shows the uncollected fees */
352
+ export declare function collectCall(chain: string, dex: string, tokenId: bigint, recipient: Address): Call;
353
+ /**
354
+ * One transaction: decrease the liquidity (with minimums and a deadline), then collect it all — principal and fees —
355
+ * to the wallet itself. `liquidity` 0 = fees only (collect).
356
+ */
357
+ export declare function removeCall(chain: string, dex: string, tokenId: bigint, amounts: {
358
+ liquidity: bigint;
359
+ amount0Min: bigint;
360
+ amount1Min: bigint;
361
+ }, recipient: Address, deadline: bigint): Call;