zrelay-sdk
The native Rust client. Same three operations as the TypeScript SDK — shield, attest, track — for services that settle on a schedule rather than in a browser tab. It talks directly to the public Nitro RPC and a public lightwalletd; there is no Z-Relay server in the path.
Installation
# Cargo.toml
[dependencies]
zrelay-sdk = "0.1"
tokio = { version = "1", features = ["full"] }
alloy = { version = "0.8", features = ["signer-local"] }- Crate
- zrelay-sdk
- MSRV
- 1.79
- Runtime
- tokio (multi-thread or current-thread)
- Signing
- alloy — any Signer implementation
- Proving
- arkworks, BN254, CPU
- Network calls
- Public RPC + public lightwalletd only
Building a client
1use zrelay_sdk::{Client, Pool};2use alloy::signers::local::PrivateKeySigner;3 4let signer: PrivateKeySigner = std::env::var("PRIVATE_KEY")?.parse()?;5 6let client = Client::builder()7 .chain_id(4663) // Robinhood Chain mainnet8 .signer(signer)9 .rpc_url("https://rpc.robinhoodchain.com") // optional override10 .lightwalletd("https://zec.rocks:443") // optional override11 .timeout(std::time::Duration::from_secs(180))12 .build()?;Quoting
A quote prices the DEX hop, the relayer fee and the expected Orchard output. Quotes are short-lived by design — an expired quote fails loudly rather than settling at a price you did not agree to.
1let quote = client2 .quote(QuoteParams {3 token: usdg,4 amount: 2_500_000_000u128,5 pool: Pool::Orchard,6 })7 .await?;8 9println!("{}", quote.estimated_zec); // "79.12840000"10println!("{}", quote.relayer_fee); // "0.0015"11println!("{}", quote.expires_at); // unix seconds — quotes are short-livedShielding
Handles the approval, the DEX route, the escrow deposit and the gas surcharge, then resolves once the Orchard output lands in a finalised block.
1use zrelay_sdk::{ShieldParams, Pool};2 3#[tokio::main]4async fn main() -> anyhow::Result<()> {5 // One call: swap, escrow, threshold-sign, mint into Orchard.6 let settlement = client7 .shield(ShieldParams {8 token: "0xUSDG_ADDRESS".parse()?,9 amount: 2_500_000_000u128, // 2,500 USDG (6 decimals)10 recipient_z_address: "u1xyz...orchard_unified_address".into(),11 memo: Some("Quarterly stock-token yield".into()),12 pool: Pool::Orchard,13 max_slippage_bps: Some(50), // 0.50%14 })15 .await?;16 17 println!("{}", settlement.zcash_tx_id);18 println!("{:?}", settlement.status); // Status::Settled19 Ok(())20}Attesting
Builds a Groth16 proof about shielded holdings. The viewing key is read from your secret store into process memory and never transmitted — but it is on a machine you operate, which is a materially different risk profile from the browser prover.
1use zrelay_sdk::{AttestParams, Condition};2 3// The witness is built in-process. The viewing key never leaves the host.4let attestation = client5 .attest(AttestParams {6 viewing_key, // read from your secret store7 condition: Condition::MinZecBalance {8 amount: "100.0".into(),9 at_block_height: Some(2_654_900),10 },11 destination_contract: vault_address,12 })13 .await?;14 15println!("{:?}", attestation.claim_hash);16println!("{} bytes", attestation.proof.len()); // ~19217 18// Cheap local check before you pay gas to submit it.19assert!(client.verify_attestation(&attestation).await?);ivk) where the predicate only concerns received value, scope the secret to a single process, and zero it as soon as the proof exists.Tracking
Settlement stages arrive as a stream, so a worker can record progress instead of polling.
1use futures::StreamExt;2 3let mut stream = client.track(&settlement.tracking_id).await?;4 5while let Some(update) = stream.next().await {6 let update = update?;7 println!("{:?} at {}", update.stage, update.at);8 // Stage::EvmSequenced | FrostSigned | OrchardIncluded | Settled9}Errors
Failures are a single Error enum. Variants match the TypeScript ZRelayError.code values one-for-one, so a service that already handles the TS errors needs no new logic.
1use zrelay_sdk::Error;2 3match client.shield(params).await {4 Ok(settlement) => println!("{}", settlement.zcash_tx_id),5 Err(Error::QuoteExpired { quote_id, .. }) => { /* re-quote, do not retry */ }6 Err(Error::TransparentReceiverOnly { .. }) => { /* z-addr has no Orchard receiver */ }7 Err(Error::InsufficientGasCoverage { .. }) => { /* attach the surcharge */ }8 Err(Error::SlippageExceeded { .. }) => { /* DEX route moved */ }9 Err(Error::ThresholdNotMet { tracking_id }) => { /* ring offline; reclaim later */ }10 Err(e) => return Err(e.into()),11}Cargo features
# Default: client + server-side proving via arkworks.
zrelay-sdk = "0.1"
# Read-only: quoting, tracking and verification, no prover, no signer.
zrelay-sdk = { version = "0.1", default-features = false, features = ["read"] }
# Bring your own runtime (the crate is runtime-agnostic under the hood).
zrelay-sdk = { version = "0.1", default-features = false, features = ["prover"] }Related
- Python SDK — the same API for Python services.
- TypeScript SDK — the browser client and the React hook.
- Run a relayer — the relayer binary shares these crates.