Skip to content

portdeveloper/gulltoppr

Repository files navigation

🐎 gulltoppr

🧪 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.

Requirements

  • 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)

Quickstart

git clone https://github.com/portdeveloper/gulltoppr.git
cd gulltoppr
cargo run

The 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 run

Docker

docker build -t gulltoppr .
docker run -p 8080:8080 gulltoppr

The image downloads a pinned, precompiled heimdall binary (no toolchain needed at runtime). Override the version with --build-arg HEIMDALL_VERSION=x.y.z.

Configuration

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).

Endpoints

GET /

Service metadata.

curl http://localhost:8080/

GET /health

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}

GET /{contract_address}?rpc_url={host}

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-decompiled
  • X-Cache: HIT | MISS
  • X-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"

GET /v1/{contract_address}?rpc_url={host}

Same resolution, but wrapped with metadata — the shape intended for programmatic/agent consumers:

{
  "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
  "source": "heimdall-decompiled",
  "cached": false,
  "elapsed_ms": 916,
  "abi": [ /* ... */ ]
}

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.

GET /v1/decode/{tx_hash}?rpc_url={host}

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"]
  }
}

Errors

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" }

Development

cargo test        # unit tests (validation, errors, failure classification)
cargo fmt         # format
cargo clippy      # lint

Deployment

Configured for Fly.io via fly.toml. Decompilation is CPU- and memory-intensive for large contracts, so the VM is provisioned accordingly.

About

Simple rust backend for heimdall-rs. used in https://abi.ninja

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors