hypersync_net_types/
transaction.rs

1use crate::{
2    hypersync_net_types_capnp,
3    types::{AnyOf, Sighash},
4    CapnpBuilder, CapnpReader, Selection,
5};
6use anyhow::Context;
7use hypersync_format::{Address, FilterWrapper, Hash};
8use serde::{Deserialize, Serialize};
9
10#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq)]
11#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
12pub struct AuthorizationSelection {
13    /// List of chain ids to match in the transaction authorizationList
14    #[serde(default, skip_serializing_if = "Vec::is_empty")]
15    pub chain_id: Vec<u64>,
16    /// List of addresses to match in the transaction authorizationList
17    #[serde(default, skip_serializing_if = "Vec::is_empty")]
18    pub address: Vec<Address>,
19}
20
21impl AuthorizationSelection {
22    /// Create an authorization selection that matches all authorizations.
23    ///
24    /// This creates an empty selection with no constraints, which will match all authorizations.
25    /// You can then use the builder methods to add specific filtering criteria.
26    pub fn all() -> Self {
27        Default::default()
28    }
29
30    /// Filter authorizations by any of the provided chain IDs.
31    ///
32    /// # Arguments
33    /// * `chain_ids` - An iterable of chain IDs to filter by
34    ///
35    /// # Examples
36    ///
37    /// ```
38    /// use hypersync_net_types::AuthorizationSelection;
39    ///
40    /// // Filter by a single chain ID (Ethereum mainnet)
41    /// let selection = AuthorizationSelection::all()
42    ///     .and_chain_id([1]);
43    ///
44    /// // Filter by multiple chain IDs
45    /// let selection = AuthorizationSelection::all()
46    ///     .and_chain_id([
47    ///         1,      // Ethereum mainnet
48    ///         137,    // Polygon
49    ///         42161,  // Arbitrum One
50    ///     ]);
51    ///
52    /// // Chain with address filter
53    /// let selection = AuthorizationSelection::all()
54    ///     .and_chain_id([1, 137])
55    ///     .and_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
56    /// # Ok::<(), anyhow::Error>(())
57    /// ```
58    pub fn and_chain_id<I>(mut self, chain_ids: I) -> Self
59    where
60        I: IntoIterator<Item = u64>,
61    {
62        self.chain_id = chain_ids.into_iter().collect();
63        self
64    }
65
66    /// Filter authorizations by any of the provided addresses.
67    ///
68    /// This method accepts any iterable of values that can be converted to `Address`.
69    /// Common input types include string slices, byte arrays, and `Address` objects.
70    ///
71    /// # Arguments
72    /// * `addresses` - An iterable of addresses to filter by
73    ///
74    /// # Returns
75    /// * `Ok(Self)` - The updated selection on success
76    /// * `Err(anyhow::Error)` - If any address fails to convert
77    ///
78    /// # Examples
79    ///
80    /// ```
81    /// use hypersync_net_types::AuthorizationSelection;
82    ///
83    /// // Filter by a single address
84    /// let selection = AuthorizationSelection::all()
85    ///     .and_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
86    ///
87    /// // Filter by multiple addresses
88    /// let selection = AuthorizationSelection::all()
89    ///     .and_address([
90    ///         "0xdac17f958d2ee523a2206206994597c13d831ec7", // Address 1
91    ///         "0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567", // Address 2
92    ///     ])?;
93    ///
94    /// // Using byte arrays
95    /// let auth_address = [
96    ///     0xda, 0xc1, 0x7f, 0x95, 0x8d, 0x2e, 0xe5, 0x23, 0xa2, 0x20,
97    ///     0x62, 0x06, 0x99, 0x45, 0x97, 0xc1, 0x3d, 0x83, 0x1e, 0xc7
98    /// ];
99    /// let selection = AuthorizationSelection::all()
100    ///     .and_address([auth_address])?;
101    /// # Ok::<(), anyhow::Error>(())
102    /// ```
103    pub fn and_address<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
104    where
105        I: IntoIterator<Item = A>,
106        A: TryInto<Address>,
107        A::Error: std::error::Error + Send + Sync + 'static,
108    {
109        let mut converted_addresses: Vec<Address> = Vec::new();
110        for (idx, address) in addresses.into_iter().enumerate() {
111            converted_addresses.push(
112                address
113                    .try_into()
114                    .with_context(|| format!("invalid authorization address at position {idx}"))?,
115            );
116        }
117        self.address = converted_addresses;
118        Ok(self)
119    }
120}
121
122pub type TransactionSelection = Selection<TransactionFilter>;
123
124impl From<TransactionFilter> for AnyOf<TransactionFilter> {
125    fn from(filter: TransactionFilter) -> Self {
126        Self::new(filter)
127    }
128}
129
130#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq)]
131#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
132pub struct TransactionFilter {
133    /// Address the transaction should originate from. If transaction.from matches any of these, the transaction
134    /// will be returned. Keep in mind that this has an and relationship with to filter, so each transaction should
135    /// match both of them. Empty means match all.
136    #[serde(default, skip_serializing_if = "Vec::is_empty")]
137    pub from: Vec<Address>,
138    #[serde(default, skip_serializing_if = "Option::is_none")]
139    pub from_filter: Option<FilterWrapper>,
140    /// Address the transaction should go to. If transaction.to matches any of these, the transaction will
141    /// be returned. Keep in mind that this has an and relationship with from filter, so each transaction should
142    /// match both of them. Empty means match all.
143    #[serde(default, skip_serializing_if = "Vec::is_empty")]
144    pub to: Vec<Address>,
145    #[serde(default, skip_serializing_if = "Option::is_none")]
146    pub to_filter: Option<FilterWrapper>,
147    /// If first 4 bytes of transaction input matches any of these, transaction will be returned. Empty means match all.
148    #[serde(default, skip_serializing_if = "Vec::is_empty")]
149    pub sighash: Vec<Sighash>,
150    /// If transaction.status matches this value, the transaction will be returned.
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub status: Option<u8>,
153    /// If transaction.type matches any of these values, the transaction will be returned
154    #[serde(rename = "type")]
155    #[serde(default, skip_serializing_if = "Vec::is_empty")]
156    pub type_: Vec<u8>,
157    /// If transaction.contract_address matches any of these values, the transaction will be returned.
158    #[serde(default, skip_serializing_if = "Vec::is_empty")]
159    pub contract_address: Vec<Address>,
160    /// Bloom filter to filter by transaction.contract_address field. If the bloom filter contains the hash
161    /// of transaction.contract_address then the transaction will be returned. This field doesn't utilize the server side filtering
162    /// so it should be used alongside some non-probabilistic filters if possible.
163    #[serde(default, skip_serializing_if = "Option::is_none")]
164    pub contract_address_filter: Option<FilterWrapper>,
165    /// If transaction.hash matches any of these values the transaction will be returned.
166    /// empty means match all.
167    #[serde(default, skip_serializing_if = "Vec::is_empty")]
168    pub hash: Vec<Hash>,
169
170    /// List of authorizations from eip-7702 transactions, the query will return transactions that match any of these selections
171    #[serde(default, skip_serializing_if = "Vec::is_empty")]
172    pub authorization_list: Vec<AuthorizationSelection>,
173}
174
175impl TransactionFilter {
176    /// Create a transaction filter that matches all transactions.
177    ///
178    /// This creates an empty filter with no constraints, which will match all transactions.
179    /// You can then use the builder methods to add specific filtering criteria.
180    pub fn all() -> Self {
181        Default::default()
182    }
183
184    /// Combine this filter with another using logical OR.
185    ///
186    /// Creates an `AnyOf` that matches transactions satisfying either this filter or the other filter.
187    /// This allows for fluent chaining of multiple transaction filters with OR semantics.
188    ///
189    /// # Arguments
190    /// * `other` - Another `TransactionFilter` to combine with this one
191    ///
192    /// # Returns
193    /// An `AnyOf<TransactionFilter>` that matches transactions satisfying either filter
194    ///
195    /// # Examples
196    ///
197    /// ```
198    /// use hypersync_net_types::TransactionFilter;
199    ///
200    /// // Match transactions from specific senders OR with specific function signatures
201    /// let filter = TransactionFilter::all()
202    ///     .and_from(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?
203    ///     .or(
204    ///         TransactionFilter::all()
205    ///             .and_sighash(["0xa9059cbb"])? // transfer(address,uint256)
206    ///     );
207    /// # Ok::<(), anyhow::Error>(())
208    /// ```
209    pub fn or(self, other: Self) -> AnyOf<Self> {
210        AnyOf::new(self).or(other)
211    }
212
213    /// Filter transactions by any of the provided sender addresses.
214    ///
215    /// This method accepts any iterable of values that can be converted to `Address`.
216    /// Common input types include string slices, byte arrays, and `Address` objects.
217    ///
218    /// # Arguments
219    /// * `addresses` - An iterable of sender addresses to filter by
220    ///
221    /// # Returns
222    /// * `Ok(Self)` - The updated filter on success
223    /// * `Err(anyhow::Error)` - If any address fails to convert
224    ///
225    /// # Examples
226    ///
227    /// ```
228    /// use hypersync_net_types::TransactionFilter;
229    ///
230    /// // Filter by a single sender address
231    /// let filter = TransactionFilter::all()
232    ///     .and_from(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
233    ///
234    /// // Filter by multiple sender addresses
235    /// let filter = TransactionFilter::all()
236    ///     .and_from([
237    ///         "0xdac17f958d2ee523a2206206994597c13d831ec7", // Address 1
238    ///         "0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567", // Address 2
239    ///     ])?;
240    ///
241    /// // Using byte arrays
242    /// let sender_address = [
243    ///     0xda, 0xc1, 0x7f, 0x95, 0x8d, 0x2e, 0xe5, 0x23, 0xa2, 0x20,
244    ///     0x62, 0x06, 0x99, 0x45, 0x97, 0xc1, 0x3d, 0x83, 0x1e, 0xc7
245    /// ];
246    /// let filter = TransactionFilter::all()
247    ///     .and_from([sender_address])?;
248    /// # Ok::<(), anyhow::Error>(())
249    /// ```
250    pub fn and_from<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
251    where
252        I: IntoIterator<Item = A>,
253        A: TryInto<Address>,
254        A::Error: std::error::Error + Send + Sync + 'static,
255    {
256        let mut converted_addresses: Vec<Address> = Vec::new();
257        for (idx, address) in addresses.into_iter().enumerate() {
258            converted_addresses.push(
259                address
260                    .try_into()
261                    .with_context(|| format!("invalid from address at position {idx}"))?,
262            );
263        }
264        self.from = converted_addresses;
265        Ok(self)
266    }
267
268    /// Filter transactions by any of the provided recipient addresses.
269    ///
270    /// This method accepts any iterable of values that can be converted to `Address`.
271    /// Common input types include string slices, byte arrays, and `Address` objects.
272    ///
273    /// # Arguments
274    /// * `addresses` - An iterable of recipient addresses to filter by
275    ///
276    /// # Returns
277    /// * `Ok(Self)` - The updated filter on success
278    /// * `Err(anyhow::Error)` - If any address fails to convert
279    ///
280    /// # Examples
281    ///
282    /// ```
283    /// use hypersync_net_types::TransactionFilter;
284    ///
285    /// // Filter by a single recipient address
286    /// let filter = TransactionFilter::all()
287    ///     .and_to(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
288    ///
289    /// // Filter by multiple recipient addresses (e.g., popular DeFi contracts)
290    /// let filter = TransactionFilter::all()
291    ///     .and_to([
292    ///         "0xdac17f958d2ee523a2206206994597c13d831ec7", // Contract 1
293    ///         "0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567", // Contract 2
294    ///     ])?;
295    ///
296    /// // Chain with sender filter
297    /// let filter = TransactionFilter::all()
298    ///     .and_from(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?
299    ///     .and_to(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
300    /// # Ok::<(), anyhow::Error>(())
301    /// ```
302    pub fn and_to<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
303    where
304        I: IntoIterator<Item = A>,
305        A: TryInto<Address>,
306        A::Error: std::error::Error + Send + Sync + 'static,
307    {
308        let mut converted_addresses: Vec<Address> = Vec::new();
309        for (idx, address) in addresses.into_iter().enumerate() {
310            converted_addresses.push(
311                address
312                    .try_into()
313                    .with_context(|| format!("invalid to address at position {idx}"))?,
314            );
315        }
316        self.to = converted_addresses;
317        Ok(self)
318    }
319
320    /// Filter transactions by any of the provided function signatures (first 4 bytes of input).
321    ///
322    /// This method accepts any iterable of values that can be converted to `Sighash`.
323    /// Common input types include string slices, byte arrays, and `Sighash` objects.
324    ///
325    /// # Arguments
326    /// * `sighashes` - An iterable of function signatures to filter by
327    ///
328    /// # Returns
329    /// * `Ok(Self)` - The updated filter on success
330    /// * `Err(anyhow::Error)` - If any sighash fails to convert
331    ///
332    /// # Examples
333    ///
334    /// ```
335    /// use hypersync_net_types::TransactionFilter;
336    ///
337    /// // Filter by a single function signature (transfer)
338    /// let filter = TransactionFilter::all()
339    ///     .and_sighash(["0xa9059cbb"])?; // transfer(address,uint256)
340    ///
341    /// // Filter by multiple function signatures
342    /// let filter = TransactionFilter::all()
343    ///     .and_sighash([
344    ///         "0xa9059cbb", // transfer(address,uint256)
345    ///         "0x23b872dd", // transferFrom(address,address,uint256)
346    ///         "0x095ea7b3", // approve(address,uint256)
347    ///     ])?;
348    ///
349    /// // Using byte arrays
350    /// let transfer_sig = [0xa9, 0x05, 0x9c, 0xbb];
351    /// let filter = TransactionFilter::all()
352    ///     .and_sighash([transfer_sig])?;
353    /// # Ok::<(), anyhow::Error>(())
354    /// ```
355    pub fn and_sighash<I, S>(mut self, sighashes: I) -> anyhow::Result<Self>
356    where
357        I: IntoIterator<Item = S>,
358        S: TryInto<Sighash>,
359        S::Error: std::error::Error + Send + Sync + 'static,
360    {
361        let mut converted_sighashes: Vec<Sighash> = Vec::new();
362        for (idx, sighash) in sighashes.into_iter().enumerate() {
363            converted_sighashes.push(
364                sighash
365                    .try_into()
366                    .with_context(|| format!("invalid sighash at position {idx}"))?,
367            );
368        }
369        self.sighash = converted_sighashes;
370        Ok(self)
371    }
372
373    /// Filter transactions by status (success or failure).
374    ///
375    /// # Arguments
376    /// * `status` - The transaction status to filter by (typically 0 for failure, 1 for success)
377    ///
378    /// # Examples
379    ///
380    /// ```
381    /// use hypersync_net_types::TransactionFilter;
382    ///
383    /// // Filter for successful transactions only
384    /// let filter = TransactionFilter::all()
385    ///     .and_status(1);
386    ///
387    /// // Filter for failed transactions only
388    /// let filter = TransactionFilter::all()
389    ///     .and_status(0);
390    ///
391    /// // Chain with other filters
392    /// let filter = TransactionFilter::all()
393    ///     .and_from(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?
394    ///     .and_status(1); // Only successful transactions from this address
395    /// # Ok::<(), anyhow::Error>(())
396    /// ```
397    pub fn and_status(mut self, status: u8) -> Self {
398        self.status = Some(status);
399        self
400    }
401
402    /// Filter transactions by any of the provided transaction types.
403    ///
404    /// # Arguments
405    /// * `types` - An iterable of transaction types to filter by
406    ///
407    /// # Examples
408    ///
409    /// ```
410    /// use hypersync_net_types::TransactionFilter;
411    ///
412    /// // Filter for legacy transactions only
413    /// let filter = TransactionFilter::all()
414    ///     .and_type([0]);
415    ///
416    /// // Filter for EIP-1559 transactions only
417    /// let filter = TransactionFilter::all()
418    ///     .and_type([2]);
419    ///
420    /// // Filter for multiple transaction types
421    /// let filter = TransactionFilter::all()
422    ///     .and_type([0, 1, 2]); // Legacy, Access List, and EIP-1559
423    /// ```
424    pub fn and_type<I>(mut self, types: I) -> Self
425    where
426        I: IntoIterator<Item = u8>,
427    {
428        self.type_ = types.into_iter().collect();
429        self
430    }
431
432    /// Filter transactions by any of the provided contract addresses.
433    ///
434    /// This method accepts any iterable of values that can be converted to `Address`.
435    /// Common input types include string slices, byte arrays, and `Address` objects.
436    ///
437    /// # Arguments
438    /// * `addresses` - An iterable of contract addresses to filter by
439    ///
440    /// # Returns
441    /// * `Ok(Self)` - The updated filter on success
442    /// * `Err(anyhow::Error)` - If any address fails to convert
443    ///
444    /// # Examples
445    ///
446    /// ```
447    /// use hypersync_net_types::TransactionFilter;
448    ///
449    /// // Filter by a single contract address
450    /// let filter = TransactionFilter::all()
451    ///     .and_contract_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
452    ///
453    /// // Filter by multiple contract addresses
454    /// let filter = TransactionFilter::all()
455    ///     .and_contract_address([
456    ///         "0xdac17f958d2ee523a2206206994597c13d831ec7", // Contract 1
457    ///         "0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567", // Contract 2
458    ///     ])?;
459    /// # Ok::<(), anyhow::Error>(())
460    /// ```
461    pub fn and_contract_address<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
462    where
463        I: IntoIterator<Item = A>,
464        A: TryInto<Address>,
465        A::Error: std::error::Error + Send + Sync + 'static,
466    {
467        let mut converted_addresses: Vec<Address> = Vec::new();
468        for (idx, address) in addresses.into_iter().enumerate() {
469            converted_addresses.push(
470                address
471                    .try_into()
472                    .with_context(|| format!("invalid contract address at position {idx}"))?,
473            );
474        }
475        self.contract_address = converted_addresses;
476        Ok(self)
477    }
478
479    /// Filter transactions by any of the provided transaction hashes.
480    ///
481    /// This method accepts any iterable of values that can be converted to `Hash`.
482    /// Common input types include string slices, byte arrays, and `Hash` objects.
483    ///
484    /// # Arguments
485    /// * `hashes` - An iterable of transaction hashes to filter by
486    ///
487    /// # Returns
488    /// * `Ok(Self)` - The updated filter on success
489    /// * `Err(anyhow::Error)` - If any hash fails to convert
490    ///
491    /// # Examples
492    ///
493    /// ```
494    /// use hypersync_net_types::TransactionFilter;
495    ///
496    /// // Filter by a single transaction hash
497    /// let filter = TransactionFilter::all()
498    ///     .and_hash(["0x40d008f2a1653f09b7b028d30c7fd1ba7c84900fcfb032040b3eb3d16f84d294"])?;
499    ///
500    /// // Filter by multiple transaction hashes
501    /// let filter = TransactionFilter::all()
502    ///     .and_hash([
503    ///         "0x40d008f2a1653f09b7b028d30c7fd1ba7c84900fcfb032040b3eb3d16f84d294",
504    ///         "0x88e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6",
505    ///     ])?;
506    ///
507    /// // Using byte arrays
508    /// let tx_hash = [
509    ///     0x40, 0xd0, 0x08, 0xf2, 0xa1, 0x65, 0x3f, 0x09, 0xb7, 0xb0, 0x28, 0xd3, 0x0c, 0x7f, 0xd1, 0xba,
510    ///     0x7c, 0x84, 0x90, 0x0f, 0xcf, 0xb0, 0x32, 0x04, 0x0b, 0x3e, 0xb3, 0xd1, 0x6f, 0x84, 0xd2, 0x94
511    /// ];
512    /// let filter = TransactionFilter::all()
513    ///     .and_hash([tx_hash])?;
514    /// # Ok::<(), anyhow::Error>(())
515    /// ```
516    pub fn and_hash<I, H>(mut self, hashes: I) -> anyhow::Result<Self>
517    where
518        I: IntoIterator<Item = H>,
519        H: TryInto<Hash>,
520        H::Error: std::error::Error + Send + Sync + 'static,
521    {
522        let mut converted_hashes: Vec<Hash> = Vec::new();
523        for (idx, hash) in hashes.into_iter().enumerate() {
524            converted_hashes.push(
525                hash.try_into()
526                    .with_context(|| format!("invalid transaction hash at position {idx}"))?,
527            );
528        }
529        self.hash = converted_hashes;
530        Ok(self)
531    }
532
533    /// Filter transactions by any of the provided authorization selections.
534    ///
535    /// This method is used for EIP-7702 transactions that include authorization lists.
536    /// It accepts any iterable of `AuthorizationSelection` objects.
537    ///
538    /// # Arguments
539    /// * `selections` - An iterable of authorization selections to filter by
540    ///
541    /// # Examples
542    ///
543    /// ```
544    /// use hypersync_net_types::{TransactionFilter, AuthorizationSelection};
545    ///
546    /// // Filter by a single authorization selection
547    /// let auth_selection = AuthorizationSelection::all()
548    ///     .and_chain_id([1, 137])
549    ///     .and_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
550    ///
551    /// let filter = TransactionFilter::all()
552    ///     .and_authorization_list([auth_selection])?;
553    ///
554    /// // Filter by multiple authorization selections
555    /// let mainnet_auth = AuthorizationSelection::all()
556    ///     .and_chain_id([1])
557    ///     .and_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?;
558    ///
559    /// let polygon_auth = AuthorizationSelection::all()
560    ///     .and_chain_id([137])
561    ///     .and_address(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?;
562    ///
563    /// let filter = TransactionFilter::all()
564    ///     .and_authorization_list([mainnet_auth, polygon_auth])?;
565    ///
566    /// // Chain with other transaction filters
567    /// let filter = TransactionFilter::all()
568    ///     .and_from(["0xa0b86a33e6c11c8c0c5c0b5e6adee30d1a234567"])?
569    ///     .and_authorization_list([
570    ///         AuthorizationSelection::all()
571    ///             .and_chain_id([1])
572    ///             .and_address(["0xdac17f958d2ee523a2206206994597c13d831ec7"])?
573    ///     ])?;
574    /// # Ok::<(), anyhow::Error>(())
575    /// ```
576    pub fn and_authorization_list<I>(mut self, selections: I) -> anyhow::Result<Self>
577    where
578        I: IntoIterator<Item = AuthorizationSelection>,
579    {
580        self.authorization_list = selections.into_iter().collect();
581        Ok(self)
582    }
583}
584
585impl CapnpBuilder<hypersync_net_types_capnp::authorization_selection::Owned>
586    for AuthorizationSelection
587{
588    fn populate_builder(
589        &self,
590        builder: &mut hypersync_net_types_capnp::authorization_selection::Builder,
591    ) -> Result<(), capnp::Error> {
592        // Set chain ids
593        if !self.chain_id.is_empty() {
594            let mut chain_list = builder.reborrow().init_chain_id(self.chain_id.len() as u32);
595            for (i, chain_id) in self.chain_id.iter().enumerate() {
596                chain_list.set(i as u32, *chain_id);
597            }
598        }
599
600        // Set addresses
601        if !self.address.is_empty() {
602            let mut addr_list = builder.reborrow().init_address(self.address.len() as u32);
603            for (i, addr) in self.address.iter().enumerate() {
604                addr_list.set(i as u32, addr.as_slice());
605            }
606        }
607
608        Ok(())
609    }
610}
611
612impl CapnpReader<hypersync_net_types_capnp::authorization_selection::Owned>
613    for AuthorizationSelection
614{
615    /// Deserialize AuthorizationSelection from Cap'n Proto reader
616    fn from_reader(
617        reader: hypersync_net_types_capnp::authorization_selection::Reader,
618    ) -> Result<Self, capnp::Error> {
619        let mut auth_selection = AuthorizationSelection::default();
620
621        // Parse chain ids
622        if reader.has_chain_id() {
623            let chain_list = reader.get_chain_id()?;
624            for i in 0..chain_list.len() {
625                auth_selection.chain_id.push(chain_list.get(i));
626            }
627        }
628
629        // Parse addresses
630        if reader.has_address() {
631            let addr_list = reader.get_address()?;
632            for i in 0..addr_list.len() {
633                let addr_data = addr_list.get(i)?;
634                if addr_data.len() == 20 {
635                    let mut addr_bytes = [0u8; 20];
636                    addr_bytes.copy_from_slice(addr_data);
637                    auth_selection.address.push(Address::from(addr_bytes));
638                }
639            }
640        }
641
642        Ok(auth_selection)
643    }
644}
645
646impl CapnpBuilder<hypersync_net_types_capnp::transaction_filter::Owned> for TransactionFilter {
647    fn populate_builder(
648        &self,
649        builder: &mut hypersync_net_types_capnp::transaction_filter::Builder,
650    ) -> Result<(), capnp::Error> {
651        // Set from addresses
652        if !self.from.is_empty() {
653            let mut from_list = builder.reborrow().init_from(self.from.len() as u32);
654            for (i, addr) in self.from.iter().enumerate() {
655                from_list.set(i as u32, addr.as_slice());
656            }
657        }
658
659        // Set from filter
660        if let Some(filter) = &self.from_filter {
661            builder.reborrow().set_from_filter(filter.0.as_bytes());
662        }
663
664        // Set to addresses
665        if !self.to.is_empty() {
666            let mut to_list = builder.reborrow().init_to(self.to.len() as u32);
667            for (i, addr) in self.to.iter().enumerate() {
668                to_list.set(i as u32, addr.as_slice());
669            }
670        }
671
672        // Set to filter
673        if let Some(filter) = &self.to_filter {
674            builder.reborrow().set_to_filter(filter.0.as_bytes());
675        }
676
677        // Set sighash
678        if !self.sighash.is_empty() {
679            let mut sighash_list = builder.reborrow().init_sighash(self.sighash.len() as u32);
680            for (i, sighash) in self.sighash.iter().enumerate() {
681                sighash_list.set(i as u32, sighash.as_slice());
682            }
683        }
684
685        // Set status
686        if let Some(status) = self.status {
687            let mut status_builder = builder.reborrow().init_status();
688            status_builder.set_value(status);
689        }
690
691        // Set type
692        if !self.type_.is_empty() {
693            let mut type_list = builder.reborrow().init_type(self.type_.len() as u32);
694            for (i, type_) in self.type_.iter().enumerate() {
695                type_list.set(i as u32, *type_);
696            }
697        }
698
699        // Set contract addresses
700        if !self.contract_address.is_empty() {
701            let mut contract_list = builder
702                .reborrow()
703                .init_contract_address(self.contract_address.len() as u32);
704            for (i, addr) in self.contract_address.iter().enumerate() {
705                contract_list.set(i as u32, addr.as_slice());
706            }
707        }
708
709        // Set contract address filter
710        if let Some(filter) = &self.contract_address_filter {
711            builder
712                .reborrow()
713                .set_contract_address_filter(filter.0.as_bytes());
714        }
715
716        // Set hashes
717        if !self.hash.is_empty() {
718            let mut hash_list = builder.reborrow().init_hash(self.hash.len() as u32);
719            for (i, hash) in self.hash.iter().enumerate() {
720                hash_list.set(i as u32, hash.as_slice());
721            }
722        }
723
724        // Set authorization list
725        if !self.authorization_list.is_empty() {
726            let mut auth_list = builder
727                .reborrow()
728                .init_authorization_list(self.authorization_list.len() as u32);
729            for (i, auth_sel) in self.authorization_list.iter().enumerate() {
730                let mut auth_builder = auth_list.reborrow().get(i as u32);
731                AuthorizationSelection::populate_builder(auth_sel, &mut auth_builder)?;
732            }
733        }
734
735        Ok(())
736    }
737}
738
739impl CapnpReader<hypersync_net_types_capnp::transaction_filter::Owned> for TransactionFilter {
740    /// Deserialize TransactionSelection from Cap'n Proto reader
741    fn from_reader(
742        reader: hypersync_net_types_capnp::transaction_filter::Reader,
743    ) -> Result<Self, capnp::Error> {
744        let mut from = Vec::new();
745
746        // Parse from addresses
747        if reader.has_from() {
748            let from_list = reader.get_from()?;
749            for i in 0..from_list.len() {
750                let addr_data = from_list.get(i)?;
751                if addr_data.len() == 20 {
752                    let mut addr_bytes = [0u8; 20];
753                    addr_bytes.copy_from_slice(addr_data);
754                    from.push(Address::from(addr_bytes));
755                }
756            }
757        }
758
759        let mut from_filter = None;
760
761        // Parse from filter
762        if reader.has_from_filter() {
763            let filter_data = reader.get_from_filter()?;
764            // For now, skip filter deserialization - this would need proper Filter construction
765            let Ok(wrapper) = FilterWrapper::from_bytes(filter_data) else {
766                return Err(capnp::Error::failed("Invalid from filter".to_string()));
767            };
768            from_filter = Some(wrapper);
769        }
770
771        let mut to = Vec::new();
772
773        // Parse to addresses
774        if reader.has_to() {
775            let to_list = reader.get_to()?;
776            for i in 0..to_list.len() {
777                let addr_data = to_list.get(i)?;
778                if addr_data.len() == 20 {
779                    let mut addr_bytes = [0u8; 20];
780                    addr_bytes.copy_from_slice(addr_data);
781                    to.push(Address::from(addr_bytes));
782                }
783            }
784        }
785
786        let mut to_filter = None;
787
788        // Parse to filter
789        if reader.has_to_filter() {
790            let filter_data = reader.get_to_filter()?;
791            let Ok(wrapper) = FilterWrapper::from_bytes(filter_data) else {
792                return Err(capnp::Error::failed("Invalid to filter".to_string()));
793            };
794            to_filter = Some(wrapper);
795        }
796
797        let mut sighash = Vec::new();
798
799        // Parse sighash
800        if reader.has_sighash() {
801            let sighash_list = reader.get_sighash()?;
802            for i in 0..sighash_list.len() {
803                let sighash_data = sighash_list.get(i)?;
804                if sighash_data.len() == 4 {
805                    let mut sighash_bytes = [0u8; 4];
806                    sighash_bytes.copy_from_slice(sighash_data);
807                    sighash.push(Sighash::from(sighash_bytes));
808                }
809            }
810        }
811
812        // Parse status
813        let mut status = None;
814        if reader.has_status() {
815            let status_reader = reader.get_status()?;
816            status = Some(status_reader.get_value());
817        }
818
819        let mut type_ = Vec::new();
820
821        // Parse type
822        if reader.has_type() {
823            let type_list = reader.get_type()?;
824            for i in 0..type_list.len() {
825                type_.push(type_list.get(i));
826            }
827        }
828
829        let mut contract_address = Vec::new();
830        // Parse contract addresses
831        if reader.has_contract_address() {
832            let contract_list = reader.get_contract_address()?;
833            for i in 0..contract_list.len() {
834                let addr_data = contract_list.get(i)?;
835                if addr_data.len() == 20 {
836                    let mut addr_bytes = [0u8; 20];
837                    addr_bytes.copy_from_slice(addr_data);
838                    contract_address.push(Address::from(addr_bytes));
839                }
840            }
841        }
842
843        let mut contract_address_filter = None;
844
845        // Parse contract address filter
846        if reader.has_contract_address_filter() {
847            let filter_data = reader.get_contract_address_filter()?;
848            let Ok(wrapper) = FilterWrapper::from_bytes(filter_data) else {
849                return Err(capnp::Error::failed(
850                    "Invalid contract address filter".to_string(),
851                ));
852            };
853            contract_address_filter = Some(wrapper);
854        }
855
856        let mut hash = Vec::new();
857
858        // Parse hashes
859        if reader.has_hash() {
860            let hash_list = reader.get_hash()?;
861            for i in 0..hash_list.len() {
862                let hash_data = hash_list.get(i)?;
863                if hash_data.len() == 32 {
864                    let mut hash_bytes = [0u8; 32];
865                    hash_bytes.copy_from_slice(hash_data);
866                    hash.push(Hash::from(hash_bytes));
867                }
868            }
869        }
870
871        let mut authorization_list = Vec::new();
872
873        // Parse authorization list
874        if reader.has_authorization_list() {
875            let auth_list = reader.get_authorization_list()?;
876            for i in 0..auth_list.len() {
877                let auth_reader = auth_list.get(i);
878                let auth_selection = AuthorizationSelection::from_reader(auth_reader)?;
879                authorization_list.push(auth_selection);
880            }
881        }
882
883        Ok(Self {
884            from,
885            from_filter,
886            to,
887            to_filter,
888            sighash,
889            status,
890            type_,
891            contract_address,
892            contract_address_filter,
893            hash,
894            authorization_list,
895        })
896    }
897}
898
899#[derive(
900    Debug,
901    Clone,
902    Copy,
903    Serialize,
904    Deserialize,
905    PartialEq,
906    Eq,
907    schemars::JsonSchema,
908    strum_macros::EnumIter,
909    strum_macros::AsRefStr,
910    strum_macros::Display,
911    strum_macros::EnumString,
912)]
913#[serde(rename_all = "snake_case")]
914#[strum(serialize_all = "snake_case")]
915#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
916pub enum TransactionField {
917    // Non-nullable fields (required)
918    BlockHash,
919    BlockNumber,
920    Gas,
921    Hash,
922    Input,
923    Nonce,
924    TransactionIndex,
925    Value,
926    CumulativeGasUsed,
927    EffectiveGasPrice,
928    GasUsed,
929    LogsBloom,
930
931    // Nullable fields (optional)
932    From,
933    GasPrice,
934    To,
935    V,
936    R,
937    S,
938    MaxPriorityFeePerGas,
939    MaxFeePerGas,
940    ChainId,
941    ContractAddress,
942    Type,
943    Root,
944    Status,
945    YParity,
946    AccessList,
947    AuthorizationList,
948    L1Fee,
949    L1GasPrice,
950    L1GasUsed,
951    L1FeeScalar,
952    GasUsedForL1,
953    MaxFeePerBlobGas,
954    BlobVersionedHashes,
955    BlobGasPrice,
956    BlobGasUsed,
957    DepositNonce,
958    DepositReceiptVersion,
959    L1BaseFeeScalar,
960    L1BlobBaseFee,
961    L1BlobBaseFeeScalar,
962    L1BlockNumber,
963    Mint,
964    Sighash,
965    SourceHash,
966}
967
968impl Ord for TransactionField {
969    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
970        self.as_ref().cmp(other.as_ref())
971    }
972}
973
974impl PartialOrd for TransactionField {
975    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
976        Some(self.cmp(other))
977    }
978}
979
980impl TransactionField {
981    pub fn all() -> std::collections::BTreeSet<Self> {
982        use strum::IntoEnumIterator;
983        Self::iter().collect()
984    }
985
986    /// Convert TransactionField to Cap'n Proto enum
987    pub fn to_capnp(&self) -> crate::hypersync_net_types_capnp::TransactionField {
988        match self {
989            TransactionField::BlockHash => {
990                crate::hypersync_net_types_capnp::TransactionField::BlockHash
991            }
992            TransactionField::BlockNumber => {
993                crate::hypersync_net_types_capnp::TransactionField::BlockNumber
994            }
995            TransactionField::Gas => crate::hypersync_net_types_capnp::TransactionField::Gas,
996            TransactionField::Hash => crate::hypersync_net_types_capnp::TransactionField::Hash,
997            TransactionField::Input => crate::hypersync_net_types_capnp::TransactionField::Input,
998            TransactionField::Nonce => crate::hypersync_net_types_capnp::TransactionField::Nonce,
999            TransactionField::TransactionIndex => {
1000                crate::hypersync_net_types_capnp::TransactionField::TransactionIndex
1001            }
1002            TransactionField::Value => crate::hypersync_net_types_capnp::TransactionField::Value,
1003            TransactionField::CumulativeGasUsed => {
1004                crate::hypersync_net_types_capnp::TransactionField::CumulativeGasUsed
1005            }
1006            TransactionField::EffectiveGasPrice => {
1007                crate::hypersync_net_types_capnp::TransactionField::EffectiveGasPrice
1008            }
1009            TransactionField::GasUsed => {
1010                crate::hypersync_net_types_capnp::TransactionField::GasUsed
1011            }
1012            TransactionField::LogsBloom => {
1013                crate::hypersync_net_types_capnp::TransactionField::LogsBloom
1014            }
1015            TransactionField::From => crate::hypersync_net_types_capnp::TransactionField::From,
1016            TransactionField::GasPrice => {
1017                crate::hypersync_net_types_capnp::TransactionField::GasPrice
1018            }
1019            TransactionField::To => crate::hypersync_net_types_capnp::TransactionField::To,
1020            TransactionField::V => crate::hypersync_net_types_capnp::TransactionField::V,
1021            TransactionField::R => crate::hypersync_net_types_capnp::TransactionField::R,
1022            TransactionField::S => crate::hypersync_net_types_capnp::TransactionField::S,
1023            TransactionField::MaxPriorityFeePerGas => {
1024                crate::hypersync_net_types_capnp::TransactionField::MaxPriorityFeePerGas
1025            }
1026            TransactionField::MaxFeePerGas => {
1027                crate::hypersync_net_types_capnp::TransactionField::MaxFeePerGas
1028            }
1029            TransactionField::ChainId => {
1030                crate::hypersync_net_types_capnp::TransactionField::ChainId
1031            }
1032            TransactionField::ContractAddress => {
1033                crate::hypersync_net_types_capnp::TransactionField::ContractAddress
1034            }
1035            TransactionField::Type => crate::hypersync_net_types_capnp::TransactionField::Type,
1036            TransactionField::Root => crate::hypersync_net_types_capnp::TransactionField::Root,
1037            TransactionField::Status => crate::hypersync_net_types_capnp::TransactionField::Status,
1038            TransactionField::YParity => {
1039                crate::hypersync_net_types_capnp::TransactionField::YParity
1040            }
1041            TransactionField::AccessList => {
1042                crate::hypersync_net_types_capnp::TransactionField::AccessList
1043            }
1044            TransactionField::AuthorizationList => {
1045                crate::hypersync_net_types_capnp::TransactionField::AuthorizationList
1046            }
1047            TransactionField::L1Fee => crate::hypersync_net_types_capnp::TransactionField::L1Fee,
1048            TransactionField::L1GasPrice => {
1049                crate::hypersync_net_types_capnp::TransactionField::L1GasPrice
1050            }
1051            TransactionField::L1GasUsed => {
1052                crate::hypersync_net_types_capnp::TransactionField::L1GasUsed
1053            }
1054            TransactionField::L1FeeScalar => {
1055                crate::hypersync_net_types_capnp::TransactionField::L1FeeScalar
1056            }
1057            TransactionField::GasUsedForL1 => {
1058                crate::hypersync_net_types_capnp::TransactionField::GasUsedForL1
1059            }
1060            TransactionField::MaxFeePerBlobGas => {
1061                crate::hypersync_net_types_capnp::TransactionField::MaxFeePerBlobGas
1062            }
1063            TransactionField::BlobVersionedHashes => {
1064                crate::hypersync_net_types_capnp::TransactionField::BlobVersionedHashes
1065            }
1066            TransactionField::BlobGasPrice => {
1067                crate::hypersync_net_types_capnp::TransactionField::BlobGasPrice
1068            }
1069            TransactionField::BlobGasUsed => {
1070                crate::hypersync_net_types_capnp::TransactionField::BlobGasUsed
1071            }
1072            TransactionField::DepositNonce => {
1073                crate::hypersync_net_types_capnp::TransactionField::DepositNonce
1074            }
1075            TransactionField::DepositReceiptVersion => {
1076                crate::hypersync_net_types_capnp::TransactionField::DepositReceiptVersion
1077            }
1078            TransactionField::L1BaseFeeScalar => {
1079                crate::hypersync_net_types_capnp::TransactionField::L1BaseFeeScalar
1080            }
1081            TransactionField::L1BlobBaseFee => {
1082                crate::hypersync_net_types_capnp::TransactionField::L1BlobBaseFee
1083            }
1084            TransactionField::L1BlobBaseFeeScalar => {
1085                crate::hypersync_net_types_capnp::TransactionField::L1BlobBaseFeeScalar
1086            }
1087            TransactionField::L1BlockNumber => {
1088                crate::hypersync_net_types_capnp::TransactionField::L1BlockNumber
1089            }
1090            TransactionField::Mint => crate::hypersync_net_types_capnp::TransactionField::Mint,
1091            TransactionField::Sighash => {
1092                crate::hypersync_net_types_capnp::TransactionField::Sighash
1093            }
1094            TransactionField::SourceHash => {
1095                crate::hypersync_net_types_capnp::TransactionField::SourceHash
1096            }
1097        }
1098    }
1099
1100    /// Convert Cap'n Proto enum to TransactionField
1101    pub fn from_capnp(field: crate::hypersync_net_types_capnp::TransactionField) -> Self {
1102        match field {
1103            crate::hypersync_net_types_capnp::TransactionField::BlockHash => {
1104                TransactionField::BlockHash
1105            }
1106            crate::hypersync_net_types_capnp::TransactionField::BlockNumber => {
1107                TransactionField::BlockNumber
1108            }
1109            crate::hypersync_net_types_capnp::TransactionField::Gas => TransactionField::Gas,
1110            crate::hypersync_net_types_capnp::TransactionField::Hash => TransactionField::Hash,
1111            crate::hypersync_net_types_capnp::TransactionField::Input => TransactionField::Input,
1112            crate::hypersync_net_types_capnp::TransactionField::Nonce => TransactionField::Nonce,
1113            crate::hypersync_net_types_capnp::TransactionField::TransactionIndex => {
1114                TransactionField::TransactionIndex
1115            }
1116            crate::hypersync_net_types_capnp::TransactionField::Value => TransactionField::Value,
1117            crate::hypersync_net_types_capnp::TransactionField::CumulativeGasUsed => {
1118                TransactionField::CumulativeGasUsed
1119            }
1120            crate::hypersync_net_types_capnp::TransactionField::EffectiveGasPrice => {
1121                TransactionField::EffectiveGasPrice
1122            }
1123            crate::hypersync_net_types_capnp::TransactionField::GasUsed => {
1124                TransactionField::GasUsed
1125            }
1126            crate::hypersync_net_types_capnp::TransactionField::LogsBloom => {
1127                TransactionField::LogsBloom
1128            }
1129            crate::hypersync_net_types_capnp::TransactionField::From => TransactionField::From,
1130            crate::hypersync_net_types_capnp::TransactionField::GasPrice => {
1131                TransactionField::GasPrice
1132            }
1133            crate::hypersync_net_types_capnp::TransactionField::To => TransactionField::To,
1134            crate::hypersync_net_types_capnp::TransactionField::V => TransactionField::V,
1135            crate::hypersync_net_types_capnp::TransactionField::R => TransactionField::R,
1136            crate::hypersync_net_types_capnp::TransactionField::S => TransactionField::S,
1137            crate::hypersync_net_types_capnp::TransactionField::MaxPriorityFeePerGas => {
1138                TransactionField::MaxPriorityFeePerGas
1139            }
1140            crate::hypersync_net_types_capnp::TransactionField::MaxFeePerGas => {
1141                TransactionField::MaxFeePerGas
1142            }
1143            crate::hypersync_net_types_capnp::TransactionField::ChainId => {
1144                TransactionField::ChainId
1145            }
1146            crate::hypersync_net_types_capnp::TransactionField::ContractAddress => {
1147                TransactionField::ContractAddress
1148            }
1149            crate::hypersync_net_types_capnp::TransactionField::Type => TransactionField::Type,
1150            crate::hypersync_net_types_capnp::TransactionField::Root => TransactionField::Root,
1151            crate::hypersync_net_types_capnp::TransactionField::Status => TransactionField::Status,
1152            crate::hypersync_net_types_capnp::TransactionField::YParity => {
1153                TransactionField::YParity
1154            }
1155            crate::hypersync_net_types_capnp::TransactionField::AccessList => {
1156                TransactionField::AccessList
1157            }
1158            crate::hypersync_net_types_capnp::TransactionField::AuthorizationList => {
1159                TransactionField::AuthorizationList
1160            }
1161            crate::hypersync_net_types_capnp::TransactionField::L1Fee => TransactionField::L1Fee,
1162            crate::hypersync_net_types_capnp::TransactionField::L1GasPrice => {
1163                TransactionField::L1GasPrice
1164            }
1165            crate::hypersync_net_types_capnp::TransactionField::L1GasUsed => {
1166                TransactionField::L1GasUsed
1167            }
1168            crate::hypersync_net_types_capnp::TransactionField::L1FeeScalar => {
1169                TransactionField::L1FeeScalar
1170            }
1171            crate::hypersync_net_types_capnp::TransactionField::GasUsedForL1 => {
1172                TransactionField::GasUsedForL1
1173            }
1174            crate::hypersync_net_types_capnp::TransactionField::MaxFeePerBlobGas => {
1175                TransactionField::MaxFeePerBlobGas
1176            }
1177            crate::hypersync_net_types_capnp::TransactionField::BlobVersionedHashes => {
1178                TransactionField::BlobVersionedHashes
1179            }
1180            crate::hypersync_net_types_capnp::TransactionField::BlobGasPrice => {
1181                TransactionField::BlobGasPrice
1182            }
1183            crate::hypersync_net_types_capnp::TransactionField::BlobGasUsed => {
1184                TransactionField::BlobGasUsed
1185            }
1186            crate::hypersync_net_types_capnp::TransactionField::DepositNonce => {
1187                TransactionField::DepositNonce
1188            }
1189            crate::hypersync_net_types_capnp::TransactionField::DepositReceiptVersion => {
1190                TransactionField::DepositReceiptVersion
1191            }
1192            crate::hypersync_net_types_capnp::TransactionField::L1BaseFeeScalar => {
1193                TransactionField::L1BaseFeeScalar
1194            }
1195            crate::hypersync_net_types_capnp::TransactionField::L1BlobBaseFee => {
1196                TransactionField::L1BlobBaseFee
1197            }
1198            crate::hypersync_net_types_capnp::TransactionField::L1BlobBaseFeeScalar => {
1199                TransactionField::L1BlobBaseFeeScalar
1200            }
1201            crate::hypersync_net_types_capnp::TransactionField::L1BlockNumber => {
1202                TransactionField::L1BlockNumber
1203            }
1204            crate::hypersync_net_types_capnp::TransactionField::Mint => TransactionField::Mint,
1205            crate::hypersync_net_types_capnp::TransactionField::Sighash => {
1206                TransactionField::Sighash
1207            }
1208            crate::hypersync_net_types_capnp::TransactionField::SourceHash => {
1209                TransactionField::SourceHash
1210            }
1211        }
1212    }
1213}
1214
1215#[cfg(test)]
1216mod tests {
1217    use hypersync_format::Hex;
1218
1219    use super::*;
1220    use crate::{query::tests::test_query_serde, Query};
1221
1222    #[test]
1223    fn test_all_fields_in_schema() {
1224        let schema = hypersync_schema::transaction();
1225        let schema_fields = schema
1226            .fields
1227            .iter()
1228            .map(|f| f.name.clone())
1229            .collect::<std::collections::BTreeSet<_>>();
1230        let all_fields = TransactionField::all()
1231            .into_iter()
1232            .map(|f| f.as_ref().to_string())
1233            .collect::<std::collections::BTreeSet<_>>();
1234        assert_eq!(schema_fields, all_fields);
1235    }
1236
1237    #[test]
1238    fn test_serde_matches_strum() {
1239        for field in TransactionField::all() {
1240            let serialized = serde_json::to_string(&field).unwrap();
1241            let strum = serde_json::to_string(&field.as_ref()).unwrap();
1242            assert_eq!(serialized, strum, "strum value should be the same as serde");
1243        }
1244    }
1245
1246    #[test]
1247    fn test_transaction_filter_serde_with_defaults() {
1248        let transaction_filter = TransactionSelection::default();
1249        let query = Query::new()
1250            .where_transactions(transaction_filter)
1251            .select_transaction_fields(TransactionField::all());
1252
1253        test_query_serde(query, "transaction selection with defaults");
1254    }
1255    #[test]
1256    fn test_transaction_filter_serde_with_explicit_defaults() {
1257        let transaction_filter = TransactionFilter {
1258            from: Vec::default(),
1259            from_filter: Some(FilterWrapper::new(16, 0)),
1260            to: Vec::default(),
1261            to_filter: Some(FilterWrapper::new(16, 0)),
1262            sighash: Vec::default(),
1263            status: Some(u8::default()),
1264            type_: Vec::default(),
1265            contract_address: Vec::default(),
1266            contract_address_filter: Some(FilterWrapper::new(16, 0)),
1267            hash: Vec::default(),
1268            authorization_list: Vec::default(),
1269        };
1270        let query = Query::new()
1271            .where_transactions(transaction_filter)
1272            .select_transaction_fields(TransactionField::all());
1273
1274        test_query_serde(query, "transaction selection with explicit defaults");
1275    }
1276
1277    #[test]
1278    fn test_transaction_filter_serde_with_full_values() {
1279        let transaction_filter = TransactionFilter {
1280            from: vec![Address::decode_hex("0xdadB0d80178819F2319190D340ce9A924f783711").unwrap()],
1281            from_filter: Some(FilterWrapper::new(16, 1)),
1282            to: vec![Address::decode_hex("0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6").unwrap()],
1283            to_filter: Some(FilterWrapper::new(16, 1)),
1284            sighash: vec![Sighash::from([0x12, 0x34, 0x56, 0x78])],
1285            status: Some(1),
1286            type_: vec![2],
1287            contract_address: vec![Address::decode_hex(
1288                "0x1234567890123456789012345678901234567890",
1289            )
1290            .unwrap()],
1291            contract_address_filter: Some(FilterWrapper::new(16, 1)),
1292            hash: vec![Hash::decode_hex(
1293                "0x40d008f2a1653f09b7b028d30c7fd1ba7c84900fcfb032040b3eb3d16f84d294",
1294            )
1295            .unwrap()],
1296            authorization_list: Vec::default(),
1297        };
1298        let query = Query::new()
1299            .where_transactions(transaction_filter)
1300            .select_transaction_fields(TransactionField::all());
1301
1302        test_query_serde(query, "transaction selection with full values");
1303    }
1304
1305    #[test]
1306    fn test_authorization_selection_serde_with_values() {
1307        let auth_selection = AuthorizationSelection {
1308            chain_id: vec![1, 137, 42161],
1309            address: vec![
1310                Address::decode_hex("0xdadB0d80178819F2319190D340ce9A924f783711").unwrap(),
1311            ],
1312        };
1313        let transaction_filter = TransactionFilter {
1314            from: Vec::default(),
1315            from_filter: Some(FilterWrapper::new(16, 0)),
1316            to: Vec::default(),
1317            to_filter: Some(FilterWrapper::new(16, 0)),
1318            sighash: Vec::default(),
1319            status: Some(u8::default()),
1320            type_: Vec::default(),
1321            contract_address: Vec::default(),
1322            contract_address_filter: Some(FilterWrapper::new(16, 0)),
1323            hash: Vec::default(),
1324            authorization_list: vec![auth_selection],
1325        };
1326        let query = Query::new()
1327            .where_transactions(transaction_filter)
1328            .select_transaction_fields(TransactionField::all());
1329
1330        test_query_serde(query, "authorization selection with rest defaults");
1331    }
1332}