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