signet_bundle/send/
bundle.rs1use crate::send::SignetEthBundleError;
3use alloy::{
4 consensus::TxEnvelope,
5 eips::Decodable2718,
6 network::Network,
7 primitives::{Bytes, B256},
8 providers::Provider,
9 rlp::Buf,
10 rpc::types::mev::EthSendBundle,
11};
12use serde::{Deserialize, Serialize};
13use signet_types::{SignedFill, SignedPermitError};
14use signet_zenith::HostOrders::HostOrdersInstance;
15use trevm::{
16 inspectors::{Layered, TimeLimit},
17 revm::{inspector::NoOpInspector, Database},
18 BundleError,
19};
20
21pub type BundleInspector<I = NoOpInspector> = Layered<TimeLimit, I>;
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(rename_all = "camelCase")]
36pub struct SignetEthBundle {
37 #[serde(flatten)]
39 pub bundle: EthSendBundle,
40 #[serde(default)]
43 pub host_fills: Option<SignedFill>,
44}
45
46impl SignetEthBundle {
47 #[allow(clippy::missing_const_for_fn)] pub fn txs(&self) -> &[Bytes] {
50 &self.bundle.txs
51 }
52
53 pub const fn block_number(&self) -> u64 {
55 self.bundle.block_number
56 }
57
58 pub const fn min_timestamp(&self) -> Option<u64> {
60 self.bundle.min_timestamp
61 }
62
63 pub const fn max_timestamp(&self) -> Option<u64> {
65 self.bundle.max_timestamp
66 }
67
68 pub fn reverting_tx_hashes(&self) -> &[B256] {
70 self.bundle.reverting_tx_hashes.as_slice()
71 }
72
73 pub fn replacement_uuid(&self) -> Option<&str> {
75 self.bundle.replacement_uuid.as_deref()
76 }
77
78 pub fn is_valid_at_timestamp(&self, timestamp: u64) -> bool {
80 let min_timestamp = self.bundle.min_timestamp.unwrap_or(0);
81 let max_timestamp = self.bundle.max_timestamp.unwrap_or(u64::MAX);
82 timestamp >= min_timestamp && timestamp <= max_timestamp
83 }
84
85 pub const fn is_valid_at_block_number(&self, block_number: u64) -> bool {
87 self.bundle.block_number == block_number
88 }
89
90 pub fn decode_and_validate_txs<Db: Database>(
92 &self,
93 ) -> Result<Vec<TxEnvelope>, BundleError<Db>> {
94 let txs = self
96 .txs()
97 .iter()
98 .map(|tx| TxEnvelope::decode_2718(&mut tx.chunk()))
99 .collect::<Result<Vec<_>, _>>()
100 .map_err(|err| BundleError::TransactionDecodingError(err))?;
101
102 if txs.iter().any(|tx| tx.is_eip4844()) {
103 return Err(BundleError::UnsupportedTransactionType);
104 }
105
106 Ok(txs)
107 }
108
109 pub fn validate_fills_offchain(&self, timestamp: u64) -> Result<(), SignedPermitError> {
111 if let Some(host_fills) = &self.host_fills {
112 host_fills.validate(timestamp)
113 } else {
114 Ok(())
115 }
116 }
117
118 pub async fn alloy_validate_fills_onchain<Db, P, N>(
126 &self,
127 orders: HostOrdersInstance<P, N>,
128 ) -> Result<(), SignetEthBundleError<Db>>
129 where
130 Db: Database,
131 P: Provider<N>,
132 N: Network,
133 {
134 if let Some(host_fills) = self.host_fills.clone() {
135 orders.try_fill(host_fills.outputs, host_fills.permit).await.map_err(Into::into)
136 } else {
137 Ok(())
138 }
139 }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct SignetEthBundleResponse {
150 pub bundle_hash: B256,
155}
156
157#[cfg(test)]
158mod test {
159 use super::*;
160 use alloy::primitives::{Address, U256};
161 use signet_zenith::HostOrders::{
162 Output, Permit2Batch, PermitBatchTransferFrom, TokenPermissions,
163 };
164
165 #[test]
166 fn send_bundle_ser_roundtrip() {
167 let bundle = SignetEthBundle {
168 bundle: EthSendBundle {
169 txs: vec![b"tx1".into(), b"tx2".into()],
170 block_number: 1,
171 min_timestamp: Some(2),
172 max_timestamp: Some(3),
173 reverting_tx_hashes: vec![B256::repeat_byte(4), B256::repeat_byte(5)],
174 replacement_uuid: Some("uuid".to_owned()),
175 ..Default::default()
176 },
177 host_fills: Some(SignedFill {
178 permit: Permit2Batch {
179 permit: PermitBatchTransferFrom {
180 permitted: vec![TokenPermissions {
181 token: Address::repeat_byte(66),
182 amount: U256::from(17),
183 }],
184 nonce: U256::from(18),
185 deadline: U256::from(19),
186 },
187 owner: Address::repeat_byte(77),
188 signature: Bytes::from(b"abcd"),
189 },
190 outputs: vec![Output {
191 token: Address::repeat_byte(88),
192 amount: U256::from(20),
193 recipient: Address::repeat_byte(99),
194 chainId: 100,
195 }],
196 }),
197 };
198
199 let serialized = serde_json::to_string(&bundle).unwrap();
200 let deserialized: SignetEthBundle = serde_json::from_str(&serialized).unwrap();
201
202 assert_eq!(bundle, deserialized);
203 }
204
205 #[test]
206 fn send_bundle_resp_ser_roundtrip() {
207 let resp = SignetEthBundleResponse { bundle_hash: B256::repeat_byte(1) };
208
209 let serialized = serde_json::to_string(&resp).unwrap();
210 let deserialized: SignetEthBundleResponse = serde_json::from_str(&serialized).unwrap();
211
212 assert_eq!(resp, deserialized);
213 }
214}