soroban_cli/commands/token/
balance.rs1use std::ffi::OsString;
2
3use clap::Parser;
4
5use crate::{
6 commands::{
7 contract::invoke,
8 global,
9 token::args::{self, OutputFormat},
10 },
11 config::{
12 self, locator, network, sign_with,
13 token::{ResolvedToken, UnresolvedToken},
14 UnresolvedContract, UnresolvedScAddress,
15 },
16 fixed_point::FixedPoint,
17 output::Output,
18};
19
20#[derive(Debug, Parser, Clone)]
21#[group(skip)]
22pub struct Cmd {
23 #[arg(long = "id")]
26 pub id: UnresolvedToken,
27
28 #[arg(long)]
30 pub account: UnresolvedScAddress,
31
32 #[arg(long)]
35 pub decimal: bool,
36
37 #[arg(long, default_value = "text")]
39 pub output: OutputFormat,
40
41 #[command(flatten)]
42 pub network: network::Args,
43
44 #[command(flatten)]
45 pub locator: locator::Args,
46}
47
48#[derive(thiserror::Error, Debug)]
49pub enum Error {
50 #[error(transparent)]
51 Config(#[from] config::Error),
52 #[error(transparent)]
53 Network(#[from] network::Error),
54 #[error(transparent)]
55 Args(#[from] args::Error),
56 #[error(transparent)]
57 Token(#[from] config::token::Error),
58 #[error(transparent)]
59 ScAddress(#[from] config::sc_address::Error),
60 #[error(transparent)]
61 Invoke(#[from] invoke::Error),
62 #[error(transparent)]
63 Serde(#[from] serde_json::Error),
64
65 #[error("could not parse {what} from the contract: {value:?}")]
66 ParseResult { what: &'static str, value: String },
67}
68
69impl Error {
70 #[must_use]
72 pub fn error_type(&self) -> &'static str {
73 match self {
74 Error::Config(_) => "config",
75 Error::Network(_) => "network",
76 Error::Args(e) => e.error_type(),
77 Error::Token(e) => e.error_type(),
78 Error::ScAddress(_) => "invalid_address",
79 Error::Invoke(_) => "invoke",
80 Error::Serde(_) | Error::ParseResult { .. } => "internal",
81 }
82 }
83}
84
85#[derive(Debug, serde::Serialize)]
87struct BalanceResult {
88 balance: String,
91 #[serde(skip_serializing_if = "Option::is_none")]
93 decimals: Option<u32>,
94}
95
96impl Cmd {
97 fn config(&self) -> config::Args {
100 config::Args {
101 network: self.network.clone(),
102 source_account: config::UnresolvedMuxedAccount::default(),
103 locator: self.locator.clone(),
104 sign_with: sign_with::Args::default(),
105 fee: None,
106 inclusion_fee: None,
107 }
108 }
109
110 pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
111 let output = Output::new(self.output.into(), global_args.quiet);
112 let quiet = global_args.quiet || output.is_json();
115 let config = self.config();
116 let network = config.get_network()?;
117
118 let token = self
119 .id
120 .resolve(&config.locator, &network.network_passphrase)?;
121 let account = self
122 .account
123 .clone()
124 .resolve(&config.locator, &network.network_passphrase, None)?
125 .to_string();
126
127 let raw: i128 = self
128 .read_parsed(
129 &config,
130 quiet,
131 global_args.no_cache,
132 &token,
133 vec![
134 OsString::from("balance"),
135 OsString::from("--id"),
136 OsString::from(&account),
137 ],
138 "balance",
139 )
140 .await?;
141
142 let (balance, decimals) = if self.decimal {
143 let decimals: u32 = self
147 .read_parsed(
148 &config,
149 quiet,
150 global_args.no_cache,
151 &token,
152 vec![OsString::from("decimals")],
153 "decimals",
154 )
155 .await?;
156 (FixedPoint::new(raw, decimals).to_string(), Some(decimals))
157 } else {
158 (raw.to_string(), None)
159 };
160
161 output.readable(|_| println!("{balance}"));
162 output.json_value(&BalanceResult { balance, decimals })?;
163
164 Ok(())
165 }
166
167 async fn read(
169 &self,
170 config: &config::Args,
171 quiet: bool,
172 no_cache: bool,
173 token: &ResolvedToken,
174 slop: Vec<OsString>,
175 ) -> Result<String, Error> {
176 let invoke_cmd = invoke::Cmd {
177 contract_id: UnresolvedContract::Resolved(token.contract_id),
178 slop,
179 config: config.clone(),
180 send: invoke::Send::No,
181 ..Default::default()
182 };
183
184 let receipt = invoke_cmd
185 .execute_with_receipt(config, quiet, no_cache)
186 .await
187 .map_err(|e| args::not_deployed_error(token, &e).map_or(Error::Invoke(e), Error::Args))?
188 .into_result();
189
190 Ok(receipt.map(|r| r.output).unwrap_or_default())
191 }
192
193 async fn read_parsed<T: std::str::FromStr>(
196 &self,
197 config: &config::Args,
198 quiet: bool,
199 no_cache: bool,
200 token: &ResolvedToken,
201 slop: Vec<OsString>,
202 what: &'static str,
203 ) -> Result<T, Error> {
204 let out = self.read(config, quiet, no_cache, token, slop).await?;
205 out.trim()
209 .trim_matches('"')
210 .parse()
211 .map_err(|_| Error::ParseResult { what, value: out })
212 }
213}