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