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.
- uni_exec_engine-0.2.0.dist-info/METADATA +576 -0
- uni_exec_engine-0.2.0.dist-info/RECORD +80 -0
- uni_exec_engine-0.2.0.dist-info/WHEEL +5 -0
- uni_exec_engine-0.2.0.dist-info/licenses/LICENSE +21 -0
- uni_exec_engine-0.2.0.dist-info/top_level.txt +1 -0
- uniswap_autopilot/__init__.py +3 -0
- uniswap_autopilot/analytics/__init__.py +0 -0
- uniswap_autopilot/analytics/il_calculator.py +525 -0
- uniswap_autopilot/analytics/portfolio.py +234 -0
- uniswap_autopilot/analytics/position.py +433 -0
- uniswap_autopilot/analytics/range_suggest.py +270 -0
- uniswap_autopilot/audit.py +194 -0
- uniswap_autopilot/common/__init__.py +0 -0
- uniswap_autopilot/common/approval_cleanup.py +255 -0
- uniswap_autopilot/common/check_balance.py +64 -0
- uniswap_autopilot/common/common.py +804 -0
- uniswap_autopilot/common/deep_link.py +132 -0
- uniswap_autopilot/common/gas.py +141 -0
- uniswap_autopilot/data/auto_trade_policy.example.json +29 -0
- uniswap_autopilot/data/chains.json +135 -0
- uniswap_autopilot/data/common-token-addresses.json +777 -0
- uniswap_autopilot/execute/__init__.py +0 -0
- uniswap_autopilot/execute/_internal/__init__.py +5 -0
- uniswap_autopilot/execute/_internal/constants.py +15 -0
- uniswap_autopilot/execute/_internal/preflight.py +150 -0
- uniswap_autopilot/execute/_internal/pure_signer.py +182 -0
- uniswap_autopilot/execute/_internal/rpc.py +462 -0
- uniswap_autopilot/execute/_internal/signer.py +298 -0
- uniswap_autopilot/execute/_internal/submit.py +73 -0
- uniswap_autopilot/execute/_internal/tx.py +370 -0
- uniswap_autopilot/execute/broadcast.py +380 -0
- uniswap_autopilot/execute/detect.py +52 -0
- uniswap_autopilot/execute/telegram_confirm.py +272 -0
- uniswap_autopilot/lp/compare_pools.py +338 -0
- uniswap_autopilot/lp/v2/__init__.py +0 -0
- uniswap_autopilot/lp/v2/approve.py +100 -0
- uniswap_autopilot/lp/v2/build_tx.py +226 -0
- uniswap_autopilot/lp/v2/flow.py +204 -0
- uniswap_autopilot/lp/v2/pair.py +135 -0
- uniswap_autopilot/lp/v2/positions.py +177 -0
- uniswap_autopilot/lp/v3/__init__.py +0 -0
- uniswap_autopilot/lp/v3/approve.py +109 -0
- uniswap_autopilot/lp/v3/auto_rebalance.py +282 -0
- uniswap_autopilot/lp/v3/build_tx.py +465 -0
- uniswap_autopilot/lp/v3/compound.py +258 -0
- uniswap_autopilot/lp/v3/flow.py +358 -0
- uniswap_autopilot/lp/v3/pool.py +175 -0
- uniswap_autopilot/lp/v3/position.py +112 -0
- uniswap_autopilot/lp/v3/tick.py +75 -0
- uniswap_autopilot/lp/v4/__init__.py +0 -0
- uniswap_autopilot/lp/v4/approve.py +105 -0
- uniswap_autopilot/lp/v4/build_tx.py +669 -0
- uniswap_autopilot/lp/v4/flow.py +368 -0
- uniswap_autopilot/lp/v4/pool.py +174 -0
- uniswap_autopilot/lp/v4/position.py +185 -0
- uniswap_autopilot/policy.py +371 -0
- uniswap_autopilot/price_feed.py +100 -0
- uniswap_autopilot/py.typed +0 -0
- uniswap_autopilot/search/__init__.py +0 -0
- uniswap_autopilot/search/risk.py +200 -0
- uniswap_autopilot/search/search.py +580 -0
- uniswap_autopilot/state_machine.py +315 -0
- uniswap_autopilot/swap/__init__.py +1 -0
- uniswap_autopilot/swap/deep_link.py +69 -0
- uniswap_autopilot/swap/extensions/__init__.py +2 -0
- uniswap_autopilot/swap/extensions/bridge.py +197 -0
- uniswap_autopilot/swap/extensions/limit_order.py +272 -0
- uniswap_autopilot/swap/extensions/slippage.py +123 -0
- uniswap_autopilot/swap/flow.py +693 -0
- uniswap_autopilot/swap/flow_core/__init__.py +2 -0
- uniswap_autopilot/swap/flow_core/artifacts.py +11 -0
- uniswap_autopilot/swap/flow_core/broadcast.py +50 -0
- uniswap_autopilot/swap/flow_core/diagnostics.py +216 -0
- uniswap_autopilot/swap/flow_core/paper.py +113 -0
- uniswap_autopilot/swap/flow_core/policy.py +142 -0
- uniswap_autopilot/swap/links/__init__.py +2 -0
- uniswap_autopilot/swap/links/deep_link.py +69 -0
- uniswap_autopilot/swap/trading_api/permit.py +41 -0
- uniswap_autopilot/swap/trading_api/quote.py +282 -0
- 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()
|