Skip to main content

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:

  1. 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.
  2. 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.

View calldata on Cyfrin

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.

View calldata on Cyfrin

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.

View calldata on Cyfrin

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.

View calldata on Cyfrin

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.

View calldata on Cyfrin

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.

View calldata on Cyfrin

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

clearsig-0.1.0.tar.gz (52.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

clearsig-0.1.0-py3-none-any.whl (17.2 kB view details)

Uploaded Python 3

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

Hashes for clearsig-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2d306222e8c56f21d0105290e752f43faf7713da65df1a5ac740ed76e27f1f8f
MD5 eb5d214526308835954aa948179ad5b4
BLAKE2b-256 c361e2c73660fb12000371a0c3bcbf4beecdc0bb792176f3449b3e575f9ced61

See more details on using hashes here.

Provenance

The following attestation bundles were made for clearsig-0.1.0.tar.gz:

Publisher: publish.yml on Cyfrin/clearsig

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for clearsig-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 770e715baa6741cb38fc26c3693158849c62b560ddb2a2aa20f64ecab2755812
MD5 c44e3bf556f1e450fd056419803f83c3
BLAKE2b-256 57ab21e7614ad94d0b33bdcc98afb98ba9fcd790e56afc2331e1fdfa878f0c67

See more details on using hashes here.

Provenance

The following attestation bundles were made for clearsig-0.1.0-py3-none-any.whl:

Publisher: publish.yml on Cyfrin/clearsig

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page