1use alloy::{consensus::TxEnvelope, primitives::B256};
3use serde::{de::DeserializeOwned, Deserialize, Serialize};
4use signet_bundle::SignetEthBundle;
5use signet_types::SignedOrder;
6use uuid::Uuid;
7
8pub trait CacheObject {
10 type Key: Serialize + DeserializeOwned;
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
16#[serde(rename_all = "camelCase")]
17pub struct CacheResponse<T: CacheObject> {
18 #[serde(flatten)]
20 inner: T,
21 #[serde(default, skip_serializing_if = "Option::is_none")]
23 next_cursor: Option<T::Key>,
24}
25
26impl<T: CacheObject> CacheObject for CacheResponse<T> {
27 type Key = T::Key;
28}
29
30impl<T: CacheObject> CacheResponse<T> {
31 pub const fn paginated(inner: T, pagination: T::Key) -> Self {
33 Self { inner, next_cursor: Some(pagination) }
34 }
35
36 pub const fn unpaginated(inner: T) -> Self {
38 Self { inner, next_cursor: None }
39 }
40
41 pub const fn inner(&self) -> &T {
43 match self {
44 Self { inner, .. } => inner,
45 }
46 }
47
48 pub const fn inner_mut(&mut self) -> &mut T {
50 match self {
51 Self { inner, .. } => inner,
52 }
53 }
54
55 pub const fn next_cursor(&self) -> Option<&T::Key> {
57 match self {
58 Self { next_cursor, .. } => next_cursor.as_ref(),
59 }
60 }
61
62 pub const fn has_more(&self) -> bool {
64 self.next_cursor().is_some()
65 }
66
67 pub const fn is_paginated(&self) -> bool {
69 self.next_cursor.is_some()
70 }
71
72 pub const fn is_unpaginated(&self) -> bool {
74 self.next_cursor.is_none()
75 }
76
77 pub fn into_inner(self) -> T {
79 match self {
80 Self { inner, .. } => inner,
81 }
82 }
83
84 pub fn into_parts(self) -> (T, Option<T::Key>) {
86 match self {
87 Self { inner, next_cursor } => (inner, next_cursor),
88 }
89 }
90
91 pub fn into_next_cursor(self) -> Option<T::Key> {
93 self.into_parts().1
94 }
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
100pub struct TxCacheBundle {
101 pub id: uuid::Uuid,
103 pub bundle: SignetEthBundle,
105}
106
107impl TxCacheBundle {
108 pub const fn new(bundle: SignetEthBundle, id: uuid::Uuid) -> Self {
110 Self { id, bundle }
111 }
112
113 #[deprecated = "Use `Self::new` instead"]
115 pub const fn from_bundle_and_id(bundle: SignetEthBundle, id: uuid::Uuid) -> Self {
116 Self::new(bundle, id)
117 }
118
119 pub fn into_bundle(self) -> SignetEthBundle {
121 self.bundle
122 }
123
124 pub fn into_id(self) -> uuid::Uuid {
126 self.id
127 }
128
129 pub const fn id(&self) -> uuid::Uuid {
131 self.id
132 }
133
134 pub const fn bundle(&self) -> &SignetEthBundle {
136 &self.bundle
137 }
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
142pub struct TxCacheBundleResponse {
143 pub bundle: TxCacheBundle,
145}
146
147impl From<TxCacheBundle> for TxCacheBundleResponse {
148 fn from(bundle: TxCacheBundle) -> Self {
149 Self { bundle }
150 }
151}
152
153impl From<TxCacheBundleResponse> for TxCacheBundle {
154 fn from(response: TxCacheBundleResponse) -> Self {
155 response.bundle
156 }
157}
158
159impl CacheObject for TxCacheBundleResponse {
160 type Key = BundleKey;
161}
162
163impl TxCacheBundleResponse {
164 pub const fn new(bundle: TxCacheBundle) -> Self {
166 Self { bundle }
167 }
168
169 #[deprecated = "Use `From::from` instead, `Self::new` in const contexts"]
171 pub const fn from_bundle(bundle: TxCacheBundle) -> Self {
172 Self::new(bundle)
173 }
174
175 #[deprecated = "Use `this.bundle` instead."]
177 pub fn into_bundle(self) -> TxCacheBundle {
178 self.bundle
179 }
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
184pub struct TxCacheBundlesResponse {
185 pub bundles: Vec<TxCacheBundle>,
187}
188
189impl From<Vec<TxCacheBundle>> for TxCacheBundlesResponse {
190 fn from(bundles: Vec<TxCacheBundle>) -> Self {
191 Self { bundles }
192 }
193}
194
195impl From<TxCacheBundlesResponse> for Vec<TxCacheBundle> {
196 fn from(response: TxCacheBundlesResponse) -> Self {
197 response.bundles
198 }
199}
200
201impl CacheObject for TxCacheBundlesResponse {
202 type Key = BundleKey;
203}
204
205impl TxCacheBundlesResponse {
206 pub const fn new(bundles: Vec<TxCacheBundle>) -> Self {
208 Self { bundles }
209 }
210
211 #[deprecated = "Use `From::from` instead, `Self::new` in const contexts"]
213 pub const fn from_bundles(bundles: Vec<TxCacheBundle>) -> Self {
214 Self { bundles }
215 }
216
217 #[deprecated = "Use `this.bundles` instead."]
219 pub fn into_bundles(self) -> Vec<TxCacheBundle> {
220 self.bundles
221 }
222
223 pub fn is_empty(&self) -> bool {
225 self.bundles.is_empty()
226 }
227}
228
229#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
231pub struct TxCacheSendBundleResponse {
232 pub id: uuid::Uuid,
234}
235
236impl TxCacheSendBundleResponse {
237 pub const fn new(id: uuid::Uuid) -> Self {
239 Self { id }
240 }
241}
242
243impl From<uuid::Uuid> for TxCacheSendBundleResponse {
244 fn from(id: uuid::Uuid) -> Self {
245 Self { id }
246 }
247}
248
249impl From<TxCacheSendBundleResponse> for uuid::Uuid {
250 fn from(response: TxCacheSendBundleResponse) -> Self {
251 response.id
252 }
253}
254
255impl CacheObject for TxCacheSendBundleResponse {
256 type Key = BundleKey;
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
261pub struct TxCacheTransactionsResponse {
262 pub transactions: Vec<TxEnvelope>,
264}
265
266impl From<Vec<TxEnvelope>> for TxCacheTransactionsResponse {
267 fn from(transactions: Vec<TxEnvelope>) -> Self {
268 Self { transactions }
269 }
270}
271
272impl From<TxCacheTransactionsResponse> for Vec<TxEnvelope> {
273 fn from(response: TxCacheTransactionsResponse) -> Self {
274 response.transactions
275 }
276}
277
278impl CacheObject for TxCacheTransactionsResponse {
279 type Key = TxKey;
280}
281
282impl TxCacheTransactionsResponse {
283 pub const fn new(transactions: Vec<TxEnvelope>) -> Self {
285 Self { transactions }
286 }
287
288 #[deprecated = "Use `From::from` instead, or `Self::new` in const contexts"]
290 pub const fn from_transactions(transactions: Vec<TxEnvelope>) -> Self {
291 Self { transactions }
292 }
293
294 #[deprecated = "Use `this.transactions` instead."]
296 pub fn into_transactions(self) -> Vec<TxEnvelope> {
297 self.transactions
298 }
299
300 pub fn is_empty(&self) -> bool {
302 self.transactions.is_empty()
303 }
304}
305
306#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
308pub struct TxCacheSendTransactionResponse {
309 pub tx_hash: B256,
311}
312
313impl From<B256> for TxCacheSendTransactionResponse {
314 fn from(tx_hash: B256) -> Self {
315 Self { tx_hash }
316 }
317}
318
319impl From<TxCacheSendTransactionResponse> for B256 {
320 fn from(response: TxCacheSendTransactionResponse) -> Self {
321 response.tx_hash
322 }
323}
324
325impl CacheObject for TxCacheSendTransactionResponse {
326 type Key = TxKey;
327}
328
329impl TxCacheSendTransactionResponse {
330 pub const fn new(tx_hash: B256) -> Self {
332 Self { tx_hash }
333 }
334
335 #[deprecated = "Use `From::from` instead, or `Self::new` in const contexts"]
337 pub const fn from_tx_hash(tx_hash: B256) -> Self {
338 Self { tx_hash }
339 }
340
341 #[deprecated = "Use `this.tx_hash` instead."]
343 pub const fn into_tx_hash(self) -> B256 {
344 self.tx_hash
345 }
346}
347
348#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
350pub struct TxCacheOrdersResponse {
351 pub orders: Vec<SignedOrder>,
353}
354
355impl From<Vec<SignedOrder>> for TxCacheOrdersResponse {
356 fn from(orders: Vec<SignedOrder>) -> Self {
357 Self { orders }
358 }
359}
360
361impl From<TxCacheOrdersResponse> for Vec<SignedOrder> {
362 fn from(response: TxCacheOrdersResponse) -> Self {
363 response.orders
364 }
365}
366
367impl CacheObject for TxCacheOrdersResponse {
368 type Key = OrderKey;
369}
370
371impl TxCacheOrdersResponse {
372 pub const fn new(orders: Vec<SignedOrder>) -> Self {
374 Self { orders }
375 }
376
377 #[deprecated = "Use `From::from` instead, `Self::new` in const contexts"]
379 pub const fn from_orders(orders: Vec<SignedOrder>) -> Self {
380 Self { orders }
381 }
382
383 #[deprecated = "Use `this.orders` instead."]
385 pub fn into_orders(self) -> Vec<SignedOrder> {
386 self.orders
387 }
388}
389
390#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
392pub struct TxCacheSendOrderResponse {
393 pub id: B256,
395}
396
397impl From<B256> for TxCacheSendOrderResponse {
398 fn from(id: B256) -> Self {
399 Self { id }
400 }
401}
402
403impl From<TxCacheSendOrderResponse> for B256 {
404 fn from(response: TxCacheSendOrderResponse) -> Self {
405 response.id
406 }
407}
408
409impl CacheObject for TxCacheSendOrderResponse {
410 type Key = OrderKey;
411}
412
413impl TxCacheSendOrderResponse {
414 pub const fn new(id: B256) -> Self {
416 Self { id }
417 }
418}
419
420#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
422#[serde(rename_all = "camelCase")]
423pub struct TxKey {
424 pub txn_hash: B256,
426 pub score: u64,
428 pub global_transaction_score_key: String,
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
434#[serde(rename_all = "camelCase")]
435pub struct BundleKey {
436 pub id: Uuid,
438 pub score: u64,
440 pub global_bundle_score_key: String,
442}
443
444#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
446pub struct OrderKey {
447 pub id: B256,
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454 use std::str::FromStr;
455
456 fn dummy_bundle_with_id(id: Uuid) -> TxCacheBundle {
457 TxCacheBundle {
458 id,
459 bundle: SignetEthBundle {
460 bundle: alloy::rpc::types::mev::EthSendBundle {
461 txs: vec![],
462 block_number: 0,
463 min_timestamp: None,
464 max_timestamp: None,
465 reverting_tx_hashes: vec![],
466 replacement_uuid: Some(id.to_string()),
467 dropping_tx_hashes: vec![],
468 refund_percent: None,
469 refund_recipient: None,
470 refund_tx_hashes: vec![],
471 extra_fields: Default::default(),
472 },
473 host_txs: vec![],
474 },
475 }
476 }
477
478 #[test]
479 fn test_unpaginated_cache_response_deser() {
480 let cache_response =
481 CacheResponse::unpaginated(TxCacheTransactionsResponse { transactions: vec![] });
482 let expected_json = r#"{"transactions":[]}"#;
483 let serialized = serde_json::to_string(&cache_response).unwrap();
484 assert_eq!(serialized, expected_json);
485 let deserialized =
486 serde_json::from_str::<CacheResponse<TxCacheTransactionsResponse>>(&serialized)
487 .unwrap();
488 assert_eq!(deserialized, cache_response);
489 }
490
491 #[test]
492 fn test_paginated_cache_response_deser() {
493 let cache_response = CacheResponse::paginated(
494 TxCacheTransactionsResponse { transactions: vec![] },
495 TxKey {
496 txn_hash: B256::repeat_byte(0xaa),
497 score: 100,
498 global_transaction_score_key: "gtsk".to_string(),
499 },
500 );
501 let expected_json = r#"{"transactions":[],"nextCursor":{"txnHash":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","score":100,"globalTransactionScoreKey":"gtsk"}}"#;
502 let serialized = serde_json::to_string(&cache_response).unwrap();
503 assert_eq!(serialized, expected_json);
504 let deserialized =
505 serde_json::from_str::<CacheResponse<TxCacheTransactionsResponse>>(expected_json)
506 .unwrap();
507 assert_eq!(deserialized, cache_response);
508 }
509
510 #[test]
513 fn test_backwards_compatibility_cache_response_deser() {
514 let expected_json = r#"{"transactions":[],"nextCursor":{"txnHash":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","score":100,"globalTransactionScoreKey":"gtsk"}}"#;
515 let deserialized =
516 serde_json::from_str::<TxCacheTransactionsResponse>(expected_json).unwrap();
517 assert_eq!(deserialized, TxCacheTransactionsResponse { transactions: vec![] });
518 }
519
520 #[test]
523 fn test_backwards_compatibility_cache_bundle_response_deser() {
524 let expected_json = r#"{"bundles":[{"id":"5932d4bb-58d9-41a9-851d-8dd7f04ccc33","bundle":{"txs":[],"blockNumber":"0x0","replacementUuid":"5932d4bb-58d9-41a9-851d-8dd7f04ccc33"}}]}"#;
525 let uuid = Uuid::from_str("5932d4bb-58d9-41a9-851d-8dd7f04ccc33").unwrap();
526
527 let deserialized = serde_json::from_str::<TxCacheBundlesResponse>(expected_json).unwrap();
528
529 assert_eq!(
530 deserialized,
531 TxCacheBundlesResponse { bundles: vec![dummy_bundle_with_id(uuid)] }
532 );
533 }
534
535 #[test]
538 fn test_backwards_compatibility_cache_order_response_deser() {
539 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"}"#;
540 let _ = serde_json::from_str::<TxCacheOrdersResponse>(expected_json).unwrap();
541 }
542
543 #[test]
544 fn test_unpaginated_cache_bundle_response_deser() {
545 let cache_response = CacheResponse::unpaginated(TxCacheBundlesResponse {
546 bundles: vec![dummy_bundle_with_id(
547 Uuid::from_str("5932d4bb-58d9-41a9-851d-8dd7f04ccc33").unwrap(),
548 )],
549 });
550 let expected_json = r#"{"bundles":[{"id":"5932d4bb-58d9-41a9-851d-8dd7f04ccc33","bundle":{"txs":[],"blockNumber":"0x0","replacementUuid":"5932d4bb-58d9-41a9-851d-8dd7f04ccc33"}}]}"#;
551 let serialized = serde_json::to_string(&cache_response).unwrap();
552 assert_eq!(serialized, expected_json);
553 let deserialized =
554 serde_json::from_str::<CacheResponse<TxCacheBundlesResponse>>(expected_json).unwrap();
555 assert_eq!(deserialized, cache_response);
556 }
557
558 #[test]
559 fn test_paginated_cache_bundle_response_deser() {
560 let uuid = Uuid::from_str("5932d4bb-58d9-41a9-851d-8dd7f04ccc33").unwrap();
561
562 let cache_response = CacheResponse::paginated(
563 TxCacheBundlesResponse { bundles: vec![dummy_bundle_with_id(uuid)] },
564 BundleKey { id: uuid, score: 100, global_bundle_score_key: "gbsk".to_string() },
565 );
566 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"}}"#;
567 let serialized = serde_json::to_string(&cache_response).unwrap();
568 assert_eq!(serialized, expected_json);
569 let deserialized =
570 serde_json::from_str::<CacheResponse<TxCacheBundlesResponse>>(expected_json).unwrap();
571 assert_eq!(deserialized, cache_response);
572 }
573
574 #[test]
575 fn test_pagination_params_simple_deser() {
576 let tx_key = TxKey {
577 txn_hash: B256::repeat_byte(0xaa),
578 score: 100,
579 global_transaction_score_key: "gtsk".to_string(),
580 };
581 let params = tx_key.clone();
582 let empty_params: Option<TxKey> = None;
583
584 let serialized = serde_urlencoded::to_string(¶ms).unwrap();
585 let empty_serialized = serde_urlencoded::to_string(&empty_params).unwrap();
586 assert_eq!(serialized, "txnHash=0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&score=100&globalTransactionScoreKey=gtsk");
587 assert_eq!(empty_serialized, "");
588 }
589}