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,255 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+
10
+ from uniswap_autopilot.common.common import (
11
+ dump_json,
12
+ get_permit2_address,
13
+ get_position_manager_address,
14
+ get_universal_router_address,
15
+ get_v2_router02_address,
16
+ load_local_env,
17
+ normalize_chain,
18
+ resolve_token,
19
+ resolve_wallet_address,
20
+ )
21
+ from uniswap_autopilot.execute._internal.rpc import (
22
+ build_calldata, encode_address, encode_uint, query_erc20_allowance, resolve_rpc_url,
23
+ )
24
+
25
+ UINT256_MAX = "115792089237316195423570985008687907853269984665640564039457584007913129639935"
26
+
27
+
28
+ def _get_spenders(chain_name: str) -> list[dict[str, str]]:
29
+ spenders: list[dict[str, str]] = []
30
+ try:
31
+ spenders.append({"label": "positionManager", "address": get_position_manager_address(chain_name)})
32
+ except ValueError:
33
+ pass
34
+ try:
35
+ spenders.append({"label": "router02", "address": get_v2_router02_address(chain_name)})
36
+ except ValueError:
37
+ pass
38
+ spenders.append({"label": "permit2", "address": get_permit2_address()})
39
+ ur = get_universal_router_address(chain_name)
40
+ if ur:
41
+ spenders.append({"label": "universalRouter", "address": ur})
42
+ return spenders
43
+
44
+
45
+ def _get_token_list(chain_name: str) -> list[dict[str, Any]]:
46
+ chain = normalize_chain(chain_name)
47
+ tokens: list[dict[str, Any]] = []
48
+ seen: set[str] = set()
49
+ for sym, tok in chain.tokens.items():
50
+ addr = tok.address
51
+ if addr == "NATIVE" or addr.lower() in seen:
52
+ continue
53
+ seen.add(addr.lower())
54
+ tokens.append({"symbol": sym, "address": addr, "decimals": tok.decimals})
55
+ return tokens
56
+
57
+
58
+ def scan_approvals(
59
+ chain_name: str,
60
+ wallet: str,
61
+ rpc_url: str | None = None,
62
+ ) -> dict[str, Any]:
63
+ chain = normalize_chain(chain_name)
64
+ rpc, _ = resolve_rpc_url(rpc_url, chain.chain_id)
65
+ if not rpc:
66
+ raise RuntimeError(f"RPC URL not configured for {chain_name}")
67
+
68
+ spenders = _get_spenders(chain_name)
69
+ tokens = _get_token_list(chain_name)
70
+
71
+ approvals: list[dict[str, Any]] = []
72
+ for tok in tokens:
73
+ for sp in spenders:
74
+ try:
75
+ allowance = query_erc20_allowance(tok["address"], wallet, sp["address"], rpc)
76
+ except Exception:
77
+ continue
78
+ if allowance == 0:
79
+ continue
80
+ approvals.append({
81
+ "token": tok["symbol"],
82
+ "tokenAddress": tok["address"],
83
+ "spenderLabel": sp["label"],
84
+ "spenderAddress": sp["address"],
85
+ "allowance": str(allowance),
86
+ "isMaxApproval": str(allowance) == UINT256_MAX,
87
+ })
88
+
89
+ return {
90
+ "action": "approval_scan",
91
+ "chain": {"key": chain.key, "chainId": chain.chain_id},
92
+ "wallet": wallet,
93
+ "spendersChecked": len(spenders),
94
+ "tokensChecked": len(tokens),
95
+ "nonZeroApprovals": len(approvals),
96
+ "approvals": approvals,
97
+ }
98
+
99
+
100
+ def _build_revoke_tx(
101
+ token_address: str,
102
+ spender_address: str,
103
+ wallet: str,
104
+ chain_id: int,
105
+ ) -> dict[str, Any]:
106
+ calldata = build_calldata(
107
+ "approve(address,uint256)",
108
+ encode_address(spender_address),
109
+ encode_uint(0),
110
+ )
111
+ return {
112
+ "kind": "approval_revoke",
113
+ "to": token_address,
114
+ "data": calldata,
115
+ "value": "0",
116
+ "chainId": chain_id,
117
+ "from": wallet,
118
+ }
119
+
120
+
121
+ def revoke_approval(
122
+ chain_name: str,
123
+ wallet: str,
124
+ token: str,
125
+ spender: str,
126
+ rpc_url: str | None = None,
127
+ ) -> dict[str, Any]:
128
+ chain = normalize_chain(chain_name)
129
+ resolved_wallet = resolve_wallet_address(wallet) or wallet
130
+
131
+ tok = resolve_token(chain, token)
132
+ if tok["address"] == "NATIVE":
133
+ raise ValueError("cannot revoke approval for native token")
134
+
135
+ spenders = _get_spenders(chain_name)
136
+ target = None
137
+ for sp in spenders:
138
+ if sp["label"].lower() == spender.lower() or sp["address"].lower() == spender.lower():
139
+ target = sp
140
+ break
141
+ if not target:
142
+ available = [sp["label"] for sp in spenders]
143
+ raise ValueError(f"spender '{spender}' not found. Available: {available}")
144
+
145
+ tx = _build_revoke_tx(tok["address"], target["address"], resolved_wallet, chain.chain_id)
146
+
147
+ return {
148
+ "action": "approval_revoke",
149
+ "chain": {"key": chain.key, "chainId": chain.chain_id},
150
+ "wallet": resolved_wallet,
151
+ "token": tok["symbol"],
152
+ "tokenAddress": tok["address"],
153
+ "spenderLabel": target["label"],
154
+ "spenderAddress": target["address"],
155
+ "transaction": tx,
156
+ }
157
+
158
+
159
+ def revoke_all(
160
+ chain_name: str,
161
+ wallet: str,
162
+ rpc_url: str | None = None,
163
+ dry_run: bool = True,
164
+ ) -> dict[str, Any]:
165
+ chain = normalize_chain(chain_name)
166
+ resolved_wallet = resolve_wallet_address(wallet) or wallet
167
+
168
+ scan = scan_approvals(chain_name, resolved_wallet, rpc_url)
169
+
170
+ revoke_txs: list[dict[str, Any]] = []
171
+ for approval in scan["approvals"]:
172
+ tx = _build_revoke_tx(
173
+ approval["tokenAddress"],
174
+ approval["spenderAddress"],
175
+ resolved_wallet,
176
+ chain.chain_id,
177
+ )
178
+ revoke_txs.append({
179
+ "token": approval["token"],
180
+ "tokenAddress": approval["tokenAddress"],
181
+ "spenderLabel": approval["spenderLabel"],
182
+ "spenderAddress": approval["spenderAddress"],
183
+ "previousAllowance": approval["allowance"],
184
+ "transaction": tx,
185
+ })
186
+
187
+ return {
188
+ "action": "approval_revoke_all",
189
+ "chain": {"key": chain.key, "chainId": chain.chain_id},
190
+ "wallet": resolved_wallet,
191
+ "dryRun": dry_run,
192
+ "totalApprovals": len(scan["approvals"]),
193
+ "revokes": revoke_txs,
194
+ }
195
+
196
+
197
+ def main() -> None:
198
+ parser = argparse.ArgumentParser(description="Scan and revoke ERC-20 approvals for Uniswap contracts")
199
+ sub = parser.add_subparsers(dest="command")
200
+
201
+ s = sub.add_parser("scan", help="Scan all non-zero ERC-20 approvals for known spenders")
202
+ s.add_argument("--chain", required=True)
203
+ s.add_argument("--wallet", required=True)
204
+ s.add_argument("--rpc-url")
205
+ s.add_argument("--output")
206
+
207
+ r = sub.add_parser("revoke", help="Revoke a specific token/spender approval")
208
+ r.add_argument("--chain", required=True)
209
+ r.add_argument("--wallet", required=True)
210
+ r.add_argument("--token", required=True, help="Token symbol or address")
211
+ r.add_argument("--spender", required=True, help="Spender label (positionManager, router02, permit2, universalRouter) or address")
212
+ r.add_argument("--rpc-url")
213
+ r.add_argument("--output")
214
+
215
+ ra = sub.add_parser("revoke-all", help="Revoke all non-zero approvals (dry-run by default)")
216
+ ra.add_argument("--chain", required=True)
217
+ ra.add_argument("--wallet", required=True)
218
+ ra.add_argument("--rpc-url")
219
+ ra.add_argument("--execute", action="store_true", help="Actually broadcast revoke transactions")
220
+ ra.add_argument("--output")
221
+
222
+ args = parser.parse_args()
223
+ load_local_env()
224
+
225
+ if args.command == "scan":
226
+ result = scan_approvals(args.chain, args.wallet, args.rpc_url)
227
+ n = result["nonZeroApprovals"]
228
+ print(f"Found {n} non-zero approvals across {result['tokensChecked']} tokens and {result['spendersChecked']} spenders")
229
+ for a in result["approvals"]:
230
+ max_flag = " [MAX]" if a["isMaxApproval"] else ""
231
+ print(f" {a['token']:8s} -> {a['spenderLabel']:20s} ({a['spenderAddress'][:10]}...){max_flag} allowance={a['allowance'][:20]}")
232
+ if args.output:
233
+ Path(args.output).write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
234
+ dump_json(result)
235
+ elif args.command == "revoke":
236
+ result = revoke_approval(args.chain, args.wallet, args.token, args.spender, args.rpc_url)
237
+ print(f"Revoke tx: {result['token']} -> {result['spenderLabel']}")
238
+ if args.output:
239
+ Path(args.output).write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
240
+ dump_json(result)
241
+ elif args.command == "revoke-all":
242
+ result = revoke_all(args.chain, args.wallet, args.rpc_url, dry_run=not args.execute)
243
+ mode = "DRY RUN" if result["dryRun"] else "EXECUTE"
244
+ print(f"Revoke all ({mode}): {result['totalApprovals']} approvals to revoke")
245
+ for r in result["revokes"]:
246
+ print(f" {r['token']:8s} -> {r['spenderLabel']:20s} (was {r['previousAllowance'][:20]})")
247
+ if args.output:
248
+ Path(args.output).write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
249
+ dump_json(result)
250
+ else:
251
+ parser.print_help()
252
+
253
+
254
+ if __name__ == "__main__":
255
+ main()
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ import sys
7
+
8
+
9
+ from uniswap_autopilot.common.common import (
10
+ check_balance,
11
+ decimal_to_base_units,
12
+ dump_json,
13
+ is_native,
14
+ load_local_env,
15
+ normalize_chain,
16
+ parse_amount,
17
+ resolve_token,
18
+ resolve_wallet_address,
19
+ )
20
+ from uniswap_autopilot.execute._internal.rpc import resolve_rpc_url
21
+
22
+
23
+ def main() -> None:
24
+ parser = argparse.ArgumentParser(description="检查钱包代币余额")
25
+ parser.add_argument("--chain", required=True, help="链名")
26
+ parser.add_argument("--token", required=True, help="代币 symbol 或地址")
27
+ parser.add_argument("--amount", help="要检查的数量(人类可读),如 1.5")
28
+ parser.add_argument("--wallet", help="钱包地址(默认从 env 读取)")
29
+ parser.add_argument("--rpc-url", help="RPC URL")
30
+ args = parser.parse_args()
31
+
32
+ load_local_env()
33
+ chain = normalize_chain(args.chain)
34
+ rpc = args.rpc_url or resolve_rpc_url(None, chain.chain_id)[0]
35
+
36
+ wallet = resolve_wallet_address(args.wallet)
37
+ if not wallet:
38
+ print(json.dumps({"error": "wallet address required"}), file=sys.stderr)
39
+ sys.exit(1)
40
+
41
+ token = resolve_token(chain, args.token, rpc)
42
+
43
+ if args.amount:
44
+ amount_base = decimal_to_base_units(parse_amount(args.amount), token["decimals"])
45
+ result = check_balance(chain, token, amount_base, wallet, rpc)
46
+ result["humanAmount"] = args.amount
47
+ result["token"] = token["symbol"]
48
+ result["wallet"] = wallet
49
+ status = "OK" if result.get("ok") else "INSUFFICIENT"
50
+ print(f"{status}: {token['symbol']} balance={result.get('balance', '?')} required={amount_base} ({args.amount})")
51
+ else:
52
+ from uniswap_autopilot.execute._internal.rpc import query_erc20_balance, query_native_balance
53
+ if is_native(chain, args.token) or token.get("address") == "NATIVE":
54
+ raw = query_native_balance(wallet, rpc)
55
+ else:
56
+ raw = query_erc20_balance(wallet, token["address"], rpc)
57
+ result = {"token": token["symbol"], "wallet": wallet, "balance": str(raw), "decimals": token["decimals"]}
58
+ print(f"{token['symbol']}: balance={raw} ({token['decimals']} decimals)")
59
+
60
+ dump_json(result)
61
+
62
+
63
+ if __name__ == "__main__":
64
+ main()