From 58eb62ba316a2d688235ce5b36b9d728eea1a9fd Mon Sep 17 00:00:00 2001 From: Teknium Date: Tue, 15 Sep 2026 15:51:08 -0700 Subject: [PATCH] fix: lazy-import eth_abi and declare python_dependencies so hermes plugins validate passes `hermes plugins validate` fails at HEAD with `capability probe: import failed: No module named 'eth_abi'` because clawmes/delegation/encoding.py imports eth_abi/eth_utils at module level and is reached from `import clawmes` (commands.delegation -> compiler). Hermes never auto-installs plugin dependencies and the probe imports the plugin bare, so the whole plugin failed to load wherever eth-abi is absent. - encoding.py: thin abi_encode()/keccak() wrappers import lazily; call sites unchanged. SEL_* selectors become the literal precomputed values (each == selector(sig); pinned by tests/delegation/test_encoding.py). - plugin.yaml: declare python_dependencies (mirrors pyproject + eth-abi). tests/delegation/test_encoding.py + test_compiler.py: 53 passed. --- clawmes/delegation/encoding.py | 36 ++++++++++++++++++++++++---------- plugin.yaml | 20 +++++++++++++++++++ 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/clawmes/delegation/encoding.py b/clawmes/delegation/encoding.py index c8308ed..8906022 100644 --- a/clawmes/delegation/encoding.py +++ b/clawmes/delegation/encoding.py @@ -18,11 +18,25 @@ from collections.abc import Sequence -from eth_abi import encode as abi_encode -from eth_utils import keccak - from clawmes.delegation.types import SignedDelegation, UnsignedDelegation + +# eth-abi / eth-utils are imported lazily: Hermes never auto-installs plugin +# dependencies, and this module is reached from ``import clawmes`` (via +# commands.delegation → compiler), so a module-level import would make the +# whole plugin fail to load — and ``hermes plugins validate`` fail — wherever +# the packages are missing. The wrappers keep every call site unchanged. +def abi_encode(types: Sequence[str], values: Sequence[object]) -> bytes: + from eth_abi import encode + + return encode(types, values) + + +def keccak(*args: object, **kwargs: object) -> bytes: + from eth_utils import keccak as _keccak + + return _keccak(*args, **kwargs) + # The Solidity signature of the Delegation tuple (with args) used for the # permission context, disableDelegation, and getDelegationHash. Caveats # carry (enforcer, terms, args) on the wire even though args is excluded @@ -55,15 +69,17 @@ def selector(signature: str) -> str: return "0x" + keccak(text=signature)[:4].hex() -# Precomputed selectors (verified against viem toFunctionSelector). -SEL_REDEEM = selector("redeemDelegations(bytes[],bytes32[],bytes[])") -SEL_DISABLE = selector(f"disableDelegation({_DELEGATION_TUPLE})") -SEL_GET_HASH = selector(f"getDelegationHash({_DELEGATION_TUPLE})") -SEL_DISABLED = selector("disabledDelegations(bytes32)") +# Precomputed selectors (verified against viem toFunctionSelector). Kept as +# literals so importing this module does not need eth-utils; each equals +# ``selector()`` and tests/delegation/test_encoding.py pins them. +SEL_REDEEM = "0xcef6d209" # redeemDelegations(bytes[],bytes32[],bytes[]) +SEL_DISABLE = "0x49934047" # disableDelegation(_DELEGATION_TUPLE) +SEL_GET_HASH = "0x66134607" # getDelegationHash(_DELEGATION_TUPLE) +SEL_DISABLED = "0x2d40d052" # disabledDelegations(bytes32) # Enforcer read selectors (spentMap / callCounts share the same signature). -SEL_SPENT_MAP = selector("spentMap(address,bytes32)") -SEL_CALL_COUNTS = selector("callCounts(address,bytes32)") +SEL_SPENT_MAP = "0x9dd5d9ab" # spentMap(address,bytes32) +SEL_CALL_COUNTS = "0x19054d89" # callCounts(address,bytes32) # ─── caveat terms encoders ────────────────────────────────────────────── diff --git a/plugin.yaml b/plugin.yaml index a21c0cd..c47386a 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -4,6 +4,26 @@ description: Hermes Agent for crypto. Wallet, swaps, DeFi, launches, automation. author: Clawnch kind: standalone +# Declared for `hermes plugins validate` / install hints; Hermes never +# auto-installs these. Mirrors [project].dependencies in pyproject.toml +# (plus eth-abi, which clawmes imports directly). +python_dependencies: + - "web3>=7.0.0,<8" + - "eth-account>=0.13.0,<1" + - "eth-abi>=5.0.0,<6" + - "eth-utils>=4.0.0,<6" + - "mnemonic>=0.21,<1" + - "pycryptodome>=3.20.0,<4" + - "keyring>=25.0.0,<26" + - "pydantic>=2.7,<3" + - "httpx>=0.27,<1" + - "tenacity>=8.5,<10" + - "python-dotenv>=1.0,<2" + - "pyyaml>=6.0,<7" + - "rich>=13.7,<15" + - "typing_extensions>=4.12,<5" + - "qrcode>=8.0,<9" + # Informational — the actual surface is registered programmatically in # register(ctx). This list is the source of truth for what users will see # in `hermes plugins list`, so it must match what register_all() actually