kabu_broadcast_flashbots/client/
jsonrpc.rs

1use std::fmt;
2
3use crate::client::BundleHash;
4use alloy_primitives::U256;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use thiserror::Error;
8// Code adapted from: https://github.com/althea-net/guac_rs/tree/master/web3/src/jsonrpc
9// NOTE: This module only exists since there is no way to use the data structures
10// in the `ethers-providers/src/transports/common.rs` from another crate.
11
12/// A JSON-RPC 2.0 error
13#[derive(Serialize, Deserialize, Debug, Clone, Error)]
14pub struct JsonRpcError {
15    /// The error code
16    pub code: i64,
17    /// The error message
18    pub message: String,
19    /// Additional data
20    pub data: Option<Value>,
21}
22
23impl fmt::Display for JsonRpcError {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        write!(f, "(code: {}, message: {}, data: {:?})", self.code, self.message, self.data)
26    }
27}
28
29fn is_zst<T>(_t: &T) -> bool {
30    std::mem::size_of::<T>() == 0
31}
32
33#[derive(Serialize, Deserialize, Debug)]
34/// A JSON-RPC request
35pub struct Request<'a, T> {
36    id: u64,
37    jsonrpc: &'a str,
38    method: &'a str,
39    #[serde(skip_serializing_if = "is_zst")]
40    params: T,
41}
42
43#[derive(Serialize, Deserialize, Debug)]
44/// A JSON-RPC Notifcation
45pub struct Notification<R> {
46    jsonrpc: String,
47    method: String,
48    pub params: Subscription<R>,
49}
50
51#[derive(Serialize, Deserialize, Debug)]
52pub struct Subscription<R> {
53    pub subscription: U256,
54    pub result: R,
55}
56
57impl<'a, T> Request<'a, T> {
58    /// Creates a new JSON RPC request
59    pub fn new(id: u64, method: &'a str, params: T) -> Self {
60        Self { id, jsonrpc: "2.0", method, params }
61    }
62}
63
64#[derive(Serialize, Deserialize, Debug, Clone)]
65pub struct Response<T> {
66    #[serde(default)]
67    pub(crate) id: u64,
68    #[serde(default)]
69    jsonrpc: String,
70    #[serde(flatten)]
71    pub data: ResponseData<T>,
72}
73
74#[derive(Serialize, Deserialize, Debug, Clone)]
75#[serde(untagged)]
76pub enum ResponseData<R> {
77    Error { error: JsonRpcError },
78    Success { result: R },
79}
80
81impl<R> ResponseData<R> {
82    /// Consume response and return value
83    pub fn into_result(self) -> Result<R, JsonRpcError> {
84        match self {
85            ResponseData::Success { result } => Ok(result),
86            ResponseData::Error { error } => Err(error),
87        }
88    }
89}
90
91#[derive(Deserialize, Debug, PartialEq, Eq, Hash)]
92#[serde(untagged)]
93pub enum SendBundleResponseType {
94    Integer(u64),
95    BundleHash(BundleHash),
96    String(String),
97    SendBundleResponse(SendBundleResponse),
98    Null(Option<()>),
99}
100
101#[derive(Deserialize, Debug, PartialEq, Eq, Hash)]
102#[serde(rename_all = "camelCase")]
103pub struct SendBundleResponse {
104    #[serde(default)]
105    pub(crate) bundle_hash: Option<BundleHash>,
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use alloy_primitives::{hex, TxHash};
112
113    #[test]
114    fn deser_response() {
115        let response: Response<u64> = serde_json::from_str(r#"{"jsonrpc": "2.0", "result": 19, "id": 1}"#).unwrap();
116        assert_eq!(response.id, 1);
117        assert_eq!(response.data.into_result().unwrap(), 19);
118    }
119
120    #[test]
121    fn ser_request() {
122        let request: Request<()> = Request::new(300, "method_name", ());
123        assert_eq!(&serde_json::to_string(&request).unwrap(), r#"{"id":300,"jsonrpc":"2.0","method":"method_name"}"#);
124
125        let request: Request<u32> = Request::new(300, "method_name", 1);
126        assert_eq!(&serde_json::to_string(&request).unwrap(), r#"{"id":300,"jsonrpc":"2.0","method":"method_name","params":1}"#);
127    }
128
129    #[test]
130    fn deser_response_enum() {
131        let response: Response<SendBundleResponseType> = serde_json::from_str(r#"{"jsonrpc": "2.0", "result": 19, "id": 1}"#).unwrap();
132        assert_eq!(response.id, 1);
133        assert_eq!(response.data.into_result().unwrap(), SendBundleResponseType::Integer(19));
134    }
135
136    #[test]
137    fn deser_response_result_null() {
138        // https://rpc.penguinbuild.org
139        let response: Response<SendBundleResponseType> = serde_json::from_str(r#"{"jsonrpc":"2.0","id":2,"result":null}"#).unwrap();
140        assert_eq!(response.id, 2);
141        assert_eq!(response.data.into_result().unwrap(), SendBundleResponseType::Null(None));
142    }
143
144    #[test]
145    fn deser_response_result_string() {
146        // https://rpc.lokibuilder.xyz
147        // https://api.securerpc.com/v1
148        let response: Response<SendBundleResponseType> = serde_json::from_str(r#"{"jsonrpc":"2.0","id":1,"result":"nil"}"#).unwrap();
149        assert_eq!(response.id, 1);
150        assert_eq!(response.data.into_result().unwrap(), SendBundleResponseType::String("nil".to_string()));
151    }
152    #[test]
153    fn deser_response_result_send_bundle_response() {
154        let response: Response<SendBundleResponseType> = serde_json::from_str(
155            r#"{"id":1,"result":{"bundleHash":"0xcc6c61428c6516a252768859d167dc8f5c8c8c682334a184710f898e422530f8"},"jsonrpc":"2.0"}"#,
156        )
157        .unwrap();
158        assert_eq!(response.id, 1);
159        assert_eq!(
160            response.data.into_result().unwrap(),
161            SendBundleResponseType::SendBundleResponse(SendBundleResponse {
162                bundle_hash: Some(TxHash::from(hex!("cc6c61428c6516a252768859d167dc8f5c8c8c682334a184710f898e422530f8")))
163            })
164        )
165    }
166    #[test]
167    fn deser_response_result_bundle_hash_response() {
168        let response: Response<SendBundleResponseType> = serde_json::from_str(
169            r#"{"jsonrpc":"2.0","id":1,"result":"0xcc6c61428c6516a252768859d167dc8f5c8c8c682334a184710f898e422530f8"}"#,
170        )
171        .unwrap();
172        assert_eq!(response.id, 1);
173        assert_eq!(
174            response.data.into_result().unwrap(),
175            SendBundleResponseType::BundleHash(TxHash::from(hex!("cc6c61428c6516a252768859d167dc8f5c8c8c682334a184710f898e422530f8")))
176        )
177    }
178
179    //
180}