EthProofs client written in Elixir, powered by ethrex.
Warning
The current version of this project only supports single-GPU ZisK proving using the cargo-zisk prove command under-the-hood. Support for distributed proving and server mode will be added in future releases.
- Erlang 28.2 (we recommend using asdf, see instructions below. Alternatively, you can follow the official instructions)
- Elixir 1.19.4-opt-28 (we recommend using asdf, see instructions below. Alternatively, you can follow the official instructions)
- ZisK toolchain v0.14.0 (see instructions below)
- CUDA Toolkit 12.9 or 13.0 (install via NVIDIA's guide)
# Install Erlang
asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git
asdf install erlang 28.3
asdf set --home erlang 28.3
asdf current erlang
# Install Elixir
asdf plugin add elixir https://github.com/asdf-vm/asdf-elixir.git
asdf install elixir 1.19.4
asdf set --home elixir 1.19.4
asdf current elixir
# Copy the following lines into your ~/.bashrc or ~/.zshrc depending on your shell
export PATH="$HOME/.asdf/shims:$PATH"
. $HOME/.asdf/asdf.sh
# Then, reload your shell configuration
source ~/.bashrc # or source ~/.zshrc
asdf reshimCaution
export CUDA_ARCH=sm_86 if you have an NVIDIA RTX 30 series GPU.
-
Ensure you have the necessary dependencies and hardware (see ZisK's installation guide).
-
Install the ZisK toolchain for GPU proving (if you have any troubles during installation, please refer to the ZisK installation guide):
# Clone the ZisK repository: git clone https://github.com/0xPolygonHermez/zisk cd zisk git checkout v0.14.0 # Build ZisK tools: cargo build --release --features gpu # Copy the tools to ~/.zisk/bin directory: mkdir -p $HOME/.zisk/bin LIB_EXT=$([[ "$(uname)" == "Darwin" ]] && echo "dylib" || echo "so") cp target/release/cargo-zisk target/release/ziskemu target/release/riscv2zisk target/release/zisk-coordinator target/release/zisk-worker target/release/libzisk_witness.$LIB_EXT target/release/libziskclib.a $HOME/.zisk/bin # Copy required files for assembly rom setup (this is only needed on Linux x86_64): mkdir -p $HOME/.zisk/zisk/emulator-asm cp -r ./emulator-asm/src $HOME/.zisk/zisk/emulator-asm cp ./emulator-asm/Makefile $HOME/.zisk/zisk/emulator-asm cp -r ./lib-c $HOME/.zisk/zisk # Add ~/.zisk/bin to your system PATH: PROFILE=$([[ "$(uname)" == "Darwin" ]] && echo ".zshenv" || echo ".bashrc") echo >>$HOME/$PROFILE && echo "export PATH=\"\$PATH:$HOME/.zisk/bin\"" >> $HOME/$PROFILE source $HOME/$PROFILE
-
Download the GPU proving key:
wget https://storage.googleapis.com/zisk-setup/zisk-provingkey-0.14.0.tar.gz tar -xzf zisk-provingkey-0.14.0.tar.gz mv provingKey $HOME/.zisk/provingKey -
Run the following in the root of the project to generate the const tree files:
cargo-zisk check-setup --proving-key $HOME/.zisk/provingKey -a -
Run the following in the root of the project to generate the ROM setup:
cargo-zisk rom-setup -e ethrex_guest_programs/ethrex-24d4b6404-zisk-0.14.0-guest.elf
First, set up the project:
make setupThen, run the application:
# Set required environment variables
export ETH_RPC_URL=<ETH_RPC_URL>
export ELF_PATH=<ELF_PATH>
# Optional: EthProofs API integration
export ETHPROOFS_API_KEY=<ETHPROOFS_API_KEY>
export ETHPROOFS_RPC_URL=<ETHPROOFS_RPC_URL>
export ETHPROOFS_CLUSTER_ID=<ETHPROOFS_CLUSTER_ID>
# Optional: Slack notifications (separate channels for success and alerts)
export SLACK_WEBHOOK_SUCCESS=<SLACK_WEBHOOK_URL_FOR_SUCCESS>
export SLACK_WEBHOOK_ALERTS=<SLACK_WEBHOOK_URL_FOR_ALERTS>
# Optional: Enable debug logging
export LOG_LEVEL=debug
# Start the application with IEx shell
make dev
# Or start just the server (no IEx shell)
make serverThe web dashboard will be available at http://localhost:4000.
Note
- Replace
<ETHPROOFS_API_TOKEN>with your EthProofs API token. - Replace
<ETHPROOFS_API_URL>with your EthProofs API (e.g. https://staging--ethproofs.netlify.app/api/v0). - Replace
<ETHPROOFS_CLUSTER_ID>with your EthProofs cluster ID. - Replace
<RPC_URL>with your Ethereum Mainnet node HTTP JSON-RPC URL. - Remove the
LOG_LEVEL=debugpart if you don't want debug logs (they're useful and not too verbose though). - Make sure the
ELF_PATHpoints to the correct guest program ELF file. You can either generate the ELF file yourself from the ethrex repository by runningcargo c -r -p ethrex-prover -F ziskfrom the root and getting the file generated incrates/l2/prover/src/guest_program/src/zisk/target/riscv64ima-zisk-zkvm-elf/release/zkvm-zisk-program, or download it from the releases page.
Tip
If you want to run the EthProofs client without sending requests to the EthProofs API (for testing purposes), you do this by not passing the ETHPROOFS_ environment variables.
Note
This is a placeholder for future troubleshooting tips. Please report any issues you encounter while running the integration tests to help us improve this section.
The EthProofs client is built as an OTP application with a supervision tree that manages the core GenServers and a Phoenix LiveView web dashboard:
EthProofsClient.Supervisor (strategy: :rest_for_one)
├── Phoenix.PubSub (real-time updates)
├── EthProofsClient.TaskSupervisor (Task.Supervisor)
├── EthProofsClient.ProvedBlocksStore (GenServer - tracks proved blocks)
├── EthProofsClient.Prover (GenServer)
├── EthProofsClient.InputGenerator (GenServer)
└── EthProofsClientWeb.Endpoint (Phoenix web server)
┌─────────────────────────────────────────────────────────────────────┐
│ Ethereum Network │
└──────────────────────────────┬──────────────────────────────────────┘
│ eth_getBlockByNumber
│ debug_executionWitness
▼
┌─────────────────────────────────────────────────────────────────────┐
│ InputGenerator │
│ • Polls latest block every 2 seconds │
│ • Triggers on blocks that are multiples of 100 │
│ • Fetches block data + execution witness via RPC │
│ • Calls Rust NIF to generate serialized input (.bin file) │
└──────────────────────────────┬──────────────────────────────────────┘
│ Prover.prove(block_number, input_path)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Prover │
│ • Manages queue of blocks to prove │
│ • Spawns cargo-zisk process via Erlang Port │
│ • Monitors proving progress and handles crashes │
│ • Reads proof artifacts (result.json, proof.bin) │
└──────────────────────────────┬──────────────────────────────────────┘
│ POST /proofs
▼
┌─────────────────────────────────────────────────────────────────────┐
│ EthProofs API │
│ • Receives proof status updates (queued → proving → proved) │
│ • Stores proof data for verification │
└─────────────────────────────────────────────────────────────────────┘
A GenServer that monitors the Ethereum chain and generates ZK proof inputs.
State Machine:
:idle- No generation in progress, ready to process next item{:generating, block_number, task_ref}- Currently generating input for a block
Behavior:
- Polls
eth_blockNumberevery 2 seconds - When a block number is a multiple of 100, queues it for input generation
- Spawns supervised tasks via
Task.Supervisor.async_nolink - Deduplicates requests using O(1) MapSet lookups
- Handles task crashes gracefully via
{:DOWN, ...}messages
Public API:
# Manually trigger input generation for a specific block
EthProofsClient.InputGenerator.generate(block_number)
# Get current status (useful for debugging)
EthProofsClient.InputGenerator.status()
# => %{status: :idle, queue_length: 0, queued_blocks: [], processed_count: 5}
# => %{status: {:generating, 21500000}, queue_length: 2, queued_blocks: [21500100, 21500200], processed_count: 3}A GenServer that manages a queue of blocks to prove using cargo-zisk.
State Machine:
:idle- No proof in progress, ready to process next item{:proving, block_number, port}- Currently proving a block
Behavior:
- Receives proof requests from InputGenerator
- Manages sequential proof generation (one at a time due to GPU constraints)
- Spawns
cargo-zisk proveas an Erlang Port for external process management - Reports proof status to EthProofs API (queued → proving → proved)
- Handles prover crashes (OOM, GPU errors) gracefully
- Deduplicates requests using O(1) MapSet lookups
Public API:
# Manually trigger proving for a specific block (input must already exist)
EthProofsClient.Prover.prove(block_number, "/path/to/input.bin")
# Get current status (useful for debugging)
EthProofsClient.Prover.status()
# => %{status: :idle, queue_length: 0, queued_blocks: []}
# => %{status: {:proving, 21500000}, queue_length: 2, queued_blocks: [21500100, 21500200]}A Task.Supervisor that supervises async tasks spawned by InputGenerator.
Benefits:
- Tasks are not linked to the GenServer (crashes don't propagate)
- Visible in
:observerfor debugging - Proper OTP supervision structure
-
EthProofsClient.EthRpc- Ethereum JSON-RPC client (Tesla-based)get_latest_block_number/0get_block_by_number/3debug_execution_witness/2
-
EthProofsClient.Rpc- EthProofs API client (Tesla-based)queued_proof/1- Report proof as queuedproving_proof/1- Report proof as in progressproved_proof/5- Submit completed proof
The project includes a Makefile for common tasks:
make help # Show all available commands
# Setup & Build
make deps # Install Elixir dependencies
make setup # Full setup (deps + assets)
make build # Compile the project
make assets # Build frontend assets (CSS/JS)
# Development
make dev # Start with IEx shell (recommended)
make server # Start Phoenix server
make run # Alias for 'make server'
# Quality
make test # Run all tests
make lint # Run Credo linter
make format # Format code
make check # Run format check, lint, and tests (CI)
# Cleanup
make clean # Remove build artifacts| Environment Variable | Required | Description |
|---|---|---|
ETH_RPC_URL |
Yes | Ethereum JSON-RPC endpoint URL |
ELF_PATH |
Yes | Path to the ZisK guest program ELF binary |
ETHPROOFS_RPC_URL |
No | EthProofs API base URL |
ETHPROOFS_API_KEY |
No | EthProofs API authentication token |
ETHPROOFS_CLUSTER_ID |
No | EthProofs cluster identifier |
LOG_LEVEL |
No | Logging level (debug, info, warning, error) |
SLACK_WEBHOOK_SUCCESS |
No | Slack webhook URL for successful proof notifications. |
SLACK_WEBHOOK_ALERTS |
No | Slack webhook URL for failure/warning notifications. |
GIT_BRANCH |
No | Override git branch detection for notifications (useful in Docker/CI) |
GIT_COMMIT |
No | Override git commit detection for notifications (useful in Docker/CI) |
HEALTH_PORT |
No | Port for health HTTP endpoint (default: 4000) |
PROVER_STUCK_THRESHOLD_SECONDS |
No | Seconds before prover is considered stuck (default: 3600). Increase for multi-GPU setups. |
Note: If
ETHPROOFS_*variables are not set, the client will still generate proofs but won't report them to the EthProofs API.
The application includes a Phoenix LiveView dashboard for real-time monitoring.
URL: http://localhost:4000
Features:
- Real-time status - Live status of InputGenerator and Prover with animated indicators
- Metrics cards - Blocks proved, queue lengths, current proving duration
- Proved blocks table - History of proved blocks with Etherscan links
- Next block countdown - Estimated time until the next target block (multiple of 100)
Screenshot:
The dashboard uses a dark theme with cyan accents, similar to ethproofs.org.
The application exposes HTTP health endpoints for monitoring and orchestration (e.g., Kubernetes probes).
Endpoints:
| Endpoint | Description |
|---|---|
GET /api/health |
Full health status with component details |
GET /api/health/ready |
Readiness probe (200 if ready, 503 if not) |
GET /api/health/live |
Liveness probe (always 200 if server is up) |
Status Levels:
| Status | Meaning | HTTP Code |
|---|---|---|
healthy |
All components up and working normally | 200 |
degraded |
Components up but prover stuck (proving > threshold) | 503 |
unhealthy |
One or more components down | 503 |
Example Response (GET /health):
{
"status": "healthy",
"timestamp": "2025-01-12T15:30:00Z",
"uptime_seconds": 3600,
"components": {
"prover": {
"status": "up",
"state": "proving_21500000",
"queue_length": 2,
"queued_blocks": [21500100, 21500200],
"proving_since": "2025-01-12T15:00:00Z",
"proving_duration_seconds": 1800
},
"input_generator": {
"status": "up",
"state": "idle",
"queue_length": 0,
"queued_blocks": [],
"processed_count": 15
},
"task_supervisor": {
"status": "up",
"pid": "#PID<0.250.0>",
"active_tasks": 0
}
},
"system": {
"beam_memory_mb": 128.5,
"process_count": 85,
"scheduler_count": 8,
"otp_release": "28"
}
}Usage:
# Check full health status
curl http://localhost:4000/api/health | jq
# Check readiness and liveness
curl -f http://localhost:4000/api/health/ready # Returns 503 if not ready
curl -f http://localhost:4000/api/health/live # Returns 200 if aliveThe application can send Slack notifications for key pipeline events to two separate channels: one for successful proofs and one for failures/warnings. Configure them with incoming webhook URLs.
Events notified:
| Event | Channel | Emoji | When |
|---|---|---|---|
| Proof submitted | success |
✅ | Proof successfully submitted to EthProofs API |
| Input generation failed | alerts |
Block input generation error or task crash | |
| Proof generation failed | alerts |
Prover exit, port crash, or timeout | |
| Proof data read failed | alerts |
Proof artifacts unreadable after successful exit | |
| EthProofs API call failed | alerts |
Any queued/proving/proved API request failed |
Requirements: Notifications only fire when at least one SLACK_WEBHOOK_* is set and all ETHPROOFS_* variables are configured. Each channel is independent — you can configure only one, and notifications for the unconfigured channel are silently skipped.
Runtime webhook updates via IEx:
Webhook URLs can be updated at runtime without restarting the application:
# Update webhook URLs
EthProofsClient.Notifications.Slack.set_webhook(:success, "https://hooks.slack.com/services/T/B/X")
EthProofsClient.Notifications.Slack.set_webhook(:alerts, "https://hooks.slack.com/services/T/B/Y")
# Check current webhooks
EthProofsClient.Notifications.Slack.get_webhook(:success)
EthProofsClient.Notifications.Slack.get_webhook(:alerts)
# Check if notifications are enabled
EthProofsClient.Notifications.enabled?()Proof artifacts are written to the output/ directory:
output/
└── {block_number}/
├── result.json # Proof metadata (cycles, time, verifier_id)
└── vadcop_final_proof.compressed.bin # Binary proof data
# Check InputGenerator status
EthProofsClient.InputGenerator.status()
# Check Prover status
EthProofsClient.Prover.status()
# Manually trigger generation for a specific block
EthProofsClient.InputGenerator.generate(21500000)
# View supervision tree
:observer.start()Start the Erlang observer to visualize the supervision tree and process states:
:observer.start()Navigate to the "Applications" tab and select ethproofs_client to see:
- Supervisor hierarchy
- TaskSupervisor with active tasks
- GenServer states and message queues
The following links, repos, companies and projects have been important in the development of this repo, we have learned a lot from them and want to thank and acknowledge them.
If we forgot to include anyone, please file an issue so we can add you. We always strive to reference the inspirations and code we use, but as an organization with multiple people, mistakes can happen, and someone might forget to include a reference.