feat(skills): unified EVM multi-chain skill (salvages #2010 + folds in base/) - #25291
feat(skills): unified EVM multi-chain skill (salvages #2010 + folds in base/)#25291ethernet8023 wants to merge 2 commits into
Conversation
Adds a comprehensive EVM blockchain skill with 14 commands: - stats, wallet, tx, token, activity, gas, price (core queries) - compare: gas + prices across all 8 chains simultaneously - whale: scan recent blocks for large transfers (configurable min USD) - multichain: scan same wallet across all 8 chains in parallel - allowance: check dangerous ERC-20 approvals (Permit2, Uniswap, 1inch...) - decode: decode tx input data via 4byte.directory - ens: resolve ENS names <-> addresses (bidirectional) - contract: inspect contracts (proxy detection, ERC-20/721, bytecode size) Chains: Ethereum, BNB Chain, Base, Arbitrum One, Polygon, Optimism, Avalanche, zkSync Era Zero external dependencies. Python stdlib only (urllib, json, argparse, threading). Co-authored-by: Mibayy <mibay@clawhub.io>
…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.
🚨 CRITICAL Supply Chain Risk DetectedThis PR contains a pattern that has been used in real supply chain attacks. A maintainer must review the flagged code carefully before merging. 🚨 CRITICAL: Install-hook file added or modifiedThese files can execute code during package installation or interpreter startup. Files: Scanner only fires on high-signal indicators: .pth files, base64+exec/eval combos, subprocess with encoded commands, or install-hook files. Low-signal warnings were removed intentionally — if you're seeing this comment, the finding is worth inspecting. |
🔎 Lint report:
|
| Rule | Count |
|---|---|
invalid-argument-type |
7 |
First entries
run_agent.py:13588: [invalid-argument-type] invalid-argument-type: Argument to function `_is_oauth_token` is incorrect: Expected `str`, found `str | dict[Unknown, Unknown] | Any | ... omitted 3 union elements`
run_agent.py:13591: [invalid-argument-type] invalid-argument-type: Argument to function `len` is incorrect: Expected `Sized`, found `(str & ~AlwaysFalsy) | (dict[Unknown, Unknown] & ~AlwaysFalsy) | (Any & ~AlwaysFalsy) | ... omitted 3 union elements`
run_agent.py:12345: [invalid-argument-type] invalid-argument-type: Argument to function `apply_anthropic_cache_control` is incorrect: Expected `bool`, found `int | str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | dict[Unknown, Unknown]`
run_agent.py:8980: [invalid-argument-type] invalid-argument-type: Argument to function `build_anthropic_client` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:7391: [invalid-argument-type] invalid-argument-type: Argument to function `build_anthropic_client` is incorrect: Expected `str`, found `str | dict[Unknown, Unknown] | Any | ... omitted 3 union elements`
run_agent.py:9174: [invalid-argument-type] invalid-argument-type: Argument to function `get_transport` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
run_agent.py:8897: [invalid-argument-type] invalid-argument-type: Argument to bound method `ContextCompressor.update_model` is incorrect: Expected `int`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | int | dict[Unknown, Unknown]`
✅ Fixed issues (44):
| Rule | Count |
|---|---|
unresolved-attribute |
26 |
invalid-argument-type |
11 |
invalid-assignment |
6 |
invalid-parameter-default |
1 |
First entries
run_agent.py:8981: [invalid-argument-type] invalid-argument-type: Argument to function `get_provider_request_timeout` is incorrect: Expected `str | None`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | ... omitted 3 union elements`
run_agent.py:14850: [unresolved-attribute] unresolved-attribute: Attribute `last_prompt_tokens` is not defined on `None` in union `None | Unknown | ContextCompressor`
cli.py:8725: [unresolved-attribute] unresolved-attribute: Attribute `context_length` is not defined on `None` in union `None | Unknown | ContextCompressor`
tests/run_agent/test_compressor_fallback_update.py:72: [unresolved-attribute] unresolved-attribute: Attribute `threshold_percent` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:8626: [unresolved-attribute] unresolved-attribute: Attribute `strip` is not defined on `dict[Unknown | str, Unknown | str | dict[str, str]] & ~AlwaysFalsy`, `int & ~AlwaysFalsy`, `dict[Unknown, Unknown] & ~AlwaysFalsy` in union `(str & ~AlwaysFalsy) | (Unknown & ~AlwaysFalsy) | (dict[Unknown | str, Unknown | str | dict[str, str]] & ~AlwaysFalsy) | ... omitted 4 union elements`
run_agent.py:12345: [invalid-argument-type] invalid-argument-type: Argument to function `apply_anthropic_cache_control` is incorrect: Expected `bool`, found `int | str | Unknown | ... omitted 3 union elements`
cli.py:8727: [unresolved-attribute] unresolved-attribute: Attribute `compression_count` is not defined on `None` in union `None | Unknown | ContextCompressor`
tests/run_agent/test_compressor_fallback_update.py:71: [unresolved-attribute] unresolved-attribute: Attribute `context_length` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:10401: [invalid-assignment] invalid-assignment: Object of type `int` is not assignable to attribute `last_prompt_tokens` on type `None | Unknown | ContextCompressor`
run_agent.py:13591: [invalid-argument-type] invalid-argument-type: Argument to function `len` is incorrect: Expected `Sized`, found `(str & ~AlwaysFalsy) | (dict[Unknown | str, Unknown | str | dict[str, str]] & ~AlwaysFalsy) | (Any & ~AlwaysFalsy) | ... omitted 4 union elements`
run_agent.py:3279: [invalid-assignment] invalid-assignment: Object of type `int | float` is not assignable to attribute `threshold_percent` on type `None | Unknown | ContextCompressor`
run_agent.py:8897: [invalid-argument-type] invalid-argument-type: Argument to bound method `ContextCompressor.update_model` is incorrect: Expected `int`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | ... omitted 3 union elements`
run_agent.py:13051: [unresolved-attribute] unresolved-attribute: Attribute `update_from_response` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:9174: [invalid-argument-type] invalid-argument-type: Argument to function `get_transport` is incorrect: Expected `str`, found `str | Unknown | dict[Unknown | str, Unknown | str | dict[str, str]] | ... omitted 3 union elements`
run_agent.py:13588: [invalid-argument-type] invalid-argument-type: Argument to function `_is_oauth_token` is incorrect: Expected `str`, found `str | dict[Unknown | str, Unknown | str | dict[str, str]] | Any | ... omitted 4 union elements`
run_agent.py:7391: [invalid-argument-type] invalid-argument-type: Argument to function `build_anthropic_client` is incorrect: Expected `str`, found `str | dict[Unknown | str, Unknown | str | dict[str, str]] | Any | ... omitted 4 union elements`
optional-skills/blockchain/base/scripts/base_client.py:99: [invalid-parameter-default] invalid-parameter-default: Default value of type `None` is not assignable to annotated parameter type `list[Unknown]`
tests/run_agent/test_switch_model_context.py:49: [unresolved-attribute] unresolved-attribute: Attribute `context_length` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:11965: [unresolved-attribute] unresolved-attribute: Attribute `protect_first_n` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:13062: [invalid-assignment] invalid-assignment: Object of type `Literal[False]` is not assignable to attribute `_context_probe_persistable` on type `None | Unknown | ContextCompressor`
run_agent.py:13960: [unresolved-attribute] unresolved-attribute: Attribute `context_length` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:11254: [unresolved-attribute] unresolved-attribute: Attribute `handle_tool_call` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:14037: [unresolved-attribute] unresolved-attribute: Attribute `update_model` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:10382: [unresolved-attribute] unresolved-attribute: Attribute `compression_count` is not defined on `None` in union `None | Unknown | ContextCompressor`
cli.py:8724: [unresolved-attribute] unresolved-attribute: Attribute `last_prompt_tokens` is not defined on `None` in union `None | Unknown | ContextCompressor`
... and 19 more
Unchanged: 4342 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
|
Merged via #25299. Your salvage was correct in intent — folding base/ into evm/ and fixing the two latent bugs in #2010 was the right call. The PR couldn't merge directly because Mibayy's underlying commit predated the video_generation subsystem on main, so a direct merge would have silently deleted 15 video_gen production files. Both your commit (e3fc081) and Mibayy's (aa1e2ed) were cherry-picked onto current main with authorship preserved. Polish commit (66c7096) tightened the description to ≤60 chars, added platforms gating, and credited all three contributors (you + @Mibayy + @youssefea) plus Hermes Agent. Thanks! |
Summary
Salvages closed PR #2010 (Mibayy's EVM multi-chain skill) and folds the existing
optional-skills/blockchain/base/skill (youssefea) into it. Net result: one unified EVM skill covering 8 chains with 14 commands instead of two overlapping skills.Branch retains Mibayy's original commit on top of which a single salvage commit applies the merge + fixes (see
git log).Why merge instead of just landing #2010
#2010 already covered Base — but only with 3 known tokens (USDC/DAI/WETH), missing the 8 Base-native tokens the standalone
baseskill curated (AERO, DEGEN, TOSHI, BRETT, WELL, cbETH, cbBTC, wstETH, rETH). Shipping both skills would mean:So this PR consolidates:
base/is gone, all its Base-specific value moves intoevm/. Pass--chain baseto get full Base coverage.What I pulled in from
base/KNOWN_TOKENS["base"](now 12 total, up from 3).gascommand only estimates L2 execution cost; actual L2 tx cost includes an L1 calldata posting fee. Documented inSKILL.mdand points at Base's L1 fee oracle (0x420000000000000000000000000000000000000F) for users who need to compute it.rpc_batch. Base's public RPC caps JSON-RPC batches at 10 calls — adding more known tokens immediately tripped that limit and brokewallet --chain basewithlist index out of range. Ported the chunking pattern frombase/_rpc_batch_chunk. Now auto-chunks atbatch_limit=10and preserves order via JSON-RPCid.Latent bugs in #2010 found + fixed while smoke-testing
cmd_multichain+cmd_allowanceunpacking bug. Both functions iteratedKNOWN_TOKENS[chain]withfor contract, (symbol, _name) in known.items()— but the dict shape is{symbol: contract_str}, not{addr: (sym, name)}. This raisedtoo many values to unpack (expected 2)on every chain where the user actually held a token. Replaced withfor symbol, contract in known.items(). (Original PR likely never noticed because the 3 Base tokens it shipped all returned 0 balance for the test addresses.)No input validation on addresses / tx hashes. Added
is_valid_address/is_valid_txhash/require_address/require_txhashhelpers and wired them into the 8 commands that take address/hash args. Fails fast withexit 2and a clear error instead of burning an RPC round-trip on garbage input. EIP-55 checksum casing is intentionally not enforced (RPC endpoints accept any case).Smoke tests (live mainnet)
Files
On the Orb code review on #2010
Worth a heads-up: the Orb GLM-4.7 bot review on #2010 claimed the PR also refactored the approval system, removed
account_usagemonitoring fromgateway/run.py, and guttedtest_approve_deny_commands.py. None of that was actually in the diff — #2010 only touched the 2 new files underoptional-skills/blockchain/evm/. The bot hallucinated entire "critical" findings about files the PR never touched. Most of its review can be safely ignored; the only legitimate nits were missing input validation (fixed here) and single-endpoint dependencies for ENS/4byte (documented in pitfalls).Credit
optional-skills/blockchain/evm/— original work by @Mibayy in PR feat: add EVM multi-chain skill (8 chains, 14 commands) #2010 (commit2c78b34a5preserved on this branch).optional-skills/blockchain/base/— original work by @youssefea; its Base-specific token list and L1-fee documentation are now part of the unified EVM skill.Closes / Supersedes