1use super::{
2 method::Method,
3 params::{Params, TxGetArgs, VersionKind},
4 types::ScriptHash,
5};
6use miniscript::bitcoin::{Script, Txid};
7use serde::Serialize;
8
9#[derive(Debug, Serialize, Clone)]
10pub struct Request {
11 jsonrpc: String,
12 pub id: usize,
13 pub method: Method,
14
15 #[serde(default)]
16 params: Params,
17}
18
19impl Request {
20 fn new(method: Method, params: Params) -> Self {
21 Request {
22 jsonrpc: "2.0".into(),
23 id: 0,
24 method,
25 params,
26 }
27 }
28
29 fn new_with_id(id: usize, method: Method, params: Params) -> Self {
30 Request {
31 jsonrpc: "2.0".into(),
32 id,
33 method,
34 params,
35 }
36 }
37
38 pub fn id(mut self, id: usize) -> Self {
39 self.id = id;
40 self
41 }
42
43 pub fn ping() -> Self {
44 Self::new(Method::Ping, Params::None)
45 }
46
47 pub fn version(client_name: String, version: String) -> Self {
48 Self::new(
49 Method::Version,
50 Params::Version((client_name, VersionKind::Single(version))),
51 )
52 }
53
54 pub fn version_range(client_name: String, min: String, max: String) -> Self {
55 Self::new(
56 Method::Version,
57 Params::Version((client_name, VersionKind::MinMax(min, max))),
58 )
59 }
60
61 pub fn banner() -> Self {
62 Self::new(Method::Banner, Params::None)
63 }
64
65 pub fn donation() -> Self {
66 Self::new(Method::Donation, Params::None)
67 }
68
69 pub fn features() -> Self {
70 Self::new(Method::Features, Params::None)
71 }
72
73 pub fn subscribe_peers() -> Self {
74 Self::new(Method::ListPeers, Params::None)
75 }
76
77 pub fn header(height: usize) -> Self {
78 Self::new(Method::BlockHeader, Params::BlockHeader((height,)))
79 }
80
81 pub fn headers(start: usize, count: usize) -> Self {
82 Self::new(Method::BlockHeaders, Params::BlockHeaders((start, count)))
83 }
84
85 pub fn estimate_fee(block_target: u16) -> Self {
86 Self::new(Method::EstimateFee, Params::EstimateFee((block_target,)))
87 }
88
89 pub fn subscribe_headers() -> Self {
90 Self::new(Method::HeadersSubscribe, Params::None)
91 }
92
93 pub fn relay_fee() -> Self {
94 Self::new(Method::RelayFee, Params::None)
95 }
96
97 pub fn sh_get_balance(script: &Script) -> Self {
98 let sh = ScriptHash::new(script);
99 Self::new(
100 Method::ScriptHashGetBalance,
101 Params::ScriptHashGetBalance((sh,)),
102 )
103 }
104
105 pub fn sh_get_history(script: &Script) -> Self {
106 let sh = ScriptHash::new(script);
107 Self::new(
108 Method::ScriptHashGetHistory,
109 Params::ScriptHashGetHistory((sh,)),
110 )
111 }
112
113 pub fn sh_list_unspent(script: &Script) -> Self {
123 let sh = ScriptHash::new(script);
124 Self::new(
125 Method::ScriptHashListUnspent,
126 Params::ScriptHashListUnspent((sh,)),
127 )
128 }
129
130 pub fn subscribe_sh(script: &Script) -> Self {
131 let sh = ScriptHash::new(script);
132 Self::new(
133 Method::ScriptHashSubscribe,
134 Params::ScriptHashSubscribe((sh,)),
135 )
136 }
137
138 pub fn unsubscribe_sh(script: &Script) -> Self {
139 let sh = ScriptHash::new(script);
140 Self::new(
141 Method::ScriptHashUnsubscribe,
142 Params::ScriptHashUnsubscribe((sh,)),
143 )
144 }
145
146 pub fn tx_broadcast(tx: String) -> Self {
147 Self::new(
148 Method::TransactionBroadcast,
149 Params::TransactionBroadcast((tx,)),
150 )
151 }
152
153 pub fn tx_get(txid: Txid) -> Self {
154 Self::new(
155 Method::TransactionGet,
156 Params::TransactionGet(TxGetArgs::Txid((txid,))),
157 )
158 }
159
160 pub fn tx_get_verbose(txid: Txid) -> Self {
161 Self::new(
162 Method::TransactionGet,
163 Params::TransactionGet(TxGetArgs::TxidVerbose(txid, true)),
164 )
165 }
166
167 pub fn tx_get_merkle(txid: Txid, height: usize) -> Self {
168 Self::new(
169 Method::TransactionGetMerkle,
170 Params::TransactionGetMerkle((txid, height)),
171 )
172 }
173
174 pub fn tx_from_pos(height: usize, tx_pos: usize, merlkle: bool) -> Self {
175 Self::new(
176 Method::TransactionFromPosition,
177 Params::TransactionFromPosition((height, tx_pos, merlkle)),
178 )
179 }
180
181 pub fn get_fee_histogram() -> Self {
182 Self::new(Method::FeeHistogram, Params::None)
183 }
184}
185
186impl From<Request> for String {
187 fn from(value: Request) -> Self {
188 serde_json::to_string(&value).unwrap()
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use std::str::FromStr;
195
196 use miniscript::bitcoin::OutPoint;
197
198 use super::*;
199 #[test]
200 fn serialize() {
201 assert_eq!(
202 &serde_json::to_string(&Request::ping()).unwrap(),
203 r#"{"jsonrpc":"2.0","id":0,"method":"server.ping","params":[]}"#
204 );
205
206 assert_eq!(
207 &serde_json::to_string(&Request::banner()).unwrap(),
208 r#"{"jsonrpc":"2.0","id":0,"method":"server.banner","params":[]}"#
209 );
210
211 assert_eq!(
212 &serde_json::to_string(&Request::donation()).unwrap(),
213 r#"{"jsonrpc":"2.0","id":0,"method":"server.donation_address","params":[]}"#
214 );
215
216 assert_eq!(
217 &serde_json::to_string(&Request::features()).unwrap(),
218 r#"{"jsonrpc":"2.0","id":0,"method":"server.features","params":[]}"#
219 );
220
221 assert_eq!(
222 &serde_json::to_string(&Request::subscribe_peers()).unwrap(),
223 r#"{"jsonrpc":"2.0","id":0,"method":"server.peers.subscribe","params":[]}"#
224 );
225
226 assert_eq!(
227 &serde_json::to_string(&Request::version("smart".into(), "1.4".into())).unwrap(),
228 r#"{"jsonrpc":"2.0","id":0,"method":"server.version","params":["smart","1.4"]}"#
229 );
230
231 assert_eq!(
232 &serde_json::to_string(&Request::version_range(
233 "smart".into(),
234 "1.1".into(),
235 "1.4".into()
236 ))
237 .unwrap(),
238 r#"{"jsonrpc":"2.0","id":0,"method":"server.version","params":["smart",["1.1","1.4"]]}"#
239 );
240
241 assert_eq!(
242 &serde_json::to_string(&Request::header(12345)).unwrap(),
243 r#"{"jsonrpc":"2.0","id":0,"method":"blockchain.block.header","params":[12345]}"#
244 );
245
246 assert_eq!(
247 &serde_json::to_string(&Request::headers(12345, 5)).unwrap(),
248 r#"{"jsonrpc":"2.0","id":0,"method":"blockchain.block.headers","params":[12345,5]}"#
249 );
250
251 assert_eq!(
252 &serde_json::to_string(&Request::estimate_fee(5)).unwrap(),
253 r#"{"jsonrpc":"2.0","id":0,"method":"blockchain.estimatefee","params":[5]}"#
254 );
255
256 assert_eq!(
257 &serde_json::to_string(&Request::subscribe_headers()).unwrap(),
258 r#"{"jsonrpc":"2.0","id":0,"method":"blockchain.headers.subscribe","params":[]}"#
259 );
260 assert_eq!(
261 &serde_json::to_string(&Request::relay_fee()).unwrap(),
262 r#"{"jsonrpc":"2.0","id":0,"method":"blockchain.relayfee","params":[]}"#
263 );
264 let script = Script::from_bytes(&[0x00]);
265 assert_eq!(
266 &serde_json::to_string(&Request::sh_get_history(script)).unwrap(),
267 r#"{"jsonrpc":"2.0","id":0,"method":"blockchain.scripthash.get_history","params":["1da0af1706a31185763837b33f1d90782c0a78bbe644a59c987ab3ff9c0b346e"]}"#
268 );
269 assert_eq!(
275 &serde_json::to_string(&Request::sh_list_unspent(script)).unwrap(),
276 r#"{"jsonrpc":"2.0","id":0,"method":"blockchain.scripthash.listunspent","params":["1da0af1706a31185763837b33f1d90782c0a78bbe644a59c987ab3ff9c0b346e"]}"#
277 );
278 assert_eq!(
279 &serde_json::to_string(&Request::sh_get_balance(script)).unwrap(),
280 r#"{"jsonrpc":"2.0","id":0,"method":"blockchain.scripthash.get_balance","params":["1da0af1706a31185763837b33f1d90782c0a78bbe644a59c987ab3ff9c0b346e"]}"#
281 );
282 assert_eq!(
283 &serde_json::to_string(&Request::subscribe_sh(script)).unwrap(),
284 r#"{"jsonrpc":"2.0","id":0,"method":"blockchain.scripthash.subscribe","params":["1da0af1706a31185763837b33f1d90782c0a78bbe644a59c987ab3ff9c0b346e"]}"#
285 );
286 assert_eq!(
287 &serde_json::to_string(&Request::unsubscribe_sh(script)).unwrap(),
288 r#"{"jsonrpc":"2.0","id":0,"method":"blockchain.scripthash.unsubscribe","params":["1da0af1706a31185763837b33f1d90782c0a78bbe644a59c987ab3ff9c0b346e"]}"#
289 );
290 let outpoint = OutPoint::from_str(
291 "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:42",
292 )
293 .unwrap();
294 let txid = outpoint.txid;
295 assert_eq!(
296 &serde_json::to_string(&Request::tx_get(txid)).unwrap(),
297 r#"{"jsonrpc":"2.0","id":0,"method":"blockchain.transaction.get","params":["5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456"]}"#
298 );
299 assert_eq!(
300 &serde_json::to_string(&Request::tx_get_verbose(txid)).unwrap(),
301 r#"{"jsonrpc":"2.0","id":0,"method":"blockchain.transaction.get","params":["5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456",true]}"#
302 );
303 assert_eq!(
304 &serde_json::to_string(&Request::tx_get_merkle(txid, 5)).unwrap(),
305 r#"{"jsonrpc":"2.0","id":0,"method":"blockchain.transaction.get_merkle","params":["5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456",5]}"#
306 );
307 assert_eq!(
308 &serde_json::to_string(&Request::get_fee_histogram()).unwrap(),
309 r#"{"jsonrpc":"2.0","id":0,"method":"mempool.get_fee_histogram","params":[]}"#
310 );
311 }
312
313 #[test]
314 fn batch_request() {
315 let mut batch = Vec::new();
316 let request = Request::ping();
317 for i in 1..12usize {
318 let mut r = request.clone();
319 r.id = i;
320 batch.push(r);
321 }
322
323 let str_req = serde_json::to_string(&batch).unwrap();
324 let expected = r#"[{"jsonrpc":"2.0","id":1,"method":"server.ping","params":[]},{"jsonrpc":"2.0","id":2,"method":"server.ping","params":[]},{"jsonrpc":"2.0","id":3,"method":"server.ping","params":[]},{"jsonrpc":"2.0","id":4,"method":"server.ping","params":[]},{"jsonrpc":"2.0","id":5,"method":"server.ping","params":[]},{"jsonrpc":"2.0","id":6,"method":"server.ping","params":[]},{"jsonrpc":"2.0","id":7,"method":"server.ping","params":[]},{"jsonrpc":"2.0","id":8,"method":"server.ping","params":[]},{"jsonrpc":"2.0","id":9,"method":"server.ping","params":[]},{"jsonrpc":"2.0","id":10,"method":"server.ping","params":[]},{"jsonrpc":"2.0","id":11,"method":"server.ping","params":[]}]"#;
325
326 assert_eq!(&str_req, expected);
327 }
328}