nautilus_model/defi/
rpc.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
16use serde::{Deserialize, de::DeserializeOwned};
17
18/// A response structure received from a WebSocket JSON-RPC blockchain node subscription.
19#[derive(Debug, Deserialize)]
20pub struct RpcNodeWssResponse<T>
21where
22    T: DeserializeOwned,
23{
24    /// JSON-RPC version identifier.
25    pub jsonrpc: String,
26    /// Name of the RPC method that was called.
27    pub method: String,
28    /// Parameters containing subscription information and the deserialized result.
29    #[serde(bound(deserialize = ""))]
30    pub params: RpcNodeSubscriptionResponse<T>,
31}
32
33/// Container for subscription data within an RPC response, holding the subscription ID and the deserialized result.
34#[derive(Debug, Deserialize)]
35pub struct RpcNodeSubscriptionResponse<T>
36where
37    T: DeserializeOwned,
38{
39    /// ID of the subscription associated with the RPC response.
40    pub subscription: String,
41    /// Deserialized result.
42    #[serde(bound(deserialize = ""))]
43    pub result: T,
44}
45
46/// A response structure received from an HTTP JSON-RPC blockchain node request.
47#[derive(Debug, Deserialize)]
48pub struct RpcNodeHttpResponse<T>
49where
50    T: DeserializeOwned,
51{
52    /// JSON-RPC version identifier (optional for non-standard error responses like rate limits).
53    pub jsonrpc: Option<String>,
54    /// Request identifier returned by the server (optional for non-standard error responses).
55    pub id: Option<u64>,
56    /// Deserialized result.
57    #[serde(bound(deserialize = ""))]
58    pub result: Option<T>,
59    /// Error information if the request failed.
60    pub error: Option<RpcError>,
61    /// Error code (for non-standard rate limit responses).
62    pub code: Option<i32>,
63    /// Error message (for non-standard rate limit responses).
64    pub message: Option<String>,
65}
66
67/// JSON-RPC error structure.
68#[derive(Debug, Deserialize)]
69pub struct RpcError {
70    /// Error code.
71    pub code: i32,
72    /// Error message.
73    pub message: String,
74}
75
76/// Log entry in standard Ethereum JSON-RPC format.
77///
78/// This struct represents an event log returned by the `eth_getLogs` RPC method.
79/// Field names use camelCase to match the Ethereum JSON-RPC specification.
80///
81/// Note: `log_index`, `transaction_index`, `transaction_hash`, `block_hash`, and
82/// `block_number` can be null for pending logs, but are always present for confirmed logs.
83#[derive(Debug, Clone, Deserialize)]
84#[serde(rename_all = "camelCase")]
85pub struct RpcLog {
86    /// Whether the log was removed due to chain reorganization.
87    pub removed: bool,
88    /// Index position of the log in the block (hex string).
89    pub log_index: Option<String>,
90    /// Index position of the transaction in the block (hex string).
91    pub transaction_index: Option<String>,
92    /// Hash of the transaction that generated this log.
93    pub transaction_hash: Option<String>,
94    /// Hash of the block containing this log.
95    pub block_hash: Option<String>,
96    /// Block number containing this log (hex string).
97    pub block_number: Option<String>,
98    /// Address of the contract that emitted the event.
99    pub address: String,
100    /// Non-indexed event parameters (hex-encoded bytes).
101    pub data: String,
102    /// Indexed event parameters.
103    pub topics: Vec<String>,
104}
105
106#[cfg(test)]
107mod tests {
108    use rstest::rstest;
109
110    use super::*;
111
112    #[rstest]
113    fn test_rpc_log_deserialize_pool_created_block_185() {
114        let json = r#"{
115            "removed": false,
116            "logIndex": "0x0",
117            "transactionIndex": "0x0",
118            "transactionHash": "0x24058dde7caf5b8b70041de8b27731f20f927365f210247c3e720e947b9098e7",
119            "blockHash": "0xd371b6c7b04ec33d6470f067a82e87d7b294b952bea7a46d7b939b4c7addc275",
120            "blockNumber": "0xb9",
121            "address": "0x1f98431c8ad98523631ae4a59f267346ea31f984",
122            "data": "0x000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000b9fc136980d98c034a529aadbd5651c087365d5f",
123            "topics": [
124                "0x783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118",
125                "0x0000000000000000000000002e5353426c89f4ecd52d1036da822d47e73376c4",
126                "0x000000000000000000000000838930cfe7502dd36b0b1ebbef8001fbf94f3bfb",
127                "0x0000000000000000000000000000000000000000000000000000000000000bb8"
128            ]
129        }"#;
130
131        let log: RpcLog = serde_json::from_str(json).expect("Failed to deserialize RpcLog");
132
133        assert!(!log.removed);
134        assert_eq!(log.log_index, Some("0x0".to_string()));
135        assert_eq!(log.transaction_index, Some("0x0".to_string()));
136        assert_eq!(
137            log.transaction_hash,
138            Some("0x24058dde7caf5b8b70041de8b27731f20f927365f210247c3e720e947b9098e7".to_string())
139        );
140        assert_eq!(
141            log.block_hash,
142            Some("0xd371b6c7b04ec33d6470f067a82e87d7b294b952bea7a46d7b939b4c7addc275".to_string())
143        );
144        assert_eq!(log.block_number, Some("0xb9".to_string()));
145        assert_eq!(log.address, "0x1f98431c8ad98523631ae4a59f267346ea31f984");
146        assert_eq!(
147            log.data,
148            "0x000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000b9fc136980d98c034a529aadbd5651c087365d5f"
149        );
150        assert_eq!(log.topics.len(), 4);
151        assert_eq!(
152            log.topics[0],
153            "0x783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118"
154        );
155        assert_eq!(
156            log.topics[1],
157            "0x0000000000000000000000002e5353426c89f4ecd52d1036da822d47e73376c4"
158        );
159        assert_eq!(
160            log.topics[2],
161            "0x000000000000000000000000838930cfe7502dd36b0b1ebbef8001fbf94f3bfb"
162        );
163        assert_eq!(
164            log.topics[3],
165            "0x0000000000000000000000000000000000000000000000000000000000000bb8"
166        );
167    }
168
169    #[rstest]
170    fn test_rpc_log_deserialize_pool_created_block_540() {
171        let json = r#"{
172            "removed": false,
173            "logIndex": "0x0",
174            "transactionIndex": "0x0",
175            "transactionHash": "0x0810b3488eba9b0264d3544b4548b70d0c8667e05ac4a5d90686f4a9f70509df",
176            "blockHash": "0x59bb10cdfd586affc6aa4a0b12f0662ec04599a1a459ac5b33129bc2c8705ccd",
177            "blockNumber": "0x21c",
178            "address": "0x1f98431c8ad98523631ae4a59f267346ea31f984",
179            "data": "0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007d25de0bb3e4e4d5f7b399db5a0bca9f60dd66e4",
180            "topics": [
181                "0x783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118",
182                "0x0000000000000000000000008dd7c686b11c115ffaba245cbfc418b371087f68",
183                "0x000000000000000000000000be5381d826375492e55e05039a541eb2cb978e76",
184                "0x00000000000000000000000000000000000000000000000000000000000001f4"
185            ]
186        }"#;
187
188        let log: RpcLog = serde_json::from_str(json).expect("Failed to deserialize RpcLog");
189
190        assert!(!log.removed);
191        assert_eq!(log.log_index, Some("0x0".to_string()));
192        assert_eq!(log.transaction_index, Some("0x0".to_string()));
193        assert_eq!(
194            log.transaction_hash,
195            Some("0x0810b3488eba9b0264d3544b4548b70d0c8667e05ac4a5d90686f4a9f70509df".to_string())
196        );
197        assert_eq!(
198            log.block_hash,
199            Some("0x59bb10cdfd586affc6aa4a0b12f0662ec04599a1a459ac5b33129bc2c8705ccd".to_string())
200        );
201        assert_eq!(log.block_number, Some("0x21c".to_string()));
202        assert_eq!(log.address, "0x1f98431c8ad98523631ae4a59f267346ea31f984");
203        assert_eq!(
204            log.data,
205            "0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007d25de0bb3e4e4d5f7b399db5a0bca9f60dd66e4"
206        );
207        assert_eq!(log.topics.len(), 4);
208        assert_eq!(
209            log.topics[0],
210            "0x783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118"
211        );
212        assert_eq!(
213            log.topics[1],
214            "0x0000000000000000000000008dd7c686b11c115ffaba245cbfc418b371087f68"
215        );
216        assert_eq!(
217            log.topics[2],
218            "0x000000000000000000000000be5381d826375492e55e05039a541eb2cb978e76"
219        );
220        assert_eq!(
221            log.topics[3],
222            "0x00000000000000000000000000000000000000000000000000000000000001f4"
223        );
224    }
225}