Skip to main content

bitcoind_async_client/
types.rs

1//! Types that are not returned by the RPC server, but used as arguments/inputs of the RPC methods.
2
3use bitcoin::{Amount, FeeRate, Txid};
4use serde::{
5    de::{self, Visitor},
6    Deserialize, Deserializer, Serialize, Serializer,
7};
8use serde_json::Value;
9
10/// Models the arguments of JSON-RPC method `createrawtransaction`.
11///
12/// # Note
13///
14/// Assumes that the transaction is always "replaceable" by default and has a locktime of 0.
15#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
16pub struct CreateRawTransactionArguments {
17    pub inputs: Vec<CreateRawTransactionInput>,
18    pub outputs: Vec<CreateRawTransactionOutput>,
19}
20
21/// Models the input of JSON-RPC method `createrawtransaction`.
22#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
23pub struct CreateRawTransactionInput {
24    pub txid: String,
25    pub vout: u32,
26}
27
28/// Models transaction outputs for Bitcoin RPC methods.
29///
30/// Used by various RPC methods such as `createrawtransaction`, `psbtbumpfee`,
31/// and `walletcreatefundedpsbt`. The outputs are specified as key-value pairs,
32/// where the keys are addresses and the values are amounts to send.
33#[derive(Clone, Debug, PartialEq, Deserialize)]
34#[serde(untagged)]
35pub enum CreateRawTransactionOutput {
36    /// A pair of an [`bitcoin::Address`] string and an [`Amount`] in BTC.
37    AddressAmount {
38        /// An [`bitcoin::Address`] string.
39        address: String,
40        /// An [`Amount`] in BTC.
41        amount: f64,
42    },
43    /// A payload such as in `OP_RETURN` transactions.
44    Data {
45        /// The payload.
46        data: String,
47    },
48}
49
50impl Serialize for CreateRawTransactionOutput {
51    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
52    where
53        S: serde::Serializer,
54    {
55        match self {
56            CreateRawTransactionOutput::AddressAmount { address, amount } => {
57                let mut map = serde_json::Map::new();
58                map.insert(
59                    address.clone(),
60                    serde_json::Value::Number(serde_json::Number::from_f64(*amount).unwrap()),
61                );
62                map.serialize(serializer)
63            }
64            CreateRawTransactionOutput::Data { data } => {
65                let mut map = serde_json::Map::new();
66                map.insert("data".to_string(), serde_json::Value::String(data.clone()));
67                map.serialize(serializer)
68            }
69        }
70    }
71}
72
73/// Models the optional previous transaction outputs argument for the method
74/// `signrawtransactionwithwallet`.
75///
76/// These are the outputs that this transaction depends on but may not yet be in the block chain.
77/// Widely used for One Parent One Child (1P1C) Relay in Bitcoin >28.0.
78///
79/// > transaction outputs
80/// > [
81/// > {                            (json object)
82/// > "txid": "hex",             (string, required) The transaction id
83/// > "vout": n,                 (numeric, required) The output number
84/// > "scriptPubKey": "hex",     (string, required) The output script
85/// > "redeemScript": "hex",     (string, optional) (required for P2SH) redeem script
86/// > "witnessScript": "hex",    (string, optional) (required for P2WSH or P2SH-P2WSH) witness
87/// > script
88/// > "amount": amount,          (numeric or string, optional) (required for Segwit inputs) the
89/// > amount spent
90/// > },
91/// > ...
92/// > ]
93#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
94pub struct PreviousTransactionOutput {
95    /// The transaction id.
96    #[serde(deserialize_with = "deserialize_txid")]
97    pub txid: Txid,
98    /// The output number.
99    pub vout: u32,
100    /// The output script.
101    #[serde(rename = "scriptPubKey")]
102    pub script_pubkey: String,
103    /// The redeem script.
104    #[serde(rename = "redeemScript")]
105    pub redeem_script: Option<String>,
106    /// The witness script.
107    #[serde(rename = "witnessScript")]
108    pub witness_script: Option<String>,
109    /// The amount spent.
110    pub amount: Option<f64>,
111}
112
113/// Models the Descriptor in the result of the JSON-RPC method `importdescriptors`.
114#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
115pub struct ImportDescriptorInput {
116    /// The descriptor.
117    pub desc: String,
118    /// Set this descriptor to be the active descriptor
119    /// for the corresponding output type/externality.
120    pub active: Option<bool>,
121    /// Time from which to start rescanning the blockchain for this descriptor,
122    /// in UNIX epoch time. Can also be a string "now"
123    pub timestamp: String,
124}
125
126/// Models the `createwallet` JSON-RPC method.
127///
128/// # Note
129///
130/// This can also be used for the `loadwallet` JSON-RPC method.
131#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
132pub struct CreateWalletArguments {
133    /// Wallet name
134    pub name: String,
135    /// Load on startup
136    pub load_on_startup: Option<bool>,
137}
138
139/// Shared options for transaction broadcast RPC methods.
140#[derive(Clone, Debug, Default, PartialEq, Eq)]
141pub struct BroadcastOptions {
142    /// Reject transactions whose fee rate is higher than this value.
143    ///
144    /// Bitcoin Core expects this value as BTC/kvB.
145    pub max_fee_rate: Option<FeeRate>,
146
147    /// Reject transactions whose provably unspendable outputs exceed this amount.
148    ///
149    /// Bitcoin Core expects this value as BTC.
150    pub max_burn_amount: Option<Amount>,
151}
152
153impl BroadcastOptions {
154    /// Converts these options to positional Bitcoin Core RPC parameters.
155    pub fn to_params(&self) -> impl IntoIterator<Item = Value> {
156        let mut params = Vec::new();
157
158        if self.max_fee_rate.is_none() && self.max_burn_amount.is_none() {
159            return params;
160        }
161
162        match self.max_fee_rate {
163            Some(max_fee_rate) => params.push(Value::from(max_fee_rate_btc_per_kvb(max_fee_rate))),
164            None => params.push(Value::Null),
165        }
166
167        if let Some(max_burn_amount) = self.max_burn_amount {
168            params.push(Value::from(max_burn_amount.to_btc()));
169        }
170
171        params
172    }
173}
174
175fn max_fee_rate_btc_per_kvb(max_fee_rate: FeeRate) -> f64 {
176    max_fee_rate.to_sat_per_kwu() as f64 / 25_000_000.0
177}
178
179/// Options for the `sendrawtransaction` RPC method.
180pub type SendRawTransactionOptions = BroadcastOptions;
181
182/// Serializes the optional [`Amount`] into BTC.
183fn serialize_option_bitcoin<S>(amount: &Option<Amount>, serializer: S) -> Result<S::Ok, S::Error>
184where
185    S: Serializer,
186{
187    match amount {
188        Some(amt) => serializer.serialize_some(&amt.to_btc()),
189        None => serializer.serialize_none(),
190    }
191}
192
193/// Serializes the optional [`FeeRate`] into sat/vB.
194///
195/// Bitcoin Core's `fee_rate` option (e.g. for `walletcreatefundedpsbt` and `psbtbumpfee`) is
196/// expressed in sat/vB, while [`FeeRate`] stores its value internally in sat/kwu
197/// (250 sat/kwu = 1 sat/vB). Serializing the value as a fractional sat/vB number preserves
198/// sub-1 sat/vB fee rates.
199fn serialize_option_fee_rate<S>(
200    fee_rate: &Option<FeeRate>,
201    serializer: S,
202) -> Result<S::Ok, S::Error>
203where
204    S: Serializer,
205{
206    match fee_rate {
207        Some(fr) => serializer.serialize_some(&(fr.to_sat_per_kwu() as f64 / 250.0)),
208        None => serializer.serialize_none(),
209    }
210}
211
212/// Deserializes the transaction id string into proper [`Txid`]s.
213fn deserialize_txid<'d, D>(deserializer: D) -> Result<Txid, D::Error>
214where
215    D: Deserializer<'d>,
216{
217    struct TxidVisitor;
218
219    impl Visitor<'_> for TxidVisitor {
220        type Value = Txid;
221
222        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
223            write!(formatter, "a transaction id string expected")
224        }
225
226        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
227        where
228            E: de::Error,
229        {
230            let txid = v.parse::<Txid>().expect("invalid txid");
231
232            Ok(txid)
233        }
234    }
235    deserializer.deserialize_any(TxidVisitor)
236}
237
238/// Signature hash types for Bitcoin transactions.
239///
240/// These types specify which parts of a transaction are included in the signature
241/// hash calculation when signing transaction inputs. Used with wallet signing
242/// operations like `walletprocesspsbt`.
243///
244/// # Note
245///
246/// These correspond to the SIGHASH flags defined in Bitcoin's script system
247/// and BIP 143 (witness transaction digest).
248#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
249#[serde(rename_all = "UPPERCASE")]
250pub enum SighashType {
251    /// Use the default signature hash type (equivalent to SIGHASH_ALL).
252    Default,
253
254    /// Sign all inputs and all outputs of the transaction.
255    ///
256    /// This is the most common and secure signature type, ensuring the entire
257    /// transaction structure cannot be modified after signing.
258    All,
259
260    /// Sign all inputs but no outputs.
261    ///
262    /// Allows outputs to be modified after signing, useful for donation scenarios
263    /// where the exact destination amounts can be adjusted.
264    None,
265
266    /// Sign all inputs and the output with the same index as this input.
267    ///
268    /// Used in scenarios where multiple parties contribute inputs and want to
269    /// ensure their corresponding output is protected.
270    Single,
271
272    /// Combination of SIGHASH_ALL with ANYONECANPAY flag.
273    ///
274    /// Signs all outputs but only this specific input, allowing other inputs
275    /// to be added or removed. Useful for crowdfunding transactions.
276    #[serde(rename = "ALL|ANYONECANPAY")]
277    AllPlusAnyoneCanPay,
278
279    /// Combination of SIGHASH_NONE with ANYONECANPAY flag.
280    ///
281    /// Signs only this specific input with no outputs committed, providing
282    /// maximum flexibility for transaction modification.
283    #[serde(rename = "NONE|ANYONECANPAY")]
284    NonePlusAnyoneCanPay,
285
286    /// Combination of SIGHASH_SINGLE with ANYONECANPAY flag.
287    ///
288    /// Signs only this input and its corresponding output, allowing other
289    /// inputs and outputs to be modified independently.
290    #[serde(rename = "SINGLE|ANYONECANPAY")]
291    SinglePlusAnyoneCanPay,
292}
293
294/// Options for creating a funded PSBT with wallet inputs.
295///
296/// Used with `wallet_create_funded_psbt` to control funding behavior,
297/// fee estimation, and transaction policies when the wallet automatically
298/// selects inputs to fund the specified outputs.
299///
300/// # Note
301///
302/// All fields are optional and will use Bitcoin Core defaults if not specified.
303/// Fee rate takes precedence over confirmation target if both are provided.
304#[derive(Clone, Debug, PartialEq, Serialize, Default)]
305pub struct WalletCreateFundedPsbtOptions {
306    /// Fee rate in sat/vB (satoshis per virtual byte) for the transaction.
307    ///
308    /// If specified, this overrides the `conf_target` parameter for fee estimation.
309    /// Must be a positive value representing the desired fee density.
310    #[serde(
311        default,
312        rename = "fee_rate",
313        skip_serializing_if = "Option::is_none",
314        serialize_with = "serialize_option_fee_rate"
315    )]
316    pub fee_rate: Option<FeeRate>,
317
318    /// Whether to lock the selected UTXOs to prevent them from being spent by other transactions.
319    ///
320    /// When `true`, the wallet will temporarily lock the selected unspent outputs
321    /// until the transaction is broadcast or manually unlocked. Default is `false`.
322    #[serde(
323        default,
324        rename = "lockUnspents",
325        skip_serializing_if = "Option::is_none"
326    )]
327    pub lock_unspents: Option<bool>,
328
329    /// Target number of confirmations for automatic fee estimation.
330    ///
331    /// Represents the desired number of blocks within which the transaction should
332    /// be confirmed. Higher values result in lower fees but longer confirmation times.
333    /// Ignored if `fee_rate` is specified.
334    #[serde(
335        default,
336        rename = "conf_target",
337        skip_serializing_if = "Option::is_none"
338    )]
339    pub conf_target: Option<u16>,
340
341    /// Whether the transaction should be BIP-125 opt-in Replace-By-Fee (RBF) enabled.
342    ///
343    /// When `true`, allows the transaction to be replaced with a higher-fee version
344    /// before confirmation. Useful for fee bumping if the initial fee proves insufficient.
345    #[serde(
346        default,
347        rename = "replaceable",
348        skip_serializing_if = "Option::is_none"
349    )]
350    pub replaceable: Option<bool>,
351}
352
353/// Options for sending transactions with the wallet.
354///
355/// Used with [`Wallet::send`](crate::traits::Wallet::send) to fund, sign, and optionally
356/// broadcast a transaction via Bitcoin Core's `send` RPC.
357///
358/// # Note
359///
360/// All fields are optional and will use Bitcoin Core defaults if not specified.
361#[derive(Clone, Debug, PartialEq, Serialize, Default)]
362pub struct SendOptions {
363    /// Add the transaction to the wallet and broadcast it. When `false`, `send` returns
364    /// the signed PSBT without broadcasting. Default is `true`.
365    #[serde(
366        default,
367        rename = "add_to_wallet",
368        skip_serializing_if = "Option::is_none"
369    )]
370    pub add_to_wallet: Option<bool>,
371
372    /// Fee rate in sat/vB.
373    #[serde(
374        default,
375        rename = "fee_rate",
376        skip_serializing_if = "Option::is_none",
377        serialize_with = "serialize_option_fee_rate"
378    )]
379    pub fee_rate: Option<FeeRate>,
380
381    /// Lock the selected UTXOs. Note: `send` expects `lock_unspents`, not `lockUnspents`.
382    #[serde(
383        default,
384        rename = "lock_unspents",
385        skip_serializing_if = "Option::is_none"
386    )]
387    pub lock_unspents: Option<bool>,
388}
389
390/// Query options for filtering unspent transaction outputs.
391///
392/// Used with `list_unspent` to apply additional filtering criteria
393/// beyond confirmation counts and addresses, allowing precise UTXO selection
394/// based on amount ranges and result limits.
395///
396/// # Note
397///
398/// All fields are optional and can be combined. UTXOs must satisfy all
399/// specified criteria to be included in the results.
400#[derive(Clone, Debug, PartialEq, Serialize)]
401#[serde(rename_all = "camelCase")]
402pub struct ListUnspentQueryOptions {
403    /// Minimum amount that UTXOs must have to be included.
404    ///
405    /// Only unspent outputs with a value greater than or equal to this amount
406    /// will be returned. Useful for filtering out dust or very small UTXOs.
407    #[serde(serialize_with = "serialize_option_bitcoin")]
408    pub minimum_amount: Option<Amount>,
409
410    /// Maximum amount that UTXOs can have to be included.
411    ///
412    /// Only unspent outputs with a value less than or equal to this amount
413    /// will be returned. Useful for finding smaller UTXOs or avoiding large ones.
414    #[serde(serialize_with = "serialize_option_bitcoin")]
415    pub maximum_amount: Option<Amount>,
416
417    /// Maximum number of UTXOs to return in the result set.
418    ///
419    /// Limits the total number of unspent outputs returned, regardless of how many
420    /// match the other criteria. Useful for pagination or limiting response size.
421    pub maximum_count: Option<u32>,
422}
423
424/// Options for psbtbumpfee RPC method.
425#[derive(Clone, Debug, Default, PartialEq, Serialize)]
426pub struct PsbtBumpFeeOptions {
427    /// Confirmation target in blocks.
428    #[serde(skip_serializing_if = "Option::is_none")]
429    pub conf_target: Option<u16>,
430
431    /// Fee rate in sat/vB.
432    #[serde(
433        skip_serializing_if = "Option::is_none",
434        serialize_with = "serialize_option_fee_rate"
435    )]
436    pub fee_rate: Option<FeeRate>,
437
438    /// Whether the new transaction should be BIP-125 replaceable.
439    #[serde(skip_serializing_if = "Option::is_none")]
440    pub replaceable: Option<bool>,
441
442    /// Fee estimate mode ("unset", "economical", "conservative").
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub estimate_mode: Option<String>,
445
446    /// New transaction outputs to replace the existing ones.
447    #[serde(skip_serializing_if = "Option::is_none")]
448    pub outputs: Option<Vec<CreateRawTransactionOutput>>,
449
450    /// Index of the change output to recycle from the original transaction.
451    #[serde(skip_serializing_if = "Option::is_none")]
452    pub original_change_index: Option<u32>,
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458    use proptest::prelude::*;
459    use serde_json::{json, Value};
460
461    #[derive(Serialize)]
462    struct FeeRateOption {
463        #[serde(serialize_with = "serialize_option_fee_rate")]
464        fee_rate: Option<FeeRate>,
465    }
466
467    const MAX_REASONABLE_FEE_RATE_SAT_PER_KWU: u64 = 1_000_000_000;
468
469    fn serialized_fee_rate_sat_per_kwu(value: &Value) -> u64 {
470        let fee_rate = value["fee_rate"].as_f64().unwrap();
471        (fee_rate * 250.0).round() as u64
472    }
473
474    proptest! {
475        #[test]
476        fn serialize_option_fee_rate_roundtrips_sat_per_kwu(
477            sat_per_kwu in 0u64..=MAX_REASONABLE_FEE_RATE_SAT_PER_KWU,
478        ) {
479            let value = serde_json::to_value(FeeRateOption {
480                fee_rate: Some(FeeRate::from_sat_per_kwu(sat_per_kwu)),
481            })
482            .unwrap();
483
484            prop_assert_eq!(serialized_fee_rate_sat_per_kwu(&value), sat_per_kwu);
485        }
486
487        #[test]
488        fn wallet_create_funded_psbt_options_roundtrips_fee_rate(
489            sat_per_kwu in 0u64..=MAX_REASONABLE_FEE_RATE_SAT_PER_KWU,
490        ) {
491            let value = serde_json::to_value(WalletCreateFundedPsbtOptions {
492                fee_rate: Some(FeeRate::from_sat_per_kwu(sat_per_kwu)),
493                ..Default::default()
494            })
495            .unwrap();
496
497            prop_assert_eq!(serialized_fee_rate_sat_per_kwu(&value), sat_per_kwu);
498        }
499
500        #[test]
501        fn psbt_bump_fee_options_roundtrips_fee_rate(
502            sat_per_kwu in 0u64..=MAX_REASONABLE_FEE_RATE_SAT_PER_KWU,
503        ) {
504            let value = serde_json::to_value(PsbtBumpFeeOptions {
505                fee_rate: Some(FeeRate::from_sat_per_kwu(sat_per_kwu)),
506                ..Default::default()
507            })
508            .unwrap();
509
510            prop_assert_eq!(serialized_fee_rate_sat_per_kwu(&value), sat_per_kwu);
511        }
512
513        #[test]
514        fn send_options_roundtrips_fee_rate(
515            sat_per_kwu in 0u64..=MAX_REASONABLE_FEE_RATE_SAT_PER_KWU,
516        ) {
517            let value = serde_json::to_value(SendOptions {
518                fee_rate: Some(FeeRate::from_sat_per_kwu(sat_per_kwu)),
519                ..Default::default()
520            })
521            .unwrap();
522
523            prop_assert_eq!(serialized_fee_rate_sat_per_kwu(&value), sat_per_kwu);
524        }
525    }
526
527    #[test]
528    fn broadcast_options_to_params_omits_empty_options() {
529        let params: Vec<_> = BroadcastOptions::default()
530            .to_params()
531            .into_iter()
532            .collect();
533
534        assert_eq!(params, Vec::<Value>::new());
535    }
536
537    #[test]
538    fn broadcast_options_to_params_adds_max_fee_rate_only() {
539        let params: Vec<_> = BroadcastOptions {
540            max_fee_rate: Some(FeeRate::from_sat_per_kwu(25_000_000)),
541            max_burn_amount: None,
542        }
543        .to_params()
544        .into_iter()
545        .collect();
546
547        assert_eq!(params, vec![json!(1.0)]);
548    }
549
550    #[test]
551    fn broadcast_options_to_params_adds_null_placeholder_for_max_burn_amount_only() {
552        let params: Vec<_> = BroadcastOptions {
553            max_fee_rate: None,
554            max_burn_amount: Some(Amount::from_sat(50_000)),
555        }
556        .to_params()
557        .into_iter()
558        .collect();
559
560        assert_eq!(params, vec![Value::Null, json!(0.0005)]);
561    }
562
563    #[test]
564    fn broadcast_options_to_params_adds_max_fee_rate_and_max_burn_amount() {
565        let params: Vec<_> = BroadcastOptions {
566            max_fee_rate: Some(FeeRate::from_sat_per_kwu(12_500_000)),
567            max_burn_amount: Some(Amount::from_sat(25_000)),
568        }
569        .to_params()
570        .into_iter()
571        .collect();
572
573        assert_eq!(params, vec![json!(0.5), json!(0.00025)]);
574    }
575
576    #[test]
577    fn serialize_option_fee_rate_preserves_sub_sat_per_vb_example() {
578        let value = serde_json::to_value(FeeRateOption {
579            fee_rate: Some(FeeRate::from_sat_per_kwu(125)),
580        })
581        .unwrap();
582
583        assert_eq!(value, json!({ "fee_rate": 0.5 }));
584    }
585
586    #[test]
587    fn wallet_create_funded_psbt_options_serializes_fee_rate_as_sat_per_vb_example() {
588        let value = serde_json::to_value(WalletCreateFundedPsbtOptions {
589            fee_rate: Some(FeeRate::from_sat_per_kwu(375)),
590            ..Default::default()
591        })
592        .unwrap();
593
594        assert_eq!(value, json!({ "fee_rate": 1.5 }));
595    }
596
597    #[test]
598    fn wallet_create_funded_psbt_options_skips_missing_fee_rate() {
599        let value = serde_json::to_value(WalletCreateFundedPsbtOptions {
600            lock_unspents: Some(true),
601            ..Default::default()
602        })
603        .unwrap();
604
605        assert_eq!(value, json!({ "lockUnspents": true }));
606    }
607
608    #[test]
609    fn psbt_bump_fee_options_serializes_fee_rate_as_sat_per_vb_example() {
610        let value = serde_json::to_value(PsbtBumpFeeOptions {
611            fee_rate: Some(FeeRate::from_sat_per_vb(20).unwrap()),
612            ..Default::default()
613        })
614        .unwrap();
615
616        assert_eq!(value, json!({ "fee_rate": 20.0 }));
617    }
618
619    #[test]
620    fn send_options_serializes_fields_example() {
621        let value = serde_json::to_value(SendOptions {
622            add_to_wallet: Some(false),
623            fee_rate: Some(FeeRate::from_sat_per_vb(2).unwrap()),
624            lock_unspents: Some(true),
625        })
626        .unwrap();
627
628        assert_eq!(
629            value,
630            json!({ "add_to_wallet": false, "fee_rate": 2.0, "lock_unspents": true })
631        );
632    }
633
634    #[test]
635    fn send_options_skips_missing_fields() {
636        let value = serde_json::to_value(SendOptions {
637            add_to_wallet: Some(false),
638            ..Default::default()
639        })
640        .unwrap();
641
642        assert_eq!(value, json!({ "add_to_wallet": false }));
643    }
644
645    #[test]
646    fn serialize_option_fee_rate_serializes_none_as_null_without_skip() {
647        let value = serde_json::to_value(FeeRateOption { fee_rate: None }).unwrap();
648
649        assert_eq!(value, json!({ "fee_rate": Value::Null }));
650    }
651}