Convert EVM calldata to human-readable output using ERC-7730 descriptors
Project description
clearsig
Convert raw EVM calldata to human-readable output using ERC-7730 clear signing descriptors.
Most of this was written by AI and has not been thoroughly vetted. Please use devcontainers and other methods to isolate your environment before running.
What works and what doesn't
| Scenario | Status | Why |
|---|---|---|
| ERC-20 transfer/approve | ✅ | Generic descriptor, works on any token |
| Aave v3 supply/borrow/repay | ✅ | Mainnet, zkSync, Polygon, and more |
Safe execTransaction → single call |
✅ | Inner calldata recursively decoded |
Safe execTransaction → Aave supply |
✅ | Nested: Safe layer + Aave layer both decoded |
zkSync requestL2Transaction (L1→L2) |
❌ | Missing descriptor from registry |
Safe execTransaction → MultiSend |
❌ | Custom packed encoding |
zkSync sendToL1 (L2→L1 governance) |
❌ | Custom packed encoding |
Uniswap Universal Router execute |
❌ | Custom packed encoding |
Why the gaps? The ERC-7730 calldata format recursively decodes inner calls when they are standard ABI (4-byte selector + encoded params). Three different failure modes exist:
- Missing descriptor — the encoding is standard ABI but no one has written a descriptor yet (e.g., zkSync
requestL2Transaction). Fixable by adding a descriptor to the registry. - Custom packed encoding — the bytes use a protocol-specific binary format that doesn't map to ABI types at all (e.g., MultiSend, Uniswap Universal Router). The ERC-7730 schema can't describe these.
See the CLI examples below for what each case looks like.
Setup
# Install
uv sync
# Download the ERC-7730 registry (one-time)
uv run clearsig update
Or set a custom registry path:
export ERC7730_REGISTRY_PATH=/path/to/clear-signing-erc7730-registry
Usage
Python SDK
from clearsig import translate, update_registry
# Download registry (one-time, saves to ~/.clearsig/registry)
update_registry()
# Translate calldata
result = translate(
"0x617ba037000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
"00000000000000000000000000000000000000000000000000000000000f4240"
"000000000000000000000000000000000000000000000000000000000000dead"
"0000000000000000000000000000000000000000000000000000000000000000",
to="0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
chain_id=1,
)
print(result.intent) # "Supply"
print(result.entity) # "Aave"
for field in result.fields:
print(f" {field.label}: {field.value}")
You can also load the registry once and reuse it:
from clearsig import Registry, translate_with_registry
registry = Registry.from_path("/path/to/clear-signing-erc7730-registry")
# or
registry = Registry.load() # uses env var or ~/.clearsig/registry
result = translate_with_registry(registry, calldata, to="0x...", chain_id=1)
Descriptor hashing
For auditor attestations under ERC-8176, compute the descriptor hash as the keccak256 of the RFC 8785 JCS-canonicalized JSON:
from pathlib import Path
from clearsig import descriptor_hash_hex
print(descriptor_hash_hex(Path("registry/lido/calldata-stETH.json")))
# 0x00a23d24e410f0cbf3836058db29177d7d249a5f3deedbb518dacbb841f73de9
descriptor_hash_hex accepts a Path, a JSON string, or an already-parsed dict. The lower-level descriptor_hash returns raw 32 bytes.
CLI
# Update registry
clearsig update
# Translate calldata
clearsig translate <calldata> --to <contract_address> [--chain-id 1] [--from-address 0x...] [--json]
# Compute the ERC-8176 descriptor hash (for auditor attestations)
clearsig hash <path-to-descriptor.json>
Testing
# Run all tests
uv run pytest tests/ -v
# Run only unit tests
uv run pytest tests/unit/ -v
# Run only CLI tests
uv run pytest tests/cli/ -v
Tests require the local registry at ./clear-signing-erc7730-registry/. Clone it if you don't have it:
git clone --depth 1 https://github.com/LedgerHQ/clear-signing-erc7730-registry.git
Releasing
Releases are cut from main. Use just:
just release patch # 0.1.0 → 0.1.1, tag v0.1.1, push, cut release
just release minor # 0.1.0 → 0.2.0
just release major # 0.1.0 → 1.0.0
just release-draft patch # cut as a draft instead
Under the hood this runs scripts/release.sh, which bumps pyproject.toml via uv version --bump, commits the bump, tags vX.Y.Z, pushes, and calls gh release create --generate-notes. Publishing the release fires .github/workflows/publish.yml, which builds the sdist + wheel and uploads to PyPI via OIDC Trusted Publishing.
One-time setup on PyPI: add a Trusted Publisher under Publishing with workflow publish.yml, environment pypi, repo Cyfrin/clearsig.
Supported protocols
Any protocol with descriptors in the ERC-7730 registry, including:
- ERC-20/721/4626 (generic)
- Aave v3
- Safe{Wallet}
- Uniswap
- Lido
- MakerDAO
- And 40+ more
CLI Examples
Safe{Wallet} execTransaction wrapping an ERC-20 approve
The inner approve(Aave Pool, 1000000) call is recursively decoded.
uv run clearsig translate \
0x6a761202000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e200000000000000000000000000000000000000000000000000000000000f4240000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \
--to 0x41675C099F32341bf84BFc5382aF534df5C7461a \
--registry-path clear-signing-erc7730-registry \
--from-address 0x9467919138E36f0252886519f34a0f8016dDb3a3
Intent: sign multisig operation (Safe)
Function: execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)
Operation type: Call
From Safe: 0x41675C099F32341bf84BFc5382aF534df5C7461a
Execution signer: 0x9467919138E36f0252886519f34a0f8016dDb3a3
Transaction: Approve
-> approve(address,uint256)
Spender: 0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2
Amount: 1000000
Gas amount: 0
Gas price: 0
Gas receiver: 0x0000000000000000000000000000000000000000
Safe{Wallet} execTransaction wrapping an Aave v3 supply
The inner supply(USDC, 1000000, recipient, 0) call is recursively decoded.
uv run clearsig translate \
0x6a76120200000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000084617ba037000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000009467919138e36f0252886519f34a0f8016ddb3a30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \
--to 0x41675C099F32341bf84BFc5382aF534df5C7461a \
--registry-path clear-signing-erc7730-registry \
--from-address 0x9467919138E36f0252886519f34a0f8016dDb3a3
Intent: sign multisig operation (Safe)
Function: execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)
Operation type: Call
From Safe: 0x41675C099F32341bf84BFc5382aF534df5C7461a
Execution signer: 0x9467919138E36f0252886519f34a0f8016dDb3a3
Transaction: Supply (Aave)
-> supply(address,uint256,address,uint16)
Amount to supply: 1000000
Collateral recipient: 0x9467919138e36f0252886519f34a0f8016ddb3a3
Gas amount: 0
Gas price: 0
Gas receiver: 0x0000000000000000000000000000000000000000
Safe{Wallet} execTransaction via MultiSend (hits schema limits)
A delegate call to MultiSend batching an ERC-20 approve + Aave supply. The outer Safe layer decodes, but the MultiSend packed encoding is not standard ABI, so the inner transactions show as raw hex.
uv run clearsig translate \
0x6a76120200000000000000000000000038869bf66a61cf6bdb996a6ae40d5853fd43b52600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034000000000000000000000000000000000000000000000000000000000000001c48d80ff0a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000017200a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e200000000000000000000000000000000000000000000000000000000000f42400087870bca3f3fd6335c3f4ce8392d69350b4fa4e200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084617ba037000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000009467919138e36f0252886519f34a0f8016ddb3a300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \
--to 0x41675C099F32341bf84BFc5382aF534df5C7461a \
--registry-path clear-signing-erc7730-registry \
--from-address 0x9467919138E36f0252886519f34a0f8016dDb3a3
Intent: sign multisig operation (Safe)
Function: execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)
Operation type: Delegate Call
From Safe: 0x41675C099F32341bf84BFc5382aF534df5C7461a
Execution signer: 0x9467919138E36f0252886519f34a0f8016dDb3a3
Transaction: 0x8d80ff0a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000017200a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000087870bca3f3fd6335c3f4ce8392d69350b4fa4e200000000000000000000000000000000000000000000000000000000000f42400087870bca3f3fd6335c3f4ce8392d69350b4fa4e200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084617ba037000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000009467919138e36f0252886519f34a0f8016ddb3a300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Gas amount: 0
Gas price: 0
Gas receiver: 0x0000000000000000000000000000000000000000
The Transaction field is raw hex because MultiSend uses a custom packed encoding (not standard ABI). The ERC-7730 calldata format only handles standard ABI-encoded inner calls.
zkSync Era requestL2Transaction (no descriptor)
An L1→L2 message via the zkSync Era Mailbox calling grantRole on an L2 contract. From a real governance upgrade proposal.
uv run clearsig translate \
0xeb6724190000000000000000000000005a7d6b2f92c77fad6ccabd7ee0624e64907eaf3e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000989680000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000001600000000000000000000000001804c8ab1f12e6bbf3894d4083f33e07309d1f3800000000000000000000000000000000000000000000000000000000000000442f2ff15d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f41eca3047b37dc7d88849de4a4dc07937ad6bc4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \
--to 0x32400084C286CF3E17e7B677ea9583e60a000324 \
--chain-id 1 \
--registry-path clear-signing-erc7730-registry
Error: No ERC-7730 descriptor found for selector 0xeb672419 on 0x32400084C286CF3E17e7B677ea9583e60a000324 (chain 1)
Uniswap Universal Router (no descriptor)
The Universal Router's execute(bytes,bytes[],uint256) uses a custom command encoding not in the ERC-7730 registry.
uv run clearsig translate \
0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000065f5e10000000000000000000000000000000000000000000000000000000000000000020b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000016345785d8a00000000000000000000000000000000000000000000000000000000000002faf08000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000 \
--to 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD \
--chain-id 1 \
--registry-path clear-signing-erc7730-registry
Error: No ERC-7730 descriptor found for selector 0x3593564c on 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD (chain 1)
zkSync sendToL1 L2→L1 governance call (implicit selector)
An L2→L1 message via the zkSync L1Messenger system contract (0x...8008). The inner bytes are an ABI-encoded UpgradeProposal struct — but without a function selector. The L1 ProtocolUpgradeHandler implicitly knows to call execute() with those bytes. This example wraps an inner transferOwnership call targeting an L1 contract.
uv run clearsig translate \
0x62f84b24000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000060000000000000000000000000defd1edee3e8c5965216bd59c866f7f5307c9b29646563656e7472616c697a6174696f6e206973206e6f74206f7074696f6e616c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000c2a36181fb524a6befe639afed37a67e77d62cf1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024f2fde38b000000000000000000000000e30dca3047b37dc7d88849de4a4dc07937ad5ab300000000000000000000000000000000000000000000000000000000 \
--to 0x0000000000000000000000000000000000008008 \
--chain-id 324 \
--registry-path clear-signing-erc7730-registry
Error: No ERC-7730 descriptor found for selector 0x62f84b24 on 0x0000000000000000000000000000000000008008 (chain 324)
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file clearsig-0.1.0.tar.gz.
File metadata
- Download URL: clearsig-0.1.0.tar.gz
- Upload date:
- Size: 52.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2d306222e8c56f21d0105290e752f43faf7713da65df1a5ac740ed76e27f1f8f
|
|
| MD5 |
eb5d214526308835954aa948179ad5b4
|
|
| BLAKE2b-256 |
c361e2c73660fb12000371a0c3bcbf4beecdc0bb792176f3449b3e575f9ced61
|
Provenance
The following attestation bundles were made for clearsig-0.1.0.tar.gz:
Publisher:
publish.yml on Cyfrin/clearsig
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
clearsig-0.1.0.tar.gz -
Subject digest:
2d306222e8c56f21d0105290e752f43faf7713da65df1a5ac740ed76e27f1f8f - Sigstore transparency entry: 1510748025
- Sigstore integration time:
-
Permalink:
Cyfrin/clearsig@0dd109af288229b34ac0d736ebdf5907a92e0974 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Cyfrin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0dd109af288229b34ac0d736ebdf5907a92e0974 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file clearsig-0.1.0-py3-none-any.whl.
File metadata
- Download URL: clearsig-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
770e715baa6741cb38fc26c3693158849c62b560ddb2a2aa20f64ecab2755812
|
|
| MD5 |
c44e3bf556f1e450fd056419803f83c3
|
|
| BLAKE2b-256 |
57ab21e7614ad94d0b33bdcc98afb98ba9fcd790e56afc2331e1fdfa878f0c67
|
Provenance
The following attestation bundles were made for clearsig-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on Cyfrin/clearsig
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
clearsig-0.1.0-py3-none-any.whl -
Subject digest:
770e715baa6741cb38fc26c3693158849c62b560ddb2a2aa20f64ecab2755812 - Sigstore transparency entry: 1510748130
- Sigstore integration time:
-
Permalink:
Cyfrin/clearsig@0dd109af288229b34ac0d736ebdf5907a92e0974 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Cyfrin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0dd109af288229b34ac0d736ebdf5907a92e0974 -
Trigger Event:
workflow_dispatch
-
Statement type: