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