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, IndexerUrl};
11use formatting::OutputFormat;
12use indexer::IndexerSubCommand;
13use solana_cli_config::Config;
14use solana_keypair::Keypair;
15use solana_signer::{EncodableKey, Signer};
16use tracing::trace;
17
18mod benchmark;
19mod blob;
20mod blober;
21mod formatting;
22mod indexer;
23
24const NAMESPACE_MISSING_MSG: &str = "Namespace is not set. Please provide a namespace using the --namespace flag or set the DATA_ANCHOR_NAMESPACE environment variable.";
25
26#[derive(Debug, Parser)]
28#[command(version, about, long_about)]
29struct Cli {
30 #[command(subcommand)]
31 pub command: Command,
32
33 #[arg(short, long, global = true, env = "DATA_ANCHOR_PROGRAM_ID")]
35 pub program_id: Option<Pubkey>,
36
37 #[arg(short, long, global = true, env = "DATA_ANCHOR_NAMESPACE")]
39 pub namespace: Option<String>,
40
41 #[arg(
43 short,
44 long,
45 global = true,
46 env = "DATA_ANCHOR_BLOBER_PDA",
47 value_name = "BLOBER_PDA"
48 )]
49 pub blober_pda: Option<Pubkey>,
50
51 #[arg(short = 's', long, global = true, env = "DATA_ANCHOR_PAYER")]
53 pub payer: Option<String>,
54
55 #[arg(
57 short,
58 long,
59 global = true,
60 env = "DATA_ANCHOR_OUTPUT",
61 value_enum,
62 default_value_t = OutputFormat::Text
63 )]
64 pub output: OutputFormat,
65
66 #[arg(short, long, global = true, env = "DATA_ANCHOR_INDEXER_URL")]
68 pub indexer_url: Option<IndexerUrl>,
69
70 #[arg(
72 long,
73 global = true,
74 env = "DATA_ANCHOR_INDEXER_API_TOKEN",
75 hide_env_values = true
76 )]
77 pub indexer_api_token: Option<String>,
78
79 #[arg(
81 short,
82 long,
83 global = true,
84 env = "DATA_ANCHOR_SOLANA_CONFIG_FILE",
85 default_value_t = solana_cli_config::CONFIG_FILE.as_ref().unwrap().clone()
86 )]
87 pub config_file: String,
88}
89
90impl Cli {
91 fn exit_with_missing_arg(msg: &str) -> ! {
92 Self::command()
93 .error(ErrorKind::MissingRequiredArgument, msg)
94 .exit()
95 }
96
97 fn payer_keypair(&self, config: &Config) -> String {
98 if let Some(payer) = &self.payer {
99 return payer.to_owned();
100 }
101
102 let Ok(path_to_config) = PathBuf::from_str(&self.config_file);
103
104 let Some(directory) = path_to_config.parent() else {
105 Self::exit_with_missing_arg("Failed to get the parent directory of the config file")
106 };
107
108 let path = directory.join(&config.keypair_path);
109
110 let Some(path_str) = path.to_str() else {
111 Self::exit_with_missing_arg("Failed to convert the keypair path to a string")
112 };
113
114 path_str.to_owned()
115 }
116}
117
118#[derive(Debug, Subcommand)]
119enum Command {
120 #[command(subcommand, visible_alias = "br")]
122 Blober(BloberSubCommand),
123 #[command(subcommand, visible_alias = "b")]
125 Blob(BlobSubCommand),
126 #[command(subcommand, visible_alias = "i")]
128 Indexer(IndexerSubCommand),
129 #[command(subcommand, visible_alias = "m")]
131 Benchmark(BenchmarkSubCommand),
132}
133
134pub struct Options {
135 command: Command,
136 program_id: Pubkey,
137 payer: Arc<Keypair>,
138 blober_pda: BloberIdentifier,
139 indexer: Option<IndexerUrl>,
140 indexer_api_token: Option<String>,
141 config: Config,
142 output: OutputFormat,
143}
144
145impl Options {
146 pub fn parse() -> Self {
149 trace!("Parsing options");
150 let args = Cli::parse();
151 let config = Config::load(&args.config_file).unwrap();
152 let payer_path = args.payer_keypair(&config);
153 let payer = Arc::new(Keypair::read_from_file(payer_path).unwrap());
154 trace!("Parsed options: {args:?} {config:?} {payer:?}");
155
156 let program_id = args.program_id.unwrap_or(data_anchor_blober::id());
157
158 let blober_pda = if let Some(blober_pda) = args.blober_pda {
159 blober_pda.into()
160 } else {
161 let Some(nmsp) = args.namespace else {
162 Cli::exit_with_missing_arg(NAMESPACE_MISSING_MSG);
163 };
164
165 (payer.pubkey(), nmsp).into()
166 };
167
168 Self {
169 indexer: args.indexer_url,
170 indexer_api_token: args.indexer_api_token,
171 command: args.command,
172 program_id,
173 output: args.output,
174 blober_pda,
175 payer,
176 config,
177 }
178 }
179
180 pub async fn run(self) -> DataAnchorClientResult {
182 let client = Arc::new(
183 DataAnchorClient::builder()
184 .payer(self.payer.clone())
185 .program_id(self.program_id)
186 .maybe_indexer(self.indexer)
187 .build_with_config(self.config, self.indexer_api_token.clone())
188 .await?,
189 );
190
191 let output = match self.command {
192 Command::Indexer(subcommand) => subcommand.run(client, self.blober_pda).await,
193 Command::Blob(subcommand) => subcommand.run(client, self.blober_pda).await,
194 Command::Benchmark(subcommand) => subcommand.run(client, self.blober_pda).await,
195 Command::Blober(subcommand) => {
196 subcommand
197 .run(
198 client,
199 self.blober_pda,
200 self.program_id,
201 self.payer.pubkey(),
202 )
203 .await
204 }
205 }?;
206
207 println!("{}", output.serialize_output(self.output));
208
209 Ok(())
210 }
211}