1use std::collections::BTreeMap;
4use std::fs::File;
5use std::io::{BufRead, BufReader};
6use std::path::PathBuf;
7use std::string::{String, ToString};
8use std::vec::Vec;
9
10use bitcoin::{Address, Amount, Block, BlockHash, FeeRate, Transaction, Txid, block::Header};
11use corepc_types::bitcoin;
12use corepc_types::model::MempoolEntry;
13#[cfg(not(feature = "28_0"))]
14use corepc_types::model::{GetBlockHeaderVerbose, GetBlockVerboseOne, GetBlockchainInfo};
15use corepc_types::v29;
16use jsonrpc::Transport;
17use jsonrpc::{serde, serde_json};
18use serde::Deserialize;
19use serde_json::json;
20
21use crate::Error;
22use crate::Rpc::{self, *};
23use crate::types::{GetBlockFilter, ImportDescriptorsRequest, ImportDescriptorsResponse};
24
25#[cfg(feature = "28_0")]
26pub mod v28;
27
28#[derive(Debug)]
30pub struct Client {
31 inner: crate::Client,
33 tp: jsonrpc::simple_http::SimpleHttpTransport,
35}
36
37#[derive(Debug, Clone)]
39pub enum Auth {
40 UserPass(String, String),
42 CookieFile(PathBuf),
44}
45
46impl Auth {
47 fn get_user_pass(self) -> Result<(String, String), Error> {
49 match self {
50 Auth::UserPass(user, pass) => Ok((user, pass)),
51 Auth::CookieFile(path) => {
52 let line = BufReader::new(File::open(path)?)
53 .lines()
54 .next()
55 .ok_or(Error::InvalidCookieFile)??;
56 let colon = line.find(':').ok_or(Error::InvalidCookieFile)?;
57
58 Ok((line[..colon].to_string(), line[colon + 1..].to_string()))
59 }
60 }
61 }
62}
63
64impl Client {
65 pub fn new(url: &str, auth: Auth) -> Result<Self, Error> {
69 let (user, pass) = auth.get_user_pass()?;
70 Ok(Self::new_user_pass(url, user, Some(pass)))
71 }
72
73 pub fn new_user_pass(url: &str, user: String, pass: Option<String>) -> Self {
75 let tp = jsonrpc::simple_http::Builder::new()
76 .url(url)
77 .expect("URL check failed")
78 .timeout(std::time::Duration::from_secs(15))
79 .auth(user, pass)
80 .build();
81
82 Self {
83 inner: crate::Client::new(),
84 tp,
85 }
86 }
87
88 pub fn new_cookie_auth(url: &str, cookie: String) -> Self {
90 let tp = jsonrpc::simple_http::Builder::new()
91 .url(url)
92 .expect("URL check failed")
93 .timeout(std::time::Duration::from_secs(15))
94 .cookie_auth(cookie)
95 .build();
96
97 Self {
98 inner: crate::Client::new(),
99 tp,
100 }
101 }
102
103 fn call<T>(&self, rpc: Rpc, params: &[serde_json::Value]) -> Result<T, Error>
105 where
106 T: for<'de> Deserialize<'de>,
107 {
108 self.inner.call(rpc, params, |request| self.tp.send_request(request))
109 }
110}
111
112impl Client {
114 pub fn get_block_count(&self) -> Result<u32, Error> {
116 self.call(GetBlockCount, &[])
117 }
118
119 pub fn get_best_block_hash(&self) -> Result<BlockHash, Error> {
121 Ok(self.call::<String>(GetBestBlockHash, &[])?.parse()?)
122 }
123
124 pub fn get_block_hash(&self, height: u32) -> Result<BlockHash, Error> {
126 let res: String = self.call(GetBlockHash, &[json!(height)])?;
127 Ok(res.parse()?)
128 }
129
130 pub fn get_block_header(&self, hash: &BlockHash) -> Result<Header, Error> {
132 let res: v29::GetBlockHeader = self.call(GetBlockHeader, &[json!(hash), json!(false)])?;
133 Ok(res.into_model().map_err(Error::model)?.0)
134 }
135
136 pub fn get_block_filter(&self, hash: &BlockHash) -> Result<GetBlockFilter, Error> {
138 use crate::types::GetBlockFilterResponse;
139 let res: GetBlockFilterResponse = self.call(GetBlockFilter, &[json!(hash)])?;
140 res.into_model().map_err(Error::model)
141 }
142
143 pub fn get_block_raw(&self, hash: &BlockHash) -> Result<String, Error> {
145 let res: v29::GetBlockVerboseZero = self.call(GetBlock, &[json!(hash), json!(0)])?;
146 Ok(res.0)
147 }
148
149 pub fn get_block(&self, hash: &BlockHash) -> Result<Block, Error> {
151 let res: v29::GetBlockVerboseZero = self.call(GetBlock, &[json!(hash), json!(0)])?;
152 res.block().map_err(Error::model)
153 }
154
155 pub fn get_raw_mempool(&self) -> Result<Vec<Txid>, Error> {
157 let res: v29::GetRawMempool = self.call(GetRawMempool, &[])?;
158 Ok(res.into_model().map_err(Error::model)?.0)
159 }
160
161 pub fn get_raw_mempool_verbose(&self) -> Result<BTreeMap<Txid, MempoolEntry>, Error> {
163 let res: v29::GetRawMempoolVerbose = self.call(GetRawMempool, &[json!(true)])?;
164 Ok(res.into_model().map_err(Error::model)?.0)
165 }
166
167 pub fn send_to_address(&self, address: &Address, amount: Amount) -> Result<Txid, Error> {
169 let res: v29::SendToAddress =
170 self.call(SendToAddress, &[json!(address), json!(amount.to_btc())])?;
171 Ok(res.txid()?)
172 }
173
174 pub fn get_raw_transaction(&self, txid: &Txid) -> Result<Transaction, Error> {
176 let res: v29::GetRawTransaction = self.call(GetRawTransaction, &[json!(txid)])?;
177 Ok(res.into_model().map_err(Error::model)?.0)
178 }
179
180 pub fn import_descriptors(
182 &self,
183 requests: &[ImportDescriptorsRequest],
184 ) -> Result<Vec<ImportDescriptorsResponse>, Error> {
185 self.call(ImportDescriptors, &[json!(requests)])
186 }
187
188 pub fn estimate_smart_fee(&self, blocks: u32) -> Result<FeeRate, Error> {
190 let res: v29::EstimateSmartFee = self.call(EstimateSmartFee, &[json!(blocks)])?;
191 if let Some(e) = res.errors.and_then(|v| v.first().cloned()) {
192 return Err(Error::Response(e));
193 }
194 let btc_kvb = res
195 .fee_rate
196 .ok_or(Error::Response("estimatesmartfee returned no fee_rate".to_string()))?;
197 if !btc_kvb.is_finite() || btc_kvb <= 0.0 {
199 return Err(Error::Response(format!("invalid feerate: {btc_kvb} BTC/kvB")));
200 }
201 if btc_kvb > Amount::MAX_MONEY.to_btc() {
203 return Err(Error::Response(format!("invalid feerate: {btc_kvb} BTC/kvB")));
204 }
205 let sat_kwu = (btc_kvb * 25_000_000.0).round() as u64;
207
208 Ok(FeeRate::from_sat_per_kwu(sat_kwu))
209 }
210}
211
212#[cfg(not(feature = "28_0"))]
213impl Client {
214 pub fn get_blockchain_info(&self) -> Result<GetBlockchainInfo, Error> {
216 let res: v29::GetBlockchainInfo = self.call(GetBlockchainInfo, &[])?;
217 res.into_model().map_err(Error::model)
218 }
219
220 pub fn get_block_header_verbose(
222 &self,
223 hash: &BlockHash,
224 ) -> Result<GetBlockHeaderVerbose, Error> {
225 let res: v29::GetBlockHeaderVerbose = self.call(GetBlockHeader, &[json!(hash)])?;
226 res.into_model().map_err(Error::model)
227 }
228
229 pub fn get_block_verbose(&self, hash: &BlockHash) -> Result<GetBlockVerboseOne, Error> {
231 let res: v29::GetBlockVerboseOne = self.call(GetBlock, &[json!(hash), json!(1)])?;
232 res.into_model().map_err(Error::model)
233 }
234
235 pub fn get_descriptor_info(&self, descriptor: &str) -> Result<v29::GetDescriptorInfo, Error> {
237 self.call(GetDescriptorInfo, &[json!(descriptor)])
238 }
239}