data_anchor/
lib.rs

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/// The CLI options for the Blober CLI client.
23#[derive(Debug, Parser)]
24#[command(version, about, long_about)]
25struct Cli {
26    #[command(subcommand)]
27    pub command: Command,
28
29    /// The program ID of the Blober program.
30    #[arg(short, long, global = true, env = "BLOBER_PROGRAM_ID")]
31    pub program_id: Option<Pubkey>,
32
33    /// The namespace to use to generate the blober PDA.
34    #[arg(short, long, global = true, env = "BLOBER_NAMESPACE")]
35    pub namespace: Option<String>,
36
37    /// The payer account to use for transactions.
38    #[arg(short = 's', long, global = true, env = "BLOBER_PAYER")]
39    pub payer: Option<String>,
40
41    /// The output format to use.
42    #[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    /// The URL of the indexer to use.
53    #[arg(short, long, global = true, env = "BLOBER_INDEXER_URL")]
54    pub indexer_url: Option<String>,
55
56    /// The path to the Solana [`Config`] file.
57    #[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    /// Subcommands for managing the blober account.
78    #[command(subcommand, visible_alias = "br")]
79    Blober(BloberSubCommand),
80    /// Subcommands for managing blobs.
81    #[command(subcommand, visible_alias = "b")]
82    Blob(BlobSubCommand),
83    /// Subcommands for querying the indexer.
84    #[command(subcommand, visible_alias = "i")]
85    Indexer(IndexerSubCommand),
86    /// Subcommands for benchmarking the blober.
87    #[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    /// Parse the CLI options and load data from the Solana [`Config`] file and the payer
103    /// [`Keypair`].
104    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    /// Run the parsed CLI command.
136    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}