1use rust_pact::{tools, fetch};
2use rust_pact::utils::KeyPair;
3use serde_json::json;
4use std::time::Duration;
5use std::thread;
6
7fn main() {
10 let run_live = std::env::var("RUN_LIVE").unwrap_or_default() == "1";
12 let attempts: u32 = std::env::var("XCHAIN_ATTEMPTS").ok().and_then(|v| v.parse().ok()).unwrap_or(30);
14 let spv_attempts: u32 = std::env::var("XCHAIN_SPV_ATTEMPTS").ok().and_then(|v| v.parse().ok()).unwrap_or(30);
15 let interval_secs: u64 = std::env::var("XCHAIN_INTERVAL").ok().and_then(|v| v.parse().ok()).unwrap_or(5);
16 let spv_interval_secs: u64 = std::env::var("XCHAIN_SPV_INTERVAL").ok().and_then(|v| v.parse().ok()).unwrap_or(interval_secs);
17 let post_confirm_wait: u64 = std::env::var("XCHAIN_POST_CONFIRM_WAIT").ok().and_then(|v| v.parse().ok()).unwrap_or(10);
18
19 if run_live {
20 println!("🌐 RUN_LIVE=1 detected - will make real network calls to testnet");
21 println!("⚠️ WARNING: This will attempt actual blockchain transactions!");
22 } else {
23 println!("🔒 Demo mode - using mock responses (set RUN_LIVE=1 for real calls)");
24 }
25
26 let sender_keypair = KeyPair {
28 public_key: "10375651f1ca0110468152bb8f47b7b8a469e36dfab1c83adf60cab84b5726d3".into(),
29 secret_key: "18d3a823139cf60cab0b738e7605bb9e4a2f3ff245c270fa55d197f9b3c4c004".into(),
30 clist: None
31 };
32
33 let receiver_keypair = KeyPair {
34 public_key: "03df480e0b300c52901fdff265f0460913fea495f39972321698740536cc38e3".into(),
35 secret_key: "".into(), clist: None
37 };
38
39 let token_address = "coin";
40 let sender_account = "k:10375651f1ca0110468152bb8f47b7b8a469e36dfab1c83adf60cab84b5726d3";
41 let receiver_account = "k:03df480e0b300c52901fdff265f0460913fea495f39972321698740536cc38e3";
42 let receiver_public_key = "03df480e0b300c52901fdff265f0460913fea495f39972321698740536cc38e3";
43 let amount = 1.0_f64;
44 let source_chain_id = "1"; let target_chain_id = "2"; let network_id = "testnet04";
47
48 println!("=== Cross-Chain Transfer Example ===\n");
49
50 println!("Step 1: Initiating cross-chain transfer from chain {} to chain {}", source_chain_id, target_chain_id);
52 println!("Amount: {} {}", amount, token_address);
53 println!("From: {}", sender_account);
54 println!("To: {}", receiver_account);
55
56 let transfer_result = if run_live {
57 tools::crosschain_transfer(
58 token_address,
59 sender_account,
60 receiver_account,
61 receiver_public_key,
62 amount,
63 sender_keypair.clone(),
64 source_chain_id,
65 target_chain_id,
66 network_id,
67 None
68 )
69 } else {
70 json!({
72 "requestKeys": ["KnTrT06r4gLKuA56fRMZOCBjS1MiSNoCGDO8g8VxYVI"]
73 })
74 };
75
76 println!("Transfer initiation result: {}", serde_json::to_string_pretty(&transfer_result).unwrap());
77
78 if let Some(request_keys) = transfer_result.get("requestKeys").and_then(|rks| rks.as_array()) {
80 if let Some(request_key) = request_keys.get(0).and_then(|rk| rk.as_str()) {
81 println!("\nTransaction hash (request key): {}", request_key);
82
83 println!("\nStep 2: Polling for transaction completion on source chain...");
85 let api_host = tools::get_api_host(network_id, source_chain_id);
86
87 let poll_result = if run_live {
88 poll_until_result(request_key, &api_host, attempts, interval_secs)
89 } else {
90 json!({
92 request_key: {
93 "result": {
94 "status": "success",
95 "data": "Cross-chain transfer initiated"
96 },
97 "continuation": {
98 "pactId": "mock-pact-id-123",
99 "step": 0
100 }
101 }
102 })
103 };
104 println!("Final poll result: {}", serde_json::to_string_pretty(&poll_result).unwrap());
105
106 if let Some(pact_id) = extract_pact_id(&poll_result) {
108 println!("\nPact ID for continuation: {}", pact_id);
109
110 println!("\nStep 4: Getting SPV proof for cross-chain completion...");
112 if run_live && post_confirm_wait > 0 {
113 println!("Waiting {}s after confirmation before requesting SPV (XCHAIN_POST_CONFIRM_WAIT)", post_confirm_wait);
114 thread::sleep(Duration::from_secs(post_confirm_wait));
115 }
116 let spv_cmd = json!({
117 "requestKey": request_key,
118 "targetChainId": target_chain_id
119 });
120
121 let spv_result = if run_live {
122 poll_for_spv_proof(&spv_cmd, &api_host, spv_attempts, spv_interval_secs)
123 } else {
124 json!({
126 "proof": "mock-spv-proof-data-for-demo"
127 })
128 };
129 println!("SPV result: {}", serde_json::to_string_pretty(&spv_result).unwrap());
130
131 if let Some(proof) = extract_spv_proof(&spv_result) {
133 println!("\nStep 5: Completing cross-chain transfer on target chain...");
134
135 let complete_result = if run_live {
136 tools::crosschain_complete(
137 &pact_id,
138 &proof,
139 receiver_account,
140 receiver_public_key,
141 amount,
142 receiver_keypair.clone(),
143 target_chain_id,
144 network_id
145 )
146 } else {
147 json!({
149 "requestKeys": ["completion-tx-hash-456"],
150 "result": "Cross-chain transfer completed successfully"
151 })
152 };
153
154 println!("Cross-chain completion result: {}", serde_json::to_string_pretty(&complete_result).unwrap());
155 } else {
156 println!("Could not extract SPV proof from result");
157 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");
158 }
159 } else {
160 println!("Could not extract pact ID from poll result");
161 }
162 }
163 } else {
164 println!("No request keys found in transfer result");
165 }
166
167 println!("\n=== Example Notes ===");
168 println!("1. This example shows the complete cross-chain transfer flow");
169 println!("2. In production, you would need to wait for block confirmations between steps");
170 println!("3. SPV proofs are generated by the blockchain network after transactions are finalized");
171 println!("4. The receiver keypair only needs the public key for the completion step");
172 println!("5. Set RUN_LIVE=1 environment variable to execute against real testnet");
173}
174
175fn poll_until_result(request_key: &str, api_host: &str, max_attempts: u32, interval_seconds: u64) -> serde_json::Value {
177 let poll_cmd = json!({
178 "requestKeys": [request_key]
179 });
180
181 for attempt in 1..=max_attempts {
182 println!("Polling attempt {}/{}", attempt, max_attempts);
183
184 let poll_result = fetch::poll(&poll_cmd, api_host);
185
186 if has_transaction_result(&poll_result, request_key) {
188 println!("✓ Transaction found in poll result!");
189 return poll_result;
190 }
191
192 if attempt < max_attempts {
193 println!("No result yet, waiting {} seconds before next attempt...", interval_seconds);
194 thread::sleep(Duration::from_secs(interval_seconds));
195 }
196 }
197
198 println!("⚠️ Polling timeout reached after {} attempts", max_attempts);
199 json!({})
200}
201
202fn has_transaction_result(poll_result: &serde_json::Value, request_key: &str) -> bool {
204 if let Some(result_map) = poll_result.as_object() {
205 if let Some(tx_result) = result_map.get(request_key) {
206 return !tx_result.is_null() && tx_result.as_object().map_or(false, |obj| !obj.is_empty());
208 }
209 }
210 false
211}
212
213fn poll_for_spv_proof(spv_cmd: &serde_json::Value, api_host: &str, max_attempts: u32, interval_seconds: u64) -> serde_json::Value {
220 for attempt in 1..=max_attempts {
221 println!("SPV proof attempt {}/{}", attempt, max_attempts);
222
223 let spv_result = fetch::spv(spv_cmd, api_host);
224
225 if !has_spv_proof(&spv_result) {
227 if let Some(err) = spv_result.get("error") {
228 println!("(diagnostic) SPV response error field: {}", err);
229 } else if spv_result.as_object().map(|o| o.is_empty()).unwrap_or(true) {
230 println!("(diagnostic) Empty SPV response – likely not yet indexed. Waiting...");
231 } else {
232 println!("(diagnostic) SPV response keys: {:?}", spv_result.as_object().map(|o| o.keys().collect::<Vec<_>>()));
233 }
234 }
235
236 if has_spv_proof(&spv_result) {
238 println!("✓ SPV proof obtained!");
239 return spv_result;
240 }
241
242 if attempt < max_attempts {
243 println!("SPV proof not ready, waiting {} seconds...", interval_seconds);
244 thread::sleep(Duration::from_secs(interval_seconds));
245 }
246 }
247
248 println!("⚠️ SPV proof polling timeout after {} attempts", max_attempts);
249 json!({})
250}
251
252fn has_spv_proof(spv_result: &serde_json::Value) -> bool {
254 spv_result.get("proof")
255 .and_then(|p| p.as_str())
256 .map_or(false, |proof| !proof.is_empty())
257}
258
259fn extract_pact_id(poll_result: &serde_json::Value) -> Option<String> {
260 poll_result
262 .as_object()?
263 .values()
264 .next()?
265 .get("continuation")?
266 .get("pactId")?
267 .as_str()
268 .map(|s| s.to_string())
269}
270
271fn extract_spv_proof(spv_result: &serde_json::Value) -> Option<String> {
272 spv_result
274 .get("proof")?
275 .as_str()
276 .map(|s| s.to_string())
277}