use rust_pact::{tools, fetch};
use rust_pact::utils::KeyPair;
use serde_json::json;
use std::time::Duration;
use std::thread;
fn main() {
let run_live = std::env::var("RUN_LIVE").unwrap_or_default() == "1";
let attempts: u32 = std::env::var("XCHAIN_ATTEMPTS").ok().and_then(|v| v.parse().ok()).unwrap_or(30);
let spv_attempts: u32 = std::env::var("XCHAIN_SPV_ATTEMPTS").ok().and_then(|v| v.parse().ok()).unwrap_or(30);
let interval_secs: u64 = std::env::var("XCHAIN_INTERVAL").ok().and_then(|v| v.parse().ok()).unwrap_or(5);
let spv_interval_secs: u64 = std::env::var("XCHAIN_SPV_INTERVAL").ok().and_then(|v| v.parse().ok()).unwrap_or(interval_secs);
let post_confirm_wait: u64 = std::env::var("XCHAIN_POST_CONFIRM_WAIT").ok().and_then(|v| v.parse().ok()).unwrap_or(10);
if run_live {
println!("🌐 RUN_LIVE=1 detected - will make real network calls to testnet");
println!("⚠️ WARNING: This will attempt actual blockchain transactions!");
} else {
println!("🔒 Demo mode - using mock responses (set RUN_LIVE=1 for real calls)");
}
let sender_keypair = KeyPair {
public_key: "10375651f1ca0110468152bb8f47b7b8a469e36dfab1c83adf60cab84b5726d3".into(),
secret_key: "18d3a823139cf60cab0b738e7605bb9e4a2f3ff245c270fa55d197f9b3c4c004".into(),
clist: None
};
let receiver_keypair = KeyPair {
public_key: "03df480e0b300c52901fdff265f0460913fea495f39972321698740536cc38e3".into(),
secret_key: "".into(), clist: None
};
let token_address = "coin";
let sender_account = "k:10375651f1ca0110468152bb8f47b7b8a469e36dfab1c83adf60cab84b5726d3";
let receiver_account = "k:03df480e0b300c52901fdff265f0460913fea495f39972321698740536cc38e3";
let receiver_public_key = "03df480e0b300c52901fdff265f0460913fea495f39972321698740536cc38e3";
let amount = 1.0_f64;
let source_chain_id = "1"; let target_chain_id = "2"; let network_id = "testnet04";
println!("=== Cross-Chain Transfer Example ===\n");
println!("Step 1: Initiating cross-chain transfer from chain {} to chain {}", source_chain_id, target_chain_id);
println!("Amount: {} {}", amount, token_address);
println!("From: {}", sender_account);
println!("To: {}", receiver_account);
let transfer_result = if run_live {
tools::crosschain_transfer(
token_address,
sender_account,
receiver_account,
receiver_public_key,
amount,
sender_keypair.clone(),
source_chain_id,
target_chain_id,
network_id,
None
)
} else {
json!({
"requestKeys": ["KnTrT06r4gLKuA56fRMZOCBjS1MiSNoCGDO8g8VxYVI"]
})
};
println!("Transfer initiation result: {}", serde_json::to_string_pretty(&transfer_result).unwrap());
if let Some(request_keys) = transfer_result.get("requestKeys").and_then(|rks| rks.as_array()) {
if let Some(request_key) = request_keys.get(0).and_then(|rk| rk.as_str()) {
println!("\nTransaction hash (request key): {}", request_key);
println!("\nStep 2: Polling for transaction completion on source chain...");
let api_host = tools::get_api_host(network_id, source_chain_id);
let poll_result = if run_live {
poll_until_result(request_key, &api_host, attempts, interval_secs)
} else {
json!({
request_key: {
"result": {
"status": "success",
"data": "Cross-chain transfer initiated"
},
"continuation": {
"pactId": "mock-pact-id-123",
"step": 0
}
}
})
};
println!("Final poll result: {}", serde_json::to_string_pretty(&poll_result).unwrap());
if let Some(pact_id) = extract_pact_id(&poll_result) {
println!("\nPact ID for continuation: {}", pact_id);
println!("\nStep 4: Getting SPV proof for cross-chain completion...");
if run_live && post_confirm_wait > 0 {
println!("Waiting {}s after confirmation before requesting SPV (XCHAIN_POST_CONFIRM_WAIT)", post_confirm_wait);
thread::sleep(Duration::from_secs(post_confirm_wait));
}
let spv_cmd = json!({
"requestKey": request_key,
"targetChainId": target_chain_id
});
let spv_result = if run_live {
poll_for_spv_proof(&spv_cmd, &api_host, spv_attempts, spv_interval_secs)
} else {
json!({
"proof": "mock-spv-proof-data-for-demo"
})
};
println!("SPV result: {}", serde_json::to_string_pretty(&spv_result).unwrap());
if let Some(proof) = extract_spv_proof(&spv_result) {
println!("\nStep 5: Completing cross-chain transfer on target chain...");
let complete_result = if run_live {
tools::crosschain_complete(
&pact_id,
&proof,
receiver_account,
receiver_public_key,
amount,
receiver_keypair.clone(),
target_chain_id,
network_id
)
} else {
json!({
"requestKeys": ["completion-tx-hash-456"],
"result": "Cross-chain transfer completed successfully"
})
};
println!("Cross-chain completion result: {}", serde_json::to_string_pretty(&complete_result).unwrap());
} else {
println!("Could not extract SPV proof from result");
println!("In a real scenario, you would need to wait for the transaction to be included in a block and then request the SPV proof");
}
} else {
println!("Could not extract pact ID from poll result");
}
}
} else {
println!("No request keys found in transfer result");
}
println!("\n=== Example Notes ===");
println!("1. This example shows the complete cross-chain transfer flow");
println!("2. In production, you would need to wait for block confirmations between steps");
println!("3. SPV proofs are generated by the blockchain network after transactions are finalized");
println!("4. The receiver keypair only needs the public key for the completion step");
println!("5. Set RUN_LIVE=1 environment variable to execute against real testnet");
}
fn poll_until_result(request_key: &str, api_host: &str, max_attempts: u32, interval_seconds: u64) -> serde_json::Value {
let poll_cmd = json!({
"requestKeys": [request_key]
});
for attempt in 1..=max_attempts {
println!("Polling attempt {}/{}", attempt, max_attempts);
let poll_result = fetch::poll(&poll_cmd, api_host);
if has_transaction_result(&poll_result, request_key) {
println!("✓ Transaction found in poll result!");
return poll_result;
}
if attempt < max_attempts {
println!("No result yet, waiting {} seconds before next attempt...", interval_seconds);
thread::sleep(Duration::from_secs(interval_seconds));
}
}
println!("⚠️ Polling timeout reached after {} attempts", max_attempts);
json!({})
}
fn has_transaction_result(poll_result: &serde_json::Value, request_key: &str) -> bool {
if let Some(result_map) = poll_result.as_object() {
if let Some(tx_result) = result_map.get(request_key) {
return !tx_result.is_null() && tx_result.as_object().map_or(false, |obj| !obj.is_empty());
}
}
false
}
fn poll_for_spv_proof(spv_cmd: &serde_json::Value, api_host: &str, max_attempts: u32, interval_seconds: u64) -> serde_json::Value {
for attempt in 1..=max_attempts {
println!("SPV proof attempt {}/{}", attempt, max_attempts);
let spv_result = fetch::spv(spv_cmd, api_host);
if !has_spv_proof(&spv_result) {
if let Some(err) = spv_result.get("error") {
println!("(diagnostic) SPV response error field: {}", err);
} else if spv_result.as_object().map(|o| o.is_empty()).unwrap_or(true) {
println!("(diagnostic) Empty SPV response – likely not yet indexed. Waiting...");
} else {
println!("(diagnostic) SPV response keys: {:?}", spv_result.as_object().map(|o| o.keys().collect::<Vec<_>>()));
}
}
if has_spv_proof(&spv_result) {
println!("✓ SPV proof obtained!");
return spv_result;
}
if attempt < max_attempts {
println!("SPV proof not ready, waiting {} seconds...", interval_seconds);
thread::sleep(Duration::from_secs(interval_seconds));
}
}
println!("⚠️ SPV proof polling timeout after {} attempts", max_attempts);
json!({})
}
fn has_spv_proof(spv_result: &serde_json::Value) -> bool {
spv_result.get("proof")
.and_then(|p| p.as_str())
.map_or(false, |proof| !proof.is_empty())
}
fn extract_pact_id(poll_result: &serde_json::Value) -> Option<String> {
poll_result
.as_object()?
.values()
.next()?
.get("continuation")?
.get("pactId")?
.as_str()
.map(|s| s.to_string())
}
fn extract_spv_proof(spv_result: &serde_json::Value) -> Option<String> {
spv_result
.get("proof")?
.as_str()
.map(|s| s.to_string())
}