Skip to main content

cast/
tx.rs

1use crate::traces::identifier::SignaturesIdentifier;
2use alloy_consensus::{SidecarBuilder, SimpleCoder};
3use alloy_dyn_abi::ErrorExt;
4use alloy_ens::NameOrAddress;
5use alloy_json_abi::Function;
6use alloy_network::{Network, TransactionBuilder};
7use alloy_primitives::{Address, B256, Bytes, TxHash, TxKind, U256, hex};
8use alloy_provider::{PendingTransactionBuilder, Provider};
9use alloy_rpc_types::{AccessList, Authorization, TransactionInputKind};
10use alloy_signer::Signer;
11use alloy_transport::TransportError;
12use clap::Args;
13use eyre::{Result, WrapErr};
14use foundry_cli::{
15    opts::{CliAuthorizationList, EthereumOpts, TransactionOpts},
16    utils::{self, parse_function_args},
17};
18use foundry_common::{
19    FoundryTransactionBuilder, TransactionReceiptWithRevertReason, fmt::*,
20    get_pretty_receipt_w_reason_attr, shell,
21};
22use foundry_config::{Chain, Config};
23use foundry_wallets::{BrowserWalletOpts, WalletOpts, WalletSigner};
24use itertools::Itertools;
25use serde_json::value::RawValue;
26use std::{fmt::Write, marker::PhantomData, str::FromStr, time::Duration};
27
28#[derive(Debug, Clone, Args)]
29pub struct SendTxOpts {
30    /// Only print the transaction hash and exit immediately.
31    #[arg(id = "async", long = "async", alias = "cast-async", env = "CAST_ASYNC")]
32    pub cast_async: bool,
33
34    /// Wait for transaction receipt synchronously instead of polling.
35    /// Note: uses `eth_sendTransactionSync` which may not be supported by all clients.
36    #[arg(long, conflicts_with = "async")]
37    pub sync: bool,
38
39    /// The number of confirmations until the receipt is fetched.
40    #[arg(long, default_value = "1")]
41    pub confirmations: u64,
42
43    /// Timeout for sending the transaction.
44    #[arg(long, env = "ETH_TIMEOUT")]
45    pub timeout: Option<u64>,
46
47    /// Polling interval for transaction receipts (in seconds).
48    #[arg(long, alias = "poll-interval", env = "ETH_POLL_INTERVAL")]
49    pub poll_interval: Option<u64>,
50
51    /// Ethereum options
52    #[command(flatten)]
53    pub eth: EthereumOpts,
54
55    /// Browser wallet options
56    #[command(flatten)]
57    pub browser: BrowserWalletOpts,
58}
59
60/// Different sender kinds used by [`CastTxBuilder`].
61pub enum SenderKind<'a> {
62    /// An address without signer. Used for read-only calls and transactions sent through unlocked
63    /// accounts.
64    Address(Address),
65    /// A reference to a signer.
66    Signer(&'a WalletSigner),
67    /// An owned signer.
68    OwnedSigner(Box<WalletSigner>),
69}
70
71impl SenderKind<'_> {
72    /// Resolves the name to an Ethereum Address.
73    pub fn address(&self) -> Address {
74        match self {
75            Self::Address(addr) => *addr,
76            Self::Signer(signer) => signer.address(),
77            Self::OwnedSigner(signer) => signer.address(),
78        }
79    }
80
81    /// Resolves the sender from the wallet options.
82    ///
83    /// This function prefers the `from` field and may return a different address from the
84    /// configured signer
85    /// If from is specified, returns it
86    /// If from is not specified, but there is a signer configured, returns the signer's address
87    /// If from is not specified and there is no signer configured, returns zero address
88    pub async fn from_wallet_opts(opts: WalletOpts) -> Result<Self> {
89        if let (Some(signer), _) = opts.maybe_signer().await? {
90            Ok(Self::OwnedSigner(Box::new(signer)))
91        } else if let Some(from) = opts.from {
92            Ok(from.into())
93        } else {
94            Ok(Address::ZERO.into())
95        }
96    }
97
98    /// Returns the signer if available.
99    pub fn as_signer(&self) -> Option<&WalletSigner> {
100        match self {
101            Self::Signer(signer) => Some(signer),
102            Self::OwnedSigner(signer) => Some(signer.as_ref()),
103            _ => None,
104        }
105    }
106}
107
108impl From<Address> for SenderKind<'_> {
109    fn from(addr: Address) -> Self {
110        Self::Address(addr)
111    }
112}
113
114impl<'a> From<&'a WalletSigner> for SenderKind<'a> {
115    fn from(signer: &'a WalletSigner) -> Self {
116        Self::Signer(signer)
117    }
118}
119
120impl From<WalletSigner> for SenderKind<'_> {
121    fn from(signer: WalletSigner) -> Self {
122        Self::OwnedSigner(Box::new(signer))
123    }
124}
125
126/// Prevents a misconfigured hwlib from sending a transaction that defies user-specified --from
127pub fn validate_from_address(
128    specified_from: Option<Address>,
129    signer_address: Address,
130) -> Result<()> {
131    if let Some(specified_from) = specified_from
132        && specified_from != signer_address
133    {
134        eyre::bail!(
135                "\
136The specified sender via CLI/env vars does not match the sender configured via
137the hardware wallet's HD Path.
138Please use the `--hd-path <PATH>` parameter to specify the BIP32 Path which
139corresponds to the sender, or let foundry automatically detect it by not specifying any sender address."
140            )
141    }
142    Ok(())
143}
144
145/// Initial state.
146#[derive(Debug)]
147pub struct InitState;
148
149/// State with known [TxKind].
150#[derive(Debug)]
151pub struct ToState {
152    to: Option<Address>,
153}
154
155/// State with known input for the transaction.
156#[derive(Debug)]
157pub struct InputState {
158    kind: TxKind,
159    input: Vec<u8>,
160    func: Option<Function>,
161}
162
163pub struct CastTxSender<N, P> {
164    provider: P,
165    _phantom: PhantomData<N>,
166}
167
168impl<N: Network, P: Provider<N>> CastTxSender<N, P>
169where
170    N::TransactionRequest: FoundryTransactionBuilder<N>,
171    N::ReceiptResponse: UIfmt + UIfmtReceiptExt,
172{
173    /// Creates a new Cast instance responsible for sending transactions.
174    pub fn new(provider: P) -> Self {
175        Self { provider, _phantom: PhantomData }
176    }
177
178    /// Sends a transaction and waits for receipt synchronously
179    pub async fn send_sync(&self, tx: N::TransactionRequest) -> Result<String> {
180        let mut receipt = TransactionReceiptWithRevertReason::<N> {
181            receipt: self.provider.send_transaction_sync(tx).await?,
182            revert_reason: None,
183        };
184        // Allow to fail silently
185        let _ = receipt.update_revert_reason(&self.provider).await;
186
187        self.format_receipt(receipt, None)
188    }
189
190    /// Sends a transaction to the specified address
191    ///
192    /// # Example
193    ///
194    /// ```
195    /// use cast::tx::CastTxSender;
196    /// use alloy_primitives::{Address, U256, Bytes};
197    /// use alloy_serde::WithOtherFields;
198    /// use alloy_rpc_types::{TransactionRequest};
199    /// use alloy_provider::{RootProvider, ProviderBuilder, network::AnyNetwork};
200    /// use std::str::FromStr;
201    /// use alloy_sol_types::{sol, SolCall};    ///
202    ///
203    /// sol!(
204    ///     function greet(string greeting) public;
205    /// );
206    ///
207    /// # async fn foo() -> eyre::Result<()> {
208    /// let provider = ProviderBuilder::<_,_, AnyNetwork>::default().connect("http://localhost:8545").await?;;
209    /// let from = Address::from_str("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")?;
210    /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?;
211    /// let greeting = greetCall { greeting: "hello".to_string() }.abi_encode();
212    /// let bytes = Bytes::from_iter(greeting.iter());
213    /// let gas = U256::from_str("200000").unwrap();
214    /// let value = U256::from_str("1").unwrap();
215    /// let nonce = U256::from_str("1").unwrap();
216    /// let tx = TransactionRequest::default().to(to).input(bytes.into()).from(from);
217    /// let tx = WithOtherFields::new(tx);
218    /// let cast = CastTxSender::new(provider);
219    /// let data = cast.send(tx).await?;
220    /// println!("{:#?}", data);
221    /// # Ok(())
222    /// # }
223    /// ```
224    pub async fn send(&self, tx: N::TransactionRequest) -> Result<PendingTransactionBuilder<N>> {
225        let res = self.provider.send_transaction(tx).await?;
226
227        Ok(res)
228    }
229
230    /// Sends a raw RLP-encoded transaction via `eth_sendRawTransaction`.
231    ///
232    /// Used for transaction types that the standard Alloy network stack doesn't understand
233    /// (e.g., Tempo transactions).
234    pub async fn send_raw(&self, raw_tx: &[u8]) -> Result<PendingTransactionBuilder<N>> {
235        let res = self.provider.send_raw_transaction(raw_tx).await?;
236        Ok(res)
237    }
238
239    /// Prints the transaction hash (if async) or waits for the receipt and prints it.
240    ///
241    /// This is the shared "output" path used by both the normal send flow and the browser wallet
242    /// flow (which sends the transaction out-of-band and only has a tx hash).
243    pub async fn print_tx_result(
244        &self,
245        tx_hash: B256,
246        cast_async: bool,
247        confs: u64,
248        timeout: u64,
249    ) -> Result<()> {
250        if cast_async {
251            sh_println!("{tx_hash:#x}")?;
252        } else {
253            let receipt =
254                self.receipt(format!("{tx_hash:#x}"), None, confs, Some(timeout), false).await?;
255            sh_println!("{receipt}")?;
256        }
257        Ok(())
258    }
259
260    /// # Example
261    ///
262    /// ```
263    /// use alloy_provider::{ProviderBuilder, RootProvider, network::AnyNetwork};
264    /// use cast::tx::CastTxSender;
265    ///
266    /// async fn foo() -> eyre::Result<()> {
267    /// let provider =
268    ///     ProviderBuilder::<_, _, AnyNetwork>::default().connect("http://localhost:8545").await?;
269    /// let cast = CastTxSender::new(provider);
270    /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc";
271    /// let receipt = cast.receipt(tx_hash.to_string(), None, 1, None, false).await?;
272    /// println!("{}", receipt);
273    /// # Ok(())
274    /// # }
275    /// ```
276    pub async fn receipt(
277        &self,
278        tx_hash: String,
279        field: Option<String>,
280        confs: u64,
281        timeout: Option<u64>,
282        cast_async: bool,
283    ) -> Result<String> {
284        let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?;
285
286        let mut receipt = TransactionReceiptWithRevertReason::<N> {
287            receipt: match self.provider.get_transaction_receipt(tx_hash).await? {
288                Some(r) => r,
289                None => {
290                    // if the async flag is provided, immediately exit if no tx is found, otherwise
291                    // try to poll for it
292                    if cast_async {
293                        eyre::bail!("tx not found: {:?}", tx_hash)
294                    }
295                    PendingTransactionBuilder::<N>::new(self.provider.root().clone(), tx_hash)
296                        .with_required_confirmations(confs)
297                        .with_timeout(timeout.map(Duration::from_secs))
298                        .get_receipt()
299                        .await?
300                }
301            },
302            revert_reason: None,
303        };
304
305        // Allow to fail silently
306        let _ = receipt.update_revert_reason(&self.provider).await;
307
308        self.format_receipt(receipt, field)
309    }
310
311    /// Helper method to format transaction receipts consistently
312    fn format_receipt(
313        &self,
314        receipt: TransactionReceiptWithRevertReason<N>,
315        field: Option<String>,
316    ) -> Result<String> {
317        Ok(if let Some(ref field) = field {
318            get_pretty_receipt_w_reason_attr(&receipt, field)
319                .ok_or_else(|| eyre::eyre!("invalid receipt field: {}", field))?
320        } else if shell::is_json() {
321            // to_value first to sort json object keys
322            serde_json::to_value(&receipt)?.to_string()
323        } else {
324            receipt.pretty()
325        })
326    }
327}
328
329/// Builder type constructing generic TransactionRequest from cast send/mktx inputs.
330///
331/// It is implemented as a stateful builder with expected state transition of [InitState] ->
332/// [ToState] -> [InputState].
333#[derive(Debug)]
334pub struct CastTxBuilder<N: Network, P, S> {
335    provider: P,
336    pub(crate) tx: N::TransactionRequest,
337    /// Whether the transaction should be sent as a legacy transaction.
338    legacy: bool,
339    blob: bool,
340    /// Whether the blob transaction should use EIP-4844 (legacy) format instead of EIP-7594.
341    eip4844: bool,
342    /// Whether to fill gas, fees and nonce. Set to `false` for read-only calls
343    /// (eth_call, eth_estimateGas, eth_createAccessList).
344    fill: bool,
345    auth: Vec<CliAuthorizationList>,
346    chain: Chain,
347    etherscan_api_key: Option<String>,
348    access_list: Option<Option<AccessList>>,
349    state: S,
350}
351
352impl<N: Network, P: Provider<N>> CastTxBuilder<N, P, InitState>
353where
354    N::TransactionRequest: FoundryTransactionBuilder<N>,
355{
356    /// Creates a new instance of [CastTxBuilder] filling transaction with fields present in
357    /// provided [TransactionOpts].
358    pub async fn new(provider: P, tx_opts: TransactionOpts, config: &Config) -> Result<Self> {
359        let mut tx = N::TransactionRequest::default();
360
361        let chain = utils::get_chain(config.chain, &provider).await?;
362        let etherscan_api_key = config.get_etherscan_api_key(Some(chain));
363        // mark it as legacy if requested or the chain is legacy and no 7702 is provided.
364        let legacy = tx_opts.legacy || (chain.is_legacy() && tx_opts.auth.is_empty());
365
366        // Apply gas, value, fee, and network-specific options.
367        tx_opts.apply::<N>(&mut tx, legacy);
368
369        Ok(Self {
370            provider,
371            tx,
372            legacy,
373            blob: tx_opts.blob,
374            eip4844: tx_opts.eip4844,
375            fill: true,
376            chain,
377            etherscan_api_key,
378            auth: tx_opts.auth,
379            access_list: tx_opts.access_list,
380            state: InitState,
381        })
382    }
383
384    /// Sets [TxKind] for this builder and changes state to [ToState].
385    pub async fn with_to(self, to: Option<NameOrAddress>) -> Result<CastTxBuilder<N, P, ToState>> {
386        let to = if let Some(to) = to { Some(to.resolve(&self.provider).await?) } else { None };
387        Ok(CastTxBuilder {
388            provider: self.provider,
389            tx: self.tx,
390            legacy: self.legacy,
391            blob: self.blob,
392            eip4844: self.eip4844,
393            fill: self.fill,
394            chain: self.chain,
395            etherscan_api_key: self.etherscan_api_key,
396            auth: self.auth,
397            access_list: self.access_list,
398            state: ToState { to },
399        })
400    }
401}
402
403impl<N: Network, P: Provider<N>> CastTxBuilder<N, P, ToState>
404where
405    N::TransactionRequest: FoundryTransactionBuilder<N>,
406{
407    /// Accepts user-provided code, sig and args params and constructs calldata for the transaction.
408    /// If code is present, input will be set to code + encoded constructor arguments. If no code is
409    /// present, input is set to just provided arguments.
410    pub async fn with_code_sig_and_args(
411        self,
412        code: Option<String>,
413        sig: Option<String>,
414        args: Vec<String>,
415    ) -> Result<CastTxBuilder<N, P, InputState>> {
416        let (mut args, func) = if let Some(sig) = sig {
417            parse_function_args(
418                &sig,
419                args,
420                self.state.to,
421                self.chain,
422                &self.provider,
423                self.etherscan_api_key.as_deref(),
424            )
425            .await?
426        } else {
427            (Vec::new(), None)
428        };
429
430        let input = if let Some(code) = &code {
431            let mut code = hex::decode(code)?;
432            code.append(&mut args);
433            code
434        } else {
435            args
436        };
437
438        if self.state.to.is_none() && code.is_none() {
439            let has_value = self.tx.value().is_some_and(|v| !v.is_zero());
440            let has_auth = !self.auth.is_empty();
441            // We only allow user to omit the recipient address if transaction is an EIP-7702 tx
442            // without a value.
443            if !has_auth || has_value {
444                eyre::bail!("Must specify a recipient address or contract code to deploy");
445            }
446        }
447
448        Ok(CastTxBuilder {
449            provider: self.provider,
450            tx: self.tx,
451            legacy: self.legacy,
452            blob: self.blob,
453            eip4844: self.eip4844,
454            fill: self.fill,
455            chain: self.chain,
456            etherscan_api_key: self.etherscan_api_key,
457            auth: self.auth,
458            access_list: self.access_list,
459            state: InputState { kind: self.state.to.into(), input, func },
460        })
461    }
462}
463
464impl<N: Network, P: Provider<N>> CastTxBuilder<N, P, InputState>
465where
466    N::TransactionRequest: FoundryTransactionBuilder<N>,
467{
468    /// Builds the TransactionRequest. Fills gas, fees and nonce unless [`raw`](Self::raw) was
469    /// called.
470    pub async fn build(
471        self,
472        sender: impl Into<SenderKind<'_>>,
473    ) -> Result<(N::TransactionRequest, Option<Function>)> {
474        let fill = self.fill;
475        self._build(sender, fill).await
476    }
477
478    async fn _build(
479        mut self,
480        sender: impl Into<SenderKind<'_>>,
481        fill: bool,
482    ) -> Result<(N::TransactionRequest, Option<Function>)> {
483        // prepare
484        let sender = sender.into();
485        self.prepare(&sender);
486
487        // For batch transactions with calls, clear `to` and `value` so the node correctly
488        // identifies this as an AA batch transaction. The `calls` field determines the actual
489        // targets. If `to` is set, `build_aa()` would add a spurious extra call.
490        self.tx.clear_batch_to();
491
492        // resolve
493        let tx_nonce = self.resolve_nonce(sender.address(), fill).await?;
494        self.resolve_auth(&sender, tx_nonce).await?;
495        self.resolve_access_list().await?;
496
497        // fill
498        if fill {
499            self.fill_fees().await?;
500        }
501
502        Ok((self.tx, self.state.func))
503    }
504
505    /// Sets the core transaction fields from the builder state: kind, input, from, and chain id.
506    fn prepare(&mut self, sender: &SenderKind<'_>) {
507        self.tx.set_kind(self.state.kind);
508        // We set both fields to the same value because some nodes only accept the legacy
509        // `data` field: https://github.com/foundry-rs/foundry/issues/7764#issuecomment-2210453249
510        self.tx.set_input_kind(self.state.input.clone(), TransactionInputKind::Both);
511        self.tx.set_from(sender.address());
512        self.tx.set_chain_id(self.chain.id());
513    }
514
515    /// Resolves the transaction nonce. Returns the existing nonce or fetches one from the
516    /// provider. Only sets it on the transaction when `fill` is true.
517    async fn resolve_nonce(&mut self, from: Address, fill: bool) -> Result<u64> {
518        if let Some(nonce) = self.tx.nonce() {
519            Ok(nonce)
520        } else {
521            let nonce = self.provider.get_transaction_count(from).await?;
522            if fill {
523                self.tx.set_nonce(nonce);
524            }
525            Ok(nonce)
526        }
527    }
528
529    /// Resolves the access list. Fetches from the provider if `--access-list` was passed without
530    /// a value.
531    async fn resolve_access_list(&mut self) -> Result<()> {
532        if let Some(access_list) = match self.access_list.take() {
533            None => None,
534            Some(None) => Some(self.provider.create_access_list(&self.tx).await?.access_list),
535            Some(Some(access_list)) => Some(access_list),
536        } {
537            self.tx.set_access_list(access_list);
538        }
539        Ok(())
540    }
541
542    /// Parses the passed --auth values and sets the authorization list on the transaction.
543    ///
544    /// If a signer is available in `sender`, address-based auths will be signed.
545    /// If no signer is available, all auths must be pre-signed.
546    async fn resolve_auth(&mut self, sender: &SenderKind<'_>, tx_nonce: u64) -> Result<()> {
547        if self.auth.is_empty() {
548            return Ok(());
549        }
550
551        let auths = std::mem::take(&mut self.auth);
552
553        // Validate that at most one address-based auth is provided (multiple addresses are
554        // almost always unintended).
555        let address_auth_count =
556            auths.iter().filter(|a| matches!(a, CliAuthorizationList::Address(_))).count();
557        if address_auth_count > 1 {
558            eyre::bail!(
559                "Multiple address-based authorizations provided. Only one address can be specified; \
560                use pre-signed authorizations (hex-encoded) for multiple authorizations."
561            );
562        }
563
564        let mut signed_auths = Vec::with_capacity(auths.len());
565
566        for auth in auths {
567            let signed_auth = match auth {
568                CliAuthorizationList::Address(address) => {
569                    let auth = Authorization {
570                        chain_id: U256::from(self.chain.id()),
571                        nonce: tx_nonce + 1,
572                        address,
573                    };
574
575                    let Some(signer) = sender.as_signer() else {
576                        eyre::bail!(
577                            "No signer available to sign authorization. \
578                            Provide a pre-signed authorization (hex-encoded) instead."
579                        );
580                    };
581                    let signature = signer.sign_hash(&auth.signature_hash()).await?;
582
583                    auth.into_signed(signature)
584                }
585                CliAuthorizationList::Signed(auth) => auth,
586            };
587            signed_auths.push(signed_auth);
588        }
589
590        self.tx.set_authorization_list(signed_auths);
591
592        Ok(())
593    }
594
595    /// Fills gas price, EIP-1559 fees, blob fees, and gas limit from the provider.
596    ///
597    /// Only fills values that haven't been explicitly set by the user.
598    async fn fill_fees(&mut self) -> Result<()> {
599        if self.legacy && self.tx.gas_price().is_none() {
600            self.tx.set_gas_price(self.provider.get_gas_price().await?);
601        }
602
603        if self.blob && self.tx.max_fee_per_blob_gas().is_none() {
604            self.tx.set_max_fee_per_blob_gas(self.provider.get_blob_base_fee().await?)
605        }
606
607        if !self.legacy
608            && (self.tx.max_fee_per_gas().is_none() || self.tx.max_priority_fee_per_gas().is_none())
609        {
610            let estimate = self.provider.estimate_eip1559_fees().await?;
611
612            if self.tx.max_fee_per_gas().is_none() {
613                self.tx.set_max_fee_per_gas(estimate.max_fee_per_gas);
614            }
615
616            if self.tx.max_priority_fee_per_gas().is_none() {
617                self.tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas);
618            }
619        }
620
621        if self.tx.gas_limit().is_none() {
622            self.estimate_gas().await?;
623        }
624
625        Ok(())
626    }
627
628    /// Estimate tx gas from provider call. Tries to decode custom error if execution reverted.
629    async fn estimate_gas(&mut self) -> Result<()> {
630        match self.provider.estimate_gas(self.tx.clone()).await {
631            Ok(estimated) => {
632                self.tx.set_gas_limit(estimated);
633                Ok(())
634            }
635            Err(err) => {
636                if let TransportError::ErrorResp(payload) = &err {
637                    // If execution reverted with code 3 during provider gas estimation then try
638                    // to decode custom errors and append it to the error message.
639                    if payload.code == 3
640                        && let Some(data) = &payload.data
641                        && let Ok(Some(decoded_error)) = decode_execution_revert(data).await
642                    {
643                        eyre::bail!("Failed to estimate gas: {}: {}", err, decoded_error)
644                    }
645                }
646                eyre::bail!("Failed to estimate gas: {}", err)
647            }
648        }
649    }
650
651    /// Populates the blob sidecar for the transaction if any blob data was provided.
652    pub fn with_blob_data(mut self, blob_data: Option<Vec<u8>>) -> Result<Self> {
653        let Some(blob_data) = blob_data else { return Ok(self) };
654
655        let mut coder = SidecarBuilder::<SimpleCoder>::default();
656        coder.ingest(&blob_data);
657
658        if self.eip4844 {
659            let sidecar = coder.build_4844()?;
660            self.tx.set_blob_sidecar_4844(sidecar);
661        } else {
662            let sidecar = coder.build_7594()?;
663            self.tx.set_blob_sidecar_7594(sidecar);
664        }
665
666        Ok(self)
667    }
668
669    /// Skips gas, fee and nonce filling. Use for read-only calls
670    /// (eth_call, eth_estimateGas, eth_createAccessList).
671    pub fn raw(mut self) -> Self {
672        self.fill = false;
673        self
674    }
675}
676
677/// Helper function that tries to decode custom error name and inputs from error payload data.
678async fn decode_execution_revert(data: &RawValue) -> Result<Option<String>> {
679    let err_data = serde_json::from_str::<Bytes>(data.get())?;
680    let Some(selector) = err_data.get(..4) else { return Ok(None) };
681    if let Some(known_error) =
682        SignaturesIdentifier::new(false)?.identify_error(selector.try_into().unwrap()).await
683    {
684        let mut decoded_error = known_error.name.clone();
685        if !known_error.inputs.is_empty()
686            && let Ok(error) = known_error.decode_error(&err_data)
687        {
688            write!(decoded_error, "({})", format_tokens(&error.body).format(", "))?;
689        }
690        return Ok(Some(decoded_error));
691    }
692    Ok(None)
693}