1use anyhow::Context;
2use bitcoin::{consensus::Decodable, io::Cursor};
3use lexe_serde::hexstr_or_bytes;
4use serde::{Deserialize, Serialize};
5
6#[cfg(any(test, feature = "test-utils"))]
7use crate::test_utils::arbitrary;
8use crate::{
9 ln::{amount::Amount, hashes::Txid, network::Network},
10 time::TimestampMs,
11};
12
13#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
15pub struct Status {
16 pub timestamp: TimestampMs,
18 }
21
22#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
24pub struct SignMsgRequest {
25 pub msg: String,
27}
28
29#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
30pub struct SignMsgResponse {
31 pub sig: String,
33}
34
35#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
37pub struct BroadcastedTx {
38 pub txid: Txid,
40 #[serde(with = "hexstr_or_bytes")]
42 pub tx: Vec<u8>,
43
44 pub created_at: TimestampMs,
46}
47
48#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
49pub struct BroadcastedTxInfo {
50 pub txid: Txid,
52 #[serde(with = "hexstr_or_bytes")]
54 pub tx: Vec<u8>,
55 pub created_at: TimestampMs,
57 pub total_outputs: Amount,
59 pub output_destinations: Vec<String>,
61 pub inputs: Vec<String>,
63 pub confirmation_block_height: Option<u32>,
65}
66
67impl BroadcastedTxInfo {
68 pub fn from_broadcasted_tx(
69 broadcasted_tx: BroadcastedTx,
70 network: Network,
71 confirmation_block_height: Option<u32>,
72 ) -> anyhow::Result<Self> {
73 let mut reader = Cursor::new(&broadcasted_tx.tx);
74
75 let tx = bitcoin::Transaction::consensus_decode(&mut reader)
76 .context("Could not parse consensus-encoded transaction")?;
77
78 let total_outputs =
79 tx.output.iter().map(|o| o.value.to_sat()).sum::<u64>();
80 let total_outputs = Amount::try_from_sats_u64(total_outputs)
81 .context("Output amount conversion error")?;
82
83 let output_destinations = tx
84 .output
85 .iter()
86 .map(|o| {
87 bitcoin::Address::from_script(
88 &o.script_pubkey,
89 network.to_bitcoin(),
90 )
91 .map_or(o.script_pubkey.to_string(), |addr| addr.to_string())
92 })
93 .collect::<Vec<_>>();
94
95 let inputs = tx
96 .input
97 .iter()
98 .map(|i| i.previous_output.to_string())
99 .collect::<Vec<_>>();
100
101 let tx_info = BroadcastedTxInfo {
102 total_outputs,
103 created_at: broadcasted_tx.created_at,
104 output_destinations,
105 inputs,
106 txid: broadcasted_tx.txid,
107 tx: broadcasted_tx.tx,
108 confirmation_block_height,
109 };
110 Ok(tx_info)
111 }
112}
113
114#[cfg(any(test, feature = "test-utils"))]
115mod arbitrary_impl {
116 use proptest::{
117 arbitrary::{Arbitrary, any},
118 collection::vec,
119 option,
120 strategy::{BoxedStrategy, Strategy},
121 };
122
123 use super::*;
124
125 impl Arbitrary for BroadcastedTx {
126 type Parameters = ();
127 type Strategy = BoxedStrategy<Self>;
128
129 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
130 (
131 arbitrary::any_raw_tx_bytes(),
132 any::<Txid>(),
133 any::<TimestampMs>(),
134 )
135 .prop_map(|(tx, txid, created_at)| Self {
136 tx,
137 txid,
138 created_at,
139 })
140 .boxed()
141 }
142 }
143
144 impl Arbitrary for BroadcastedTxInfo {
145 type Parameters = ();
146 type Strategy = BoxedStrategy<Self>;
147
148 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
149 let any_vec_input_str = vec(
150 (arbitrary::any_outpoint())
151 .prop_map(|outpoint| outpoint.to_string()),
152 0..=8,
153 );
154 let any_vec_output_destination_str = vec(
155 (arbitrary::any_mainnet_addr())
156 .prop_map(|address| address.to_string()),
157 0..=8,
158 );
159 let any_confirmation_block_height_optional =
160 option::of(any::<u32>());
161
162 (
163 any::<BroadcastedTx>(),
164 any::<Amount>(),
165 any_vec_output_destination_str,
166 any_vec_input_str,
167 any_confirmation_block_height_optional,
168 )
169 .prop_map(
170 |(
171 tx,
172 total_outputs,
173 output_destinations,
174 inputs,
175 confirmation_block_height,
176 )| Self {
177 txid: tx.txid,
178 tx: tx.tx,
179 created_at: tx.created_at,
180 total_outputs,
181 output_destinations,
182 inputs,
183 confirmation_block_height,
184 },
185 )
186 .boxed()
187 }
188 }
189}
190
191#[cfg(test)]
192mod test {
193 use super::*;
194 use crate::test_utils::roundtrip;
195
196 #[test]
197 fn broadcasted_tx_roundtrip_proptest() {
198 roundtrip::json_value_roundtrip_proptest::<BroadcastedTx>();
199 }
200
201 #[test]
202 fn broadcasted_tx_info_roundtrip_proptest() {
203 roundtrip::json_value_roundtrip_proptest::<BroadcastedTxInfo>();
204 }
205}