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    pub const fn is_nullable(&self) -> bool {
987        match self {
988            TransactionField::From
989            | TransactionField::GasPrice
990            | TransactionField::To
991            | TransactionField::V
992            | TransactionField::R
993            | TransactionField::S
994            | TransactionField::MaxPriorityFeePerGas
995            | TransactionField::MaxFeePerGas
996            | TransactionField::ChainId
997            | TransactionField::ContractAddress
998            | TransactionField::Type
999            | TransactionField::Root
1000            | TransactionField::Status
1001            | TransactionField::Sighash
1002            | TransactionField::YParity
1003            | TransactionField::AccessList
1004            | TransactionField::AuthorizationList
1005            | TransactionField::L1Fee
1006            | TransactionField::L1GasPrice
1007            | TransactionField::L1GasUsed
1008            | TransactionField::L1FeeScalar
1009            | TransactionField::GasUsedForL1
1010            | TransactionField::MaxFeePerBlobGas
1011            | TransactionField::BlobVersionedHashes
1012            | TransactionField::DepositNonce
1013            | TransactionField::BlobGasPrice
1014            | TransactionField::DepositReceiptVersion
1015            | TransactionField::BlobGasUsed
1016            | TransactionField::L1BaseFeeScalar
1017            | TransactionField::L1BlobBaseFee
1018            | TransactionField::L1BlobBaseFeeScalar
1019            | TransactionField::L1BlockNumber
1020            | TransactionField::Mint
1021            | TransactionField::SourceHash => true,
1022            TransactionField::BlockHash
1023            | TransactionField::BlockNumber
1024            | TransactionField::Gas
1025            | TransactionField::Hash
1026            | TransactionField::Input
1027            | TransactionField::Nonce
1028            | TransactionField::TransactionIndex
1029            | TransactionField::Value
1030            | TransactionField::CumulativeGasUsed
1031            | TransactionField::EffectiveGasPrice
1032            | TransactionField::GasUsed
1033            | TransactionField::LogsBloom => false,
1034        }
1035    }
1036
1037    /// Convert TransactionField to Cap'n Proto enum
1038    pub fn to_capnp(&self) -> crate::hypersync_net_types_capnp::TransactionField {
1039        match self {
1040            TransactionField::BlockHash => {
1041                crate::hypersync_net_types_capnp::TransactionField::BlockHash
1042            }
1043            TransactionField::BlockNumber => {
1044                crate::hypersync_net_types_capnp::TransactionField::BlockNumber
1045            }
1046            TransactionField::Gas => crate::hypersync_net_types_capnp::TransactionField::Gas,
1047            TransactionField::Hash => crate::hypersync_net_types_capnp::TransactionField::Hash,
1048            TransactionField::Input => crate::hypersync_net_types_capnp::TransactionField::Input,
1049            TransactionField::Nonce => crate::hypersync_net_types_capnp::TransactionField::Nonce,
1050            TransactionField::TransactionIndex => {
1051                crate::hypersync_net_types_capnp::TransactionField::TransactionIndex
1052            }
1053            TransactionField::Value => crate::hypersync_net_types_capnp::TransactionField::Value,
1054            TransactionField::CumulativeGasUsed => {
1055                crate::hypersync_net_types_capnp::TransactionField::CumulativeGasUsed
1056            }
1057            TransactionField::EffectiveGasPrice => {
1058                crate::hypersync_net_types_capnp::TransactionField::EffectiveGasPrice
1059            }
1060            TransactionField::GasUsed => {
1061                crate::hypersync_net_types_capnp::TransactionField::GasUsed
1062            }
1063            TransactionField::LogsBloom => {
1064                crate::hypersync_net_types_capnp::TransactionField::LogsBloom
1065            }
1066            TransactionField::From => crate::hypersync_net_types_capnp::TransactionField::From,
1067            TransactionField::GasPrice => {
1068                crate::hypersync_net_types_capnp::TransactionField::GasPrice
1069            }
1070            TransactionField::To => crate::hypersync_net_types_capnp::TransactionField::To,
1071            TransactionField::V => crate::hypersync_net_types_capnp::TransactionField::V,
1072            TransactionField::R => crate::hypersync_net_types_capnp::TransactionField::R,
1073            TransactionField::S => crate::hypersync_net_types_capnp::TransactionField::S,
1074            TransactionField::MaxPriorityFeePerGas => {
1075                crate::hypersync_net_types_capnp::TransactionField::MaxPriorityFeePerGas
1076            }
1077            TransactionField::MaxFeePerGas => {
1078                crate::hypersync_net_types_capnp::TransactionField::MaxFeePerGas
1079            }
1080            TransactionField::ChainId => {
1081                crate::hypersync_net_types_capnp::TransactionField::ChainId
1082            }
1083            TransactionField::ContractAddress => {
1084                crate::hypersync_net_types_capnp::TransactionField::ContractAddress
1085            }
1086            TransactionField::Type => crate::hypersync_net_types_capnp::TransactionField::Type,
1087            TransactionField::Root => crate::hypersync_net_types_capnp::TransactionField::Root,
1088            TransactionField::Status => crate::hypersync_net_types_capnp::TransactionField::Status,
1089            TransactionField::YParity => {
1090                crate::hypersync_net_types_capnp::TransactionField::YParity
1091            }
1092            TransactionField::AccessList => {
1093                crate::hypersync_net_types_capnp::TransactionField::AccessList
1094            }
1095            TransactionField::AuthorizationList => {
1096                crate::hypersync_net_types_capnp::TransactionField::AuthorizationList
1097            }
1098            TransactionField::L1Fee => crate::hypersync_net_types_capnp::TransactionField::L1Fee,
1099            TransactionField::L1GasPrice => {
1100                crate::hypersync_net_types_capnp::TransactionField::L1GasPrice
1101            }
1102            TransactionField::L1GasUsed => {
1103                crate::hypersync_net_types_capnp::TransactionField::L1GasUsed
1104            }
1105            TransactionField::L1FeeScalar => {
1106                crate::hypersync_net_types_capnp::TransactionField::L1FeeScalar
1107            }
1108            TransactionField::GasUsedForL1 => {
1109                crate::hypersync_net_types_capnp::TransactionField::GasUsedForL1
1110            }
1111            TransactionField::MaxFeePerBlobGas => {
1112                crate::hypersync_net_types_capnp::TransactionField::MaxFeePerBlobGas
1113            }
1114            TransactionField::BlobVersionedHashes => {
1115                crate::hypersync_net_types_capnp::TransactionField::BlobVersionedHashes
1116            }
1117            TransactionField::BlobGasPrice => {
1118                crate::hypersync_net_types_capnp::TransactionField::BlobGasPrice
1119            }
1120            TransactionField::BlobGasUsed => {
1121                crate::hypersync_net_types_capnp::TransactionField::BlobGasUsed
1122            }
1123            TransactionField::DepositNonce => {
1124                crate::hypersync_net_types_capnp::TransactionField::DepositNonce
1125            }
1126            TransactionField::DepositReceiptVersion => {
1127                crate::hypersync_net_types_capnp::TransactionField::DepositReceiptVersion
1128            }
1129            TransactionField::L1BaseFeeScalar => {
1130                crate::hypersync_net_types_capnp::TransactionField::L1BaseFeeScalar
1131            }
1132            TransactionField::L1BlobBaseFee => {
1133                crate::hypersync_net_types_capnp::TransactionField::L1BlobBaseFee
1134            }
1135            TransactionField::L1BlobBaseFeeScalar => {
1136                crate::hypersync_net_types_capnp::TransactionField::L1BlobBaseFeeScalar
1137            }
1138            TransactionField::L1BlockNumber => {
1139                crate::hypersync_net_types_capnp::TransactionField::L1BlockNumber
1140            }
1141            TransactionField::Mint => crate::hypersync_net_types_capnp::TransactionField::Mint,
1142            TransactionField::Sighash => {
1143                crate::hypersync_net_types_capnp::TransactionField::Sighash
1144            }
1145            TransactionField::SourceHash => {
1146                crate::hypersync_net_types_capnp::TransactionField::SourceHash
1147            }
1148        }
1149    }
1150
1151    /// Convert Cap'n Proto enum to TransactionField
1152    pub fn from_capnp(field: crate::hypersync_net_types_capnp::TransactionField) -> Self {
1153        match field {
1154            crate::hypersync_net_types_capnp::TransactionField::BlockHash => {
1155                TransactionField::BlockHash
1156            }
1157            crate::hypersync_net_types_capnp::TransactionField::BlockNumber => {
1158                TransactionField::BlockNumber
1159            }
1160            crate::hypersync_net_types_capnp::TransactionField::Gas => TransactionField::Gas,
1161            crate::hypersync_net_types_capnp::TransactionField::Hash => TransactionField::Hash,
1162            crate::hypersync_net_types_capnp::TransactionField::Input => TransactionField::Input,
1163            crate::hypersync_net_types_capnp::TransactionField::Nonce => TransactionField::Nonce,
1164            crate::hypersync_net_types_capnp::TransactionField::TransactionIndex => {
1165                TransactionField::TransactionIndex
1166            }
1167            crate::hypersync_net_types_capnp::TransactionField::Value => TransactionField::Value,
1168            crate::hypersync_net_types_capnp::TransactionField::CumulativeGasUsed => {
1169                TransactionField::CumulativeGasUsed
1170            }
1171            crate::hypersync_net_types_capnp::TransactionField::EffectiveGasPrice => {
1172                TransactionField::EffectiveGasPrice
1173            }
1174            crate::hypersync_net_types_capnp::TransactionField::GasUsed => {
1175                TransactionField::GasUsed
1176            }
1177            crate::hypersync_net_types_capnp::TransactionField::LogsBloom => {
1178                TransactionField::LogsBloom
1179            }
1180            crate::hypersync_net_types_capnp::TransactionField::From => TransactionField::From,
1181            crate::hypersync_net_types_capnp::TransactionField::GasPrice => {
1182                TransactionField::GasPrice
1183            }
1184            crate::hypersync_net_types_capnp::TransactionField::To => TransactionField::To,
1185            crate::hypersync_net_types_capnp::TransactionField::V => TransactionField::V,
1186            crate::hypersync_net_types_capnp::TransactionField::R => TransactionField::R,
1187            crate::hypersync_net_types_capnp::TransactionField::S => TransactionField::S,
1188            crate::hypersync_net_types_capnp::TransactionField::MaxPriorityFeePerGas => {
1189                TransactionField::MaxPriorityFeePerGas
1190            }
1191            crate::hypersync_net_types_capnp::TransactionField::MaxFeePerGas => {
1192                TransactionField::MaxFeePerGas
1193            }
1194            crate::hypersync_net_types_capnp::TransactionField::ChainId => {
1195                TransactionField::ChainId
1196            }
1197            crate::hypersync_net_types_capnp::TransactionField::ContractAddress => {
1198                TransactionField::ContractAddress
1199            }
1200            crate::hypersync_net_types_capnp::TransactionField::Type => TransactionField::Type,
1201            crate::hypersync_net_types_capnp::TransactionField::Root => TransactionField::Root,
1202            crate::hypersync_net_types_capnp::TransactionField::Status => TransactionField::Status,
1203            crate::hypersync_net_types_capnp::TransactionField::YParity => {
1204                TransactionField::YParity
1205            }
1206            crate::hypersync_net_types_capnp::TransactionField::AccessList => {
1207                TransactionField::AccessList
1208            }
1209            crate::hypersync_net_types_capnp::TransactionField::AuthorizationList => {
1210                TransactionField::AuthorizationList
1211            }
1212            crate::hypersync_net_types_capnp::TransactionField::L1Fee => TransactionField::L1Fee,
1213            crate::hypersync_net_types_capnp::TransactionField::L1GasPrice => {
1214                TransactionField::L1GasPrice
1215            }
1216            crate::hypersync_net_types_capnp::TransactionField::L1GasUsed => {
1217                TransactionField::L1GasUsed
1218            }
1219            crate::hypersync_net_types_capnp::TransactionField::L1FeeScalar => {
1220                TransactionField::L1FeeScalar
1221            }
1222            crate::hypersync_net_types_capnp::TransactionField::GasUsedForL1 => {
1223                TransactionField::GasUsedForL1
1224            }
1225            crate::hypersync_net_types_capnp::TransactionField::MaxFeePerBlobGas => {
1226                TransactionField::MaxFeePerBlobGas
1227            }
1228            crate::hypersync_net_types_capnp::TransactionField::BlobVersionedHashes => {
1229                TransactionField::BlobVersionedHashes
1230            }
1231            crate::hypersync_net_types_capnp::TransactionField::BlobGasPrice => {
1232                TransactionField::BlobGasPrice
1233            }
1234            crate::hypersync_net_types_capnp::TransactionField::BlobGasUsed => {
1235                TransactionField::BlobGasUsed
1236            }
1237            crate::hypersync_net_types_capnp::TransactionField::DepositNonce => {
1238                TransactionField::DepositNonce
1239            }
1240            crate::hypersync_net_types_capnp::TransactionField::DepositReceiptVersion => {
1241                TransactionField::DepositReceiptVersion
1242            }
1243            crate::hypersync_net_types_capnp::TransactionField::L1BaseFeeScalar => {
1244                TransactionField::L1BaseFeeScalar
1245            }
1246            crate::hypersync_net_types_capnp::TransactionField::L1BlobBaseFee => {
1247                TransactionField::L1BlobBaseFee
1248            }
1249            crate::hypersync_net_types_capnp::TransactionField::L1BlobBaseFeeScalar => {
1250                TransactionField::L1BlobBaseFeeScalar
1251            }
1252            crate::hypersync_net_types_capnp::TransactionField::L1BlockNumber => {
1253                TransactionField::L1BlockNumber
1254            }
1255            crate::hypersync_net_types_capnp::TransactionField::Mint => TransactionField::Mint,
1256            crate::hypersync_net_types_capnp::TransactionField::Sighash => {
1257                TransactionField::Sighash
1258            }
1259            crate::hypersync_net_types_capnp::TransactionField::SourceHash => {
1260                TransactionField::SourceHash
1261            }
1262        }
1263    }
1264}
1265
1266#[cfg(test)]
1267mod tests {
1268    use hypersync_format::Hex;
1269
1270    use super::*;
1271    use crate::{query::tests::test_query_serde, Query};
1272
1273    #[test]
1274    fn test_all_fields_in_schema() {
1275        let schema = hypersync_schema::transaction();
1276        let schema_fields = schema
1277            .fields
1278            .iter()
1279            .map(|f| f.name().clone())
1280            .collect::<std::collections::BTreeSet<_>>();
1281        let all_fields = TransactionField::all()
1282            .into_iter()
1283            .map(|f| f.as_ref().to_string())
1284            .collect::<std::collections::BTreeSet<_>>();
1285        assert_eq!(schema_fields, all_fields);
1286    }
1287
1288    #[test]
1289    fn test_serde_matches_strum() {
1290        for field in TransactionField::all() {
1291            let serialized = serde_json::to_string(&field).unwrap();
1292            let strum = serde_json::to_string(&field.as_ref()).unwrap();
1293            assert_eq!(serialized, strum, "strum value should be the same as serde");
1294        }
1295    }
1296
1297    #[test]
1298    fn test_transaction_filter_serde_with_defaults() {
1299        let transaction_filter = TransactionSelection::default();
1300        let query = Query::new()
1301            .where_transactions(transaction_filter)
1302            .select_transaction_fields(TransactionField::all());
1303
1304        test_query_serde(query, "transaction selection with defaults");
1305    }
1306    #[test]
1307    fn test_transaction_filter_serde_with_explicit_defaults() {
1308        let transaction_filter = TransactionFilter {
1309            from: Vec::default(),
1310            from_filter: Some(FilterWrapper::new(16, 0)),
1311            to: Vec::default(),
1312            to_filter: Some(FilterWrapper::new(16, 0)),
1313            sighash: Vec::default(),
1314            status: Some(u8::default()),
1315            type_: Vec::default(),
1316            contract_address: Vec::default(),
1317            contract_address_filter: Some(FilterWrapper::new(16, 0)),
1318            hash: Vec::default(),
1319            authorization_list: Vec::default(),
1320        };
1321        let query = Query::new()
1322            .where_transactions(transaction_filter)
1323            .select_transaction_fields(TransactionField::all());
1324
1325        test_query_serde(query, "transaction selection with explicit defaults");
1326    }
1327
1328    #[test]
1329    fn test_transaction_filter_serde_with_full_values() {
1330        let transaction_filter = TransactionFilter {
1331            from: vec![Address::decode_hex("0xdadB0d80178819F2319190D340ce9A924f783711").unwrap()],
1332            from_filter: Some(FilterWrapper::new(16, 1)),
1333            to: vec![Address::decode_hex("0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6").unwrap()],
1334            to_filter: Some(FilterWrapper::new(16, 1)),
1335            sighash: vec![Sighash::from([0x12, 0x34, 0x56, 0x78])],
1336            status: Some(1),
1337            type_: vec![2],
1338            contract_address: vec![Address::decode_hex(
1339                "0x1234567890123456789012345678901234567890",
1340            )
1341            .unwrap()],
1342            contract_address_filter: Some(FilterWrapper::new(16, 1)),
1343            hash: vec![Hash::decode_hex(
1344                "0x40d008f2a1653f09b7b028d30c7fd1ba7c84900fcfb032040b3eb3d16f84d294",
1345            )
1346            .unwrap()],
1347            authorization_list: Vec::default(),
1348        };
1349        let query = Query::new()
1350            .where_transactions(transaction_filter)
1351            .select_transaction_fields(TransactionField::all());
1352
1353        test_query_serde(query, "transaction selection with full values");
1354    }
1355
1356    #[test]
1357    fn test_authorization_selection_serde_with_values() {
1358        let auth_selection = AuthorizationSelection {
1359            chain_id: vec![1, 137, 42161],
1360            address: vec![
1361                Address::decode_hex("0xdadB0d80178819F2319190D340ce9A924f783711").unwrap(),
1362            ],
1363        };
1364        let transaction_filter = TransactionFilter {
1365            from: Vec::default(),
1366            from_filter: Some(FilterWrapper::new(16, 0)),
1367            to: Vec::default(),
1368            to_filter: Some(FilterWrapper::new(16, 0)),
1369            sighash: Vec::default(),
1370            status: Some(u8::default()),
1371            type_: Vec::default(),
1372            contract_address: Vec::default(),
1373            contract_address_filter: Some(FilterWrapper::new(16, 0)),
1374            hash: Vec::default(),
1375            authorization_list: vec![auth_selection],
1376        };
1377        let query = Query::new()
1378            .where_transactions(transaction_filter)
1379            .select_transaction_fields(TransactionField::all());
1380
1381        test_query_serde(query, "authorization selection with rest defaults");
1382    }
1383
1384    #[test]
1385    fn nullable_fields() {
1386        use std::collections::HashMap;
1387
1388        let is_nullable_map: HashMap<_, _> = TransactionField::all()
1389            .iter()
1390            .map(|f| (f.to_string(), f.is_nullable()))
1391            .collect();
1392        for field in hypersync_schema::transaction().fields.iter() {
1393            let should_be_nullable = is_nullable_map.get(field.name().as_str()).unwrap();
1394            assert_eq!(
1395                field.is_nullable(),
1396                *should_be_nullable,
1397                "field {} nullable mismatch",
1398                field.name()
1399            );
1400        }
1401    }
1402}