bwk_electrum/electrum/
request.rs

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    // NOTE: not supported by electrs
114    // pub fn sh_get_mempool(script: &Script) -> Self {
115    //     let sh = ScriptHash::new(script);
116    //     Self::new(
117    //         Method::ScriptHashGetMempool,
118    //         Params::ScriptHashGetMempool((sh,)),
119    //     )
120    // }
121
122    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        // NOTE: not supported by electrs
270        // assert_eq!(
271        //     &serde_json::to_string(&Request::sh_get_mempool(script)).unwrap(),
272        //     r#"{"jsonrpc":"2.0","id":0,"method":"blockchain.scripthash.get_mempool","params":["1da0af1706a31185763837b33f1d90782c0a78bbe644a59c987ab3ff9c0b346e"]}"#
273        // );
274        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}