1mod crypto;
2mod serialization;
3mod types;
4
5use base64::{Engine, prelude::BASE64_STANDARD};
6pub use crypto::*;
7pub use types::*;
8
9use anyhow::{Result, anyhow};
10use reqwest::{Client, Response};
11use serde::{Deserialize, Serialize};
12use serde_json::{Value, json};
13use std::collections::HashMap;
14
15#[derive(Debug, Clone)]
16pub struct BitcoinClient {
17 client: Client,
18 url: String,
19 auth: String,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23struct BitcoinNetWorkRequest {
24 jsonrpc: String,
25 id: u64,
26 method: String,
27 params: Value,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31struct BitcoinNetWorkResponse<T> {
32 jsonrpc: String,
33 id: u64,
34 result: Option<T>,
35 error: Option<RpcError>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39struct RpcError {
40 code: i32,
41 message: String,
42}
43
44impl BitcoinClient {
45 pub fn new(url: &str, username: &str, password: &str) -> Self {
46 let client = Client::builder()
47 .timeout(std::time::Duration::from_secs(30))
48 .build()
49 .unwrap();
50 let auth = format!("{}:{}", username, password);
51 let auth = format!("Basic {}", BASE64_STANDARD.encode(&auth));
52 BitcoinClient {
53 client,
54 url: url.to_string(),
55 auth,
56 }
57 }
58
59 pub fn new_local(network: BitcoinClientType) -> Self {
60 let (url, port) = match network {
61 BitcoinClientType::Mainnet => ("http://127.0.0.1", 8332),
62 BitcoinClientType::Testnet => ("http://127.0.0.1", 18332),
63 BitcoinClientType::Signet => ("http://127.0.0.1", 38332),
64 BitcoinClientType::Regtest => ("http://127.0.0.1", 18443),
65 };
66 Self::new(&format!("{}:{}", url, port), "bitcoin", "password")
67 }
68
69 async fn call<T: for<'de> Deserialize<'de>>(&self, method: &str, params: Value) -> Result<T> {
70 let request = BitcoinNetWorkRequest {
71 jsonrpc: "2.0".to_string(),
72 id: 1,
73 method: method.to_string(),
74 params,
75 };
76 let response: Response = self
77 .client
78 .post(&self.url)
79 .header("Authorization", &self.auth)
80 .header("Content-Type", "application/json")
81 .json(&request)
82 .send()
83 .await?;
84 if !response.status().is_success() {
85 let status = response.status();
86 let text = response.text().await.unwrap_or_default();
87 return Err(anyhow!("HTTP error {}: {}", status, text));
88 }
89 let rpc_response: BitcoinNetWorkResponse<T> = response.json().await?;
90 if let Some(error) = rpc_response.error {
91 return Err(anyhow!("RPC error {}: {}", error.code, error.message));
92 }
93 rpc_response
94 .result
95 .ok_or_else(|| anyhow!("No result in response"))
96 }
97
98 pub async fn get_blockchain_info(&self) -> Result<BlockchainInfo> {
99 self.call("getblockchaininfo", Value::Null).await
100 }
101
102 pub async fn get_block_count(&self) -> Result<u64> {
103 self.call("getblockcount", Value::Null).await
104 }
105
106 pub async fn get_best_block_hash(&self) -> Result<String> {
107 self.call("getbestblockhash", Value::Null).await
108 }
109
110 pub async fn get_block(&self, block_hash: &str, verbosity: u8) -> Result<Block> {
111 self.call("getblock", json!([block_hash, verbosity])).await
112 }
113
114 pub async fn get_block_hash(&self, height: u64) -> Result<String> {
115 self.call("getblockhash", json!([height])).await
116 }
117
118 pub async fn get_block_header(&self, block_hash: &str, verbose: bool) -> Result<BlockHeader> {
119 self.call("getblockheader", json!([block_hash, verbose]))
120 .await
121 }
122
123 pub async fn get_chain_tips(&self) -> Result<Vec<ChainTip>> {
124 self.call("getchaintips", Value::Null).await
125 }
126
127 pub async fn get_difficulty(&self) -> Result<f64> {
128 self.call("getdifficulty", Value::Null).await
129 }
130
131 pub async fn get_raw_transaction(&self, txid: &str, verbose: bool) -> Result<Transaction> {
132 self.call("getrawtransaction", json!([txid, verbose])).await
133 }
134
135 pub async fn decode_raw_transaction(&self, tx_hex: &str) -> Result<DecodedTransaction> {
136 self.call("decoderawtransaction", json!([tx_hex])).await
137 }
138
139 pub async fn send_raw_transaction(&self, tx_hex: &str) -> Result<String> {
140 self.call("sendrawtransaction", json!([tx_hex])).await
141 }
142
143 pub async fn get_tx_out(
144 &self,
145 txid: &str,
146 vout: u32,
147 include_mempool: bool,
148 ) -> Result<Option<TxOut>> {
149 self.call("gettxout", json!([txid, vout, include_mempool]))
150 .await
151 }
152
153 pub async fn get_tx_out_set_info(&self) -> Result<TxOutSetInfo> {
154 self.call("gettxoutsetinfo", Value::Null).await
155 }
156
157 pub async fn get_wallet_info(&self) -> Result<WalletInfo> {
158 self.call("getwalletinfo", Value::Null).await
159 }
160
161 pub async fn get_balance(
162 &self,
163 dummy: &str,
164 min_conf: i32,
165 include_watchonly: bool,
166 ) -> Result<f64> {
167 self.call("getbalance", json!([dummy, min_conf, include_watchonly]))
168 .await
169 }
170
171 pub async fn get_new_address(
172 &self,
173 label: Option<&str>,
174 address_type: Option<&str>,
175 ) -> Result<String> {
176 let params = match (label, address_type) {
177 (Some(l), Some(t)) => json!([l, t]),
178 (Some(l), None) => json!([l]),
179 _ => Value::Null,
180 };
181 self.call("getnewaddress", params).await
182 }
183
184 pub async fn get_addresses_by_label(
185 &self,
186 label: &str,
187 ) -> Result<HashMap<String, AddressInfo>> {
188 self.call("getaddressesbylabel", json!([label])).await
189 }
190
191 pub async fn validate_address(&self, address: &str) -> Result<ValidateAddress> {
192 self.call("validateaddress", json!([address])).await
193 }
194
195 pub async fn send_to_address(&self, address: &str, amount: f64) -> Result<String> {
196 self.call("sendtoaddress", json!([address, amount])).await
197 }
198
199 pub async fn list_unspent(
200 &self,
201 min_conf: i32,
202 max_conf: i32,
203 addresses: Option<Vec<&str>>,
204 ) -> Result<Vec<Utxo>> {
205 let params = match addresses {
206 Some(addrs) => json!([min_conf, max_conf, addrs]),
207 None => json!([min_conf, max_conf]),
208 };
209 self.call("listunspent", params).await
210 }
211
212 pub async fn get_network_info(&self) -> Result<NetworkInfo> {
213 self.call("getnetworkinfo", Value::Null).await
214 }
215
216 pub async fn get_peer_info(&self) -> Result<Vec<PeerInfo>> {
217 self.call("getpeerinfo", Value::Null).await
218 }
219
220 pub async fn get_network_hash_ps(
221 &self,
222 nblocks: Option<i32>,
223 height: Option<i32>,
224 ) -> Result<f64> {
225 let params = match (nblocks, height) {
226 (Some(n), Some(h)) => json!([n, h]),
227 (Some(n), None) => json!([n]),
228 _ => Value::Null,
229 };
230 self.call("getnetworkhashps", params).await
231 }
232
233 pub async fn get_mempool_info(&self) -> Result<MempoolInfo> {
234 self.call("getmempoolinfo", Value::Null).await
235 }
236
237 pub async fn get_raw_mempool(&self, verbose: bool) -> Result<Vec<String>> {
238 self.call("getrawmempool", json!([verbose])).await
239 }
240
241 pub async fn estimate_smart_fee(&self, conf_target: i32) -> Result<FeeEstimate> {
242 self.call("estimatesmartfee", json!([conf_target])).await
243 }
244
245 pub async fn get_mining_info(&self) -> Result<MiningInfo> {
246 self.call("getmininginfo", Value::Null).await
247 }
248
249 pub async fn generate_to_address(&self, nblocks: u32, address: &str) -> Result<Vec<String>> {
250 self.call("generatetoaddress", json!([nblocks, address]))
251 .await
252 }
253
254 pub async fn create_raw_transaction(
255 &self,
256 inputs: Vec<CreateTxInput>,
257 outputs: HashMap<String, f64>,
258 ) -> Result<String> {
259 self.call("createrawtransaction", json!([inputs, outputs]))
260 .await
261 }
262
263 pub async fn sign_raw_transaction_with_wallet(
264 &self,
265 tx_hex: &str,
266 ) -> Result<SignedTransaction> {
267 self.call("signrawtransactionwithwallet", json!([tx_hex]))
268 .await
269 }
270
271 pub async fn get_block_stats(&self, height: u64) -> Result<BlockStats> {
272 self.call("getblockstats", json!([height])).await
273 }
274
275 pub async fn batch_call(&self, requests: Vec<(String, Value)>) -> Result<Vec<Value>> {
276 let batch_requests: Vec<BitcoinNetWorkRequest> = requests
277 .into_iter()
278 .enumerate()
279 .map(|(i, (method, params))| BitcoinNetWorkRequest {
280 jsonrpc: "2.0".to_string(),
281 id: i as u64,
282 method,
283 params,
284 })
285 .collect();
286 let response: Response = self
287 .client
288 .post(&self.url)
289 .header("Authorization", &self.auth)
290 .header("Content-Type", "application/json")
291 .json(&batch_requests)
292 .send()
293 .await?;
294
295 if !response.status().is_success() {
296 return Err(anyhow!("HTTP error: {}", response.status()));
297 }
298 let responses: Vec<BitcoinNetWorkResponse<Value>> = response.json().await?;
299 let mut results = Vec::new();
300 for response in responses {
301 if let Some(error) = response.error {
302 return Err(anyhow!("RPC error {}: {}", error.code, error.message));
303 }
304 results.push(response.result.unwrap_or(Value::Null));
305 }
306 Ok(results)
307 }
308}