Skip to main content

newton_cli/commands/
policy_client.rs

1use alloy::{
2    network::EthereumWallet,
3    primitives::{Address, Bytes},
4    providers::ProviderBuilder,
5    signers::local::PrivateKeySigner,
6    transports::http::reqwest::Url,
7};
8use clap::{Parser, Subcommand};
9use eyre::Context;
10use newton_prover_chainio::{
11    policy_client::PolicyClientController, policy_client_registry::PolicyClientRegistryController,
12};
13use newton_prover_core::{config::NewtonAvsConfig, newton_policy_client::NewtonPolicyClient};
14use std::{path::PathBuf, str::FromStr};
15use tracing::info;
16
17use crate::config::NewtonCliConfig;
18
19/// Policy client commands
20#[derive(Debug, Parser)]
21#[command(name = "policy-client")]
22pub struct PolicyClientCommand {
23    #[command(subcommand)]
24    pub subcommand: PolicyClientSubcommand,
25}
26
27#[derive(Debug, Subcommand)]
28pub enum PolicyClientSubcommand {
29    /// Register a policy client with the PolicyClientRegistry
30    #[command(name = "register")]
31    Register(RegisterCommand),
32
33    /// Deactivate a registered policy client
34    #[command(name = "deactivate")]
35    Deactivate(DeactivateCommand),
36
37    /// Reactivate a previously deactivated policy client
38    #[command(name = "activate")]
39    Activate(ActivateCommand),
40
41    /// Transfer registry ownership record of a policy client
42    #[command(name = "transfer-ownership")]
43    TransferOwnership(TransferOwnershipCommand),
44
45    /// Query registration status and record for a policy client
46    #[command(name = "status")]
47    Status(StatusCommand),
48
49    /// List all policy clients owned by an address
50    #[command(name = "list")]
51    List(ListCommand),
52
53    /// Set the policy address on a policy client contract (owner-only)
54    #[command(name = "set-policy")]
55    SetPolicy(SetPolicyCommand),
56
57    /// Set policy parameters for a policy client
58    #[command(name = "set-policy-params")]
59    SetPolicyParams(SetPolicyParamsCommand),
60}
61
62/// Common arguments for registry write operations
63#[derive(Debug, Parser)]
64struct RegistryWriteArgs {
65    /// PolicyClientRegistry contract address
66    #[arg(long)]
67    registry: Address,
68
69    /// Policy client contract address
70    #[arg(long)]
71    client: Address,
72
73    /// Private key of the transaction signer
74    #[arg(long, env = "PRIVATE_KEY")]
75    private_key: Option<String>,
76
77    /// RPC endpoint URL
78    #[arg(long, env = "RPC_URL")]
79    rpc_url: Option<String>,
80}
81
82impl RegistryWriteArgs {
83    fn validate(&self) -> eyre::Result<(String, String)> {
84        let private_key = self
85            .private_key
86            .clone()
87            .ok_or_else(|| eyre::eyre!("PRIVATE_KEY is required (set via --private-key or env var)"))?;
88        let rpc_url = self
89            .rpc_url
90            .clone()
91            .ok_or_else(|| eyre::eyre!("RPC_URL is required (set via --rpc-url or env var)"))?;
92        Ok((private_key, rpc_url))
93    }
94}
95
96/// Register a policy client with the PolicyClientRegistry
97#[derive(Debug, Parser)]
98pub struct RegisterCommand {
99    #[command(flatten)]
100    args: RegistryWriteArgs,
101}
102
103/// Deactivate a registered policy client
104#[derive(Debug, Parser)]
105pub struct DeactivateCommand {
106    #[command(flatten)]
107    args: RegistryWriteArgs,
108}
109
110/// Reactivate a deactivated policy client
111#[derive(Debug, Parser)]
112pub struct ActivateCommand {
113    #[command(flatten)]
114    args: RegistryWriteArgs,
115}
116
117/// Transfer registry ownership of a policy client
118#[derive(Debug, Parser)]
119pub struct TransferOwnershipCommand {
120    /// PolicyClientRegistry contract address
121    #[arg(long)]
122    registry: Address,
123
124    /// Policy client contract address
125    #[arg(long)]
126    client: Address,
127
128    /// Address of the new owner
129    #[arg(long)]
130    new_owner: Address,
131
132    /// Private key of the current owner
133    #[arg(long, env = "PRIVATE_KEY")]
134    private_key: Option<String>,
135
136    /// RPC endpoint URL
137    #[arg(long, env = "RPC_URL")]
138    rpc_url: Option<String>,
139}
140
141/// Query status of a policy client in the registry
142#[derive(Debug, Parser)]
143pub struct StatusCommand {
144    /// PolicyClientRegistry contract address
145    #[arg(long)]
146    registry: Address,
147
148    /// Policy client contract address
149    #[arg(long)]
150    client: Address,
151
152    /// RPC endpoint URL
153    #[arg(long, env = "RPC_URL")]
154    rpc_url: Option<String>,
155}
156
157/// List all policy clients for an owner
158#[derive(Debug, Parser)]
159pub struct ListCommand {
160    /// PolicyClientRegistry contract address
161    #[arg(long)]
162    registry: Address,
163
164    /// Owner address to query
165    #[arg(long)]
166    owner: Address,
167
168    /// RPC endpoint URL
169    #[arg(long, env = "RPC_URL")]
170    rpc_url: Option<String>,
171}
172
173/// Set the policy address on a policy client contract
174#[derive(Debug, Parser)]
175pub struct SetPolicyCommand {
176    /// Policy client contract address
177    #[arg(long)]
178    client: Address,
179
180    /// New policy contract address
181    #[arg(long)]
182    policy: Address,
183
184    /// Private key of the policy client owner
185    #[arg(long, env = "PRIVATE_KEY")]
186    private_key: Option<String>,
187
188    /// RPC endpoint URL
189    #[arg(long, env = "RPC_URL")]
190    rpc_url: Option<String>,
191}
192
193/// Set policy parameters command
194#[derive(Debug, Parser)]
195pub struct SetPolicyParamsCommand {
196    /// Policy client address
197    #[arg(long)]
198    policy_client: Address,
199
200    /// Path to JSON file containing policy parameters
201    #[arg(long)]
202    policy_params: PathBuf,
203
204    /// Expire after (in blocks)
205    #[arg(long)]
206    expire_after: u32,
207
208    #[arg(long, env = "PRIVATE_KEY")]
209    private_key: Option<String>,
210
211    #[arg(long, env = "RPC_URL")]
212    rpc_url: Option<String>,
213}
214
215impl PolicyClientCommand {
216    /// Execute the policy-client command
217    pub async fn execute(self: Box<Self>, _config: NewtonAvsConfig<NewtonCliConfig>) -> eyre::Result<()> {
218        match self.subcommand {
219            PolicyClientSubcommand::Register(cmd) => {
220                let (private_key, rpc_url) = cmd.args.validate()?;
221                let controller = PolicyClientRegistryController::new(private_key, rpc_url, cmd.args.registry);
222                let receipt = controller
223                    .register_client(cmd.args.client)
224                    .await
225                    .map_err(|e| eyre::eyre!("failed to register client: {}", e))?;
226                info!("Transaction hash: {}", receipt.transaction_hash);
227                Ok(())
228            }
229
230            PolicyClientSubcommand::Deactivate(cmd) => {
231                let (private_key, rpc_url) = cmd.args.validate()?;
232                let controller = PolicyClientRegistryController::new(private_key, rpc_url, cmd.args.registry);
233                let receipt = controller
234                    .deactivate_client(cmd.args.client)
235                    .await
236                    .map_err(|e| eyre::eyre!("failed to deactivate client: {}", e))?;
237                info!("Transaction hash: {}", receipt.transaction_hash);
238                Ok(())
239            }
240
241            PolicyClientSubcommand::Activate(cmd) => {
242                let (private_key, rpc_url) = cmd.args.validate()?;
243                let controller = PolicyClientRegistryController::new(private_key, rpc_url, cmd.args.registry);
244                let receipt = controller
245                    .activate_client(cmd.args.client)
246                    .await
247                    .map_err(|e| eyre::eyre!("failed to activate client: {}", e))?;
248                info!("Transaction hash: {}", receipt.transaction_hash);
249                Ok(())
250            }
251
252            PolicyClientSubcommand::TransferOwnership(cmd) => {
253                let private_key = cmd
254                    .private_key
255                    .ok_or_else(|| eyre::eyre!("PRIVATE_KEY is required (set via --private-key or env var)"))?;
256                let rpc_url = cmd
257                    .rpc_url
258                    .ok_or_else(|| eyre::eyre!("RPC_URL is required (set via --rpc-url or env var)"))?;
259
260                let controller = PolicyClientRegistryController::new(private_key, rpc_url, cmd.registry);
261                let receipt = controller
262                    .set_client_owner(cmd.client, cmd.new_owner)
263                    .await
264                    .map_err(|e| eyre::eyre!("failed to transfer ownership: {}", e))?;
265                info!("Transaction hash: {}", receipt.transaction_hash);
266                Ok(())
267            }
268
269            PolicyClientSubcommand::Status(cmd) => {
270                let rpc_url = cmd
271                    .rpc_url
272                    .ok_or_else(|| eyre::eyre!("RPC_URL is required (set via --rpc-url or env var)"))?;
273
274                let controller = PolicyClientRegistryController::new_read_only(rpc_url, cmd.registry);
275
276                let is_active = controller
277                    .is_registered_client(cmd.client)
278                    .await
279                    .map_err(|e| eyre::eyre!("failed to query registration status: {}", e))?;
280
281                // Try to fetch the full record (reverts if not registered at all)
282                match controller.get_client_record(cmd.client).await {
283                    Ok(record) => {
284                        println!("Client: {}", cmd.client);
285                        println!("  Owner: {}", record.owner);
286                        println!("  Active: {}", record.active);
287                        println!("  Registered At: {} (unix timestamp)", record.registeredAt);
288                        println!("  isRegisteredClient: {}", is_active);
289                    }
290                    Err(_) => {
291                        println!("Client: {}", cmd.client);
292                        println!("  Not registered in registry {}", cmd.registry);
293                    }
294                }
295                Ok(())
296            }
297
298            PolicyClientSubcommand::List(cmd) => {
299                let rpc_url = cmd
300                    .rpc_url
301                    .ok_or_else(|| eyre::eyre!("RPC_URL is required (set via --rpc-url or env var)"))?;
302
303                let controller = PolicyClientRegistryController::new_read_only(rpc_url, cmd.registry);
304
305                let clients = controller
306                    .get_clients_by_owner(cmd.owner)
307                    .await
308                    .map_err(|e| eyre::eyre!("failed to query clients: {}", e))?;
309
310                println!("Owner: {}", cmd.owner);
311                println!("Registered clients: {}", clients.len());
312                for client in &clients {
313                    println!("  {}", client);
314                }
315                Ok(())
316            }
317
318            PolicyClientSubcommand::SetPolicy(cmd) => {
319                let private_key = cmd
320                    .private_key
321                    .ok_or_else(|| eyre::eyre!("PRIVATE_KEY is required (set via --private-key or env var)"))?;
322                let rpc_url = cmd
323                    .rpc_url
324                    .ok_or_else(|| eyre::eyre!("RPC_URL is required (set via --rpc-url or env var)"))?;
325
326                let private_key_str = private_key.strip_prefix("0x").unwrap_or(&private_key);
327                let signer = PrivateKeySigner::from_str(private_key_str).context("failed to parse private key")?;
328                let wallet = EthereumWallet::from(signer);
329                let url = Url::parse(&rpc_url).context("invalid RPC URL")?;
330                let provider = ProviderBuilder::new().wallet(wallet).connect_http(url);
331
332                let policy_client = NewtonPolicyClient::new(cmd.client, provider);
333
334                info!(
335                    "Calling setPolicyAddress({}) on policy client {}",
336                    cmd.policy, cmd.client
337                );
338
339                let tx = policy_client
340                    .setPolicyAddress(cmd.policy)
341                    .send()
342                    .await
343                    .context("failed to send setPolicyAddress transaction")?;
344
345                info!("Transaction sent: {:?}", tx.tx_hash());
346
347                let receipt = tx
348                    .get_receipt()
349                    .await
350                    .context("failed to get setPolicyAddress receipt")?;
351
352                if !receipt.status() {
353                    return Err(eyre::eyre!(
354                        "setPolicyAddress transaction reverted (tx: {:?})",
355                        receipt.transaction_hash
356                    ));
357                }
358
359                info!("Policy client {} now points to policy {}", cmd.client, cmd.policy);
360                info!("Transaction hash: {}", receipt.transaction_hash);
361                Ok(())
362            }
363
364            PolicyClientSubcommand::SetPolicyParams(cmd) => {
365                let private_key = cmd
366                    .private_key
367                    .ok_or_else(|| eyre::eyre!("PRIVATE_KEY is required (set via --private-key or env var)"))?;
368                let rpc_url = cmd
369                    .rpc_url
370                    .ok_or_else(|| eyre::eyre!("RPC_URL is required (set via --rpc-url or env var)"))?;
371
372                info!("Setting policy params for policy client: {}", cmd.policy_client);
373
374                // Read policy params JSON file
375                let policy_params_str = std::fs::read_to_string(&cmd.policy_params)
376                    .with_context(|| format!("failed to read policy params file: {:?}", cmd.policy_params))?;
377
378                // Validate it's valid JSON
379                let _policy_params_json: serde_json::Value = serde_json::from_str(&policy_params_str)
380                    .with_context(|| format!("failed to parse policy params as JSON: {:?}", cmd.policy_params))?;
381
382                // Convert to bytes
383                let policy_params_bytes = Bytes::copy_from_slice(policy_params_str.as_bytes());
384
385                // Create controller and set policy
386                let controller = PolicyClientController::new(private_key, rpc_url, cmd.policy_client);
387                let (receipt, policy_id) = controller
388                    .set_policy(policy_params_bytes, cmd.expire_after)
389                    .await
390                    .map_err(|e| eyre::eyre!("failed to set policy: {}", e))?;
391
392                info!("Policy set successfully");
393                info!("Transaction hash: {:?}", receipt.transaction_hash);
394                info!("Policy ID: {:?}", policy_id);
395
396                Ok(())
397            }
398        }
399    }
400}