Skip to main content

nautilus_blockchain/rpc/
utils.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16/// Determines if a JSON message is a subscription response from the blockchain RPC server.
17///
18/// Example response:
19/// ```json
20/// { "id": 1, "jsonrpc": "2.0", "result": "0x9cef478923ff08bf67fde6c64013158d"}
21/// ```
22#[must_use]
23pub fn is_subscription_confirmation_response(json: &serde_json::Value) -> bool {
24    json.get("id").is_some() && json.get("result").is_some_and(serde_json::Value::is_string)
25}
26
27/// Determines if a JSON message is an unsubscribe acknowledgement from the blockchain RPC server.
28///
29/// Example response:
30/// ```json
31/// { "id": 1, "jsonrpc": "2.0", "result": true}
32/// ```
33#[must_use]
34pub fn is_unsubscribe_confirmation_response(json: &serde_json::Value) -> bool {
35    json.get("id").is_some()
36        && json
37            .get("result")
38            .is_some_and(serde_json::Value::is_boolean)
39}
40
41/// Determines if a JSON message is a subscription event notification from the blockchain RPC server.
42///
43/// Example response:
44/// ```json
45/// {
46///   "jsonrpc": "2.0", "method": "eth_subscription", "params": {
47///     "subscription": "0x9cef478923ff08bf67fde6c64013158d",
48///     "result": ...
49///    }
50/// }
51/// ```
52#[must_use]
53pub fn is_subscription_event(json: &serde_json::Value) -> bool {
54    json.get("method")
55        .is_some_and(|value| value.as_str() == Some("eth_subscription"))
56}
57
58/// Extracts the subscription ID from a blockchain RPC subscription event notification.
59#[must_use]
60pub fn extract_rpc_subscription_id(json: &serde_json::Value) -> Option<&str> {
61    json.get("params")
62        .and_then(|params| params.get("subscription"))
63        .and_then(|subscription| subscription.as_str())
64}
65
66#[cfg(test)]
67mod tests {
68    use rstest::{fixture, rstest};
69
70    use super::*;
71
72    #[fixture]
73    fn subscription_confirmation() -> serde_json::Value {
74        serde_json::from_str(
75            r#"{"jsonrpc":"2.0","id":1,"result":"0x4edabdfee3c542878dcc064c12151869"}"#,
76        )
77        .unwrap()
78    }
79
80    #[fixture]
81    fn subscription_event() -> serde_json::Value {
82        serde_json::from_str(r#"{"jsonrpc":"2.0","method":"eth_subscription",
83        "params":{"subscription":"0x4edabdfee3c542878dcc064c12151869",
84        "result":{"baseFeePerGas":"0x989680","difficulty":"0x1",
85        "extraData":"0x5fcd3faec8b0c37571510e87ab402f0b7e6693ec607c880d38343e1884eb6823",
86        "gasLimit":"0x4000000000000","gasUsed":"0x47a6d4",
87        "hash":"0xb1e9f3e327e0686c9a299d9d6dbb6f2a77b60e1b948ddab9055bacbe02b7aee0",
88        "miner":"0xa4b000000000000000000073657175656e636572",
89        "mixHash":"0x00000000000231fe000000000154e0b000000000000000200000000000000000",
90        "nonce":"0x00000000001dc3fe","number":"0x13a7cad4",
91        "parentHash":"0x37356a864e9fd6eca0d4ebdd704739717f70e0e1f733b52317d377107c9b51ca",
92        "receiptsRoot":"0x0604749e4d9c71de05e0a1661fa9b3eafeac1da1e98125dcc48015d9d9c5d0da",
93        "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
94        "stateRoot":"0x41e066985a516865c2d29a2fd5672f8de54b0459098a0d134ceb092e1e578e28",
95        "timestamp":"0x680a58bf","totalDifficulty":"0x1254ed8c",
96        "transactionsRoot":"0x1e5209d3a83f6315c74d5e39d59ad85420b51709b695473ee4f321147c356564"}}}"#
97        ).unwrap()
98    }
99
100    #[rstest]
101    fn test_is_subscription_confirmation_response(subscription_confirmation: serde_json::Value) {
102        assert!(is_subscription_confirmation_response(
103            &subscription_confirmation
104        ));
105    }
106
107    #[rstest]
108    fn test_unsubscribe_confirmation_is_not_subscription_confirmation() {
109        let unsubscribe_confirmation =
110            serde_json::json!({"jsonrpc": "2.0", "id": 1, "result": true});
111
112        assert!(!is_subscription_confirmation_response(
113            &unsubscribe_confirmation
114        ));
115        assert!(is_unsubscribe_confirmation_response(
116            &unsubscribe_confirmation
117        ));
118    }
119
120    #[rstest]
121    fn test_is_subscription_event(subscription_event: serde_json::Value) {
122        assert!(is_subscription_event(&subscription_event));
123    }
124
125    #[rstest]
126    fn test_extract_subscription_id(subscription_event: serde_json::Value) {
127        let id = extract_rpc_subscription_id(&subscription_event);
128        assert_eq!(id, Some("0x4edabdfee3c542878dcc064c12151869"));
129    }
130}