bitcoind_async_client/client/
v29.rs

1//! This module contains the implementation of the [`Client`] for Bitcoin Core v29.
2
3use std::env::var;
4
5use bitcoin::{
6    bip32::Xpriv,
7    block::Header,
8    consensus::{self, encode::serialize_hex},
9    Address, Block, BlockHash, Network, Transaction, Txid,
10};
11use corepc_types::model;
12use corepc_types::v29::{
13    CreateWallet, GetAddressInfo, GetBlockHeader, GetBlockVerboseOne, GetBlockVerboseZero,
14    GetBlockchainInfo, GetMempoolInfo, GetNewAddress, GetRawMempool, GetRawMempoolVerbose,
15    GetRawTransaction, GetRawTransactionVerbose, GetTransaction, GetTxOut, ImportDescriptors,
16    ListDescriptors, ListTransactions, ListUnspent, PsbtBumpFee, SignRawTransactionWithWallet,
17    SubmitPackage, TestMempoolAccept, WalletCreateFundedPsbt, WalletProcessPsbt,
18};
19use serde_json::value::{RawValue, Value};
20use tracing::*;
21
22use crate::{
23    client::Client,
24    error::ClientError,
25    to_value,
26    traits::{Broadcaster, Reader, Signer, Wallet},
27    types::{
28        CreateRawTransactionArguments, CreateRawTransactionInput, CreateRawTransactionOutput,
29        CreateWalletArguments, ImportDescriptorInput, ListUnspentQueryOptions,
30        PreviousTransactionOutput, PsbtBumpFeeOptions, SighashType, WalletCreateFundedPsbtOptions,
31    },
32    ClientResult,
33};
34
35/// Minimum relay fee rate: 1 sat/vB = 0.00001 BTC/kvB
36const MIN_FEE_RATE_BTC_VKB: f64 = 0.00001;
37
38impl Reader for Client {
39    async fn estimate_smart_fee(&self, conf_target: u16) -> ClientResult<u64> {
40        let result = self
41            .call::<Box<RawValue>>("estimatesmartfee", &[to_value(conf_target)?])
42            .await?
43            .to_string();
44
45        let result_map: Value = result.parse::<Value>()?;
46
47        let btc_vkb = result_map
48            .get("feerate")
49            .and_then(|v| v.as_f64())
50            .unwrap_or(MIN_FEE_RATE_BTC_VKB); // Default to minimum if missing or invalid
51
52        // Ensure fee rate is positive and non-zero
53        if btc_vkb <= 0.0 {
54            return Err(ClientError::Other(
55                "Invalid fee rate: must be positive".to_string(),
56            ));
57        }
58
59        // Convert BTC/vB to sat/vB
60        let sat_vb = (btc_vkb * 100_000_000.0 / 1_000.0) as u64;
61
62        Ok(sat_vb)
63    }
64
65    async fn get_block_header(&self, hash: &BlockHash) -> ClientResult<Header> {
66        let get_block_header = self
67            .call::<GetBlockHeader>(
68                "getblockheader",
69                &[to_value(hash.to_string())?, to_value(false)?],
70            )
71            .await?;
72        let header = get_block_header
73            .block_header()
74            .map_err(|err| ClientError::Other(format!("header decode: {err}")))?;
75        Ok(header)
76    }
77
78    async fn get_block(&self, hash: &BlockHash) -> ClientResult<Block> {
79        let get_block = self
80            .call::<GetBlockVerboseZero>("getblock", &[to_value(hash.to_string())?, to_value(0)?])
81            .await?;
82        let block = get_block
83            .into_model()
84            .map_err(|e| ClientError::Parse(e.to_string()))?
85            .0;
86        Ok(block)
87    }
88
89    async fn get_block_height(&self, hash: &BlockHash) -> ClientResult<u64> {
90        let block_verobose = self
91            .call::<GetBlockVerboseOne>("getblock", &[to_value(hash.to_string())?])
92            .await?;
93
94        let block_height = block_verobose.height as u64;
95        Ok(block_height)
96    }
97
98    async fn get_block_header_at(&self, height: u64) -> ClientResult<Header> {
99        let hash = self.get_block_hash(height).await?;
100        self.get_block_header(&hash).await
101    }
102
103    async fn get_block_at(&self, height: u64) -> ClientResult<Block> {
104        let hash = self.get_block_hash(height).await?;
105        self.get_block(&hash).await
106    }
107
108    async fn get_block_count(&self) -> ClientResult<u64> {
109        self.call::<u64>("getblockcount", &[]).await
110    }
111
112    async fn get_block_hash(&self, height: u64) -> ClientResult<BlockHash> {
113        self.call::<BlockHash>("getblockhash", &[to_value(height)?])
114            .await
115    }
116
117    async fn get_blockchain_info(&self) -> ClientResult<model::GetBlockchainInfo> {
118        let res = self
119            .call::<GetBlockchainInfo>("getblockchaininfo", &[])
120            .await?;
121        res.into_model()
122            .map_err(|e| ClientError::Parse(e.to_string()))
123    }
124
125    async fn get_current_timestamp(&self) -> ClientResult<u32> {
126        let best_block_hash = self.call::<BlockHash>("getbestblockhash", &[]).await?;
127        let block = self.get_block(&best_block_hash).await?;
128        Ok(block.header.time)
129    }
130
131    async fn get_raw_mempool(&self) -> ClientResult<model::GetRawMempool> {
132        let resp = self.call::<GetRawMempool>("getrawmempool", &[]).await?;
133        resp.into_model()
134            .map_err(|e| ClientError::Parse(e.to_string()))
135    }
136
137    async fn get_raw_mempool_verbose(&self) -> ClientResult<model::GetRawMempoolVerbose> {
138        let resp = self
139            .call::<GetRawMempoolVerbose>("getrawmempool", &[to_value(true)?])
140            .await?;
141
142        resp.into_model()
143            .map_err(|e| ClientError::Parse(e.to_string()))
144    }
145
146    async fn get_mempool_info(&self) -> ClientResult<model::GetMempoolInfo> {
147        let resp = self.call::<GetMempoolInfo>("getmempoolinfo", &[]).await?;
148        resp.into_model()
149            .map_err(|e| ClientError::Parse(e.to_string()))
150    }
151
152    async fn get_raw_transaction_verbosity_zero(
153        &self,
154        txid: &Txid,
155    ) -> ClientResult<model::GetRawTransaction> {
156        let resp = self
157            .call::<GetRawTransaction>(
158                "getrawtransaction",
159                &[to_value(txid.to_string())?, to_value(0)?],
160            )
161            .await
162            .map_err(|e| ClientError::Parse(e.to_string()))?;
163        resp.into_model()
164            .map_err(|e| ClientError::Parse(e.to_string()))
165    }
166
167    async fn get_raw_transaction_verbosity_one(
168        &self,
169        txid: &Txid,
170    ) -> ClientResult<model::GetRawTransactionVerbose> {
171        let resp = self
172            .call::<GetRawTransactionVerbose>(
173                "getrawtransaction",
174                &[to_value(txid.to_string())?, to_value(1)?],
175            )
176            .await
177            .map_err(|e| ClientError::Parse(e.to_string()))?;
178        resp.into_model()
179            .map_err(|e| ClientError::Parse(e.to_string()))
180    }
181
182    async fn get_tx_out(
183        &self,
184        txid: &Txid,
185        vout: u32,
186        include_mempool: bool,
187    ) -> ClientResult<model::GetTxOut> {
188        let resp = self
189            .call::<GetTxOut>(
190                "gettxout",
191                &[
192                    to_value(txid.to_string())?,
193                    to_value(vout)?,
194                    to_value(include_mempool)?,
195                ],
196            )
197            .await
198            .map_err(|e| ClientError::Parse(e.to_string()))?;
199        resp.into_model()
200            .map_err(|e| ClientError::Parse(e.to_string()))
201    }
202
203    async fn network(&self) -> ClientResult<Network> {
204        self.call::<GetBlockchainInfo>("getblockchaininfo", &[])
205            .await?
206            .chain
207            .parse::<Network>()
208            .map_err(|e| ClientError::Parse(e.to_string()))
209    }
210}
211
212impl Broadcaster for Client {
213    async fn send_raw_transaction(&self, tx: &Transaction) -> ClientResult<Txid> {
214        let txstr = serialize_hex(tx);
215        trace!(txstr = %txstr, "Sending raw transaction");
216        match self
217            .call::<Txid>("sendrawtransaction", &[to_value(txstr)?])
218            .await
219        {
220            Ok(txid) => {
221                trace!(?txid, "Transaction sent");
222                Ok(txid)
223            }
224            Err(ClientError::Server(i, s)) => match i {
225                // Dealing with known and common errors
226                -27 => Ok(tx.compute_txid()), // Tx already in chain
227                _ => Err(ClientError::Server(i, s)),
228            },
229            Err(e) => Err(ClientError::Other(e.to_string())),
230        }
231    }
232
233    async fn test_mempool_accept(
234        &self,
235        tx: &Transaction,
236    ) -> ClientResult<model::TestMempoolAccept> {
237        let txstr = serialize_hex(tx);
238        trace!(%txstr, "Testing mempool accept");
239        let resp = self
240            .call::<TestMempoolAccept>("testmempoolaccept", &[to_value([txstr])?])
241            .await?;
242        resp.into_model()
243            .map_err(|e| ClientError::Parse(e.to_string()))
244    }
245
246    async fn submit_package(&self, txs: &[Transaction]) -> ClientResult<model::SubmitPackage> {
247        let txstrs: Vec<String> = txs.iter().map(serialize_hex).collect();
248        let resp = self
249            .call::<SubmitPackage>("submitpackage", &[to_value(txstrs)?])
250            .await?;
251        trace!(?resp, "Got submit package response");
252
253        resp.into_model()
254            .map_err(|e| ClientError::Parse(e.to_string()))
255    }
256}
257
258impl Wallet for Client {
259    async fn get_new_address(&self) -> ClientResult<Address> {
260        let address_unchecked = self
261            .call::<GetNewAddress>("getnewaddress", &[])
262            .await?
263            .0
264            .parse::<Address<_>>()
265            .map_err(|e| ClientError::Parse(e.to_string()))?
266            .assume_checked();
267        Ok(address_unchecked)
268    }
269    async fn get_transaction(&self, txid: &Txid) -> ClientResult<model::GetTransaction> {
270        let resp = self
271            .call::<GetTransaction>("gettransaction", &[to_value(txid.to_string())?])
272            .await?;
273        resp.into_model()
274            .map_err(|e| ClientError::Parse(e.to_string()))
275    }
276
277    async fn list_transactions(
278        &self,
279        count: Option<usize>,
280    ) -> ClientResult<model::ListTransactions> {
281        let resp = self
282            .call::<ListTransactions>("listtransactions", &[to_value(count)?])
283            .await?;
284        resp.into_model()
285            .map_err(|e| ClientError::Parse(e.to_string()))
286    }
287
288    async fn list_wallets(&self) -> ClientResult<Vec<String>> {
289        self.call::<Vec<String>>("listwallets", &[]).await
290    }
291
292    async fn create_raw_transaction(
293        &self,
294        raw_tx: CreateRawTransactionArguments,
295    ) -> ClientResult<Transaction> {
296        let raw_tx = self
297            .call::<String>(
298                "createrawtransaction",
299                &[to_value(raw_tx.inputs)?, to_value(raw_tx.outputs)?],
300            )
301            .await?;
302        trace!(%raw_tx, "Created raw transaction");
303        consensus::encode::deserialize_hex(&raw_tx)
304            .map_err(|e| ClientError::Other(format!("Failed to deserialize raw transaction: {e}")))
305    }
306
307    async fn wallet_create_funded_psbt(
308        &self,
309        inputs: &[CreateRawTransactionInput],
310        outputs: &[CreateRawTransactionOutput],
311        locktime: Option<u32>,
312        options: Option<WalletCreateFundedPsbtOptions>,
313        bip32_derivs: Option<bool>,
314    ) -> ClientResult<model::WalletCreateFundedPsbt> {
315        let resp = self
316            .call::<WalletCreateFundedPsbt>(
317                "walletcreatefundedpsbt",
318                &[
319                    to_value(inputs)?,
320                    to_value(outputs)?,
321                    to_value(locktime.unwrap_or(0))?,
322                    to_value(options.unwrap_or_default())?,
323                    to_value(bip32_derivs)?,
324                ],
325            )
326            .await?;
327        resp.into_model()
328            .map_err(|e| ClientError::Parse(e.to_string()))
329    }
330
331    async fn get_address_info(&self, address: &Address) -> ClientResult<model::GetAddressInfo> {
332        trace!(address = %address, "Getting address info");
333        let resp = self
334            .call::<GetAddressInfo>("getaddressinfo", &[to_value(address.to_string())?])
335            .await?;
336        resp.into_model()
337            .map_err(|e| ClientError::Parse(e.to_string()))
338    }
339
340    async fn list_unspent(
341        &self,
342        min_conf: Option<u32>,
343        max_conf: Option<u32>,
344        addresses: Option<&[Address]>,
345        include_unsafe: Option<bool>,
346        query_options: Option<ListUnspentQueryOptions>,
347    ) -> ClientResult<model::ListUnspent> {
348        let addr_strings: Vec<String> = addresses
349            .map(|addrs| addrs.iter().map(|a| a.to_string()).collect())
350            .unwrap_or_default();
351
352        let mut params = vec![
353            to_value(min_conf.unwrap_or(1))?,
354            to_value(max_conf.unwrap_or(9_999_999))?,
355            to_value(addr_strings)?,
356            to_value(include_unsafe.unwrap_or(true))?,
357        ];
358
359        if let Some(query_options) = query_options {
360            params.push(to_value(query_options)?);
361        }
362
363        let resp = self.call::<ListUnspent>("listunspent", &params).await?;
364        trace!(?resp, "Got UTXOs");
365
366        resp.into_model()
367            .map_err(|e| ClientError::Parse(e.to_string()))
368    }
369}
370
371impl Signer for Client {
372    async fn sign_raw_transaction_with_wallet(
373        &self,
374        tx: &Transaction,
375        prev_outputs: Option<Vec<PreviousTransactionOutput>>,
376    ) -> ClientResult<model::SignRawTransactionWithWallet> {
377        let tx_hex = serialize_hex(tx);
378        trace!(tx_hex = %tx_hex, "Signing transaction");
379        trace!(?prev_outputs, "Signing transaction with previous outputs");
380        let resp = self
381            .call::<SignRawTransactionWithWallet>(
382                "signrawtransactionwithwallet",
383                &[to_value(tx_hex)?, to_value(prev_outputs)?],
384            )
385            .await?;
386        resp.into_model()
387            .map_err(|e| ClientError::Parse(e.to_string()))
388    }
389
390    async fn get_xpriv(&self) -> ClientResult<Option<Xpriv>> {
391        // If the ENV variable `BITCOIN_XPRIV_RETRIEVABLE` is not set, we return `None`
392        if var("BITCOIN_XPRIV_RETRIEVABLE").is_err() {
393            return Ok(None);
394        }
395
396        let descriptors = self
397            .call::<ListDescriptors>("listdescriptors", &[to_value(true)?]) // true is the xpriv, false is the xpub
398            .await?
399            .descriptors;
400        if descriptors.is_empty() {
401            return Err(ClientError::Other("No descriptors found".to_string()));
402        }
403
404        // We are only interested in the one that contains `tr(`
405        let descriptor = descriptors
406            .iter()
407            .find(|d| d.descriptor.contains("tr("))
408            .map(|d| d.descriptor.clone())
409            .ok_or(ClientError::Xpriv)?;
410
411        // Now we extract the xpriv from the `tr()` up to the first `/`
412        let xpriv_str = descriptor
413            .split("tr(")
414            .nth(1)
415            .ok_or(ClientError::Xpriv)?
416            .split("/")
417            .next()
418            .ok_or(ClientError::Xpriv)?;
419
420        let xpriv = xpriv_str.parse::<Xpriv>().map_err(|_| ClientError::Xpriv)?;
421        Ok(Some(xpriv))
422    }
423
424    async fn import_descriptors(
425        &self,
426        descriptors: Vec<ImportDescriptorInput>,
427        wallet_name: String,
428    ) -> ClientResult<ImportDescriptors> {
429        let wallet_args = CreateWalletArguments {
430            name: wallet_name,
431            load_on_startup: Some(true),
432        };
433
434        // TODO: this should check for -35 error code which is good,
435        //       means that is already created
436        let _wallet_create = self
437            .call::<CreateWallet>("createwallet", &[to_value(wallet_args.clone())?])
438            .await;
439        // TODO: this should check for -35 error code which is good, -18 is bad.
440        let _wallet_load = self
441            .call::<CreateWallet>("loadwallet", &[to_value(wallet_args)?])
442            .await;
443
444        let result = self
445            .call::<ImportDescriptors>("importdescriptors", &[to_value(descriptors)?])
446            .await?;
447        Ok(result)
448    }
449
450    async fn wallet_process_psbt(
451        &self,
452        psbt: &str,
453        sign: Option<bool>,
454        sighashtype: Option<SighashType>,
455        bip32_derivs: Option<bool>,
456    ) -> ClientResult<model::WalletProcessPsbt> {
457        let mut params = vec![to_value(psbt)?, to_value(sign.unwrap_or(true))?];
458
459        if let Some(sighashtype) = sighashtype {
460            params.push(to_value(sighashtype)?);
461        }
462
463        if let Some(bip32_derivs) = bip32_derivs {
464            params.push(to_value(bip32_derivs)?);
465        }
466
467        let resp = self
468            .call::<WalletProcessPsbt>("walletprocesspsbt", &params)
469            .await?;
470        resp.into_model()
471            .map_err(|e| ClientError::Parse(e.to_string()))
472    }
473
474    async fn psbt_bump_fee(
475        &self,
476        txid: &Txid,
477        options: Option<PsbtBumpFeeOptions>,
478    ) -> ClientResult<model::PsbtBumpFee> {
479        let mut params = vec![to_value(txid.to_string())?];
480
481        if let Some(options) = options {
482            params.push(to_value(options)?);
483        }
484
485        let resp = self.call::<PsbtBumpFee>("psbtbumpfee", &params).await?;
486        resp.into_model()
487            .map_err(|e| ClientError::Parse(e.to_string()))
488    }
489}
490
491#[cfg(test)]
492mod test {
493
494    use std::sync::Once;
495
496    use bitcoin::{hashes::Hash, transaction, Amount, FeeRate, NetworkKind};
497    use corepc_types::v29::ImportDescriptorsResult;
498    use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
499
500    use super::*;
501    use crate::{
502        test_utils::corepc_node_helpers::{get_bitcoind_and_client, mine_blocks},
503        types::{CreateRawTransactionInput, CreateRawTransactionOutput},
504        Auth,
505    };
506
507    /// 50 BTC in [`Network::Regtest`].
508    const COINBASE_AMOUNT: Amount = Amount::from_sat(50 * 100_000_000);
509
510    /// Only attempts to start tracing once.
511    fn init_tracing() {
512        static INIT: Once = Once::new();
513
514        INIT.call_once(|| {
515            tracing_subscriber::registry()
516                .with(fmt::layer())
517                .with(EnvFilter::from_default_env())
518                .try_init()
519                .ok();
520        });
521    }
522
523    #[tokio::test()]
524    async fn client_works() {
525        init_tracing();
526
527        let (bitcoind, client) = get_bitcoind_and_client();
528
529        // network
530        let got = client.network().await.unwrap();
531        let expected = Network::Regtest;
532
533        assert_eq!(expected, got);
534        // get_blockchain_info
535        let get_blockchain_info = client.get_blockchain_info().await.unwrap();
536        assert_eq!(get_blockchain_info.blocks, 0);
537
538        // get_current_timestamp
539        let _ = client
540            .get_current_timestamp()
541            .await
542            .expect("must be able to get current timestamp");
543
544        let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
545
546        // get_block
547        let expected = blocks.last().unwrap();
548        let got = client.get_block(expected).await.unwrap().block_hash();
549        assert_eq!(*expected, got);
550
551        // get_block_at
552        let target_height = blocks.len() as u64;
553        let expected = blocks.last().unwrap();
554        let got = client
555            .get_block_at(target_height)
556            .await
557            .unwrap()
558            .block_hash();
559        assert_eq!(*expected, got);
560
561        // get_block_count
562        let expected = blocks.len() as u64;
563        let got = client.get_block_count().await.unwrap();
564        assert_eq!(expected, got);
565
566        // get_block_hash
567        let target_height = blocks.len() as u64;
568        let expected = blocks.last().unwrap();
569        let got = client.get_block_hash(target_height).await.unwrap();
570        assert_eq!(*expected, got);
571
572        // get_block_header_at
573        let target_height = blocks.len() as u64;
574        let expected = blocks.last().unwrap();
575        let got = client.get_block_header_at(target_height).await.unwrap();
576        assert_eq!(*expected, got.block_hash());
577
578        // get_new_address
579        let address = client.get_new_address().await.unwrap();
580        let txid = client
581            .call::<String>(
582                "sendtoaddress",
583                &[to_value(address.to_string()).unwrap(), to_value(1).unwrap()],
584            )
585            .await
586            .unwrap()
587            .parse::<Txid>()
588            .unwrap();
589
590        // get_transaction
591        let tx = client.get_transaction(&txid).await.unwrap().tx;
592        let got = client.send_raw_transaction(&tx).await.unwrap();
593        let expected = txid; // Don't touch this!
594        assert_eq!(expected, got);
595
596        // get_raw_transaction_verbosity_zero
597        let got = client
598            .get_raw_transaction_verbosity_zero(&txid)
599            .await
600            .unwrap()
601            .0
602            .compute_txid();
603        assert_eq!(expected, got);
604
605        // get_raw_transaction_verbosity_one
606        let got = client
607            .get_raw_transaction_verbosity_one(&txid)
608            .await
609            .unwrap()
610            .transaction
611            .compute_txid();
612        assert_eq!(expected, got);
613
614        // get_raw_mempool
615        let got = client.get_raw_mempool().await.unwrap();
616        let expected = vec![txid];
617        assert_eq!(expected, got.0);
618
619        // get_raw_mempool_verbose
620        let got = client.get_raw_mempool_verbose().await.unwrap();
621        assert_eq!(got.0.len(), 1);
622        assert_eq!(got.0.get(&txid).unwrap().height, 101);
623
624        // get_mempool_info
625        let got = client.get_mempool_info().await.unwrap();
626        assert!(got.loaded.unwrap_or(false));
627        assert_eq!(got.size, 1);
628        assert_eq!(got.unbroadcast_count, Some(1));
629
630        // estimate_smart_fee
631        let got = client.estimate_smart_fee(1).await.unwrap();
632        let expected = 1; // 1 sat/vB
633        assert_eq!(expected, got);
634
635        // sign_raw_transaction_with_wallet
636        let got = client
637            .sign_raw_transaction_with_wallet(&tx, None)
638            .await
639            .unwrap();
640        assert!(got.complete);
641        assert!(got.errors.is_empty());
642
643        // test_mempool_accept
644        let txids = client
645            .test_mempool_accept(&tx)
646            .await
647            .expect("must be able to test mempool accept");
648        let got = txids
649            .results
650            .first()
651            .expect("there must be at least one txid");
652        assert_eq!(
653            got.txid,
654            tx.compute_txid(),
655            "txids must match in the mempool"
656        );
657
658        // send_raw_transaction
659        let got = client.send_raw_transaction(&tx).await.unwrap();
660        assert!(got.as_byte_array().len() == 32);
661
662        // list_transactions
663        let got = client.list_transactions(None).await.unwrap();
664        assert_eq!(got.0.len(), 10);
665
666        // list_unspent
667        // let's mine one more block
668        mine_blocks(&bitcoind, 1, None).unwrap();
669        let got = client
670            .list_unspent(None, None, None, None, None)
671            .await
672            .unwrap();
673        assert_eq!(got.0.len(), 3);
674
675        // listdescriptors
676        let got = client.get_xpriv().await.unwrap().unwrap().network;
677        let expected = NetworkKind::Test;
678        assert_eq!(expected, got);
679
680        // importdescriptors
681        // taken from https://github.com/rust-bitcoin/rust-bitcoin/blob/bb38aeb786f408247d5bbc88b9fa13616c74c009/bitcoin/examples/taproot-psbt.rs#L18C38-L18C149
682        let descriptor_string = "tr([e61b318f/20000'/20']tprv8ZgxMBicQKsPd4arFr7sKjSnKFDVMR2JHw9Y8L9nXN4kiok4u28LpHijEudH3mMYoL4pM5UL9Bgdz2M4Cy8EzfErmU9m86ZTw6hCzvFeTg7/101/*)#2plamwqs".to_owned();
683        let timestamp = "now".to_owned();
684        let list_descriptors = vec![ImportDescriptorInput {
685            desc: descriptor_string,
686            active: Some(true),
687            timestamp,
688        }];
689        let got = client
690            .import_descriptors(list_descriptors, "strata".to_owned())
691            .await
692            .unwrap()
693            .0;
694        let expected = vec![ImportDescriptorsResult {
695            success: true,
696            warnings: Some(vec![
697                "Range not given, using default keypool range".to_string()
698            ]),
699            error: None,
700        }];
701        assert_eq!(expected, got);
702
703        let psbt_address = client.get_new_address().await.unwrap();
704        let psbt_outputs = vec![CreateRawTransactionOutput::AddressAmount {
705            address: psbt_address.to_string(),
706            amount: 1.0,
707        }];
708
709        let funded_psbt = client
710            .wallet_create_funded_psbt(&[], &psbt_outputs, None, None, None)
711            .await
712            .unwrap();
713        assert!(!funded_psbt.psbt.inputs.is_empty());
714        assert!(funded_psbt.fee.to_sat() > 0);
715
716        let processed_psbt = client
717            .wallet_process_psbt(&funded_psbt.psbt.to_string(), None, None, None)
718            .await
719            .unwrap();
720        assert!(!processed_psbt.psbt.inputs.is_empty());
721        assert!(processed_psbt.complete);
722
723        let finalized_psbt = client
724            .wallet_process_psbt(&funded_psbt.psbt.to_string(), Some(true), None, None)
725            .await
726            .unwrap();
727        assert!(finalized_psbt.complete);
728        assert!(finalized_psbt.hex.is_some());
729        let signed_tx = finalized_psbt.hex.as_ref().unwrap();
730        let signed_txid = signed_tx.compute_txid();
731        let got = client
732            .test_mempool_accept(signed_tx)
733            .await
734            .unwrap()
735            .results
736            .first()
737            .unwrap()
738            .txid;
739        assert_eq!(signed_txid, got);
740
741        let info_address = client.get_new_address().await.unwrap();
742        let address_info = client.get_address_info(&info_address).await.unwrap();
743        assert_eq!(address_info.address, info_address.as_unchecked().clone());
744        assert!(address_info.is_mine);
745        assert!(address_info.solvable.unwrap_or(false));
746
747        let unspent_address = client.get_new_address().await.unwrap();
748        let unspent_txid = client
749            .call::<String>(
750                "sendtoaddress",
751                &[
752                    to_value(unspent_address.to_string()).unwrap(),
753                    to_value(1.0).unwrap(),
754                ],
755            )
756            .await
757            .unwrap();
758        mine_blocks(&bitcoind, 1, None).unwrap();
759
760        let utxos = client
761            .list_unspent(Some(1), Some(9_999_999), None, Some(true), None)
762            .await
763            .unwrap();
764        assert!(!utxos.0.is_empty());
765
766        let utxos_filtered = client
767            .list_unspent(
768                Some(1),
769                Some(9_999_999),
770                Some(std::slice::from_ref(&unspent_address)),
771                Some(true),
772                None,
773            )
774            .await
775            .unwrap();
776        assert!(!utxos_filtered.0.is_empty());
777        let found_utxo = utxos_filtered.0.iter().any(|utxo| {
778            utxo.txid.to_string() == unspent_txid
779                && utxo.address.clone().assume_checked().to_string() == unspent_address.to_string()
780        });
781        assert!(found_utxo);
782
783        let query_options = ListUnspentQueryOptions {
784            minimum_amount: Some(Amount::from_btc(0.5).unwrap()),
785            maximum_amount: Some(Amount::from_btc(2.0).unwrap()),
786            maximum_count: Some(10),
787        };
788        let utxos_with_query = client
789            .list_unspent(
790                Some(1),
791                Some(9_999_999),
792                None,
793                Some(true),
794                Some(query_options),
795            )
796            .await
797            .unwrap();
798        assert!(!utxos_with_query.0.is_empty());
799        for utxo in &utxos_with_query.0 {
800            let amount_btc = utxo.amount.to_btc();
801            assert!((0.5..=2.0).contains(&amount_btc));
802        }
803
804        let tx = finalized_psbt.hex.unwrap();
805        assert!(!tx.input.is_empty());
806        assert!(!tx.output.is_empty());
807    }
808
809    #[tokio::test()]
810    async fn get_tx_out() {
811        init_tracing();
812
813        let (bitcoind, client) = get_bitcoind_and_client();
814
815        // network sanity check
816        let got = client.network().await.unwrap();
817        let expected = Network::Regtest;
818        assert_eq!(expected, got);
819
820        let address = bitcoind.client.new_address().unwrap();
821        let blocks = mine_blocks(&bitcoind, 101, Some(address)).unwrap();
822        let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
823        let coinbase_tx = last_block.coinbase().unwrap();
824
825        // gettxout should work with a non-spent UTXO.
826        let got = client
827            .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
828            .await
829            .unwrap();
830        assert_eq!(got.tx_out.value, COINBASE_AMOUNT);
831
832        // gettxout should fail with a spent UTXO.
833        let new_address = bitcoind.client.new_address().unwrap();
834        let send_amount = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 2_000); // 2k sats as fees.
835        let _send_tx = bitcoind
836            .client
837            .send_to_address(&new_address, send_amount)
838            .unwrap()
839            .txid()
840            .unwrap();
841        let result = client
842            .get_tx_out(&coinbase_tx.compute_txid(), 0, true)
843            .await;
844        trace!(?result, "gettxout result");
845        assert!(result.is_err());
846    }
847
848    /// Create two transactions.
849    /// 1. Normal one: sends 1 BTC to an address that we control.
850    /// 2. CFFP: replaces the first transaction with a different one that we also control.
851    ///
852    /// This is needed because we must SIGN all these transactions, and we can't sign a transaction
853    /// that we don't control.
854    #[tokio::test()]
855    async fn submit_package() {
856        init_tracing();
857
858        let (bitcoind, client) = get_bitcoind_and_client();
859
860        // network sanity check
861        let got = client.network().await.unwrap();
862        let expected = Network::Regtest;
863        assert_eq!(expected, got);
864
865        let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
866        let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
867        let coinbase_tx = last_block.coinbase().unwrap();
868
869        let destination = client.get_new_address().await.unwrap();
870        let change_address = client.get_new_address().await.unwrap();
871        let amount = Amount::from_btc(1.0).unwrap();
872        let fees = Amount::from_btc(0.0001).unwrap();
873        let change_amount = COINBASE_AMOUNT - amount - fees;
874        let amount_minus_fees = Amount::from_sat(amount.to_sat() - 2_000);
875
876        let send_back_address = client.get_new_address().await.unwrap();
877        let parent_raw_tx = CreateRawTransactionArguments {
878            inputs: vec![CreateRawTransactionInput {
879                txid: coinbase_tx.compute_txid().to_string(),
880                vout: 0,
881            }],
882            outputs: vec![
883                // Destination
884                CreateRawTransactionOutput::AddressAmount {
885                    address: destination.to_string(),
886                    amount: amount.to_btc(),
887                },
888                // Change
889                CreateRawTransactionOutput::AddressAmount {
890                    address: change_address.to_string(),
891                    amount: change_amount.to_btc(),
892                },
893            ],
894        };
895        let parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
896        let signed_parent = client
897            .sign_raw_transaction_with_wallet(&parent, None)
898            .await
899            .unwrap()
900            .tx;
901
902        // sanity check
903        let parent_submitted = client.send_raw_transaction(&signed_parent).await.unwrap();
904
905        let child_raw_tx = CreateRawTransactionArguments {
906            inputs: vec![CreateRawTransactionInput {
907                txid: parent_submitted.to_string(),
908                vout: 0,
909            }],
910            outputs: vec![
911                // Send back
912                CreateRawTransactionOutput::AddressAmount {
913                    address: send_back_address.to_string(),
914                    amount: amount_minus_fees.to_btc(),
915                },
916            ],
917        };
918        let child = client.create_raw_transaction(child_raw_tx).await.unwrap();
919        let signed_child = client
920            .sign_raw_transaction_with_wallet(&child, None)
921            .await
922            .unwrap()
923            .tx;
924
925        // Ok now we have a parent and a child transaction.
926        let result = client
927            .submit_package(&[signed_parent, signed_child])
928            .await
929            .unwrap();
930        assert_eq!(result.tx_results.len(), 2);
931        assert_eq!(result.package_msg, "success");
932    }
933
934    /// Similar to [`submit_package`], but with where the parent does not pay fees,
935    /// and the child has to pay fees.
936    ///
937    /// This is called 1P1C because it has one parent and one child.
938    /// See <https://bitcoinops.org/en/bitcoin-core-28-wallet-integration-guide/>
939    /// for more information.
940    #[tokio::test]
941    async fn submit_package_1p1c() {
942        init_tracing();
943
944        let (bitcoind, client) = get_bitcoind_and_client();
945
946        // 1p1c sanity check
947        let server_version = bitcoind.client.server_version().unwrap();
948        assert!(server_version > 28);
949
950        let destination = client.get_new_address().await.unwrap();
951
952        let blocks = mine_blocks(&bitcoind, 101, None).unwrap();
953        let last_block = client.get_block(blocks.first().unwrap()).await.unwrap();
954        let coinbase_tx = last_block.coinbase().unwrap();
955
956        let parent_raw_tx = CreateRawTransactionArguments {
957            inputs: vec![CreateRawTransactionInput {
958                txid: coinbase_tx.compute_txid().to_string(),
959                vout: 0,
960            }],
961            outputs: vec![CreateRawTransactionOutput::AddressAmount {
962                address: destination.to_string(),
963                amount: COINBASE_AMOUNT.to_btc(),
964            }],
965        };
966        let mut parent = client.create_raw_transaction(parent_raw_tx).await.unwrap();
967        parent.version = transaction::Version(3);
968        assert_eq!(parent.version, transaction::Version(3));
969        trace!(?parent, "parent:");
970        let signed_parent = client
971            .sign_raw_transaction_with_wallet(&parent, None)
972            .await
973            .unwrap()
974            .tx;
975        assert_eq!(signed_parent.version, transaction::Version(3));
976
977        // Assert that the parent tx cannot be broadcasted.
978        let parent_broadcasted = client.send_raw_transaction(&signed_parent).await;
979        assert!(parent_broadcasted.is_err());
980
981        // 5k sats as fees.
982        let amount_minus_fees = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 43_000);
983        let child_raw_tx = CreateRawTransactionArguments {
984            inputs: vec![CreateRawTransactionInput {
985                txid: signed_parent.compute_txid().to_string(),
986                vout: 0,
987            }],
988            outputs: vec![CreateRawTransactionOutput::AddressAmount {
989                address: destination.to_string(),
990                amount: amount_minus_fees.to_btc(),
991            }],
992        };
993        let mut child = client.create_raw_transaction(child_raw_tx).await.unwrap();
994        child.version = transaction::Version(3);
995        assert_eq!(child.version, transaction::Version(3));
996        trace!(?child, "child:");
997        let prev_outputs = vec![PreviousTransactionOutput {
998            txid: parent.compute_txid(),
999            vout: 0,
1000            script_pubkey: parent.output[0].script_pubkey.to_hex_string(),
1001            redeem_script: None,
1002            witness_script: None,
1003            amount: Some(COINBASE_AMOUNT.to_btc()),
1004        }];
1005        let signed_child = client
1006            .sign_raw_transaction_with_wallet(&child, Some(prev_outputs))
1007            .await
1008            .unwrap()
1009            .tx;
1010        assert_eq!(signed_child.version, transaction::Version(3));
1011
1012        // Assert that the child tx cannot be broadcasted.
1013        let child_broadcasted = client.send_raw_transaction(&signed_child).await;
1014        assert!(child_broadcasted.is_err());
1015
1016        // Let's send as a package 1C1P.
1017        let result = client
1018            .submit_package(&[signed_parent, signed_child])
1019            .await
1020            .unwrap();
1021        assert_eq!(result.tx_results.len(), 2);
1022        assert_eq!(result.package_msg, "success");
1023    }
1024
1025    #[tokio::test]
1026    async fn test_invalid_credentials_return_401_error() {
1027        init_tracing();
1028
1029        let (bitcoind, _) = get_bitcoind_and_client();
1030        let url = bitcoind.rpc_url();
1031
1032        let auth = Auth::UserPass("wrong_user".to_string(), "wrong_password".to_string());
1033        let invalid_client = Client::new(url, auth, None, None, None).unwrap();
1034
1035        // Try to make any RPC call
1036        let result = invalid_client.get_blockchain_info().await;
1037
1038        // Verify we get a 401 Status error, not a Parse error
1039        assert!(result.is_err());
1040        let error = result.unwrap_err();
1041
1042        match error {
1043            ClientError::Status(status_code, message) => {
1044                assert_eq!(status_code, 401);
1045                assert!(message.contains("Unauthorized"));
1046            }
1047            _ => panic!("Expected Status(401, _) error, but got: {error:?}"),
1048        }
1049    }
1050
1051    #[tokio::test]
1052    async fn psbt_bump_fee() {
1053        init_tracing();
1054
1055        let (bitcoind, client) = get_bitcoind_and_client();
1056
1057        // Mine blocks to have funds
1058        mine_blocks(&bitcoind, 101, None).unwrap();
1059
1060        // Send to the next address
1061        let destination = client.get_new_address().await.unwrap();
1062        let amount = Amount::from_btc(0.001).unwrap(); // 0.001 BTC
1063
1064        // Create transaction with RBF enabled
1065        let txid = bitcoind
1066            .client
1067            .send_to_address_rbf(&destination, amount)
1068            .unwrap()
1069            .txid()
1070            .unwrap();
1071
1072        // Verify transaction is in mempool (unconfirmed)
1073        let mempool = client.get_raw_mempool().await.unwrap();
1074        assert!(
1075            mempool.0.contains(&txid),
1076            "Transaction should be in mempool for RBF"
1077        );
1078
1079        // Test psbt_bump_fee with default options
1080        let signed_tx = client
1081            .psbt_bump_fee(&txid, None)
1082            .await
1083            .unwrap()
1084            .psbt
1085            .extract_tx()
1086            .unwrap();
1087        let signed_txid = signed_tx.compute_txid();
1088        let got = client
1089            .test_mempool_accept(&signed_tx)
1090            .await
1091            .unwrap()
1092            .results
1093            .first()
1094            .unwrap()
1095            .txid;
1096        assert_eq!(
1097            got, signed_txid,
1098            "Bumped transaction should be accepted in mempool"
1099        );
1100
1101        // Test psbt_bump_fee with custom fee rate
1102        let options = PsbtBumpFeeOptions {
1103            fee_rate: Some(FeeRate::from_sat_per_kwu(20)), // 20 sat/vB - higher than default
1104            ..Default::default()
1105        };
1106        trace!(?options, "Calling psbt_bump_fee");
1107        let signed_tx = client
1108            .psbt_bump_fee(&txid, Some(options))
1109            .await
1110            .unwrap()
1111            .psbt
1112            .extract_tx()
1113            .unwrap();
1114        let signed_txid = signed_tx.compute_txid();
1115        let got = client
1116            .test_mempool_accept(&signed_tx)
1117            .await
1118            .unwrap()
1119            .results
1120            .first()
1121            .unwrap()
1122            .txid;
1123        assert_eq!(
1124            got, signed_txid,
1125            "Bumped transaction should be accepted in mempool"
1126        );
1127    }
1128
1129    #[cfg(feature = "raw_rpc")]
1130    #[tokio::test]
1131    async fn call_raw() {
1132        init_tracing();
1133
1134        let (bitcoind, client) = get_bitcoind_and_client();
1135
1136        mine_blocks(&bitcoind, 5, None).unwrap();
1137
1138        let expected = client.get_block_count().await.unwrap();
1139
1140        let got: u64 = client.call_raw("getblockcount", &[]).await.unwrap();
1141
1142        assert_eq!(expected, got);
1143
1144        let height = 0;
1145
1146        let expected_hash = client.get_block_hash(height).await.unwrap();
1147
1148        let got_hash: BlockHash = client
1149            .call_raw("getblockhash", &[to_value(height).unwrap()])
1150            .await
1151            .unwrap();
1152
1153        assert_eq!(expected_hash, got_hash);
1154    }
1155}