uni-exec-engine 0.2.0__py3-none-any.whl

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.
Files changed (80) hide show
  1. uni_exec_engine-0.2.0.dist-info/METADATA +576 -0
  2. uni_exec_engine-0.2.0.dist-info/RECORD +80 -0
  3. uni_exec_engine-0.2.0.dist-info/WHEEL +5 -0
  4. uni_exec_engine-0.2.0.dist-info/licenses/LICENSE +21 -0
  5. uni_exec_engine-0.2.0.dist-info/top_level.txt +1 -0
  6. uniswap_autopilot/__init__.py +3 -0
  7. uniswap_autopilot/analytics/__init__.py +0 -0
  8. uniswap_autopilot/analytics/il_calculator.py +525 -0
  9. uniswap_autopilot/analytics/portfolio.py +234 -0
  10. uniswap_autopilot/analytics/position.py +433 -0
  11. uniswap_autopilot/analytics/range_suggest.py +270 -0
  12. uniswap_autopilot/audit.py +194 -0
  13. uniswap_autopilot/common/__init__.py +0 -0
  14. uniswap_autopilot/common/approval_cleanup.py +255 -0
  15. uniswap_autopilot/common/check_balance.py +64 -0
  16. uniswap_autopilot/common/common.py +804 -0
  17. uniswap_autopilot/common/deep_link.py +132 -0
  18. uniswap_autopilot/common/gas.py +141 -0
  19. uniswap_autopilot/data/auto_trade_policy.example.json +29 -0
  20. uniswap_autopilot/data/chains.json +135 -0
  21. uniswap_autopilot/data/common-token-addresses.json +777 -0
  22. uniswap_autopilot/execute/__init__.py +0 -0
  23. uniswap_autopilot/execute/_internal/__init__.py +5 -0
  24. uniswap_autopilot/execute/_internal/constants.py +15 -0
  25. uniswap_autopilot/execute/_internal/preflight.py +150 -0
  26. uniswap_autopilot/execute/_internal/pure_signer.py +182 -0
  27. uniswap_autopilot/execute/_internal/rpc.py +462 -0
  28. uniswap_autopilot/execute/_internal/signer.py +298 -0
  29. uniswap_autopilot/execute/_internal/submit.py +73 -0
  30. uniswap_autopilot/execute/_internal/tx.py +370 -0
  31. uniswap_autopilot/execute/broadcast.py +380 -0
  32. uniswap_autopilot/execute/detect.py +52 -0
  33. uniswap_autopilot/execute/telegram_confirm.py +272 -0
  34. uniswap_autopilot/lp/compare_pools.py +338 -0
  35. uniswap_autopilot/lp/v2/__init__.py +0 -0
  36. uniswap_autopilot/lp/v2/approve.py +100 -0
  37. uniswap_autopilot/lp/v2/build_tx.py +226 -0
  38. uniswap_autopilot/lp/v2/flow.py +204 -0
  39. uniswap_autopilot/lp/v2/pair.py +135 -0
  40. uniswap_autopilot/lp/v2/positions.py +177 -0
  41. uniswap_autopilot/lp/v3/__init__.py +0 -0
  42. uniswap_autopilot/lp/v3/approve.py +109 -0
  43. uniswap_autopilot/lp/v3/auto_rebalance.py +282 -0
  44. uniswap_autopilot/lp/v3/build_tx.py +465 -0
  45. uniswap_autopilot/lp/v3/compound.py +258 -0
  46. uniswap_autopilot/lp/v3/flow.py +358 -0
  47. uniswap_autopilot/lp/v3/pool.py +175 -0
  48. uniswap_autopilot/lp/v3/position.py +112 -0
  49. uniswap_autopilot/lp/v3/tick.py +75 -0
  50. uniswap_autopilot/lp/v4/__init__.py +0 -0
  51. uniswap_autopilot/lp/v4/approve.py +105 -0
  52. uniswap_autopilot/lp/v4/build_tx.py +669 -0
  53. uniswap_autopilot/lp/v4/flow.py +368 -0
  54. uniswap_autopilot/lp/v4/pool.py +174 -0
  55. uniswap_autopilot/lp/v4/position.py +185 -0
  56. uniswap_autopilot/policy.py +371 -0
  57. uniswap_autopilot/price_feed.py +100 -0
  58. uniswap_autopilot/py.typed +0 -0
  59. uniswap_autopilot/search/__init__.py +0 -0
  60. uniswap_autopilot/search/risk.py +200 -0
  61. uniswap_autopilot/search/search.py +580 -0
  62. uniswap_autopilot/state_machine.py +315 -0
  63. uniswap_autopilot/swap/__init__.py +1 -0
  64. uniswap_autopilot/swap/deep_link.py +69 -0
  65. uniswap_autopilot/swap/extensions/__init__.py +2 -0
  66. uniswap_autopilot/swap/extensions/bridge.py +197 -0
  67. uniswap_autopilot/swap/extensions/limit_order.py +272 -0
  68. uniswap_autopilot/swap/extensions/slippage.py +123 -0
  69. uniswap_autopilot/swap/flow.py +693 -0
  70. uniswap_autopilot/swap/flow_core/__init__.py +2 -0
  71. uniswap_autopilot/swap/flow_core/artifacts.py +11 -0
  72. uniswap_autopilot/swap/flow_core/broadcast.py +50 -0
  73. uniswap_autopilot/swap/flow_core/diagnostics.py +216 -0
  74. uniswap_autopilot/swap/flow_core/paper.py +113 -0
  75. uniswap_autopilot/swap/flow_core/policy.py +142 -0
  76. uniswap_autopilot/swap/links/__init__.py +2 -0
  77. uniswap_autopilot/swap/links/deep_link.py +69 -0
  78. uniswap_autopilot/swap/trading_api/permit.py +41 -0
  79. uniswap_autopilot/swap/trading_api/quote.py +282 -0
  80. uniswap_autopilot/swap/trading_api/swap.py +248 -0
