Skip to Content
Indexer & RPCRunning an Indexer

Running an Indexer

The ZNS indexer can be run to independently verify registry state - this is what makes ZNS trust-minimized.

An indexer you run yourself will derive the exact same name-registry database as everyone else watching the same chain with the same UIVK and admin pubkey.

Build

git clone https://github.com/zcashme/ZNS.git cd ZNS cargo build --release --features testnet # or --features mainnet

The testnet and mainnet features are mutually exclusive. Exactly one must be enabled at compile time - they pick different lightwalletd URLs, birthday heights, and path defaults.

Run

export ZNS_UIVK="uivktest1qqq…" export ZNS_ADMIN_PUBKEY="0123abcd…64 hex chars…" cargo run --release --features testnet

The RPC server binds to 127.0.0.1:3000 by default. To expose it externally, front it with a reverse proxy (nginx, caddy) - the indexer has no built-in auth, TLS, or rate limiting.

Configuration

All configuration goes through two channels:

Compile-time constants (in src/config.rs):

  • Network (testnet or mainnet Cargo feature)
  • lightwalletd URL
  • Birthday height
  • SQLite database path
  • RPC listen address + port

Runtime environment variables:

VariableRequiredDescription
ZNS_UIVKyesUnified Incoming Viewing Key for the registry wallet
ZNS_ADMIN_PUBKEYyes32-byte Ed25519 admin public key, hex-encoded

Missing either secret causes the indexer to exit immediately on startup.

First sync

On a fresh database, the indexer scans from the birthday height forward. Expect:

  1. No-op blocks until the first SETPRICE memo is observed on chain. CLAIMs before pricing exists are rejected.
  2. Bootstrap SETPRICE - this establishes the initial pricing tiers and unblocks claims.
  3. Backfill - every CLAIM / UPDATE / LIST / … up to the chain tip is processed in order.

Sync time depends on chain length and lightwalletd responsiveness. A testnet cold sync is a few minutes; mainnet is longer.

Verify state

Once the indexer has caught up, query it:

curl -sX POST http://localhost:3000 \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"status","params":{}}' | jq
{ "result": { "synced_height": 3902500, "admin_pubkey": "abc123…", "uivk": "uivktest1qqq…", "registered": 42, "listed": 3, "pricing": { "nonce": 1, "height": 3901000, "tiers": [500000000, 100000000, ] } } }

Compare registered, listed, and synced_height against a trusted indexer (e.g. https://light.zcash.me/zns-testnet). They should converge. admin_pubkey and uivk should exactly match the values you pinned.

See Verifying State for a deeper walkthrough of cross-checking two indexers.

Hosted endpoints

If you don’t want to run your own, ZcashNames operates public endpoints:

NetworkURL
Testnethttps://light.zcash.me/zns-testnet
Mainnet (beta)https://light.zcash.me/zns-mainnet-test

These are the defaults used by the TypeScript SDK and the web app. For production applications that care about trust, running your own indexer is strongly recommended.

Operational notes

  • Backups. The SQLite database is derived state - you can always re-sync from the chain. Backups are only useful if you want to avoid the re-sync cost.
  • Upgrades. When upgrading the indexer across protocol changes, re-sync from scratch rather than trying to migrate. State derivation is cheap.
  • Monitoring. Poll /status and alert on synced_height lag relative to lightwalletd’s chain tip.
  • Reorgs. The current indexer does not handle deep reorgs. For mainnet, consider adding rollback logic or accepting a confirmation depth before trusting state.
Last updated on