alloy_provider/fillers/
mod.rs

1//! Transaction Fillers
2//!
3//! Fillers decorate a [`Provider`], filling transaction details before they
4//! are sent to the network. Fillers are used to set the nonce, gas price, gas
5//! limit, and other transaction details, and are called before any other layer.
6//!
7//! [`Provider`]: crate::Provider
8
9mod chain_id;
10use alloy_eips::{BlockId, BlockNumberOrTag};
11use alloy_primitives::{
12    Address, BlockHash, BlockNumber, StorageKey, StorageValue, TxHash, B256, U128, U256,
13};
14use alloy_rpc_client::NoParams;
15use alloy_rpc_types_eth::{Bundle, Index, SyncStatus};
16pub use chain_id::ChainIdFiller;
17use std::borrow::Cow;
18
19mod wallet;
20pub use wallet::WalletFiller;
21
22mod nonce;
23pub use nonce::{CachedNonceManager, NonceFiller, NonceManager, SimpleNonceManager};
24
25mod gas;
26pub use gas::{BlobGasFiller, GasFillable, GasFiller};
27
28mod join_fill;
29pub use join_fill::JoinFill;
30use tracing::error;
31
32use crate::{
33    provider::SendableTx, EthCall, EthCallMany, EthGetBlock, FilterPollerBuilder, Identity,
34    PendingTransaction, PendingTransactionBuilder, PendingTransactionConfig,
35    PendingTransactionError, Provider, ProviderCall, ProviderLayer, RootProvider, RpcWithBlock,
36};
37use alloy_json_rpc::RpcError;
38use alloy_network::{AnyNetwork, Ethereum, Network};
39use alloy_primitives::{Bytes, U64};
40use alloy_rpc_types_eth::{
41    erc4337::TransactionConditional,
42    simulate::{SimulatePayload, SimulatedBlock},
43    AccessListResult, EIP1186AccountProofResponse, EthCallResponse, FeeHistory, Filter,
44    FilterChanges, Log,
45};
46use alloy_transport::TransportResult;
47use async_trait::async_trait;
48use futures_utils_wasm::impl_future;
49use serde_json::value::RawValue;
50use std::marker::PhantomData;
51
52/// The recommended filler, a preconfigured set of layers handling gas estimation, nonce
53/// management, and chain-id fetching.
54pub type RecommendedFiller =
55    JoinFill<JoinFill<JoinFill<Identity, GasFiller>, NonceFiller>, ChainIdFiller>;
56
57/// The control flow for a filler.
58#[derive(Clone, Debug, PartialEq, Eq)]
59pub enum FillerControlFlow {
60    /// The filler is missing a required property.
61    ///
62    /// To allow joining fillers while preserving their associated missing
63    /// lists, this variant contains a list of `(name, missing)` tuples. When
64    /// absorbing another control flow, if both are missing, the missing lists
65    /// are combined.
66    Missing(Vec<(&'static str, Vec<&'static str>)>),
67    /// The filler is ready to fill in the transaction request.
68    Ready,
69    /// The filler has filled in all properties that it can fill.
70    Finished,
71}
72
73impl FillerControlFlow {
74    /// Absorb the control flow of another filler.
75    ///
76    /// # Behavior:
77    /// - If either is finished, return the unfinished one
78    /// - If either is ready, return ready.
79    /// - If both are missing, return missing.
80    pub fn absorb(self, other: Self) -> Self {
81        if other.is_finished() {
82            return self;
83        }
84
85        if self.is_finished() {
86            return other;
87        }
88
89        if other.is_ready() || self.is_ready() {
90            return Self::Ready;
91        }
92
93        if let (Self::Missing(mut a), Self::Missing(b)) = (self, other) {
94            a.extend(b);
95            return Self::Missing(a);
96        }
97
98        unreachable!()
99    }
100
101    /// Creates a new `Missing` control flow.
102    pub fn missing(name: &'static str, missing: Vec<&'static str>) -> Self {
103        Self::Missing(vec![(name, missing)])
104    }
105
106    /// Returns true if the filler is missing a required property.
107    pub fn as_missing(&self) -> Option<&[(&'static str, Vec<&'static str>)]> {
108        match self {
109            Self::Missing(missing) => Some(missing),
110            _ => None,
111        }
112    }
113
114    /// Returns `true` if the filler is missing information required to fill in
115    /// the transaction request.
116    pub const fn is_missing(&self) -> bool {
117        matches!(self, Self::Missing(_))
118    }
119
120    /// Returns `true` if the filler is ready to fill in the transaction
121    /// request.
122    pub const fn is_ready(&self) -> bool {
123        matches!(self, Self::Ready)
124    }
125
126    /// Returns `true` if the filler is finished filling in the transaction
127    /// request.
128    pub const fn is_finished(&self) -> bool {
129        matches!(self, Self::Finished)
130    }
131}
132
133/// A layer that can fill in a `TransactionRequest` with additional information.
134///
135/// ## Lifecycle Notes
136///
137/// The [`FillerControlFlow`] determines the lifecycle of a filler. Fillers
138/// may be in one of three states:
139/// - **Missing**: The filler is missing a required property to fill in the transaction request.
140///   [`TxFiller::status`] should return [`FillerControlFlow::Missing`]. with a list of the missing
141///   properties.
142/// - **Ready**: The filler is ready to fill in the transaction request. [`TxFiller::status`] should
143///   return [`FillerControlFlow::Ready`].
144/// - **Finished**: The filler has filled in all properties that it can fill. [`TxFiller::status`]
145///   should return [`FillerControlFlow::Finished`].
146#[doc(alias = "TransactionFiller")]
147pub trait TxFiller<N: Network = Ethereum>: Clone + Send + Sync + std::fmt::Debug {
148    /// The properties that this filler retrieves from the RPC. to fill in the
149    /// TransactionRequest.
150    type Fillable: Send + Sync + 'static;
151
152    /// Joins this filler with another filler to compose multiple fillers.
153    fn join_with<T>(self, other: T) -> JoinFill<Self, T>
154    where
155        T: TxFiller<N>,
156    {
157        JoinFill::new(self, other)
158    }
159
160    /// Return a control-flow enum indicating whether the filler is ready to
161    /// fill in the transaction request, or if it is missing required
162    /// properties.
163    fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow;
164
165    /// Returns `true` if the filler is should continue filling.
166    fn continue_filling(&self, tx: &SendableTx<N>) -> bool {
167        tx.as_builder().is_some_and(|tx| self.status(tx).is_ready())
168    }
169
170    /// Returns `true` if the filler is ready to fill in the transaction request.
171    fn ready(&self, tx: &N::TransactionRequest) -> bool {
172        self.status(tx).is_ready()
173    }
174
175    /// Returns `true` if the filler is finished filling in the transaction request.
176    fn finished(&self, tx: &N::TransactionRequest) -> bool {
177        self.status(tx).is_finished()
178    }
179
180    /// Performs any synchronous filling. This should be called before
181    /// [`TxFiller::prepare`] and [`TxFiller::fill`] to fill in any properties
182    /// that can be filled synchronously.
183    fn fill_sync(&self, tx: &mut SendableTx<N>);
184
185    /// Prepares fillable properties, potentially by making an RPC request.
186    fn prepare<P: Provider<N>>(
187        &self,
188        provider: &P,
189        tx: &N::TransactionRequest,
190    ) -> impl_future!(<Output = TransportResult<Self::Fillable>>);
191
192    /// Fills in the transaction request with the fillable properties.
193    fn fill(
194        &self,
195        fillable: Self::Fillable,
196        tx: SendableTx<N>,
197    ) -> impl_future!(<Output = TransportResult<SendableTx<N>>>);
198
199    /// Prepares and fills the transaction request with the fillable properties.
200    fn prepare_and_fill<P>(
201        &self,
202        provider: &P,
203        tx: SendableTx<N>,
204    ) -> impl_future!(<Output = TransportResult<SendableTx<N>>>)
205    where
206        P: Provider<N>,
207    {
208        async move {
209            if tx.is_envelope() {
210                return Ok(tx);
211            }
212
213            let fillable =
214                self.prepare(provider, tx.as_builder().expect("checked by is_envelope")).await?;
215
216            self.fill(fillable, tx).await
217        }
218    }
219
220    /// Prepares transaction request with necessary fillers required for eth_call operations
221    /// asyncronously
222    fn prepare_call(
223        &self,
224        tx: &mut N::TransactionRequest,
225    ) -> impl_future!(<Output = TransportResult<()>>) {
226        let _ = tx;
227        // This is a no-op by default
228        futures::future::ready(Ok(()))
229    }
230
231    /// Prepares transaction request with necessary fillers required for eth_call operations
232    /// syncronously
233    fn prepare_call_sync(&self, tx: &mut N::TransactionRequest) -> TransportResult<()> {
234        let _ = tx;
235        // No-op default
236        Ok(())
237    }
238}
239
240/// A [`Provider`] that applies one or more [`TxFiller`]s.
241///
242/// Fills arbitrary properties in a transaction request by composing multiple
243/// fill layers. This struct should always be the outermost layer in a provider
244/// stack, and this is enforced when using [`ProviderBuilder::filler`] to
245/// construct this layer.
246///
247/// Users should NOT use this struct directly. Instead, use
248/// [`ProviderBuilder::filler`] to construct and apply it to a stack.
249///
250/// [`ProviderBuilder::filler`]: crate::ProviderBuilder::filler
251#[derive(Clone, Debug)]
252pub struct FillProvider<F, P, N = Ethereum>
253where
254    F: TxFiller<N>,
255    P: Provider<N>,
256    N: Network,
257{
258    pub(crate) inner: P,
259    pub(crate) filler: F,
260    _pd: PhantomData<fn() -> N>,
261}
262
263impl<F, P, N> FillProvider<F, P, N>
264where
265    F: TxFiller<N>,
266    P: Provider<N>,
267    N: Network,
268{
269    /// Creates a new `FillProvider` with the given filler and inner provider.
270    pub fn new(inner: P, filler: F) -> Self {
271        Self { inner, filler, _pd: PhantomData }
272    }
273
274    /// Joins a filler to this provider
275    pub fn join_with<Other: TxFiller<N>>(
276        self,
277        other: Other,
278    ) -> FillProvider<JoinFill<F, Other>, P, N> {
279        self.filler.join_with(other).layer(self.inner)
280    }
281
282    async fn fill_inner(&self, mut tx: SendableTx<N>) -> TransportResult<SendableTx<N>> {
283        let mut count = 0;
284
285        while self.filler.continue_filling(&tx) {
286            self.filler.fill_sync(&mut tx);
287            tx = self.filler.prepare_and_fill(&self.inner, tx).await?;
288
289            count += 1;
290            if count >= 20 {
291                const ERROR: &str = "Tx filler loop detected. This indicates a bug in some filler implementation. Please file an issue containing this message.";
292                error!(
293                    ?tx, ?self.filler,
294                    ERROR
295                );
296                panic!("{}, {:?}, {:?}", ERROR, &tx, &self.filler);
297            }
298        }
299        Ok(tx)
300    }
301
302    /// Fills the transaction request, using the configured fillers
303    pub async fn fill(&self, tx: N::TransactionRequest) -> TransportResult<SendableTx<N>> {
304        self.fill_inner(SendableTx::Builder(tx)).await
305    }
306
307    /// Prepares a transaction request for eth_call operations using the configured fillers
308    pub fn prepare_call(
309        &self,
310        mut tx: N::TransactionRequest,
311    ) -> TransportResult<N::TransactionRequest> {
312        self.filler.prepare_call_sync(&mut tx)?;
313        Ok(tx)
314    }
315}
316
317#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
318#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
319impl<F, P, N> Provider<N> for FillProvider<F, P, N>
320where
321    F: TxFiller<N>,
322    P: Provider<N>,
323    N: Network,
324{
325    fn root(&self) -> &RootProvider<N> {
326        self.inner.root()
327    }
328
329    fn get_accounts(&self) -> ProviderCall<NoParams, Vec<Address>> {
330        self.inner.get_accounts()
331    }
332
333    fn get_blob_base_fee(&self) -> ProviderCall<NoParams, U128, u128> {
334        self.inner.get_blob_base_fee()
335    }
336
337    fn get_block_number(&self) -> ProviderCall<NoParams, U64, BlockNumber> {
338        self.inner.get_block_number()
339    }
340
341    fn call<'req>(&self, tx: N::TransactionRequest) -> EthCall<N, Bytes> {
342        let mut tx = tx;
343        let _ = self.filler.prepare_call_sync(&mut tx);
344        self.inner.call(tx)
345    }
346
347    fn call_many<'req>(
348        &self,
349        bundles: &'req Vec<Bundle>,
350    ) -> EthCallMany<'req, N, Vec<Vec<EthCallResponse>>> {
351        self.inner.call_many(bundles)
352    }
353
354    fn simulate<'req>(
355        &self,
356        payload: &'req SimulatePayload,
357    ) -> RpcWithBlock<&'req SimulatePayload, Vec<SimulatedBlock<N::BlockResponse>>> {
358        self.inner.simulate(payload)
359    }
360
361    fn get_chain_id(&self) -> ProviderCall<NoParams, U64, u64> {
362        self.inner.get_chain_id()
363    }
364
365    fn create_access_list<'a>(
366        &self,
367        request: &'a N::TransactionRequest,
368    ) -> RpcWithBlock<&'a N::TransactionRequest, AccessListResult> {
369        self.inner.create_access_list(request)
370    }
371
372    fn estimate_gas<'req>(&self, tx: N::TransactionRequest) -> EthCall<N, U64, u64> {
373        let mut tx = tx;
374        let _ = self.filler.prepare_call_sync(&mut tx);
375        self.inner.estimate_gas(tx)
376    }
377
378    async fn get_fee_history(
379        &self,
380        block_count: u64,
381        last_block: BlockNumberOrTag,
382        reward_percentiles: &[f64],
383    ) -> TransportResult<FeeHistory> {
384        self.inner.get_fee_history(block_count, last_block, reward_percentiles).await
385    }
386
387    fn get_gas_price(&self) -> ProviderCall<NoParams, U128, u128> {
388        self.inner.get_gas_price()
389    }
390
391    fn get_account(&self, address: Address) -> RpcWithBlock<Address, alloy_consensus::Account> {
392        self.inner.get_account(address)
393    }
394
395    fn get_balance(&self, address: Address) -> RpcWithBlock<Address, U256, U256> {
396        self.inner.get_balance(address)
397    }
398
399    fn get_block(&self, block: BlockId) -> EthGetBlock<N::BlockResponse> {
400        self.inner.get_block(block)
401    }
402
403    fn get_block_by_hash(&self, hash: BlockHash) -> EthGetBlock<N::BlockResponse> {
404        self.inner.get_block_by_hash(hash)
405    }
406
407    fn get_block_by_number(&self, number: BlockNumberOrTag) -> EthGetBlock<N::BlockResponse> {
408        self.inner.get_block_by_number(number)
409    }
410
411    async fn get_block_transaction_count_by_hash(
412        &self,
413        hash: BlockHash,
414    ) -> TransportResult<Option<u64>> {
415        self.inner.get_block_transaction_count_by_hash(hash).await
416    }
417
418    async fn get_block_transaction_count_by_number(
419        &self,
420        block_number: BlockNumberOrTag,
421    ) -> TransportResult<Option<u64>> {
422        self.inner.get_block_transaction_count_by_number(block_number).await
423    }
424
425    fn get_block_receipts(
426        &self,
427        block: BlockId,
428    ) -> ProviderCall<(BlockId,), Option<Vec<N::ReceiptResponse>>> {
429        self.inner.get_block_receipts(block)
430    }
431
432    fn get_code_at(&self, address: Address) -> RpcWithBlock<Address, Bytes> {
433        self.inner.get_code_at(address)
434    }
435
436    async fn watch_blocks(&self) -> TransportResult<FilterPollerBuilder<B256>> {
437        self.inner.watch_blocks().await
438    }
439
440    async fn watch_pending_transactions(&self) -> TransportResult<FilterPollerBuilder<B256>> {
441        self.inner.watch_pending_transactions().await
442    }
443
444    async fn watch_logs(&self, filter: &Filter) -> TransportResult<FilterPollerBuilder<Log>> {
445        self.inner.watch_logs(filter).await
446    }
447
448    async fn watch_full_pending_transactions(
449        &self,
450    ) -> TransportResult<FilterPollerBuilder<N::TransactionResponse>> {
451        self.inner.watch_full_pending_transactions().await
452    }
453
454    async fn get_filter_changes_dyn(&self, id: U256) -> TransportResult<FilterChanges> {
455        self.inner.get_filter_changes_dyn(id).await
456    }
457
458    async fn get_filter_logs(&self, id: U256) -> TransportResult<Vec<Log>> {
459        self.inner.get_filter_logs(id).await
460    }
461
462    async fn uninstall_filter(&self, id: U256) -> TransportResult<bool> {
463        self.inner.uninstall_filter(id).await
464    }
465
466    async fn watch_pending_transaction(
467        &self,
468        config: PendingTransactionConfig,
469    ) -> Result<PendingTransaction, PendingTransactionError> {
470        self.inner.watch_pending_transaction(config).await
471    }
472
473    async fn get_logs(&self, filter: &Filter) -> TransportResult<Vec<Log>> {
474        self.inner.get_logs(filter).await
475    }
476
477    fn get_proof(
478        &self,
479        address: Address,
480        keys: Vec<StorageKey>,
481    ) -> RpcWithBlock<(Address, Vec<StorageKey>), EIP1186AccountProofResponse> {
482        self.inner.get_proof(address, keys)
483    }
484
485    fn get_storage_at(
486        &self,
487        address: Address,
488        key: U256,
489    ) -> RpcWithBlock<(Address, U256), StorageValue> {
490        self.inner.get_storage_at(address, key)
491    }
492
493    fn get_transaction_by_hash(
494        &self,
495        hash: TxHash,
496    ) -> ProviderCall<(TxHash,), Option<N::TransactionResponse>> {
497        self.inner.get_transaction_by_hash(hash)
498    }
499
500    fn get_transaction_by_block_hash_and_index(
501        &self,
502        block_hash: B256,
503        index: usize,
504    ) -> ProviderCall<(B256, Index), Option<N::TransactionResponse>> {
505        self.inner.get_transaction_by_block_hash_and_index(block_hash, index)
506    }
507
508    fn get_raw_transaction_by_block_hash_and_index(
509        &self,
510        block_hash: B256,
511        index: usize,
512    ) -> ProviderCall<(B256, Index), Option<Bytes>> {
513        self.inner.get_raw_transaction_by_block_hash_and_index(block_hash, index)
514    }
515
516    fn get_transaction_by_block_number_and_index(
517        &self,
518        block_number: BlockNumberOrTag,
519        index: usize,
520    ) -> ProviderCall<(BlockNumberOrTag, Index), Option<N::TransactionResponse>> {
521        self.inner.get_transaction_by_block_number_and_index(block_number, index)
522    }
523
524    fn get_raw_transaction_by_block_number_and_index(
525        &self,
526        block_number: BlockNumberOrTag,
527        index: usize,
528    ) -> ProviderCall<(BlockNumberOrTag, Index), Option<Bytes>> {
529        self.inner.get_raw_transaction_by_block_number_and_index(block_number, index)
530    }
531
532    fn get_raw_transaction_by_hash(&self, hash: TxHash) -> ProviderCall<(TxHash,), Option<Bytes>> {
533        self.inner.get_raw_transaction_by_hash(hash)
534    }
535
536    fn get_transaction_count(
537        &self,
538        address: Address,
539    ) -> RpcWithBlock<Address, U64, u64, fn(U64) -> u64> {
540        self.inner.get_transaction_count(address)
541    }
542
543    fn get_transaction_receipt(
544        &self,
545        hash: TxHash,
546    ) -> ProviderCall<(TxHash,), Option<N::ReceiptResponse>> {
547        self.inner.get_transaction_receipt(hash)
548    }
549
550    async fn get_uncle(&self, tag: BlockId, idx: u64) -> TransportResult<Option<N::BlockResponse>> {
551        self.inner.get_uncle(tag, idx).await
552    }
553
554    async fn get_uncle_count(&self, tag: BlockId) -> TransportResult<u64> {
555        self.inner.get_uncle_count(tag).await
556    }
557
558    fn get_max_priority_fee_per_gas(&self) -> ProviderCall<NoParams, U128, u128> {
559        self.inner.get_max_priority_fee_per_gas()
560    }
561
562    async fn new_block_filter(&self) -> TransportResult<U256> {
563        self.inner.new_block_filter().await
564    }
565
566    async fn new_filter(&self, filter: &Filter) -> TransportResult<U256> {
567        self.inner.new_filter(filter).await
568    }
569
570    async fn new_pending_transactions_filter(&self, full: bool) -> TransportResult<U256> {
571        self.inner.new_pending_transactions_filter(full).await
572    }
573
574    async fn send_raw_transaction(
575        &self,
576        encoded_tx: &[u8],
577    ) -> TransportResult<PendingTransactionBuilder<N>> {
578        self.inner.send_raw_transaction(encoded_tx).await
579    }
580
581    async fn send_raw_transaction_conditional(
582        &self,
583        encoded_tx: &[u8],
584        conditional: TransactionConditional,
585    ) -> TransportResult<PendingTransactionBuilder<N>> {
586        self.inner.send_raw_transaction_conditional(encoded_tx, conditional).await
587    }
588
589    async fn send_transaction_internal(
590        &self,
591        mut tx: SendableTx<N>,
592    ) -> TransportResult<PendingTransactionBuilder<N>> {
593        tx = self.fill_inner(tx).await?;
594
595        if let Some(builder) = tx.as_builder() {
596            if let FillerControlFlow::Missing(missing) = self.filler.status(builder) {
597                // TODO: improve this.
598                // blocked by #431
599                let message = format!("missing properties: {:?}", missing);
600                return Err(RpcError::local_usage_str(&message));
601            }
602        }
603
604        // Errors in tx building happen further down the stack.
605        self.inner.send_transaction_internal(tx).await
606    }
607
608    #[cfg(feature = "pubsub")]
609    async fn subscribe_blocks(
610        &self,
611    ) -> TransportResult<alloy_pubsub::Subscription<N::HeaderResponse>> {
612        self.inner.subscribe_blocks().await
613    }
614
615    #[cfg(feature = "pubsub")]
616    async fn subscribe_pending_transactions(
617        &self,
618    ) -> TransportResult<alloy_pubsub::Subscription<B256>> {
619        self.inner.subscribe_pending_transactions().await
620    }
621
622    #[cfg(feature = "pubsub")]
623    async fn subscribe_full_pending_transactions(
624        &self,
625    ) -> TransportResult<alloy_pubsub::Subscription<N::TransactionResponse>> {
626        self.inner.subscribe_full_pending_transactions().await
627    }
628
629    #[cfg(feature = "pubsub")]
630    async fn subscribe_logs(
631        &self,
632        filter: &Filter,
633    ) -> TransportResult<alloy_pubsub::Subscription<Log>> {
634        self.inner.subscribe_logs(filter).await
635    }
636
637    #[cfg(feature = "pubsub")]
638    async fn unsubscribe(&self, id: B256) -> TransportResult<()> {
639        self.inner.unsubscribe(id).await
640    }
641
642    fn syncing(&self) -> ProviderCall<NoParams, SyncStatus> {
643        self.inner.syncing()
644    }
645
646    fn get_client_version(&self) -> ProviderCall<NoParams, String> {
647        self.inner.get_client_version()
648    }
649
650    fn get_sha3(&self, data: &[u8]) -> ProviderCall<(String,), B256> {
651        self.inner.get_sha3(data)
652    }
653
654    fn get_net_version(&self) -> ProviderCall<NoParams, U64, u64> {
655        self.inner.get_net_version()
656    }
657
658    async fn raw_request_dyn(
659        &self,
660        method: Cow<'static, str>,
661        params: &RawValue,
662    ) -> TransportResult<Box<RawValue>> {
663        self.inner.raw_request_dyn(method, params).await
664    }
665
666    fn transaction_request(&self) -> N::TransactionRequest {
667        self.inner.transaction_request()
668    }
669}
670
671/// A trait which may be used to configure default fillers for [Network] implementations.
672pub trait RecommendedFillers: Network {
673    /// Recommended fillers for this network.
674    type RecommendedFillers: TxFiller<Self>;
675
676    /// Returns the recommended filler for this provider.
677    fn recommended_fillers() -> Self::RecommendedFillers;
678}
679
680impl RecommendedFillers for Ethereum {
681    type RecommendedFillers =
682        JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>;
683
684    fn recommended_fillers() -> Self::RecommendedFillers {
685        Default::default()
686    }
687}
688
689impl RecommendedFillers for AnyNetwork {
690    type RecommendedFillers =
691        JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>;
692
693    fn recommended_fillers() -> Self::RecommendedFillers {
694        Default::default()
695    }
696}