Skip to content

Commit a95752f

Browse files
committed
Custom serialisation for bundles
1 parent 69fa90c commit a95752f

File tree

2 files changed

+91
-14
lines changed

2 files changed

+91
-14
lines changed

searcher-api-types/src/beaver.rs

Lines changed: 88 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,25 @@ use alloy_primitives::{hex, Address, BlockNumber, TxHash};
33
use alloy_rlp::Decodable;
44
use eyre::eyre;
55
use reth_primitives::TransactionSigned;
6-
use serde::{Deserialize, Serialize};
7-
use serde_with::{serde_as, skip_serializing_none};
6+
use serde::ser::{Serialize, SerializeStruct, Serializer};
87

98
/// Bundle as recognised by Beaverbuild
109
///
1110
/// Consult <https://beaverbuild.org/docs.html>. Note that the deprecated `replacementUuid` field
1211
/// has been omitted.
13-
#[skip_serializing_none]
14-
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
15-
#[serde(rename_all = "camelCase")]
16-
#[serde_as]
12+
#[derive(Clone, Debug, Default)]
1713
pub struct BeaverBundle {
1814
/// List of hex-encoded, raw transactions. Can be empty for cancelling a bundle
19-
#[serde(rename = "txs")]
2015
pub transactions: Vec<TransactionSigned>,
2116
/// The block that this bundle will be valid for. 0 means it's valid for the next block (and only this one)
22-
#[serde(with = "alloy_serde::quantity")]
2317
pub block_number: BlockNumber,
2418
/// If specified and >0, the bundle will only be valid if the block timestamp is greater or equal to `minTimestamp`
2519
pub min_timestamp: Option<u64>,
2620
/// If specified and >0, the bundle will only be valid if the block timestamp is smaller or equal to `maxTimestamp`
2721
pub max_timestamp: Option<u64>,
2822
/// A list of transaction hashes contained in the bundle, that can be allowed to revert, or be removed from your bundle if it's deemed useful
29-
#[serde(skip_serializing_if = "Vec::is_empty")]
3023
pub reverting_transaction_hashes: Vec<TxHash>,
3124
/// A list of transaction hashes contained in the bundle, that can be allowed to be removed from your bundle if it's deemed useful (but not revert)
32-
#[serde(skip_serializing_if = "Vec::is_empty")]
3325
pub dropping_transaction_hashes: Vec<TxHash>,
3426
/// An UUID string, which allows you to update/cancel your bundles: if you specify an uuid and we already have a bundle with an identical one, we'll forget about the old bundle. So we can only have a single bundle with a certain `uuid` at all times (and we keep the most recent)
3527
pub uuid: Option<String>,
@@ -38,7 +30,6 @@ pub struct BeaverBundle {
3830
/// You can specify an address that the funds from `refundPercent` will be sent to. If not specified, they will be sent to the `from` address of the first transaction
3931
pub refund_recipient: Option<Address>,
4032
/// The hashes of transactions in the bundle that the refund will be based on. If it's empty, we'll use the last transaction
41-
#[serde(skip_serializing_if = "Vec::is_empty")]
4233
pub refund_transaction_hashes: Vec<TxHash>,
4334
}
4435

@@ -62,12 +53,85 @@ impl BeaverBundle {
6253
}
6354
}
6455

