1use alloy::primitives::{keccak256, Address, Bytes, B256, U256};
2use alloy::{
3 eips::{eip2718::Encodable2718, BlockNumberOrTag},
4 rpc::types::mev::{EthCallBundle, EthCallBundleResponse, EthSendBundle},
5};
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeMap;
8
9use crate::SignedOrder;
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14pub struct ZenithEthBundle {
15 #[serde(flatten)]
18 pub bundle: EthSendBundle,
19 pub host_fills: Option<SignedOrder>,
21}
22
23impl ZenithEthBundle {
24 pub fn txs(&self) -> &[Bytes] {
26 &self.bundle.txs
27 }
28
29 pub const fn block_number(&self) -> u64 {
31 self.bundle.block_number
32 }
33
34 pub const fn min_timestamp(&self) -> Option<u64> {
36 self.bundle.min_timestamp
37 }
38
39 pub const fn max_timestamp(&self) -> Option<u64> {
41 self.bundle.max_timestamp
42 }
43
44 pub fn reverting_tx_hashes(&self) -> &[B256] {
46 self.bundle.reverting_tx_hashes.as_slice()
47 }
48
49 pub fn replacement_uuid(&self) -> Option<&str> {
51 self.bundle.replacement_uuid.as_deref()
52 }
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(rename_all = "camelCase")]
58pub struct ZenithEthBundleResponse {
59 pub bundle_hash: B256,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67#[serde(rename_all = "camelCase")]
68pub struct ZenithCallBundle {
69 #[serde(flatten)]
72 pub bundle: EthCallBundle,
73 pub host_fills: BTreeMap<Address, BTreeMap<Address, U256>>,
76}
77
78impl ZenithCallBundle {
79 pub const fn host_fills(&self) -> &BTreeMap<Address, BTreeMap<Address, U256>> {
81 &self.host_fills
82 }
83
84 pub fn txs(&self) -> &[Bytes] {
86 &self.bundle.txs
87 }
88
89 pub const fn block_number(&self) -> u64 {
91 self.bundle.block_number
92 }
93
94 pub const fn state_block_number(&self) -> BlockNumberOrTag {
96 self.bundle.state_block_number
97 }
98
99 pub const fn timestamp(&self) -> Option<u64> {
101 self.bundle.timestamp
102 }
103
104 pub const fn gas_limit(&self) -> Option<u64> {
106 self.bundle.gas_limit
107 }
108
109 pub const fn difficulty(&self) -> Option<U256> {
111 self.bundle.difficulty
112 }
113
114 pub const fn base_fee(&self) -> Option<u128> {
116 self.bundle.base_fee
117 }
118
119 pub fn from_2718_and_host_fills<I, T>(
121 txs: I,
122 host_fills: BTreeMap<Address, BTreeMap<Address, U256>>,
123 ) -> Self
124 where
125 I: IntoIterator<Item = T>,
126 T: Encodable2718,
127 {
128 Self::from_raw_txs_and_host_fills(txs.into_iter().map(|tx| tx.encoded_2718()), host_fills)
129 }
130
131 pub fn from_raw_txs_and_host_fills<I, T>(
133 txs: I,
134 host_fills: BTreeMap<Address, BTreeMap<Address, U256>>,
135 ) -> Self
136 where
137 I: IntoIterator<Item = T>,
138 T: Into<Bytes>,
139 {
140 Self {
141 bundle: EthCallBundle {
142 txs: txs.into_iter().map(Into::into).collect(),
143 ..Default::default()
144 },
145 host_fills,
146 }
147 }
148
149 pub fn append_2718_tx(self, tx: impl Encodable2718) -> Self {
151 self.append_raw_tx(tx.encoded_2718())
152 }
153
154 pub fn append_raw_tx(mut self, tx: impl Into<Bytes>) -> Self {
156 self.bundle.txs.push(tx.into());
157 self
158 }
159
160 pub fn extend_2718_txs<I, T>(self, tx: I) -> Self
162 where
163 I: IntoIterator<Item = T>,
164 T: Encodable2718,
165 {
166 self.extend_raw_txs(tx.into_iter().map(|tx| tx.encoded_2718()))
167 }
168
169 pub fn extend_raw_txs<I, T>(mut self, txs: I) -> Self
171 where
172 I: IntoIterator<Item = T>,
173 T: Into<Bytes>,
174 {
175 self.bundle.txs.extend(txs.into_iter().map(Into::into));
176 self
177 }
178
179 pub const fn with_block_number(mut self, block_number: u64) -> Self {
181 self.bundle.block_number = block_number;
182 self
183 }
184
185 pub fn with_state_block_number(
187 mut self,
188 state_block_number: impl Into<BlockNumberOrTag>,
189 ) -> Self {
190 self.bundle.state_block_number = state_block_number.into();
191 self
192 }
193
194 pub const fn with_timestamp(mut self, timestamp: u64) -> Self {
196 self.bundle.timestamp = Some(timestamp);
197 self
198 }
199
200 pub const fn with_gas_limit(mut self, gas_limit: u64) -> Self {
202 self.bundle.gas_limit = Some(gas_limit);
203 self
204 }
205
206 pub const fn with_difficulty(mut self, difficulty: U256) -> Self {
208 self.bundle.difficulty = Some(difficulty);
209 self
210 }
211
212 pub const fn with_base_fee(mut self, base_fee: u128) -> Self {
214 self.bundle.base_fee = Some(base_fee);
215 self
216 }
217
218 pub fn bundle_hash(&self) -> B256 {
225 let mut hasher = alloy::primitives::Keccak256::new();
226
227 for tx in self.bundle.txs.iter() {
229 hasher.update(keccak256(tx).as_slice());
231 }
232 let tx_preimage = hasher.finalize();
233
234 let mut hasher = alloy::primitives::Keccak256::new();
241
242 hasher.update(U256::from(self.host_fills.len()).as_le_slice());
244
245 for (asset, fills) in self.host_fills.iter() {
246 hasher.update(asset.as_slice());
248
249 hasher.update(U256::from(fills.len()).as_le_slice());
251
252 for (user, amount) in fills.iter() {
253 hasher.update(user.as_slice());
255 hasher.update(amount.as_le_slice());
256 }
257 }
258
259 let host_preimage = hasher.finalize();
261
262 let mut pre_image = alloy::primitives::Keccak256::new();
263 pre_image.update(tx_preimage.as_slice());
264 pre_image.update(host_preimage.as_slice());
265
266 pre_image.finalize()
268 }
269}
270
271#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
273#[serde(rename_all = "camelCase")]
274pub struct ZenithCallBundleResponse {
275 #[serde(flatten)]
277 pub response: EthCallBundleResponse,
278}