A privacy-preserving RPC proxy that routes RPC requests through the Nym mixnet to protect metadata and enhance privacy for blockchain interactions.
⚠️ Note: This project is currently under active development and not yet ready for production use. See TODO section below for planned features and improvements.
Run the zkool Zcash wallet over the Nym mixnet in three steps. No account, no config files — just a download and one command.
1. Download the client. Grab nym-rpc-client-linux-x86_64 from the
latest release and make it
executable:
chmod +x nym-rpc-client-linux-x86_642. Start the tunnel pointing at the live Zaino-over-Nym server. Leave it
running — it listens on 127.0.0.1:8137:
./nym-rpc-client-linux-x86_64 --zcash \
-x BbTPrU1gNTsPiieXdC58xkp5QFSHhUUM98BP1Rm2adf9.GKiGLNQB116YszFwbuweeL2GsrfpHpuUzq6JuqFQ8EEE@ZXSDhRTKU5HgMpH8ma78FftvLiKyZ6jWL1e2U7GD7gQYou should see Raw tunnel listening on 127.0.0.1:8137. (Press Ctrl+C to
stop it when you're done.)
3. Point zkool at it. Open zkool → Settings, turn on Light Node,
and set Light Node Server to http://127.0.0.1:8137:
That's it — zkool now syncs and sends over the Nym mixnet. Your wallet traffic reaches Zcash without ever exposing your IP to the light-wallet server.
First sync over the mixnet is slow (it streams a lot of blocks); it's best for ongoing refresh and sending. A broadcast may briefly show an error even when it succeeded — check the transaction on a block explorer before resending.
Nym RPC enables anonymous and private access to blockchain RPC endpoints by routing requests through the Nym mixnet. This protects users' IP addresses, request patterns, and other metadata from being exposed to RPC providers.
The system uses full TLS encryption end-to-end, ensuring that not even the final Nym exit node can see the content of your RPC requests - only the destination server can decrypt and read the actual request data.
Client App → HTTP Proxy (localhost:8545) → TCP Proxy Client → Nym Mixnet → TCP Proxy Server → RPC Provider
- HTTP Proxy: Runs a local HTTP proxy server that accepts standard RPC requests, forwards them to a specified provider.
- TCP Proxy Client: Inserts UPSTREAM packet information and sends through the Nym mixnet
- Mixnet: Routes requests through the Nym mixnet for privacy
- TCP Proxy Server: Receives requests from mixnet, extracts UPSTREAM packet info, and forwards to target RPC provider
See architecture diagram for visual representation of the whole flow.
- 🔒 Privacy-first: All requests routed through Nym mixnet
- 🚀 Drop-in replacement: Compatible with existing RPC clients
- ⚡ Connection pooling: Maintains NYM client pools for optimal performance
- 🌐 Multi-provider support: Works with any JSON-RPC endpoint
- 🛠️ Easy configuration: Simple CLI interface
- detect misbehaving nodes
- allowlist
- node discovery
- memory: prune sessions
cargo build --release- (optional) Run a
nym-rpc-serveron a VM - Copy it's NYM address displayed in the logs
- Run
nym-rpc-clienton your laptop and specifynym-rpc-serveras exit-node. - If you don't specify an exit-node, it will use one of the four available.
nym-rpc-client -x <nym-rpc-server-address> -r <RPC_PROVIDER_URL>- Confirm it works
curl -vX POST http://localhost:8545 \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'- Connect with your wallet (http://localhost:8545)
- Make sure
nym-rpc-clientis running and http://localhost:8545 is available - Go to metamask and add a new RPC for Ethereum Mainnet
- Do not try to add it into test networks, this will not work
- Add 'http://localhost:8545'
nym-rpc also tunnels the Zcash Zaino
lightwalletd-compatible gRPC service through the mixnet, for wallets like
zingo-cli / Zingo PC.
Wallet (zingo-cli, etc.) → local raw listener (127.0.0.1:8137) → Nym Mixnet → pinned exit (nym-rpc-server) → Zaino
Unlike the HTTP RPC mode above, this uses --raw/--zcash mode: the client
opens a raw TCP listener and pipes bytes through the mixnet unmodified
(required for gRPC, which isn't plain HTTP/JSON), and the server pins every
session to a single upstream instead of letting clients choose one.
./target/release/nym-rpc-server \
--config-dir /var/lib/nym-rpc/state \
--pin-upstream 127.0.0.1:8137--pin-upstream <host:port> is required for public operation — without
it the server is an open relay that forwards to whatever upstream a client
requests. It prints its Nym address on startup (also available at
GET /api/v1/nym-address on the HTTP API, default 127.0.0.1:8080); publish
that address to your users. Two more flags harden a public deployment:
--session-idle-timeout <secs> (default 3600) closes sessions with no
traffic in either direction, and --max-sessions <n> (default 512) caps
concurrent sessions. Monitoring stats (active/total sessions, bytes
forwarded, watchdog liveness) are exposed at GET /api/v1/stats.
Mixnet watchdog. The embedded Nym client can lose its gateway connection
in ways it never reports and never recovers from: the process keeps running
but every outbound packet is dropped, and clients simply get no replies. To
catch this the server sends a tagged self-ping to its own Nym address every
--watchdog-interval <secs> (default 60). If no ping has completed a
round trip for --watchdog-timeout <secs> (default 300), the server logs
the failure and exits non-zero so systemd (Restart=on-failure) brings it
back within seconds. GET /api/v1/health reflects the same signal: it
returns 200 {"status":"up"} while pings are returning and
503 {"status":"down","reason":...} once the watchdog has timed out, so an
external monitor can alert on it. Each pong is logged at info level
(watchdog: pong received), which doubles as a heartbeat in the journal.
⚠️ On a public deployment the HTTP API listener must stay on loopback or behind a firewall (never0.0.0.0): the live session and byte counters in/api/v1/statsare traffic-sensitive and would let an observer correlate mixnet flows with this exit, undermining the anonymity the tunnel provides. Note also that the mixnet only encrypts traffic between the client and this exit — the exit↔Zaino hop is plaintext h2c, so Zaino must likewise stay bound to loopback on the same host.
./target/release/nym-rpc-client --zcash -x <NYM_ADDRESS>--zcash is a preset for --raw mode that listens on 127.0.0.1:8137 and
targets a Zaino upstream; -x <NYM_ADDRESS> is the server's Nym address from
above (required — there's no default exit node for Zcash traffic until one is
published as ZCASH_SERVICE_NYM_ADDRESS in src/client_config.rs).
Point your wallet at http://127.0.0.1:8137 as its lightwalletd server, e.g.:
zingo-cli --server http://127.0.0.1:8137 --data-dir /tmp/zingo-nym-test infoLimitation: initial wallet sync over the mixnet is slow (the mixnet adds latency and the sync involves fetching a large range of compact blocks), so this tunnel is best suited for wallets that already have a synced state — i.e. periodic refresh and transaction submission — rather than syncing a wallet from birth height for the first time.
After deploying nym-rpc-server pinned at a live zainod/Zaino instance,
verify the tunnel end-to-end before publishing the Nym address:
# terminal 1 (on the VM, or a machine with zainod at 127.0.0.1:8137)
./target/release/nym-rpc-server --config-dir /tmp/nym-rpc-zcash --pin-upstream 127.0.0.1:8137
# note the printed Nym address
# terminal 2 (laptop)
./target/release/nym-rpc-client --zcash -x <NYM_ADDRESS>
# terminal 3 (laptop) — zingo-cli from zingolib
cargo run --release --package zingo-cli -- \
--server http://127.0.0.1:8137 --data-dir /tmp/zingo-nym-test infoExpected: info returns the lightwalletd info block (vendor/taddr height)
through the mixnet.
