use crate::{
cmd::{
Command,
config::{ConfigSettings, Endpoint},
},
utils::HexBytes,
};
use anyhow::{Context, Result, anyhow};
use clap::Parser;
use gsdk::{Api, SignedApi, ext::sp_core};
use gsigner::schemes::sr25519::{Keyring, Keystore};
use std::{env, fs, io, path::PathBuf, time::Duration};
use tracing_subscriber::EnvFilter;
#[derive(Debug, Clone, Parser)]
pub struct Opts {
#[arg(short, long, default_value = "60000")]
pub timeout: u64,
#[clap(short, long = "verbose", action = clap::ArgAction::Count)]
pub verbosity: u8,
#[arg(short, long)]
pub endpoint: Option<Endpoint>,
#[arg(short, long)]
pub passwd: Option<HexBytes>,
}
#[derive(Debug)]
pub struct App {
opts: Opts,
}
impl App {
pub fn new(opts: Opts) -> Self {
Self { opts }
}
pub async fn run(mut self, command: Command) -> Result<()> {
sp_core::crypto::set_default_ss58_version(runtime_primitives::VARA_SS58_PREFIX.into());
let name = env!("CARGO_PKG_NAME");
let filter = if env::var(EnvFilter::DEFAULT_ENV).is_ok() {
EnvFilter::from_default_env()
} else {
match self.opts.verbosity {
0 => format!("{name}=info,gsdk=info").into(),
1 => format!("{name}=debug,gsdk=debug").into(),
2 => "debug".into(),
_ => "trace".into(),
}
};
tracing_subscriber::fmt()
.with_env_filter(filter)
.without_time()
.with_writer(io::stderr)
.try_init()
.map_err(|err| anyhow!("{err}"))?;
command.exec(&mut self).await
}
pub fn config(&self) -> Result<ConfigSettings> {
ConfigSettings::read()
}
pub async fn api(&self) -> Result<Api> {
let endpoint = match self.opts.endpoint.clone() {
Some(endpoint) => endpoint,
None => self.config()?.endpoint,
};
Ok(Api::builder()
.timeout(Duration::from_millis(self.opts.timeout))
.uri(endpoint.as_str())
.build()
.await?)
}
pub fn keyring(&self) -> Result<Keyring> {
let path = env::var_os("GCLI_CONFIG_DIR")
.map_or_else(store_path, |dir| Ok(PathBuf::from(dir).join("keyring")))?;
if !path.exists() {
fs::create_dir_all(&path).context("failed to create keyring directory")?;
}
Keyring::load(path)
}
pub fn keystore(&self) -> Result<Keystore> {
let mut keyring = self.keyring()?;
Ok(keyring.primary()?.clone())
}
pub fn ss58_address(&self) -> Result<String> {
Ok(self.keystore()?.address.to_owned())
}
pub async fn signed_api(&self) -> Result<SignedApi> {
let passwd_str = self
.opts
.passwd
.as_deref()
.map(|b| std::str::from_utf8(b))
.transpose()
.context("password must be valid UTF-8")?;
let private_key = self.keystore()?.private_key_with_password(passwd_str)?;
Ok(SignedApi::with_pair(
self.api().await?,
private_key.keypair().into(),
))
}
}
fn store_path() -> Result<PathBuf> {
let data_dir = dirs::data_dir().ok_or_else(|| anyhow!("Failed to locate app directory."))?;
let store = gsigner::keyring::resolve_namespaced_path(
data_dir.join("gsigner"),
gsigner::keyring::NAMESPACE_SR,
);
fs::create_dir_all(&store)?;
Ok(store)
}