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, Clone, Serialize, Deserialize, PartialEq, Eq)]
422#[serde(rename_all = "camelCase")]
423pub struct BundleKey {
424 pub id: Uuid,
426 pub score: u64,
428 pub global_bundle_score_key: String,
430}
431
432#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
434pub struct OrderKey {
435 pub id: B256,
437}
438
439#[deprecated(since = "0.16.0", note = "renamed to `CachedBundle`")]
444pub type TxCacheBundle = CachedBundle;
446
447#[deprecated(since = "0.16.0", note = "renamed to `BundleList`")]
448pub type TxCacheBundlesResponse = BundleList;
450
451#[deprecated(since = "0.16.0", note = "renamed to `BundleResponse`")]
452pub type BundleReceipt = BundleResponse;
454
455#[deprecated(since = "0.16.0", note = "renamed to `BundleResponse`")]
456pub type TxCacheSendBundleResponse = BundleResponse;
458
459#[deprecated(since = "0.16.0", note = "renamed to `TransactionList`")]
460pub type TxCacheTransactionsResponse = TransactionList;
462
463#[deprecated(since = "0.16.0", note = "renamed to `TransactionResponse`")]
464pub type TransactionReceipt = TransactionResponse;
466
467#[deprecated(since = "0.16.0", note = "renamed to `TransactionResponse`")]
468pub type TxCacheSendTransactionResponse = TransactionResponse;
470
471#[deprecated(since = "0.16.0", note = "renamed to `OrderList`")]
472pub type TxCacheOrdersResponse = OrderList;
474
475#[deprecated(since = "0.16.0", note = "renamed to `OrderResponse`")]
476pub type OrderReceipt = OrderResponse;
478
479#[deprecated(since = "0.16.0", note = "renamed to `OrderResponse`")]
480pub type TxCacheSendOrderResponse = OrderResponse;
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486 use std::str::FromStr;
487
488 fn dummy_bundle_with_id(id: Uuid) -> CachedBundle {
489 CachedBundle {
490 id,
491 bundle: SignetEthBundle {
492 bundle: alloy::rpc::types::mev::EthSendBundle {
493 txs: vec![],
494 block_number: 0,
495 min_timestamp: None,
496 max_timestamp: None,
497 reverting_tx_hashes: vec![],
498 replacement_uuid: Some(id.to_string()),
499 dropping_tx_hashes: vec![],
500 refund_percent: None,
501 refund_recipient: None,
502 refund_tx_hashes: vec![],
503 extra_fields: Default::default(),
504 },
505 host_txs: vec![],
506 },
507 }
508 }
509
510 #[test]
511 fn test_unpaginated_cache_response_deser() {
512 let cache_response = CacheResponse::unpaginated(TransactionList { transactions: vec![] });
513 let expected_json = r#"{"transactions":[]}"#;
514 let serialized = serde_json::to_string(&cache_response).unwrap();
515 assert_eq!(serialized, expected_json);
516 let deserialized =
517 serde_json::from_str::<CacheResponse<TransactionList>>(&serialized).unwrap();
518 assert_eq!(deserialized, cache_response);
519 }
520
521 #[test]
522 fn test_paginated_cache_response_deser() {
523 let cache_response = CacheResponse::paginated(
524 TransactionList { transactions: vec![] },
525 TxKey {
526 txn_hash: B256::repeat_byte(0xaa),
527 score: 100,
528 global_transaction_score_key: "gtsk".to_string(),
529 },
530 );
531 let expected_json = r#"{"transactions":[],"nextCursor":{"txnHash":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","score":100,"globalTransactionScoreKey":"gtsk"}}"#;
532 let serialized = serde_json::to_string(&cache_response).unwrap();
533 assert_eq!(serialized, expected_json);
534 let deserialized =
535 serde_json::from_str::<CacheResponse<TransactionList>>(expected_json).unwrap();
536 assert_eq!(deserialized, cache_response);
537 }
538
539 #[test]
542 fn test_backwards_compatibility_cache_response_deser() {
543 let expected_json = r#"{"transactions":[],"nextCursor":{"txnHash":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","score":100,"globalTransactionScoreKey":"gtsk"}}"#;
544 let deserialized = serde_json::from_str::<TransactionList>(expected_json).unwrap();
545 assert_eq!(deserialized, TransactionList { transactions: vec![] });
546 }
547
548 #[test]
551 fn test_backwards_compatibility_cache_bundle_response_deser() {
552 let expected_json = r#"{"bundles":[{"id":"5932d4bb-58d9-41a9-851d-8dd7f04ccc33","bundle":{"txs":[],"blockNumber":"0x0","replacementUuid":"5932d4bb-58d9-41a9-851d-8dd7f04ccc33"}}]}"#;
553 let uuid = Uuid::from_str("5932d4bb-58d9-41a9-851d-8dd7f04ccc33").unwrap();
554
555 let deserialized = serde_json::from_str::<BundleList>(expected_json).unwrap();
556
557 assert_eq!(deserialized, BundleList { bundles: vec![dummy_bundle_with_id(uuid)] });
558 }
559
560 #[test]
563 fn test_backwards_compatibility_cache_order_response_deser() {
564 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"}"#;
565 let _ = serde_json::from_str::<OrderList>(expected_json).unwrap();
566 }
567
568 #[test]
569 fn test_unpaginated_cache_bundle_response_deser() {
570 let cache_response = CacheResponse::unpaginated(BundleList {
571 bundles: vec![dummy_bundle_with_id(
572 Uuid::from_str("5932d4bb-58d9-41a9-851d-8dd7f04ccc33").unwrap(),
573 )],
574 });
575 let expected_json = r#"{"bundles":[{"id":"5932d4bb-58d9-41a9-851d-8dd7f04ccc33","bundle":{"txs":[],"blockNumber":"0x0","replacementUuid":"5932d4bb-58d9-41a9-851d-8dd7f04ccc33"}}]}"#;
576 let serialized = serde_json::to_string(&cache_response).unwrap();
577 assert_eq!(serialized, expected_json);
578 let deserialized =
579 serde_json::from_str::<CacheResponse<BundleList>>(expected_json).unwrap();
580 assert_eq!(deserialized, cache_response);
581 }
582
583 #[test]
584 fn test_paginated_cache_bundle_response_deser() {
585 let uuid = Uuid::from_str("5932d4bb-58d9-41a9-851d-8dd7f04ccc33").unwrap();
586
587 let cache_response = CacheResponse::paginated(
588 BundleList { bundles: vec![dummy_bundle_with_id(uuid)] },
589 BundleKey { id: uuid, score: 100, global_bundle_score_key: "gbsk".to_string() },
590 );
591 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,"globalBundleScoreKey":"gbsk"}}"#;
592 let serialized = serde_json::to_string(&cache_response).unwrap();
593 assert_eq!(serialized, expected_json);
594 let deserialized =
595 serde_json::from_str::<CacheResponse<BundleList>>(expected_json).unwrap();
596 assert_eq!(deserialized, cache_response);
597 }
598
599 #[test]
600 fn test_pagination_params_simple_deser() {
601 let tx_key = TxKey {
602 txn_hash: B256::repeat_byte(0xaa),
603 score: 100,
604 global_transaction_score_key: "gtsk".to_string(),
605 };
606 let params = tx_key.clone();
607 let empty_params: Option<TxKey> = None;
608
609 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
610 let empty_serialized = serde_urlencoded::to_string(&empty_params).unwrap();
611 assert_eq!(serialized, "txnHash=0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&score=100&globalTransactionScoreKey=gtsk");
612 assert_eq!(empty_serialized, "");
613 }
614
615 #[test]
616 fn test_cache_response_deref() {
617 let uuid = Uuid::new_v4();
618 let response =
619 CacheResponse::unpaginated(BundleList::new(vec![dummy_bundle_with_id(uuid)]));
620
621 assert_eq!(response.bundles.len(), 1);
622 assert_eq!(response.bundles[0].id, uuid);
623 }
624
625 #[test]
626 fn test_cache_response_deref_mut() {
627 let uuid = Uuid::new_v4();
628 let mut response =
629 CacheResponse::unpaginated(BundleList::new(vec![dummy_bundle_with_id(uuid)]));
630
631 response.bundles.clear();
632 assert!(response.bundles.is_empty());
633 }
634}