//! Contract commands. use std::fs; use std::path::PathBuf; use anyhow::Result; use crate::client::RpcClient; use crate::config::CliConfig; use crate::output::{self, OutputFormat}; use crate::ContractCommands; /// Handle contract commands. pub async fn handle( client: &RpcClient, _config: &CliConfig, cmd: ContractCommands, format: OutputFormat, ) -> Result<()> { match cmd { ContractCommands::Deploy { wasm, deployer, args, gas } => { deploy(client, wasm, &deployer, args.as_deref(), Some(gas), format).await } ContractCommands::Call { contract_id, method, caller, args, value, gas } => { call(client, &contract_id, &method, &caller, args.as_deref(), value, Some(gas), format).await } ContractCommands::Code { contract_id } => { code(client, &contract_id, format).await } ContractCommands::Storage { contract_id, key } => { storage(client, &contract_id, &key, format).await } ContractCommands::EstimateGas { contract_id, method, caller, args, value } => { estimate_gas(client, &contract_id, &method, &caller, args.as_deref(), value, format).await } ContractCommands::Info { contract_id } => { info(client, &contract_id, format).await } } } /// Deploy a contract. async fn deploy( client: &RpcClient, wasm_path: PathBuf, deployer: &str, args: Option<&str>, gas_limit: Option, format: OutputFormat, ) -> Result<()> { // Read WASM file let wasm_bytes = fs::read(&wasm_path)?; let wasm_hex = hex::encode(&wasm_bytes); output::print_info(&format!("Deploying contract ({} bytes)...", wasm_bytes.len())); let args_hex = args.unwrap_or(""); let spinner = output::create_spinner("Deploying contract..."); let result = client.deploy_contract(&wasm_hex, args_hex, deployer, gas_limit).await?; spinner.finish_and_clear(); if let Some(error) = &result.error { match format { OutputFormat::Json => { println!("{}", serde_json::to_string_pretty(&serde_json::json!({ "success": false, "error": error }))?); } OutputFormat::Text => { output::print_error(&format!("Deployment failed: {}", error)); } } return Ok(()); } match format { OutputFormat::Json => { println!("{}", serde_json::to_string_pretty(&serde_json::json!({ "success": true, "contractId": result.contract_id, "address": result.address, "gasUsed": result.gas_used, }))?); } OutputFormat::Text => { output::print_success("Contract deployed!"); output::print_kv("Contract ID", &result.contract_id); output::print_kv("Address", &result.address); output::print_kv("Gas Used", &result.gas_used.to_string()); } } Ok(()) } /// Call a contract method. async fn call( client: &RpcClient, contract_id: &str, method: &str, caller: &str, args: Option<&str>, value: u64, gas_limit: Option, format: OutputFormat, ) -> Result<()> { let args_hex = args.unwrap_or(""); let result = client.call_contract(contract_id, method, args_hex, caller, value, gas_limit).await?; if let Some(error) = &result.error { match format { OutputFormat::Json => { output::print_value(&serde_json::json!({ "success": false, "error": error }), format); } OutputFormat::Text => { output::print_error(&format!("Contract call failed: {}", error)); } } return Ok(()); } match format { OutputFormat::Json => { output::print_value(&serde_json::json!({ "success": result.success, "data": result.data, "gasUsed": result.gas_used, "logs": result.logs }), format); } OutputFormat::Text => { output::print_header("Contract Call Result"); output::print_kv("Success", if result.success { "Yes" } else { "No" }); output::print_kv("Gas Used", &result.gas_used.to_string()); if !result.data.is_empty() { output::print_kv("Return Data", &result.data); } if !result.logs.is_empty() { println!("\nLogs:"); for (i, log) in result.logs.iter().enumerate() { println!(" [{}] Contract: {}", i, log.contract_id); for topic in &log.topics { println!(" Topic: {}", topic); } if !log.data.is_empty() { println!(" Data: {}", log.data); } } } } } Ok(()) } /// Get contract code. async fn code(client: &RpcClient, contract_id: &str, format: OutputFormat) -> Result<()> { let result = client.get_contract_code(contract_id).await?; if let Some(error) = &result.error { match format { OutputFormat::Json => { println!("{}", serde_json::to_string_pretty(&serde_json::json!({ "error": error }))?); } OutputFormat::Text => { output::print_error(&format!("Failed to get code: {}", error)); } } return Ok(()); } let code_hex = result.code.unwrap_or_default(); match format { OutputFormat::Json => { let result = serde_json::json!({ "contractId": contract_id, "code": code_hex, "size": code_hex.len() / 2, }); println!("{}", serde_json::to_string_pretty(&result)?); } OutputFormat::Text => { output::print_kv("Contract ID", contract_id); output::print_kv("Size", &format!("{} bytes", code_hex.len() / 2)); if code_hex.len() <= 256 { println!("\nCode: {}", code_hex); } else { println!("\nCode (truncated): {}...", &code_hex[..256]); } } } Ok(()) } /// Get contract storage. async fn storage(client: &RpcClient, contract_id: &str, key: &str, format: OutputFormat) -> Result<()> { let result = client.get_contract_storage(contract_id, key).await?; if let Some(error) = &result.error { match format { OutputFormat::Json => { println!("{}", serde_json::to_string_pretty(&serde_json::json!({ "error": error }))?); } OutputFormat::Text => { output::print_error(&format!("Failed to get storage: {}", error)); } } return Ok(()); } let value = result.value.unwrap_or_else(|| "null".to_string()); match format { OutputFormat::Json => { let result = serde_json::json!({ "contractId": contract_id, "key": key, "value": value, }); println!("{}", serde_json::to_string_pretty(&result)?); } OutputFormat::Text => { output::print_kv("Contract ID", contract_id); output::print_kv("Key", key); output::print_kv("Value", &value); } } Ok(()) } /// Estimate gas for a call. async fn estimate_gas( client: &RpcClient, contract_id: &str, method: &str, caller: &str, args: Option<&str>, value: u64, format: OutputFormat, ) -> Result<()> { let args_hex = args.unwrap_or(""); let result = client.estimate_gas(contract_id, method, args_hex, caller, value).await?; if let Some(error) = &result.error { match format { OutputFormat::Json => { println!("{}", serde_json::to_string_pretty(&serde_json::json!({ "error": error }))?); } OutputFormat::Text => { output::print_error(&format!("Failed to estimate gas: {}", error)); } } return Ok(()); } match format { OutputFormat::Json => { let result = serde_json::json!({ "contractId": contract_id, "method": method, "estimatedGas": result.estimated_gas, }); println!("{}", serde_json::to_string_pretty(&result)?); } OutputFormat::Text => { output::print_kv("Contract ID", contract_id); output::print_kv("Method", method); output::print_kv("Estimated Gas", &result.estimated_gas.to_string()); } } Ok(()) } /// Get contract metadata. async fn info(client: &RpcClient, contract_id: &str, format: OutputFormat) -> Result<()> { let result = client.get_contract(contract_id).await?; if let Some(error) = &result.error { match format { OutputFormat::Json => { println!("{}", serde_json::to_string_pretty(&serde_json::json!({ "error": error }))?); } OutputFormat::Text => { output::print_error(&format!("Failed to get contract info: {}", error)); } } return Ok(()); } match format { OutputFormat::Json => { println!("{}", serde_json::to_string_pretty(&serde_json::json!({ "contractId": contract_id, "codeHash": result.code_hash, "deployer": result.deployer, "deployedAt": result.deployed_at, "deployedHeight": result.deployed_height, }))?); } OutputFormat::Text => { output::print_header("Contract Info"); output::print_kv("Contract ID", contract_id); if let Some(hash) = &result.code_hash { output::print_kv("Code Hash", hash); } if let Some(deployer) = &result.deployer { output::print_kv("Deployer", deployer); } if let Some(time) = result.deployed_at { output::print_kv("Deployed At", &format!("{}", time)); } if let Some(height) = result.deployed_height { output::print_kv("Deployed Height", &format!("{}", height)); } } } Ok(()) }