cli/commands/wallet/
status.rs

1use anyhow::{Context, Result};
2use serde_json::Value;
3
4#[derive(Debug, clap::Args)]
5pub struct Status {
6    /// Transaction hash to check
7    #[arg(long)]
8    pub hash: String,
9
10    /// Node RPC endpoint
11    #[arg(long, default_value = "http://localhost:3000")]
12    pub node: String,
13
14    /// Check if transaction is in mempool (pooled transactions)
15    #[arg(long)]
16    pub check_mempool: bool,
17}
18
19impl Status {
20    pub fn run(self) -> Result<()> {
21        println!("Checking transaction status...");
22        println!("Transaction hash: {}", self.hash);
23
24        let client = reqwest::blocking::Client::builder()
25            .timeout(std::time::Duration::from_secs(30))
26            .build()
27            .context("Failed to create HTTP client")?;
28
29        let url = format!("{}/graphql", self.node);
30
31        // First, try to find the transaction in the best chain
32        let query = serde_json::json!({
33            "query": format!(
34                r#"query {{
35                    transactionStatus(payment: "{}")
36                }}"#,
37                self.hash
38            )
39        });
40
41        let response = client
42            .post(&url)
43            .json(&query)
44            .send()
45            .context("Failed to query transaction status")?;
46
47        if !response.status().is_success() {
48            anyhow::bail!("Failed to query node: HTTP {}", response.status());
49        }
50
51        let response_json: Value = response
52            .json()
53            .context("Failed to parse GraphQL response")?;
54
55        // Check for GraphQL errors
56        if let Some(_errors) = response_json.get("errors") {
57            // Transaction might not be found in the chain yet
58            // Automatically check mempool as fallback
59            println!("\nTransaction not found in blockchain, checking mempool...");
60            return self.check_pooled_transactions(&client, &url);
61        }
62
63        // Parse transaction status
64        if let Some(status) = response_json["data"]["transactionStatus"].as_str() {
65            println!("\nTransaction Status: {}", status);
66
67            match status {
68                "INCLUDED" => {
69                    println!("āœ“ Transaction has been included in a block");
70                }
71                "PENDING" => {
72                    println!("ā³ Transaction is pending inclusion");
73                }
74                "UNKNOWN" => {
75                    println!("? Transaction status is unknown");
76                    if !self.check_mempool {
77                        println!(
78                            "\nTry using --check-mempool to check if it's in the transaction pool"
79                        );
80                    }
81                }
82                _ => {
83                    println!("Status: {}", status);
84                }
85            }
86        } else {
87            println!("Unable to determine transaction status");
88        }
89
90        Ok(())
91    }
92
93    fn check_pooled_transactions(
94        &self,
95        client: &reqwest::blocking::Client,
96        url: &str,
97    ) -> Result<()> {
98        let query = serde_json::json!({
99            "query": r#"query {
100                pooledUserCommands {
101                    hash
102                    from
103                    to
104                    amount
105                    fee
106                    nonce
107                }
108            }"#
109        });
110
111        let response = client
112            .post(url)
113            .json(&query)
114            .send()
115            .context("Failed to query pooled transactions")?;
116
117        if !response.status().is_success() {
118            anyhow::bail!("Failed to query mempool: HTTP {}", response.status());
119        }
120
121        let response_json: Value = response
122            .json()
123            .context("Failed to parse GraphQL response")?;
124
125        if let Some(pooled) = response_json["data"]["pooledUserCommands"].as_array() {
126            // Look for our transaction in the pool
127            for tx in pooled {
128                if let Some(hash) = tx["hash"].as_str() {
129                    if hash == self.hash {
130                        println!("\nāœ“ Transaction found in mempool!");
131                        println!("\nTransaction Details:");
132                        println!("  Hash:   {}", hash);
133                        if let Some(from) = tx["from"].as_str() {
134                            println!("  From:   {}", from);
135                        }
136                        if let Some(to) = tx["to"].as_str() {
137                            println!("  To:     {}", to);
138                        }
139                        if let Some(amount) = tx["amount"].as_str() {
140                            println!("  Amount: {} nanomina", amount);
141                        }
142                        if let Some(fee) = tx["fee"].as_str() {
143                            println!("  Fee:    {} nanomina", fee);
144                        }
145                        // Nonce can be either string or number depending on the node
146                        if let Some(nonce) = tx["nonce"].as_u64() {
147                            println!("  Nonce:  {}", nonce);
148                        } else if let Some(nonce) = tx["nonce"].as_str() {
149                            println!("  Nonce:  {}", nonce);
150                        }
151
152                        println!("\nStatus: PENDING (waiting to be included in a block)");
153                        return Ok(());
154                    }
155                }
156            }
157
158            println!("\nāœ— Transaction not found in mempool");
159            println!("\nThe transaction may have:");
160            println!("  - Already been included in a block");
161            println!("  - Been rejected by the network");
162            println!("  - Not yet propagated to this node");
163        }
164
165        Ok(())
166    }
167}