1#![doc = include_str!("../README.md")]
2
3use std::sync::Arc;
4
5use benchmark::BenchmarkSubCommand;
6use blob::BlobSubCommand;
7use blober::BloberSubCommand;
8use clap::{error::ErrorKind, CommandFactory, Parser, Subcommand};
9use data_anchor_client::{BloberClient, BloberClientResult};
10use formatting::OutputFormat;
11use indexer::IndexerSubCommand;
12use solana_cli_config::Config;
13use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::EncodableKey};
14use tracing::trace;
15
16mod benchmark;
17mod blob;
18mod blober;
19mod formatting;
20mod indexer;
21
22#[derive(Debug, Parser)]
24#[command(version, about, long_about)]
25struct Cli {
26 #[command(subcommand)]
27 pub command: Command,
28
29 #[arg(short, long, global = true, env = "BLOBER_PROGRAM_ID")]
31 pub program_id: Option<Pubkey>,
32
33 #[arg(short, long, global = true, env = "BLOBER_NAMESPACE")]
35 pub namespace: Option<String>,
36
37 #[arg(short = 's', long, global = true, env = "BLOBER_PAYER")]
39 pub payer: Option<String>,
40
41 #[arg(
43 short,
44 long,
45 global = true,
46 env = "BLOBER_OUTPUT",
47 value_enum,
48 default_value_t = OutputFormat::Text
49 )]
50 pub output: OutputFormat,
51
52 #[arg(short, long, global = true, env = "BLOBER_INDEXER_URL")]
54 pub indexer_url: Option<String>,
55
56 #[arg(
58 short,
59 long,
60 global = true,
61 env = "BLOBER_SOLANA_CONFIG_FILE",
62 default_value_t = solana_cli_config::CONFIG_FILE.as_ref().unwrap().clone()
63 )]
64 pub config_file: String,
65}
66
67impl Cli {
68 fn exit_with_missing_arg(msg: &str) -> ! {
69 Self::command()
70 .error(ErrorKind::MissingRequiredArgument, msg)
71 .exit()
72 }
73}
74
75#[derive(Debug, Subcommand)]
76enum Command {
77 #[command(subcommand, visible_alias = "br")]
79 Blober(BloberSubCommand),
80 #[command(subcommand, visible_alias = "b")]
82 Blob(BlobSubCommand),
83 #[command(subcommand, visible_alias = "i")]
85 Indexer(IndexerSubCommand),
86 #[command(subcommand, visible_alias = "m")]
88 Benchmark(BenchmarkSubCommand),
89}
90
91pub struct Options {
92 command: Command,
93 program_id: Pubkey,
94 payer: Arc<Keypair>,
95 indexer_url: Option<String>,
96 namespace: String,
97 config: Config,
98 output: OutputFormat,
99}
100
101impl Options {
102 pub fn parse() -> Self {
105 trace!("Parsing options");
106 let args = Cli::parse();
107 let config = Config::load(&args.config_file).unwrap();
108 let payer_path = args.payer.as_ref().unwrap_or(&config.keypair_path);
109 let payer = Arc::new(Keypair::read_from_file(payer_path).unwrap());
110 trace!("Parsed options: {args:?} {config:?} {payer:?}");
111
112 let Some(namespace) = args.namespace else {
113 Cli::exit_with_missing_arg(
114 "Namespace is not set. Please provide a namespace using the --namespace flag or set the BLOBER_NAMESPACE environment variable.",
115 );
116 };
117
118 let Some(program_id) = args.program_id else {
119 Cli::exit_with_missing_arg(
120 "Program ID is not set. Please provide a program ID using the --program-id flag or set the BLOBER_PROGRAM_ID environment variable.",
121 );
122 };
123
124 Self {
125 namespace,
126 indexer_url: args.indexer_url,
127 command: args.command,
128 program_id,
129 output: args.output,
130 payer,
131 config,
132 }
133 }
134
135 pub async fn run(self) -> BloberClientResult {
137 let builder = BloberClient::builder()
138 .payer(self.payer.clone())
139 .program_id(self.program_id);
140
141 let client = if let Some(indexer_url) = self.indexer_url {
142 builder
143 .indexer_from_url(&indexer_url)
144 .await?
145 .build_with_config(self.config)
146 .await?
147 } else {
148 builder.build_with_config(self.config).await?
149 };
150
151 let client = Arc::new(client);
152
153 let output = match self.command {
154 Command::Blober(subcommand) => subcommand.run(client.clone(), &self.namespace).await,
155 Command::Blob(subcommand) => subcommand.run(client.clone(), &self.namespace).await,
156 Command::Indexer(subcommand) => subcommand.run(client.clone(), &self.namespace).await,
157 Command::Benchmark(subcommand) => subcommand.run(client.clone(), &self.namespace).await,
158 }?;
159
160 println!("{}", output.serialize_output(self.output));
161
162 Ok(())
163 }
164}