56+
impl Serialize for BeaverBundle {
57+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
58+
where
59+
S: Serializer,
60+
{
61+
let mut state = serializer.serialize_struct("BeaverBundle", 2)?;
62+
state.serialize_field(
63+
"txs",
64+
&self
65+
.transactions
66+
.iter()
67+
.map(|tx| hex::encode(alloy_rlp::encode(tx)))
68+
.collect::<Vec<String>>(),
69+
)?;
70+
state.serialize_field("blockNumber", &format!("0x{:x}", self.block_number))?;
71+
72+
if let Some(ref t) = self.min_timestamp {
73+
state.serialize_field("minTimestamp", t)?;
74+
}
75+
76+
if let Some(ref t) = self.max_timestamp {
77+
state.serialize_field("maxTimestamp", t)?;
78+
}
79+
80+
if !self.reverting_transaction_hashes.is_empty() {
81+
state.serialize_field(
82+
"revertingTxHashes",
83+
&self
84+
.reverting_transaction_hashes
85+
.iter()
86+
.map(|hash| hash.to_string())
87+
.collect::<Vec<String>>(),
88+
)?;
89+
}
90+
91+
if !self.dropping_transaction_hashes.is_empty() {
92+
state.serialize_field(
93+
"droppingTxHashes",
94+
&self
95+
.dropping_transaction_hashes
96+
.iter()
97+
.map(|hash| hash.to_string())
98+
.collect::<Vec<String>>(),
99+
)?;
100+
}
101+
102+
if let Some(ref uuid) = self.uuid {
103+
state.serialize_field("uuid", uuid)?;
104+
}
105+
106+
if let Some(ref refund_pct) = self.refund_percent {
107+
state.serialize_field("refundPercent", refund_pct)?;
108+
}
109+
110+
if let Some(ref refund_addr) = self.refund_recipient {
111+
state.serialize_field("refundRecipient", refund_addr)?;
112+
}
113+
114+
if !self.refund_transaction_hashes.is_empty() {
115+
state.serialize_field(
116+
"refundTxHashes",
117+
&self
118+
.refund_transaction_hashes
119+
.iter()
120+
.map(|hash| hash.to_string())
121+
.collect::<Vec<String>>(),
122+
)?;
123+
}
124+
125+
state.end()
126+
}
127+
}
128+
65129
#[cfg(test)]
66130
mod test {
67131
use super::*;
68132

69133
#[test]
70-
fn test_beaver_bundle_default_serialisation() {
134+
fn test_beaver_bundle_serialisation() {
71135
assert!(serde_json::to_string(&BeaverBundle::default()).is_ok());
72136
assert_eq!(
73137
serde_json::to_string(&BeaverBundle::default()).unwrap(),
@@ -89,5 +153,17 @@ mod test {
89153
.unwrap(),
90154
"{\"txs\":[],\"blockNumber\":\"0x14d99d9\"}".to_string()
91155
);
156+
assert!(
157+
serde_json::to_string(&
158+
BeaverBundle::from_rlp_hex(vec!["0x02f8b20181948449bdee618501dcd6500083016b93942dabcea55a12d73191aece59f508b191fb68adac80b844095ea7b300000000000000000000000054e44dbb92dba848ace27f44c0cb4268981ef1cc00000000000000000000000000000000000000000000000052616e065f6915ebc080a0c497b6e53d7cb78e68c37f6186c8bb9e1b8a55c3e22462163495979b25c2caafa052769811779f438b73159c4cc6a05a889da8c1a16e432c2e37e3415c9a0b9887".to_string()], 21862873).unwrap()
159+
)
160+
.is_ok());
161+
assert_eq!(
162+
serde_json::to_string(&
163+
BeaverBundle::from_rlp_hex(vec!["0x02f8b20181948449bdee618501dcd6500083016b93942dabcea55a12d73191aece59f508b191fb68adac80b844095ea7b300000000000000000000000054e44dbb92dba848ace27f44c0cb4268981ef1cc00000000000000000000000000000000000000000000000052616e065f6915ebc080a0c497b6e53d7cb78e68c37f6186c8bb9e1b8a55c3e22462163495979b25c2caafa052769811779f438b73159c4cc6a05a889da8c1a16e432c2e37e3415c9a0b9887".to_string()], 21862873).unwrap()
164+
)
165+
.unwrap(),
166+
"{\"txs\":[\"0x02f8b20181948449bdee618501dcd6500083016b93942dabcea55a12d73191aece59f508b191fb68adac80b844095ea7b300000000000000000000000054e44dbb92dba848ace27f44c0cb4268981ef1cc00000000000000000000000000000000000000000000000052616e065f6915ebc080a0c497b6e53d7cb78e68c37f6186c8bb9e1b8a55c3e22462163495979b25c2caafa052769811779f438b73159c4cc6a05a889da8c1a16e432c2e37e3415c9a0b9887\"],\"blockNumber\":\"0x14d99d9\"}".to_string()
167+
);
92168
}
93169
}

searcher-api-types/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use alloy_primitives::{BlockNumber, Bytes, TxHash};
22
use alloy_rlp::encode;
3-
use serde::{Deserialize, Serialize};
3+
use serde::Serialize;
44

55
pub mod beaver;
66
pub mod flashbots;
@@ -13,7 +13,8 @@ pub use titan::*;
1313
/// Universal bundle submission RPC type
1414
///
1515
/// This type represents what Lynx accepts from external order flow providers.
16-
#[derive(Clone, Debug, Deserialize, Serialize)]
16+
#[derive(Clone, Debug, Serialize)]
17+
#[serde(untagged)]
1718
pub enum SendBundleRequest {
1819
/// Flashbots bundle
1920
Flashbots(FlashbotsBundle),

0 commit comments

Comments
 (0)