use std::{env, fs, process::exit};
use sha2::{Digest, Sha256};
use serde_json::json;
fn to_hex_ascii(s: &str) -> String {
s.as_bytes().iter().map(|b| format!("{:02x}", b)).collect()
}
fn to_hex_ascii_bytes(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
fn main() {
let mut args = std::env::args().skip(1).collect::<Vec<_>>();
if args.is_empty() {
eprintln!("Usage: license_exporter <vault_path>");
exit(2);
}
let vault_path = &args[0];
let rpc_url = env::var("XRPL_RPC_URL").unwrap_or_else(|_| "https://s.altnet.rippletest.net:51234".to_string());
let account = env::var("XRPL_ADDRESS").unwrap_or_else(|_| {
eprintln!("XRPL_ADDRESS is required in environment.");
exit(2);
});
let secret = env::var("XRPL_SEED").unwrap_or_else(|_| {
eprintln!("XRPL_SEED is required in environment.");
exit(2);
});
let license_text = env::var("VIOS_LICENSE_TEXT").unwrap_or_else(|_| "Emotionprint License v1".to_string());
let data = match fs::read(vault_path) {
Ok(b) => b,
Err(e) => {
eprintln!("Failed to read {}: {}", vault_path, e);
exit(1);
}
};
let hash_hex = format!("{:x}", Sha256::digest(&data));
let memo_type_license = to_hex_ascii("license");
let memo_type_sha256 = to_hex_ascii("sha256");
let memo_data_license = to_hex_ascii(&license_text);
let memo_data_sha256 = to_hex_ascii(&hash_hex);
let tx_json = json!({
"TransactionType": "Payment",
"Account": account,
"Destination": account,
"Amount": "1", "Memos": [
{ "Memo": { "MemoType": memo_type_license, "MemoData": memo_data_license } },
{ "Memo": { "MemoType": memo_type_sha256, "MemoData": memo_data_sha256 } }
]
});
let req = json!({
"method": "submit",
"params": [{
"secret": secret,
"tx_json": tx_json
}]
});
let resp = match ureq::post(&rpc_url).send_json(req) {
Ok(r) => match r.into_string() {
Ok(s) => s,
Err(e) => {
eprintln!("XRPL response read error: {}", e);
exit(1);
}
},
Err(e) => {
eprintln!("XRPL submit error: {}", e);
exit(1);
}
};
let v: serde_json::Value = match serde_json::from_str(&resp) {
Ok(v) => v,
Err(e) => {
eprintln!("XRPL JSON parse error: {}\nRaw: {}", e, resp);
exit(1);
}
};
let engine = v.pointer("/result/engine_result").and_then(|x| x.as_str()).unwrap_or("<unknown>");
let accepted = v.pointer("/result/engine_result_code").and_then(|x| x.as_i64()).unwrap_or(-999);
let hash = v.pointer("/result/tx_json/hash").and_then(|x| x.as_str()).unwrap_or("");
println!("--- VIOS License Anchor ---");
println!("Vault: {}", vault_path);
println!("SHA256: {}", hash_hex);
println!("License: {}", license_text);
println!("XRPL engine_result: {} (code {})", engine, accepted);
if !hash.is_empty() {
println!("TX Hash: {}", hash);
println!("Explorer: https://testnet.xrpl.org/transactions/{}", hash);
} else {
if let Some(h) = find_any_hex64(&v) {
println!("TX Hash: {}", h);
println!("Explorer: https://testnet.xrpl.org/transactions/{}", h);
} else {
println!("No TX hash field in response. Full response below:\n{}", resp);
exit(1);
}
}
}
fn find_any_hex64(v: &serde_json::Value) -> Option<String> {
let s = v.to_string();
let mut cur = String::new();
for c in s.chars() {
if c.is_ascii_hexdigit() {
cur.push(c);
if cur.len() == 64 { return Some(cur); }
} else {
cur.clear();
}
}
None
}