mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-25 05:52:34 +00:00
feat(skills): merge blockchain/base into blockchain/evm; salvage PR #2010
Salvages the closed PR #2010 (Mibayy's EVM multi-chain skill) and folds the existing optional-skills/blockchain/base/ skill into it, so we ship one unified EVM skill instead of two overlapping ones. Pulled in from base/: - 8 missing Base-specific tokens (AERO, DEGEN, TOSHI, BRETT, WELL, cbETH, cbBTC, wstETH, rETH) added to KNOWN_TOKENS['base'] — base/ had 11, evm/ only had 3 (USDC/DAI/WETH). - L1 data-fee pitfall note for rollups (Base, Arbitrum, Optimism, zkSync). - Batch-size chunking in rpc_batch (Base RPC caps batches at 10 calls per JSON-RPC request; adding more known tokens tripped that limit and broke 'wallet --chain base' with a 'list index out of range' error). Ported the chunking pattern from base/_rpc_batch_chunk. Latent bugs found and fixed while smoke-testing the merge: - cmd_multichain and cmd_allowance both iterated KNOWN_TOKENS[chain] with 'for contract, (symbol, _name) in known.items()' — but the dict shape is {symbol: contract_str}, not {addr: (sym, name)}. This raised 'too many values to unpack (expected 2)' on every non-zero balance. Now iterates as 'for symbol, contract in known.items()'. - Input validation: added is_valid_address / is_valid_txhash / require_address / require_txhash helpers and wired them into cmd_wallet, cmd_tx, cmd_token, cmd_activity, cmd_allowance, cmd_decode, cmd_contract, cmd_multichain. Fails fast with exit 2 on malformed input instead of burning an RPC round-trip on garbage. Documentation: - SKILL.md now flags that this skill supersedes optional-skills/blockchain/base. - Pitfalls expanded for ENS (single-endpoint dependency on ensideas.com), tx decoding (single-endpoint dependency on 4byte.directory), and rollup L1 fees. - Regenerated website/docs/user-guide/skills/optional/blockchain/ blockchain-evm.md and removed the old blockchain-base.md page; catalog updated. Removed: - optional-skills/blockchain/base/SKILL.md - optional-skills/blockchain/base/scripts/base_client.py - website/docs/user-guide/skills/optional/blockchain/blockchain-base.md Smoke-tested live against Base mainnet: stats, price, token, wallet (vitalik.eth — 3.12 ETH + 13.88 USDC + 4.23 DAI + 0.06 WETH on Base) and allowance (ethereum, 7 unlimited approvals to Uniswap/Permit2). Original PR #2010 author: Mibayy. Original base/ skill author: youssefea.
This commit is contained in:
parent
aa1e2edd35
commit
e3fc081499
7 changed files with 354 additions and 1521 deletions
|
|
@ -1,232 +0,0 @@
|
|||
---
|
||||
name: base
|
||||
description: Query Base (Ethereum L2) blockchain data with USD pricing — wallet balances, token info, transaction details, gas analysis, contract inspection, whale detection, and live network stats. Uses Base RPC + CoinGecko. No API key required.
|
||||
version: 0.1.0
|
||||
author: youssefea
|
||||
license: MIT
|
||||
platforms: [linux, macos, windows]
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [Base, Blockchain, Crypto, Web3, RPC, DeFi, EVM, L2, Ethereum]
|
||||
related_skills: []
|
||||
---
|
||||
|
||||
# Base Blockchain Skill
|
||||
|
||||
Query Base (Ethereum L2) on-chain data enriched with USD pricing via CoinGecko.
|
||||
8 commands: wallet portfolio, token info, transactions, gas analysis,
|
||||
contract inspection, whale detection, network stats, and price lookup.
|
||||
|
||||
No API key needed. Uses only Python standard library (urllib, json, argparse).
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
- User asks for a Base wallet balance, token holdings, or portfolio value
|
||||
- User wants to inspect a specific transaction by hash
|
||||
- User wants ERC-20 token metadata, price, supply, or market cap
|
||||
- User wants to understand Base gas costs and L1 data fees
|
||||
- User wants to inspect a contract (ERC type detection, proxy resolution)
|
||||
- User wants to find large ETH transfers (whale detection)
|
||||
- User wants Base network health, gas price, or ETH price
|
||||
- User asks "what's the price of USDC/AERO/DEGEN/ETH?"
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The helper script uses only Python standard library (urllib, json, argparse).
|
||||
No external packages required.
|
||||
|
||||
Pricing data comes from CoinGecko's free API (no key needed, rate-limited
|
||||
to ~10-30 requests/minute). For faster lookups, use `--no-prices` flag.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
RPC endpoint (default): https://mainnet.base.org
|
||||
Override: export BASE_RPC_URL=https://your-private-rpc.com
|
||||
|
||||
Helper script path: ~/.hermes/skills/blockchain/base/scripts/base_client.py
|
||||
|
||||
```
|
||||
python3 base_client.py wallet <address> [--limit N] [--all] [--no-prices]
|
||||
python3 base_client.py tx <hash>
|
||||
python3 base_client.py token <contract_address>
|
||||
python3 base_client.py gas
|
||||
python3 base_client.py contract <address>
|
||||
python3 base_client.py whales [--min-eth N]
|
||||
python3 base_client.py stats
|
||||
python3 base_client.py price <contract_address_or_symbol>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Procedure
|
||||
|
||||
### 0. Setup Check
|
||||
|
||||
```bash
|
||||
python3 --version
|
||||
|
||||
# Optional: set a private RPC for better rate limits
|
||||
export BASE_RPC_URL="https://mainnet.base.org"
|
||||
|
||||
# Confirm connectivity
|
||||
python3 ~/.hermes/skills/blockchain/base/scripts/base_client.py stats
|
||||
```
|
||||
|
||||
### 1. Wallet Portfolio
|
||||
|
||||
Get ETH balance and ERC-20 token holdings with USD values.
|
||||
Checks ~15 well-known Base tokens (USDC, WETH, AERO, DEGEN, etc.)
|
||||
via on-chain `balanceOf` calls. Tokens sorted by value, dust filtered.
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/base/scripts/base_client.py \
|
||||
wallet 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
||||
```
|
||||
|
||||
Flags:
|
||||
- `--limit N` — show top N tokens (default: 20)
|
||||
- `--all` — show all tokens, no dust filter, no limit
|
||||
- `--no-prices` — skip CoinGecko price lookups (faster, RPC-only)
|
||||
|
||||
Output includes: ETH balance + USD value, token list with prices sorted
|
||||
by value, dust count, total portfolio value in USD.
|
||||
|
||||
Note: Only checks known tokens. Unknown ERC-20s are not discovered.
|
||||
Use the `token` command with a specific contract address for any token.
|
||||
|
||||
### 2. Transaction Details
|
||||
|
||||
Inspect a full transaction by its hash. Shows ETH value transferred,
|
||||
gas used, fee in ETH/USD, status, and decoded ERC-20/ERC-721 transfers.
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/base/scripts/base_client.py \
|
||||
tx 0xabc123...your_tx_hash_here
|
||||
```
|
||||
|
||||
Output: hash, block, from, to, value (ETH + USD), gas price, gas used,
|
||||
fee, status, contract creation address (if any), token transfers.
|
||||
|
||||
### 3. Token Info
|
||||
|
||||
Get ERC-20 token metadata: name, symbol, decimals, total supply, price,
|
||||
market cap, and contract code size.
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/base/scripts/base_client.py \
|
||||
token 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
|
||||
```
|
||||
|
||||
Output: name, symbol, decimals, total supply, price, market cap.
|
||||
Reads name/symbol/decimals directly from the contract via eth_call.
|
||||
|
||||
### 4. Gas Analysis
|
||||
|
||||
Detailed gas analysis with cost estimates for common operations.
|
||||
Shows current gas price, base fee trends over 10 blocks, block
|
||||
utilization, and estimated costs for ETH transfers, ERC-20 transfers,
|
||||
and swaps.
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/base/scripts/base_client.py gas
|
||||
```
|
||||
|
||||
Output: current gas price, base fee, block utilization, 10-block trend,
|
||||
cost estimates in ETH and USD.
|
||||
|
||||
Note: Base is an L2 — actual transaction costs include an L1 data
|
||||
posting fee that depends on calldata size and L1 gas prices. The
|
||||
estimates shown are for L2 execution only.
|
||||
|
||||
### 5. Contract Inspection
|
||||
|
||||
Inspect an address: determine if it's an EOA or contract, detect
|
||||
ERC-20/ERC-721/ERC-1155 interfaces, resolve EIP-1967 proxy
|
||||
implementation addresses.
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/base/scripts/base_client.py \
|
||||
contract 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
|
||||
```
|
||||
|
||||
Output: is_contract, code size, ETH balance, detected interfaces
|
||||
(ERC-20, ERC-721, ERC-1155), ERC-20 metadata, proxy implementation
|
||||
address.
|
||||
|
||||
### 6. Whale Detector
|
||||
|
||||
Scan the most recent block for large ETH transfers with USD values.
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/base/scripts/base_client.py \
|
||||
whales --min-eth 1.0
|
||||
```
|
||||
|
||||
Note: scans the latest block only — point-in-time snapshot, not historical.
|
||||
Default threshold is 1.0 ETH (lower than Solana's default since ETH
|
||||
values are higher).
|
||||
|
||||
### 7. Network Stats
|
||||
|
||||
Live Base network health: latest block, chain ID, gas price, base fee,
|
||||
block utilization, transaction count, and ETH price.
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/base/scripts/base_client.py stats
|
||||
```
|
||||
|
||||
### 8. Price Lookup
|
||||
|
||||
Quick price check for any token by contract address or known symbol.
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/base/scripts/base_client.py price ETH
|
||||
python3 ~/.hermes/skills/blockchain/base/scripts/base_client.py price USDC
|
||||
python3 ~/.hermes/skills/blockchain/base/scripts/base_client.py price AERO
|
||||
python3 ~/.hermes/skills/blockchain/base/scripts/base_client.py price DEGEN
|
||||
python3 ~/.hermes/skills/blockchain/base/scripts/base_client.py price 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
|
||||
```
|
||||
|
||||
Known symbols: ETH, WETH, USDC, cbETH, AERO, DEGEN, TOSHI, BRETT,
|
||||
WELL, wstETH, rETH, cbBTC.
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **CoinGecko rate-limits** — free tier allows ~10-30 requests/minute.
|
||||
Price lookups use 1 request per token. Use `--no-prices` for speed.
|
||||
- **Public RPC rate-limits** — Base's public RPC limits requests.
|
||||
For production use, set BASE_RPC_URL to a private endpoint
|
||||
(Alchemy, QuickNode, Infura).
|
||||
- **Wallet shows known tokens only** — unlike Solana, EVM chains have no
|
||||
built-in "get all tokens" RPC. The wallet command checks ~15 popular
|
||||
Base tokens via `balanceOf`. Unknown ERC-20s won't appear. Use the
|
||||
`token` command for any specific contract.
|
||||
- **Token names read from contract** — if a contract doesn't implement
|
||||
`name()` or `symbol()`, these fields may be empty. Known tokens have
|
||||
hardcoded labels as fallback.
|
||||
- **Gas estimates are L2 only** — Base transaction costs include an L1
|
||||
data posting fee (depends on calldata size and L1 gas prices). The gas
|
||||
command estimates L2 execution cost only.
|
||||
- **Whale detector scans latest block only** — not historical. Results
|
||||
vary by the moment you query. Default threshold is 1.0 ETH.
|
||||
- **Proxy detection** — only EIP-1967 proxies are detected. Other proxy
|
||||
patterns (EIP-1167 minimal proxy, custom storage slots) are not checked.
|
||||
- **Retry on 429** — both RPC and CoinGecko calls retry up to 2 times
|
||||
with exponential backoff on rate-limit errors.
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
# Should print Base chain ID (8453), latest block, gas price, and ETH price
|
||||
python3 ~/.hermes/skills/blockchain/base/scripts/base_client.py stats
|
||||
```
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -25,6 +25,11 @@ Optimism, Avalanche (C-Chain), zkSync Era.
|
|||
No API key needed. Zero external dependencies — Python standard library only
|
||||
(urllib, json, argparse, threading).
|
||||
|
||||
> **Supersedes the standalone `base` skill.** Base-specific tokens (AERO, DEGEN,
|
||||
> TOSHI, BRETT, WELL, cbETH, cbBTC, wstETH, rETH) and all Base RPC functionality
|
||||
> previously living under `optional-skills/blockchain/base/` have been folded
|
||||
> into this skill. Pass `--chain base` to any command for Base coverage.
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
|
@ -188,8 +193,10 @@ Shows gwei price + USD cost for: transfer, ERC-20 transfer, approve, swap, NFT m
|
|||
- `wallet` and `allowance` only check known token list (~30 tokens per chain). Use a block explorer for complete token discovery.
|
||||
- `activity` scans recent blocks only (max 200). For full history, use Etherscan API.
|
||||
- `multichain` runs 8 parallel threads — can trigger rate limits on public RPCs.
|
||||
- ENS requires internet access to ensideas.com.
|
||||
- Tx decode requires internet access to 4byte.directory.
|
||||
- ENS resolution depends on a single public endpoint (ensideas.com / ens.vitalik.ca) with no fallback. If that endpoint is down, `ens` will fail — re-run later or use a block explorer.
|
||||
- Tx decoding depends on a single public endpoint (4byte.directory) with no fallback. Selectors not in their database show up as `unknown`.
|
||||
- **L2 gas estimates are L2-execution only.** On rollups like Base, Arbitrum, Optimism, and zkSync, the actual transaction cost also includes an L1 data-posting fee that depends on calldata size and current L1 gas prices. The `gas` command does not estimate that L1 component. For Base specifically, see the network's L1 fee oracle (contract `0x420000000000000000000000000000000000000F`).
|
||||
- Address / tx-hash inputs are validated for 0x-prefix + correct length + hex, but EIP-55 checksum casing is **not** enforced (RPC endpoints accept any-case hex).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -137,9 +137,21 @@ KNOWN_TOKENS: Dict[str, Dict[str, str]] = {
|
|||
"DOGE": "0xbA2aE424d960c26247Dd6c32edC70B295c744C43",
|
||||
},
|
||||
"base": {
|
||||
"USDC": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
||||
"DAI": "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
|
||||
"WETH": "0x4200000000000000000000000000000000000006",
|
||||
# Stables + wrapped
|
||||
"USDC": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
||||
"DAI": "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
|
||||
"WETH": "0x4200000000000000000000000000000000000006",
|
||||
# Liquid-staked ETH variants
|
||||
"cbETH": "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cF0DEc22",
|
||||
"wstETH": "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452",
|
||||
"rETH": "0xB6fe221Fe9EeF5aBa221c348bA20A1Bf5e73624c",
|
||||
"cbBTC": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
|
||||
# Base-native DeFi + meme tokens (carried over from the standalone base/ skill)
|
||||
"AERO": "0x940181a94A35A4569E4529A3CDfB74e38FD98631",
|
||||
"DEGEN": "0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed",
|
||||
"TOSHI": "0xAC1Bd2486aAf3B5C0fc3Fd868558b082a531B2B4",
|
||||
"BRETT": "0x532f27101965dd16442E59d40670FaF5eBB142E4",
|
||||
"WELL": "0xA88594D404727625A9437C3f886C7643872296AE",
|
||||
},
|
||||
"arbitrum": {
|
||||
"USDC": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
||||
|
|
@ -226,9 +238,73 @@ def hex_to_int(h: str) -> int:
|
|||
return 0
|
||||
return int(h, 16)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Input validation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def is_valid_address(s: str) -> bool:
|
||||
"""Return True if `s` looks like a 20-byte hex Ethereum address.
|
||||
|
||||
Does NOT validate EIP-55 checksum — RPC endpoints accept any-case hex.
|
||||
Just guards against typos / wrong-length input before we burn an RPC call.
|
||||
"""
|
||||
if not isinstance(s, str):
|
||||
return False
|
||||
if not s.startswith("0x") and not s.startswith("0X"):
|
||||
return False
|
||||
if len(s) != 42:
|
||||
return False
|
||||
try:
|
||||
int(s, 16)
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_valid_txhash(s: str) -> bool:
|
||||
"""Return True if `s` looks like a 32-byte hex transaction hash."""
|
||||
if not isinstance(s, str):
|
||||
return False
|
||||
if not s.startswith("0x") and not s.startswith("0X"):
|
||||
return False
|
||||
if len(s) != 66:
|
||||
return False
|
||||
try:
|
||||
int(s, 16)
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def require_address(s: str, *, field: str = "address") -> str:
|
||||
"""Return `s` lowercased if valid, else exit with an error message.
|
||||
|
||||
Centralizing validation here means every subcommand fails fast on bad input
|
||||
instead of bubbling up an opaque RPC error 30 seconds later.
|
||||
"""
|
||||
if not is_valid_address(s):
|
||||
sys.stderr.write(
|
||||
f"error: invalid {field} {s!r}: expected 0x-prefixed 40-hex-char address\n"
|
||||
)
|
||||
sys.exit(2)
|
||||
return s.lower()
|
||||
|
||||
|
||||
def require_txhash(s: str, *, field: str = "tx hash") -> str:
|
||||
"""Return `s` lowercased if valid, else exit with an error message."""
|
||||
if not is_valid_txhash(s):
|
||||
sys.stderr.write(
|
||||
f"error: invalid {field} {s!r}: expected 0x-prefixed 64-hex-char tx hash\n"
|
||||
)
|
||||
sys.exit(2)
|
||||
return s.lower()
|
||||
|
||||
|
||||
def wei_to_native(wei: int, decimals: int = 18) -> float:
|
||||
return wei / (10 ** decimals)
|
||||
|
||||
|
||||
def gwei_from_wei(wei: int) -> float:
|
||||
return wei / 1e9
|
||||
|
||||
|
|
@ -326,25 +402,36 @@ def rpc_call(chain: str, method: str, params: List[Any], req_id: int = 1) -> Any
|
|||
raise RuntimeError(f"RPC error: {resp['error']}")
|
||||
return resp.get("result")
|
||||
|
||||
def rpc_batch(chain: str, calls: List[Tuple[str, List[Any]]]) -> List[Any]:
|
||||
"""Send a batch of JSON-RPC calls; returns list of results in same order."""
|
||||
def rpc_batch(chain: str, calls: List[Tuple[str, List[Any]]], batch_limit: int = 10) -> List[Any]:
|
||||
"""Send a batch of JSON-RPC calls; returns list of results in same order.
|
||||
|
||||
Auto-chunks at `batch_limit` (default 10) so we stay under per-RPC limits.
|
||||
Base's public RPC caps batches at 10 — exceeding that returns a single error
|
||||
dict instead of a results list, which would mask all our calls.
|
||||
"""
|
||||
url = get_rpc_url(chain)
|
||||
payload = [
|
||||
|
||||
# Build the full payload, preserving order via JSON-RPC `id`
|
||||
items = [
|
||||
{"jsonrpc": "2.0", "id": i, "method": m, "params": p}
|
||||
for i, (m, p) in enumerate(calls)
|
||||
]
|
||||
resp = _http_post(url, payload)
|
||||
if isinstance(resp, list):
|
||||
# Sort by id to preserve order
|
||||
resp_sorted = sorted(resp, key=lambda x: x.get("id", 0))
|
||||
results = []
|
||||
for r in resp_sorted:
|
||||
if "error" in r:
|
||||
results.append(None)
|
||||
else:
|
||||
results.append(r.get("result"))
|
||||
return results
|
||||
return [resp.get("result")]
|
||||
|
||||
out: List[Any] = [None] * len(items)
|
||||
for start in range(0, len(items), batch_limit):
|
||||
chunk = items[start:start + batch_limit]
|
||||
resp = _http_post(url, chunk)
|
||||
if not isinstance(resp, list):
|
||||
# Single error response (e.g. batch-too-large) — leave this chunk as None
|
||||
continue
|
||||
for r in resp:
|
||||
rid = r.get("id")
|
||||
if isinstance(rid, int) and 0 <= rid < len(out):
|
||||
if "error" in r:
|
||||
out[rid] = None
|
||||
else:
|
||||
out[rid] = r.get("result")
|
||||
return out
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ABI encoding helpers (minimal, for ERC-20 calls)
|
||||
|
|
@ -556,7 +643,7 @@ def cmd_stats(args: argparse.Namespace) -> None:
|
|||
|
||||
|
||||
def cmd_wallet(args: argparse.Namespace) -> None:
|
||||
address = args.address
|
||||
address = require_address(args.address)
|
||||
chain = args.chain
|
||||
limit = args.limit
|
||||
no_prices = args.no_prices
|
||||
|
|
@ -633,7 +720,7 @@ def cmd_wallet(args: argparse.Namespace) -> None:
|
|||
|
||||
|
||||
def cmd_tx(args: argparse.Namespace) -> None:
|
||||
tx_hash = args.hash
|
||||
tx_hash = require_txhash(args.hash)
|
||||
chain = args.chain
|
||||
cfg = CHAINS[chain]
|
||||
|
||||
|
|
@ -702,7 +789,7 @@ def cmd_tx(args: argparse.Namespace) -> None:
|
|||
|
||||
|
||||
def cmd_token(args: argparse.Namespace) -> None:
|
||||
contract = args.contract
|
||||
contract = require_address(args.contract, field="contract address")
|
||||
chain = args.chain
|
||||
|
||||
# Batch all ERC-20 metadata calls
|
||||
|
|
@ -744,7 +831,7 @@ def cmd_token(args: argparse.Namespace) -> None:
|
|||
|
||||
|
||||
def cmd_activity(args: argparse.Namespace) -> None:
|
||||
address = args.address
|
||||
address = require_address(args.address)
|
||||
chain = args.chain
|
||||
limit = args.limit
|
||||
cfg = CHAINS[chain]
|
||||
|
|
@ -1000,7 +1087,7 @@ def cmd_multichain(args: argparse.Namespace) -> None:
|
|||
"""Scan same wallet across all 8 chains simultaneously."""
|
||||
import threading
|
||||
|
||||
address = args.address
|
||||
address = require_address(args.address)
|
||||
results: Dict[str, Any] = {}
|
||||
lock = threading.Lock()
|
||||
|
||||
|
|
@ -1019,9 +1106,10 @@ def cmd_multichain(args: argparse.Namespace) -> None:
|
|||
"tokens": [],
|
||||
"total_usd": native_usd or 0.0,
|
||||
}
|
||||
# Check known tokens for this chain
|
||||
# Check known tokens for this chain.
|
||||
# KNOWN_TOKENS[chain] maps {symbol: contract_address}, not {addr: (sym, name)}.
|
||||
known = KNOWN_TOKENS.get(chain, {})
|
||||
for contract, (symbol, _name) in known.items():
|
||||
for symbol, contract in known.items():
|
||||
raw = eth_call_erc20(chain, contract, "balanceOf(address)", address)
|
||||
if not raw or raw == "0x":
|
||||
continue
|
||||
|
|
@ -1067,7 +1155,7 @@ def cmd_multichain(args: argparse.Namespace) -> None:
|
|||
|
||||
def cmd_allowance(args: argparse.Namespace) -> None:
|
||||
"""Check dangerous ERC-20 approvals for a wallet (known spenders)."""
|
||||
address = args.address
|
||||
address = require_address(args.address)
|
||||
chain = args.chain
|
||||
|
||||
# Well-known spender contracts (DEXes, bridges, etc.)
|
||||
|
|
@ -1085,7 +1173,8 @@ def cmd_allowance(args: argparse.Namespace) -> None:
|
|||
known = KNOWN_TOKENS.get(chain, {})
|
||||
approvals = []
|
||||
|
||||
for contract, (symbol, _name) in known.items():
|
||||
# KNOWN_TOKENS[chain] is {symbol: contract_address}, not {addr: (sym, name)}.
|
||||
for symbol, contract in known.items():
|
||||
for spender_addr, spender_name in KNOWN_SPENDERS.items():
|
||||
# allowance(owner, spender) = 0xdd62ed3e
|
||||
owner_pad = address.lower().replace("0x", "").zfill(64)
|
||||
|
|
@ -1127,7 +1216,7 @@ def cmd_allowance(args: argparse.Namespace) -> None:
|
|||
def cmd_decode(args: argparse.Namespace) -> None:
|
||||
"""Decode transaction input data using 4byte.directory."""
|
||||
chain = args.chain
|
||||
tx_hash = args.hash
|
||||
tx_hash = require_txhash(args.hash)
|
||||
|
||||
tx = rpc_call(chain, "eth_getTransactionByHash", [tx_hash])
|
||||
if not tx:
|
||||
|
|
@ -1212,7 +1301,7 @@ def cmd_ens(args: argparse.Namespace) -> None:
|
|||
def cmd_contract(args: argparse.Namespace) -> None:
|
||||
"""Inspect a smart contract: bytecode size, proxy detection, creation info."""
|
||||
chain = args.chain
|
||||
address = args.address
|
||||
address = require_address(args.address)
|
||||
|
||||
# Get bytecode
|
||||
code_hex = rpc_call(chain, "eth_getCode", [address, "latest"])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue