Skip to main content

cast/
args.rs

1use crate::{
2    Cast, SimpleCast,
3    cmd::erc20::IERC20,
4    opts::{Cast as CastArgs, CastSubcommand, ToBaseArgs},
5    traces::identifier::SignaturesIdentifier,
6    tx::CastTxSender,
7};
8use alloy_dyn_abi::{DynSolValue, ErrorExt, EventExt};
9use alloy_eips::eip7702::SignedAuthorization;
10use alloy_ens::{ProviderEnsExt, namehash};
11use alloy_network::Ethereum;
12use alloy_primitives::{Address, B256, eip191_hash_message, hex, keccak256};
13use alloy_provider::Provider;
14use alloy_rpc_types::{BlockId, BlockNumberOrTag::Latest};
15use clap::{CommandFactory, Parser};
16use clap_complete::generate;
17use eyre::{Result, WrapErr};
18use foundry_cli::utils::{self, LoadConfig};
19use foundry_common::{
20    abi::{get_error, get_event},
21    fmt::{format_tokens, format_uint_exp, serialize_value_as_json},
22    fs,
23    provider::ProviderBuilder,
24    selectors::{
25        ParsedSignatures, SelectorImportData, SelectorKind, decode_calldata, decode_event_topic,
26        decode_function_selector, decode_selectors, import_selectors, parse_signatures,
27        pretty_calldata,
28    },
29    shell, stdin,
30};
31use foundry_evm_networks::NetworkVariant;
32#[cfg(feature = "optimism")]
33use op_alloy_network::Optimism;
34use std::time::Instant;
35use tempo_alloy::TempoNetwork;
36
37/// Run the `cast` command-line interface.
38pub fn run() -> Result<()> {
39    // Pre-parse discovery flags run before `setup()` so they cannot be blocked
40    // by panic-handler / tracing init failures and avoid that init's cost.
41    foundry_cli::opts::GlobalArgs::check_introspect::<CastArgs>();
42    foundry_cli::opts::GlobalArgs::check_markdown_help::<CastArgs>();
43
44    setup()?;
45
46    let args = CastArgs::parse();
47    args.global.init()?;
48    args.global.tokio_runtime().block_on(run_command(args))
49}
50
51/// Setup the global logger and other utilities.
52pub fn setup() -> Result<()> {
53    utils::common_setup();
54    utils::subscriber();
55
56    Ok(())
57}
58
59/// Run the subcommand.
60#[allow(clippy::large_stack_frames)]
61pub async fn run_command(args: CastArgs) -> Result<()> {
62    match args.cmd {
63        // Constants
64        CastSubcommand::MaxInt { r#type } => {
65            sh_println!("{}", SimpleCast::max_int(&r#type)?)?;
66        }
67        CastSubcommand::MinInt { r#type } => {
68            sh_println!("{}", SimpleCast::min_int(&r#type)?)?;
69        }
70        CastSubcommand::MaxUint { r#type } => {
71            sh_println!("{}", SimpleCast::max_int(&r#type)?)?;
72        }
73        CastSubcommand::AddressZero => {
74            sh_println!("{:?}", Address::ZERO)?;
75        }
76        CastSubcommand::HashZero => {
77            sh_println!("{:?}", B256::ZERO)?;
78        }
79
80        // Conversions & transformations
81        CastSubcommand::FromUtf8 { text } => {
82            let value = stdin::unwrap(text, false)?;
83            sh_println!("{}", SimpleCast::from_utf8(&value))?
84        }
85        CastSubcommand::ToAscii { hexdata } => {
86            let value = stdin::unwrap(hexdata, false)?;
87            sh_println!("{}", SimpleCast::to_ascii(value.trim())?)?
88        }
89        CastSubcommand::ToUtf8 { hexdata } => {
90            let value = stdin::unwrap(hexdata, false)?;
91            sh_println!("{}", SimpleCast::to_utf8(&value)?)?
92        }
93        CastSubcommand::FromFixedPoint { value, decimals } => {
94            let (value, decimals) = stdin::unwrap2(value, decimals)?;
95            sh_println!("{}", SimpleCast::from_fixed_point(&value, &decimals)?)?
96        }
97        CastSubcommand::ToFixedPoint { value, decimals } => {
98            let (value, decimals) = stdin::unwrap2(value, decimals)?;
99            sh_println!("{}", SimpleCast::to_fixed_point(&value, &decimals)?)?
100        }
101        CastSubcommand::ConcatHex { data } => {
102            if data.is_empty() {
103                let s = stdin::read(true)?;
104                sh_println!("{}", SimpleCast::concat_hex(s.split_whitespace()))?
105            } else {
106                sh_println!("{}", SimpleCast::concat_hex(data))?
107            }
108        }
109        CastSubcommand::FromBin => {
110            let hex = stdin::read_bytes(false)?;
111            sh_println!("{}", hex::encode_prefixed(hex))?
112        }
113        CastSubcommand::ToHexdata { input } => {
114            let value = stdin::unwrap_line(input)?;
115            let output = match value {
116                s if s.starts_with('@') => hex::encode(std::env::var(&s[1..])?),
117                s if s.starts_with('/') => hex::encode(fs::read(s)?),
118                s => s.split(':').map(|s| s.trim_start_matches("0x").to_lowercase()).collect(),
119            };
120            sh_println!("0x{output}")?
121        }
122        CastSubcommand::ToCheckSumAddress { address, chain_id } => {
123            let value = stdin::unwrap_line(address)?;
124            sh_println!("{}", value.to_checksum(chain_id))?
125        }
126        CastSubcommand::ToUint256 { value } => {
127            let value = stdin::unwrap_line(value)?;
128            sh_println!("{}", SimpleCast::to_uint256(&value)?)?
129        }
130        CastSubcommand::ToInt256 { value } => {
131            let value = stdin::unwrap_line(value)?;
132            sh_println!("{}", SimpleCast::to_int256(&value)?)?
133        }
134        CastSubcommand::ToUnit { value, unit } => {
135            let value = stdin::unwrap_line(value)?;
136            sh_println!("{}", SimpleCast::to_unit(&value, &unit)?)?
137        }
138        CastSubcommand::ParseUnits { value, unit } => {
139            let value = stdin::unwrap_line(value)?;
140            sh_println!("{}", SimpleCast::parse_units(&value, unit)?)?;
141        }
142        CastSubcommand::FormatUnits { value, unit } => {
143            let value = stdin::unwrap_line(value)?;
144            sh_println!("{}", SimpleCast::format_units(&value, unit)?)?;
145        }
146        CastSubcommand::FromWei { value, unit } => {
147            let value = stdin::unwrap_line(value)?;
148            sh_println!("{}", SimpleCast::from_wei(&value, &unit)?)?
149        }
150        CastSubcommand::ToWei { value, unit } => {
151            let value = stdin::unwrap_line(value)?;
152            sh_println!("{}", SimpleCast::to_wei(&value, &unit)?)?
153        }
154        CastSubcommand::FromRlp { value, as_int } => {
155            let value = stdin::unwrap_line(value)?;
156            sh_println!("{}", SimpleCast::from_rlp(value, as_int)?)?
157        }
158        CastSubcommand::ToRlp { value } => {
159            let value = stdin::unwrap_line(value)?;
160            sh_println!("{}", SimpleCast::to_rlp(&value)?)?
161        }
162        CastSubcommand::ToHex(ToBaseArgs { value, base_in }) => {
163            let value = stdin::unwrap_line(value)?;
164            sh_println!("{}", SimpleCast::to_base(&value, base_in.as_deref(), "hex")?)?
165        }
166        CastSubcommand::ToDec(ToBaseArgs { value, base_in }) => {
167            let value = stdin::unwrap_line(value)?;
168            sh_println!("{}", SimpleCast::to_base(&value, base_in.as_deref(), "dec")?)?
169        }
170        CastSubcommand::ToBase { base: ToBaseArgs { value, base_in }, base_out } => {
171            let (value, base_out) = stdin::unwrap2(value, base_out)?;
172            sh_println!("{}", SimpleCast::to_base(&value, base_in.as_deref(), &base_out)?)?
173        }
174        CastSubcommand::ToBytes32 { bytes } => {
175            let value = stdin::unwrap_line(bytes)?;
176            sh_println!("{}", SimpleCast::to_bytes32(&value)?)?
177        }
178        CastSubcommand::Pad { data, right, left: _, len } => {
179            let value = stdin::unwrap_line(data)?;
180            sh_println!("{}", SimpleCast::pad(&value, right, len)?)?
181        }
182        CastSubcommand::FormatBytes32String { string } => {
183            let value = stdin::unwrap_line(string)?;
184            sh_println!("{}", SimpleCast::format_bytes32_string(&value)?)?
185        }
186        CastSubcommand::ParseBytes32String { bytes } => {
187            let value = stdin::unwrap_line(bytes)?;
188            sh_println!("{}", SimpleCast::parse_bytes32_string(&value)?)?
189        }
190        CastSubcommand::ParseBytes32Address { bytes } => {
191            let value = stdin::unwrap_line(bytes)?;
192            sh_println!("{}", SimpleCast::parse_bytes32_address(&value)?)?
193        }
194
195        // ABI encoding & decoding
196        CastSubcommand::DecodeAbi { sig, calldata, input } => {
197            let tokens = SimpleCast::abi_decode(&sig, &calldata, input)?;
198            print_tokens(&tokens);
199        }
200        CastSubcommand::AbiEncode { sig, packed, args } => {
201            if packed {
202                sh_println!("{}", SimpleCast::abi_encode_packed(&sig, &args)?)?
203            } else {
204                sh_println!("{}", SimpleCast::abi_encode(&sig, &args)?)?
205            }
206        }
207        CastSubcommand::AbiEncodeEvent { sig, args } => {
208            let log_data = SimpleCast::abi_encode_event(&sig, &args)?;
209            for (i, topic) in log_data.topics().iter().enumerate() {
210                sh_println!("[topic{}]: {}", i, topic)?;
211            }
212            if !log_data.data.is_empty() {
213                sh_println!("[data]: {}", hex::encode_prefixed(log_data.data))?;
214            }
215        }
216        CastSubcommand::DecodeCalldata { sig, calldata, file } => {
217            let raw_hex = if let Some(file_path) = file {
218                let contents = fs::read_to_string(&file_path)?;
219                contents.trim().to_string()
220            } else {
221                calldata.unwrap()
222            };
223
224            let tokens = SimpleCast::calldata_decode(&sig, &raw_hex, true)?;
225            print_tokens(&tokens);
226        }
227        CastSubcommand::CalldataEncode { sig, args, file } => {
228            let final_args = if let Some(file_path) = file {
229                let contents = fs::read_to_string(file_path)?;
230                contents
231                    .lines()
232                    .map(str::trim)
233                    .filter(|line| !line.is_empty())
234                    .map(String::from)
235                    .collect()
236            } else {
237                args
238            };
239            sh_println!("{}", SimpleCast::calldata_encode(sig, &final_args)?)?;
240        }
241        CastSubcommand::DecodeString { data } => {
242            let tokens = SimpleCast::calldata_decode("Any(string)", &data, true)?;
243            print_tokens(&tokens);
244        }
245        CastSubcommand::DecodeEvent { sig, data } => {
246            let decoded_event = if let Some(event_sig) = sig {
247                let event = get_event(event_sig.as_str())?;
248                event.decode_log_parts(core::iter::once(event.selector()), &hex::decode(data)?)?
249            } else {
250                let data = crate::strip_0x(&data);
251                let selector = data.get(..64).unwrap_or_default();
252                let selector = selector.parse()?;
253                let identified_event =
254                    SignaturesIdentifier::new(false)?.identify_event(selector).await;
255                if let Some(event) = identified_event {
256                    let _ = sh_println!("{}", event.signature());
257                    let data = data.get(64..).unwrap_or_default();
258                    get_event(event.signature().as_str())?
259                        .decode_log_parts(core::iter::once(selector), &hex::decode(data)?)?
260                } else {
261                    eyre::bail!("No matching event signature found for selector `{selector}`")
262                }
263            };
264            print_tokens(&decoded_event.body);
265        }
266        CastSubcommand::DecodeError { sig, data } => {
267            let error = if let Some(err_sig) = sig {
268                get_error(err_sig.as_str())?
269            } else {
270                let data = crate::strip_0x(&data);
271                let selector = data.get(..8).unwrap_or_default();
272                let identified_error =
273                    SignaturesIdentifier::new(false)?.identify_error(selector.parse()?).await;
274                if let Some(error) = identified_error {
275                    let _ = sh_println!("{}", error.signature());
276                    error
277                } else {
278                    eyre::bail!("No matching error signature found for selector `{selector}`")
279                }
280            };
281            let decoded_error = error.decode_error(&hex::decode(data)?)?;
282            print_tokens(&decoded_error.body);
283        }
284        CastSubcommand::Interface(cmd) => cmd.run().await?,
285        CastSubcommand::CreationCode(cmd) => cmd.run().await?,
286        CastSubcommand::ConstructorArgs(cmd) => cmd.run().await?,
287        CastSubcommand::Artifact(cmd) => cmd.run().await?,
288        CastSubcommand::Bind(cmd) => cmd.run().await?,
289        CastSubcommand::B2EPayload(cmd) => cmd.run().await?,
290        CastSubcommand::PrettyCalldata { calldata, offline } => {
291            let calldata = stdin::unwrap_line(calldata)?;
292            sh_println!("{}", pretty_calldata(&calldata, offline).await?)?;
293        }
294        CastSubcommand::Sig { sig, optimize } => {
295            let sig = stdin::unwrap_line(sig)?;
296            match optimize {
297                Some(opt) => {
298                    sh_println!("Starting to optimize signature...")?;
299                    let start_time = Instant::now();
300                    let (selector, signature) = SimpleCast::get_selector(&sig, opt)?;
301                    sh_println!("Successfully generated in {:?}", start_time.elapsed())?;
302                    sh_println!("Selector: {selector}")?;
303                    sh_println!("Optimized signature: {signature}")?;
304                }
305                None => sh_println!("{}", SimpleCast::get_selector(&sig, 0)?.0)?,
306            }
307        }
308
309        // Blockchain & RPC queries
310        CastSubcommand::AccessList(cmd) => cmd.run().await?,
311        CastSubcommand::Age { block, rpc } => {
312            let config = rpc.load_config()?;
313            let provider = utils::get_provider(&config)?;
314            sh_println!(
315                "{} UTC",
316                Cast::new(provider).age(block.unwrap_or(BlockId::Number(Latest))).await?
317            )?
318        }
319        CastSubcommand::Balance { block, who, ether, rpc, erc20 } => {
320            let config = rpc.load_config()?;
321            let provider = utils::get_provider(&config)?;
322            let account_addr = who.resolve(&provider).await?;
323
324            match erc20 {
325                Some(token) => {
326                    let balance = IERC20::new(token, &provider)
327                        .balanceOf(account_addr)
328                        .block(block.unwrap_or_default())
329                        .call()
330                        .await?;
331
332                    sh_warn!("--erc20 flag is deprecated, use `cast erc20 balance` instead")?;
333                    sh_println!("{}", format_uint_exp(balance))?
334                }
335                None => {
336                    let value = Cast::new(&provider).balance(account_addr, block).await?;
337                    if ether {
338                        sh_println!("{}", SimpleCast::from_wei(&value.to_string(), "eth")?)?
339                    } else {
340                        sh_println!("{value}")?
341                    }
342                }
343            }
344        }
345        CastSubcommand::BaseFee { block, rpc } => {
346            let config = rpc.load_config()?;
347            let provider = utils::get_provider(&config)?;
348            sh_println!(
349                "{}",
350                Cast::new(provider).base_fee(block.unwrap_or(BlockId::Number(Latest))).await?
351            )?
352        }
353        CastSubcommand::Block { block, full, fields, raw, rpc, network } => {
354            let config = rpc.load_config()?;
355            // Can use either --raw or specify raw as a field
356            let output = if raw || fields.contains(&"raw".into()) {
357                match network {
358                    #[cfg(feature = "optimism")]
359                    Some(NetworkVariant::Optimism) => {
360                        let provider =
361                            ProviderBuilder::<Optimism>::from_config(&config)?.build()?;
362
363                        Cast::new(&provider)
364                            .block_raw(block.unwrap_or(BlockId::Number(Latest)), full)
365                            .await?
366                    }
367                    Some(NetworkVariant::Tempo) => {
368                        let provider =
369                            ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
370                        Cast::new(&provider)
371                            .block_raw(block.unwrap_or(BlockId::Number(Latest)), full)
372                            .await?
373                    }
374                    // Ethereum (default) or no --raw flag
375                    _ => {
376                        let provider =
377                            ProviderBuilder::<Ethereum>::from_config(&config)?.build()?;
378                        Cast::new(&provider)
379                            .block_raw(block.unwrap_or(BlockId::Number(Latest)), full)
380                            .await?
381                    }
382                }
383            } else {
384                let provider = utils::get_provider(&config)?;
385                Cast::new(provider)
386                    .block(block.unwrap_or(BlockId::Number(Latest)), full, fields)
387                    .await?
388            };
389            sh_println!("{}", output)?
390        }
391        CastSubcommand::BlockNumber { rpc, block } => {
392            let config = rpc.load_config()?;
393            let provider = utils::get_provider(&config)?;
394            let number = match block {
395                Some(id) => {
396                    provider
397                        .get_block(id)
398                        .await?
399                        .ok_or_else(|| eyre::eyre!("block {id:?} not found"))?
400                        .header
401                        .number
402                }
403                None => Cast::new(provider).block_number().await?,
404            };
405            sh_println!("{number}")?
406        }
407        CastSubcommand::Chain { rpc } => {
408            let config = rpc.load_config()?;
409            let provider = utils::get_provider(&config)?;
410            sh_println!("{}", Cast::new(provider).chain().await?)?
411        }
412        CastSubcommand::ChainId { rpc } => {
413            let config = rpc.load_config()?;
414            let provider = utils::get_provider(&config)?;
415            sh_println!("{}", Cast::new(provider).chain_id().await?)?
416        }
417        CastSubcommand::Client { rpc } => {
418            let config = rpc.load_config()?;
419            let provider = utils::get_provider(&config)?;
420            sh_println!("{}", provider.get_client_version().await?)?
421        }
422        CastSubcommand::Code { block, who, disassemble, rpc } => {
423            let config = rpc.load_config()?;
424            let provider = utils::get_provider(&config)?;
425            let who = who.resolve(&provider).await?;
426            sh_println!("{}", Cast::new(provider).code(who, block, disassemble).await?)?
427        }
428        CastSubcommand::Codesize { block, who, rpc } => {
429            let config = rpc.load_config()?;
430            let provider = utils::get_provider(&config)?;
431            let who = who.resolve(&provider).await?;
432            sh_println!("{}", Cast::new(provider).codesize(who, block).await?)?
433        }
434        CastSubcommand::ComputeAddress { address, nonce, salt, init_code, init_code_hash, rpc } => {
435            let address = stdin::unwrap_line(address)?;
436            let computed = {
437                // For CREATE2, init_code_hash is needed to compute the address
438                if let Some(init_code_hash) = init_code_hash {
439                    address.create2(salt.unwrap_or(B256::ZERO), init_code_hash)
440                } else if let Some(init_code) = init_code {
441                    address.create2(salt.unwrap_or(B256::ZERO), keccak256(hex::decode(init_code)?))
442                } else {
443                    // For CREATE, rpc is needed to compute the address
444                    let config = rpc.load_config()?;
445                    let provider = utils::get_provider(&config)?;
446                    Cast::new(provider).compute_address(address, nonce).await?
447                }
448            };
449            sh_println!("Computed Address: {}", computed.to_checksum(None))?
450        }
451        CastSubcommand::Disassemble { bytecode } => {
452            let bytecode = stdin::unwrap_line(bytecode)?;
453            sh_println!("{}", SimpleCast::disassemble(&hex::decode(bytecode)?)?)?
454        }
455        CastSubcommand::Selectors { bytecode, resolve } => {
456            let bytecode = stdin::unwrap_line(bytecode)?;
457            let functions = SimpleCast::extract_functions(&bytecode)?;
458            let max_args_len = functions.iter().map(|r| r.1.len()).max().unwrap_or(0);
459            let max_mutability_len = functions.iter().map(|r| r.2.len()).max().unwrap_or(0);
460
461            let resolve_results = if resolve {
462                let selectors = functions
463                    .iter()
464                    .map(|&(selector, ..)| SelectorKind::Function(selector))
465                    .collect::<Vec<_>>();
466                let ds = decode_selectors(&selectors).await?;
467                ds.into_iter().map(|v| v.join("|")).collect()
468            } else {
469                vec![]
470            };
471            for (pos, (selector, arguments, state_mutability)) in functions.into_iter().enumerate()
472            {
473                if resolve {
474                    let resolved = &resolve_results[pos];
475                    sh_println!(
476                        "{selector}\t{arguments:max_args_len$}\t{state_mutability:max_mutability_len$}\t{resolved}"
477                    )?
478                } else {
479                    sh_println!("{selector}\t{arguments:max_args_len$}\t{state_mutability}")?
480                }
481            }
482        }
483        CastSubcommand::FindBlock(cmd) => cmd.run().await?,
484        CastSubcommand::GasPrice { rpc } => {
485            let config = rpc.load_config()?;
486            let provider = utils::get_provider(&config)?;
487            sh_println!("{}", Cast::new(provider).gas_price().await?)?;
488        }
489        CastSubcommand::Index { key_type, key, slot_number } => {
490            sh_println!("{}", SimpleCast::index(&key_type, &key, &slot_number)?)?;
491        }
492        CastSubcommand::IndexErc7201 { id, formula_id } => {
493            eyre::ensure!(formula_id == "erc7201", "unsupported formula ID: {formula_id}");
494            let id = stdin::unwrap_line(id)?;
495            sh_println!("{}", foundry_common::erc7201(&id))?;
496        }
497        CastSubcommand::Implementation { block, beacon, who, rpc } => {
498            let config = rpc.load_config()?;
499            let provider = utils::get_provider(&config)?;
500            let who = who.resolve(&provider).await?;
501            sh_println!("{}", Cast::new(provider).implementation(who, beacon, block).await?)?;
502        }
503        CastSubcommand::Admin { block, who, rpc } => {
504            let config = rpc.load_config()?;
505            let provider = utils::get_provider(&config)?;
506            let who = who.resolve(&provider).await?;
507            sh_println!("{}", Cast::new(provider).admin(who, block).await?)?;
508        }
509        CastSubcommand::Nonce { block, who, rpc } => {
510            let config = rpc.load_config()?;
511            let provider = utils::get_provider(&config)?;
512            let who = who.resolve(&provider).await?;
513            sh_println!("{}", Cast::new(provider).nonce(who, block).await?)?;
514        }
515        CastSubcommand::Codehash { block, who, slots, rpc } => {
516            let config = rpc.load_config()?;
517            let provider = utils::get_provider(&config)?;
518            let who = who.resolve(&provider).await?;
519            sh_println!("{}", Cast::new(provider).codehash(who, slots, block).await?)?;
520        }
521        CastSubcommand::StorageRoot { block, who, slots, rpc } => {
522            let config = rpc.load_config()?;
523            let provider = utils::get_provider(&config)?;
524            let who = who.resolve(&provider).await?;
525            sh_println!("{}", Cast::new(provider).storage_root(who, slots, block).await?)?;
526        }
527        CastSubcommand::Proof { address, slots, rpc, block } => {
528            let config = rpc.load_config()?;
529            let provider = utils::get_provider(&config)?;
530            let address = address.resolve(&provider).await?;
531            let value = provider
532                .get_proof(address, slots.into_iter().collect())
533                .block_id(block.unwrap_or_default())
534                .await?;
535            sh_println!("{}", serde_json::to_string(&value)?)?;
536        }
537        CastSubcommand::Rpc(cmd) => cmd.run().await?,
538        CastSubcommand::Storage(cmd) => cmd.run().await?,
539
540        // Calls & transactions
541        CastSubcommand::Call(cmd) => cmd.run().await?,
542        CastSubcommand::Estimate(cmd) => cmd.run().await?,
543        CastSubcommand::MakeTx(cmd) => cmd.run().await?,
544        CastSubcommand::PublishTx { raw_tx, cast_async, rpc } => {
545            let config = rpc.load_config()?;
546            let provider = utils::get_provider(&config)?;
547            let cast = Cast::new(&provider);
548            let pending_tx = cast.publish(raw_tx).await?;
549            let tx_hash = pending_tx.inner().tx_hash();
550
551            if cast_async {
552                sh_println!("{tx_hash:#x}")?;
553            } else {
554                let receipt = pending_tx.get_receipt().await?;
555                sh_println!("{}", serde_json::json!(receipt))?;
556            }
557        }
558        CastSubcommand::Receipt { tx_hash, field, cast_async, confirmations, rpc } => {
559            let config = rpc.load_config()?;
560            let provider = utils::get_provider(&config)?;
561            sh_println!(
562                "{}",
563                CastTxSender::new(provider)
564                    .receipt(tx_hash, field, confirmations, None, cast_async)
565                    .await?
566            )?
567        }
568        CastSubcommand::Run(cmd) => cmd.run().await?,
569        CastSubcommand::SendTx(cmd) => cmd.run().await?,
570        CastSubcommand::BatchMakeTx(cmd) => cmd.run().await?,
571        CastSubcommand::BatchSend(cmd) => cmd.run().await?,
572        CastSubcommand::Tx { tx_hash, from, nonce, field, raw, rpc, to_request, network } => {
573            let config = rpc.load_config()?;
574            // Can use either --raw or specify raw as a field
575            let is_raw = raw || field.as_ref().is_some_and(|f| f == "raw");
576            let output = match network {
577                #[cfg(feature = "optimism")]
578                Some(NetworkVariant::Optimism) => {
579                    let provider = ProviderBuilder::<Optimism>::from_config(&config)?.build()?;
580
581                    Cast::new(&provider)
582                        .transaction(tx_hash, from, nonce, field, is_raw, to_request)
583                        .await?
584                }
585                Some(NetworkVariant::Tempo) => {
586                    let provider =
587                        ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
588                    Cast::new(&provider)
589                        .transaction(tx_hash, from, nonce, field, is_raw, to_request)
590                        .await?
591                }
592                // Ethereum (default) or no --raw flag
593                _ => {
594                    let provider = utils::get_provider(&config)?;
595                    Cast::new(&provider)
596                        .transaction(tx_hash, from, nonce, field, is_raw, to_request)
597                        .await?
598                }
599            };
600            sh_println!("{}", output)?
601        }
602
603        // 4Byte
604        CastSubcommand::FourByte { selector } => {
605            let selector = stdin::unwrap_line(selector)?;
606            let sigs = decode_function_selector(selector).await?;
607            if sigs.is_empty() {
608                eyre::bail!("No matching function signatures found for selector `{selector}`");
609            }
610            for sig in sigs {
611                sh_println!("{sig}")?
612            }
613        }
614
615        CastSubcommand::FourByteCalldata { calldata } => {
616            let calldata = stdin::unwrap_line(calldata)?;
617
618            if calldata.len() == 10 {
619                let sigs = decode_function_selector(calldata.parse()?).await?;
620                if sigs.is_empty() {
621                    eyre::bail!("No matching function signatures found for calldata `{calldata}`");
622                }
623                for sig in sigs {
624                    sh_println!("{sig}")?
625                }
626                return Ok(());
627            }
628
629            let sigs = decode_calldata(&calldata).await?;
630            sigs.iter().enumerate().for_each(|(i, sig)| {
631                let _ = sh_println!("{}) \"{sig}\"", i + 1);
632            });
633
634            let sig = match sigs.len() {
635                0 => eyre::bail!("No signatures found"),
636                1 => sigs.first().unwrap(),
637                _ => {
638                    let i: usize = prompt!("Select a function signature by number: ")?;
639                    sigs.get(i - 1).ok_or_else(|| eyre::eyre!("Invalid signature index"))?
640                }
641            };
642
643            let tokens = SimpleCast::calldata_decode(sig, &calldata, true)?;
644            print_tokens(&tokens);
645        }
646
647        CastSubcommand::FourByteEvent { topic } => {
648            let topic = stdin::unwrap_line(topic)?;
649            let sigs = decode_event_topic(topic).await?;
650            if sigs.is_empty() {
651                eyre::bail!("No matching event signatures found for topic `{topic}`");
652            }
653            for sig in sigs {
654                sh_println!("{sig}")?
655            }
656        }
657        CastSubcommand::UploadSignature { signatures } => {
658            let signatures = stdin::unwrap_vec(signatures)?;
659            let ParsedSignatures { signatures, abis } = parse_signatures(signatures);
660            if !abis.is_empty() {
661                import_selectors(SelectorImportData::Abi(abis)).await?.describe();
662            }
663            if !signatures.is_empty() {
664                import_selectors(SelectorImportData::Raw(signatures)).await?.describe();
665            }
666        }
667
668        // ENS
669        CastSubcommand::Namehash { name } => {
670            let name = stdin::unwrap_line(name)?;
671            sh_println!("{}", namehash(&name))?
672        }
673        CastSubcommand::LookupAddress { who, rpc, verify } => {
674            let config = rpc.load_config()?;
675            let provider = utils::get_provider(&config)?;
676
677            let who = stdin::unwrap_line(who)?;
678            let name = provider.lookup_address(&who).await?;
679            if verify {
680                let address = provider.resolve_name(&name).await?;
681                eyre::ensure!(
682                    address == who,
683                    "Reverse lookup verification failed: got `{address}`, expected `{who}`"
684                );
685            }
686            sh_println!("{name}")?
687        }
688        CastSubcommand::ResolveName { who, rpc, verify } => {
689            let config = rpc.load_config()?;
690            let provider = utils::get_provider(&config)?;
691
692            let who = stdin::unwrap_line(who)?;
693            let address = provider
694                .resolve_name(&who)
695                .await
696                .wrap_err(format!("Failed to resolve ENS name: {who}"))?;
697            if verify {
698                let name = provider.lookup_address(&address).await?;
699                eyre::ensure!(
700                    name == who,
701                    "Forward lookup verification failed: got `{name}`, expected `{who}`"
702                );
703            }
704            sh_println!("{address}")?
705        }
706
707        // Misc
708        CastSubcommand::Keccak { data } => {
709            let bytes = match data {
710                Some(data) => data.into_bytes(),
711                None => stdin::read_bytes(false)?,
712            };
713            match String::from_utf8(bytes) {
714                Ok(s) => {
715                    let s = SimpleCast::keccak(&s)?;
716                    sh_println!("{s}")?
717                }
718                Err(e) => {
719                    let hash = keccak256(e.as_bytes());
720                    let s = hex::encode(hash);
721                    sh_println!("0x{s}")?
722                }
723            };
724        }
725        CastSubcommand::HashMessage { message } => {
726            let message = stdin::unwrap(message, false)?;
727            sh_println!("{}", eip191_hash_message(message))?
728        }
729        CastSubcommand::SigEvent { event_string } => {
730            let event_string = stdin::unwrap_line(event_string)?;
731            let parsed_event = get_event(&event_string)?;
732            sh_println!("{:?}", parsed_event.selector())?
733        }
734        CastSubcommand::LeftShift { value, bits, base_in, base_out } => sh_println!(
735            "{}",
736            SimpleCast::left_shift(&value, &bits, base_in.as_deref(), &base_out)?
737        )?,
738        CastSubcommand::RightShift { value, bits, base_in, base_out } => sh_println!(
739            "{}",
740            SimpleCast::right_shift(&value, &bits, base_in.as_deref(), &base_out)?
741        )?,
742        CastSubcommand::Source {
743            address,
744            directory,
745            explorer_api_url,
746            explorer_url,
747            etherscan,
748            flatten,
749        } => {
750            let config = etherscan.load_config()?;
751            let chain = config.chain.unwrap_or_default();
752            let api_key = config.get_etherscan_api_key(Some(chain));
753            match (directory, flatten) {
754                (Some(dir), false) => {
755                    SimpleCast::expand_etherscan_source_to_directory(
756                        chain,
757                        address,
758                        api_key,
759                        dir,
760                        explorer_api_url,
761                        explorer_url,
762                    )
763                    .await?
764                }
765                (None, false) => sh_println!(
766                    "{}",
767                    SimpleCast::etherscan_source(
768                        chain,
769                        address,
770                        api_key,
771                        explorer_api_url,
772                        explorer_url
773                    )
774                    .await?
775                )?,
776                (dir, true) => {
777                    SimpleCast::etherscan_source_flatten(
778                        chain,
779                        address,
780                        api_key,
781                        dir,
782                        explorer_api_url,
783                        explorer_url,
784                    )
785                    .await?;
786                }
787            }
788        }
789        CastSubcommand::Create2(cmd) => {
790            cmd.run()?;
791        }
792        CastSubcommand::Wallet { command } => command.run().await?,
793        CastSubcommand::Completions { shell } => {
794            generate(shell, &mut CastArgs::command(), "cast", &mut std::io::stdout())
795        }
796        CastSubcommand::Logs(cmd) => cmd.run().await?,
797        CastSubcommand::DecodeTransaction { tx, network } => {
798            let tx = stdin::unwrap_line(tx)?;
799            let decoded_tx = match network {
800                #[cfg(feature = "optimism")]
801                Some(NetworkVariant::Optimism) => {
802                    SimpleCast::decode_raw_transaction::<Optimism>(&tx)?
803                }
804                Some(NetworkVariant::Tempo) => {
805                    SimpleCast::decode_raw_transaction::<TempoNetwork>(&tx)?
806                }
807                _ => SimpleCast::decode_raw_transaction::<Ethereum>(&tx)?,
808            };
809            sh_println!("{}", serde_json::to_string_pretty(&decoded_tx)?)?;
810        }
811        CastSubcommand::RecoverAuthority { auth } => {
812            let auth: SignedAuthorization = serde_json::from_str(&auth)?;
813            sh_println!("{}", auth.recover_authority()?)?;
814        }
815        CastSubcommand::TxPool { command } => command.run().await?,
816        CastSubcommand::Erc20Token { command } => command.run().await?,
817        CastSubcommand::Tip20Token { command } => command.run().await?,
818        CastSubcommand::Keychain { command } => command.run().await?,
819        CastSubcommand::Tempo { command } => command.run().await?,
820        CastSubcommand::VirtualAddress { command } => command.run().await?,
821        #[cfg(feature = "optimism")]
822        CastSubcommand::DAEstimate(cmd) => {
823            cmd.run().await?;
824        }
825        CastSubcommand::Trace(cmd) => cmd.run().await?,
826    };
827
828    /// Prints slice of tokens using [`format_tokens`] or [`serialize_value_as_json`] depending
829    /// whether the shell is in JSON mode.
830    ///
831    /// This is included here to avoid a cyclic dependency between `fmt` and `common`.
832    fn print_tokens(tokens: &[DynSolValue]) {
833        if shell::is_json() {
834            let tokens: Vec<serde_json::Value> = tokens
835                .iter()
836                .cloned()
837                .map(|t| serialize_value_as_json(t, None))
838                .collect::<Result<Vec<_>>>()
839                .unwrap();
840            let _ = sh_println!("{}", serde_json::to_string_pretty(&tokens).unwrap());
841        } else {
842            let tokens = format_tokens(tokens);
843            tokens.for_each(|t| {
844                let _ = sh_println!("{t}");
845            });
846        }
847    }
848
849    Ok(())
850}
851
852#[cfg(test)]
853mod tests {
854    use super::*;
855    use foundry_cli::introspect::{
856        CommandRegistry, INTROSPECT_SCHEMA_ID, IntrospectDocument, build_document,
857        duplicate_command_ids, render_introspect_document,
858    };
859
860    /// Every `command_id` exposed by `cast --introspect` MUST be unique.
861    /// This is the foundation of the agent contract — agents key on
862    /// `command_id` to identify commands, and duplicates would silently break
863    /// downstream tooling.
864    ///
865    /// Cast's clap tree is large and exhausts the default test-thread stack
866    /// (2 MiB) when constructed in debug builds, so we spawn a worker thread
867    /// with an explicit, generous stack size.
868    #[test]
869    fn introspect_command_ids_are_unique() {
870        let dups = std::thread::Builder::new()
871            .stack_size(16 * 1024 * 1024)
872            .spawn(|| {
873                let cmd = <CastArgs as clap::CommandFactory>::command();
874                let doc = build_document(&cmd, &CommandRegistry::EMPTY);
875                duplicate_command_ids(&doc)
876            })
877            .expect("spawn worker thread")
878            .join()
879            .expect("worker thread join");
880        assert!(dups.is_empty(), "duplicate cast command_ids: {dups:?}");
881    }
882
883    /// `cast --introspect` must produce a JSON document that parses back into
884    /// the canonical `IntrospectDocument` shape. Runs on a 16 MiB worker
885    /// thread for the same reason as the uniqueness check above.
886    #[test]
887    fn introspect_document_is_valid_json() {
888        let json = std::thread::Builder::new()
889            .stack_size(16 * 1024 * 1024)
890            .spawn(|| {
891                let cmd = <CastArgs as clap::CommandFactory>::command();
892                render_introspect_document(&cmd, &CommandRegistry::EMPTY)
893            })
894            .expect("spawn worker thread")
895            .join()
896            .expect("worker thread join");
897        let doc: IntrospectDocument = serde_json::from_str(&json).expect("valid JSON");
898        assert_eq!(doc.schema_id, INTROSPECT_SCHEMA_ID);
899        assert_eq!(doc.binary.name, "cast");
900    }
901}