Skip to content

feat(skills): unified EVM multi-chain skill (salvages #2010 + folds in base/) - #25291

Closed
ethernet8023 wants to merge 2 commits into
mainfrom
salvage/evm-skill-merge-base
Closed

feat(skills): unified EVM multi-chain skill (salvages #2010 + folds in base/)#25291
ethernet8023 wants to merge 2 commits into
mainfrom
salvage/evm-skill-merge-base

Conversation

@ethernet8023

@ethernet8023 ethernet8023 commented May 13, 2026

Copy link
Copy Markdown
Collaborator

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 base skill curated (AERO, DEGEN, TOSHI, BRETT, WELL, cbETH, cbBTC, wstETH, rETH). Shipping both skills would mean:

  • Users picking the wrong one for Base get worse Base coverage
  • Two divergent code paths for the same chain
  • Doc duplication

So this PR consolidates: base/ is gone, all its Base-specific value moves into evm/. Pass --chain base to get full Base coverage.

What I pulled in from base/

  • 8 Base-specific tokens added to KNOWN_TOKENS["base"] (now 12 total, up from 3).
  • L1 data-fee pitfall note for rollups (Base / Arbitrum / Optimism / zkSync). The gas command only estimates L2 execution cost; actual L2 tx cost includes an L1 calldata posting fee. Documented in SKILL.md and points at Base's L1 fee oracle (0x420000000000000000000000000000000000000F) for users who need to compute it.
  • Batch-size chunking in rpc_batch. Base's public RPC caps JSON-RPC batches at 10 calls — adding more known tokens immediately tripped that limit and broke wallet --chain base with list index out of range. Ported the chunking pattern from base/_rpc_batch_chunk. Now auto-chunks at batch_limit=10 and preserves order via JSON-RPC id.

Latent bugs in #2010 found + fixed while smoke-testing

  1. cmd_multichain + cmd_allowance unpacking bug. Both functions 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 chain where the user actually held a token. Replaced with for 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.)

  2. No input validation on addresses / tx hashes. Added is_valid_address / is_valid_txhash / require_address / require_txhash helpers and wired them into the 8 commands that take address/hash args. Fails fast with exit 2 and 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)

$ python3 evm_client.py stats --chain base
{ "chain": "base", "block_number": 45962929, "gas_price_gwei": 0.006,
  "native_token": "ETH", "native_price_usd": 2256.53, ... }

$ python3 evm_client.py wallet 0xd8dA...96045 --chain base --no-prices
# 3.12 ETH + 13.88 USDC + 4.23 DAI + 0.06 WETH on Base ✓

$ python3 evm_client.py allowance 0xd8dA...96045 --chain ethereum
# 7 unlimited approvals to Uniswap V3 Router / Permit2 etc, HIGH risk flagged ✓

$ python3 evm_client.py wallet bad-address
error: invalid address 'bad-address': expected 0x-prefixed 40-hex-char address
$ echo $?
2 ✓

Files

 optional-skills/blockchain/base/SKILL.md                              | -232  (deleted)
 optional-skills/blockchain/base/scripts/base_client.py                | -1008 (deleted)
 optional-skills/blockchain/evm/SKILL.md                               |   +11 -5
 optional-skills/blockchain/evm/scripts/evm_client.py                  |   +175 -29
 website/docs/reference/optional-skills-catalog.md                     |   +1 -1
 website/docs/user-guide/skills/optional/blockchain/blockchain-base.md | -249  (deleted)
 website/docs/user-guide/skills/optional/blockchain/blockchain-evm.md  |   +226 (auto-gen)

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_usage monitoring from gateway/run.py, and gutted test_approve_deny_commands.py. None of that was actually in the diff#2010 only touched the 2 new files under optional-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

Closes / Supersedes

Mibayy and others added 2 commits May 13, 2026 19:21
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.
@github-actions

Copy link
Copy Markdown
Contributor

🚨 CRITICAL Supply Chain Risk Detected

This 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 modified

These files can execute code during package installation or interpreter startup.

Files:

hermes_cli/setup.py

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.

@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: salvage/evm-skill-merge-base vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 8270 on HEAD, 8320 on base (✅ -50)

🆕 New issues (7):

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.

@ethernet8023
ethernet8023 requested a review from teknium1 May 13, 2026 23:56
@alt-glitch alt-glitch added type/feature New feature or request tool/skills Skills system (list, view, manage) P3 Low — cosmetic, nice to have labels May 14, 2026
@teknium1

Copy link
Copy Markdown
Contributor

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!

@ethernet8023
ethernet8023 deleted the salvage/evm-skill-merge-base branch May 22, 2026 20:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P3 Low — cosmetic, nice to have tool/skills Skills system (list, view, manage) type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants