diff --git a/src/agent_deposit.rs b/src/agent_deposit.rs index 9a826cd..6b0b95d 100644 --- a/src/agent_deposit.rs +++ b/src/agent_deposit.rs @@ -1,6 +1,6 @@ -//! AgentDeposit — Create Protocol agent funding primitive. +//! `AgentDeposit` — Create Protocol agent funding primitive. //! -//! AgentDeposit is the core contract in [Create Protocol] Phase 1: agents +//! `AgentDeposit` is the core contract in [Create Protocol] Phase 1: agents //! register, deposit USDC, spend it on compute/tasks, and the protocol //! distributes fees. This module wraps the read-only surface (balance + agent //! metadata) so an AI agent can introspect its own on-chain state using only @@ -11,7 +11,7 @@ //! eth_sendRawTransaction` (or any wallet). This keeps the CLI read-safe by //! default; no keys ever touch this binary. //! -//! **Contract addresses.** AgentDeposit is deployed on Sepolia today; Arbitrum +//! **Contract addresses.** `AgentDeposit` is deployed on Sepolia today; Arbitrum //! One redeployment lands with Phase 1. To wire the real address, update the //! [`agent_deposit_address`] match — it's a one-line swap per chain. //! @@ -21,7 +21,7 @@ use crate::rpc::{hex_to_u64, rpc_call}; use eyre::{eyre, Result}; use serde_json::{json, Value}; -/// ABI selectors for the Create Protocol AgentDeposit contract. +/// ABI selectors for the Create Protocol `AgentDeposit` contract. /// /// These match the deployed Sepolia ABI (`AgentDeposit.sol`). Kept as string /// constants so they're easy to eyeball against an ABI file or a block @@ -37,25 +37,23 @@ pub mod selectors { pub const IS_REGISTERED: &str = "0xc3c5a547"; } -/// Resolve the AgentDeposit contract address for a given chain id. +/// Resolve the `AgentDeposit` contract address for a given chain id. /// /// Returns `None` until Create Protocol Phase 1 lands on that chain. Swapping /// in a real deployment is one line per arm. /// /// - Arbitrum One (42161): placeholder — Phase 1 deploy pending /// - Arbitrum Sepolia (421614): placeholder — mirrors staging deploy -/// - All other chains: unsupported (AgentDeposit is Arbitrum-first) +/// - All other chains: unsupported (`AgentDeposit` is Arbitrum-first) pub fn agent_deposit_address(chain_id: u64) -> Option<&'static str> { match chain_id { - // TODO(create-protocol): replace with real Arbitrum One deployment - 42161 => Some("0x0000000000000000000000000000000000000000"), - // TODO(create-protocol): replace with real Arbitrum Sepolia deployment - 421614 => Some("0x0000000000000000000000000000000000000000"), + // TODO(create-protocol): replace with real Arbitrum One/Sepolia deployment + 42_161 | 421_614 => Some("0x0000000000000000000000000000000000000000"), _ => None, } } -/// What the user asked us to do with the AgentDeposit contract. +/// What the user asked us to do with the `AgentDeposit` contract. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Action { /// Read: `balanceOf(agent)` — returns USDC on deposit (raw + decimal). @@ -112,7 +110,7 @@ pub fn encode_address_call(selector: &str, address: &str) -> Result { /// Encode `selector(uint256)` calldata. /// /// The amount is a raw on-chain integer (e.g., USDC has 6 decimals, so -/// $1.00 = 1_000_000). We keep the CLI honest — no hidden decimal scaling. +/// $1.00 = `1_000_000`). We keep the CLI honest — no hidden decimal scaling. pub fn encode_uint256_call(selector: &str, amount: u128) -> Result { let sel = selector.trim_start_matches("0x"); if sel.len() != 8 { @@ -136,7 +134,7 @@ pub fn decode_uint_result(hex_word: &str) -> Result { } /// Fetch the chain id from the RPC endpoint so we can resolve the right -/// AgentDeposit deployment without asking the user. +/// `AgentDeposit` deployment without asking the user. pub async fn fetch_chain_id(rpc: &str) -> Result { let result = rpc_call(rpc, "eth_chainId", json!([])).await?; let hex = result @@ -145,7 +143,7 @@ pub async fn fetch_chain_id(rpc: &str) -> Result { hex_to_u64(hex) } -/// Call `balanceOf(agent)` on AgentDeposit. Returns raw USDC (6 decimals). +/// Call `balanceOf(agent)` on `AgentDeposit`. Returns raw USDC (6 decimals). pub async fn read_balance(rpc: &str, contract: &str, agent: &str) -> Result { let data = encode_address_call(selectors::BALANCE_OF, agent)?; let result = rpc_call( @@ -183,7 +181,8 @@ pub fn format_balance_response( // AgentDeposit settles in USDC — 6 decimals. Scaling stays local to // presentation; the raw value is always the source of truth. const USDC_DECIMALS: u32 = 6; - let balance_human = balance_raw as f64 / 10f64.powi(USDC_DECIMALS as i32); + #[allow(clippy::cast_precision_loss)] + let balance_human = balance_raw as f64 / 10f64.powi(i32::try_from(USDC_DECIMALS).unwrap_or(6)); json!({ "agent": agent, "contract": contract, @@ -304,8 +303,8 @@ mod tests { fn known_chains_resolve_address() { // Both arms return Some — the value is placeholder until Phase 1, // but the resolver contract must remain stable. - assert!(agent_deposit_address(42161).is_some()); - assert!(agent_deposit_address(421614).is_some()); + assert!(agent_deposit_address(42_161).is_some()); + assert!(agent_deposit_address(421_614).is_some()); assert!(agent_deposit_address(1).is_none()); } diff --git a/src/commands.rs b/src/commands.rs index 9f19727..84d0207 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -20,7 +20,7 @@ struct SupportedChain { const SUPPORTED_CHAINS: &[SupportedChain] = &[ SupportedChain { - chain_id: 42161, + chain_id: 42_161, name: "Arbitrum One", rpc_default: "https://arb1.arbitrum.io/rpc", explorer: "https://arbiscan.io", @@ -28,7 +28,7 @@ const SUPPORTED_CHAINS: &[SupportedChain] = &[ uniswap_v3_quoter: Some("0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6"), }, SupportedChain { - chain_id: 421614, + chain_id: 421_614, name: "Arbitrum Sepolia", rpc_default: "https://sepolia-rollup.arbitrum.io/rpc", explorer: "https://sepolia.arbiscan.io", @@ -119,7 +119,8 @@ pub async fn token_balance(rpc: &str, token: &str, address: &str, mode: Mode) -> .unwrap_or(18); let balance_raw = u128::from_str_radix(raw.trim_start_matches("0x"), 16).unwrap_or(0); - let balance_human = balance_raw as f64 / 10f64.powi(decimals as i32); + #[allow(clippy::cast_precision_loss)] + let balance_human = balance_raw as f64 / 10f64.powi(i32::try_from(decimals).unwrap_or(18)); let out = json!({ "token": token, @@ -150,6 +151,7 @@ pub async fn gas(rpc: &str, mode: Mode) -> Result<()> { let block_num = rpc_call(rpc, "eth_blockNumber", json!([])).await?; let gas_hex = gas_price.as_str().unwrap_or("0x0"); + #[allow(clippy::cast_precision_loss)] let gwei = u128::from_str_radix(gas_hex.trim_start_matches("0x"), 16).unwrap_or(0) as f64 / 1e9; let out = json!({ @@ -219,7 +221,7 @@ fn chain_inventory() -> Vec { .collect() } -fn subcommand_inventory(command: Command) -> Vec { +fn subcommand_inventory(command: &Command) -> Vec { command .get_subcommands() .map(|subcommand| { @@ -229,21 +231,21 @@ fn subcommand_inventory(command: Command) -> Vec { json!({ "name": arg.get_id().as_str(), "required": arg.is_required_set(), - "help": arg.get_help().map(|help| help.to_string()), + "help": arg.get_help().map(std::string::ToString::to_string), }) }) .collect(); json!({ "name": subcommand.get_name(), - "description": subcommand.get_about().map(|about| about.to_string()), + "description": subcommand.get_about().map(std::string::ToString::to_string), "args": args, }) }) .collect() } -pub(crate) fn info_inventory(command: Command) -> Value { +pub(crate) fn info_inventory(command: &Command) -> Value { json!({ "name": "arbitrum-cli", "version": env!("CARGO_PKG_VERSION"), @@ -300,7 +302,8 @@ fn print_info_human(inventory: &Value) { } // ── info ── -pub fn info(mode: Mode, command: Command) -> Result<()> { +#[allow(clippy::unnecessary_wraps)] +pub fn info(mode: Mode, command: &Command) -> Result<()> { let inventory = info_inventory(command); match mode { Mode::Json => emit(mode, "arbitrum-cli info", &inventory), @@ -381,7 +384,8 @@ pub async fn agent_deposit( } // ── mcp (stub) ── -pub async fn mcp(_rpc: &str, bind: &str) -> Result<()> { +#[allow(clippy::unnecessary_wraps)] +pub fn mcp(_rpc: &str, bind: &str) -> Result<()> { // MCP server stub — production version would expose tools via stdio or SSE // following the Model Context Protocol spec. eprintln!("MCP server mode — stub implementation"); diff --git a/src/main.rs b/src/main.rs index cbb1908..b966b12 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ mod rpc; long_about = "A single Rust binary to query Arbitrum, interact with contracts, monitor events, and expose an MCP server for AI agents. Default output is JSON (agent-friendly). Use --human for pretty terminal output." )] struct Cli { - /// RPC URL (default: https://arb1.arbitrum.io/rpc) + /// RPC URL (default: ) #[arg(long, global = true, env = "ARBITRUM_RPC_URL")] rpc: Option, @@ -54,7 +54,7 @@ enum Commands { address: String, }, - /// Read from a contract (eth_call) + /// Read from a contract (`eth_call`) Call { /// Contract address to: String, @@ -75,7 +75,7 @@ enum Commands { /// Execute a generic JSON-RPC call (agent-friendly) Exec { - /// RPC method name (e.g., eth_blockNumber) + /// RPC method name (e.g., `eth_blockNumber`) method: String, /// Params as JSON array @@ -90,7 +90,7 @@ enum Commands { bind: String, }, - /// Interact with Create Protocol AgentDeposit on Arbitrum + /// Interact with Create Protocol `AgentDeposit` on Arbitrum /// /// Read agent balance / registration state, or produce unsigned calldata /// for deposit/withdraw (sign + broadcast externally — the CLI never @@ -108,7 +108,7 @@ enum Commands { #[arg(long)] amount: Option, - /// Override the AgentDeposit contract address (advanced; defaults to + /// Override the `AgentDeposit` contract address (advanced; defaults to /// the deployment registered for the connected chain). #[arg(long)] contract: Option, @@ -135,15 +135,15 @@ async fn main() -> eyre::Result<()> { Commands::Tx { hash } => commands::tx(&rpc_url, &hash, out_mode).await?, Commands::Balance { address } => commands::balance(&rpc_url, &address, out_mode).await?, Commands::Token { token, address } => { - commands::token_balance(&rpc_url, &token, &address, out_mode).await? + commands::token_balance(&rpc_url, &token, &address, out_mode).await?; } Commands::Call { to, data } => commands::call(&rpc_url, &to, &data, out_mode).await?, Commands::Gas => commands::gas(&rpc_url, out_mode).await?, Commands::Watch { target } => commands::watch(&rpc_url, &target, out_mode).await?, Commands::Exec { method, params } => { - commands::exec(&rpc_url, &method, ¶ms, out_mode).await? + commands::exec(&rpc_url, &method, ¶ms, out_mode).await?; } - Commands::Mcp { bind } => commands::mcp(&rpc_url, &bind).await?, + Commands::Mcp { bind } => commands::mcp(&rpc_url, &bind)?, Commands::AgentDeposit { address, action, @@ -158,9 +158,9 @@ async fn main() -> eyre::Result<()> { contract.as_deref(), out_mode, ) - .await? + .await?; } - Commands::Info => commands::info(out_mode, Cli::command())?, + Commands::Info => commands::info(out_mode, &Cli::command())?, } Ok(()) @@ -172,8 +172,8 @@ mod tests { #[test] fn info_inventory_starts_with_arbitrum_one() { - let inventory = commands::info_inventory(Cli::command()); - assert_eq!(inventory["chains"][0]["chain_id"], 42161); + let inventory = commands::info_inventory(&Cli::command()); + assert_eq!(inventory["chains"][0]["chain_id"], 42_161); } #[test] @@ -183,7 +183,7 @@ mod tests { .get_subcommands() .map(|subcommand| subcommand.get_name().to_string()) .collect(); - let inventory = commands::info_inventory(command); + let inventory = commands::info_inventory(&command); let actual: Vec = inventory["subcommands"] .as_array() .expect("subcommands array") diff --git a/src/rpc.rs b/src/rpc.rs index 5a76029..8ab3675 100644 --- a/src/rpc.rs +++ b/src/rpc.rs @@ -38,5 +38,6 @@ pub fn hex_to_u64(hex: &str) -> Result { pub fn wei_hex_to_eth(hex: &str) -> Result { let stripped = hex.trim_start_matches("0x"); let wei = u128::from_str_radix(stripped, 16).map_err(|e| eyre!("Invalid wei: {e}"))?; + #[allow(clippy::cast_precision_loss)] Ok(wei as f64 / 1e18) }