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