#![doc = include_str!("../README.md")]
use std::{path::PathBuf, str::FromStr, sync::Arc};
use anchor_lang::prelude::Pubkey;
use benchmark::BenchmarkSubCommand;
use blob::BlobSubCommand;
use blober::BloberSubCommand;
use clap::{CommandFactory, Parser, Subcommand, error::ErrorKind};
use data_anchor_client::{BloberIdentifier, DataAnchorClient, DataAnchorClientResult, IndexerUrl};
use formatting::OutputFormat;
use indexer::IndexerSubCommand;
use solana_cli_config::Config;
use solana_keypair::Keypair;
use solana_signer::{EncodableKey, Signer};
use tokio_util::sync::CancellationToken;
use tracing::trace;
mod benchmark;
mod blob;
mod blober;
mod formatting;
mod indexer;
const NAMESPACE_MISSING_MSG: &str = "Namespace is not set. Please provide a namespace using the --namespace flag or set the DATA_ANCHOR_NAMESPACE environment variable.";
#[derive(Debug, Parser)]
#[command(version, about, long_about)]
struct Cli {
#[command(subcommand)]
pub command: Command,
#[arg(short, long, global = true, env = "DATA_ANCHOR_PROGRAM_ID")]
pub program_id: Option<Pubkey>,
#[arg(short, long, global = true, env = "DATA_ANCHOR_NAMESPACE")]
pub namespace: Option<String>,
#[arg(
short,
long,
global = true,
env = "DATA_ANCHOR_BLOBER_PDA",
value_name = "BLOBER_PDA"
)]
pub blober_pda: Option<Pubkey>,
#[arg(short = 's', long, global = true, env = "DATA_ANCHOR_PAYER")]
pub payer: Option<String>,
#[arg(
short,
long,
global = true,
env = "DATA_ANCHOR_OUTPUT",
value_enum,
default_value_t = OutputFormat::Text
)]
pub output: OutputFormat,
#[arg(short, long, global = true, env = "DATA_ANCHOR_INDEXER_URL")]
pub indexer_url: Option<IndexerUrl>,
#[arg(
long,
global = true,
env = "DATA_ANCHOR_INDEXER_API_TOKEN",
hide_env_values = true
)]
pub indexer_api_token: Option<String>,
#[arg(
short,
long,
global = true,
env = "DATA_ANCHOR_SOLANA_CONFIG_FILE",
default_value_t = solana_cli_config::CONFIG_FILE.as_ref().unwrap().clone()
)]
pub config_file: String,
}
impl Cli {
fn exit_with_missing_arg(msg: &str) -> ! {
Self::command()
.error(ErrorKind::MissingRequiredArgument, msg)
.exit()
}
fn payer_keypair(&self, config: &Config) -> String {
if let Some(payer) = &self.payer {
return payer.to_owned();
}
let Ok(path_to_config) = PathBuf::from_str(&self.config_file);
let Some(directory) = path_to_config.parent() else {
Self::exit_with_missing_arg("Failed to get the parent directory of the config file")
};
let path = directory.join(&config.keypair_path);
let Some(path_str) = path.to_str() else {
Self::exit_with_missing_arg("Failed to convert the keypair path to a string")
};
path_str.to_owned()
}
}
#[derive(Debug, Subcommand)]
enum Command {
#[command(subcommand, visible_alias = "br")]
Blober(BloberSubCommand),
#[command(subcommand, visible_alias = "b")]
Blob(BlobSubCommand),
#[command(subcommand, visible_alias = "i")]
Indexer(IndexerSubCommand),
#[command(subcommand, visible_alias = "m")]
Benchmark(BenchmarkSubCommand),
}
pub struct Options {
command: Command,
program_id: Pubkey,
payer: Arc<Keypair>,
blober_pda: BloberIdentifier,
indexer: Option<IndexerUrl>,
indexer_api_token: Option<String>,
config: Config,
output: OutputFormat,
}
impl Options {
pub fn parse() -> Self {
trace!("Parsing options");
let args = Cli::parse();
let config = Config::load(&args.config_file).unwrap();
let payer_path = args.payer_keypair(&config);
let payer = Arc::new(Keypair::read_from_file(payer_path).unwrap());
trace!("Parsed options: {args:?} {config:?} {payer:?}");
let program_id = args.program_id.unwrap_or(data_anchor_blober::id());
let blober_pda = if let Some(blober_pda) = args.blober_pda {
blober_pda.into()
} else {
let Some(nmsp) = args.namespace else {
Cli::exit_with_missing_arg(NAMESPACE_MISSING_MSG);
};
(payer.pubkey(), nmsp).into()
};
Self {
indexer: args.indexer_url,
indexer_api_token: args.indexer_api_token,
command: args.command,
program_id,
output: args.output,
blober_pda,
payer,
config,
}
}
pub async fn run(self) -> DataAnchorClientResult {
let cancellation_token = CancellationToken::new();
let client = Arc::new(
DataAnchorClient::builder()
.payer(self.payer.clone())
.program_id(self.program_id)
.maybe_indexer(self.indexer)
.build_with_config(
self.config,
cancellation_token.clone(),
self.indexer_api_token.clone(),
)
.await?,
);
let output = match self.command {
Command::Indexer(subcommand) => subcommand.run(client, self.blober_pda).await,
Command::Blob(subcommand) => subcommand.run(client, self.blober_pda).await,
Command::Benchmark(subcommand) => subcommand.run(client, self.blober_pda).await,
Command::Blober(subcommand) => {
subcommand
.run(
client,
self.blober_pda,
self.program_id,
self.payer.pubkey(),
)
.await
}
}?;
println!("{}", output.serialize_output(self.output));
cancellation_token.cancel();
Ok(())
}
}