zinder-compat-cipherscan serves Cipherscan's current REST and WebSocket
contracts from Zinder's native query services. It lets an unchanged Cipherscan
application use Zinder for covered chain-data workflows.
The adapter is a stateless edge service. It does not open the Zinder store, index blocks, or own Cipherscan product data.
browser -> Cipherscan web -> zinder-compat-cipherscan
| |
v v
ExplorerQuery WalletQuery
\ /
Zinder store
Zinder owns canonical facts, reusable projections, coverage, and freshness. This crate owns Cipherscan paths, JSON shapes, query coercion, display units, cache behavior, mining-pool metadata, and product formulas. The coverage matrix records the owner and current status of every route family.
Z3 and Cipherscan are separate projects. This repository does not vendor them or own their deployment procedures.
| System | Role in this topology | Repository and authoritative guide |
|---|---|---|
| Z3 | Runs the Zebra node that Zinder ingests from. The adapter does not connect to Z3 directly. | Z3 repository, operator quick start, Compose-peer integration, and platform contract |
| Zinder | Indexes Zebra, owns reusable projections, and serves WalletQuery and ExplorerQuery to this adapter. |
This repository and the VM deployment runbook |
| Cipherscan | Runs the Next.js product UI and any Cipherscan-owned sidecars an operator chooses to retain. | Cipherscan repository and Cipherscan deployment guide |
Follow each project's guide to deploy that project. This README defines the integration delta: attach Zinder to Z3, run this adapter against Zinder's two query services, and point Cipherscan's API traffic at the adapter.
After completing Z3's own setup guide, the local walkthrough uses independent Zinder and Cipherscan checkouts. They do not need to share a parent directory. Set these variables to their absolute locations before using the commands below:
export ZINDER_ROOT="/absolute/path/to/zinder"
export CIPHERSCAN_ROOT="/absolute/path/to/cipherscan"Use matching revisions of these Zinder services:
zinder-ingest, caught up on the intended network;zinder-query, servingWalletQuery;zinder-explorer, servingExplorerQuery; andzinder-compat-cipherscan, configured for the same network.
The adapter connects to both gRPC readers at startup. If they require bearer
authentication, both endpoints must accept the token loaded from the adapter's
single bearer_token_path.
The adapter needs outbound HTTPS access only for the configured current and historical market-price providers. A price-provider failure degrades price routes; it does not replace missing prices with zero.
First complete Z3's
testnet quick start,
including its one-time network setup and Zebra readiness wait. That procedure
creates the external z3-testnet network and z3-testnet-cookie volume used by
Zinder. Do not replace it with a bare docker compose up: first-time setup and
the two-phase sync sequence belong to Z3.
After Z3 reports Zebra ready, start the Zinder testnet services. This reuses
the named zinder-testnet-data volume; it does not recreate or replace the
store.
docker compose \
--project-directory "$ZINDER_ROOT" \
--env-file "$ZINDER_ROOT/deploy/.env.testnet" \
-f "$ZINDER_ROOT/deploy/docker-compose.yml" \
up -d zinder-ingest zinder-query zinder-explorerWait for the three Zinder services to become ready, then run the adapter on the
host against the testnet reader ports. The values below match
deploy/.env.testnet; use the ports published by your deployment if you
override that file:
cargo run \
--manifest-path "$ZINDER_ROOT/Cargo.toml" \
--locked \
-p zinder-compat-cipherscan -- \
--network zcash-testnet \
--explorer-query-endpoint http://127.0.0.1:19068 \
--wallet-query-endpoint http://127.0.0.1:19101 \
--listen-addr 127.0.0.1:9070 \
--ops-listen-addr 127.0.0.1:9108Confirm both operational readiness and a data-bearing route:
curl --fail http://127.0.0.1:9108/healthz
curl --fail http://127.0.0.1:9108/readyz
curl --fail http://127.0.0.1:9070/api/info
curl --fail 'http://127.0.0.1:9070/api/mining/pool-distribution?period=7d'/readyz proves that the process started and connected to its dependencies.
It does not prove that every projection covers the requested range. Keep the
data-bearing probe in deployment checks so incomplete backfill or unavailable
capabilities remain visible.
Complete the dependency-installation steps in Cipherscan's local development guide first. Do not start its full PostgreSQL/API/indexer stack for this test.
Cipherscan currently exposes a configurable custom API URL through its Crosslink deployment slot. Use that slot to run the source-unmodified UI against a local adapter:
NEXT_PUBLIC_NETWORK=crosslink-testnet \
NEXT_PUBLIC_CROSSLINK_API_URL=http://127.0.0.1:9070 \
CIPHERSCAN_API_URL=http://127.0.0.1:9070 \
npm --prefix "$CIPHERSCAN_ROOT" run dev -- --port 3003Open http://127.0.0.1:3003. Core explorer workflows use the adapter. Crosslink-specific, sidecar-owned, and explicitly unavailable panels may show degraded states; they are not supplied by Zinder.
The public NEXT_PUBLIC_CROSSLINK_API_URL is used by browser requests.
CIPHERSCAN_API_URL is used by Cipherscan server routes and must be reachable
from the Next.js process. They can be different URLs in a container
deployment.
Deploy the adapter with Zinder, not as another indexer inside Cipherscan. The boxes below are independent deployment boundaries:
+---------------- Z3 deployment ----------------+
| Zebra node |
+----------------------|-------------------------+
| JSON-RPC + cookie auth
+---------------- Zinder deployment ------------+
| zinder-ingest -> canonical and derive volume |
| | |
| +----------+----------+ |
| | | |
| zinder-query zinder-explorer |
| +----------+----------+ |
| | gRPC |
| zinder-compat-cipherscan |
+----------------------|-------------------------+
| REST + WebSocket
TLS reverse proxy
|
+---------------- Cipherscan deployment --------+
| Next.js web application |
| Optional Cipherscan-owned sidecars |
+------------------------------------------------+
Deploy in this order:
- Deploy and synchronize Z3 using its operator guide.
- Deploy Zinder against Z3 using the Zinder VM runbook. Zinder's Compose topology consumes the network and cookie identifiers defined by the Z3 platform contract.
- Deploy
zinder-compat-cipherscanbeside the Zinder readers using the configuration below. - Deploy the Cipherscan frontend using its deployment guide, applying the API endpoint substitutions in Build Cipherscan for a custom endpoint.
The adapter:
- mounts no Zinder or Cipherscan data volume;
- can be restarted or replaced without backfill;
- keeps only bounded in-memory response caches;
- must use the same Zcash network as both Zinder readers;
- should reach Zinder gRPC over a private network; and
- should expose its REST listener through a TLS reverse proxy.
Do not run Cipherscan's current docker-compose.yml unchanged for this
topology. That file starts Cipherscan's Express API, PostgreSQL, Redis, Zebra,
and lightwalletd. For Zinder-backed covered routes, the adapter takes the place
of the Express API. Deploy any still-required Cipherscan-owned sidecars
separately; do not add their product schemas to Zinder.
Build the release binary from the same revision as the Zinder readers:
cargo build \
--manifest-path "$ZINDER_ROOT/Cargo.toml" \
--locked \
--release \
--bin zinder-compat-cipherscan
"$ZINDER_ROOT/target/release/zinder-compat-cipherscan" --helpInstall that binary in the same way as the other per-service Zinder binaries and supervise it with the platform's normal process manager. The service handles termination signals and shuts down its realtime tasks before exit.
A production TOML configuration can keep both listeners on loopback when a local reverse proxy terminates TLS:
[network]
name = "zcash-testnet"
[security]
allow_public_bind = false
[ops]
listen_addr = "127.0.0.1:9108"
[cipherscan]
listen_addr = "127.0.0.1:9070"
explorer_query_endpoint = "http://127.0.0.1:9068"
wallet_query_endpoint = "http://127.0.0.1:9101"
current_price_endpoint = "https://api.coingecko.com/api/v3/simple/price?ids=zcash&vs_currencies=usd&include_24hr_change=true"
historical_price_endpoint_template = "https://api.coingecko.com/api/v3/coins/zcash/history?localization=false&date={date}"
# bearer_token_path = "/run/secrets/zinder-reader-token"Run it with:
zinder-compat-cipherscan --config /etc/zinder/cipherscan.tomlConfiguration precedence is defaults, TOML file, ZINDER_* environment
variables, then CLI flags. The equivalent container-oriented environment
variables are:
ZINDER_NETWORK__NAME=zcash-testnet
ZINDER_SECURITY__ALLOW_PUBLIC_BIND=true
ZINDER_OPS__LISTEN_ADDR=0.0.0.0:9108
ZINDER_CIPHERSCAN__LISTEN_ADDR=0.0.0.0:9070
ZINDER_CIPHERSCAN__EXPLORER_QUERY_ENDPOINT=http://zinder-explorer:9068
ZINDER_CIPHERSCAN__WALLET_QUERY_ENDPOINT=http://zinder-query:9101
ZINDER_CIPHERSCAN__BEARER_TOKEN_PATH=/run/secrets/zinder-reader-tokenUse --print-config to inspect the resolved configuration. Secret material is
not printed.
Follow Cipherscan's
production deployment guide
for frontend hosting, TLS, and Cipherscan-owned services. For an adapter-backed
deployment, replace its API backend for covered routes with the adapter.
Cipherscan's NEXT_PUBLIC_* values are compiled into the browser bundle, so
set them while building the production application, then provide the internal
server URL when starting it:
NEXT_PUBLIC_NETWORK=crosslink-testnet \
NEXT_PUBLIC_CROSSLINK_API_URL=https://api.example.test \
npm --prefix "$CIPHERSCAN_ROOT" run build
CIPHERSCAN_API_URL=http://zinder-compat-cipherscan:9070 \
npm --prefix "$CIPHERSCAN_ROOT" startRoute https://api.example.test to the adapter. Route WebSocket upgrades on
the same origin. Keep the adapter's operational port private.
Use these probes for distinct questions:
| Probe | Meaning |
|---|---|
GET /healthz on the ops port |
The adapter process is alive. |
GET /readyz on the ops port |
Startup completed and initial gRPC connections succeeded. |
GET /api/info on the REST port |
The adapter can serve current chain data. |
GET /api/mining/pool-distribution?period=7d |
The block-production projection covers the requested window. |
Treat non-success and degraded responses as operational signals. The adapter does not fabricate zero, empty, complete, healthy, or canonical values when a source fact is missing.
A restart clears in-memory caches. That can change generatedAt and refresh
provider-backed data, but it does not affect canonical or projection state.
The mining pool-distribution cache lasts five minutes and is keyed by the
effective raw period string.
For route-level acceptance and the focused UI regression, follow the Cipherscan adapter verification runbook. Projection identity, backfill, restart, pagination, and reorg behavior are specified by ADR 0033.