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::{CommandFactory, Parser, Subcommand, error::ErrorKind};
9use data_anchor_client::{DataAnchorClient, DataAnchorClientResult};
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 = "DATA_ANCHOR_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 = "DATA_ANCHOR_NAMESPACE")]
35    pub namespace: Option<String>,
36
37    /// The payer account to use for transactions.
38    #[arg(short = 's', long, global = true, env = "DATA_ANCHOR_PAYER")]
39    pub payer: Option<String>,
40
41    /// The output format to use.
42    #[arg(
43        short,
44        long,
45        global = true,
46        env = "DATA_ANCHOR_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 = "DATA_ANCHOR_INDEXER_URL")]
54    pub indexer_url: Option<String>,
55
56    /// The API token for the indexer, if required.
57    #[arg(
58        long,
59        global = true,
60        env = "DATA_ANCHOR_INDEXER_API_TOKEN",
61        hide_env_values = true
62    )]
63    pub indexer_api_token: Option<String>,
64
65    /// The path to the Solana [`Config`] file.
66    #[arg(
67        short,
68        long,
69        global = true,
70        env = "DATA_ANCHOR_SOLANA_CONFIG_FILE",
71        default_value_t = solana_cli_config::CONFIG_FILE.as_ref().unwrap().clone()
72    )]
73    pub config_file: String,
74}
75
76impl Cli {
77    fn exit_with_missing_arg(msg: &str) -> ! {
78        Self::command()
79            .error(ErrorKind::MissingRequiredArgument, msg)
80            .exit()
81    }
82}
83
84#[derive(Debug, Subcommand)]
85enum Command {
86    /// Subcommands for managing the blober account.
87    #[command(subcommand, visible_alias = "br")]
88    Blober(BloberSubCommand),
89    /// Subcommands for managing blobs.
90    #[command(subcommand, visible_alias = "b")]
91    Blob(BlobSubCommand),
92    /// Subcommands for querying the indexer.
93    #[command(subcommand, visible_alias = "i")]
94    Indexer(IndexerSubCommand),
95    /// Subcommands for benchmarking the blober.
96    #[command(subcommand, visible_alias = "m")]
97    Benchmark(BenchmarkSubCommand),
98}
99
100pub struct Options {
101    command: Command,
102    program_id: Pubkey,
103    payer: Arc<Keypair>,
104    indexer_url: Option<String>,
105    indexer_api_token: Option<String>,
106    namespace: String,
107    config: Config,
108    output: OutputFormat,
109}
110
111impl Options {
112    /// Parse the CLI options and load data from the Solana [`Config`] file and the payer
113    /// [`Keypair`].
114    pub fn parse() -> Self {
115        trace!("Parsing options");
116        let args = Cli::parse();
117        let config = Config::load(&args.config_file).unwrap();
118        let payer_path = args.payer.as_ref().unwrap_or(&config.keypair_path);
119        let payer = Arc::new(Keypair::read_from_file(payer_path).unwrap());
120        trace!("Parsed options: {args:?} {config:?} {payer:?}");
121
122        let Some(namespace) = args.namespace else {
123            Cli::exit_with_missing_arg(
124                "Namespace is not set. Please provide a namespace using the --namespace flag or set the DATA_ANCHOR_NAMESPACE environment variable.",
125            );
126        };
127
128        let Some(program_id) = args.program_id else {
129            Cli::exit_with_missing_arg(
130                "Program ID is not set. Please provide a program ID using the --program-id flag or set the DATA_ANCHOR_PROGRAM_ID environment variable.",
131            );
132        };
133
134        Self {
135            namespace,
136            indexer_url: args.indexer_url,
137            indexer_api_token: args.indexer_api_token,
138            command: args.command,
139            program_id,
140            output: args.output,
141            payer,
142            config,
143        }
144    }
145
146    /// Run the parsed CLI command.
147    pub async fn run(self) -> DataAnchorClientResult {
148        let builder = DataAnchorClient::builder()
149            .payer(self.payer.clone())
150            .program_id(self.program_id);
151
152        let client = if let Some(indexer_url) = self.indexer_url {
153            builder
154                .indexer_from_url(&indexer_url, self.indexer_api_token.clone())
155                .await?
156                .build_with_config(self.config)
157                .await?
158        } else {
159            builder.build_with_config(self.config).await?
160        };
161
162        let client = Arc::new(client);
163
164        let output = match self.command {
165            Command::Blober(subcommand) => subcommand.run(client.clone(), &self.namespace).await,
166            Command::Blob(subcommand) => subcommand.run(client.clone(), &self.namespace).await,
167            Command::Indexer(subcommand) => subcommand.run(client.clone(), &self.namespace).await,
168            Command::Benchmark(subcommand) => subcommand.run(client.clone(), &self.namespace).await,
169        }?;
170
171        println!("{}", output.serialize_output(self.output));
172
173        Ok(())
174    }
175}