newton_cli/cli/
mod.rs

1use crate::{
2    commands::{
3        newton_dashboard::NewtonDashboardCommand, policy::PolicyCommand, policy_client::PolicyClientCommand,
4        policy_data::PolicyDataCommand, policy_files::PolicyFilesCommand, task::TaskCommand,
5    },
6    config::NewtonCliConfig,
7};
8use clap::{Parser, Subcommand};
9use newton_cli_runner::NewtonRunner;
10use newton_prover_core::config::{
11    log::{init_logger, LogFormat, LoggerConfig},
12    NewtonAvsConfigBuilder,
13};
14use std::{ffi::OsString, path::PathBuf};
15use tracing::info;
16
17/// newton protocol cli entry point interface
18#[derive(Debug, Parser)]
19#[command(author, about = "Newton protocol cli", long_about = None)]
20pub struct NewtonCli {
21    /// chain id
22    #[arg(long, value_name = "CHAIN_ID", global = true, env = "CHAIN_ID")]
23    chain_id: Option<u64>,
24
25    /// optional path to the configuration file
26    #[arg(long, value_name = "FILE", global = true)]
27    config_path: Option<PathBuf>,
28
29    /// log format
30    #[arg(
31        long,
32        value_enum,
33        default_value = "minimal",
34        global = true,
35        help = "Log format: full, compact, pretty, json, or minimal"
36    )]
37    log_format: LogFormat,
38
39    /// Suppress verbose config loading logs
40    #[arg(long, global = true)]
41    quiet: bool,
42
43    #[command(subcommand)]
44    command: Commands,
45}
46
47impl NewtonCli {
48    /// parsers only the default cli arguments
49    pub fn parse_args() -> Self {
50        Self::parse()
51    }
52
53    /// parsers only the default cli arguments from the given iterator
54    pub fn try_parse_args_from<I, T>(itr: I) -> Result<Self, clap::error::Error>
55    where
56        I: IntoIterator<Item = T>,
57        T: Into<OsString> + Clone,
58    {
59        Self::try_parse_from(itr)
60    }
61
62    /// Resolve config path: use provided path, or check ~/.newton/newton-cli.toml, or None
63    fn resolve_config_path(&self) -> Option<PathBuf> {
64        if let Some(path) = &self.config_path {
65            Some(path.clone())
66        } else {
67            let home = std::env::var("HOME").ok()?;
68            let path = std::path::PathBuf::from(home).join(".newton").join("newton-cli.toml");
69            if path.exists() {
70                Some(path)
71            } else {
72                None
73            }
74        }
75    }
76
77    /// Try to load config, falling back to minimal config if service config loading fails
78    /// This is for commands that don't actually use the config (marked with _config)
79    /// With bundled deployment files, contract addresses should always load successfully.
80    fn load_config_or_minimal(&self, chain_id: u64) -> newton_prover_core::config::NewtonAvsConfig<NewtonCliConfig> {
81        let mut builder = NewtonAvsConfigBuilder::new(chain_id);
82        if let Some(config_path) = self.resolve_config_path() {
83            info!("Loading cli config from: {:?}", config_path);
84            builder = builder.with_service_path(config_path);
85        }
86
87        // Try to load full config - should work now with bundled deployment files
88        match builder.build::<NewtonCliConfig>() {
89            Ok(config) => config,
90            Err(e) => {
91                // If service config loading fails, create minimal config with defaults
92                // Deployment files are bundled, so we can still load contract addresses
93                tracing::debug!("Service config loading failed, using defaults: {}", e);
94                let env = std::env::var("DEPLOYMENT_ENV").unwrap_or_else(|_| "stagef".to_string());
95                let service = NewtonCliConfig {
96                    eth_rpc_url: "http://127.0.0.1:8545".to_string(),
97                    newton_rpc_url: "http://127.0.0.1:8545".to_string(),
98                    signer: newton_prover_core::config::key::EcdsaKey {
99                        private_key: None,
100                        keystore_path: None,
101                        keystore_password: None,
102                    },
103                    dashboard_bearer_token: None,
104                    dashboard_api_base_url: None,
105                };
106                // Load contract addresses from bundled files, then construct config manually
107                use newton_prover_core::config::{contracts::*, ipfs::IpfsConfig, rpc::RpcProviderConfig};
108                // Try to load contracts from bundled files (should succeed)
109                // Clippy: use unwrap_or instead of unwrap_or_else when error isn't used
110                #[allow(clippy::unnecessary_lazy_evaluations)]
111                let contracts = ContractsConfig::load(chain_id, env.clone()).unwrap_or_else(|_| {
112                    // Shouldn't happen with bundled files, but provide fallback
113                    ContractsConfig {
114                        avs: NewtonAvsContractsConfig {
115                            newton_prover_service_manager: alloy::primitives::Address::ZERO,
116                            newton_prover_task_manager: alloy::primitives::Address::ZERO,
117                            challenge_verifier: alloy::primitives::Address::ZERO,
118                            rego_verifier: alloy::primitives::Address::ZERO,
119                            attestation_validator: alloy::primitives::Address::ZERO,
120                            operator_registry: alloy::primitives::Address::ZERO,
121                            operator_state_retriever: alloy::primitives::Address::ZERO,
122                            bls_apk_registry: alloy::primitives::Address::ZERO,
123                            index_registry: alloy::primitives::Address::ZERO,
124                            stake_registry: alloy::primitives::Address::ZERO,
125                            socket_registry: alloy::primitives::Address::ZERO,
126                            strategy: alloy::primitives::Address::ZERO,
127                        },
128                        eigenlayer: EigenlayerContractsConfig {
129                            delegation_manager: alloy::primitives::Address::ZERO,
130                            avs_directory: alloy::primitives::Address::ZERO,
131                            strategy_manager: alloy::primitives::Address::ZERO,
132                            allocation_manager: alloy::primitives::Address::ZERO,
133                            rewards_coordinator: alloy::primitives::Address::ZERO,
134                            strategy_factory: alloy::primitives::Address::ZERO,
135                            permission_controller: alloy::primitives::Address::ZERO,
136                            operator_table_updater: alloy::primitives::Address::ZERO,
137                            ecdsa_certificate_verifier: alloy::primitives::Address::ZERO,
138                            bn254_certificate_verifier: alloy::primitives::Address::ZERO,
139                        },
140                        policy: NewtonPolicyContractsConfig {
141                            policy_factory: alloy::primitives::Address::ZERO,
142                            policy_data_factory: alloy::primitives::Address::ZERO,
143                        },
144                    }
145                });
146                // Construct config with loaded contracts and default service config
147                newton_prover_core::config::NewtonAvsConfig {
148                    env: env.clone(),
149                    chain_id,
150                    source_chain_id: None,
151                    rpc: RpcProviderConfig::load(chain_id).unwrap_or(RpcProviderConfig {
152                        http: "http://127.0.0.1:8545".to_string(),
153                        ws: "ws://127.0.0.1:8545".to_string(),
154                    }),
155                    ipfs: IpfsConfig::default(),
156                    contracts,
157                    service,
158                }
159            }
160        }
161    }
162
163    /// Load config, returning an error if it fails
164    /// This is for commands that actually need the config
165    fn load_config_required(
166        &self,
167        chain_id: u64,
168    ) -> eyre::Result<newton_prover_core::config::NewtonAvsConfig<NewtonCliConfig>> {
169        let mut builder = NewtonAvsConfigBuilder::new(chain_id);
170        if let Some(config_path) = self.resolve_config_path() {
171            info!("Loading cli config from: {:?}", config_path);
172            builder = builder.with_service_path(config_path);
173        }
174        builder.build::<NewtonCliConfig>().map_err(|e| {
175            eyre::eyre!(
176                "Failed to load configuration: {}. \
177                Make sure deployment files exist for chain_id {} or set DEPLOYMENT_ENV environment variable.",
178                e,
179                chain_id
180            )
181        })
182    }
183
184    /// execute the configured cli command.
185    pub fn run(self) -> eyre::Result<()> {
186        // Build log filter - suppress verbose config loading logs by default
187        let env_filter = "warn,newton_cli=info".to_string();
188
189        // Initialize logging with specified format
190        init_logger(LoggerConfig::new(self.log_format).with_env_filter(env_filter));
191
192        let runner = NewtonRunner::default();
193
194        // Commands that don't need config at all (all subcommands use _config)
195        // These can work with a dummy config if loading fails
196        let commands_that_dont_need_config =
197            matches!(&self.command, Commands::PolicyFiles(_) | Commands::PolicyClient(_));
198
199        // Determine chain_id - optional for commands that don't need config
200        let chain_id = if commands_that_dont_need_config {
201            self.chain_id.unwrap_or(1)
202        } else {
203            self.chain_id.ok_or_else(|| eyre::eyre!("chain id is required"))?
204        };
205
206        // Load config - graceful fallback only for commands that truly don't need it
207        // Note: Policy and PolicyData have Simulate subcommands that need real config (for execute_wasm),
208        // so they require real config even though Deploy subcommands don't use it
209        // With bundled deployment files, config loading should succeed, but we allow fallback
210        // for commands that don't use config in case service config file is missing
211        let config = if commands_that_dont_need_config {
212            self.load_config_or_minimal(chain_id)
213        } else {
214            // Task, Policy, and PolicyData require real config
215            self.load_config_required(chain_id)?
216        };
217
218        match self.command {
219            Commands::Task(command) => runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?,
220            Commands::PolicyData(command) => runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?,
221            Commands::Policy(command) => runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?,
222            Commands::PolicyFiles(command) => runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?,
223            Commands::PolicyClient(command) => runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?,
224            Commands::NewtonDashboard(command) => {
225                runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?
226            }
227        }
228
229        Ok(())
230    }
231}
232
233/// commands to be executed
234#[derive(Debug, Subcommand)]
235pub enum Commands {
236    /// task commands
237    Task(TaskCommand),
238
239    /// policy data commands
240    PolicyData(PolicyDataCommand),
241
242    /// policy commands
243    Policy(PolicyCommand),
244
245    /// policy files commands
246    PolicyFiles(PolicyFilesCommand),
247
248    /// policy client commands
249    #[command(name = "policy-client")]
250    PolicyClient(PolicyClientCommand),
251
252    /// newton dashboard commands
253    #[command(name = "newton-dashboard")]
254    NewtonDashboard(NewtonDashboardCommand),
255}