cryptoagent-ai 0.1.0__tar.gz

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 (63) hide show
  1. cryptoagent_ai-0.1.0/.claude/settings.local.json +15 -0
  2. cryptoagent_ai-0.1.0/.env.example +25 -0
  3. cryptoagent_ai-0.1.0/LICENSE +21 -0
  4. cryptoagent_ai-0.1.0/PKG-INFO +96 -0
  5. cryptoagent_ai-0.1.0/README.md +58 -0
  6. cryptoagent_ai-0.1.0/chain/__init__.py +0 -0
  7. cryptoagent_ai-0.1.0/chain/abi_registry.py +57 -0
  8. cryptoagent_ai-0.1.0/chain/client.py +207 -0
  9. cryptoagent_ai-0.1.0/chain/constants.py +122 -0
  10. cryptoagent_ai-0.1.0/chain/multicall.py +128 -0
  11. cryptoagent_ai-0.1.0/chain/provider.py +134 -0
  12. cryptoagent_ai-0.1.0/chain/transaction.py +158 -0
  13. cryptoagent_ai-0.1.0/cli/__init__.py +0 -0
  14. cryptoagent_ai-0.1.0/cli/main.py +271 -0
  15. cryptoagent_ai-0.1.0/config/__init__.py +0 -0
  16. cryptoagent_ai-0.1.0/config/agents.yaml +69 -0
  17. cryptoagent_ai-0.1.0/config/chains.yaml +64 -0
  18. cryptoagent_ai-0.1.0/config/protocols.yaml +44 -0
  19. cryptoagent_ai-0.1.0/config/settings.py +100 -0
  20. cryptoagent_ai-0.1.0/core/__init__.py +0 -0
  21. cryptoagent_ai-0.1.0/core/agent.py +284 -0
  22. cryptoagent_ai-0.1.0/core/brain.py +171 -0
  23. cryptoagent_ai-0.1.0/core/exceptions.py +153 -0
  24. cryptoagent_ai-0.1.0/core/tool_registry.py +159 -0
  25. cryptoagent_ai-0.1.0/core/types.py +148 -0
  26. cryptoagent_ai-0.1.0/events/__init__.py +0 -0
  27. cryptoagent_ai-0.1.0/events/bus.py +93 -0
  28. cryptoagent_ai-0.1.0/events/monitor.py +73 -0
  29. cryptoagent_ai-0.1.0/events/price_feed.py +110 -0
  30. cryptoagent_ai-0.1.0/events/triggers.py +82 -0
  31. cryptoagent_ai-0.1.0/memory/__init__.py +0 -0
  32. cryptoagent_ai-0.1.0/memory/models.py +87 -0
  33. cryptoagent_ai-0.1.0/memory/portfolio.py +88 -0
  34. cryptoagent_ai-0.1.0/memory/store.py +185 -0
  35. cryptoagent_ai-0.1.0/orchestration/__init__.py +0 -0
  36. cryptoagent_ai-0.1.0/orchestration/coordinator.py +84 -0
  37. cryptoagent_ai-0.1.0/orchestration/scheduler.py +88 -0
  38. cryptoagent_ai-0.1.0/orchestration/strategies.py +100 -0
  39. cryptoagent_ai-0.1.0/protocols/__init__.py +0 -0
  40. cryptoagent_ai-0.1.0/protocols/aave/__init__.py +205 -0
  41. cryptoagent_ai-0.1.0/protocols/base.py +34 -0
  42. cryptoagent_ai-0.1.0/protocols/erc20/__init__.py +87 -0
  43. cryptoagent_ai-0.1.0/protocols/registry.py +60 -0
  44. cryptoagent_ai-0.1.0/protocols/uniswap/__init__.py +195 -0
  45. cryptoagent_ai-0.1.0/pyproject.toml +78 -0
  46. cryptoagent_ai-0.1.0/safety/__init__.py +0 -0
  47. cryptoagent_ai-0.1.0/safety/approval.py +89 -0
  48. cryptoagent_ai-0.1.0/safety/audit.py +107 -0
  49. cryptoagent_ai-0.1.0/safety/guardian.py +119 -0
  50. cryptoagent_ai-0.1.0/safety/limits.py +72 -0
  51. cryptoagent_ai-0.1.0/safety/rules.py +159 -0
  52. cryptoagent_ai-0.1.0/safety/slippage.py +58 -0
  53. cryptoagent_ai-0.1.0/tools/__init__.py +0 -0
  54. cryptoagent_ai-0.1.0/tools/blockchain_tools.py +88 -0
  55. cryptoagent_ai-0.1.0/tools/defi_tools.py +127 -0
  56. cryptoagent_ai-0.1.0/tools/memory_tools.py +62 -0
  57. cryptoagent_ai-0.1.0/tools/portfolio_tools.py +45 -0
  58. cryptoagent_ai-0.1.0/tools/wallet_tools.py +45 -0
  59. cryptoagent_ai-0.1.0/wallet/__init__.py +0 -0
  60. cryptoagent_ai-0.1.0/wallet/hd.py +60 -0
  61. cryptoagent_ai-0.1.0/wallet/keystore.py +104 -0
  62. cryptoagent_ai-0.1.0/wallet/manager.py +135 -0
  63. cryptoagent_ai-0.1.0/wallet/signer.py +68 -0
