Skip to main content

newton_cli/cli/
mod.rs

1use crate::{
2    commands::{
3        policy::PolicyCommand, policy_client::PolicyClientCommand, policy_data::PolicyDataCommand,
4        policy_files::PolicyFilesCommand, regorus::RegorusCommand, 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                    gateway_url: "http://127.0.0.1:8080".to_string(),
99                    signer: newton_prover_core::config::key::EcdsaKey {
100                        private_key: None,
101                        keystore_path: None,
102                        keystore_password: None,
103                    },
104                };
105                // Load contract addresses from bundled files, then construct config manually
106                use newton_prover_core::config::{contracts::*, ipfs::IpfsConfig, rpc::ChainRpcProviderConfig};
107                // Try to load contracts from bundled files (should succeed)
108                // Clippy: use unwrap_or instead of unwrap_or_else when error isn't used
109                #[allow(clippy::unnecessary_lazy_evaluations)]
110                let contracts = ContractsConfig::load(chain_id, env.clone()).unwrap_or_else(|_| {
111                    // Shouldn't happen with bundled files, but provide fallback
112                    ContractsConfig {
113                        avs: NewtonAvsContractsConfig {
114                            newton_prover_service_manager: Address::ZERO,
115                            newton_prover_task_manager: Address::ZERO,
116                            challenge_verifier: Address::ZERO,
117                            rego_verifier: Address::ZERO,
118                            attestation_validator: Address::ZERO,
119                            operator_registry: Address::ZERO,
120                            operator_state_retriever: Address::ZERO,
121                            bls_apk_registry: Address::ZERO,
122                            index_registry: Address::ZERO,
123                            stake_registry: Address::ZERO,
124                            socket_registry: Address::ZERO,
125                            strategy: Address::ZERO,
126                        },
127                        eigenlayer: EigenlayerContractsConfig {
128                            delegation_manager: Address::ZERO,
129                            avs_directory: Address::ZERO,
130                            strategy_manager: Address::ZERO,
131                            allocation_manager: Address::ZERO,
132                            rewards_coordinator: Address::ZERO,
133                            strategy_factory: Address::ZERO,
134                            permission_controller: Address::ZERO,
135                            bn254_certificate_verifier: Address::ZERO,
136                            key_registrar: Address::ZERO,
137                        },
138                        destination_multichain: None,
139                        policy: NewtonPolicyContractsConfig {
140                            policy_factory: Address::ZERO,
141                            policy_data_factory: Address::ZERO,
142                        },
143                    }
144                });
145                // Construct config with loaded contracts and default service config
146                newton_prover_core::config::NewtonAvsConfig {
147                    env: env.clone(),
148                    chain_id,
149                    source_chain_id: None,
150                    rpc: ChainRpcProviderConfig::load(),
151                    ipfs: IpfsConfig::default(),
152                    contracts,
153                    service,
154                    data_provider: newton_prover_core::config::DataProviderConfig::default(),
155                }
156            }
157        }
158    }
159
160    /// Load config, returning an error if it fails
161    /// This is for commands that actually need the config
162    fn load_config_required(
163        &self,
164        chain_id: u64,
165    ) -> eyre::Result<newton_prover_core::config::NewtonAvsConfig<NewtonCliConfig>> {
166        let mut builder = NewtonAvsConfigBuilder::new(chain_id);
167        if let Some(config_path) = self.resolve_config_path() {
168            info!("Loading cli config from: {:?}", config_path);
169            builder = builder.with_service_path(config_path);
170        }
171        builder.build::<NewtonCliConfig>().map_err(|e| {
172            eyre::eyre!(
173                "Failed to load configuration: {}. \
174                Make sure deployment files exist for chain_id {} or set DEPLOYMENT_ENV environment variable.",
175                e,
176                chain_id
177            )
178        })
179    }
180
181    /// execute the configured cli command.
182    pub fn run(self) -> eyre::Result<()> {
183        // Build log filter - suppress verbose config loading logs by default
184        let env_filter = "warn,newton_cli=info".to_string();
185
186        // Initialize logging with specified format
187        init_logger(LoggerConfig::new(self.log_format).with_env_filter(env_filter));
188
189        let runner = NewtonRunner::default();
190
191        // Commands that don't need config at all (all subcommands use _config)
192        // These can work with a dummy config if loading fails
193        let commands_that_dont_need_config = matches!(
194            &self.command,
195            Commands::PolicyFiles(_) | Commands::PolicyClient(_) | Commands::Regorus(_)
196        );
197
198        // Determine chain_id - optional for commands that don't need config
199        let chain_id = if commands_that_dont_need_config {
200            self.chain_id.unwrap_or(1)
201        } else {
202            self.chain_id.ok_or_else(|| eyre::eyre!("chain id is required"))?
203        };
204
205        // Load config - graceful fallback only for commands that truly don't need it
206        // Note: Policy and PolicyData have Simulate subcommands that need real config (for execute_wasm),
207        // so they require real config even though Deploy subcommands don't use it
208        // With bundled deployment files, config loading should succeed, but we allow fallback
209        // for commands that don't use config in case service config file is missing
210        let config = if commands_that_dont_need_config {
211            self.load_config_or_minimal(chain_id)
212        } else {
213            // Task, Policy, and PolicyData require real config
214            self.load_config_required(chain_id)?
215        };
216
217        match self.command {
218            Commands::Task(command) => runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?,
219            Commands::PolicyData(command) => runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?,
220            Commands::Policy(command) => runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?,
221            Commands::PolicyFiles(command) => runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?,
222            Commands::PolicyClient(command) => runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?,
223            Commands::Regorus(command) => {
224                // Regorus command doesn't need the config, executes synchronously
225                command.execute().map_err(|e| eyre::eyre!("{}", e))?;
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    /// regorus Rego policy engine with Newton extensions
253    Regorus(RegorusCommand),
254}