@@ -0,0 +1,465 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ import os
7
+ import sys
8
+ import time
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ from uniswap_autopilot.common.common import (
14
+ decimal_to_base_units,
15
+ dump_json,
16
+ get_position_manager_address,
17
+ get_v3_factory_address,
18
+ load_local_env,
19
+ normalize_chain,
20
+ parse_amount,
21
+ resolve_token,
22
+ resolve_wallet_address,
23
+ sort_token_addresses,
24
+ validate_fee_tier,
25
+ )
26
+ from uniswap_autopilot.execute._internal.rpc import build_calldata, encode_address, encode_int, encode_uint
27
+ from uniswap_autopilot.lp.v3.pool import query_pool_address, query_pool_full_info, query_slot0
28
+ from uniswap_autopilot.lp.v3.position import query_position
29
+ from uniswap_autopilot.analytics.position import calculate_position_amounts
30
+
31
+
32
+ def _encode_mint_calldata(
33
+ token0: str, token1: str, fee: int,
34
+ tick_lower: int, tick_upper: int,
35
+ amount0_desired: str, amount1_desired: str,
36
+ amount0_min: str, amount1_min: str,
37
+ recipient: str, deadline: str,
38
+ ) -> str:
39
+ return build_calldata(
40
+ "mint((address,address,uint24,int24,int24,uint256,uint256,uint256,uint256,address,uint256))",
41
+ encode_address(token0),
42
+ encode_address(token1),
43
+ encode_uint(fee),
44
+ encode_int(tick_lower),
45
+ encode_int(tick_upper),
46
+ encode_uint(int(amount0_desired)),
47
+ encode_uint(int(amount1_desired)),
48
+ encode_uint(int(amount0_min)),
49
+ encode_uint(int(amount1_min)),
50
+ encode_address(recipient),
51
+ encode_uint(int(deadline)),
52
+ )
53
+
54
+
55
+ def _encode_increase_liquidity_calldata(
56
+ token_id: str,
57
+ amount0_desired: str, amount1_desired: str,
58
+ amount0_min: str, amount1_min: str,
59
+ deadline: str,
60
+ ) -> str:
61
+ return build_calldata(
62
+ "increaseLiquidity((uint256,uint256,uint256,uint256,uint256))",
63
+ encode_uint(int(token_id)),
64
+ encode_uint(int(amount0_desired)),
65
+ encode_uint(int(amount1_desired)),
66
+ encode_uint(int(amount0_min)),
67
+ encode_uint(int(amount1_min)),
68
+ encode_uint(int(deadline)),
69
+ )
70
+
71
+
72
+ def _encode_decrease_liquidity_calldata(
73
+ token_id: str, liquidity: str,
74
+ amount0_min: str, amount1_min: str,
75
+ deadline: str,
76
+ ) -> str:
77
+ return build_calldata(
78
+ "decreaseLiquidity((uint256,uint128,uint256,uint256,uint256))",
79
+ encode_uint(int(token_id)),
80
+ encode_uint(int(liquidity)),
81
+ encode_uint(int(amount0_min)),
82
+ encode_uint(int(amount1_min)),
83
+ encode_uint(int(deadline)),
84
+ )
85
+
86
+
87
+ def _encode_collect_calldata(
88
+ token_id: str, recipient: str,
89
+ amount0_max: str, amount1_max: str,
90
+ ) -> str:
91
+ return build_calldata(
92
+ "collect((uint256,address,uint128,uint128))",
93
+ encode_uint(int(token_id)),
94
+ encode_address(recipient),
95
+ encode_uint(int(amount0_max)),
96
+ encode_uint(int(amount1_max)),
97
+ )
98
+
99
+
100
+ def _apply_slippage(amount_str: str, slippage_pct: float) -> str:
101
+ from decimal import Decimal, ROUND_DOWN
102
+ amount = Decimal(amount_str)
103
+ factor = Decimal("1") - Decimal(str(slippage_pct)) / Decimal("100")
104
+ result = (amount * factor).to_integral_value(rounding=ROUND_DOWN)
105
+ return str(result)
106
+
107
+
108
+ def build_mint_transaction(
109
+ chain_name: str,
110
+ token_a: str,
111
+ token_b: str,
112
+ fee_tier: int,
113
+ tick_lower: int,
114
+ tick_upper: int,
115
+ amount_a: str,
116
+ amount_b: str,
117
+ slippage_pct: float = 0.5,
118
+ recipient: str | None = None,
119
+ deadline_seconds: int = 600,
120
+ rpc_url: str | None = None,
121
+ request_only: bool = False,
122
+ ) -> dict[str, Any]:
123
+ chain = normalize_chain(chain_name)
124
+ fee = validate_fee_tier(fee_tier)
125
+ pm = get_position_manager_address(chain_name)
126
+
127
+ if tick_lower >= tick_upper:
128
+ raise ValueError(f"tick_lower ({tick_lower}) must be less than tick_upper ({tick_upper})")
129
+
130
+ token_a_info = resolve_token(chain, token_a, rpc_url)
131
+ token_b_info = resolve_token(chain, token_b, rpc_url)
132
+ addr_a = token_a_info["address"]
133
+ addr_b = token_b_info["address"]
134
+ if addr_a == "NATIVE" or addr_b == "NATIVE":
135
+ raise ValueError("LP does not support NATIVE; use wrapped token (e.g. WETH)")
136
+
137
+ token0_addr, token1_addr = sort_token_addresses(addr_a, addr_b)
138
+ decimals0 = token_a_info["decimals"] if token0_addr == addr_a else token_b_info["decimals"]
139
+ decimals1 = token_b_info["decimals"] if token1_addr == addr_b else token_a_info["decimals"]
140
+
141
+ amount0_human = amount_a if token0_addr == addr_a else amount_b
142
+ amount1_human = amount_b if token1_addr == addr_b else amount_a
143
+
144
+ base0 = decimal_to_base_units(parse_amount(amount0_human), decimals0)
145
+ base1 = decimal_to_base_units(parse_amount(amount1_human), decimals1)
146
+ min0 = _apply_slippage(base0, slippage_pct)
147
+ min1 = _apply_slippage(base1, slippage_pct)
148
+
149
+ wallet = recipient or os.environ.get("SECURE_WALLET_ADDRESS") or os.environ.get("HOT_WALLET_ADDRESS")
150
+ if not wallet:
151
+ raise ValueError("recipient is required; pass --recipient or set wallet env")
152
+
153
+ deadline = str(int(time.time()) + deadline_seconds)
154
+ calldata = _encode_mint_calldata(
155
+ token0_addr, token1_addr, fee,
156
+ tick_lower, tick_upper,
157
+ base0, base1, min0, min1,
158
+ wallet, deadline,
159
+ )
160
+
161
+ result: dict[str, Any] = {
162
+ "action": "lp_mint",
163
+ "chain": {"key": chain.key, "chainId": chain.chain_id},
164
+ "feeTier": fee,
165
+ "tickLower": tick_lower,
166
+ "tickUpper": tick_upper,
167
+ "token0": {"address": token0_addr, "decimals": decimals0, "amountDesired": base0, "amountMin": min0},
168
+ "token1": {"address": token1_addr, "decimals": decimals1, "amountDesired": base1, "amountMin": min1},
169
+ "recipient": wallet,
170
+ "deadline": deadline,
171
+ "transaction": {
172
+ "kind": "lp_mint",
173
+ "to": pm,
174
+ "data": calldata,
175
+ "value": "0",
176
+ "chainId": chain.chain_id,
177
+ "from": wallet,
178
+ },
179
+ }
180
+
181
+ if not request_only and rpc_url:
182
+ try:
183
+ pool_info = query_pool_full_info(chain_name, token_a, token_b, fee_tier, rpc_url)
184
+ result["pool"] = pool_info
185
+ except Exception as exc:
186
+ result["poolError"] = str(exc)
187
+
188
+ return result
189
+
190
+
191
+ def build_increase_liquidity_transaction(
192
+ chain_name: str,
193
+ token_id: int,
194
+ amount0: str,
195
+ amount1: str,
196
+ slippage_pct: float = 0.5,
197
+ deadline_seconds: int = 600,
198
+ rpc_url: str | None = None,
199
+ wallet: str | None = None,
200
+ ) -> dict[str, Any]:
201
+ chain = normalize_chain(chain_name)
202
+ pm = get_position_manager_address(chain_name)
203
+ rpc_url_resolved = rpc_url
204
+ if not rpc_url_resolved:
205
+ from uniswap_autopilot.execute._internal.rpc import resolve_rpc_url
206
+ r, _ = resolve_rpc_url(None, chain.chain_id)
207
+ rpc_url_resolved = r
208
+ if not rpc_url_resolved:
209
+ raise RuntimeError(f"RPC URL not configured for {chain_name}; set an RPC env var or pass --rpc-url")
210
+
211
+ pos = query_position(token_id, chain_name, rpc_url_resolved)
212
+ if pos["liquidity"] == "0":
213
+ raise ValueError(f"position {token_id} has no liquidity; use mint instead")
214
+
215
+ owner = resolve_wallet_address(wallet) or os.environ.get("SECURE_WALLET_ADDRESS") or os.environ.get("HOT_WALLET_ADDRESS")
216
+ if not owner:
217
+ raise ValueError("wallet is required; pass --wallet or set wallet env")
218
+
219
+ token0_info = resolve_token(chain, pos["token0"])
220
+ token1_info = resolve_token(chain, pos["token1"])
221
+ base0 = decimal_to_base_units(parse_amount(amount0), token0_info["decimals"])
222
+ base1 = decimal_to_base_units(parse_amount(amount1), token1_info["decimals"])
223
+ min0 = _apply_slippage(base0, slippage_pct)
224
+ min1 = _apply_slippage(base1, slippage_pct)
225
+
226
+ deadline = str(int(time.time()) + deadline_seconds)
227
+ calldata = _encode_increase_liquidity_calldata(
228
+ str(token_id), base0, base1, min0, min1, deadline,
229
+ )
230
+
231
+ return {
232
+ "action": "lp_increase",
233
+ "chain": {"key": chain.key, "chainId": chain.chain_id},
234
+ "tokenId": token_id,
235
+ "position": pos,
236
+ "owner": owner,
237
+ "token0": {"address": pos["token0"], "decimals": token0_info["decimals"], "amountDesired": base0, "amountMin": min0},
238
+ "token1": {"address": pos["token1"], "decimals": token1_info["decimals"], "amountDesired": base1, "amountMin": min1},
239
+ "deadline": deadline,
240
+ "transaction": {
241
+ "kind": "lp_increase",
242
+ "to": pm,
243
+ "data": calldata,
244
+ "value": "0",
245
+ "chainId": chain.chain_id,
246
+ "from": owner,
247
+ },
248
+ }
249
+
250
+
251
+ def build_decrease_liquidity_transaction(
252
+ chain_name: str,
253
+ token_id: int,
254
+ liquidity_pct: float,
255
+ slippage_pct: float = 0.5,
256
+ deadline_seconds: int = 600,
257
+ rpc_url: str | None = None,
258
+ wallet: str | None = None,
259
+ ) -> dict[str, Any]:
260
+ chain = normalize_chain(chain_name)
261
+ pm = get_position_manager_address(chain_name)
262
+ rpc_url_resolved = rpc_url
263
+ if not rpc_url_resolved:
264
+ from uniswap_autopilot.execute._internal.rpc import resolve_rpc_url
265
+ r, _ = resolve_rpc_url(None, chain.chain_id)
266
+ rpc_url_resolved = r
267
+ if not rpc_url_resolved:
268
+ raise RuntimeError(f"RPC URL not configured for {chain_name}; set an RPC env var or pass --rpc-url")
269
+
270
+ pos = query_position(token_id, chain_name, rpc_url_resolved)
271
+ current_liq = int(pos["liquidity"])
272
+ if current_liq == 0:
273
+ raise ValueError(f"position {token_id} has no liquidity to remove")
274
+
275
+ owner = resolve_wallet_address(wallet) or os.environ.get("SECURE_WALLET_ADDRESS") or os.environ.get("HOT_WALLET_ADDRESS")
276
+ if not owner:
277
+ raise ValueError("wallet is required; pass --wallet or set wallet env")
278
+
279
+ if liquidity_pct <= 0 or liquidity_pct > 100:
280
+ raise ValueError("liquidity_pct must be between 0 and 100")
281
+ remove_liq = int(current_liq * liquidity_pct / 100)
282
+ if remove_liq == 0:
283
+ raise ValueError(
284
+ f"liquidity_pct {liquidity_pct}% of {current_liq} rounds to 0; increase percentage or remove all"
285
+ )
286
+
287
+ token0_info = resolve_token(chain, pos["token0"])
288
+ token1_info = resolve_token(chain, pos["token1"])
289
+ min0 = "0"
290
+ min1 = "0"
291
+ if slippage_pct > 0:
292
+ from decimal import Decimal
293
+ try:
294
+ factory = get_v3_factory_address(chain_name)
295
+ pool_addr = query_pool_address(pos["token0"], pos["token1"], int(pos["fee"]), factory, rpc_url_resolved)
296
+ pool_slot = query_slot0(pool_addr, rpc_url_resolved)
297
+ current_tick = pool_slot["tick"]
298
+ amount0_human, amount1_human = calculate_position_amounts(
299
+ liquidity=int(pos["liquidity"]),
300
+ current_tick=current_tick,
301
+ tick_lower=int(pos["tickLower"]),
302
+ tick_upper=int(pos["tickUpper"]),
303
+ decimals0=token0_info["decimals"],
304
+ decimals1=token1_info["decimals"],
305
+ )
306
+ frac = Decimal(remove_liq) / Decimal(current_liq)
307
+ amt0_est = int(Decimal(str(amount0_human)) * frac * Decimal(10 ** token0_info["decimals"]))
308
+ amt1_est = int(Decimal(str(amount1_human)) * frac * Decimal(10 ** token1_info["decimals"]))
309
+ except Exception:
310
+ amt0_est = 0
311
+ amt1_est = 0
312
+ min0 = _apply_slippage(str(amt0_est), slippage_pct)
313
+ min1 = _apply_slippage(str(amt1_est), slippage_pct)
314
+
315
+ deadline = str(int(time.time()) + deadline_seconds)
316
+ calldata = _encode_decrease_liquidity_calldata(
317
+ str(token_id), str(remove_liq), min0, min1, deadline,
318
+ )
319
+
320
+ return {
321
+ "action": "lp_decrease",
322
+ "chain": {"key": chain.key, "chainId": chain.chain_id},
323
+ "tokenId": token_id,
324
+ "position": pos,
325
+ "owner": owner,
326
+ "currentLiquidity": str(current_liq),
327
+ "removeLiquidity": str(remove_liq),
328
+ "removePct": liquidity_pct,
329
+ "deadline": deadline,
330
+ "transaction": {
331
+ "kind": "lp_decrease",
332
+ "to": pm,
333
+ "data": calldata,
334
+ "value": "0",
335
+ "chainId": chain.chain_id,
336
+ "from": owner,
337
+ },
338
+ }
339
+
340
+
341
+ def build_collect_transaction(
342
+ chain_name: str,
343
+ token_id: int,
344
+ recipient: str | None = None,
345
+ ) -> dict[str, Any]:
346
+ chain = normalize_chain(chain_name)
347
+ pm = get_position_manager_address(chain_name)
348
+
349
+ wallet = recipient or os.environ.get("SECURE_WALLET_ADDRESS") or os.environ.get("HOT_WALLET_ADDRESS")
350
+ if not wallet:
351
+ raise ValueError("recipient is required; pass --recipient or set wallet env")
352
+
353
+ max_uint128 = str(2**128 - 1)
354
+ calldata = _encode_collect_calldata(str(token_id), wallet, max_uint128, max_uint128)
355
+
356
+ return {
357
+ "action": "lp_collect",
358
+ "chain": {"key": chain.key, "chainId": chain.chain_id},
359
+ "tokenId": token_id,
360
+ "recipient": wallet,
361
+ "transaction": {
362
+ "kind": "lp_collect",
363
+ "to": pm,
364
+ "data": calldata,
365
+ "value": "0",
366
+ "chainId": chain.chain_id,
367
+ "from": wallet,
368
+ },
369
+ }
370
+
371
+
372
+ def main() -> None:
373
+ parser = argparse.ArgumentParser(description="构造 Uniswap v3 LP 交易")
374
+ sub = parser.add_subparsers(dest="command", required=True)
375
+
376
+ # mint
377
+ mint_p = sub.add_parser("mint", help="创建新 LP 仓位")
378
+ mint_p.add_argument("--chain", required=True)
379
+ mint_p.add_argument("--token-a", required=True)
380
+ mint_p.add_argument("--token-b", required=True)
381
+ mint_p.add_argument("--fee-tier", type=int, required=True)
382
+ mint_p.add_argument("--tick-lower", type=int, required=True)
383
+ mint_p.add_argument("--tick-upper", type=int, required=True)
384
+ mint_p.add_argument("--amount-a", required=True, help="tokenA 数量(人类可读)")
385
+ mint_p.add_argument("--amount-b", required=True, help="tokenB 数量(人类可读)")
386
+ mint_p.add_argument("--slippage", type=float, default=0.5)
387
+ mint_p.add_argument("--recipient", help="接收 LP NFT 的地址")
388
+ mint_p.add_argument("--deadline", type=int, default=600)
389
+ mint_p.add_argument("--rpc-url")
390
+ mint_p.add_argument("--request-only", action="store_true")
391
+ mint_p.add_argument("--output")
392
+
393
+ # increase
394
+ inc_p = sub.add_parser("increase", help="增加流动性")
395
+ inc_p.add_argument("--chain", required=True)
396
+ inc_p.add_argument("--token-id", type=int, required=True)
397
+ inc_p.add_argument("--amount0", required=True)
398
+ inc_p.add_argument("--amount1", required=True)
399
+ inc_p.add_argument("--slippage", type=float, default=0.5)
400
+ inc_p.add_argument("--deadline", type=int, default=600)
401
+ inc_p.add_argument("--rpc-url")
402
+ inc_p.add_argument("--output")
403
+
404
+ # decrease
405
+ dec_p = sub.add_parser("decrease", help="减少流动性")
406
+ dec_p.add_argument("--chain", required=True)
407
+ dec_p.add_argument("--token-id", type=int, required=True)
408
+ dec_p.add_argument("--liquidity-pct", type=float, required=True, help="移除百分比 (1-100)")
409
+ dec_p.add_argument("--slippage", type=float, default=0.5)
410
+ dec_p.add_argument("--deadline", type=int, default=600)
411
+ dec_p.add_argument("--rpc-url")
412
+ dec_p.add_argument("--output")
413
+
414
+ # collect
415
+ col_p = sub.add_parser("collect", help="收取手续费")
416
+ col_p.add_argument("--chain", required=True)
417
+ col_p.add_argument("--token-id", type=int, required=True)
418
+ col_p.add_argument("--recipient")
419
+ col_p.add_argument("--output")
420
+
421
+ args = parser.parse_args()
422
+
423
+ try:
424
+ load_local_env()
425
+ if args.command == "mint":
426
+ result = build_mint_transaction(
427
+ chain_name=args.chain, token_a=args.token_a, token_b=args.token_b,
428
+ fee_tier=args.fee_tier, tick_lower=args.tick_lower, tick_upper=args.tick_upper,
429
+ amount_a=args.amount_a, amount_b=args.amount_b,
430
+ slippage_pct=args.slippage, recipient=args.recipient,
431
+ deadline_seconds=args.deadline, rpc_url=args.rpc_url,
432
+ request_only=args.request_only,
433
+ )
434
+ elif args.command == "increase":
435
+ result = build_increase_liquidity_transaction(
436
+ chain_name=args.chain, token_id=args.token_id,
437
+ amount0=args.amount0, amount1=args.amount1,
438
+ slippage_pct=args.slippage, deadline_seconds=args.deadline,
439
+ rpc_url=args.rpc_url,
440
+ )
441
+ elif args.command == "decrease":
442
+ result = build_decrease_liquidity_transaction(
443
+ chain_name=args.chain, token_id=args.token_id,
444
+ liquidity_pct=args.liquidity_pct, slippage_pct=args.slippage,
445
+ deadline_seconds=args.deadline, rpc_url=args.rpc_url,
446
+ )
447
+ elif args.command == "collect":
448
+ result = build_collect_transaction(
449
+ chain_name=args.chain, token_id=args.token_id,
450
+ recipient=args.recipient,
451
+ )
452
+ else:
453
+ parser.print_help()
454
+ sys.exit(1)
455
+
456
+ if args.output:
457
+ Path(args.output).write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
458
+ dump_json(result)
459
+ except Exception as exc:
460
+ print(json.dumps({"error": str(exc)}, ensure_ascii=False, indent=2), file=sys.stderr)
461
+ sys.exit(1)
462
+
463
+
464
+ if __name__ == "__main__":
465
+ main()