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