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