use std::ffi::OsString;
use clap::Parser;
use crate::{
commands::{
contract::invoke,
global,
token::args::{self, OutputFormat},
},
config::{
self, locator, network, sign_with,
token::{ResolvedToken, UnresolvedToken},
UnresolvedContract, UnresolvedScAddress,
},
fixed_point::FixedPoint,
output::Output,
};
#[derive(Debug, Parser, Clone)]
#[group(skip)]
pub struct Cmd {
#[arg(long = "id")]
pub id: UnresolvedToken,
#[arg(long)]
pub account: UnresolvedScAddress,
#[arg(long)]
pub decimal: bool,
#[arg(long, default_value = "text")]
pub output: OutputFormat,
#[command(flatten)]
pub network: network::Args,
#[command(flatten)]
pub locator: locator::Args,
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Config(#[from] config::Error),
#[error(transparent)]
Network(#[from] network::Error),
#[error(transparent)]
Args(#[from] args::Error),
#[error(transparent)]
Token(#[from] config::token::Error),
#[error(transparent)]
ScAddress(#[from] config::sc_address::Error),
#[error(transparent)]
Invoke(#[from] invoke::Error),
#[error(transparent)]
Serde(#[from] serde_json::Error),
#[error("could not parse {what} from the contract: {value:?}")]
ParseResult { what: &'static str, value: String },
}
impl Error {
#[must_use]
pub fn error_type(&self) -> &'static str {
match self {
Error::Config(_) => "config",
Error::Network(_) => "network",
Error::Args(e) => e.error_type(),
Error::Token(e) => e.error_type(),
Error::ScAddress(_) => "invalid_address",
Error::Invoke(_) => "invoke",
Error::Serde(_) | Error::ParseResult { .. } => "internal",
}
}
}
#[derive(Debug, serde::Serialize)]
struct BalanceResult {
balance: String,
#[serde(skip_serializing_if = "Option::is_none")]
decimals: Option<u32>,
}
impl Cmd {
fn config(&self) -> config::Args {
config::Args {
network: self.network.clone(),
source_account: config::UnresolvedMuxedAccount::default(),
locator: self.locator.clone(),
sign_with: sign_with::Args::default(),
fee: None,
inclusion_fee: None,
}
}
pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
let output = Output::new(self.output.into(), global_args.quiet);
let quiet = global_args.quiet || output.is_json();
let config = self.config();
let network = config.get_network()?;
let token = self
.id
.resolve(&config.locator, &network.network_passphrase)?;
let account = self
.account
.clone()
.resolve(&config.locator, &network.network_passphrase, None)?
.to_string();
let raw: i128 = self
.read_parsed(
&config,
quiet,
global_args.no_cache,
&token,
vec![
OsString::from("balance"),
OsString::from("--id"),
OsString::from(&account),
],
"balance",
)
.await?;
let (balance, decimals) = if self.decimal {
let decimals: u32 = self
.read_parsed(
&config,
quiet,
global_args.no_cache,
&token,
vec![OsString::from("decimals")],
"decimals",
)
.await?;
(FixedPoint::new(raw, decimals).to_string(), Some(decimals))
} else {
(raw.to_string(), None)
};
output.readable(|_| println!("{balance}"));
output.json_value(&BalanceResult { balance, decimals })?;
Ok(())
}
async fn read(
&self,
config: &config::Args,
quiet: bool,
no_cache: bool,
token: &ResolvedToken,
slop: Vec<OsString>,
) -> Result<String, Error> {
let invoke_cmd = invoke::Cmd {
contract_id: UnresolvedContract::Resolved(token.contract_id),
slop,
config: config.clone(),
send: invoke::Send::No,
..Default::default()
};
let receipt = invoke_cmd
.execute_with_receipt(config, quiet, no_cache)
.await
.map_err(|e| args::not_deployed_error(token, &e).map_or(Error::Invoke(e), Error::Args))?
.into_result();
Ok(receipt.map(|r| r.output).unwrap_or_default())
}
async fn read_parsed<T: std::str::FromStr>(
&self,
config: &config::Args,
quiet: bool,
no_cache: bool,
token: &ResolvedToken,
slop: Vec<OsString>,
what: &'static str,
) -> Result<T, Error> {
let out = self.read(config, quiet, no_cache, token, slop).await?;
out.trim()
.trim_matches('"')
.parse()
.map_err(|_| Error::ParseResult { what, value: out })
}
}