@@ -0,0 +1,15 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "WebSearch",
5
+ "Bash(pip install:*)",
6
+ "Bash(python -c:*)",
7
+ "Bash(python -m cli.main:*)",
8
+ "Bash(python -m build:*)",
9
+ "Bash(twine check:*)",
10
+ "Bash(twine upload:*)",
11
+ "Bash(TWINE_USERNAME=__token__ TWINE_PASSWORD=pypi-AgEIcHlwaS5vcmcCJDk1YThkNjc3LWNjNTUtNGU1Mi05YTkzLTgwYWVkOGM2OGQzMgACKlszLCI3YjVlODQzZC01M2E3LTRkMTQtOGNjMS0wZDg5NDU3OWEzMzUiXQAABiDC0ffAMKgCvZqXdhEztxeHys83JcIz6R--q-mJPGzwCw twine upload:*)",
12
+ "Bash(pip index:*)"
13
+ ]
14
+ }
15
+ }
@@ -0,0 +1,25 @@
1
+ # Claude API
2
+ ANTHROPIC_API_KEY=sk-ant-...
3
+
4
+ # RPC Endpoints (override defaults in chains.yaml)
5
+ ETH_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
6
+ BASE_RPC_URL=https://base-mainnet.g.alchemy.com/v2/YOUR_KEY
7
+ ARBITRUM_RPC_URL=https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY
8
+ POLYGON_RPC_URL=https://polygon-mainnet.g.alchemy.com/v2/YOUR_KEY
9
+
10
+ # Wallet encryption password
11
+ WALLET_PASSWORD=change-me-to-a-strong-password
12
+
13
+ # Safety
14
+ MAX_TX_VALUE_USD=100.0
15
+ DAILY_SPENDING_LIMIT_USD=1000.0
16
+ REQUIRE_HUMAN_APPROVAL_ABOVE_USD=50.0
17
+
18
+ # Price API (optional, falls back to Chainlink on-chain)
19
+ COINGECKO_API_KEY=
20
+
21
+ # Database
22
+ DATABASE_URL=sqlite+aiosqlite:///data/agent.db
23
+
24
+ # Logging
25
+ LOG_LEVEL=INFO
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 coins
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: cryptoagent-ai
3
+ Version: 0.1.0
4
+ Summary: Autonomous AI agents that own wallets and transact on EVM chains
5
+ Project-URL: Homepage, https://github.com/coins/cryptoagent-ai
6
+ Project-URL: Repository, https://github.com/coins/cryptoagent-ai
7
+ Author-email: coins <gobeyondfj@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: agent,ai,claude,crypto,defi,ethereum,web3
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: aiosqlite>=0.20.0
19
+ Requires-Dist: anthropic>=0.40.0
20
+ Requires-Dist: cryptography>=44.0.0
21
+ Requires-Dist: eth-account>=0.13.0
22
+ Requires-Dist: httpx>=0.28.0
23
+ Requires-Dist: mnemonic>=0.21
24
+ Requires-Dist: pydantic-settings>=2.6.0
25
+ Requires-Dist: pyyaml>=6.0.2
26
+ Requires-Dist: rich>=13.9.0
27
+ Requires-Dist: sqlmodel>=0.0.22
28
+ Requires-Dist: structlog>=24.4.0
29
+ Requires-Dist: tenacity>=9.0.0
30
+ Requires-Dist: typer[all]>=0.12.0
31
+ Requires-Dist: web3>=7.0.0
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
34
+ Requires-Dist: pytest-cov>=6.0; extra == 'dev'
35
+ Requires-Dist: pytest>=8.0; extra == 'dev'
36
+ Requires-Dist: ruff>=0.8.0; extra == 'dev'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # CryptoAgent AI
40
+
41
+ Autonomous AI agents that own wallets and independently transact on EVM chains (Ethereum, Base, Arbitrum, Polygon), powered by Claude API.
42
+
43
+ ## Features
44
+
45
+ - **HD Wallets** — BIP-44 derivation with Fernet+PBKDF2 encrypted key storage
46
+ - **Multi-chain** — Ethereum, Base, Arbitrum, Polygon with failover RPC
47
+ - **DeFi Protocols** — Uniswap V3 swaps, Aave V3 lending
48
+ - **Safety First** — Spending limits, slippage guards, human approval, audit log
49
+ - **AI Agent Loop** — Perceive → Reason (Claude) → Act → Reflect
50
+ - **16 Tools** — Balances, swaps, lending, portfolio, memory — all exposed to Claude
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ pip install cryptoagent-ai
56
+ ```
57
+
58
+ ## Quick Start
59
+
60
+ ```bash
61
+ # Show config
62
+ cryptoagent info
63
+
64
+ # Create a wallet
65
+ cryptoagent wallet create my-wallet -p "strong-password"
66
+
67
+ # Start an agent
68
+ export ANTHROPIC_API_KEY=sk-ant-...
69
+ cryptoagent agent start --wallet my-wallet -p "strong-password"
70
+ ```
71
+
72
+ ## Architecture
73
+
74
+ ```
75
+ EventMonitor → PERCEIVE (balances, events, portfolio)
76
+
77
+ REASON (Claude + tool-use)
78
+
79
+ ACT (Guardian safety checks → execute → audit)
80
+
81
+ REFLECT (update portfolio, log metrics)
82
+
83
+ sleep → loop
84
+ ```
85
+
86
+ ## Security
87
+
88
+ - Keys encrypted at rest (Fernet + PBKDF2, 600k iterations)
89
+ - Per-transaction, hourly, and daily USD spending caps
90
+ - Human-in-the-loop approval for high-value transactions
91
+ - Slippage protection on all swaps
92
+ - Append-only audit log
93
+
94
+ ## License
95
+
96
+ MIT
@@ -0,0 +1,58 @@
1
+ # CryptoAgent AI
2
+
3
+ Autonomous AI agents that own wallets and independently transact on EVM chains (Ethereum, Base, Arbitrum, Polygon), powered by Claude API.
4
+
5
+ ## Features
6
+
7
+ - **HD Wallets** — BIP-44 derivation with Fernet+PBKDF2 encrypted key storage
8
+ - **Multi-chain** — Ethereum, Base, Arbitrum, Polygon with failover RPC
9
+ - **DeFi Protocols** — Uniswap V3 swaps, Aave V3 lending
10
+ - **Safety First** — Spending limits, slippage guards, human approval, audit log
11
+ - **AI Agent Loop** — Perceive → Reason (Claude) → Act → Reflect
12
+ - **16 Tools** — Balances, swaps, lending, portfolio, memory — all exposed to Claude
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install cryptoagent-ai
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```bash
23
+ # Show config
24
+ cryptoagent info
25
+
26
+ # Create a wallet
27
+ cryptoagent wallet create my-wallet -p "strong-password"
28
+
29
+ # Start an agent
30
+ export ANTHROPIC_API_KEY=sk-ant-...
31
+ cryptoagent agent start --wallet my-wallet -p "strong-password"
32
+ ```
33
+
34
+ ## Architecture
35
+
36
+ ```
37
+ EventMonitor → PERCEIVE (balances, events, portfolio)
38
+
39
+ REASON (Claude + tool-use)
40
+
41
+ ACT (Guardian safety checks → execute → audit)
42
+
43
+ REFLECT (update portfolio, log metrics)
44
+
45
+ sleep → loop
46
+ ```
47
+
48
+ ## Security
49
+
50
+ - Keys encrypted at rest (Fernet + PBKDF2, 600k iterations)
51
+ - Per-transaction, hourly, and daily USD spending caps
52
+ - Human-in-the-loop approval for high-value transactions
53
+ - Slippage protection on all swaps
54
+ - Append-only audit log
55
+
56
+ ## License
57
+
58
+ MIT
File without changes
@@ -0,0 +1,57 @@
1
+ """ABI loading and caching registry."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from chain.constants import ERC20_ABI
10
+
11
+ # Built-in ABIs
12
+ _BUILTIN_ABIS: dict[str, list[dict[str, Any]]] = {
13
+ "erc20": ERC20_ABI,
14
+ }
15
+
16
+ # File-based ABI cache
17
+ _ABI_DIR = Path("config/abis")
18
+ _cache: dict[str, list[dict[str, Any]]] = {}
19
+
20
+
21
+ def get_abi(name: str) -> list[dict[str, Any]]:
22
+ """Get an ABI by name.
23
+
24
+ Checks built-in ABIs first, then file cache, then config/abis/ directory.
25
+ """
26
+ # Built-in
27
+ if name in _BUILTIN_ABIS:
28
+ return _BUILTIN_ABIS[name]
29
+
30
+ # Memory cache
31
+ if name in _cache:
32
+ return _cache[name]
33
+
34
+ # Load from file
35
+ path = _ABI_DIR / f"{name}.json"
36
+ if path.exists():
37
+ with open(path) as f:
38
+ abi = json.load(f)
39
+ _cache[name] = abi
40
+ return abi
41
+
42
+ raise ValueError(f"ABI not found: {name}")
43
+
44
+
45
+ def register_abi(name: str, abi: list[dict[str, Any]]) -> None:
46
+ """Register an ABI in the runtime cache."""
47
+ _cache[name] = abi
48
+
49
+
50
+ def save_abi(name: str, abi: list[dict[str, Any]]) -> Path:
51
+ """Save an ABI to the config/abis directory."""
52
+ _ABI_DIR.mkdir(parents=True, exist_ok=True)
53
+ path = _ABI_DIR / f"{name}.json"
54
+ with open(path, "w") as f:
55
+ json.dump(abi, f, indent=2)
56
+ _cache[name] = abi
57
+ return path
@@ -0,0 +1,207 @@
1
+ """High-level blockchain client for read/write operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import structlog
8
+ from web3 import AsyncWeb3
9
+
10
+ from chain.constants import ERC20_ABI, NATIVE_TOKEN
11
+ from chain.provider import ChainProvider, MultiChainProvider
12
+ from chain.transaction import NonceManager, TransactionBuilder
13
+ from core.exceptions import (
14
+ ChainError,
15
+ InsufficientFundsError,
16
+ TransactionError,
17
+ TransactionRevertedError,
18
+ )
19
+ from core.types import TokenInfo, TransactionReceipt, TransactionRequest
20
+ from wallet.signer import TransactionSigner
21
+
22
+ logger = structlog.get_logger(__name__)
23
+
24
+
25
+ class ChainClient:
26
+ """High-level async client for reading and writing to EVM chains."""
27
+
28
+ def __init__(
29
+ self,
30
+ providers: MultiChainProvider,
31
+ signer: TransactionSigner,
32
+ nonce_manager: NonceManager | None = None,
33
+ ) -> None:
34
+ self._providers = providers
35
+ self._signer = signer
36
+ self._nonce_manager = nonce_manager or NonceManager()
37
+
38
+ def _get_provider(self, chain_id: int) -> ChainProvider:
39
+ return self._providers.get(chain_id)
40
+
41
+ # ── Read Operations ────────────────────────────────────────────────
42
+
43
+ async def get_eth_balance(self, chain_id: int, address: str | None = None) -> int:
44
+ """Get native token balance in wei."""
45
+ address = address or self._signer.address
46
+ provider = self._get_provider(chain_id)
47
+ return await provider.get_balance(address)
48
+
49
+ async def get_token_balance(
50
+ self, chain_id: int, token_address: str, address: str | None = None
51
+ ) -> int:
52
+ """Get ERC-20 token balance (raw units)."""
53
+ address = address or self._signer.address
54
+ provider = self._get_provider(chain_id)
55
+ contract = provider.w3.eth.contract(
56
+ address=AsyncWeb3.to_checksum_address(token_address),
57
+ abi=ERC20_ABI,
58
+ )
59
+ return await contract.functions.balanceOf(
60
+ AsyncWeb3.to_checksum_address(address)
61
+ ).call()
62
+
63
+ async def get_token_info(self, chain_id: int, token_address: str) -> TokenInfo:
64
+ """Get token metadata (symbol, decimals, name)."""
65
+ provider = self._get_provider(chain_id)
66
+ contract = provider.w3.eth.contract(
67
+ address=AsyncWeb3.to_checksum_address(token_address),
68
+ abi=ERC20_ABI,
69
+ )
70
+ symbol, decimals, name = await asyncio.gather(
71
+ contract.functions.symbol().call(),
72
+ contract.functions.decimals().call(),
73
+ contract.functions.name().call(),
74
+ )
75
+ return TokenInfo(
76
+ address=token_address,
77
+ symbol=symbol,
78
+ decimals=decimals,
79
+ chain_id=chain_id,
80
+ name=name,
81
+ )
82
+
83
+ async def get_allowance(
84
+ self,
85
+ chain_id: int,
86
+ token_address: str,
87
+ spender: str,
88
+ owner: str | None = None,
89
+ ) -> int:
90
+ """Get ERC-20 allowance."""
91
+ owner = owner or self._signer.address
92
+ provider = self._get_provider(chain_id)
93
+ contract = provider.w3.eth.contract(
94
+ address=AsyncWeb3.to_checksum_address(token_address),
95
+ abi=ERC20_ABI,
96
+ )
97
+ return await contract.functions.allowance(
98
+ AsyncWeb3.to_checksum_address(owner),
99
+ AsyncWeb3.to_checksum_address(spender),
100
+ ).call()
101
+
102
+ async def get_gas_price(self, chain_id: int) -> int:
103
+ """Get current gas price in wei."""
104
+ provider = self._get_provider(chain_id)
105
+ return await provider.get_gas_price()
106
+
107
+ async def get_block_number(self, chain_id: int) -> int:
108
+ provider = self._get_provider(chain_id)
109
+ return await provider.get_block_number()
110
+
111
+ # ── Write Operations ───────────────────────────────────────────────
112
+
113
+ async def send_transaction(self, request: TransactionRequest) -> TransactionReceipt:
114
+ """Build, sign, send, and wait for a transaction."""
115
+ provider = self._get_provider(request.chain_id)
116
+ builder = TransactionBuilder(provider, self._nonce_manager)
117
+
118
+ # Build tx dict
119
+ tx_dict = await builder.build(request, self._signer.address)
120
+
121
+ # Check balance
122
+ balance = await provider.get_balance(self._signer.address)
123
+ total_cost = request.value + (tx_dict["gas"] * tx_dict["maxFeePerGas"])
124
+ if balance < total_cost:
125
+ raise InsufficientFundsError(
126
+ f"Balance {balance} wei < required {total_cost} wei"
127
+ )
128
+
129
+ # Sign
130
+ raw_tx = self._signer.sign_transaction(tx_dict)
131
+
132
+ # Send
133
+ try:
134
+ tx_hash = await provider.call("eth.send_raw_transaction", raw_tx)
135
+ self._nonce_manager.confirm_nonce(
136
+ request.chain_id, self._signer.address, tx_dict["nonce"]
137
+ )
138
+ except Exception as e:
139
+ self._nonce_manager.reset(request.chain_id, self._signer.address)
140
+ raise TransactionError(f"Failed to send transaction: {e}") from e
141
+
142
+ # Wait for receipt
143
+ logger.info("transaction_sent", tx_hash=tx_hash.hex(), chain_id=request.chain_id)
144
+ receipt = await provider.call("eth.wait_for_transaction_receipt", tx_hash, timeout=120)
145
+
146
+ result = TransactionReceipt(
147
+ tx_hash=receipt["transactionHash"].hex(),
148
+ chain_id=request.chain_id,
149
+ block_number=receipt["blockNumber"],
150
+ gas_used=receipt["gasUsed"],
151
+ status=receipt["status"],
152
+ logs=[dict(log) for log in receipt.get("logs", [])],
153
+ )
154
+
155
+ if not result.success:
156
+ raise TransactionRevertedError(result.tx_hash, "Transaction reverted")
157
+
158
+ logger.info(
159
+ "transaction_confirmed",
160
+ tx_hash=result.tx_hash,
161
+ block=result.block_number,
162
+ gas_used=result.gas_used,
163
+ )
164
+ return result
165
+
166
+ async def send_eth(
167
+ self, chain_id: int, to: str, value_wei: int, description: str = ""
168
+ ) -> TransactionReceipt:
169
+ """Send native ETH/MATIC."""
170
+ request = TransactionRequest(
171
+ chain_id=chain_id,
172
+ to=to,
173
+ value=value_wei,
174
+ description=description or f"Send {value_wei} wei to {to}",
175
+ )
176
+ return await self.send_transaction(request)
177
+
178
+ async def approve_token(
179
+ self,
180
+ chain_id: int,
181
+ token_address: str,
182
+ spender: str,
183
+ amount: int | None = None,
184
+ ) -> TransactionReceipt:
185
+ """Approve an ERC-20 token for spending."""
186
+ max_uint256 = 2**256 - 1
187
+ amount = amount if amount is not None else max_uint256
188
+ provider = self._get_provider(chain_id)
189
+ contract = provider.w3.eth.contract(
190
+ address=AsyncWeb3.to_checksum_address(token_address),
191
+ abi=ERC20_ABI,
192
+ )
193
+ data = contract.functions.approve(
194
+ AsyncWeb3.to_checksum_address(spender), amount
195
+ )._encode_transaction_data()
196
+
197
+ request = TransactionRequest(
198
+ chain_id=chain_id,
199
+ to=token_address,
200
+ data=bytes.fromhex(data[2:]) if isinstance(data, str) else data,
201
+ description=f"Approve {spender} to spend token {token_address}",
202
+ )
203
+ return await self.send_transaction(request)
204
+
205
+
206
+ # Missing import
207
+ import asyncio
@@ -0,0 +1,122 @@
1
+ """Well-known contract addresses per chain."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from core.types import ChainId
6
+
7
+ # Native ETH sentinel address (used by protocols to represent native gas token)
8
+ NATIVE_TOKEN = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
9
+
10
+ # WETH addresses per chain
11
+ WETH: dict[int, str] = {
12
+ ChainId.ETHEREUM: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
13
+ ChainId.BASE: "0x4200000000000000000000000000000000000006",
14
+ ChainId.ARBITRUM: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
15
+ ChainId.POLYGON: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", # WMATIC
16
+ }
17
+
18
+ # USDC addresses per chain
19
+ USDC: dict[int, str] = {
20
+ ChainId.ETHEREUM: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
21
+ ChainId.BASE: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
22
+ ChainId.ARBITRUM: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
23
+ ChainId.POLYGON: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
24
+ }
25
+
26
+ # USDT addresses per chain
27
+ USDT: dict[int, str] = {
28
+ ChainId.ETHEREUM: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
29
+ ChainId.ARBITRUM: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
30
+ ChainId.POLYGON: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
31
+ }
32
+
33
+ # DAI addresses per chain
34
+ DAI: dict[int, str] = {
35
+ ChainId.ETHEREUM: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
36
+ ChainId.BASE: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
37
+ ChainId.ARBITRUM: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
38
+ ChainId.POLYGON: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
39
+ }
40
+
41
+ # Multicall3 — same address on all supported chains
42
+ MULTICALL3 = "0xcA11bde05977b3631167028862bE2a173976CA11"
43
+
44
+ # ERC-20 minimal ABI for balance/approve/transfer
45
+ ERC20_ABI = [
46
+ {
47
+ "constant": True,
48
+ "inputs": [{"name": "account", "type": "address"}],
49
+ "name": "balanceOf",
50
+ "outputs": [{"name": "", "type": "uint256"}],
51
+ "type": "function",
52
+ },
53
+ {
54
+ "constant": True,
55
+ "inputs": [],
56
+ "name": "decimals",
57
+ "outputs": [{"name": "", "type": "uint8"}],
58
+ "type": "function",
59
+ },
60
+ {
61
+ "constant": True,
62
+ "inputs": [],
63
+ "name": "symbol",
64
+ "outputs": [{"name": "", "type": "string"}],
65
+ "type": "function",
66
+ },
67
+ {
68
+ "constant": True,
69
+ "inputs": [],
70
+ "name": "name",
71
+ "outputs": [{"name": "", "type": "string"}],
72
+ "type": "function",
73
+ },
74
+ {
75
+ "constant": True,
76
+ "inputs": [
77
+ {"name": "owner", "type": "address"},
78
+ {"name": "spender", "type": "address"},
79
+ ],
80
+ "name": "allowance",
81
+ "outputs": [{"name": "", "type": "uint256"}],
82
+ "type": "function",
83
+ },
84
+ {
85
+ "constant": False,
86
+ "inputs": [
87
+ {"name": "spender", "type": "address"},
88
+ {"name": "amount", "type": "uint256"},
89
+ ],
90
+ "name": "approve",
91
+ "outputs": [{"name": "", "type": "bool"}],
92
+ "type": "function",
93
+ },
94
+ {
95
+ "constant": False,
96
+ "inputs": [
97
+ {"name": "to", "type": "address"},
98
+ {"name": "amount", "type": "uint256"},
99
+ ],
100
+ "name": "transfer",
101
+ "outputs": [{"name": "", "type": "bool"}],
102
+ "type": "function",
103
+ },
104
+ {
105
+ "constant": False,
106
+ "inputs": [
107
+ {"name": "from", "type": "address"},
108
+ {"name": "to", "type": "address"},
109
+ {"name": "amount", "type": "uint256"},
110
+ ],
111
+ "name": "transferFrom",
112
+ "outputs": [{"name": "", "type": "bool"}],
113
+ "type": "function",
114
+ },
115
+ {
116
+ "constant": True,
117
+ "inputs": [],
118
+ "name": "totalSupply",
119
+ "outputs": [{"name": "", "type": "uint256"}],
120
+ "type": "function",
121
+ },
122
+ ]