1use bitcoin::{Amount, FeeRate, Txid};
4use serde::{
5 de::{self, Visitor},
6 Deserialize, Deserializer, Serialize, Serializer,
7};
8use serde_json::Value;
9
10#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
16pub struct CreateRawTransactionArguments {
17 pub inputs: Vec<CreateRawTransactionInput>,
18 pub outputs: Vec<CreateRawTransactionOutput>,
19}
20
21#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
23pub struct CreateRawTransactionInput {
24 pub txid: String,
25 pub vout: u32,
26}
27
28#[derive(Clone, Debug, PartialEq, Deserialize)]
34#[serde(untagged)]
35pub enum CreateRawTransactionOutput {
36 AddressAmount {
38 address: String,
40 amount: f64,
42 },
43 Data {
45 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#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
94pub struct PreviousTransactionOutput {
95 #[serde(deserialize_with = "deserialize_txid")]
97 pub txid: Txid,
98 pub vout: u32,
100 #[serde(rename = "scriptPubKey")]
102 pub script_pubkey: String,
103 #[serde(rename = "redeemScript")]
105 pub redeem_script: Option<String>,
106 #[serde(rename = "witnessScript")]
108 pub witness_script: Option<String>,
109 pub amount: Option<f64>,
111}
112
113#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
115pub struct ImportDescriptorInput {
116 pub desc: String,
118 pub active: Option<bool>,
121 pub timestamp: String,
124}
125
126#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
132pub struct CreateWalletArguments {
133 pub name: String,
135 pub load_on_startup: Option<bool>,
137}
138
139#[derive(Clone, Debug, Default, PartialEq, Eq)]
141pub struct BroadcastOptions {
142 pub max_fee_rate: Option<FeeRate>,
146
147 pub max_burn_amount: Option<Amount>,
151}
152
153impl BroadcastOptions {
154 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
179pub type SendRawTransactionOptions = BroadcastOptions;
181
182fn 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
193fn 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
212fn 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#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
249#[serde(rename_all = "UPPERCASE")]
250pub enum SighashType {
251 Default,
253
254 All,
259
260 None,
265
266 Single,
271
272 #[serde(rename = "ALL|ANYONECANPAY")]
277 AllPlusAnyoneCanPay,
278
279 #[serde(rename = "NONE|ANYONECANPAY")]
284 NonePlusAnyoneCanPay,
285
286 #[serde(rename = "SINGLE|ANYONECANPAY")]
291 SinglePlusAnyoneCanPay,
292}
293
294#[derive(Clone, Debug, PartialEq, Serialize, Default)]
305pub struct WalletCreateFundedPsbtOptions {
306 #[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 #[serde(
323 default,
324 rename = "lockUnspents",
325 skip_serializing_if = "Option::is_none"
326 )]
327 pub lock_unspents: Option<bool>,
328
329 #[serde(
335 default,
336 rename = "conf_target",
337 skip_serializing_if = "Option::is_none"
338 )]
339 pub conf_target: Option<u16>,
340
341 #[serde(
346 default,
347 rename = "replaceable",
348 skip_serializing_if = "Option::is_none"
349 )]
350 pub replaceable: Option<bool>,
351}
352
353#[derive(Clone, Debug, PartialEq, Serialize, Default)]
362pub struct SendOptions {
363 #[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 #[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 #[serde(
383 default,
384 rename = "lock_unspents",
385 skip_serializing_if = "Option::is_none"
386 )]
387 pub lock_unspents: Option<bool>,
388}
389
390#[derive(Clone, Debug, PartialEq, Serialize)]
401#[serde(rename_all = "camelCase")]
402pub struct ListUnspentQueryOptions {
403 #[serde(serialize_with = "serialize_option_bitcoin")]
408 pub minimum_amount: Option<Amount>,
409
410 #[serde(serialize_with = "serialize_option_bitcoin")]
415 pub maximum_amount: Option<Amount>,
416
417 pub maximum_count: Option<u32>,
422}
423
424#[derive(Clone, Debug, Default, PartialEq, Serialize)]
426pub struct PsbtBumpFeeOptions {
427 #[serde(skip_serializing_if = "Option::is_none")]
429 pub conf_target: Option<u16>,
430
431 #[serde(
433 skip_serializing_if = "Option::is_none",
434 serialize_with = "serialize_option_fee_rate"
435 )]
436 pub fee_rate: Option<FeeRate>,
437
438 #[serde(skip_serializing_if = "Option::is_none")]
440 pub replaceable: Option<bool>,
441
442 #[serde(skip_serializing_if = "Option::is_none")]
444 pub estimate_mode: Option<String>,
445
446 #[serde(skip_serializing_if = "Option::is_none")]
448 pub outputs: Option<Vec<CreateRawTransactionOutput>>,
449
450 #[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}