Run it locally
The repository is a monorepo: a Rust workspace for the chain under chain/, Anchor programs under programs/, circom circuits under circuits/, and a pnpm workspace for the TypeScript SDK, indexer, and explorer.
Prerequisites
- Rustvia rustup, plus the usual native build dependencies (a C toolchain, pkg-config, OpenSSL headers, protobuf compiler, clang). Budget ≥ 8 GB RAM for compiling Agave-derived crates.
- Node.js + pnpm for the web workspace.
- PostgreSQL if you want the indexer + explorer reading real chain data.
Run the node
cd chain
cargo test # unit + integration tests
cargo run -p zul-node -- --config ../config/node.example.tomlThe example config produces a block every 500 ms, serves JSON-RPC on 127.0.0.1:8899 and WebSocket on 127.0.0.1:8900, and enables the built-in faucet (airdrops are real System transfers signed by a faucet key). L1 settlement is disabled until program IDs and keys are configured.
[node]
slot_duration_ms = 500
[rpc]
http_addr = "127.0.0.1:8899"
ws_addr = "127.0.0.1:8900"
faucet_enabled = true
faucet_max_lamports = 10000000000 # 10 ZUL per request
[l1]
enabled = false # on once programs + keys exist
batch_interval_slots = 120Talk to it
The RPC speaks the Solana protocol, so the standard web3 client works unchanged — point a connection at the L2 and airdrop yourself gas:
import { Connection, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js";
const conn = new Connection("http://127.0.0.1:8899", "confirmed");
const me = Keypair.generate();
// 1 ZUL — the chain's lamport unit is ZUL, not SOL
await conn.requestAirdrop(me.publicKey, 1 * LAMPORTS_PER_SOL);
console.log(await conn.getBalance(me.publicKey));
console.log(await conn.getSlot());The Solana CLI also works: solana balance -u http://127.0.0.1:8899 <pubkey>.
Run the web stack
pnpm install
pnpm -r build
# indexer: copy its .env.example, set DATABASE_URL + node RPC, then run it
# explorer: same — it reads the indexer's Postgres via PrismaSequencer, faucet, and bridge keys are intentionally absent from the repository. Copy the example config files and generate keys locally; nothing that signs should ever be committed.
From here: how blocks work, or jump straight to shield / transfer / unshield.