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/// Query options for filtering unspent transaction outputs.
354///
355/// Used with `list_unspent` to apply additional filtering criteria
356/// beyond confirmation counts and addresses, allowing precise UTXO selection
357/// based on amount ranges and result limits.
358///
359/// # Note
360///
361/// All fields are optional and can be combined. UTXOs must satisfy all
362/// specified criteria to be included in the results.
363#[derive(Clone, Debug, PartialEq, Serialize)]
364#[serde(rename_all = "camelCase")]
365pub struct ListUnspentQueryOptions {
366    /// Minimum amount that UTXOs must have to be included.
367    ///
368    /// Only unspent outputs with a value greater than or equal to this amount
369    /// will be returned. Useful for filtering out dust or very small UTXOs.
370    #[serde(serialize_with = "serialize_option_bitcoin")]
371    pub minimum_amount: Option<Amount>,
372
373    /// Maximum amount that UTXOs can have to be included.
374    ///
375    /// Only unspent outputs with a value less than or equal to this amount
376    /// will be returned. Useful for finding smaller UTXOs or avoiding large ones.
377    #[serde(serialize_with = "serialize_option_bitcoin")]
378    pub maximum_amount: Option<Amount>,
379
380    /// Maximum number of UTXOs to return in the result set.
381    ///
382    /// Limits the total number of unspent outputs returned, regardless of how many
383    /// match the other criteria. Useful for pagination or limiting response size.
384    pub maximum_count: Option<u32>,
385}
386
387/// Options for psbtbumpfee RPC method.
388#[derive(Clone, Debug, Default, PartialEq, Serialize)]
389pub struct PsbtBumpFeeOptions {
390    /// Confirmation target in blocks.
391    #[serde(skip_serializing_if = "Option::is_none")]
392    pub conf_target: Option<u16>,
393
394    /// Fee rate in sat/vB.
395    #[serde(
396        skip_serializing_if = "Option::is_none",
397        serialize_with = "serialize_option_fee_rate"
398    )]
399    pub fee_rate: Option<FeeRate>,
400
401    /// Whether the new transaction should be BIP-125 replaceable.
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub replaceable: Option<bool>,
404
405    /// Fee estimate mode ("unset", "economical", "conservative").
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub estimate_mode: Option<String>,
408
409    /// New transaction outputs to replace the existing ones.
410    #[serde(skip_serializing_if = "Option::is_none")]
411    pub outputs: Option<Vec<CreateRawTransactionOutput>>,
412
413    /// Index of the change output to recycle from the original transaction.
414    #[serde(skip_serializing_if = "Option::is_none")]
415    pub original_change_index: Option<u32>,
416}
417
418#[cfg(test)]
419mod tests {
420    use super::*;
421    use proptest::prelude::*;
422    use serde_json::{json, Value};
423
424    #[derive(Serialize)]
425    struct FeeRateOption {
426        #[serde(serialize_with = "serialize_option_fee_rate")]
427        fee_rate: Option<FeeRate>,
428    }
429
430    const MAX_REASONABLE_FEE_RATE_SAT_PER_KWU: u64 = 1_000_000_000;
431
432    fn serialized_fee_rate_sat_per_kwu(value: &Value) -> u64 {
433        let fee_rate = value["fee_rate"].as_f64().unwrap();
434        (fee_rate * 250.0).round() as u64
435    }
436
437    proptest! {
438        #[test]
439        fn serialize_option_fee_rate_roundtrips_sat_per_kwu(
440            sat_per_kwu in 0u64..=MAX_REASONABLE_FEE_RATE_SAT_PER_KWU,
441        ) {
442            let value = serde_json::to_value(FeeRateOption {
443                fee_rate: Some(FeeRate::from_sat_per_kwu(sat_per_kwu)),
444            })
445            .unwrap();
446
447            prop_assert_eq!(serialized_fee_rate_sat_per_kwu(&value), sat_per_kwu);
448        }
449
450        #[test]
451        fn wallet_create_funded_psbt_options_roundtrips_fee_rate(
452            sat_per_kwu in 0u64..=MAX_REASONABLE_FEE_RATE_SAT_PER_KWU,
453        ) {
454            let value = serde_json::to_value(WalletCreateFundedPsbtOptions {
455                fee_rate: Some(FeeRate::from_sat_per_kwu(sat_per_kwu)),
456                ..Default::default()
457            })
458            .unwrap();
459
460            prop_assert_eq!(serialized_fee_rate_sat_per_kwu(&value), sat_per_kwu);
461        }
462
463        #[test]
464        fn psbt_bump_fee_options_roundtrips_fee_rate(
465            sat_per_kwu in 0u64..=MAX_REASONABLE_FEE_RATE_SAT_PER_KWU,
466        ) {
467            let value = serde_json::to_value(PsbtBumpFeeOptions {
468                fee_rate: Some(FeeRate::from_sat_per_kwu(sat_per_kwu)),
469                ..Default::default()
470            })
471            .unwrap();
472
473            prop_assert_eq!(serialized_fee_rate_sat_per_kwu(&value), sat_per_kwu);
474        }
475    }
476
477    #[test]
478    fn broadcast_options_to_params_omits_empty_options() {
479        let params: Vec<_> = BroadcastOptions::default()
480            .to_params()
481            .into_iter()
482            .collect();
483
484        assert_eq!(params, Vec::<Value>::new());
485    }
486
487    #[test]
488    fn broadcast_options_to_params_adds_max_fee_rate_only() {
489        let params: Vec<_> = BroadcastOptions {
490            max_fee_rate: Some(FeeRate::from_sat_per_kwu(25_000_000)),
491            max_burn_amount: None,
492        }
493        .to_params()
494        .into_iter()
495        .collect();
496
497        assert_eq!(params, vec![json!(1.0)]);
498    }
499
500    #[test]
501    fn broadcast_options_to_params_adds_null_placeholder_for_max_burn_amount_only() {
502        let params: Vec<_> = BroadcastOptions {
503            max_fee_rate: None,
504            max_burn_amount: Some(Amount::from_sat(50_000)),
505        }
506        .to_params()
507        .into_iter()
508        .collect();
509
510        assert_eq!(params, vec![Value::Null, json!(0.0005)]);
511    }
512
513    #[test]
514    fn broadcast_options_to_params_adds_max_fee_rate_and_max_burn_amount() {
515        let params: Vec<_> = BroadcastOptions {
516            max_fee_rate: Some(FeeRate::from_sat_per_kwu(12_500_000)),
517            max_burn_amount: Some(Amount::from_sat(25_000)),
518        }
519        .to_params()
520        .into_iter()
521        .collect();
522
523        assert_eq!(params, vec![json!(0.5), json!(0.00025)]);
524    }
525
526    #[test]
527    fn serialize_option_fee_rate_preserves_sub_sat_per_vb_example() {
528        let value = serde_json::to_value(FeeRateOption {
529            fee_rate: Some(FeeRate::from_sat_per_kwu(125)),
530        })
531        .unwrap();
532
533        assert_eq!(value, json!({ "fee_rate": 0.5 }));
534    }
535
536    #[test]
537    fn wallet_create_funded_psbt_options_serializes_fee_rate_as_sat_per_vb_example() {
538        let value = serde_json::to_value(WalletCreateFundedPsbtOptions {
539            fee_rate: Some(FeeRate::from_sat_per_kwu(375)),
540            ..Default::default()
541        })
542        .unwrap();
543
544        assert_eq!(value, json!({ "fee_rate": 1.5 }));
545    }
546
547    #[test]
548    fn wallet_create_funded_psbt_options_skips_missing_fee_rate() {
549        let value = serde_json::to_value(WalletCreateFundedPsbtOptions {
550            lock_unspents: Some(true),
551            ..Default::default()
552        })
553        .unwrap();
554
555        assert_eq!(value, json!({ "lockUnspents": true }));
556    }
557
558    #[test]
559    fn psbt_bump_fee_options_serializes_fee_rate_as_sat_per_vb_example() {
560        let value = serde_json::to_value(PsbtBumpFeeOptions {
561            fee_rate: Some(FeeRate::from_sat_per_vb(20).unwrap()),
562            ..Default::default()
563        })
564        .unwrap();
565
566        assert_eq!(value, json!({ "fee_rate": 20.0 }));
567    }
568
569    #[test]
570    fn serialize_option_fee_rate_serializes_none_as_null_without_skip() {
571        let value = serde_json::to_value(FeeRateOption { fee_rate: None }).unwrap();
572
573        assert_eq!(value, json!({ "fee_rate": Value::Null }));
574    }
575}