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