🧪 gulltoppr is a streamlined backend service that wraps heimdall-rs, an advanced EVM smart-contract toolkit, behind a small HTTP API. Give it a contract address and an RPC URL and it returns a usable ABI decompiled from bytecode — even for unverified contracts. It powers abi.ninja.
Check out the amazing tool that made this little project possible:
⚙️ Built with Rust and Actix.
- ✅ Works on unverified contracts — ABIs are reconstructed from bytecode via heimdall decompilation.
- ⚡ Cached & coalesced — decompiled ABIs are cached in memory (per address + RPC), so repeat lookups are instant; concurrent requests for the same uncached contract share a single decompile instead of each spawning their own.
- 🧱 Non-blocking — decompilation runs as an async subprocess with a hard timeout; one slow request no longer stalls others.
- 🧭 Agent-friendly — structured JSON responses with provenance metadata and meaningful HTTP status codes.
- Rust
- heimdall on your
PATH(install via bifrost:curl -L https://get.heimdall.rs | bash && bifrost) - Docker (optional, for containerized runs — the image installs heimdall for you)
git clone https://github.com/portdeveloper/gulltoppr.git
cd gulltoppr
cargo runThe server listens on http://localhost:8080. If heimdall is not on your PATH, point the service at it:
HEIMDALL_BIN="$HOME/.bifrost/bin/heimdall" cargo rundocker build -t gulltoppr .
docker run -p 8080:8080 gulltopprThe image downloads a pinned, precompiled heimdall binary (no toolchain needed at runtime). Override the version with --build-arg HEIMDALL_VERSION=x.y.z.
All configuration is via environment variables (with sensible defaults):
| Variable | Default | Description |
|---|---|---|
HEIMDALL_BIN |
heimdall |
Path to (or name of) the heimdall binary. |
HEIMDALL_VERSION |
unknown |
Pinned heimdall release, reported in /health (set by the Docker build). |
BIND_ADDR |
0.0.0.0:8080 |
Address:port to bind. |
DECOMPILE_TIMEOUT_SECS |
90 |
Max wall-clock time for a single decompilation. |
CACHE_TTL_SECS |
86400 (24h) |
How long a decompiled ABI stays cached. |
CACHE_CAPACITY |
5000 |
Max number of cached (address, rpc) entries. |
RATE_LIMIT_PER_SECOND |
2 |
Sustained requests/second per client IP. |
RATE_LIMIT_BURST |
5 |
Burst allowance per client IP. |
RUST_LOG |
info |
Log filter (e.g. debug, heimdall_api=debug). |
Service metadata.
curl http://localhost:8080/Liveness and lightweight introspection (version, cache size).
curl http://localhost:8080/health
# {"status":"ok","service":"gulltoppr","version":"0.2.0","heimdall_version":"0.9.3","cache_entries":42}Returns the decompiled ABI as a JSON array. Backwards compatible with the original API — the body is the raw heimdall ABI. Provenance is exposed via response headers:
X-Source: heimdall-decompiledX-Cache: HIT | MISSX-Elapsed-Ms: <n>
rpc_url may be a bare host (defaults to https://) or an explicit http(s):// URL.
curl "http://localhost:8080/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2?rpc_url=ethereum-rpc.publicnode.com"Same resolution, but wrapped with metadata — the shape intended for programmatic/agent consumers:
Provenance matters. This ABI is decompiled from bytecode, not read from verified source. Function and event names that heimdall could not resolve appear as synthetic identifiers (e.g.
Unresolved_0x...). Treat the result accordingly.
Decode a transaction's calldata into a function name, signature, and typed arguments — "what did this transaction do". Results are cached by tx hash (a transaction's calldata is immutable).
curl "http://localhost:8080/v1/decode/0x7e5cad0c0beea3416b649fa1ac6f3a2368dc9cc5e454a9e764c91b3b5b9ddc8b?rpc_url=ethereum-rpc.publicnode.com"{
"tx_hash": "0x7e5cad0c...",
"source": "heimdall-decoded",
"cached": false,
"elapsed_ms": 412,
"decoded": {
"name": "transfer",
"signature": "transfer(address,uint256)",
"inputs": [
{ "name": "arg0", "type": "address" },
{ "name": "arg1", "type": "uint256" }
],
"decoded_inputs": ["0xDb666b6CEACc321FCC107EBd0e391B5cF8Cb497F", "396929927"]
}
}Failures return a JSON body with a machine-readable code and an appropriate HTTP status:
| Status | code |
Meaning |
|---|---|---|
| 400 | invalid_address |
Not a 0x-prefixed 40-hex address. |
| 400 | invalid_tx_hash |
Not a 0x-prefixed 64-hex transaction hash. |
| 400 | missing_rpc_url |
rpc_url query parameter absent. |
| 400 | invalid_rpc_url |
rpc_url malformed. |
| 422 | no_bytecode |
No contract bytecode at the address/chain. |
| 502 | decompilation_failed |
heimdall ran but failed (e.g. RPC error). |
| 503 | heimdall_unavailable |
heimdall binary missing/not executable. |
| 504 | timeout |
Decompilation exceeded DECOMPILE_TIMEOUT_SECS. |
// GET /0x1234...? (no rpc_url)
{ "error": "missing required 'rpc_url' query parameter", "code": "missing_rpc_url" }cargo test # unit tests (validation, errors, failure classification)
cargo fmt # format
cargo clippy # lintConfigured for Fly.io via fly.toml. Decompilation is CPU- and memory-intensive for large contracts, so the VM is provisioned accordingly.
{ "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "source": "heimdall-decompiled", "cached": false, "elapsed_ms": 916, "abi": [ /* ... */ ] }