1use color_eyre::{eyre::eyre, Result};
19
20use autonomi::AttoTokens;
21use evmlib::common::{Amount, U256};
22
23use crate::client::DwebClient;
24
25#[derive(clap::ValueEnum, Clone, Debug)]
27pub enum ShowCost {
28 Token,
29 Gas,
30 Both,
31 None,
32}
33
34#[derive(Clone)]
35pub struct Spends {
36 client: DwebClient,
37 pub token: Amount,
38 pub gas: Amount,
39
40 show_cost: ShowCost,
41 label: String,
42}
43
44impl Spends {
46 pub async fn new(client: &DwebClient, label: Option<&str>) -> Result<Spends> {
47 let label = label.unwrap_or("Cost total: ").to_string();
48 let client = client.clone();
49 let show_cost = client.api_control.show_dweb_costs.clone();
50 let token = client.wallet.balance_of_tokens().await?;
51 let gas = client.wallet.balance_of_gas_tokens().await?;
52 Ok(Spends {
53 token: token,
54 gas: gas,
55 client,
56 show_cost,
57 label,
58 })
59 }
60
61 pub async fn update(&mut self) -> Result<()> {
62 self.token = self.client.wallet.balance_of_tokens().await?;
63 self.gas = self.client.wallet.balance_of_gas_tokens().await?;
64 Ok(())
65 }
66
67 pub async fn show_spend(&self) -> Result<()> {
69 let label = &self.label;
70 let spent_gas = AttoTokens::from(self.spent_gas().await?);
71 let spent_gas_string = format_tokens(spent_gas.as_atto());
72
73 let spent_tokens = AttoTokens::from(self.spent_tokens().await?);
74 let spent_tokens_string = format_tokens(spent_tokens.as_atto());
75
76 let spent_gas = if let Some(eth_rate) = &self.client.eth_rate {
77 format!(
78 "{label}{} ({spent_gas_string} Gas)",
79 eth_rate.to_currency(&spent_gas)
80 )
81 } else {
82 format!("{label}{spent_gas_string} Gas")
83 };
84 let spent_tokens = if let Some(ant_rate) = &self.client.ant_rate {
85 format!(
86 "{label}{} ({spent_tokens_string} ANT)",
87 ant_rate.to_currency(&spent_tokens)
88 )
89 } else {
90 format!("{label}{spent_tokens_string} ANT")
91 };
92
93 match self.show_cost {
94 ShowCost::Gas => {
95 println!("{spent_gas}");
96 }
97 ShowCost::Token => {
98 println!("{spent_tokens}");
99 }
100 ShowCost::Both => {
101 println!("{spent_gas}");
102 println!("{spent_tokens}");
103 }
104 _ => {}
105 }
106 Ok(())
107 }
108
109 pub async fn spent_tokens(&self) -> Result<Amount> {
110 let balance = self.client.wallet.balance_of_tokens().await?;
111 match self.token.checked_sub(balance) {
112 Some(spent) => Ok(spent),
113 None => Err(eyre!("Error calculating spent tokens")),
114 }
115 }
116
117 pub async fn spent_gas(&self) -> Result<Amount> {
118 let balance = self.client.wallet.balance_of_gas_tokens().await?;
119 match self.gas.checked_sub(balance) {
120 Some(spent) => Ok(spent),
121 None => {
122 println!("Error calculating spent gas at balance.checked_sub(self.gas)");
123 Err(eyre!("Error calculating spent gas"))
124 }
125 }
126 }
127}
128
129const UNITS_PER_TOKEN_U64: u64 = 1_000_000_000_000_000_000;
130const UNITS_PER_TOKEN_F32: f32 = 1_000_000_000_000_000_000.0;
131
132pub fn format_tokens(amount: Amount) -> String {
134 let unit = amount / Amount::from(UNITS_PER_TOKEN_U64);
135 let remainder = amount % Amount::from(UNITS_PER_TOKEN_U64);
136 format!("{unit}.{remainder:018}").to_string()
137}
138
139pub async fn show_spend_return_value<T>(spends: &Spends, value: T) -> T {
143 let _ = spends.show_spend().await;
144 value
145}
146
147const RATE_VAR_PREFIX: &str = "DWEB_RATE_";
148
149#[derive(Clone)]
150pub struct Rate {
151 pub ticker: String, pub currency: String, pub rate: f32,
154 }
156
157impl Rate {
158 pub fn from_environment(ticker: String) -> Option<Rate> {
159 let env_var = Self::env_var_for(&ticker);
160 let env_value = match std::env::var(&env_var) {
161 Ok(value) => value,
162 Err(_) => return None,
163 };
164
165 let mut iter = env_value.split(',');
166 let rate = match iter.next().unwrap_or("0.0").parse::<f32>() {
167 Ok(rate) => rate,
168 Err(_) => return None,
169 };
170
171 let currency = iter.next().unwrap_or("ERROR").to_string();
172
173 let _date = iter.next().unwrap_or("ERROR");
175
176 Some(Rate {
177 ticker: ticker.clone(),
178 currency,
179 rate,
180 })
181 }
182
183 pub fn env_var_for(ticker: &String) -> String {
184 return format!("{RATE_VAR_PREFIX}{ticker}");
185 }
186
187 pub fn to_currency(&self, tokens: &AttoTokens) -> String {
188 const MIN_FACTOR: f32 = 100_f32; let factor = match self.rate {
190 rate if rate < 0.001 => MIN_FACTOR * 1000_f32,
191 rate if rate < 0.01 => MIN_FACTOR * 100_f32,
192 rate if rate < 0.1 => MIN_FACTOR * 10_f32,
193 rate if rate < 1. => MIN_FACTOR,
194 rate if rate >= 1. => MIN_FACTOR,
195 _ => 1.0, };
197
198 let scaled_rate = U256::from(self.rate * factor);
200 let scaled_value = scaled_rate * tokens.as_atto();
201 let scaled_value = scaled_value.to::<u64>();
202 let value = match format!("{scaled_value}").parse::<f32>() {
203 Ok(scaled_value) => (scaled_value / factor) / UNITS_PER_TOKEN_F32,
204 Err(_) => {
205 return "[Invalid value]".to_string();
206 }
207 };
208
209 format!("{}{value:.8}", self.currency).to_string()
210 }
211}