1use alloy::{consensus::TxEnvelope, primitives::B256};
3use core::ops::{Deref, DerefMut};
4use serde::{de::DeserializeOwned, Deserialize, Serialize};
5use signet_bundle::SignetEthBundle;
6use signet_types::SignedOrder;
7use uuid::Uuid;
8
9pub trait CacheObject {
11 type Key: Serialize + DeserializeOwned;
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
17#[serde(rename_all = "camelCase")]
18pub struct CacheResponse<T: CacheObject> {
19 #[serde(flatten)]
21 inner: T,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
24 next_cursor: Option<T::Key>,
25}
26
27impl<T: CacheObject> CacheObject for CacheResponse<T> {
28 type Key = T::Key;
29}
30
31impl<T: CacheObject> Deref for CacheResponse<T> {
32 type Target = T;
33
34 fn deref(&self) -> &Self::Target {
35 &self.inner
36 }
37}
38
39impl<T: CacheObject> DerefMut for CacheResponse<T> {
40 fn deref_mut(&mut self) -> &mut Self::Target {
41 &mut self.inner
42 }
43}
44
45impl<T: CacheObject> CacheResponse<T> {
46 pub const fn paginated(inner: T, pagination: T::Key) -> Self {
48 Self { inner, next_cursor: Some(pagination) }
49 }
50
51 pub const fn unpaginated(inner: T) -> Self {
53 Self { inner, next_cursor: None }
54 }
55
56 #[deprecated = "use deref instead"]
58 pub const fn inner(&self) -> &T {
59 match self {
60 Self { inner, .. } => inner,
61 }
62 }
63
64 #[deprecated = "use deref_mut instead"]
66 pub const fn inner_mut(&mut self) -> &mut T {
67 match self {
68 Self { inner, .. } => inner,
69 }
70 }
71
72 pub const fn next_cursor(&self) -> Option<&T::Key> {
74 match self {
75 Self { next_cursor, .. } => next_cursor.as_ref(),
76 }
77 }
78
79 pub const fn has_more(&self) -> bool {
81 self.next_cursor().is_some()
82 }
83
84 pub const fn is_paginated(&self) -> bool {
86 self.next_cursor.is_some()
87 }
88
89 pub const fn is_unpaginated(&self) -> bool {
91 self.next_cursor.is_none()
92 }
93
94 pub fn into_inner(self) -> T {
96 match self {
97 Self { inner, .. } => inner,
98 }
99 }
100
101 pub fn into_parts(self) -> (T, Option<T::Key>) {
103 match self {
104 Self { inner, next_cursor } => (inner, next_cursor),
105 }
106 }
107
108 pub fn into_next_cursor(self) -> Option<T::Key> {
110 self.into_parts().1
111 }
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
118pub struct CachedBundle {
119 pub id: uuid::Uuid,
121 pub bundle: SignetEthBundle,
123}
124
125impl CachedBundle {
126 pub const fn new(bundle: SignetEthBundle, id: uuid::Uuid) -> Self {
128 Self { id, bundle }
129 }
130
131 #[deprecated = "Use `Self::new` instead"]
133 pub const fn from_bundle_and_id(bundle: SignetEthBundle, id: uuid::Uuid) -> Self {
134 Self::new(bundle, id)
135 }
136
137 pub fn into_bundle(self) -> SignetEthBundle {
139 self.bundle
140 }
141
142 pub fn into_id(self) -> uuid::Uuid {
144 self.id
145 }
146
147 pub const fn id(&self) -> uuid::Uuid {
149 self.id
150 }
151
152 pub const fn bundle(&self) -> &SignetEthBundle {
154 &self.bundle
155 }
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
162pub struct BundleList {
163 pub bundles: Vec<CachedBundle>,
165}
166
167impl From<Vec<CachedBundle>> for BundleList {
168 fn from(bundles: Vec<CachedBundle>) -> Self {
169 Self { bundles }
170 }
171}
172
173impl From<BundleList> for Vec<CachedBundle> {
174 fn from(response: BundleList) -> Self {
175 response.bundles
176 }
177}
178
179impl CacheObject for BundleList {
180 type Key = BundleKey;
181}
182
183impl BundleList {
184 pub const fn new(bundles: Vec<CachedBundle>) -> Self {
186 Self { bundles }
187 }
188
189 #[deprecated = "Use `From::from` instead, `Self::new` in const contexts"]
191 pub const fn from_bundles(bundles: Vec<CachedBundle>) -> Self {
192 Self { bundles }
193 }
194
195 #[deprecated = "Use `this.bundles` instead."]
197 pub fn into_bundles(self) -> Vec<CachedBundle> {
198 self.bundles
199 }
200
201 pub const fn is_empty(&self) -> bool {
203 self.bundles.is_empty()
204 }
205}
206
207#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
211pub struct BundleResponse {
212 pub id: uuid::Uuid,
214}
215
216impl BundleResponse {
217 pub const fn new(id: uuid::Uuid) -> Self {
219 Self { id }
220 }
221}
222
223impl From<uuid::Uuid> for BundleResponse {
224 fn from(id: uuid::Uuid) -> Self {
225 Self { id }
226 }
227}
228
229impl From<BundleResponse> for uuid::Uuid {
230 fn from(response: BundleResponse) -> Self {
231 response.id
232 }
233}
234
235impl CacheObject for BundleResponse {
236 type Key = BundleKey;
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
243pub struct TransactionList {
244 pub transactions: Vec<TxEnvelope>,
246}
247
248impl From<Vec<TxEnvelope>> for TransactionList {
249 fn from(transactions: Vec<TxEnvelope>) -> Self {
250 Self { transactions }
251 }
252}
253
254impl From<TransactionList> for Vec<TxEnvelope> {
255 fn from(response: TransactionList) -> Self {
256 response.transactions
257 }
258}
259
260impl CacheObject for TransactionList {
261 type Key = TxKey;
262}
263
264impl TransactionList {
265 pub const fn new(transactions: Vec<TxEnvelope>) -> Self {
267 Self { transactions }
268 }
269
270 #[deprecated = "Use `From::from` instead, or `Self::new` in const contexts"]
272 pub const fn from_transactions(transactions: Vec<TxEnvelope>) -> Self {
273 Self { transactions }
274 }
275
276 #[deprecated = "Use `this.transactions` instead."]
278 pub fn into_transactions(self) -> Vec<TxEnvelope> {
279 self.transactions
280 }
281
282 pub const fn is_empty(&self) -> bool {
284 self.transactions.is_empty()
285 }
286}
287
288#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
292pub struct TransactionResponse {
293 pub tx_hash: B256,
295}
296
297impl From<B256> for TransactionResponse {
298 fn from(tx_hash: B256) -> Self {
299 Self { tx_hash }
300 }
301}
302
303impl From<TransactionResponse> for B256 {
304 fn from(response: TransactionResponse) -> Self {
305 response.tx_hash
306 }
307}
308
309impl CacheObject for TransactionResponse {
310 type Key = TxKey;
311}
312
313impl TransactionResponse {
314 pub const fn new(tx_hash: B256) -> Self {
316 Self { tx_hash }
317 }
318
319 #[deprecated = "Use `From::from` instead, or `Self::new` in const contexts"]
321 pub const fn from_tx_hash(tx_hash: B256) -> Self {
322 Self { tx_hash }
323 }
324
325 #[deprecated = "Use `this.tx_hash` instead."]
327 pub const fn into_tx_hash(self) -> B256 {
328 self.tx_hash
329 }
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
336pub struct OrderList {
337 pub orders: Vec<SignedOrder>,
339}
340
341impl From<Vec<SignedOrder>> for OrderList {
342 fn from(orders: Vec<SignedOrder>) -> Self {
343 Self { orders }
344 }
345}
346
347impl From<OrderList> for Vec<SignedOrder> {
348 fn from(response: OrderList) -> Self {
349 response.orders
350 }
351}
352
353impl CacheObject for OrderList {
354 type Key = OrderKey;
355}
356
357impl OrderList {
358 pub const fn new(orders: Vec<SignedOrder>) -> Self {
360 Self { orders }
361 }
362
363 #[deprecated = "Use `From::from` instead, `Self::new` in const contexts"]
365 pub const fn from_orders(orders: Vec<SignedOrder>) -> Self {
366 Self { orders }
367 }
368
369 #[deprecated = "Use `this.orders` instead."]
371 pub fn into_orders(self) -> Vec<SignedOrder> {
372 self.orders
373 }
374}
375
376#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
380pub struct OrderResponse {
381 pub id: B256,
383}
384
385impl From<B256> for OrderResponse {
386 fn from(id: B256) -> Self {
387 Self { id }
388 }
389}
390
391impl From<OrderResponse> for B256 {
392 fn from(response: OrderResponse) -> Self {
393 response.id
394 }
395}
396
397impl CacheObject for OrderResponse {
398 type Key = OrderKey;
399}
400
401impl OrderResponse {
402 pub const fn new(id: B256) -> Self {
404 Self { id }
405 }
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
410#[serde(rename_all = "camelCase")]
411pub struct TxKey {
412 pub txn_hash: B256,
414 pub score: u64,
416 pub global_transaction_score_key: String,
418}
419
420#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
425#[serde(rename_all = "camelCase")]
426pub struct BundleKey {
427 pub id: Uuid,
429 pub score: u64,
431 pub target_block_number: u64,
433}
434
435#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
437pub struct OrderKey {
438 pub id: B256,
440}
441
442#[deprecated(since = "0.16.0", note = "renamed to `CachedBundle`")]
447pub type TxCacheBundle = CachedBundle;
449
450#[deprecated(since = "0.16.0", note = "renamed to `BundleList`")]
451pub type TxCacheBundlesResponse = BundleList;
453
454#[deprecated(since = "0.16.0", note = "renamed to `BundleResponse`")]
455pub type BundleReceipt = BundleResponse;
457
458#[deprecated(since = "0.16.0", note = "renamed to `BundleResponse`")]
459pub type TxCacheSendBundleResponse = BundleResponse;
461
462#[deprecated(since = "0.16.0", note = "renamed to `TransactionList`")]
463pub type TxCacheTransactionsResponse = TransactionList;
465
466#[deprecated(since = "0.16.0", note = "renamed to `TransactionResponse`")]
467pub type TransactionReceipt = TransactionResponse;
469
470#[deprecated(since = "0.16.0", note = "renamed to `TransactionResponse`")]
471pub type TxCacheSendTransactionResponse = TransactionResponse;
473
474#[deprecated(since = "0.16.0", note = "renamed to `OrderList`")]
475pub type TxCacheOrdersResponse = OrderList;
477
478#[deprecated(since = "0.16.0", note = "renamed to `OrderResponse`")]
479pub type OrderReceipt = OrderResponse;
481
482#[deprecated(since = "0.16.0", note = "renamed to `OrderResponse`")]
483pub type TxCacheSendOrderResponse = OrderResponse;
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489 use std::str::FromStr;
490
491 fn dummy_bundle_with_id(id: Uuid) -> CachedBundle {
492 CachedBundle {
493 id,
494 bundle: SignetEthBundle {
495 bundle: alloy::rpc::types::mev::EthSendBundle {
496 txs: vec![],
497 block_number: 0,
498 min_timestamp: None,
499 max_timestamp: None,
500 reverting_tx_hashes: vec![],
501 replacement_uuid: Some(id.to_string()),
502 dropping_tx_hashes: vec![],
503 refund_percent: None,
504 refund_recipient: None,
505 refund_tx_hashes: vec![],
506 extra_fields: Default::default(),
507 },
508 host_txs: vec![],
509 },
510 }
511 }
512
513 #[test]
514 fn test_unpaginated_cache_response_deser() {
515 let cache_response = CacheResponse::unpaginated(TransactionList { transactions: vec![] });
516 let expected_json = r#"{"transactions":[]}"#;
517 let serialized = serde_json::to_string(&cache_response).unwrap();
518 assert_eq!(serialized, expected_json);
519 let deserialized =
520 serde_json::from_str::<CacheResponse<TransactionList>>(&serialized).unwrap();
521 assert_eq!(deserialized, cache_response);
522 }
523
524 #[test]
525 fn test_paginated_cache_response_deser() {
526 let cache_response = CacheResponse::paginated(
527 TransactionList { transactions: vec![] },
528 TxKey {
529 txn_hash: B256::repeat_byte(0xaa),
530 score: 100,
531 global_transaction_score_key: "gtsk".to_string(),
532 },
533 );
534 let expected_json = r#"{"transactions":[],"nextCursor":{"txnHash":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","score":100,"globalTransactionScoreKey":"gtsk"}}"#;
535 let serialized = serde_json::to_string(&cache_response).unwrap();
536 assert_eq!(serialized, expected_json);
537 let deserialized =
538 serde_json::from_str::<CacheResponse<TransactionList>>(expected_json).unwrap();
539 assert_eq!(deserialized, cache_response);
540 }
541
542 #[test]
545 fn test_backwards_compatibility_cache_response_deser() {
546 let expected_json = r#"{"transactions":[],"nextCursor":{"txnHash":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","score":100,"globalTransactionScoreKey":"gtsk"}}"#;
547 let deserialized = serde_json::from_str::<TransactionList>(expected_json).unwrap();
548 assert_eq!(deserialized, TransactionList { transactions: vec![] });
549 }
550
551 #[test]
554 fn test_backwards_compatibility_cache_bundle_response_deser() {
555 let expected_json = r#"{"bundles":[{"id":"5932d4bb-58d9-41a9-851d-8dd7f04ccc33","bundle":{"txs":[],"blockNumber":"0x0","replacementUuid":"5932d4bb-58d9-41a9-851d-8dd7f04ccc33"}}]}"#;
556 let uuid = Uuid::from_str("5932d4bb-58d9-41a9-851d-8dd7f04ccc33").unwrap();
557
558 let deserialized = serde_json::from_str::<BundleList>(expected_json).unwrap();
559
560 assert_eq!(deserialized, BundleList { bundles: vec![dummy_bundle_with_id(uuid)] });
561 }
562
563 #[test]
566 fn test_backwards_compatibility_cache_order_response_deser() {
567 let expected_json = r#"{"orders":[{"permit":{"permitted":[{"token":"0x0b8bc5e60ee10957e0d1a0d95598fa63e65605e2","amount":"0xf4240"}],"nonce":"0x637253c1eb651","deadline":"0x6846fde6"},"owner":"0x492e9c316f073fe4de9d665221568cdad1a7e95b","signature":"0x73e31a7c80f02840c4e0671230c408a5cbc7cddefc780db4dd102eed8e87c5740fc89944eb8e5756edd368ed755415ed090b043d1740ee6869c20cb1676329621c","outputs":[{"token":"0x885f8db528dc8a38aa3ddad9d3f619746b4a6a81","amount":"0xf4240","recipient":"0x492e9c316f073fe4de9d665221568cdad1a7e95b","chainId":3151908}]}], "id":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}"#;
568 let _ = serde_json::from_str::<OrderList>(expected_json).unwrap();
569 }
570
571 #[test]
572 fn test_unpaginated_cache_bundle_response_deser() {
573 let cache_response = CacheResponse::unpaginated(BundleList {
574 bundles: vec![dummy_bundle_with_id(
575 Uuid::from_str("5932d4bb-58d9-41a9-851d-8dd7f04ccc33").unwrap(),
576 )],
577 });
578 let expected_json = r#"{"bundles":[{"id":"5932d4bb-58d9-41a9-851d-8dd7f04ccc33","bundle":{"txs":[],"blockNumber":"0x0","replacementUuid":"5932d4bb-58d9-41a9-851d-8dd7f04ccc33"}}]}"#;
579 let serialized = serde_json::to_string(&cache_response).unwrap();
580 assert_eq!(serialized, expected_json);
581 let deserialized =
582 serde_json::from_str::<CacheResponse<BundleList>>(expected_json).unwrap();
583 assert_eq!(deserialized, cache_response);
584 }
585
586 #[test]
587 fn test_paginated_cache_bundle_response_deser() {
588 let uuid = Uuid::from_str("5932d4bb-58d9-41a9-851d-8dd7f04ccc33").unwrap();
589
590 let cache_response = CacheResponse::paginated(
591 BundleList { bundles: vec![dummy_bundle_with_id(uuid)] },
592 BundleKey { id: uuid, score: 100, target_block_number: 42 },
593 );
594 let expected_json = r#"{"bundles":[{"id":"5932d4bb-58d9-41a9-851d-8dd7f04ccc33","bundle":{"txs":[],"blockNumber":"0x0","replacementUuid":"5932d4bb-58d9-41a9-851d-8dd7f04ccc33"}}],"nextCursor":{"id":"5932d4bb-58d9-41a9-851d-8dd7f04ccc33","score":100,"targetBlockNumber":42}}"#;
595 let serialized = serde_json::to_string(&cache_response).unwrap();
596 assert_eq!(serialized, expected_json);
597 let deserialized =
598 serde_json::from_str::<CacheResponse<BundleList>>(expected_json).unwrap();
599 assert_eq!(deserialized, cache_response);
600 }
601
602 #[test]
603 fn test_pagination_params_simple_deser() {
604 let tx_key = TxKey {
605 txn_hash: B256::repeat_byte(0xaa),
606 score: 100,
607 global_transaction_score_key: "gtsk".to_string(),
608 };
609 let params = tx_key.clone();
610 let empty_params: Option<TxKey> = None;
611
612 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
613 let empty_serialized = serde_urlencoded::to_string(&empty_params).unwrap();
614 assert_eq!(serialized, "txnHash=0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&score=100&globalTransactionScoreKey=gtsk");
615 assert_eq!(empty_serialized, "");
616 }
617
618 #[test]
619 fn test_cache_response_deref() {
620 let uuid = Uuid::new_v4();
621 let response =
622 CacheResponse::unpaginated(BundleList::new(vec![dummy_bundle_with_id(uuid)]));
623
624 assert_eq!(response.bundles.len(), 1);
625 assert_eq!(response.bundles[0].id, uuid);
626 }
627
628 #[test]
629 fn test_cache_response_deref_mut() {
630 let uuid = Uuid::new_v4();
631 let mut response =
632 CacheResponse::unpaginated(BundleList::new(vec![dummy_bundle_with_id(uuid)]));
633
634 response.bundles.clear();
635 assert!(response.bundles.is_empty());
636 }
637}