use std::env;
use codec::Encode;
use quantus_cli::{
at_best_block,
chain::{client::QuantusClient, quantus_subxt::api::wormhole},
compute_merkle_positions,
compute_wormhole_address,
decode_full_leaf_data,
error::Result,
get_zk_merkle_proof,
parse_transfer_events,
read_proof_file,
wormhole_lib::{self, NATIVE_ASSET_ID},
write_proof_file,
IncludedAt,
NativeTransferred,
TransferInfo,
};
use subxt::utils::AccountId32 as SubxtAccountId;
const RECENT_BLOCKS_TO_SCAN: u32 = 200;
fn parse_node_arg() -> Option<String> {
let mut args = env::args().skip(1);
while let Some(a) = args.next() {
match a.as_str() {
"--node" => return args.next(),
s if s.starts_with("--node=") => return Some(s["--node=".len()..].to_string()),
s if s.starts_with("ws://") || s.starts_with("wss://") => return Some(s.to_string()),
_ => continue,
}
}
None
}
#[tokio::main]
async fn main() -> Result<()> {
println!("Quantus wormhole SDK example");
println!("============================");
offline_demo()?;
online_demo().await?;
println!();
println!("Done. See `examples/wormhole_sdk_usage.rs` for the full SDK API surface.");
Ok(())
}
fn offline_demo() -> Result<()> {
println!();
println!("--- Offline ---");
let secret: [u8; 32] = [42u8; 32];
let address = compute_wormhole_address(&secret).expect("compute_wormhole_address");
println!(" secret : 0x{}", hex::encode(secret));
println!(" address : 0x{}", hex::encode(address));
let leaf = (
SubxtAccountId::from(address),
7u64, NATIVE_ASSET_ID, 1_234_567_890_000u128, );
let leaf_bytes = leaf.encode();
let (to, transfer_count, asset_id, raw_amount) = decode_full_leaf_data(&leaf_bytes)?;
println!(
" leaf : to=0x{} transfer_count={} asset_id={} raw_amount={} planck",
hex::encode(to),
transfer_count,
asset_id,
raw_amount,
);
let quantized = wormhole_lib::quantize_amount(raw_amount)
.map_err(|e| quantus_cli::error::QuantusError::Generic(e.message))?;
println!(" quantized (2 decimals): {quantized}");
let tmp = std::env::temp_dir().join("wormhole-sdk-demo.hex");
let dummy_proof: Vec<u8> = (0u8..32).collect();
write_proof_file(tmp.to_str().unwrap(), &dummy_proof)
.map_err(quantus_cli::error::QuantusError::Generic)?;
let read_back = read_proof_file(tmp.to_str().unwrap())
.map_err(quantus_cli::error::QuantusError::Generic)?;
assert_eq!(read_back, dummy_proof);
let _ = std::fs::remove_file(&tmp);
println!(" proof : write+read round trip OK ({} bytes)", read_back.len());
for v in [IncludedAt::Best, IncludedAt::Finalized] {
println!(" IncludedAt::{:?} -> {}", v, v);
}
let info = TransferInfo {
block_hash: subxt::utils::H256::zero(),
transfer_count,
amount: raw_amount,
wormhole_address: SubxtAccountId::from(address),
funding_account: SubxtAccountId::from([0u8; 32]),
leaf_index: 0,
};
println!(
" TransferInfo: block={:?} transfer_count={} leaf_index={} amount={} planck",
info.block_hash, info.transfer_count, info.leaf_index, info.amount,
);
let _zero_event_type: Option<NativeTransferred> = None;
Ok(())
}
async fn online_demo() -> Result<()> {
println!();
println!("--- Online ---");
let node_url = parse_node_arg().unwrap_or_else(|| "ws://127.0.0.1:9944".to_string());
println!(" Trying node: {node_url}");
let client = match QuantusClient::new(&node_url).await {
Ok(c) => c,
Err(e) => {
println!(" No node reachable ({e}); skipping online section.");
print_online_recipe();
return Ok(());
},
};
let block = at_best_block(&client).await?;
let header = block.header();
let best_number = header.number;
let best_hash = block.hash();
println!(" Best block: #{} {:?}", best_number, best_hash);
println!(" Parent : {:?}", header.parent_hash);
match find_recent_native_transferred(&client, best_number).await? {
Some((event_block_hash, event)) => {
scan_real_event(&client, event_block_hash, event).await?;
},
None => {
println!();
println!(
" No NativeTransferred event found in the last {RECENT_BLOCKS_TO_SCAN} blocks."
);
println!(
" This is normal on a fresh dev chain. Run examples/wormhole_sdk_e2e.rs first"
);
println!(" (it submits a deposit + verify_aggregated_proof) to populate the chain.");
},
}
print_online_recipe();
Ok(())
}
async fn find_recent_native_transferred(
client: &QuantusClient,
best_number: u32,
) -> Result<Option<(subxt::utils::H256, NativeTransferred)>> {
use jsonrpsee::core::client::ClientT;
let lower = best_number.saturating_sub(RECENT_BLOCKS_TO_SCAN);
println!();
println!(" Scanning blocks #{lower}..=#{best_number} for NativeTransferred...");
for n in (lower..=best_number).rev() {
let hash: Option<subxt::utils::H256> =
client.rpc_client().request("chain_getBlockHash", [n]).await.map_err(|e| {
quantus_cli::error::QuantusError::NetworkError(format!(
"chain_getBlockHash({n}): {e:?}"
))
})?;
let Some(block_hash) = hash else { continue };
let events = match client.client().events().at(block_hash).await {
Ok(e) => e,
Err(_) => continue,
};
let first = events.find::<wormhole::events::NativeTransferred>().flatten().next();
if let Some(ev) = first {
println!(" Found NativeTransferred in block #{n} ({:?})", block_hash);
return Ok(Some((block_hash, ev)));
}
}
Ok(None)
}
async fn scan_real_event(
client: &QuantusClient,
event_block_hash: subxt::utils::H256,
event: NativeTransferred,
) -> Result<()> {
let to_addr = event.to.0;
let leaf_index = event.leaf_index;
let infos: Vec<TransferInfo> =
parse_transfer_events(&[event], &[SubxtAccountId::from(to_addr)], event_block_hash)?;
let info = infos.first().expect("parse_transfer_events returned the input event");
println!();
println!(" parse_transfer_events ->");
println!(" block_hash : {:?}", info.block_hash);
println!(" transfer_count : {}", info.transfer_count);
println!(" leaf_index : {}", info.leaf_index);
println!(" amount (planck) : {}", info.amount);
println!(" funding_account : {:?}", info.funding_account);
println!(" wormhole_addr : 0x{}", hex::encode(to_addr));
let proof_block = client.get_latest_block().await?;
let proof = match get_zk_merkle_proof(client, leaf_index, proof_block).await {
Ok(p) => p,
Err(e) => {
println!(" get_zk_merkle_proof failed (zkTree RPC may be disabled): {e}");
return Ok(());
},
};
println!();
println!(" get_zk_merkle_proof ->");
println!(" leaf_index : {}", proof.leaf_index);
println!(" leaf_hash : 0x{}", hex::encode(proof.leaf_hash));
println!(" root : 0x{}", hex::encode(proof.root));
println!(" depth : {}", proof.depth);
println!(" siblings : {} levels", proof.siblings.len());
let (sorted, positions) = compute_merkle_positions(&proof.siblings, proof.leaf_hash);
println!(" compute_merkle_positions ->");
println!(" sorted siblings: {} levels", sorted.len());
println!(" positions : {:?}", positions.iter().take(8).copied().collect::<Vec<_>>());
let (to, transfer_count, asset_id, raw_amount) = decode_full_leaf_data(&proof.leaf_data)?;
let quantized = wormhole_lib::quantize_amount(raw_amount)
.map_err(|e| quantus_cli::error::QuantusError::Generic(e.message))?;
println!();
println!(" decode_full_leaf_data ->");
println!(" to : 0x{}", hex::encode(to));
println!(" transfer_count : {transfer_count}");
println!(" asset_id : {asset_id} (NATIVE_ASSET_ID = {NATIVE_ASSET_ID})");
println!(" raw amount : {raw_amount} planck");
println!(" quantized : {quantized} (2 decimals)");
assert_eq!(to, to_addr, "leaf 'to' must match the event recipient");
Ok(())
}
fn print_online_recipe() {
println!();
println!("Typical SDK recipe (pseudo-code):");
println!(" let bytes = std::fs::read(\"agg.hex\")?;");
println!(" let bytes = hex::decode(bytes.trim_ascii())?;");
println!(" let (included_at, block_hash, tx_hash) =");
println!(" submit_unsigned_verify_aggregated_proof(&client, bytes).await?;");
println!(" println!(\"included @ {{}} block={{:?}} tx={{:?}}\",");
println!(" included_at, block_hash, tx_hash);");
println!();
println!(" // Or, with local verify + event collection:");
println!(" let (block_hash, tx_hash, transfers) =");
println!(" verify_aggregated_and_get_events(\"agg.hex\", &client).await?;");
println!(" for ev in transfers {{");
println!(" println!(\" -> {{}} planck to {{}}\", ev.amount, ev.to.to_ss58check());");
println!(" }}");
}