Skip to main content

blockchain_client/providers/
jsonrpc.rs

1use async_trait::async_trait;
2use reqwest::Client;
3use serde_json::json;
4use std::sync::Arc;
5
6use crate::client::{JsonRpcRequest, JsonRpcResponse, RpcConfig};
7use crate::error::{Result, RpcError};
8use crate::traits::BlockchainClient;
9use crate::types::*;
10
11#[derive(Clone)]
12pub struct JsonRpcClient {
13    config: Arc<RpcConfig>,
14    http_client: Client,
15    chain: Chain,
16    network: Network,
17}
18
19impl JsonRpcClient {
20    pub fn new(config: RpcConfig, chain: Chain, network: Network) -> Result<Self> {
21        let http_client = Client::builder()
22            .timeout(config.timeout())
23            .pool_max_idle_per_host(config.pool_max_idle_per_host)
24            .pool_idle_timeout(config.pool_idle_timeout())
25            .tcp_keepalive(config.tcp_keepalive())
26            .build()?;
27
28        Ok(Self {
29            config: Arc::new(config),
30            http_client,
31            chain,
32            network,
33        })
34    }
35
36    pub fn chain(&self) -> Chain {
37        self.chain
38    }
39
40    pub fn network(&self) -> Network {
41        self.network
42    }
43
44    async fn call<T: serde::de::DeserializeOwned>(
45        &self,
46        method: &str,
47        params: serde_json::Value,
48    ) -> Result<T> {
49        let request = JsonRpcRequest::new(method.to_string(), params);
50
51        let response = self
52            .http_client
53            .post(&self.config.url)
54            .basic_auth(&self.config.username, Some(&self.config.password))
55            .json(&request)
56            .send()
57            .await?;
58
59        let status = response.status();
60
61        if !status.is_success() {
62            let body = response.text().await.unwrap_or_default();
63            return Err(RpcError::RequestFailed(format!(
64                "http {}: {}",
65                status, body
66            )));
67        }
68
69        let text = response.text().await?;
70        let rpc_response: JsonRpcResponse<T> = serde_json::from_str(&text)?;
71
72        if let Some(error) = rpc_response.error {
73            return Err(RpcError::RpcResponseError {
74                code: error.code,
75                message: error.message,
76            });
77        }
78
79        rpc_response
80            .result
81            .ok_or_else(|| RpcError::InvalidResponse("missing result field".to_string()))
82    }
83}
84
85#[async_trait]
86impl BlockchainClient for JsonRpcClient {
87    async fn get_blockchain_info(&self) -> Result<BlockchainInfo> {
88        self.call("getblockchaininfo", json!([])).await
89    }
90
91    async fn get_raw_transaction_with_block(
92        &self,
93        txid: &str,
94        verbose: bool,
95        blockhash: Option<&str>,
96    ) -> Result<RawTransaction> {
97        let params = if let Some(hash) = blockhash {
98            json!([txid, if verbose { 1 } else { 0 }, hash])
99        } else {
100            json!([txid, if verbose { 1 } else { 0 }])
101        };
102
103        match self
104            .call::<RawTransaction>("getrawtransaction", params)
105            .await
106        {
107            Ok(tx) => Ok(tx),
108
109            Err(RpcError::RpcResponseError { code: -5, .. }) => {
110                // Standard “No such mempool or blockchain transaction. Use gettransaction…” case.[web:42][web:45][web:47]
111                let wallet_tx: serde_json::Value =
112                    self.call("gettransaction", json!([txid])).await?;
113
114                if let Some(hex) = wallet_tx.get("hex").and_then(|h| h.as_str()) {
115                    self.call("decoderawtransaction", json!([hex])).await
116                } else {
117                    Err(RpcError::InvalidResponse(
118                        "gettransaction response missing hex field".to_string(),
119                    ))
120                }
121            }
122
123            Err(e) => Err(e),
124        }
125    }
126
127    async fn list_unspent(
128        &self,
129        min_conf: Option<u32>,
130        max_conf: Option<u32>,
131        addresses: Option<&[String]>,
132    ) -> Result<Vec<Utxo>> {
133        let params = json!([
134            min_conf.unwrap_or(1),
135            max_conf.unwrap_or(9_999_999),
136            addresses.unwrap_or(&[])
137        ]);
138
139        self.call("listunspent", params).await
140    }
141
142    async fn list_transactions(
143        &self,
144        label: Option<&str>,
145        count: Option<usize>,
146        skip: Option<usize>,
147        include_watchonly: bool,
148    ) -> Result<Vec<TransactionListItem>> {
149        let params = json!([
150            label.unwrap_or("*"),
151            count.unwrap_or(10),
152            skip.unwrap_or(0),
153            include_watchonly
154        ]);
155
156        self.call("listtransactions", params).await
157    }
158
159    async fn get_received_by_address(&self, address: &str, min_conf: Option<u32>) -> Result<f64> {
160        let params = json!([address, min_conf.unwrap_or(1)]);
161        self.call("getreceivedbyaddress", params).await
162    }
163
164    async fn list_received_by_address(
165        &self,
166        min_conf: Option<u32>,
167        include_empty: bool,
168        include_watchonly: bool,
169    ) -> Result<Vec<ReceivedByAddress>> {
170        let params = json!([min_conf.unwrap_or(1), include_empty, include_watchonly]);
171        self.call("listreceivedbyaddress", params).await
172    }
173
174    async fn is_address_watched(&self, address: &str) -> Result<bool> {
175        let validation = self.validate_address(address).await?;
176
177        if !validation.isvalid {
178            return Ok(false);
179        }
180
181        if validation.ismine == Some(true) || validation.iswatchonly == Some(true) {
182            return Ok(true);
183        }
184
185        let received = self.list_received_by_address(Some(0), true, true).await?;
186
187        Ok(received.iter().any(|r| r.address == address))
188    }
189
190    async fn import_address(&self, address: &str, label: Option<&str>, rescan: bool) -> Result<()> {
191        let params = json!([address, label.unwrap_or(""), rescan]);
192        self.call::<serde_json::Value>("importaddress", params)
193            .await?;
194        Ok(())
195    }
196
197    async fn validate_address(&self, address: &str) -> Result<AddressValidation> {
198        self.call("validateaddress", json!([address])).await
199    }
200
201    async fn get_transaction(&self, txid: &str) -> Result<serde_json::Value> {
202        self.call("gettransaction", json!([txid])).await
203    }
204
205    async fn get_block_count(&self) -> Result<u64> {
206        self.call("getblockcount", json!([])).await
207    }
208
209    async fn get_best_block_hash(&self) -> Result<String> {
210        self.call("getbestblockhash", json!([])).await
211    }
212}