1use 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
35const 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); if btc_vkb <= 0.0 {
54 return Err(ClientError::Other(
55 "Invalid fee rate: must be positive".to_string(),
56 ));
57 }
58
59 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 -27 => Ok(tx.compute_txid()), _ => 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", ¶ms).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 var("BITCOIN_XPRIV_RETRIEVABLE").is_err() {
393 return Ok(None);
394 }
395
396 let descriptors = self
397 .call::<ListDescriptors>("listdescriptors", &[to_value(true)?]) .await?
399 .descriptors;
400 if descriptors.is_empty() {
401 return Err(ClientError::Other("No descriptors found".to_string()));
402 }
403
404 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 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 let _wallet_create = self
437 .call::<CreateWallet>("createwallet", &[to_value(wallet_args.clone())?])
438 .await;
439 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", ¶ms)
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", ¶ms).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 const COINBASE_AMOUNT: Amount = Amount::from_sat(50 * 100_000_000);
509
510 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 let got = client.network().await.unwrap();
531 let expected = Network::Regtest;
532
533 assert_eq!(expected, got);
534 let get_blockchain_info = client.get_blockchain_info().await.unwrap();
536 assert_eq!(get_blockchain_info.blocks, 0);
537
538 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 let expected = blocks.last().unwrap();
548 let got = client.get_block(expected).await.unwrap().block_hash();
549 assert_eq!(*expected, got);
550
551 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 let expected = blocks.len() as u64;
563 let got = client.get_block_count().await.unwrap();
564 assert_eq!(expected, got);
565
566 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 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 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 let tx = client.get_transaction(&txid).await.unwrap().tx;
592 let got = client.send_raw_transaction(&tx).await.unwrap();
593 let expected = txid; assert_eq!(expected, got);
595
596 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 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 let got = client.get_raw_mempool().await.unwrap();
616 let expected = vec![txid];
617 assert_eq!(expected, got.0);
618
619 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 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 let got = client.estimate_smart_fee(1).await.unwrap();
632 let expected = 1; assert_eq!(expected, got);
634
635 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 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 let got = client.send_raw_transaction(&tx).await.unwrap();
660 assert!(got.as_byte_array().len() == 32);
661
662 let got = client.list_transactions(None).await.unwrap();
664 assert_eq!(got.0.len(), 10);
665
666 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 let got = client.get_xpriv().await.unwrap().unwrap().network;
677 let expected = NetworkKind::Test;
678 assert_eq!(expected, got);
679
680 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 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 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 let new_address = bitcoind.client.new_address().unwrap();
834 let send_amount = Amount::from_sat(COINBASE_AMOUNT.to_sat() - 2_000); 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 #[tokio::test()]
855 async fn submit_package() {
856 init_tracing();
857
858 let (bitcoind, client) = get_bitcoind_and_client();
859
860 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 CreateRawTransactionOutput::AddressAmount {
885 address: destination.to_string(),
886 amount: amount.to_btc(),
887 },
888 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 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 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 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 #[tokio::test]
941 async fn submit_package_1p1c() {
942 init_tracing();
943
944 let (bitcoind, client) = get_bitcoind_and_client();
945
946 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 let parent_broadcasted = client.send_raw_transaction(&signed_parent).await;
979 assert!(parent_broadcasted.is_err());
980
981 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 let child_broadcasted = client.send_raw_transaction(&signed_child).await;
1014 assert!(child_broadcasted.is_err());
1015
1016 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 let result = invalid_client.get_blockchain_info().await;
1037
1038 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(&bitcoind, 101, None).unwrap();
1059
1060 let destination = client.get_new_address().await.unwrap();
1062 let amount = Amount::from_btc(0.001).unwrap(); let txid = bitcoind
1066 .client
1067 .send_to_address_rbf(&destination, amount)
1068 .unwrap()
1069 .txid()
1070 .unwrap();
1071
1072 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 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 let options = PsbtBumpFeeOptions {
1103 fee_rate: Some(FeeRate::from_sat_per_kwu(20)), ..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}