yellowstone_shield_cli/
lib.rs1mod command;
2
3use anyhow::{Context, Result};
4use bs58::decode;
5use clap_derive::{Parser as DeriveParser, Subcommand};
6use serde_json::from_str as parse_json_str;
7use solana_cli_config::Config;
8use solana_client::nonblocking::rpc_client::RpcClient;
9use solana_sdk::commitment_config::CommitmentConfig;
10use solana_sdk::pubkey::Pubkey;
11use solana_sdk::signature::Keypair;
12use std::fs::read_to_string as read_path;
13use std::path::PathBuf;
14use std::sync::Arc;
15use std::{str::FromStr, time::Duration};
16use yellowstone_shield_client::types::PermissionStrategy;
17
18pub use command::*;
19
20#[derive(Debug, DeriveParser)]
21#[command(
22 author,
23 version,
24 name = "Yellowstone Shield CLI",
25 about = "CLI for managing Yellowstone shield policies"
26)]
27pub struct Args {
28 #[arg(short, long, global = true)]
30 pub rpc: Option<String>,
31
32 #[arg(short, long, global = true, default_value = "off")]
34 pub log_level: String,
35
36 #[arg(short, long, global = true)]
38 pub keypair: Option<String>,
39
40 #[command(subcommand)]
41 pub command: Command,
42}
43
44#[derive(Subcommand, Debug)]
45pub enum Command {
46 Policy {
48 #[command(subcommand)]
49 action: PolicyAction,
50 },
51 Identities {
53 #[command(subcommand)]
54 action: IdentitiesAction,
55 },
56}
57
58#[derive(Subcommand, Debug)]
59pub enum PolicyAction {
60 Create {
62 #[arg(long)]
64 strategy: PermissionStrategy,
65
66 #[arg(long)]
68 name: String,
69
70 #[arg(long)]
72 symbol: String,
73
74 #[arg(long)]
76 uri: String,
77 },
78 Delete {
80 #[arg(long)]
82 mint: Pubkey,
83 },
84 Show {
86 #[arg(long)]
88 mint: Pubkey,
89 },
90}
91
92#[derive(Subcommand, Debug)]
93pub enum IdentitiesAction {
94 Add {
96 #[arg(long)]
98 mint: Pubkey,
99 #[arg(long)]
101 identities_path: PathBuf,
102 },
103 Remove {
105 #[arg(long)]
107 mint: Pubkey,
108 #[arg(long)]
110 identities_path: PathBuf,
111 },
112}
113
114#[derive(thiserror::Error, Debug)]
115pub enum CliError {
116 #[error("unable to get config file path")]
117 ConfigFilePathError,
118 #[error(transparent)]
119 Io(#[from] std::io::Error),
120 #[error(transparent)]
121 Other(#[from] anyhow::Error),
122 #[error(transparent)]
123 ParseCommitmentLevelError(#[from] solana_sdk::commitment_config::ParseCommitmentLevelError),
124 #[error("unable to parse keypair")]
125 Keypair,
126}
127
128pub async fn run(config: Arc<Config>, command: Command) -> RunResult {
129 let client = RpcClient::new_with_timeout_and_commitment(
130 config.json_rpc_url.clone(),
131 Duration::from_secs(90),
132 CommitmentConfig::from_str(&config.commitment).map_err::<CliError, _>(Into::into)?,
133 );
134 let keypair = parse_keypair(&config.keypair_path)?;
135 let context = command::CommandContext { keypair, client };
136
137 match &command {
138 Command::Policy { action } => match action {
139 PolicyAction::Create {
140 strategy,
141 name,
142 symbol,
143 uri,
144 } => {
145 policy::CreateCommandBuilder::new()
146 .strategy(*strategy)
147 .name(name.clone())
148 .symbol(symbol.clone())
149 .uri(uri.clone())
150 .run(context)
151 .await
152 }
153 PolicyAction::Delete { mint } => {
154 policy::DeleteCommandBuilder::new()
155 .mint(mint)
156 .run(context)
157 .await
158 }
159 PolicyAction::Show { mint } => {
160 policy::ShowCommandBuilder::new()
161 .mint(mint)
162 .run(context)
163 .await
164 }
165 },
166 Command::Identities { action } => match action {
167 IdentitiesAction::Add {
168 mint,
169 identities_path,
170 } => {
171 let identities: Vec<Pubkey> = read_path(identities_path)?
172 .lines()
173 .filter_map(|s| Pubkey::from_str(s.trim()).ok())
174 .collect();
175
176 identity::AddBatchCommandBuilder::new()
177 .mint(mint)
178 .identities(identities)
179 .run(context)
180 .await
181 }
182 IdentitiesAction::Remove {
183 mint,
184 identities_path,
185 } => {
186 let identities: Vec<Pubkey> = read_path(identities_path)?
187 .lines()
188 .filter_map(|s| Pubkey::from_str(s.trim()).ok())
189 .collect();
190
191 identity::RemoveBatchCommandBuilder::new()
192 .mint(mint)
193 .identities(identities)
194 .run(context)
195 .await
196 }
197 },
198 }
199}
200
201fn parse_keypair(keypair_path: &str) -> Result<Keypair, CliError> {
202 let secret_string = read_path(keypair_path).context("Can't find key file")?;
203 let secret_bytes = parse_json_str(&secret_string)
204 .or_else(|_| decode(&secret_string.trim()).into_vec())
205 .map_err(|_| CliError::ConfigFilePathError)?;
206
207 Keypair::from_bytes(&secret_bytes).map_err(|_| CliError::Keypair)
208}