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)]
364#[serde(rename_all = "camelCase")]
365pub struct ListUnspentQueryOptions {
366 #[serde(serialize_with = "serialize_option_bitcoin")]
371 pub minimum_amount: Option<Amount>,
372
373 #[serde(serialize_with = "serialize_option_bitcoin")]
378 pub maximum_amount: Option<Amount>,
379
380 pub maximum_count: Option<u32>,
385}
386
387#[derive(Clone, Debug, Default, PartialEq, Serialize)]
389pub struct PsbtBumpFeeOptions {
390 #[serde(skip_serializing_if = "Option::is_none")]
392 pub conf_target: Option<u16>,
393
394 #[serde(
396 skip_serializing_if = "Option::is_none",
397 serialize_with = "serialize_option_fee_rate"
398 )]
399 pub fee_rate: Option<FeeRate>,
400
401 #[serde(skip_serializing_if = "Option::is_none")]
403 pub replaceable: Option<bool>,
404
405 #[serde(skip_serializing_if = "Option::is_none")]
407 pub estimate_mode: Option<String>,
408
409 #[serde(skip_serializing_if = "Option::is_none")]
411 pub outputs: Option<Vec<CreateRawTransactionOutput>>,
412
413 #[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}