Skip to main content

nautilus_blockchain/rpc/
types.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 alloy::primitives::{Address, B256, Bytes, U256};
17use nautilus_model::defi::{Block, DexType, rpc::RpcLog};
18use serde::Deserialize;
19use serde_json::Value;
20
21use crate::events::{
22    burn::BurnEvent, collect::CollectEvent, fee_protocol_collect::FeeProtocolCollectEvent,
23    fee_protocol_update::FeeProtocolUpdateEvent, flash::FlashEvent, mint::MintEvent,
24    swap::SwapEvent,
25};
26
27/// Represents normalized blockchain messages.
28#[derive(Debug, Clone)]
29pub enum BlockchainMessage {
30    Block(Block),
31    SwapEvent(SwapEvent),
32    MintEvent(MintEvent),
33    BurnEvent(BurnEvent),
34    CollectEvent(CollectEvent),
35    FlashEvent(FlashEvent),
36    FeeProtocolUpdateEvent(FeeProtocolUpdateEvent),
37    FeeProtocolCollectEvent(FeeProtocolCollectEvent),
38}
39
40/// Represents the types of events that can be subscribed to via the blockchain RPC interface.
41///
42/// This enum defines the various event types that the application can subscribe to using
43/// the WebSocket-based RPC subscription.
44#[derive(Debug, Clone, Copy, Hash, PartialOrd, Ord, PartialEq, Eq)]
45pub enum RpcEventType {
46    NewBlock,
47    PoolSwap(DexType),
48    PoolMint(DexType),
49    PoolBurn(DexType),
50    PoolCollect(DexType),
51    PoolFlash(DexType),
52    PoolFeeProtocolUpdate(DexType),
53    PoolFeeProtocolCollect(DexType),
54}
55
56/// Result of an explicit-height `eth_call` before contract-specific output decoding.
57#[cfg(feature = "hypersync")]
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub(crate) enum RpcCallResult {
60    Success(Bytes),
61    Reverted,
62}
63
64/// Represents the minimal block view required for execution fee derivation.
65#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
66#[serde(rename_all = "camelCase")]
67pub struct RpcBlock {
68    /// The block number.
69    #[serde(deserialize_with = "deserialize_hex_u64")]
70    pub number: u64,
71    /// The canonical hash of the block.
72    pub hash: B256,
73    /// The canonical hash of the parent block.
74    pub parent_hash: B256,
75    /// The block timestamp in seconds since the Unix epoch.
76    #[serde(deserialize_with = "deserialize_hex_u64")]
77    pub timestamp: u64,
78    /// The block base fee per gas in wei (`None` on pre-London chains).
79    #[serde(default, deserialize_with = "deserialize_hex_u128_opt")]
80    pub base_fee_per_gas: Option<u128>,
81    /// Full transactions when requested, otherwise empty.
82    #[serde(skip)]
83    pub transactions: Vec<RpcTransaction>,
84}
85
86#[derive(Debug, Deserialize)]
87pub(crate) struct RpcBlockResponse {
88    #[serde(flatten)]
89    pub block: RpcBlock,
90    #[serde(default)]
91    pub transactions: Vec<Value>,
92}
93
94/// Represents the transaction identity required for signer-nonce reconciliation.
95#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
96#[serde(rename_all = "camelCase")]
97pub struct RpcTransaction {
98    /// The transaction hash.
99    pub hash: B256,
100    /// The signer address.
101    pub from: Address,
102    /// The signer nonce.
103    #[serde(deserialize_with = "deserialize_hex_u64")]
104    pub nonce: u64,
105    /// The chain ID authenticated by the transaction signature.
106    #[serde(default, deserialize_with = "deserialize_hex_u64_opt")]
107    pub chain_id: Option<u64>,
108    /// The EIP-2718 transaction type.
109    #[serde(default, rename = "type", deserialize_with = "deserialize_hex_u8_opt")]
110    pub transaction_type: Option<u8>,
111    /// The destination address, or `None` for contract creation.
112    pub to: Option<Address>,
113    /// The transaction calldata.
114    pub input: Bytes,
115    /// The native value in wei.
116    pub value: U256,
117    /// The transaction gas limit.
118    #[serde(default, deserialize_with = "deserialize_hex_u64_opt")]
119    pub gas: Option<u64>,
120    /// The EIP-1559 maximum fee per gas in wei.
121    pub max_fee_per_gas: Option<U256>,
122    /// The EIP-1559 maximum priority fee per gas in wei.
123    pub max_priority_fee_per_gas: Option<U256>,
124}
125
126/// Represents the minimal transaction receipt view required for inclusion observation.
127#[derive(Debug, Clone, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct RpcTransactionReceipt {
130    /// The transaction hash.
131    pub transaction_hash: B256,
132    /// The canonical block hash reported with the receipt.
133    pub block_hash: B256,
134    /// The block number that included the transaction.
135    #[serde(deserialize_with = "deserialize_hex_u64")]
136    pub block_number: u64,
137    /// The gas used by the transaction.
138    #[serde(deserialize_with = "deserialize_hex_u64")]
139    pub gas_used: u64,
140    /// The effective gas price charged in wei.
141    pub effective_gas_price: U256,
142    /// The transaction index within the block.
143    #[serde(deserialize_with = "deserialize_hex_u64")]
144    pub transaction_index: u64,
145    /// Whether the transaction executed successfully (status `0x1`).
146    #[serde(deserialize_with = "deserialize_hex_bool")]
147    pub status: bool,
148    /// Logs emitted by the transaction.
149    #[serde(default)]
150    pub logs: Vec<RpcLog>,
151}
152
153/// The call operation reported by Geth's `callTracer`.
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
155#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
156pub enum RpcCallType {
157    Call,
158    Callcode,
159    Delegatecall,
160    Staticcall,
161    Create,
162    Create2,
163    Selfdestruct,
164}
165
166/// A normalized frame from Geth's `callTracer`.
167#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
168#[serde(rename_all = "camelCase")]
169pub struct RpcCallTrace {
170    /// The EVM call operation.
171    #[serde(rename = "type")]
172    pub call_type: RpcCallType,
173    /// The frame caller.
174    pub from: Address,
175    /// The frame target, when the operation has one.
176    pub to: Option<Address>,
177    /// The native value supplied to the frame.
178    #[serde(default)]
179    pub value: U256,
180    /// The frame gas allowance.
181    #[serde(deserialize_with = "deserialize_hex_u64")]
182    pub gas: u64,
183    /// The gas consumed by the frame.
184    #[serde(deserialize_with = "deserialize_hex_u64")]
185    pub gas_used: u64,
186    /// The input bytes supplied to the frame.
187    #[serde(default)]
188    pub input: Bytes,
189    /// The output bytes returned by the frame.
190    #[serde(default)]
191    pub output: Bytes,
192    /// Whether the frame reported an execution error.
193    #[serde(default)]
194    pub error: Option<String>,
195    /// Child frames in execution order.
196    #[serde(default)]
197    pub calls: Vec<Self>,
198}
199
200fn deserialize_hex_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
201where
202    D: serde::Deserializer<'de>,
203{
204    let s = String::deserialize(deserializer)?;
205    let value = parse_hex_quantity(&s).map_err(serde::de::Error::custom)?;
206    u64::try_from(value).map_err(serde::de::Error::custom)
207}
208
209fn deserialize_hex_u64_opt<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
210where
211    D: serde::Deserializer<'de>,
212{
213    let value = Option::<String>::deserialize(deserializer)?
214        .map(|value| parse_hex_quantity(&value))
215        .transpose()
216        .map_err(serde::de::Error::custom)?;
217    value
218        .map(u64::try_from)
219        .transpose()
220        .map_err(serde::de::Error::custom)
221}
222
223fn deserialize_hex_u8_opt<'de, D>(deserializer: D) -> Result<Option<u8>, D::Error>
224where
225    D: serde::Deserializer<'de>,
226{
227    let value = Option::<String>::deserialize(deserializer)?
228        .map(|value| parse_hex_quantity(&value))
229        .transpose()
230        .map_err(serde::de::Error::custom)?;
231    value
232        .map(u8::try_from)
233        .transpose()
234        .map_err(serde::de::Error::custom)
235}
236
237fn deserialize_hex_u128_opt<'de, D>(deserializer: D) -> Result<Option<u128>, D::Error>
238where
239    D: serde::Deserializer<'de>,
240{
241    let s: Option<String> = Option::deserialize(deserializer)?;
242    s.map(|s| parse_hex_quantity(&s).map_err(serde::de::Error::custom))
243        .transpose()
244}
245
246fn deserialize_hex_bool<'de, D>(deserializer: D) -> Result<bool, D::Error>
247where
248    D: serde::Deserializer<'de>,
249{
250    let s = String::deserialize(deserializer)?;
251    match s.as_str() {
252        "0x0" => Ok(false),
253        "0x1" => Ok(true),
254        _ => Err(serde::de::Error::custom(
255            "invalid transaction receipt status; expected 0x0 or 0x1",
256        )),
257    }
258}
259
260fn parse_hex_quantity(s: &str) -> anyhow::Result<u128> {
261    let stripped = s.strip_prefix("0x").unwrap_or(s);
262    u128::from_str_radix(stripped, 16)
263        .map_err(|e| anyhow::anyhow!("Failed to parse hex quantity '{s}': {e}"))
264}