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::{self, GetBlockHeaderVerbose, GetBlockVerboseOne, MempoolEntry};
13use corepc_types::v31;
14use jsonrpc::Transport;
15use jsonrpc::{serde, serde_json};
16use serde::Deserialize;
17use serde_json::json;
18
19use crate::Error;
20use crate::Rpc::{self, *};
21use crate::types::{GetBlockFilter, ImportDescriptorsRequest, ImportDescriptorsResponse};
22
23#[derive(Debug)]
25pub struct Client {
26 inner: crate::Client,
28 tp: jsonrpc::simple_http::SimpleHttpTransport,
30}
31
32#[derive(Debug, Clone)]
34pub enum Auth {
35 UserPass(String, String),
37 CookieFile(PathBuf),
39}
40
41impl Auth {
42 fn get_user_pass(self) -> Result<(String, String), Error> {
44 match self {
45 Auth::UserPass(user, pass) => Ok((user, pass)),
46 Auth::CookieFile(path) => {
47 let line = BufReader::new(File::open(path)?)
48 .lines()
49 .next()
50 .ok_or(Error::InvalidCookieFile)??;
51 let colon = line.find(':').ok_or(Error::InvalidCookieFile)?;
52
53 Ok((line[..colon].to_string(), line[colon + 1..].to_string()))
54 }
55 }
56 }
57}
58
59impl Client {
60 pub fn new(url: &str, auth: Auth) -> Result<Self, Error> {
64 let (user, pass) = auth.get_user_pass()?;
65 Ok(Self::new_user_pass(url, user, Some(pass)))
66 }
67
68 pub fn new_user_pass(url: &str, user: String, pass: Option<String>) -> Self {
70 let tp = jsonrpc::simple_http::Builder::new()
71 .url(url)
72 .expect("URL check failed")
73 .timeout(std::time::Duration::from_secs(15))
74 .auth(user, pass)
75 .build();
76
77 Self {
78 inner: crate::Client::new(),
79 tp,
80 }
81 }
82
83 pub fn new_cookie_auth(url: &str, cookie: String) -> Self {
85 let tp = jsonrpc::simple_http::Builder::new()
86 .url(url)
87 .expect("URL check failed")
88 .timeout(std::time::Duration::from_secs(15))
89 .cookie_auth(cookie)
90 .build();
91
92 Self {
93 inner: crate::Client::new(),
94 tp,
95 }
96 }
97
98 fn call<T>(&self, rpc: Rpc, params: &[serde_json::Value]) -> Result<T, Error>
100 where
101 T: for<'de> Deserialize<'de>,
102 {
103 self.inner.call(rpc, params, |request| self.tp.send_request(request))
104 }
105}
106
107impl Client {
109 pub fn get_blockchain_info(&self) -> Result<model::GetBlockchainInfo, Error> {
111 let res: v31::GetBlockchainInfo = self.call(GetBlockchainInfo, &[])?;
112 res.into_model().map_err(Error::model)
113 }
114
115 pub fn get_descriptor_info(&self, descriptor: &str) -> Result<v31::GetDescriptorInfo, Error> {
117 self.call(GetDescriptorInfo, &[json!(descriptor)])
118 }
119
120 pub fn get_block_count(&self) -> Result<u32, Error> {
122 self.call(GetBlockCount, &[])
123 }
124
125 pub fn get_best_block_hash(&self) -> Result<BlockHash, Error> {
127 Ok(self.call::<String>(GetBestBlockHash, &[])?.parse()?)
128 }
129
130 pub fn get_block_hash(&self, height: u32) -> Result<BlockHash, Error> {
132 let res: String = self.call(GetBlockHash, &[json!(height)])?;
133 Ok(res.parse()?)
134 }
135
136 pub fn get_block_header(&self, hash: &BlockHash) -> Result<Header, Error> {
138 let res: v31::GetBlockHeader = self.call(GetBlockHeader, &[json!(hash), json!(false)])?;
139 Ok(res.into_model().map_err(Error::model)?.0)
140 }
141
142 pub fn get_block_header_verbose(
144 &self,
145 hash: &BlockHash,
146 ) -> Result<GetBlockHeaderVerbose, Error> {
147 let res: v31::GetBlockHeaderVerbose = self.call(GetBlockHeader, &[json!(hash)])?;
148 res.into_model().map_err(Error::model)
149 }
150
151 pub fn get_block_filter(&self, hash: &BlockHash) -> Result<GetBlockFilter, Error> {
153 use crate::types::GetBlockFilterResponse;
154 let res: GetBlockFilterResponse = self.call(GetBlockFilter, &[json!(hash)])?;
155 res.into_model().map_err(Error::model)
156 }
157
158 pub fn get_block_raw(&self, hash: &BlockHash) -> Result<String, Error> {
160 let res: v31::GetBlockVerboseZero = self.call(GetBlock, &[json!(hash), json!(0)])?;
161 Ok(res.0)
162 }
163
164 pub fn get_block(&self, hash: &BlockHash) -> Result<Block, Error> {
166 let res: v31::GetBlockVerboseZero = self.call(GetBlock, &[json!(hash), json!(0)])?;
167 res.block().map_err(Error::model)
168 }
169
170 pub fn get_raw_mempool(&self) -> Result<Vec<Txid>, Error> {
172 let res: v31::GetRawMempool = self.call(GetRawMempool, &[])?;
173 Ok(res.into_model().map_err(Error::model)?.0)
174 }
175
176 pub fn send_to_address(&self, address: &Address, amount: Amount) -> Result<Txid, Error> {
178 let res: v31::SendToAddress =
179 self.call(SendToAddress, &[json!(address), json!(amount.to_btc())])?;
180 Ok(res.txid()?)
181 }
182
183 pub fn get_raw_transaction(&self, txid: &Txid) -> Result<Transaction, Error> {
185 let res: v31::GetRawTransaction = self.call(GetRawTransaction, &[json!(txid)])?;
186 Ok(res.into_model().map_err(Error::model)?.0)
187 }
188
189 pub fn import_descriptors(
191 &self,
192 requests: &[ImportDescriptorsRequest],
193 ) -> Result<Vec<ImportDescriptorsResponse>, Error> {
194 self.call(ImportDescriptors, &[json!(requests)])
195 }
196
197 pub fn estimate_smart_fee(&self, blocks: u32) -> Result<FeeRate, Error> {
199 let res: v31::EstimateSmartFee = self.call(EstimateSmartFee, &[json!(blocks)])?;
200 if let Some(e) = res.errors.and_then(|v| v.first().cloned()) {
201 return Err(Error::Response(e));
202 }
203 let btc_kvb = res
204 .fee_rate
205 .ok_or(Error::Response("estimatesmartfee returned no fee_rate".to_string()))?;
206 if !btc_kvb.is_finite() || btc_kvb <= 0.0 {
208 return Err(Error::Response(format!("invalid feerate: {btc_kvb} BTC/kvB")));
209 }
210 if btc_kvb > Amount::MAX_MONEY.to_btc() {
212 return Err(Error::Response(format!("invalid feerate: {btc_kvb} BTC/kvB")));
213 }
214 let sat_kwu = (btc_kvb * 25_000_000.0).round() as u64;
216
217 Ok(FeeRate::from_sat_per_kwu(sat_kwu))
218 }
219}
220
221#[cfg(feature = "31_0")]
224impl Client {
225 pub fn get_block_verbose(&self, hash: &BlockHash) -> Result<GetBlockVerboseOne, Error> {
227 let res: v31::GetBlockVerboseOne = self.call(GetBlock, &[json!(hash), json!(1)])?;
228 res.into_model().map_err(Error::model)
229 }
230
231 pub fn get_raw_mempool_verbose(&self) -> Result<BTreeMap<Txid, MempoolEntry>, Error> {
233 let res: v31::GetRawMempoolVerbose = self.call(GetRawMempool, &[json!(true)])?;
234 Ok(res.into_model().map_err(Error::model)?.0)
235 }
236}
237
238#[cfg(not(feature = "31_0"))]
241use corepc_types::v30;
242
243#[cfg(not(feature = "31_0"))]
244impl Client {
245 pub fn get_block_verbose(&self, hash: &BlockHash) -> Result<GetBlockVerboseOne, Error> {
247 let res: v30::GetBlockVerboseOne = self.call(GetBlock, &[json!(hash), json!(1)])?;
248 res.into_model().map_err(Error::model)
249 }
250
251 pub fn get_raw_mempool_verbose(&self) -> Result<BTreeMap<Txid, MempoolEntry>, Error> {
253 let res: v30::GetRawMempoolVerbose = self.call(GetRawMempool, &[json!(true)])?;
254 Ok(res.into_model().map_err(Error::model)?.0)
255 }
256}