bitcoind_async_client/types.rs
1use std::collections::BTreeMap;
2
3use bitcoin::{
4 absolute::Height,
5 address::{self, NetworkUnchecked},
6 block::Header,
7 consensus::{self, encode},
8 Address, Amount, Block, BlockHash, FeeRate, Psbt, SignedAmount, Transaction, Txid, Wtxid,
9};
10use serde::{
11 de::{self, IntoDeserializer, Visitor},
12 Deserialize, Deserializer, Serialize, Serializer,
13};
14use tracing::*;
15
16use crate::error::SignRawTransactionWithWalletError;
17
18/// The category of a transaction.
19///
20/// This is one of the results of `listtransactions` RPC method.
21///
22/// # Note
23///
24/// This is a subset of the categories available in Bitcoin Core.
25/// It also assumes that the transactions are present in the underlying Bitcoin
26/// client's wallet.
27#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
28#[serde(rename_all = "lowercase")]
29pub enum TransactionCategory {
30 /// Transactions sent.
31 Send,
32 /// Non-coinbase transactions received.
33 Receive,
34 /// Coinbase transactions received with more than 100 confirmations.
35 Generate,
36 /// Coinbase transactions received with 100 or less confirmations.
37 Immature,
38 /// Orphaned coinbase transactions received.
39 Orphan,
40}
41
42/// Result of JSON-RPC method `getblockchaininfo`.
43///
44/// Method call: `getblockchaininfo`
45///
46/// > Returns an object containing various state info regarding blockchain processing.
47#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
48pub struct GetBlockchainInfo {
49 /// Current network name as defined in BIP70 (main, test, signet, regtest).
50 pub chain: String,
51 /// The current number of blocks processed in the server.
52 pub blocks: u64,
53 /// The current number of headers we have validated.
54 pub headers: u64,
55 /// The hash of the currently best block.
56 #[serde(rename = "bestblockhash")]
57 pub best_block_hash: String,
58 /// The current difficulty.
59 pub difficulty: f64,
60 /// Median time for the current best block.
61 #[serde(rename = "mediantime")]
62 pub median_time: u64,
63 /// Estimate of verification progress (between 0 and 1).
64 #[serde(rename = "verificationprogress")]
65 pub verification_progress: f64,
66 /// Estimate of whether this node is in Initial Block Download (IBD) mode.
67 #[serde(rename = "initialblockdownload")]
68 pub initial_block_download: bool,
69 /// Total amount of work in active chain, in hexadecimal.
70 #[serde(rename = "chainwork")]
71 pub chain_work: String,
72 /// The estimated size of the block and undo files on disk.
73 pub size_on_disk: u64,
74 /// If the blocks are subject to pruning.
75 pub pruned: bool,
76 /// Lowest-height complete block stored (only present if pruning is enabled).
77 #[serde(rename = "pruneheight")]
78 pub prune_height: Option<u64>,
79 /// Whether automatic pruning is enabled (only present if pruning is enabled).
80 pub automatic_pruning: Option<bool>,
81 /// The target size used by pruning (only present if automatic pruning is enabled).
82 pub prune_target_size: Option<u64>,
83}
84
85/// Result of JSON-RPC method `getblockheader` with verbosity set to 0.
86///
87/// A string that is serialized, hex-encoded data for block 'hash'.
88///
89/// Method call: `getblockheader "blockhash" ( verbosity )`
90#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
91pub struct GetBlockHeaderVerbosityZero(pub String);
92
93impl GetBlockHeaderVerbosityZero {
94 /// Converts json straight to a [`Header`].
95 pub fn header(self) -> Result<Header, encode::FromHexError> {
96 let header: Header = encode::deserialize_hex(&self.0)?;
97 Ok(header)
98 }
99}
100
101/// Result of JSON-RPC method `getblock` with verbosity set to 0.
102///
103/// A string that is serialized, hex-encoded data for block 'hash'.
104///
105/// Method call: `getblock "blockhash" ( verbosity )`
106#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
107pub struct GetBlockVerbosityZero(pub String);
108
109impl GetBlockVerbosityZero {
110 /// Converts json straight to a [`Block`].
111 pub fn block(self) -> Result<Block, encode::FromHexError> {
112 let block: Block = encode::deserialize_hex(&self.0)?;
113 Ok(block)
114 }
115}
116
117/// Result of JSON-RPC method `getblock` with verbosity set to 1.
118#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
119pub struct GetBlockVerbosityOne {
120 /// The block hash (same as provided) in RPC call.
121 pub hash: String,
122 /// The number of confirmations, or -1 if the block is not on the main chain.
123 pub confirmations: i32,
124 /// The block size.
125 pub size: usize,
126 /// The block size excluding witness data.
127 #[serde(rename = "strippedsize")]
128 pub stripped_size: Option<usize>,
129 /// The block weight as defined in BIP-141.
130 pub weight: u64,
131 /// The block height or index.
132 pub height: usize,
133 /// The block version.
134 pub version: i32,
135 /// The block version formatted in hexadecimal.
136 #[serde(rename = "versionHex")]
137 pub version_hex: String,
138 /// The merkle root
139 #[serde(rename = "merkleroot")]
140 pub merkle_root: String,
141 /// The transaction ids
142 pub tx: Vec<String>,
143 /// The block time expressed in UNIX epoch time.
144 pub time: usize,
145 /// The median block time expressed in UNIX epoch time.
146 #[serde(rename = "mediantime")]
147 pub median_time: Option<usize>,
148 /// The nonce
149 pub nonce: u32,
150 /// The bits.
151 pub bits: String,
152 /// The difficulty.
153 pub difficulty: f64,
154 /// Expected number of hashes required to produce the chain up to this block (in hex).
155 #[serde(rename = "chainwork")]
156 pub chain_work: String,
157 /// The number of transactions in the block.
158 #[serde(rename = "nTx")]
159 pub n_tx: u32,
160 /// The hash of the previous block (if available).
161 #[serde(rename = "previousblockhash")]
162 pub previous_block_hash: Option<String>,
163 /// The hash of the next block (if available).
164 #[serde(rename = "nextblockhash")]
165 pub next_block_hash: Option<String>,
166}
167
168/// Result of JSON-RPC method `getrawtransaction` with verbosity set to 0.
169///
170/// A string that is serialized, hex-encoded data for transaction.
171///
172/// Method call: `getrawtransaction "txid" ( verbosity )`
173#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
174pub struct GetRawTransactionVerbosityZero(pub String);
175
176impl GetRawTransactionVerbosityZero {
177 /// Converts json straight to a [`Transaction`].
178 pub fn transaction(self) -> Result<Transaction, encode::FromHexError> {
179 let transaction: Transaction = encode::deserialize_hex(&self.0)?;
180 Ok(transaction)
181 }
182}
183
184/// Result of JSON-RPC method `getmempoolinfo`.
185///
186/// Method call: `getmempoolinfo`
187#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
188pub struct GetMempoolInfo {
189 pub loaded: bool,
190 pub size: usize,
191 pub bytes: usize,
192 pub usage: usize,
193 pub maxmempool: usize,
194 pub mempoolminfee: f64,
195 pub minrelaytxfee: f64,
196 pub unbroadcastcount: usize,
197}
198
199/// Result of JSON-RPC method `getrawtransaction` with verbosity set to 1.
200///
201/// Method call: `getrawtransaction "txid" ( verbosity )`
202#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
203pub struct GetRawTransactionVerbosityOne {
204 pub in_active_chain: Option<bool>,
205 #[serde(deserialize_with = "deserialize_tx")]
206 #[serde(rename = "hex")]
207 pub transaction: Transaction,
208 pub txid: Txid,
209 pub hash: Wtxid,
210 pub size: usize,
211 pub vsize: usize,
212 pub version: u32,
213 pub locktime: u32,
214 pub blockhash: Option<BlockHash>,
215 pub confirmations: Option<u32>,
216 pub time: Option<usize>,
217 pub blocktime: Option<usize>,
218}
219
220/// Result of JSON-RPC method `gettxout`.
221///
222/// > gettxout "txid" n ( include_mempool )
223/// >
224/// > Returns details about an unspent transaction output.
225/// >
226/// > Arguments:
227/// > 1. txid (string, required) The transaction id
228/// > 2. n (numeric, required) vout number
229#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
230pub struct GetTxOut {
231 /// The hash of the block at the tip of the chain.
232 #[serde(rename = "bestblock")]
233 pub best_block: String,
234 /// The number of confirmations.
235 pub confirmations: u32, // TODO: Change this to an i64.
236 /// The transaction value in BTC.
237 pub value: f64,
238 /// The script pubkey.
239 #[serde(rename = "scriptPubkey")]
240 pub script_pubkey: Option<ScriptPubkey>,
241 /// Coinbase or not.
242 pub coinbase: bool,
243}
244
245/// A script pubkey.
246#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
247pub struct ScriptPubkey {
248 /// Script assembly.
249 pub asm: String,
250 /// Script hex.
251 pub hex: String,
252 #[serde(rename = "reqSigs")]
253 pub req_sigs: i64,
254 /// The type, eg pubkeyhash.
255 #[serde(rename = "type")]
256 pub type_: String,
257 /// Bitcoin address.
258 pub address: Option<String>,
259}
260
261/// Models the arguments of JSON-RPC method `createrawtransaction`.
262///
263/// # Note
264///
265/// Assumes that the transaction is always "replaceable" by default and has a locktime of 0.
266#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
267pub struct CreateRawTransaction {
268 pub inputs: Vec<CreateRawTransactionInput>,
269 pub outputs: Vec<CreateRawTransactionOutput>,
270}
271
272/// Models the input of JSON-RPC method `createrawtransaction`.
273#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
274pub struct CreateRawTransactionInput {
275 pub txid: String,
276 pub vout: u32,
277}
278
279/// Models transaction outputs for Bitcoin RPC methods.
280///
281/// Used by various RPC methods such as `createrawtransaction`, `psbtbumpfee`,
282/// and `walletcreatefundedpsbt`. The outputs are specified as key-value pairs,
283/// where the keys are addresses and the values are amounts to send.
284#[derive(Clone, Debug, PartialEq, Deserialize)]
285#[serde(untagged)]
286pub enum CreateRawTransactionOutput {
287 /// A pair of an [`Address`] string and an [`Amount`] in BTC.
288 AddressAmount {
289 /// An [`Address`] string.
290 address: String,
291 /// An [`Amount`] in BTC.
292 amount: f64,
293 },
294 /// A payload such as in `OP_RETURN` transactions.
295 Data {
296 /// The payload.
297 data: String,
298 },
299}
300
301impl Serialize for CreateRawTransactionOutput {
302 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
303 where
304 S: serde::Serializer,
305 {
306 match self {
307 CreateRawTransactionOutput::AddressAmount { address, amount } => {
308 let mut map = serde_json::Map::new();
309 map.insert(
310 address.clone(),
311 serde_json::Value::Number(serde_json::Number::from_f64(*amount).unwrap()),
312 );
313 map.serialize(serializer)
314 }
315 CreateRawTransactionOutput::Data { data } => {
316 let mut map = serde_json::Map::new();
317 map.insert("data".to_string(), serde_json::Value::String(data.clone()));
318 map.serialize(serializer)
319 }
320 }
321 }
322}
323
324/// Result of JSON-RPC method `submitpackage`.
325///
326/// > submitpackage ["rawtx",...] ( maxfeerate maxburnamount )
327/// >
328/// > Submit a package of raw transactions (serialized, hex-encoded) to local node.
329/// > The package will be validated according to consensus and mempool policy rules. If any
330/// > transaction passes, it will be accepted to mempool.
331/// > This RPC is experimental and the interface may be unstable. Refer to doc/policy/packages.md
332/// > for documentation on package policies.
333/// > Warning: successful submission does not mean the transactions will propagate throughout the
334/// > network.
335/// >
336/// > Arguments:
337/// > 1. package (json array, required) An array of raw transactions.
338/// > The package must solely consist of a child and its parents. None of the parents may depend on
339/// > each other.
340/// > The package must be topologically sorted, with the child being the last element in the array.
341/// > [
342/// > "rawtx", (string)
343/// > ...
344/// > ]
345#[allow(clippy::doc_lazy_continuation)]
346#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
347pub struct SubmitPackage {
348 /// The transaction package result message.
349 ///
350 /// "success" indicates all transactions were accepted into or are already in the mempool.
351 pub package_msg: String,
352 /// Transaction results keyed by wtxid.
353 #[serde(rename = "tx-results")]
354 pub tx_results: BTreeMap<String, SubmitPackageTxResult>,
355 /// List of txids of replaced transactions.
356 #[serde(rename = "replaced-transactions")]
357 pub replaced_transactions: Vec<String>,
358}
359
360/// Models the per-transaction result included in the JSON-RPC method `submitpackage`.
361#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
362pub struct SubmitPackageTxResult {
363 /// The transaction id.
364 pub txid: String,
365 /// The wtxid of a different transaction with the same txid but different witness found in the
366 /// mempool.
367 ///
368 /// If set, this means the submitted transaction was ignored.
369 #[serde(rename = "other-wtxid")]
370 pub other_wtxid: Option<String>,
371 /// Sigops-adjusted virtual transaction size.
372 pub vsize: i64,
373 /// Transaction fees.
374 pub fees: Option<SubmitPackageTxResultFees>,
375 /// The transaction error string, if it was rejected by the mempool
376 pub error: Option<String>,
377}
378
379/// Models the fees included in the per-transaction result of the JSON-RPC method `submitpackage`.
380#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
381pub struct SubmitPackageTxResultFees {
382 /// Transaction fee.
383 #[serde(rename = "base")]
384 pub base_fee: f64,
385 /// The effective feerate.
386 ///
387 /// Will be `None` if the transaction was already in the mempool. For example, the package
388 /// feerate and/or feerate with modified fees from the `prioritisetransaction` JSON-RPC method.
389 #[serde(rename = "effective-feerate")]
390 pub effective_fee_rate: Option<f64>,
391 /// If [`Self::effective_fee_rate`] is provided, this holds the wtxid's of the transactions
392 /// whose fees and vsizes are included in effective-feerate.
393 #[serde(rename = "effective-includes")]
394 pub effective_includes: Option<Vec<String>>,
395}
396
397/// Result of JSON-RPC method `gettxout`.
398///
399/// # Note
400///
401/// This assumes that the UTXOs are present in the underlying Bitcoin
402/// client's wallet.
403#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
404pub struct GetTransactionDetail {
405 pub address: String,
406 pub category: GetTransactionDetailCategory,
407 pub amount: f64,
408 pub label: Option<String>,
409 pub vout: u32,
410 pub fee: Option<f64>,
411 pub abandoned: Option<bool>,
412}
413
414/// Enum to represent the category of a transaction.
415#[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
416#[serde(rename_all = "lowercase")]
417pub enum GetTransactionDetailCategory {
418 Send,
419 Receive,
420 Generate,
421 Immature,
422 Orphan,
423}
424
425/// Result of the JSON-RPC method `getnewaddress`.
426///
427/// # Note
428///
429/// This assumes that the UTXOs are present in the underlying Bitcoin
430/// client's wallet.
431#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
432pub struct GetNewAddress(pub String);
433
434impl GetNewAddress {
435 /// Converts json straight to a [`Address`].
436 pub fn address(self) -> Result<Address<NetworkUnchecked>, address::ParseError> {
437 let address = self.0.parse::<Address<_>>()?;
438 Ok(address)
439 }
440}
441
442/// Models the result of JSON-RPC method `listunspent`.
443///
444/// # Note
445///
446/// This assumes that the UTXOs are present in the underlying Bitcoin
447/// client's wallet.
448///
449/// Careful with the amount field. It is a [`SignedAmount`], hence can be negative.
450/// Negative amounts for the [`TransactionCategory::Send`], and is positive
451/// for all other categories.
452#[derive(Clone, Debug, PartialEq, Deserialize)]
453pub struct GetTransaction {
454 /// The signed amount in BTC.
455 #[serde(deserialize_with = "deserialize_signed_bitcoin")]
456 pub amount: SignedAmount,
457 /// The signed fee in BTC.
458 pub confirmations: u64,
459 pub generated: Option<bool>,
460 pub trusted: Option<bool>,
461 pub blockhash: Option<String>,
462 pub blockheight: Option<u64>,
463 pub blockindex: Option<u32>,
464 pub blocktime: Option<u64>,
465 /// The transaction id.
466 #[serde(deserialize_with = "deserialize_txid")]
467 pub txid: Txid,
468 pub wtxid: String,
469 pub walletconflicts: Vec<String>,
470 pub replaced_by_txid: Option<String>,
471 pub replaces_txid: Option<String>,
472 pub comment: Option<String>,
473 pub to: Option<String>,
474 pub time: u64,
475 pub timereceived: u64,
476 #[serde(rename = "bip125-replaceable")]
477 pub bip125_replaceable: String,
478 pub details: Vec<GetTransactionDetail>,
479 /// The transaction itself.
480 #[serde(deserialize_with = "deserialize_tx")]
481 pub hex: Transaction,
482}
483
484impl GetTransaction {
485 pub fn block_height(&self) -> u64 {
486 if self.confirmations == 0 {
487 return 0;
488 }
489 self.blockheight.unwrap_or_else(|| {
490 warn!("Txn confirmed but did not obtain blockheight. Setting height to zero");
491 0
492 })
493 }
494}
495
496/// Models the result of JSON-RPC method `listunspent`.
497///
498/// # Note
499///
500/// This assumes that the UTXOs are present in the underlying Bitcoin
501/// client's wallet.
502#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
503pub struct ListUnspent {
504 /// The transaction id.
505 #[serde(deserialize_with = "deserialize_txid")]
506 pub txid: Txid,
507 /// The vout value.
508 pub vout: u32,
509 /// The Bitcoin address.
510 #[serde(deserialize_with = "deserialize_address")]
511 pub address: Address<NetworkUnchecked>,
512 // The associated label, if any.
513 pub label: Option<String>,
514 /// The script pubkey.
515 #[serde(rename = "scriptPubKey")]
516 pub script_pubkey: String,
517 /// The transaction output amount in BTC.
518 #[serde(deserialize_with = "deserialize_bitcoin")]
519 pub amount: Amount,
520 /// The number of confirmations.
521 pub confirmations: u32,
522 /// Whether we have the private keys to spend this output.
523 pub spendable: bool,
524 /// Whether we know how to spend this output, ignoring the lack of keys.
525 pub solvable: bool,
526 /// Whether this output is considered safe to spend.
527 /// Unconfirmed transactions from outside keys and unconfirmed replacement
528 /// transactions are considered unsafe and are not eligible for spending by
529 /// `fundrawtransaction` and `sendtoaddress`.
530 pub safe: bool,
531}
532
533/// Models the result of JSON-RPC method `listtransactions`.
534///
535/// # Note
536///
537/// This assumes that the transactions are present in the underlying Bitcoin
538/// client's wallet.
539///
540/// Careful with the amount field. It is a [`SignedAmount`], hence can be negative.
541/// Negative amounts for the [`TransactionCategory::Send`], and is positive
542/// for all other categories.
543#[derive(Clone, Debug, PartialEq, Deserialize)]
544pub struct ListTransactions {
545 /// The Bitcoin address.
546 #[serde(deserialize_with = "deserialize_address")]
547 pub address: Address<NetworkUnchecked>,
548 /// Category of the transaction.
549 category: TransactionCategory,
550 /// The signed amount in BTC.
551 #[serde(deserialize_with = "deserialize_signed_bitcoin")]
552 pub amount: SignedAmount,
553 /// The label associated with the address, if any.
554 pub label: Option<String>,
555 /// The number of confirmations.
556 pub confirmations: u32,
557 pub trusted: Option<bool>,
558 pub generated: Option<bool>,
559 pub blockhash: Option<String>,
560 pub blockheight: Option<u64>,
561 pub blockindex: Option<u32>,
562 pub blocktime: Option<u64>,
563 /// The transaction id.
564 #[serde(deserialize_with = "deserialize_txid")]
565 pub txid: Txid,
566}
567
568/// Models the result of JSON-RPC method `testmempoolaccept`.
569#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
570pub struct TestMempoolAccept {
571 /// The transaction id.
572 #[serde(deserialize_with = "deserialize_txid")]
573 pub txid: Txid,
574 /// Rejection reason, if any.
575 pub reject_reason: Option<String>,
576}
577
578/// Models the result of JSON-RPC method `signrawtransactionwithwallet`.
579///
580/// # Note
581///
582/// This assumes that the transactions are present in the underlying Bitcoin
583/// client's wallet.
584#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
585pub struct SignRawTransactionWithWallet {
586 /// The Transaction ID.
587 pub hex: String,
588 /// If the transaction has a complete set of signatures.
589 pub complete: bool,
590 /// Errors, if any.
591 pub errors: Option<Vec<SignRawTransactionWithWalletError>>,
592}
593
594/// Models the optional previous transaction outputs argument for the method
595/// `signrawtransactionwithwallet`.
596///
597/// These are the outputs that this transaction depends on but may not yet be in the block chain.
598/// Widely used for One Parent One Child (1P1C) Relay in Bitcoin >28.0.
599///
600/// > transaction outputs
601/// > [
602/// > { (json object)
603/// > "txid": "hex", (string, required) The transaction id
604/// > "vout": n, (numeric, required) The output number
605/// > "scriptPubKey": "hex", (string, required) The output script
606/// > "redeemScript": "hex", (string, optional) (required for P2SH) redeem script
607/// > "witnessScript": "hex", (string, optional) (required for P2WSH or P2SH-P2WSH) witness
608/// > script
609/// > "amount": amount, (numeric or string, optional) (required for Segwit inputs) the
610/// > amount spent
611/// > },
612/// > ...
613/// > ]
614#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
615pub struct PreviousTransactionOutput {
616 /// The transaction id.
617 #[serde(deserialize_with = "deserialize_txid")]
618 pub txid: Txid,
619 /// The output number.
620 pub vout: u32,
621 /// The output script.
622 #[serde(rename = "scriptPubKey")]
623 pub script_pubkey: String,
624 /// The redeem script.
625 #[serde(rename = "redeemScript")]
626 pub redeem_script: Option<String>,
627 /// The witness script.
628 #[serde(rename = "witnessScript")]
629 pub witness_script: Option<String>,
630 /// The amount spent.
631 pub amount: Option<f64>,
632}
633
634/// Models the result of the JSON-RPC method `listdescriptors`.
635#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
636pub struct ListDescriptors {
637 /// The descriptors
638 pub descriptors: Vec<ListDescriptor>,
639}
640
641/// Models the Descriptor in the result of the JSON-RPC method `listdescriptors`.
642#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
643pub struct ListDescriptor {
644 /// The descriptor.
645 pub desc: String,
646}
647
648/// Models the result of the JSON-RPC method `importdescriptors`.
649#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
650pub struct ImportDescriptors {
651 /// The descriptors
652 pub descriptors: Vec<ListDescriptor>,
653}
654
655/// Models the Descriptor in the result of the JSON-RPC method `importdescriptors`.
656#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
657pub struct ImportDescriptor {
658 /// The descriptor.
659 pub desc: String,
660 /// Set this descriptor to be the active descriptor
661 /// for the corresponding output type/externality.
662 pub active: Option<bool>,
663 /// Time from which to start rescanning the blockchain for this descriptor,
664 /// in UNIX epoch time. Can also be a string "now"
665 pub timestamp: String,
666}
667/// Models the Descriptor in the result of the JSON-RPC method `importdescriptors`.
668#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
669pub struct ImportDescriptorResult {
670 /// Result.
671 pub success: bool,
672}
673
674/// Models the `createwallet` JSON-RPC method.
675///
676/// # Note
677///
678/// This can also be used for the `loadwallet` JSON-RPC method.
679#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
680pub struct CreateWallet {
681 /// Wallet name
682 pub wallet_name: String,
683 /// Load on startup
684 pub load_on_startup: Option<bool>,
685}
686
687/// Deserializes the amount in BTC into proper [`Amount`]s.
688fn deserialize_bitcoin<'d, D>(deserializer: D) -> Result<Amount, D::Error>
689where
690 D: Deserializer<'d>,
691{
692 struct SatVisitor;
693
694 impl Visitor<'_> for SatVisitor {
695 type Value = Amount;
696
697 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
698 write!(formatter, "a float representation of btc values expected")
699 }
700
701 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
702 where
703 E: de::Error,
704 {
705 let amount = Amount::from_btc(v).expect("Amount deserialization failed");
706 Ok(amount)
707 }
708 }
709 deserializer.deserialize_any(SatVisitor)
710}
711
712/// Serializes the optional [`Amount`] into BTC.
713fn serialize_option_bitcoin<S>(amount: &Option<Amount>, serializer: S) -> Result<S::Ok, S::Error>
714where
715 S: Serializer,
716{
717 match amount {
718 Some(amt) => serializer.serialize_some(&amt.to_btc()),
719 None => serializer.serialize_none(),
720 }
721}
722
723/// Deserializes the fee rate from sat/vB into proper [`FeeRate`].
724///
725/// Note: Bitcoin Core 0.21+ uses sat/vB for fee rates for most RPC methods/results.
726fn deserialize_feerate<'d, D>(deserializer: D) -> Result<FeeRate, D::Error>
727where
728 D: Deserializer<'d>,
729{
730 struct FeeRateVisitor;
731
732 impl Visitor<'_> for FeeRateVisitor {
733 type Value = FeeRate;
734
735 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
736 write!(
737 formatter,
738 "a numeric representation of fee rate in sat/vB expected"
739 )
740 }
741
742 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
743 where
744 E: de::Error,
745 {
746 // The value is already in sat/vB (Bitcoin Core 0.21+)
747 let sat_per_vb = v.round() as u64;
748 let fee_rate = FeeRate::from_sat_per_vb(sat_per_vb)
749 .ok_or_else(|| de::Error::custom("Invalid fee rate"))?;
750 Ok(fee_rate)
751 }
752 }
753 deserializer.deserialize_any(FeeRateVisitor)
754}
755
756/// Deserializes the *signed* amount in BTC into proper [`SignedAmount`]s.
757fn deserialize_signed_bitcoin<'d, D>(deserializer: D) -> Result<SignedAmount, D::Error>
758where
759 D: Deserializer<'d>,
760{
761 struct SatVisitor;
762
763 impl Visitor<'_> for SatVisitor {
764 type Value = SignedAmount;
765
766 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
767 write!(formatter, "a float representation of btc values expected")
768 }
769
770 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
771 where
772 E: de::Error,
773 {
774 let signed_amount = SignedAmount::from_btc(v).expect("Amount deserialization failed");
775 Ok(signed_amount)
776 }
777 }
778 deserializer.deserialize_any(SatVisitor)
779}
780
781/// Deserializes the *signed* amount in BTC into proper [`SignedAmount`]s.
782#[expect(dead_code)]
783fn deserialize_signed_bitcoin_option<'d, D>(
784 deserializer: D,
785) -> Result<Option<SignedAmount>, D::Error>
786where
787 D: Deserializer<'d>,
788{
789 let f: Option<f64> = Option::deserialize(deserializer)?;
790 match f {
791 Some(v) => deserialize_signed_bitcoin(v.into_deserializer()).map(Some),
792 None => Ok(None),
793 }
794}
795
796/// Deserializes the transaction id string into proper [`Txid`]s.
797fn deserialize_txid<'d, D>(deserializer: D) -> Result<Txid, D::Error>
798where
799 D: Deserializer<'d>,
800{
801 struct TxidVisitor;
802
803 impl Visitor<'_> for TxidVisitor {
804 type Value = Txid;
805
806 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
807 write!(formatter, "a transaction id string expected")
808 }
809
810 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
811 where
812 E: de::Error,
813 {
814 let txid = v.parse::<Txid>().expect("invalid txid");
815
816 Ok(txid)
817 }
818 }
819 deserializer.deserialize_any(TxidVisitor)
820}
821
822/// Deserializes the transaction hex string into proper [`Transaction`]s.
823fn deserialize_tx<'d, D>(deserializer: D) -> Result<Transaction, D::Error>
824where
825 D: Deserializer<'d>,
826{
827 struct TxVisitor;
828
829 impl Visitor<'_> for TxVisitor {
830 type Value = Transaction;
831
832 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
833 write!(formatter, "a transaction hex string expected")
834 }
835
836 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
837 where
838 E: de::Error,
839 {
840 let tx = consensus::encode::deserialize_hex::<Transaction>(v)
841 .expect("failed to deserialize tx hex");
842 Ok(tx)
843 }
844 }
845 deserializer.deserialize_any(TxVisitor)
846}
847
848/// Deserializes a base64-encoded PSBT string into proper [`Psbt`]s.
849///
850/// # Note
851///
852/// Expects a valid base64-encoded PSBT as defined in BIP 174. The PSBT
853/// string must contain valid transaction data and metadata for successful parsing.
854fn deserialize_psbt<'d, D>(deserializer: D) -> Result<Psbt, D::Error>
855where
856 D: Deserializer<'d>,
857{
858 struct PsbtVisitor;
859
860 impl Visitor<'_> for PsbtVisitor {
861 type Value = Psbt;
862
863 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
864 write!(formatter, "a base64-encoded PSBT string expected")
865 }
866
867 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
868 where
869 E: de::Error,
870 {
871 v.parse::<Psbt>()
872 .map_err(|e| E::custom(format!("failed to deserialize PSBT: {e}")))
873 }
874 }
875 deserializer.deserialize_any(PsbtVisitor)
876}
877
878/// Deserializes an optional base64-encoded PSBT string into `Option<Psbt>`.
879///
880/// # Note
881///
882/// When the JSON field is `null` or missing, returns `None`. When present,
883/// deserializes the base64 PSBT string using the same validation as [`deserialize_psbt`].
884fn deserialize_option_psbt<'d, D>(deserializer: D) -> Result<Option<Psbt>, D::Error>
885where
886 D: Deserializer<'d>,
887{
888 let opt: Option<String> = Option::deserialize(deserializer)?;
889 match opt {
890 Some(s) => s
891 .parse::<Psbt>()
892 .map(Some)
893 .map_err(|e| de::Error::custom(format!("failed to deserialize PSBT: {e}"))),
894 None => Ok(None),
895 }
896}
897
898fn deserialize_option_tx<'d, D>(deserializer: D) -> Result<Option<Transaction>, D::Error>
899where
900 D: Deserializer<'d>,
901{
902 let opt: Option<String> = Option::deserialize(deserializer)?;
903 match opt {
904 Some(s) => consensus::encode::deserialize_hex::<Transaction>(&s)
905 .map(Some)
906 .map_err(|e| de::Error::custom(format!("failed to deserialize transaction hex: {e}"))),
907 None => Ok(None),
908 }
909}
910
911/// Deserializes the address string into proper [`Address`]s.
912///
913/// # Note
914///
915/// The user is responsible for ensuring that the address is valid,
916/// since this functions returns an [`Address<NetworkUnchecked>`].
917fn deserialize_address<'d, D>(deserializer: D) -> Result<Address<NetworkUnchecked>, D::Error>
918where
919 D: Deserializer<'d>,
920{
921 struct AddressVisitor;
922 impl Visitor<'_> for AddressVisitor {
923 type Value = Address<NetworkUnchecked>;
924
925 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
926 write!(formatter, "a Bitcoin address string expected")
927 }
928
929 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
930 where
931 E: de::Error,
932 {
933 v.parse::<Address<_>>()
934 .map_err(|e| E::custom(format!("failed to deserialize address: {e}")))
935 }
936 }
937 deserializer.deserialize_any(AddressVisitor)
938}
939
940/// Deserializes the blockhash string into proper [`BlockHash`]s.
941#[expect(dead_code)]
942fn deserialize_blockhash<'d, D>(deserializer: D) -> Result<BlockHash, D::Error>
943where
944 D: Deserializer<'d>,
945{
946 struct BlockHashVisitor;
947
948 impl Visitor<'_> for BlockHashVisitor {
949 type Value = BlockHash;
950
951 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
952 write!(formatter, "a blockhash string expected")
953 }
954
955 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
956 where
957 E: de::Error,
958 {
959 let blockhash = consensus::encode::deserialize_hex::<BlockHash>(v)
960 .expect("BlockHash deserialization failed");
961 Ok(blockhash)
962 }
963 }
964 deserializer.deserialize_any(BlockHashVisitor)
965}
966
967/// Deserializes the height string into proper [`Height`]s.
968#[expect(dead_code)]
969fn deserialize_height<'d, D>(deserializer: D) -> Result<Height, D::Error>
970where
971 D: Deserializer<'d>,
972{
973 struct HeightVisitor;
974
975 impl Visitor<'_> for HeightVisitor {
976 type Value = Height;
977
978 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
979 write!(formatter, "a height u32 string expected")
980 }
981
982 fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
983 where
984 E: de::Error,
985 {
986 let height = Height::from_consensus(v).expect("Height deserialization failed");
987 Ok(height)
988 }
989 }
990 deserializer.deserialize_any(HeightVisitor)
991}
992
993/// Signature hash types for Bitcoin transactions.
994///
995/// These types specify which parts of a transaction are included in the signature
996/// hash calculation when signing transaction inputs. Used with wallet signing
997/// operations like `wallet_process_psbt`.
998///
999/// # Note
1000///
1001/// These correspond to the SIGHASH flags defined in Bitcoin's script system
1002/// and BIP 143 (witness transaction digest).
1003#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1004#[serde(rename_all = "UPPERCASE")]
1005pub enum SighashType {
1006 /// Use the default signature hash type (equivalent to SIGHASH_ALL).
1007 Default,
1008
1009 /// Sign all inputs and all outputs of the transaction.
1010 ///
1011 /// This is the most common and secure signature type, ensuring the entire
1012 /// transaction structure cannot be modified after signing.
1013 All,
1014
1015 /// Sign all inputs but no outputs.
1016 ///
1017 /// Allows outputs to be modified after signing, useful for donation scenarios
1018 /// where the exact destination amounts can be adjusted.
1019 None,
1020
1021 /// Sign all inputs and the output with the same index as this input.
1022 ///
1023 /// Used in scenarios where multiple parties contribute inputs and want to
1024 /// ensure their corresponding output is protected.
1025 Single,
1026
1027 /// Combination of SIGHASH_ALL with ANYONECANPAY flag.
1028 ///
1029 /// Signs all outputs but only this specific input, allowing other inputs
1030 /// to be added or removed. Useful for crowdfunding transactions.
1031 #[serde(rename = "ALL|ANYONECANPAY")]
1032 AllPlusAnyoneCanPay,
1033
1034 /// Combination of SIGHASH_NONE with ANYONECANPAY flag.
1035 ///
1036 /// Signs only this specific input with no outputs committed, providing
1037 /// maximum flexibility for transaction modification.
1038 #[serde(rename = "NONE|ANYONECANPAY")]
1039 NonePlusAnyoneCanPay,
1040
1041 /// Combination of SIGHASH_SINGLE with ANYONECANPAY flag.
1042 ///
1043 /// Signs only this input and its corresponding output, allowing other
1044 /// inputs and outputs to be modified independently.
1045 #[serde(rename = "SINGLE|ANYONECANPAY")]
1046 SinglePlusAnyoneCanPay,
1047}
1048
1049/// Options for creating a funded PSBT with wallet inputs.
1050///
1051/// Used with `wallet_create_funded_psbt` to control funding behavior,
1052/// fee estimation, and transaction policies when the wallet automatically
1053/// selects inputs to fund the specified outputs.
1054///
1055/// # Note
1056///
1057/// All fields are optional and will use Bitcoin Core defaults if not specified.
1058/// Fee rate takes precedence over confirmation target if both are provided.
1059#[derive(Clone, Debug, PartialEq, Serialize, Default)]
1060pub struct WalletCreateFundedPsbtOptions {
1061 /// Fee rate in sat/vB (satoshis per virtual byte) for the transaction.
1062 ///
1063 /// If specified, this overrides the `conf_target` parameter for fee estimation.
1064 /// Must be a positive value representing the desired fee density.
1065 #[serde(default, rename = "fee_rate", skip_serializing_if = "Option::is_none")]
1066 pub fee_rate: Option<f64>,
1067
1068 /// Whether to lock the selected UTXOs to prevent them from being spent by other transactions.
1069 ///
1070 /// When `true`, the wallet will temporarily lock the selected unspent outputs
1071 /// until the transaction is broadcast or manually unlocked. Default is `false`.
1072 #[serde(
1073 default,
1074 rename = "lockUnspents",
1075 skip_serializing_if = "Option::is_none"
1076 )]
1077 pub lock_unspents: Option<bool>,
1078
1079 /// Target number of confirmations for automatic fee estimation.
1080 ///
1081 /// Represents the desired number of blocks within which the transaction should
1082 /// be confirmed. Higher values result in lower fees but longer confirmation times.
1083 /// Ignored if `fee_rate` is specified.
1084 #[serde(
1085 default,
1086 rename = "conf_target",
1087 skip_serializing_if = "Option::is_none"
1088 )]
1089 pub conf_target: Option<u16>,
1090
1091 /// Whether the transaction should be BIP-125 opt-in Replace-By-Fee (RBF) enabled.
1092 ///
1093 /// When `true`, allows the transaction to be replaced with a higher-fee version
1094 /// before confirmation. Useful for fee bumping if the initial fee proves insufficient.
1095 #[serde(
1096 default,
1097 rename = "replaceable",
1098 skip_serializing_if = "Option::is_none"
1099 )]
1100 pub replaceable: Option<bool>,
1101}
1102
1103/// Result of the `walletcreatefundedpsbt` RPC method.
1104///
1105/// Contains a funded PSBT created by the wallet with automatically selected inputs
1106/// to cover the specified outputs, along with fee information and change output details.
1107///
1108/// # Note
1109///
1110/// The PSBT returned is not signed and requires further processing with
1111/// `wallet_process_psbt` or `finalize_psbt` before broadcasting.
1112#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
1113pub struct WalletCreateFundedPsbt {
1114 /// The funded PSBT with inputs selected by the wallet.
1115 ///
1116 /// Contains the unsigned transaction structure with all necessary
1117 /// input and output information for subsequent signing operations.
1118 #[serde(deserialize_with = "deserialize_psbt")]
1119 pub psbt: Psbt,
1120
1121 /// The fee amount in BTC paid by this transaction.
1122 ///
1123 /// Represents the total fee calculated based on the selected inputs,
1124 /// outputs, and the specified fee rate or confirmation target.
1125 #[serde(deserialize_with = "deserialize_bitcoin")]
1126 pub fee: Amount,
1127
1128 /// The position of the change output in the transaction outputs array.
1129 ///
1130 /// If no change output was created (exact amount match), this will be -1.
1131 /// Otherwise, indicates the zero-based index of the change output.
1132 #[serde(rename = "changepos")]
1133 pub change_pos: i32,
1134}
1135
1136/// Result of the `walletprocesspsbt` and `finalizepsbt` RPC methods.
1137///
1138/// Contains the processed PSBT state, completion status, and optionally the
1139/// extracted final transaction. This struct handles the Bitcoin Core's PSBT
1140/// workflow where PSBTs can be incrementally signed and eventually finalized.
1141///
1142/// # Note
1143///
1144/// The `psbt` field contains the updated PSBT after processing, while `hex`
1145/// contains the final transaction only when `complete` is `true` and extraction
1146/// is requested. Both fields may be `None` depending on the operation context.
1147#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
1148pub struct WalletProcessPsbtResult {
1149 /// The processed Partially Signed Bitcoin Transaction.
1150 ///
1151 /// Contains the PSBT after wallet processing with any signatures or input data
1152 /// that could be added. Will be `None` if the transaction was fully extracted
1153 /// and the PSBT is no longer needed.
1154 #[serde(deserialize_with = "deserialize_option_psbt")]
1155 pub psbt: Option<Psbt>,
1156
1157 /// Whether the transaction is complete and ready for broadcast.
1158 ///
1159 /// `true` indicates all required signatures have been collected and the
1160 /// transaction can be finalized. `false` means more signatures are needed
1161 /// before the transaction can be broadcast to the network.
1162 pub complete: bool,
1163
1164 /// The final transaction ready for broadcast (when complete).
1165 ///
1166 /// Contains the fully signed and finalized transaction when `complete` is `true`
1167 /// and extraction was requested. Will be `None` for incomplete transactions or
1168 /// when extraction is not performed.
1169 #[serde(
1170 deserialize_with = "deserialize_option_tx",
1171 skip_serializing_if = "Option::is_none",
1172 default
1173 )]
1174 pub hex: Option<Transaction>,
1175}
1176
1177/// Result of the `getaddressinfo` RPC method.
1178///
1179/// Provides detailed information about a Bitcoin address, including ownership
1180/// status, watching capabilities, and spending permissions within the wallet.
1181///
1182/// # Note
1183///
1184/// Optional fields may be `None` if the wallet doesn't have specific information
1185/// about the address or if the address is not related to the wallet.
1186#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
1187pub struct GetAddressInfo {
1188 /// The Bitcoin address that was queried.
1189 ///
1190 /// Returns the same address that was provided as input to `getaddressinfo`,
1191 /// validated and parsed into the proper Address type.
1192 #[serde(deserialize_with = "deserialize_address")]
1193 pub address: Address<NetworkUnchecked>,
1194
1195 /// Whether the address belongs to the wallet (can receive payments to it).
1196 ///
1197 /// `true` if the wallet owns the private key or can generate signatures for this address.
1198 /// `false` if the address is not owned by the wallet. `None` if ownership status is unknown.
1199 #[serde(rename = "ismine")]
1200 pub is_mine: Option<bool>,
1201
1202 /// Whether the address is watch-only (monitored but not spendable).
1203 ///
1204 /// `true` if the wallet watches this address for incoming transactions but cannot
1205 /// spend from it (no private key). `false` if the address is fully controlled.
1206 /// `None` if watch status is not applicable.
1207 #[serde(rename = "iswatchonly")]
1208 pub is_watchonly: Option<bool>,
1209
1210 /// Whether the wallet knows how to spend coins sent to this address.
1211 ///
1212 /// `true` if the wallet has enough information (private keys, scripts) to create
1213 /// valid spending transactions from this address. `false` if the address cannot
1214 /// be spent by this wallet. `None` if spendability cannot be determined.
1215 pub solvable: Option<bool>,
1216}
1217
1218/// Query options for filtering unspent transaction outputs.
1219///
1220/// Used with `list_unspent` to apply additional filtering criteria
1221/// beyond confirmation counts and addresses, allowing precise UTXO selection
1222/// based on amount ranges and result limits.
1223///
1224/// # Note
1225///
1226/// All fields are optional and can be combined. UTXOs must satisfy all
1227/// specified criteria to be included in the results.
1228#[derive(Clone, Debug, PartialEq, Serialize)]
1229#[serde(rename_all = "camelCase")]
1230pub struct ListUnspentQueryOptions {
1231 /// Minimum amount that UTXOs must have to be included.
1232 ///
1233 /// Only unspent outputs with a value greater than or equal to this amount
1234 /// will be returned. Useful for filtering out dust or very small UTXOs.
1235 #[serde(serialize_with = "serialize_option_bitcoin")]
1236 pub minimum_amount: Option<Amount>,
1237
1238 /// Maximum amount that UTXOs can have to be included.
1239 ///
1240 /// Only unspent outputs with a value less than or equal to this amount
1241 /// will be returned. Useful for finding smaller UTXOs or avoiding large ones.
1242 #[serde(serialize_with = "serialize_option_bitcoin")]
1243 pub maximum_amount: Option<Amount>,
1244
1245 /// Maximum number of UTXOs to return in the result set.
1246 ///
1247 /// Limits the total number of unspent outputs returned, regardless of how many
1248 /// match the other criteria. Useful for pagination or limiting response size.
1249 pub maximum_count: Option<u32>,
1250}
1251
1252/// Options for psbtbumpfee RPC method.
1253#[derive(Clone, Debug, Default, PartialEq, Serialize)]
1254pub struct PsbtBumpFeeOptions {
1255 /// Confirmation target in blocks.
1256 #[serde(skip_serializing_if = "Option::is_none")]
1257 pub conf_target: Option<u16>,
1258
1259 /// Fee rate in sat/vB.
1260 #[serde(skip_serializing_if = "Option::is_none")]
1261 pub fee_rate: Option<FeeRate>,
1262
1263 /// Whether the new transaction should be BIP-125 replaceable.
1264 #[serde(skip_serializing_if = "Option::is_none")]
1265 pub replaceable: Option<bool>,
1266
1267 /// Fee estimate mode ("unset", "economical", "conservative").
1268 #[serde(skip_serializing_if = "Option::is_none")]
1269 pub estimate_mode: Option<String>,
1270
1271 /// New transaction outputs to replace the existing ones.
1272 #[serde(skip_serializing_if = "Option::is_none")]
1273 pub outputs: Option<Vec<CreateRawTransactionOutput>>,
1274
1275 /// Index of the change output to recycle from the original transaction.
1276 #[serde(skip_serializing_if = "Option::is_none")]
1277 pub original_change_index: Option<u32>,
1278}
1279
1280/// Result of the psbtbumpfee RPC method.
1281#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
1282pub struct PsbtBumpFee {
1283 /// The base64-encoded unsigned PSBT of the new transaction.
1284 #[serde(deserialize_with = "deserialize_psbt")]
1285 pub psbt: Psbt,
1286
1287 /// The fee of the replaced transaction.
1288 #[serde(deserialize_with = "deserialize_feerate")]
1289 pub origfee: FeeRate,
1290
1291 /// The fee of the new transaction.
1292 #[serde(deserialize_with = "deserialize_feerate")]
1293 pub fee: FeeRate,
1294
1295 /// Errors encountered during processing (if any).
1296 pub errors: Option<Vec<String>>,
1297}
1298
1299#[cfg(test)]
1300mod tests {
1301 use super::*;
1302 use serde_json;
1303
1304 // Taken from https://docs.rs/bitcoin/0.32.6/src/bitcoin/psbt/mod.rs.html#1515-1520
1305 // BIP 174 test vector with inputs and outputs (more realistic than empty transaction)
1306 const TEST_PSBT: &str = "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA";
1307
1308 // Valid Bitcoin transaction hex (Genesis block coinbase transaction)
1309 const TEST_TX_HEX: &str = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000";
1310
1311 #[test]
1312 fn test_wallet_process_psbt_result() {
1313 let valid_psbt = TEST_PSBT;
1314
1315 // Test complete with hex
1316 let test_tx_hex = TEST_TX_HEX;
1317 let json1 = format!(r#"{{"psbt":"{valid_psbt}","complete":true,"hex":"{test_tx_hex}"}}"#);
1318 let result1: WalletProcessPsbtResult = serde_json::from_str(&json1).unwrap();
1319 assert!(result1.psbt.is_some());
1320 assert!(result1.complete);
1321 assert!(result1.hex.is_some());
1322 let tx = result1.hex.unwrap();
1323 assert!(!tx.input.is_empty());
1324 assert!(!tx.output.is_empty());
1325
1326 // Test incomplete without hex
1327 let json2 = format!(r#"{{"psbt":"{valid_psbt}","complete":false}}"#);
1328 let result2: WalletProcessPsbtResult = serde_json::from_str(&json2).unwrap();
1329 assert!(result2.psbt.is_some());
1330 assert!(!result2.complete);
1331 }
1332
1333 #[test]
1334 fn test_sighashtype_serialize() {
1335 let sighash = SighashType::All;
1336 let serialized = serde_json::to_string(&sighash).unwrap();
1337 assert_eq!(serialized, "\"ALL\"");
1338
1339 let sighash2 = SighashType::AllPlusAnyoneCanPay;
1340 let serialized2 = serde_json::to_string(&sighash2).unwrap();
1341 assert_eq!(serialized2, "\"ALL|ANYONECANPAY\"");
1342 }
1343
1344 #[test]
1345 fn test_list_unspent_query_options_camelcase() {
1346 let options = ListUnspentQueryOptions {
1347 minimum_amount: Some(Amount::from_btc(0.5).unwrap()),
1348 maximum_amount: Some(Amount::from_btc(2.0).unwrap()),
1349 maximum_count: Some(10),
1350 };
1351 let serialized = serde_json::to_string(&options).unwrap();
1352
1353 assert!(serialized.contains("\"minimumAmount\":0.5"));
1354 assert!(serialized.contains("\"maximumAmount\":2.0"));
1355 assert!(serialized.contains("\"maximumCount\":10"));
1356 }
1357
1358 #[test]
1359 fn test_psbt_parsing() {
1360 // Test valid PSBT parsing
1361 let valid_psbt = TEST_PSBT;
1362 let json1 = format!(r#"{{"psbt":"{valid_psbt}","fee":0.001,"changepos":-1}}"#);
1363 let result1: WalletCreateFundedPsbt = serde_json::from_str(&json1).unwrap();
1364 assert!(!result1.psbt.inputs.is_empty()); // BIP 174 test vector has inputs
1365
1366 // Test invalid PSBT parsing fails
1367 let invalid_psbt = "invalid_base64";
1368 let json2 = format!(r#"{{"psbt":"{invalid_psbt}","fee":0.001,"changepos":-1}}"#);
1369 let result2 = serde_json::from_str::<WalletCreateFundedPsbt>(&json2);
1370 assert!(result2.is_err());
1371 }
1372}