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::{revm::Database, BundleError};
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(rename_all = "camelCase")]
29pub struct SignetEthBundle {
30 #[serde(flatten)]
32 pub bundle: EthSendBundle,
33 #[serde(default)]
36 pub host_fills: Option<SignedFill>,
37}
38
39impl SignetEthBundle {
40 #[allow(clippy::missing_const_for_fn)] pub fn txs(&self) -> &[Bytes] {
43 &self.bundle.txs
44 }
45
46 pub const fn block_number(&self) -> u64 {
48 self.bundle.block_number
49 }
50
51 pub const fn min_timestamp(&self) -> Option<u64> {
53 self.bundle.min_timestamp
54 }
55
56 pub const fn max_timestamp(&self) -> Option<u64> {
58 self.bundle.max_timestamp
59 }
60
61 pub fn reverting_tx_hashes(&self) -> &[B256] {
63 self.bundle.reverting_tx_hashes.as_slice()
64 }
65
66 pub fn replacement_uuid(&self) -> Option<&str> {
68 self.bundle.replacement_uuid.as_deref()
69 }
70
71 pub fn is_valid_at_timestamp(&self, timestamp: u64) -> bool {
73 let min_timestamp = self.bundle.min_timestamp.unwrap_or(0);
74 let max_timestamp = self.bundle.max_timestamp.unwrap_or(u64::MAX);
75 timestamp >= min_timestamp && timestamp <= max_timestamp
76 }
77
78 pub const fn is_valid_at_block_number(&self, block_number: u64) -> bool {
80 self.bundle.block_number == block_number
81 }
82
83 pub fn decode_and_validate_txs<Db: Database>(
85 &self,
86 ) -> Result<Vec<TxEnvelope>, BundleError<Db>> {
87 let txs = self
89 .txs()
90 .iter()
91 .map(|tx| TxEnvelope::decode_2718(&mut tx.chunk()))
92 .collect::<Result<Vec<_>, _>>()
93 .map_err(|err| BundleError::TransactionDecodingError(err))?;
94
95 if txs.iter().any(|tx| tx.is_eip4844()) {
96 return Err(BundleError::UnsupportedTransactionType);
97 }
98
99 Ok(txs)
100 }
101
102 pub fn validate_fills_offchain(&self, timestamp: u64) -> Result<(), SignedPermitError> {
104 if let Some(host_fills) = &self.host_fills {
105 host_fills.validate(timestamp)
106 } else {
107 Ok(())
108 }
109 }
110
111 pub async fn alloy_validate_fills_onchain<Db, P, N>(
114 &self,
115 orders: HostOrdersInstance<P, N>,
116 ) -> Result<(), SignetEthBundleError<Db>>
117 where
118 Db: Database,
119 P: Provider<N>,
120 N: Network,
121 {
122 if let Some(host_fills) = self.host_fills.clone() {
123 orders.try_fill(host_fills.outputs, host_fills.permit).await.map_err(Into::into)
124 } else {
125 Ok(())
126 }
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
136#[serde(rename_all = "camelCase")]
137pub struct SignetEthBundleResponse {
138 pub bundle_hash: B256,
143}
144
145#[cfg(test)]
146mod test {
147 use super::*;
148 use alloy::primitives::{Address, U256};
149 use signet_zenith::HostOrders::{
150 Output, Permit2Batch, PermitBatchTransferFrom, TokenPermissions,
151 };
152
153 #[test]
154 fn send_bundle_ser_roundtrip() {
155 let bundle = SignetEthBundle {
156 bundle: EthSendBundle {
157 txs: vec![b"tx1".into(), b"tx2".into()],
158 block_number: 1,
159 min_timestamp: Some(2),
160 max_timestamp: Some(3),
161 reverting_tx_hashes: vec![B256::repeat_byte(4), B256::repeat_byte(5)],
162 replacement_uuid: Some("uuid".to_owned()),
163 ..Default::default()
164 },
165 host_fills: Some(SignedFill {
166 permit: Permit2Batch {
167 permit: PermitBatchTransferFrom {
168 permitted: vec![TokenPermissions {
169 token: Address::repeat_byte(66),
170 amount: U256::from(17),
171 }],
172 nonce: U256::from(18),
173 deadline: U256::from(19),
174 },
175 owner: Address::repeat_byte(77),
176 signature: Bytes::from(b"abcd"),
177 },
178 outputs: vec![Output {
179 token: Address::repeat_byte(88),
180 amount: U256::from(20),
181 recipient: Address::repeat_byte(99),
182 chainId: 100,
183 }],
184 }),
185 };
186
187 let serialized = serde_json::to_string(&bundle).unwrap();
188 let deserialized: SignetEthBundle = serde_json::from_str(&serialized).unwrap();
189
190 assert_eq!(bundle, deserialized);
191 }
192
193 #[test]
194 fn send_bundle_resp_ser_roundtrip() {
195 let resp = SignetEthBundleResponse { bundle_hash: B256::repeat_byte(1) };
196
197 let serialized = serde_json::to_string(&resp).unwrap();
198 let deserialized: SignetEthBundleResponse = serde_json::from_str(&serialized).unwrap();
199
200 assert_eq!(resp, deserialized);
201 }
202}