Rust Examples¶
The Dilithia Rust SDK builds request objects (DilithiaRequest::Get / DilithiaRequest::Post) that describe the HTTP call to make. It does not include an HTTP runtime -- you execute the requests with your own HTTP client (reqwest, ureq, hyper, etc.). This keeps the SDK lightweight and compatible with any async runtime or blocking context.
Prerequisites¶
Add to your Cargo.toml:
Scenario 1: Balance Monitor Bot¶
A bot that recovers its wallet, checks its balance, and if the balance exceeds a threshold, sends tokens to a destination address via a contract call.
use dilithia_sdk_rust::{
DilithiaClient, DilithiaCryptoAdapter, DilithiaRequest, NativeCryptoAdapter,
};
use serde_json::json;
const RPC_URL: &str = "https://rpc.dilithia.network/rpc";
const TOKEN_CONTRACT: &str = "dil1_token_main";
const DESTINATION: &str = "dil1_recipient_address";
const THRESHOLD: u64 = 500_000;
const SEND_AMOUNT: u64 = 100_000;
fn main() -> Result<(), String> {
// 1. Initialize client and crypto adapter
let client = DilithiaClient::new(RPC_URL, Some(15_000))
.map_err(|e| e.to_string())?;
let crypto = NativeCryptoAdapter;
// 2. Recover wallet from saved mnemonic
let mnemonic = std::env::var("BOT_MNEMONIC")
.map_err(|_| "Set BOT_MNEMONIC env var".to_string())?;
let account = crypto.recover_hd_wallet(&mnemonic)?;
println!("Bot address: {}", account.address);
// 3. Check current balance
let balance_req = client.get_balance_request(&account.address);
match &balance_req {
DilithiaRequest::Get { path } => {
println!("GET {path}");
// execute with your HTTP client
// let response = ureq::get(path).call()?.into_string()?;
// let balance: u64 = parse_balance(&response);
}
_ => return Err("unexpected request type".into()),
}
// (Assuming we parsed the balance from the response)
let balance: u64 = 600_000; // placeholder
println!("Current balance: {balance}");
if balance < THRESHOLD {
println!("Balance {balance} below threshold {THRESHOLD}. Nothing to do.");
return Ok(());
}
// 4. Build a contract call to transfer tokens
let call = client.build_contract_call(
TOKEN_CONTRACT,
"transfer",
json!({ "to": DESTINATION, "amount": SEND_AMOUNT }),
None,
);
// 5. Simulate first to verify it would succeed
let sim_req = client.simulate_request(call.clone());
match &sim_req {
DilithiaRequest::Post { path, body } => {
println!("POST {path}");
println!("Body: {body}");
// execute with your HTTP client
}
_ => return Err("unexpected request type".into()),
}
// 6. Sign the call payload
let call_json = call.to_string();
let sig = crypto.sign_message(&account.secret_key, &call_json)?;
println!("Signed with algorithm: {}", sig.algorithm);
// 7. Submit the signed call
let signed_call = json!({
"call": call,
"signature": sig.signature,
"public_key": account.public_key,
});
let send_req = client.send_call_request(signed_call);
match &send_req {
DilithiaRequest::Post { path, body } => {
println!("POST {path}");
println!("Body: {body}");
// execute with your HTTP client
// let tx_hash = parse_tx_hash(&response);
}
_ => return Err("unexpected request type".into()),
}
// 8. Poll for receipt
let tx_hash = "placeholder_tx_hash";
let receipt_req = client.get_receipt_request(tx_hash);
match &receipt_req {
DilithiaRequest::Get { path } => {
println!("GET {path}");
// execute with your HTTP client, retry until receipt appears
}
_ => return Err("unexpected request type".into()),
}
println!("Transaction confirmed");
Ok(())
}
Scenario 2: Multi-Account Treasury Manager¶
Derives multiple accounts from a single mnemonic, checks each balance, and prints a treasury summary.
use dilithia_sdk_rust::{
DilithiaClient, DilithiaCryptoAdapter, DilithiaRequest, NativeCryptoAdapter,
};
const RPC_URL: &str = "https://rpc.dilithia.network/rpc";
const NUM_ACCOUNTS: u32 = 5;
fn main() -> Result<(), String> {
let client = DilithiaClient::new(RPC_URL, Some(10_000))
.map_err(|e| e.to_string())?;
let crypto = NativeCryptoAdapter;
// 1. Recover the root mnemonic
let mnemonic = std::env::var("TREASURY_MNEMONIC")
.map_err(|_| "Set TREASURY_MNEMONIC env var".to_string())?;
crypto.validate_mnemonic(&mnemonic)?;
// 2. Derive multiple HD accounts and query each balance
println!("Treasury accounts:");
println!("{:<8} {:<50} {}", "Index", "Address", "Balance Request");
for index in 0..NUM_ACCOUNTS {
let account = crypto.recover_hd_wallet_account(&mnemonic, index)?;
let balance_req = client.get_balance_request(&account.address);
match &balance_req {
DilithiaRequest::Get { path } => {
println!("{:<8} {:<50} GET {}", index, account.address, path);
// execute with your HTTP client
// let balance = fetch_balance(path)?;
}
_ => return Err("unexpected request type".into()),
}
}
// 3. Check nonce on the root account for pending transactions
let root_account = crypto.recover_hd_wallet(&mnemonic)?;
let nonce_req = client.get_nonce_request(&root_account.address);
match &nonce_req {
DilithiaRequest::Get { path } => {
println!("\nRoot account nonce request: GET {path}");
// execute with your HTTP client
}
_ => return Err("unexpected request type".into()),
}
// 4. Get address summary via JSON-RPC for the root account
let summary_req = client.get_address_summary_request(&root_account.address);
match &summary_req {
DilithiaRequest::Post { path, body } => {
println!("Address summary: POST {path}");
println!("Body: {body}");
// execute with your HTTP client
}
_ => return Err("unexpected request type".into()),
}
println!("\nTreasury report complete.");
Ok(())
}
Scenario 3: Signature Verification Service¶
Generates a keypair, signs a message, then verifies the signature -- demonstrating the full cryptographic round-trip.
use dilithia_sdk_rust::{DilithiaCryptoAdapter, NativeCryptoAdapter};
fn main() -> Result<(), String> {
let crypto = NativeCryptoAdapter;
// 1. Generate a fresh keypair
let keypair = crypto.keygen()?;
println!("Address: {}", keypair.address);
println!("Public key: {} ({} hex chars)", &keypair.public_key[..32], keypair.public_key.len());
// 2. Sign a message
let message = "Authorize withdrawal of 1000 tokens";
let sig = crypto.sign_message(&keypair.secret_key, message)?;
println!("Algorithm: {}", sig.algorithm);
println!("Signature: {}... ({} hex chars)", &sig.signature[..32], sig.signature.len());
// 3. Verify with the correct public key
let valid = crypto.verify_message(&keypair.public_key, message, &sig.signature)?;
println!("Valid signature: {valid}");
assert!(valid, "signature should be valid");
// 4. Verify with a tampered message should fail
let tampered = crypto.verify_message(&keypair.public_key, "tampered message", &sig.signature)?;
println!("Tampered message valid: {tampered}");
assert!(!tampered, "tampered message should fail verification");
// 5. Validate individual components
crypto.validate_public_key(&keypair.public_key)?;
crypto.validate_secret_key(&keypair.secret_key)?;
crypto.validate_signature(&sig.signature)?;
println!("All key/signature validations passed.");
// 6. Derive address from public key and confirm it matches
let derived_addr = crypto.address_from_public_key(&keypair.public_key)?;
assert_eq!(derived_addr, keypair.address, "derived address must match");
println!("Address derivation confirmed: {derived_addr}");
Ok(())
}
Scenario 4: Wallet Backup and Recovery¶
Creates an encrypted wallet file from a mnemonic, then recovers the account from that file -- demonstrating the backup/restore workflow.
use dilithia_sdk_rust::{DilithiaCryptoAdapter, NativeCryptoAdapter};
fn main() -> Result<(), String> {
let crypto = NativeCryptoAdapter;
// 1. Generate a fresh mnemonic
let mnemonic = crypto.generate_mnemonic()?;
println!("Generated mnemonic ({} words)", mnemonic.split_whitespace().count());
crypto.validate_mnemonic(&mnemonic)?;
// 2. Create an encrypted wallet file (account index 0)
let password = "strong-passphrase-here";
let account = crypto.create_hd_wallet_file_from_mnemonic(&mnemonic, password)?;
println!("Account address: {}", account.address);
println!("Account index: {}", account.account_index);
// The wallet_file field contains the encrypted JSON to persist
let wallet_json = account.wallet_file
.as_ref()
.ok_or("wallet_file should be present")?;
println!("Wallet file size: {} bytes", wallet_json.to_string().len());
// 3. Simulate saving and loading the wallet file
let wallet_file_str = wallet_json.to_string();
println!("Wallet file saved to disk (simulated).");
// 4. Create a second account at index 1 from the same mnemonic
let account_1 = crypto.create_hd_wallet_account_from_mnemonic(&mnemonic, password, 1)?;
println!("Account 1 address: {}", account_1.address);
assert_ne!(account.address, account_1.address, "different indices yield different addresses");
// 5. Recover the original account: verify the address matches
let recovered = crypto.recover_hd_wallet(&mnemonic)?;
assert_eq!(recovered.address, account.address, "recovered address must match");
println!("Recovery verified: addresses match.");
// 6. Derive a checksummed address
let checksummed = crypto.address_from_pk_checksummed(&account.public_key)?;
println!("Checksummed address: {checksummed}");
// 7. Demonstrate seed-based key derivation
let seed = crypto.seed_from_mnemonic(&mnemonic)?;
let child_seed = crypto.derive_child_seed(&seed, 0)?;
let child_keypair = crypto.keygen_from_seed(&child_seed)?;
println!("Child keypair address: {}", child_keypair.address);
println!("\nWallet backup and recovery complete.");
Ok(())
}
Scenario 5: Gas-Sponsored Meta-Transaction¶
Uses the DilithiaGasSponsorConnector to check if a user is eligible for gas sponsorship, then builds and submits a sponsored contract call.
use dilithia_sdk_rust::{
DilithiaClient, DilithiaCryptoAdapter, DilithiaGasSponsorConnector,
DilithiaRequest, NativeCryptoAdapter,
};
use serde_json::json;
const RPC_URL: &str = "https://rpc.dilithia.network/rpc";
const SPONSOR_CONTRACT: &str = "wasm:gas_sponsor";
const PAYMASTER: &str = "gas_sponsor";
const TARGET_CONTRACT: &str = "wasm:amm";
fn main() -> Result<(), String> {
let client = DilithiaClient::new(RPC_URL, Some(10_000))
.map_err(|e| e.to_string())?;
let crypto = NativeCryptoAdapter;
let sponsor = DilithiaGasSponsorConnector::new(SPONSOR_CONTRACT, Some(PAYMASTER.to_string()));
// 1. Recover the user's wallet
let mnemonic = std::env::var("USER_MNEMONIC")
.map_err(|_| "Set USER_MNEMONIC env var".to_string())?;
let account = crypto.recover_hd_wallet(&mnemonic)?;
println!("User address: {}", account.address);
// 2. Check remaining gas quota for the user
let quota_query = sponsor.build_remaining_quota_query(&account.address);
let quota_req = client.query_contract_request(
"a_query["contract"].as_str().unwrap_or(""),
"a_query["method"].as_str().unwrap_or(""),
quota_query["args"].clone(),
);
match "a_req {
DilithiaRequest::Get { path } => {
println!("Quota check: GET {path}");
// execute with your HTTP client
// let quota: u64 = parse_quota(&response);
}
_ => return Err("unexpected request type".into()),
}
// 3. Check if the sponsor accepts this call
let accept_query = sponsor.build_accept_query(&account.address, TARGET_CONTRACT, "swap");
let accept_req = client.query_contract_request(
&accept_query["contract"].as_str().unwrap_or(""),
&accept_query["method"].as_str().unwrap_or(""),
accept_query["args"].clone(),
);
match &accept_req {
DilithiaRequest::Get { path } => {
println!("Accept check: GET {path}");
// execute with your HTTP client
}
_ => return Err("unexpected request type".into()),
}
// 4. Build the contract call with gas sponsorship
let call = client.build_contract_call(
TARGET_CONTRACT,
"swap",
json!({ "token_in": "DAI", "token_out": "USDC", "amount": 1000 }),
Some(PAYMASTER),
);
println!("Call with paymaster: {call}");
// 5. Alternatively, build a call and apply the paymaster after
let plain_call = client.build_contract_call(
TARGET_CONTRACT,
"swap",
json!({ "token_in": "DAI", "token_out": "USDC", "amount": 1000 }),
None,
);
let sponsored_call = sponsor.apply_paymaster(&client, plain_call);
println!("Sponsored call: {sponsored_call}");
// 6. Sign and submit
let call_json = sponsored_call.to_string();
let sig = crypto.sign_message(&account.secret_key, &call_json)?;
let signed = json!({
"call": sponsored_call,
"signature": sig.signature,
"public_key": account.public_key,
});
let send_req = client.send_call_request(signed);
match &send_req {
DilithiaRequest::Post { path, body } => {
println!("Submit sponsored tx: POST {path}");
println!("Body: {body}");
// execute with your HTTP client
}
_ => return Err("unexpected request type".into()),
}
println!("Gas-sponsored transaction submitted.");
Ok(())
}
Scenario 6: Cross-Chain Message Sender¶
Uses the DilithiaMessagingConnector to send a cross-chain message and build a receive handler for incoming messages.
use dilithia_sdk_rust::{
DilithiaClient, DilithiaCryptoAdapter, DilithiaMessagingConnector,
DilithiaRequest, NativeCryptoAdapter,
};
use serde_json::json;
const RPC_URL: &str = "https://rpc.dilithia.network/rpc";
const MESSAGING_CONTRACT: &str = "wasm:messaging";
const PAYMASTER: &str = "gas_sponsor";
fn main() -> Result<(), String> {
let client = DilithiaClient::new(RPC_URL, Some(10_000))
.map_err(|e| e.to_string())?;
let crypto = NativeCryptoAdapter;
let messaging = DilithiaMessagingConnector::new(
MESSAGING_CONTRACT,
Some(PAYMASTER.to_string()),
);
// 1. Recover the sender's wallet
let mnemonic = std::env::var("SENDER_MNEMONIC")
.map_err(|_| "Set SENDER_MNEMONIC env var".to_string())?;
let account = crypto.recover_hd_wallet(&mnemonic)?;
println!("Sender address: {}", account.address);
// 2. Build an outbound cross-chain message
let payload = json!({
"action": "bridge_transfer",
"token": "USDC",
"amount": 5000,
"recipient": "0xRecipientOnEthereum",
});
let outbound_call = messaging.build_send_message_call(&client, "ethereum", payload);
println!("Outbound call: {outbound_call}");
// 3. Sign and submit the outbound message
let call_json = outbound_call.to_string();
let sig = crypto.sign_message(&account.secret_key, &call_json)?;
let signed_outbound = json!({
"call": outbound_call,
"signature": sig.signature,
"public_key": account.public_key,
});
let send_req = client.send_call_request(signed_outbound);
match &send_req {
DilithiaRequest::Post { path, body } => {
println!("Send cross-chain message: POST {path}");
println!("Body: {body}");
// execute with your HTTP client
}
_ => return Err("unexpected request type".into()),
}
// 4. Build a receive handler for an incoming cross-chain message
let incoming_payload = json!({
"action": "bridge_confirmation",
"tx_hash": "0xabc123",
"status": "confirmed",
});
let inbound_call = messaging.build_receive_message_call(
&client,
"ethereum",
"0xBridgeContract",
incoming_payload,
);
println!("Inbound call: {inbound_call}");
// 5. Simulate the inbound call before submitting
let sim_req = client.simulate_request(inbound_call.clone());
match &sim_req {
DilithiaRequest::Post { path, body } => {
println!("Simulate inbound: POST {path}");
println!("Body: {body}");
// execute with your HTTP client
}
_ => return Err("unexpected request type".into()),
}
// 6. Submit the inbound call
let inbound_json = inbound_call.to_string();
let inbound_sig = crypto.sign_message(&account.secret_key, &inbound_json)?;
let signed_inbound = json!({
"call": inbound_call,
"signature": inbound_sig.signature,
"public_key": account.public_key,
});
let submit_req = client.send_call_request(signed_inbound);
match &submit_req {
DilithiaRequest::Post { path, body } => {
println!("Submit inbound: POST {path}");
println!("Body: {body}");
// execute with your HTTP client
}
_ => return Err("unexpected request type".into()),
}
println!("Cross-chain messaging complete.");
Ok(())
}
Scenario 7: Contract Deployment¶
Deploy a WASM smart contract to the Dilithia chain. Reads the WASM binary, builds and signs a canonical deploy payload (the Rust SDK hashes the bytecode internally), assembles the full DeployPayload, and sends the deploy request.
use dilithia_sdk_rust::{
read_wasm_file_hex, DilithiaClient, DilithiaCryptoAdapter, DeployPayload,
DilithiaRequest, NativeCryptoAdapter,
};
use std::path::Path;
const RPC_URL: &str = "https://rpc.dilithia.network/rpc";
const CONTRACT_NAME: &str = "my_contract";
const WASM_PATH: &str = "./my_contract.wasm";
const CHAIN_ID: &str = "dilithia-mainnet";
fn main() -> Result<(), String> {
// 1. Initialize client and crypto adapter
let client = DilithiaClient::new(RPC_URL, Some(30_000))
.map_err(|e| e.to_string())?;
let crypto = NativeCryptoAdapter;
// 2. Recover wallet from mnemonic
let mnemonic = std::env::var("DEPLOYER_MNEMONIC")
.map_err(|_| "Set DEPLOYER_MNEMONIC env var".to_string())?;
let account = crypto.recover_hd_wallet(&mnemonic)?;
println!("Deployer address: {}", account.address);
// 3. Read the WASM file as hex
let bytecode_hex = read_wasm_file_hex(Path::new(WASM_PATH))?;
println!("Bytecode size: {} bytes", bytecode_hex.len() / 2);
// 4. Get the current nonce from the node
let nonce_req = client.get_nonce_request(&account.address);
match &nonce_req {
DilithiaRequest::Get { path } => {
println!("Nonce request: GET {path}");
// execute with your HTTP client
// let nonce: u64 = parse_nonce(&response);
}
_ => return Err("unexpected request type".into()),
}
let nonce: u64 = 0; // placeholder -- parse from HTTP response
// 5. Build the canonical deploy payload
// (Rust SDK hashes the bytecode_hex internally)
let canonical = DilithiaClient::build_deploy_canonical_payload(
&account.address,
CONTRACT_NAME,
&bytecode_hex,
nonce,
CHAIN_ID,
);
println!("Canonical payload: {canonical}");
// 6. Sign the canonical payload
let canonical_json = canonical.to_string();
let sig = crypto.sign_message(&account.secret_key, &canonical_json)?;
println!("Signed with algorithm: {}", sig.algorithm);
// 7. Assemble the full DeployPayload
let deploy_payload = DeployPayload {
name: CONTRACT_NAME.to_string(),
bytecode: bytecode_hex,
from: account.address.clone(),
alg: sig.algorithm,
pk: account.public_key.clone(),
sig: sig.signature,
nonce,
chain_id: CHAIN_ID.to_string(),
version: 1,
};
// 8. Send the deploy request
let deploy_req = client.deploy_contract_request(&deploy_payload);
match &deploy_req {
DilithiaRequest::Post { path, body } => {
println!("Deploy request: POST {path}");
println!("Body keys: name, bytecode, from, alg, pk, sig, nonce, chain_id");
// execute with your HTTP client:
// let response = ureq::post(path)
// .set("Content-Type", "application/json")
// .send_string(&body.to_string())?;
// let tx_hash = parse_tx_hash(&response.into_string()?);
}
_ => return Err("unexpected request type".into()),
}
// 9. Poll for receipt
let tx_hash = "placeholder_tx_hash"; // parse from deploy response
let receipt_req = client.get_receipt_request(tx_hash);
match &receipt_req {
DilithiaRequest::Get { path } => {
println!("Receipt request: GET {path}");
// execute with your HTTP client, retry until receipt appears
}
_ => return Err("unexpected request type".into()),
}
println!("Contract deployed successfully.");
Ok(())
}
Scenario 8: Shielded Pool Deposit & Withdraw¶
Performs a full shielded deposit-and-withdraw cycle: computes a ZK commitment and nullifier, generates a preimage proof, deposits into the shielded pool, then withdraws using the nullifier -- all with idiomatic Rust error handling.
use dilithia_sdk_rust::{
DilithiaClient, DilithiaCryptoAdapter, DilithiaZkAdapter,
NativeCryptoAdapter, NativeZkAdapter,
};
const RPC_URL: &str = "https://rpc.dilithia.network/rpc";
const DEPOSIT_VALUE: u64 = 50_000;
const WITHDRAW_AMOUNT: u64 = 50_000;
const RECIPIENT: &str = "dil1_recipient_address";
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Initialize client, crypto, and ZK adapters
let client = DilithiaClient::new(RPC_URL, None)?;
let crypto = NativeCryptoAdapter;
let zk = NativeZkAdapter;
// 2. Recover the depositor's wallet
let mnemonic = std::env::var("DEPOSITOR_MNEMONIC")
.map_err(|_| "Set DEPOSITOR_MNEMONIC env var")?;
let account = crypto.recover_hd_wallet(&mnemonic)?;
println!("Depositor address: {}", account.address);
// 3. Generate secret and nonce, then compute commitment and nullifier
let secret = crypto.hash_hex(b"my_deposit_secret")?;
let nonce = crypto.hash_hex(b"unique_nonce_value")?;
let commitment_hash = zk.compute_commitment(DEPOSIT_VALUE, &secret, &nonce)?;
let nullifier_hash = zk.compute_nullifier(&secret, &nonce)?;
println!("Commitment: {commitment_hash}");
println!("Nullifier: {nullifier_hash}");
// 4. Generate a preimage proof for the deposit
let proof = zk.generate_preimage_proof(&[
DEPOSIT_VALUE.to_string(),
secret.clone(),
nonce.clone(),
])?;
println!("Proof generated ({} bytes)", proof.proof.len());
// 5. Deposit into the shielded pool
let deposit_tx = client.shielded_deposit(
&commitment_hash,
DEPOSIT_VALUE,
&proof.proof,
)?;
println!("Deposit tx: {deposit_tx}");
// 6. Wait for the deposit receipt
let deposit_receipt = client.wait_for_receipt(&deposit_tx, 20, 2000)?;
println!("Deposit confirmed: {deposit_receipt}");
// 7. Verify the commitment is in the tree
let commitment_root = client.get_commitment_root()?;
println!("Commitment root: {commitment_root}");
// 8. Ensure the nullifier has not been spent yet
let already_spent = client.is_nullifier_spent(&nullifier_hash)?;
assert!(!already_spent, "nullifier should not be spent before withdrawal");
println!("Nullifier unspent -- ready to withdraw");
// 9. Withdraw from the shielded pool
let withdraw_tx = client.shielded_withdraw(
&nullifier_hash,
WITHDRAW_AMOUNT,
RECIPIENT,
&proof.proof,
&commitment_root,
)?;
println!("Withdraw tx: {withdraw_tx}");
// 10. Wait for the withdrawal receipt
let withdraw_receipt = client.wait_for_receipt(&withdraw_tx, 20, 2000)?;
println!("Withdrawal confirmed: {withdraw_receipt}");
// 11. Confirm the nullifier is now spent
let spent = client.is_nullifier_spent(&nullifier_hash)?;
assert!(spent, "nullifier should be spent after withdrawal");
println!("Nullifier spent -- double-spend protection verified");
println!("\nShielded deposit and withdraw complete.");
Ok(())
}
Scenario 9: ZK Proof Generation & Verification¶
Standalone zero-knowledge operations: Poseidon hashing, commitment/nullifier derivation, preimage proof round-trip, and range proof round-trip -- no on-chain interaction required.
use dilithia_sdk_rust::{
DilithiaCryptoAdapter, DilithiaZkAdapter,
NativeCryptoAdapter, NativeZkAdapter,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let crypto = NativeCryptoAdapter;
let zk = NativeZkAdapter;
// 1. Poseidon hash of field elements
let hash = zk.poseidon_hash(&[42, 1337, 99])?;
println!("Poseidon hash: {hash}");
// 2. Compute a commitment and its corresponding nullifier
let secret = crypto.hash_hex(b"zk_secret_material")?;
let nonce = crypto.hash_hex(b"zk_nonce_material")?;
let value: u64 = 10_000;
let commitment = zk.compute_commitment(value, &secret, &nonce)?;
let nullifier = zk.compute_nullifier(&secret, &nonce)?;
println!("Commitment: {commitment}");
println!("Nullifier: {nullifier}");
// 3. Generate a preimage proof and verify it
let preimage_proof = zk.generate_preimage_proof(&[
value.to_string(),
secret.clone(),
nonce.clone(),
])?;
println!(
"Preimage proof: {} bytes, vk: {} bytes",
preimage_proof.proof.len(),
preimage_proof.vk.len()
);
let preimage_valid = zk.verify_preimage_proof(
&preimage_proof.proof,
&preimage_proof.vk,
&preimage_proof.public_inputs,
)?;
assert!(preimage_valid, "preimage proof should verify");
println!("Preimage proof valid: {preimage_valid}");
// 4. Tamper with inputs and confirm verification fails
let mut bad_inputs = preimage_proof.public_inputs.clone();
bad_inputs.push("extra_field".to_string());
let tampered_valid = zk.verify_preimage_proof(
&preimage_proof.proof,
&preimage_proof.vk,
&bad_inputs,
)?;
assert!(!tampered_valid, "tampered inputs should fail verification");
println!("Tampered preimage valid: {tampered_valid}");
// 5. Generate a range proof (prove value is within [min, max])
let min = 1_000u64;
let max = 100_000u64;
let range_proof = zk.generate_range_proof(value, min, max)?;
println!(
"Range proof: {} bytes, vk: {} bytes",
range_proof.proof.len(),
range_proof.vk.len()
);
let range_valid = zk.verify_range_proof(
&range_proof.proof,
&range_proof.vk,
&range_proof.public_inputs,
)?;
assert!(range_valid, "range proof should verify");
println!("Range proof valid: {range_valid}");
// 6. Generate a range proof that should fail (value outside range)
let out_of_range = zk.generate_range_proof(200_000, min, max);
match out_of_range {
Err(e) => println!("Expected range error for out-of-bounds value: {e}"),
Ok(bad_proof) => {
let bad_valid = zk.verify_range_proof(
&bad_proof.proof,
&bad_proof.vk,
&bad_proof.public_inputs,
)?;
assert!(!bad_valid, "out-of-range proof should not verify");
println!("Out-of-range proof valid: {bad_valid}");
}
}
println!("\nZK proof generation and verification complete.");
Ok(())
}
¶
use dilithia_sdk_rust::{
DilithiaCryptoAdapter, DilithiaZkAdapter,
NativeCryptoAdapter, NativeZkAdapter,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let crypto = NativeCryptoAdapter;
let zk = NativeZkAdapter;
// 1. Poseidon hash of field elements
let hash = zk.poseidon_hash(&[42, 1337, 99])?;
println!("Poseidon hash: {hash}");
// 2. Compute a commitment and its corresponding nullifier
let secret = crypto.hash_hex(b"zk_secret_material")?;
let nonce = crypto.hash_hex(b"zk_nonce_material")?;
let value: u64 = 10_000;
let commitment = zk.compute_commitment(value, &secret, &nonce)?;
let nullifier = zk.compute_nullifier(&secret, &nonce)?;
println!("Commitment: {commitment}");
println!("Nullifier: {nullifier}");
// 3. Generate a preimage proof and verify it
let preimage_proof = zk.generate_preimage_proof(&[
value.to_string(),
secret.clone(),
nonce.clone(),
])?;
println!(
"Preimage proof: {} bytes, vk: {} bytes",
preimage_proof.proof.len(),
preimage_proof.vk.len()
);
let preimage_valid = zk.verify_preimage_proof(
&preimage_proof.proof,
&preimage_proof.vk,
&preimage_proof.public_inputs,
)?;
assert!(preimage_valid, "preimage proof should verify");
println!("Preimage proof valid: {preimage_valid}");
// 4. Tamper with inputs and confirm verification fails
let mut bad_inputs = preimage_proof.public_inputs.clone();
bad_inputs.push("extra_field".to_string());
let tampered_valid = zk.verify_preimage_proof(
&preimage_proof.proof,
&preimage_proof.vk,
&bad_inputs,
)?;
assert!(!tampered_valid, "tampered inputs should fail verification");
println!("Tampered preimage valid: {tampered_valid}");
// 5. Generate a range proof (prove value is within [min, max])
let min = 1_000u64;
let max = 100_000u64;
let range_proof = zk.generate_range_proof(value, min, max)?;
println!(
"Range proof: {} bytes, vk: {} bytes",
range_proof.proof.len(),
range_proof.vk.len()
);
let range_valid = zk.verify_range_proof(
&range_proof.proof,
&range_proof.vk,
&range_proof.public_inputs,
)?;
assert!(range_valid, "range proof should verify");
println!("Range proof valid: {range_valid}");
// 6. Generate a range proof that should fail (value outside range)
let out_of_range = zk.generate_range_proof(200_000, min, max);
match out_of_range {
Err(e) => println!("Expected range error for out-of-bounds value: {e}"),
Ok(bad_proof) => {
let bad_valid = zk.verify_range_proof(
&bad_proof.proof,
&bad_proof.vk,
&bad_proof.public_inputs,
)?;
assert!(!bad_valid, "out-of-range proof should not verify");
println!("Out-of-range proof valid: {bad_valid}");
}
}
println!("\nZK proof generation and verification complete.");
Ok(())
}
Scenario 10: Name Service & Identity Profile¶
A utility that registers a .dili name, configures profile records, resolves names, and demonstrates the full name service lifecycle. Covers: getRegistrationCost, isNameAvailable, registerName, setNameTarget, setNameRecord, getNameRecords, lookupName, resolveName, reverseResolveName, getNamesByOwner, renewName, transferName, releaseName.
use dilithia_sdk_rust::{
DilithiaClient, DilithiaCryptoAdapter, NativeCryptoAdapter,
};
const RPC_URL: &str = "https://rpc.dilithia.network/rpc";
const NAME: &str = "alice";
const TRANSFER_TO: &str = "dil1_bob_address";
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Initialize client and crypto adapter
let client = DilithiaClient::new(RPC_URL, None)?;
let crypto = NativeCryptoAdapter;
// 2. Recover wallet from mnemonic
let mnemonic = std::env::var("WALLET_MNEMONIC")
.map_err(|_| "Set WALLET_MNEMONIC env var")?;
let account = crypto.recover_hd_wallet(&mnemonic)?;
println!("Address: {}", account.address);
// 3. Query registration cost for the name
let cost = client.get_registration_cost(NAME)?.execute_json()?;
println!("Registration cost for \"{NAME}\": {cost}");
// 4. Check if name is available
let available: bool = client.is_name_available(NAME)?.execute_json()?;
println!("Name \"{NAME}\" available: {available}");
if !available {
return Err(format!("Name \"{NAME}\" is already taken").into());
}
// 5. Register name
let reg_req = client.register_name(NAME, &account.address, &account.secret_key)?;
let reg_hash: String = reg_req.execute_json()?;
let reg_receipt = client.wait_for_receipt(®_hash, 20, 2_000)?.execute_json()?;
println!("Name registered in block {}", reg_receipt.block_height);
// 6. Set target address
let target_req = client.set_name_target(NAME, &account.address, &account.secret_key)?;
let target_hash: String = target_req.execute_json()?;
client.wait_for_receipt(&target_hash, 20, 2_000)?.execute_json()?;
println!("Target address set to {}", account.address);
// 7. Set profile records
let records = [
("display_name", "Alice"),
("avatar", "https://example.com/alice.png"),
("bio", "Builder on Dilithia"),
("email", "alice@example.com"),
("website", "https://alice.dev"),
];
for (key, value) in records {
let req = client.set_name_record(NAME, key, value, &account.secret_key)?;
let hash: String = req.execute_json()?;
client.wait_for_receipt(&hash, 20, 2_000)?.execute_json()?;
println!(" Set record \"{key}\" = \"{value}\"");
}
// 8. Get all records
let all_records = client.get_name_records(NAME)?.execute_json::<serde_json::Value>()?;
println!("All records: {all_records}");
// 9. Resolve name to address
let resolved: String = client.resolve_name(NAME)?.execute_json()?;
println!("resolve_name(\"{NAME}\") -> {resolved}");
// 10. Reverse resolve address to name
let reverse_name: String = client.reverse_resolve_name(&account.address)?.execute_json()?;
println!("reverse_resolve_name(\"{}\") -> {reverse_name}", account.address);
// 11. List all names by owner
let owned: Vec<String> = client.get_names_by_owner(&account.address)?.execute_json()?;
println!("Names owned by {}: {:?}", account.address, owned);
// 12. Renew name
let renew_req = client.renew_name(NAME, &account.secret_key)?;
let renew_hash: String = renew_req.execute_json()?;
let renew_receipt = client.wait_for_receipt(&renew_hash, 20, 2_000)?.execute_json()?;
println!("Name renewed in block {}", renew_receipt.block_height);
// 13. Transfer name to another address
let transfer_req = client.transfer_name(NAME, TRANSFER_TO, &account.secret_key)?;
let transfer_hash: String = transfer_req.execute_json()?;
let transfer_receipt = client.wait_for_receipt(&transfer_hash, 20, 2_000)?.execute_json()?;
println!("Name transferred to {TRANSFER_TO} in block {}", transfer_receipt.block_height);
Ok(())
}
Scenario 11: Credential Issuance & Verification¶
An issuer creates a KYC credential schema, issues a credential to a holder, and a verifier checks it with selective disclosure. Covers: registerSchema, issueCredential, getSchema, getCredential, listCredentialsByHolder, listCredentialsByIssuer, verifyCredential, revokeCredential.
use dilithia_sdk_rust::{
DilithiaClient, DilithiaCryptoAdapter, NativeCryptoAdapter,
};
use serde_json::json;
use std::time::{SystemTime, UNIX_EPOCH};
const RPC_URL: &str = "https://rpc.dilithia.network/rpc";
const HOLDER_ADDRESS: &str = "dil1_holder_address";
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Initialize client and crypto adapter
let client = DilithiaClient::new(RPC_URL, None)?;
let crypto = NativeCryptoAdapter;
// 2. Recover issuer wallet
let mnemonic = std::env::var("ISSUER_MNEMONIC")
.map_err(|_| "Set ISSUER_MNEMONIC env var")?;
let issuer = crypto.recover_hd_wallet(&mnemonic)?;
println!("Issuer address: {}", issuer.address);
// 3. Register a KYC schema
let schema = json!({
"name": "KYC_Basic_v1",
"attributes": [
{ "name": "full_name", "type": "string" },
{ "name": "country", "type": "string" },
{ "name": "age", "type": "u64" },
{ "name": "verified", "type": "bool" },
],
});
let schema_req = client.register_schema(&schema, &issuer.secret_key)?;
let schema_hash: String = schema_req.execute_json()?;
let schema_receipt = client.wait_for_receipt(&schema_hash, 20, 2_000)?.execute_json()?;
let schema_hash = schema_receipt.logs[0]["schema_hash"]
.as_str()
.ok_or("missing schema_hash in receipt")?;
println!("Schema registered: {schema_hash}");
// 4. Issue credential to holder with commitment hash
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
let commitment_input = format!("{HOLDER_ADDRESS}:KYC_Basic_v1:{now}");
let commitment_hash = crypto.hash_hex(commitment_input.as_bytes())?;
let issue_req = client.issue_credential(
schema_hash,
HOLDER_ADDRESS,
&commitment_hash,
&json!({ "full_name": "Alice Smith", "country": "CH", "age": 30, "verified": true }),
&issuer.secret_key,
)?;
let issue_hash: String = issue_req.execute_json()?;
let issue_receipt = client.wait_for_receipt(&issue_hash, 20, 2_000)?.execute_json()?;
println!("Credential issued in block {}", issue_receipt.block_height);
// 5. Get schema by hash
let fetched_schema = client.get_schema(schema_hash)?.execute_json::<serde_json::Value>()?;
println!("Schema: {fetched_schema}");
// 6. Get credential by commitment
let credential = client.get_credential(&commitment_hash)?.execute_json::<serde_json::Value>()?;
println!("Credential: {credential}");
// 7. List credentials by holder
let holder_creds: Vec<serde_json::Value> = client
.list_credentials_by_holder(HOLDER_ADDRESS)?
.execute_json()?;
println!("Holder has {} credential(s)", holder_creds.len());
// 8. List credentials by issuer
let issuer_creds: Vec<serde_json::Value> = client
.list_credentials_by_issuer(&issuer.address)?
.execute_json()?;
println!("Issuer has {} credential(s)", issuer_creds.len());
// 9. Verify selective disclosure — prove age > 18 without revealing exact age
let proof = crypto.generate_selective_disclosure_proof(
&commitment_hash,
&["age"],
&json!({ "age": { "operator": "gt", "threshold": 18 } }),
)?;
let verified: bool = client
.verify_credential(&commitment_hash, &proof)?
.execute_json()?;
println!("Selective disclosure (age > 18) verified: {verified}");
// 10. Revoke the credential
let revoke_req = client.revoke_credential(&commitment_hash, &issuer.secret_key)?;
let revoke_hash: String = revoke_req.execute_json()?;
let revoke_receipt = client.wait_for_receipt(&revoke_hash, 20, 2_000)?.execute_json()?;
println!("Credential revoked in block {}", revoke_receipt.block_height);
// 11. Verify revocation by fetching credential again
let revoked = client.get_credential(&commitment_hash)?.execute_json::<serde_json::Value>()?;
println!("Credential status after revocation: {}", revoked["status"]);
Ok(())
}