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}