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,132 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import sys
6
+ from urllib.parse import urlencode
7
+
8
+
9
+ from uniswap_autopilot.common.common import dump_json, normalize_chain, resolve_token, load_local_env
10
+
11
+
12
+ def _swap_url(chain_url_param: str, addr_in: str, addr_out: str) -> str:
13
+ params = {
14
+ "chain": chain_url_param,
15
+ "inputCurrency": addr_in,
16
+ "outputCurrency": addr_out,
17
+ }
18
+ return f"https://app.uniswap.org/swap?{urlencode(params)}"
19
+
20
+
21
+ def _lp_url(chain_url_param: str, symbol_a: str, symbol_b: str, fee: int | None = None) -> str:
22
+ base = f"https://app.uniswap.org/add/{symbol_a}/{symbol_b}?chain={chain_url_param}"
23
+ if fee is not None:
24
+ base += f"&fee={fee}"
25
+ return base
26
+
27
+
28
+ def build_swap_link(
29
+ chain_name: str,
30
+ token_in_name: str,
31
+ token_out_name: str,
32
+ ) -> dict[str, object]:
33
+ chain = normalize_chain(chain_name)
34
+ token_in = resolve_token(chain, token_in_name)
35
+ token_out = resolve_token(chain, token_out_name)
36
+
37
+ # For swap links, native tokens use "NATIVE" as inputCurrency (Uniswap convention)
38
+ addr_in = "NATIVE" if token_in["address"] == "NATIVE" else token_in["address"]
39
+ addr_out = "NATIVE" if token_out["address"] == "NATIVE" else token_out["address"]
40
+
41
+ url = _swap_url(chain.url_param, addr_in, addr_out)
42
+
43
+ print(f"Swap URL: {url}")
44
+
45
+ return {
46
+ "action": "deep_link_swap",
47
+ "chain": {"key": chain.key, "chainId": chain.chain_id},
48
+ "tokenIn": token_in,
49
+ "tokenOut": token_out,
50
+ "url": url,
51
+ }
52
+
53
+
54
+ def build_lp_link(
55
+ chain_name: str,
56
+ token_a_name: str,
57
+ token_b_name: str,
58
+ fee_tier: int | None = None,
59
+ ) -> dict[str, object]:
60
+ chain = normalize_chain(chain_name)
61
+ token_a = resolve_token(chain, token_a_name)
62
+ token_b = resolve_token(chain, token_b_name)
63
+
64
+ # For LP links, use token symbols in the URL path
65
+ symbol_a = token_a["symbol"]
66
+ symbol_b = token_b["symbol"]
67
+
68
+ url = _lp_url(chain.url_param, symbol_a, symbol_b, fee_tier)
69
+
70
+ print(f"LP URL: {url}")
71
+
72
+ return {
73
+ "action": "deep_link_lp",
74
+ "chain": {"key": chain.key, "chainId": chain.chain_id},
75
+ "tokenA": token_a,
76
+ "tokenB": token_b,
77
+ "feeTier": fee_tier,
78
+ "url": url,
79
+ }
80
+
81
+
82
+ def main() -> None:
83
+ parser = argparse.ArgumentParser(description="Generate app.uniswap.org deep links")
84
+ sub = parser.add_subparsers(dest="command")
85
+
86
+ # swap
87
+ sw = sub.add_parser("swap", help="Generate a swap deep link")
88
+ sw.add_argument("--chain", required=True, help="Chain name, e.g. base / ethereum")
89
+ sw.add_argument("--token-in", required=True, help="Input token symbol, address, or NATIVE")
90
+ sw.add_argument("--token-out", required=True, help="Output token symbol, address, or NATIVE")
91
+
92
+ # lp
93
+ lp = sub.add_parser("lp", help="Generate an LP deep link")
94
+ lp.add_argument("--chain", required=True, help="Chain name, e.g. base / ethereum")
95
+ lp.add_argument("--token-a", required=True, help="Token A symbol or address")
96
+ lp.add_argument("--token-b", required=True, help="Token B symbol or address")
97
+ lp.add_argument("--fee-tier", type=int, default=None, help="V3 fee tier (e.g. 3000)")
98
+
99
+ args = parser.parse_args()
100
+
101
+ try:
102
+ load_local_env()
103
+
104
+ if args.command == "swap":
105
+ result = build_swap_link(
106
+ chain_name=args.chain,
107
+ token_in_name=args.token_in,
108
+ token_out_name=args.token_out,
109
+ )
110
+ dump_json(result)
111
+
112
+ elif args.command == "lp":
113
+ result = build_lp_link(
114
+ chain_name=args.chain,
115
+ token_a_name=args.token_a,
116
+ token_b_name=args.token_b,
117
+ fee_tier=args.fee_tier,
118
+ )
119
+ dump_json(result)
120
+
121
+ else:
122
+ parser.print_help()
123
+ sys.exit(1)
124
+
125
+ except Exception as exc:
126
+ import json
127
+ print(json.dumps({"error": str(exc)}, ensure_ascii=False, indent=2), file=sys.stderr)
128
+ sys.exit(1)
129
+
130
+
131
+ if __name__ == "__main__":
132
+ main()
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ from typing import Any
6
+
7
+
8
+ from uniswap_autopilot.common.common import dump_json, load_local_env, normalize_chain, PUBLIC_RPC_URLS
9
+ from uniswap_autopilot.execute._internal.rpc import (
10
+ build_calldata, encode_uint, eth_fee_history, query_gas_price, resolve_rpc_url,
11
+ )
12
+
13
+ L2_UNWRAP_CHAINS = {
14
+ "base", "arbitrum", "optimism", "polygon", "unichain",
15
+ "linea", "blast", "zora", "world_chain", "soneium",
16
+ }
17
+
18
+ SPEED_PERCENTILE = {"slow": 15, "standard": 50, "fast": 85}
19
+
20
+
21
+ def estimate_gas_price(
22
+ chain_name: str,
23
+ speed: str = "standard",
24
+ ) -> dict[str, Any]:
25
+ chain = normalize_chain(chain_name)
26
+ rpc_url = PUBLIC_RPC_URLS.get(chain.key) or resolve_rpc_url(None, chain.chain_id)[0]
27
+ if not rpc_url:
28
+ raise RuntimeError(f"RPC URL not configured for {chain_name}")
29
+
30
+ result: dict[str, Any] = {
31
+ "chain": {"key": chain.key, "chainId": chain.chain_id},
32
+ "speed": speed,
33
+ }
34
+
35
+ # Try EIP-1559 fee history
36
+ try:
37
+ data = eth_fee_history(4, "latest", rpc_url, reward_percentiles=[25, 50, 75])
38
+ base_fee_hex = (data.get("baseFeePerGas") or [])[-1]
39
+ base_fee = int(base_fee_hex, 16) if base_fee_hex else None
40
+ result["baseFee"] = base_fee
41
+
42
+ rewards = data.get("reward") or []
43
+ if rewards and rewards[0] and rewards[-1]:
44
+ pct = SPEED_PERCENTILE.get(speed, 50)
45
+ idx = min(pct // 25, len(rewards[0]) - 1)
46
+ priority_hex = rewards[-1][idx] if idx < len(rewards[-1]) else "0x0"
47
+ priority_fee = int(priority_hex, 16)
48
+ else:
49
+ priority_fee = 1_500_000_000 # 1.5 gwei default
50
+ result["priorityFee"] = priority_fee
51
+ result["totalEstimate"] = (base_fee or 0) + priority_fee
52
+ result["mode"] = "eip1559"
53
+ except Exception:
54
+ # Fallback to legacy gas price
55
+ try:
56
+ gas_price = query_gas_price(rpc_url)
57
+ result["gasPriceLegacy"] = gas_price
58
+ result["totalEstimate"] = gas_price
59
+ result["mode"] = "legacy"
60
+ except Exception as exc:
61
+ result["error"] = str(exc)
62
+ result["totalEstimate"] = 0
63
+
64
+ return result
65
+
66
+
67
+ def check_weth_unwrap_needed(
68
+ chain_name: str,
69
+ token_out_info: dict[str, Any],
70
+ original_token_out: str,
71
+ ) -> bool:
72
+ if chain_name not in L2_UNWRAP_CHAINS:
73
+ return False
74
+ chain = normalize_chain(chain_name)
75
+ wrapped = chain.tokens.get(chain.wrapped_native_symbol.upper())
76
+ if not wrapped:
77
+ return False
78
+ if token_out_info.get("address", "").lower() != wrapped.address.lower():
79
+ return False
80
+ upper = original_token_out.strip().upper()
81
+ return upper in {"NATIVE", chain.native_symbol.upper()}
82
+
83
+
84
+ def build_weth_unwrap_tx(
85
+ chain_name: str,
86
+ amount_wei: str,
87
+ wallet: str,
88
+ ) -> dict[str, Any]:
89
+ chain = normalize_chain(chain_name)
90
+ wrapped = chain.tokens.get(chain.wrapped_native_symbol.upper())
91
+ if not wrapped:
92
+ raise ValueError(f"wrapped native not configured for {chain_name}")
93
+
94
+ calldata = build_calldata("withdraw(uint256)", encode_uint(int(amount_wei)))
95
+
96
+ return {
97
+ "action": "weth_unwrap",
98
+ "chain": {"key": chain.key, "chainId": chain.chain_id},
99
+ "wethAddress": wrapped.address,
100
+ "amountWei": amount_wei,
101
+ "transaction": {
102
+ "kind": "weth_unwrap",
103
+ "to": wrapped.address,
104
+ "data": calldata,
105
+ "value": "0",
106
+ "chainId": chain.chain_id,
107
+ "from": wallet,
108
+ },
109
+ }
110
+
111
+
112
+ def main() -> None:
113
+ parser = argparse.ArgumentParser(description="Gas price estimation and WETH unwrap utilities")
114
+ sub = parser.add_subparsers(dest="command")
115
+
116
+ g = sub.add_parser("estimate", help="Estimate gas price for a chain")
117
+ g.add_argument("--chain", required=True)
118
+ g.add_argument("--speed", choices=["slow", "standard", "fast"], default="standard")
119
+
120
+ w = sub.add_parser("unwrap", help="Build WETH.unwrap transaction")
121
+ w.add_argument("--chain", required=True)
122
+ w.add_argument("--amount-wei", required=True, help="Amount in wei to unwrap")
123
+ w.add_argument("--wallet", required=True)
124
+
125
+ args = parser.parse_args()
126
+ load_local_env()
127
+
128
+ if args.command == "estimate":
129
+ result = estimate_gas_price(args.chain, args.speed)
130
+ total_gwei = result["totalEstimate"] / 1e9
131
+ print(f"Gas estimate ({args.speed}): {total_gwei:.2f} gwei ({result.get('mode', 'unknown')})")
132
+ dump_json(result)
133
+ elif args.command == "unwrap":
134
+ result = build_weth_unwrap_tx(args.chain, args.amount_wei, args.wallet)
135
+ dump_json(result)
136
+ else:
137
+ parser.print_help()
138
+
139
+
140
+ if __name__ == "__main__":
141
+ main()
@@ -0,0 +1,29 @@
1
+ {
2
+ "enabled": false,
3
+ "allowedChains": [
4
+ "base"
5
+ ],
6
+ "allowedPairs": [
7
+ {
8
+ "chain": "base",
9
+ "tokenIn": "USDC",
10
+ "tokenOut": "WETH",
11
+ "maxAmount": "100",
12
+ "maxSlippage": 0.5,
13
+ "allowAutoSignPermit": false,
14
+ "allowAutoApproval": false,
15
+ "allowAutoBroadcastSwap": false
16
+ },
17
+ {
18
+ "chain": "base",
19
+ "tokenIn": "NATIVE",
20
+ "tokenOut": "USDC",
21
+ "maxAmount": "0.1",
22
+ "maxSlippage": 0.5,
23
+ "allowAutoSignPermit": false,
24
+ "allowAutoApproval": false,
25
+ "allowAutoBroadcastSwap": false
26
+ }
27
+ ],
28
+ "requirePreflightOk": true
29
+ }
@@ -0,0 +1,135 @@
1
+ {
2
+ "ethereum": {
3
+ "chainId": 1,
4
+ "nativeSymbol": "ETH",
5
+ "wrappedNativeSymbol": "WETH",
6
+ "urlParam": "ethereum",
7
+ "geckoterminalNetworkId": "eth"
8
+ },
9
+ "optimism": {
10
+ "chainId": 10,
11
+ "nativeSymbol": "ETH",
12
+ "wrappedNativeSymbol": "WETH",
13
+ "urlParam": "optimism",
14
+ "geckoterminalNetworkId": "optimism"
15
+ },
16
+ "bsc": {
17
+ "chainId": 56,
18
+ "nativeSymbol": "BNB",
19
+ "wrappedNativeSymbol": "WBNB",
20
+ "urlParam": "bnb",
21
+ "geckoterminalNetworkId": "bsc"
22
+ },
23
+ "unichain": {
24
+ "chainId": 130,
25
+ "nativeSymbol": "ETH",
26
+ "wrappedNativeSymbol": "WETH",
27
+ "urlParam": "unichain",
28
+ "geckoterminalNetworkId": "unichain"
29
+ },
30
+ "polygon": {
31
+ "chainId": 137,
32
+ "nativeSymbol": "MATIC",
33
+ "wrappedNativeSymbol": "WMATIC",
34
+ "urlParam": "polygon",
35
+ "geckoterminalNetworkId": "polygon_pos"
36
+ },
37
+ "monad": {
38
+ "chainId": 143,
39
+ "nativeSymbol": "MON",
40
+ "wrappedNativeSymbol": "WMON",
41
+ "urlParam": "monad",
42
+ "geckoterminalNetworkId": "monad"
43
+ },
44
+ "x_layer": {
45
+ "chainId": 196,
46
+ "nativeSymbol": "OKB",
47
+ "wrappedNativeSymbol": "WOKB",
48
+ "urlParam": "xlayer",
49
+ "geckoterminalNetworkId": "xlayer"
50
+ },
51
+ "zksync": {
52
+ "chainId": 324,
53
+ "nativeSymbol": "ETH",
54
+ "wrappedNativeSymbol": "WETH",
55
+ "urlParam": "zksync",
56
+ "geckoterminalNetworkId": "zksync"
57
+ },
58
+ "world_chain": {
59
+ "chainId": 480,
60
+ "nativeSymbol": "ETH",
61
+ "wrappedNativeSymbol": "WETH",
62
+ "urlParam": "worldchain",
63
+ "geckoterminalNetworkId": "worldchain"
64
+ },
65
+ "soneium": {
66
+ "chainId": 1868,
67
+ "nativeSymbol": "ETH",
68
+ "wrappedNativeSymbol": "WETH",
69
+ "urlParam": "soneium",
70
+ "geckoterminalNetworkId": "soneium"
71
+ },
72
+ "tempo": {
73
+ "chainId": 4217,
74
+ "nativeSymbol": "AVAX",
75
+ "wrappedNativeSymbol": "WAVAX",
76
+ "urlParam": "tempo",
77
+ "geckoterminalNetworkId": "tempo"
78
+ },
79
+ "base": {
80
+ "chainId": 8453,
81
+ "nativeSymbol": "ETH",
82
+ "wrappedNativeSymbol": "WETH",
83
+ "urlParam": "base",
84
+ "geckoterminalNetworkId": "base"
85
+ },
86
+ "arbitrum": {
87
+ "chainId": 42161,
88
+ "nativeSymbol": "ETH",
89
+ "wrappedNativeSymbol": "WETH",
90
+ "urlParam": "arbitrum",
91
+ "geckoterminalNetworkId": "arbitrum"
92
+ },
93
+ "celo": {
94
+ "chainId": 42220,
95
+ "nativeSymbol": "CELO",
96
+ "wrappedNativeSymbol": "CELO",
97
+ "urlParam": "celo",
98
+ "geckoterminalNetworkId": "celo"
99
+ },
100
+ "avalanche": {
101
+ "chainId": 43114,
102
+ "nativeSymbol": "AVAX",
103
+ "wrappedNativeSymbol": "WAVAX",
104
+ "urlParam": "avalanche",
105
+ "geckoterminalNetworkId": "avax"
106
+ },
107
+ "linea": {
108
+ "chainId": 59144,
109
+ "nativeSymbol": "ETH",
110
+ "wrappedNativeSymbol": "WETH",
111
+ "urlParam": "linea",
112
+ "geckoterminalNetworkId": "linea"
113
+ },
114
+ "blast": {
115
+ "chainId": 81457,
116
+ "nativeSymbol": "ETH",
117
+ "wrappedNativeSymbol": "WETH",
118
+ "urlParam": "blast",
119
+ "geckoterminalNetworkId": "blast"
120
+ },
121
+ "zora": {
122
+ "chainId": 7777777,
123
+ "nativeSymbol": "ETH",
124
+ "wrappedNativeSymbol": "WETH",
125
+ "urlParam": "zora",
126
+ "geckoterminalNetworkId": "zora"
127
+ },
128
+ "ink": {
129
+ "chainId": 57073,
130
+ "nativeSymbol": "ETH",
131
+ "wrappedNativeSymbol": "WETH",
132
+ "urlParam": "ink",
133
+ "geckoterminalNetworkId": "ink"
134
+ }
135
+ }