Closes the two integration stubs and one broken import that block
hanzo_agents.extensions.web3 from doing real threshold-signed work:
1. New `mpc_client.py` — pydantic-typed `MpcClient` with `keygen`,
`sign`, `verify`. HTTP-only against the `hanzoai/mpc` HTTP bridge
(`POST /v1/{keygen,sign,verify}`). Configurable endpoint, timeout,
retries, parties/threshold, optional bearer auth. Retries 5xx with
exponential backoff; 4xx fail fast.
2. `web3_agent.verify_payment` — actually checks the chain. Pulls the
tx receipt + tx, validates status==1, value >= expected (in wei),
recipient == self.wallet.address, and >= configurable confirmations.
Optional MPC cross-verification of the block-hash signature so a
single party cannot spoof an ack. Mock-wallet path preserves legacy
"trust the caller" behavior so existing tests keep passing.
3. `wallet.verify_signature` — uses `eth_account.Account.recover_message`
with the EIP-191 prefix (matches `sign_message`). Returns False on
malformed input rather than raising.
4. Fix `from .tee import …` → `from ..tee import …`. The `tee` package
lives at `hanzo_agents.extensions.tee`, not under `extensions.web3`,
so `from hanzo_agents.extensions.web3 import Web3Agent` currently
raises `ModuleNotFoundError`.
Tests: `tests/test_web3_mpc_client.py` mocks `aiohttp.ClientSession`
and covers happy path for keygen/sign/verify, retry-on-5xx, fail-fast
on 4xx, hex validation, bearer-token header.
Docs: `docs/web3-mpc-integration.md` shows enable + wire protocol +
failure modes.
No new top-level deps. `aiohttp`, `pydantic`, `eth-account` are
already declared in `pyproject.toml`.
Total ~430 LOC across 1 new module, 2 patches, 1 test file, 1 doc.
2.8 KiB
Pointing a Web3 agent at a real MPC cluster
The Web3Agent ships with an optional integration to a hanzoai/mpc
(or luxfi/mpc) threshold-signing cluster via an HTTP bridge. When
enabled, payment-confirmation signatures are cross-verified against
the cluster before the payment is recorded — so a single malicious
party cannot spoof an ack.
Enable
from hanzo_agents.extensions.web3 import Web3Agent, Web3AgentConfig
from hanzo_agents.extensions.web3.mpc_client import MpcConfig
agent = Web3Agent(
name="paid-worker",
description="Does paid work, verifies via MPC",
web3_config=Web3AgentConfig(
wallet_enabled=True,
wallet_config=..., # your WalletConfig
mpc_enabled=True,
mpc_config=MpcConfig(
endpoint="https://mpc.example.com", # HTTP bridge
timeout_seconds=10.0,
max_retries=3,
parties=3,
threshold=2,
api_key="...", # if cluster requires bearer auth
),
payment_min_confirmations=1, # raise to 12+ for mainnet
),
)
mpc_enabled=False (the default) keeps the legacy chain-only verification
path. mpc_config=None with mpc_enabled=True falls back to a
localhost:8080 bridge — useful for development.
Wire protocol the bridge must speak
The client expects the HTTP bridge documented in hanzoai/mpc
docs/BRIDGE-INTEGRATION.md:
| Verb · path | Request | Response |
|---|---|---|
POST /v1/keygen |
{"parties": n, "threshold": t} |
{"public_key": "<hex>", "key_id": "<id>"} |
POST /v1/sign |
{"key_id": "<id>", "digest": "<hex>"} |
{"signature": "<hex>", "scheme": "ecdsa"} |
POST /v1/verify |
{"signature": "<hex>", "digest": "<hex>", "public_key": "<hex>"} |
`{"valid": true |
Failure modes
The client retries 5xx and network failures with exponential backoff
(backoff_seconds * 2^attempt), respecting max_retries. 4xx responses
are not retried — a malformed request will not be re-sent.
Web3Agent.verify_payment is conservative on MPC outage: if the bridge
is unreachable but the chain-side checks pass (status, value, recipient,
confirmations), the payment is still credited. Adjust by overriding
verify_payment in a subclass if your threat model requires the MPC
verify to be load-bearing.
Running the test suite
pytest sdk/python/tests/test_web3_mpc_client.py
The tests mock aiohttp.ClientSession, so no live bridge is needed.