A trust-minimized light client for BNB Smart Chain. It serves a local JSON-RPC on
127.0.0.1:8545 and answers wallet reads from proofs it verified itself against Parlia
consensus — so a lying upstream RPC produces an error, not a wrong balance. No chain
storage, no p2p, ~0 disk.
Quickstart · What it verifies · Threat model · All docs
When your wallet asks a public RPC for your balance, it believes the answer. There is no proof involved and no way to tell a mistake from a lie. The alternative — running a full BSC node — costs a multi-TB SSD and constant upkeep.
helios-bsc sits between the two. It uses an ordinary RPC endpoint as an untrusted
data plane: it asks for eth_getProof and headers, verifies the Parlia seals and the
Merkle-Patricia proofs locally, and serves the result. The upstream never has to be
trusted, only reachable.
Download the archive for your platform from the latest release, unpack it, and verify the checksum:
sha256sum -c SHA256SUMS --ignore-missingOr run the published image, no toolchain and no build:
docker run --rm -p 127.0.0.1:8545:8545 ghcr.io/alt-bi/helios-bsc:latest run --upstream https://bsc-mainnet.public.blastapi.io --listen 0.0.0.0:8545 --allow-non-loopbacklinux/amd64 and linux/arm64. The bind flags are explained in
docs/deploy.md — in short, 0.0.0.0 is inside the container and
-p 127.0.0.1: is what keeps it off the network.
The image carries a HEALTHCHECK, so docker ps tells you whether the head is still
moving rather than only whether the process is alive. The same probe runs standalone:
helios-bsc health exits 0 while it is, 1 when it is not.
From source instead, if you have Rust:
cargo install --locked --git https://github.com/Alt-bi/helios-bsc helios-bschelios-bsc write-checkpoint --upstream https://bsc-rpc.publicnode.com --checkpoint-oracle https://bsc-dataseed.bnbchain.org --block latest --out checkpoint.json
helios-bsc run --upstream https://bsc-mainnet.public.blastapi.io --backup https://bsc-rpc.publicnode.com --checkpoint checkpoint.jsonThen point MetaMask at http://127.0.0.1:8545, chainId 56. Step-by-step, including
what each line of output means: docs/quickstart.md.
The two URLs are different on purpose. --upstream has to serve eth_getProof
at a named block, because this client proves against a header it sealed itself and
can never ask an upstream what latest means. Plenty of public endpoints serve
proofs only for the tag — including the one above as a checkpoint source — and on
those every verified read fails. Which hosts do what is measured in
docs/proof-provider-matrix.md; run also probes
its upstream at startup and says so plainly if it cannot serve proofs.
| Verified | Balances, nonces, code, storage, eth_call, eth_estimateGas, headers, receipts, logs and log filters over ranges up to 128 blocks — each checked against a state root this client walked to from your checkpoint |
| Passed through | eth_sendRawTransaction (broadcasting is not something a proof can cover), and opt-in fee oracles |
| Refused | eth_subscribe (no WebSocket), pending-transaction filters (no mempool), debug_*, trace_*, anything needing keys — -32601, never an unverified guess. Log ranges and filter spans wider than 128 blocks are -32602 |
| History | ~112 blocks. This is not an archive node |
| Audited | No. See the threat model for what is and is not claimed |
Method-by-method detail: docs/rpc-matrix.md.
- A checkpoint fixes one block and the validator set sealing at it. Everything is verified relative to it, so it is written from two independent endpoints that agree.
- Headers are walked forward, each one checked: ECDSA seal, in-turn difficulty, sealing-set membership, epoch activation, timing floors.
- A head is chosen. By default that is the BEP-126 BLS-finalized head — the aggregate signature is verified against the epoch vote keys, ~2 blocks behind the tip. Without vote keys the client falls back to confirmation depth (15 distinct subsequent sealers, ~110 blocks) and says so on startup.
- State reads fetch
eth_getProofat that head and verify the Merkle-Patricia proof against the sealedstateRoot. An unproven value is an error, never a zero.
Depth: design.md · fast-finality.md · consensus-appendix.md
MVP-2. The ≥24 h differential soak passed 2026-08-24 (24.06 h, exit 0), with 4 h on the shipped build (2871 compared, 0 mismatch). A nightly soak runs in CI against live mainnet. Full engineering record: STATUS.md.
Not audited. Use it to read, and read SECURITY.md before exposing it to anything but loopback.
bin/helios-bsc/ # CLI
crates/
helios-bsc-types/
helios-bsc-config/ # fork schedule, chain constants
helios-bsc-consensus/ # seals, epochs, snapshots, BLS finality
helios-bsc-execution/ # MPT proofs, revm
helios-bsc-rpc/ # method policy, wallet tags
helios-bsc-mock/ # in-process lying upstream (CI, no network)
docs/ # see docs/README.md
fixtures/mainnet/ # pinned headers
scripts/ # proof probe, header capture, soak
| Project | Relation |
|---|---|
| a16z/helios | Ethereum light client with local verified JSON-RPC. Inspiration, not a fork — BSC has Parlia, not sync committees, so none of the consensus layer ports across |
| datachainlab/parlia-elc | Parlia light client for IBC/LCP bridges. Different product, useful consensus reference |
| Public BSC RPC providers | The blind-trust baseline this exists to replace. helios-bsc still needs one as a data plane, but verifies what it says |
CONTRIBUTING.md · SECURITY.md · MIT OR Apache-2.0
