1#![doc = include_str!("../README.md")]
2
3use std::{path::PathBuf, str::FromStr, sync::Arc};
4
5use benchmark::BenchmarkSubCommand;
6use blob::BlobSubCommand;
7use blober::BloberSubCommand;
8use clap::{CommandFactory, Parser, Subcommand, error::ErrorKind};
9use data_anchor_client::{BloberIdentifier, DataAnchorClient, DataAnchorClientResult};
10use formatting::OutputFormat;
11use indexer::IndexerSubCommand;
12use solana_cli_config::Config;
13use solana_sdk::{
14 pubkey::Pubkey,
15 signature::Keypair,
16 signer::{EncodableKey, Signer},
17};
18use tracing::trace;
19
20mod benchmark;
21mod blob;
22mod blober;
23mod formatting;
24mod indexer;
25
26const NAMESPACE_MISSING_MSG: &str = "Namespace is not set. Please provide a namespace using the --namespace flag or set the DATA_ANCHOR_NAMESPACE environment variable.";
27const PROGRAM_ID_MISSING_MSG: &str = "Program ID is not set. Please provide a program ID using the --program-id flag or set the DATA_ANCHOR_PROGRAM_ID environment variable.";
28const INDEXER_URL_MISSING_MSG: &str = "Indexer URL is not set. Please provide a URL using the --indexer-url flag or set the DATA_ANCHOR_INDEXER_URL environment variable.";
29
30#[derive(Debug, Parser)]
32#[command(version, about, long_about)]
33struct Cli {
34 #[command(subcommand)]
35 pub command: Command,
36
37 #[arg(short, long, global = true, env = "DATA_ANCHOR_PROGRAM_ID")]
39 pub program_id: Option<Pubkey>,
40
41 #[arg(short, long, global = true, env = "DATA_ANCHOR_NAMESPACE")]
43 pub namespace: Option<String>,
44
45 #[arg(
47 short,
48 long,
49 global = true,
50 env = "DATA_ANCHOR_BLOBER_PDA",
51 value_name = "BLOBER_PDA"
52 )]
53 pub blober_pda: Option<Pubkey>,
54
55 #[arg(short = 's', long, global = true, env = "DATA_ANCHOR_PAYER")]
57 pub payer: Option<String>,
58
59 #[arg(
61 short,
62 long,
63 global = true,
64 env = "DATA_ANCHOR_OUTPUT",
65 value_enum,
66 default_value_t = OutputFormat::Text
67 )]
68 pub output: OutputFormat,
69
70 #[arg(short, long, global = true, env = "DATA_ANCHOR_INDEXER_URL")]
72 pub indexer_url: Option<String>,
73
74 #[arg(
76 long,
77 global = true,
78 env = "DATA_ANCHOR_INDEXER_API_TOKEN",
79 hide_env_values = true
80 )]
81 pub indexer_api_token: Option<String>,
82
83 #[arg(
85 short,
86 long,
87 global = true,
88 env = "DATA_ANCHOR_SOLANA_CONFIG_FILE",
89 default_value_t = solana_cli_config::CONFIG_FILE.as_ref().unwrap().clone()
90 )]
91 pub config_file: String,
92}
93
94impl Cli {
95 fn exit_with_missing_arg(msg: &str) -> ! {
96 Self::command()
97 .error(ErrorKind::MissingRequiredArgument, msg)
98 .exit()
99 }
100
101 fn payer_keypair(&self, config: &Config) -> String {
102 if let Some(payer) = &self.payer {
103 return payer.to_owned();
104 }
105
106 let Ok(path_to_config) = PathBuf::from_str(&self.config_file);
107
108 let Some(directory) = path_to_config.parent() else {
109 Self::exit_with_missing_arg("Failed to get the parent directory of the config file")
110 };
111
112 let path = directory.join(&config.keypair_path);
113
114 let Some(path_str) = path.to_str() else {
115 Self::exit_with_missing_arg("Failed to convert the keypair path to a string")
116 };
117
118 path_str.to_owned()
119 }
120}
121
122#[derive(Debug, Subcommand)]
123enum Command {
124 #[command(subcommand, visible_alias = "br")]
126 Blober(BloberSubCommand),
127 #[command(subcommand, visible_alias = "b")]
129 Blob(BlobSubCommand),
130 #[command(subcommand, visible_alias = "i")]
132 Indexer(IndexerSubCommand),
133 #[command(subcommand, visible_alias = "m")]
135 Benchmark(BenchmarkSubCommand),
136}
137
138pub struct Options {
139 command: Command,
140 program_id: Pubkey,
141 payer: Arc<Keypair>,
142 blober_pda: BloberIdentifier,
143 indexer_url: Option<String>,
144 indexer_api_token: Option<String>,
145 config: Config,
146 output: OutputFormat,
147}
148
149impl Options {
150 pub fn parse() -> Self {
153 trace!("Parsing options");
154 let args = Cli::parse();
155 let config = Config::load(&args.config_file).unwrap();
156 let payer_path = args.payer_keypair(&config);
157 let payer = Arc::new(Keypair::read_from_file(payer_path).unwrap());
158 trace!("Parsed options: {args:?} {config:?} {payer:?}");
159
160 let Some(program_id) = args.program_id else {
161 Cli::exit_with_missing_arg(PROGRAM_ID_MISSING_MSG);
162 };
163
164 let blober_pda = if let Some(blober_pda) = args.blober_pda {
165 blober_pda.into()
166 } else {
167 let Some(nmsp) = args.namespace else {
168 Cli::exit_with_missing_arg(NAMESPACE_MISSING_MSG);
169 };
170
171 (payer.pubkey(), nmsp).into()
172 };
173
174 Self {
175 indexer_url: args.indexer_url,
176 indexer_api_token: args.indexer_api_token,
177 command: args.command,
178 program_id,
179 output: args.output,
180 blober_pda,
181 payer,
182 config,
183 }
184 }
185
186 pub async fn run(self) -> DataAnchorClientResult {
188 let output = match self.command {
189 Command::Indexer(subcommand) => {
190 let Some(indexer_url) = self.indexer_url else {
191 Cli::exit_with_missing_arg(INDEXER_URL_MISSING_MSG);
192 };
193 let client = DataAnchorClient::builder()
194 .payer(self.payer.clone())
195 .program_id(self.program_id)
196 .indexer_from_url(&indexer_url, self.indexer_api_token.clone())
197 .await?
198 .build_with_config(self.config)
199 .await?;
200 let client = Arc::new(client);
201
202 subcommand
203 .run(
204 client.clone(),
205 self.blober_pda
206 .to_blober_address(self.program_id, self.payer.pubkey()),
207 )
208 .await
209 }
210 subcommand => {
211 let Some(namespace) = &self.blober_pda.namespace() else {
212 Cli::exit_with_missing_arg(NAMESPACE_MISSING_MSG);
213 };
214 let client = DataAnchorClient::builder()
215 .payer(self.payer.clone())
216 .program_id(self.program_id)
217 .build_with_config(self.config)
218 .await?;
219 let client = Arc::new(client);
220
221 match subcommand {
222 Command::Blob(subcommand) => subcommand.run(client.clone(), namespace).await,
223 Command::Blober(subcommand) => {
224 subcommand
225 .run(
226 client.clone(),
227 self.blober_pda,
228 self.program_id,
229 self.payer.pubkey(),
230 )
231 .await
232 }
233 Command::Benchmark(subcommand) => {
234 subcommand.run(client.clone(), namespace).await
235 }
236 _ => unreachable!("Indexer subcommands should have been handled above"),
237 }
238 }
239 }?;
240
241 println!("{}", output.serialize_output(self.output));
242
243 Ok(())
244 }
245}