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