Merge pull request #29 from Electric-Coin-Company/exchange-rate-usd
balance: Add currency conversion over Tor
This commit is contained in:
commit
3a60108fe9
File diff suppressed because it is too large
Load Diff
|
@ -26,7 +26,7 @@ tonic = { version = "0.12", features = ["gzip", "tls-webpki-roots"] }
|
|||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
||||
zcash_address = "0.3.2"
|
||||
zcash_client_backend = { version = "0.12.1", features = ["lightwalletd-tonic", "orchard"] }
|
||||
zcash_client_backend = { version = "0.12.1", features = ["lightwalletd-tonic", "orchard", "tor"] }
|
||||
zcash_client_sqlite = { version = "0.10.2", features = ["unstable", "orchard"] }
|
||||
zcash_keys = { version = "0.2", features = ["unstable", "orchard"] }
|
||||
zcash_primitives = "0.15"
|
||||
|
@ -34,6 +34,10 @@ zcash_proofs = "0.15"
|
|||
zcash_protocol = "0.1"
|
||||
zip321 = "0.0.0"
|
||||
|
||||
# Currency conversion
|
||||
iso_currency = { version = "0.4", features = ["with-serde"] }
|
||||
rust_decimal = "1"
|
||||
|
||||
# TUI
|
||||
crossterm = { version = "0.27", optional = true, features = ["event-stream"] }
|
||||
ratatui = { version = "0.26", optional = true }
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
use std::path::Path;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use gumdrop::Options;
|
||||
|
||||
use zcash_client_backend::data_api::WalletRead;
|
||||
use iso_currency::Currency;
|
||||
use rust_decimal::{prelude::FromPrimitive, Decimal};
|
||||
use tracing::{info, warn};
|
||||
use zcash_client_backend::{data_api::WalletRead, tor};
|
||||
use zcash_client_sqlite::WalletDb;
|
||||
use zcash_protocol::value::{Zatoshis, COIN};
|
||||
|
||||
use crate::{
|
||||
data::{get_db_paths, get_wallet_network},
|
||||
data::{get_db_paths, get_tor_dir, get_wallet_network},
|
||||
error,
|
||||
ui::format_zec,
|
||||
MIN_CONFIRMATIONS,
|
||||
|
@ -13,13 +19,16 @@ use crate::{
|
|||
|
||||
// Options accepted for the `balance` command
|
||||
#[derive(Debug, Options)]
|
||||
pub(crate) struct Command {}
|
||||
pub(crate) struct Command {
|
||||
#[options(help = "Convert ZEC values into the given currency")]
|
||||
convert: Option<Currency>,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub(crate) fn run(self, wallet_dir: Option<String>) -> Result<(), anyhow::Error> {
|
||||
pub(crate) async fn run(self, wallet_dir: Option<String>) -> Result<(), anyhow::Error> {
|
||||
let params = get_wallet_network(wallet_dir.as_ref())?;
|
||||
|
||||
let (_, db_data) = get_db_paths(wallet_dir);
|
||||
let (_, db_data) = get_db_paths(wallet_dir.as_ref());
|
||||
let db_data = WalletDb::for_path(db_data, params)?;
|
||||
let account_id = *db_data
|
||||
.get_account_ids()?
|
||||
|
@ -30,6 +39,12 @@ impl Command {
|
|||
.get_current_address(account_id)?
|
||||
.ok_or(error::Error::InvalidRecipient)?;
|
||||
|
||||
let printer = if let Some(currency) = self.convert {
|
||||
ValuePrinter::with_exchange_rate(&get_tor_dir(wallet_dir), currency).await?
|
||||
} else {
|
||||
ValuePrinter::ZecOnly
|
||||
};
|
||||
|
||||
if let Some(wallet_summary) = db_data.get_wallet_summary(MIN_CONFIRMATIONS.into())? {
|
||||
let balance = wallet_summary
|
||||
.account_balances()
|
||||
|
@ -45,17 +60,20 @@ impl Command {
|
|||
(*progress.numerator() as f64) * 100f64 / (*progress.denominator() as f64)
|
||||
);
|
||||
}
|
||||
println!(" Balance: {}", format_zec(balance.total()));
|
||||
println!(" Balance: {}", printer.format(balance.total()));
|
||||
println!(
|
||||
" Sapling Spendable: {}",
|
||||
format_zec(balance.sapling_balance().spendable_value())
|
||||
printer.format(balance.sapling_balance().spendable_value()),
|
||||
);
|
||||
println!(
|
||||
" Orchard Spendable: {}",
|
||||
format_zec(balance.orchard_balance().spendable_value())
|
||||
printer.format(balance.orchard_balance().spendable_value()),
|
||||
);
|
||||
#[cfg(feature = "transparent-inputs")]
|
||||
println!(" Unshielded: {}", format_zec(balance.unshielded()));
|
||||
println!(
|
||||
" Unshielded: {}",
|
||||
printer.format(balance.unshielded()),
|
||||
);
|
||||
} else {
|
||||
println!("Insufficient information to build a wallet summary.");
|
||||
}
|
||||
|
@ -63,3 +81,45 @@ impl Command {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
enum ValuePrinter {
|
||||
WithConversion { currency: Currency, rate: Decimal },
|
||||
ZecOnly,
|
||||
}
|
||||
|
||||
impl ValuePrinter {
|
||||
async fn with_exchange_rate(tor_dir: &Path, currency: Currency) -> anyhow::Result<Self> {
|
||||
// Ensure Tor directory exists.
|
||||
tokio::fs::create_dir_all(tor_dir).await?;
|
||||
|
||||
let tor = tor::Client::create(tor_dir).await?;
|
||||
|
||||
info!("Fetching {:?}/ZEC exchange rate", currency);
|
||||
let exchanges = tor::http::cryptex::Exchanges::unauthenticated_known_with_gemini_trusted();
|
||||
let usd_zec = tor.get_latest_zec_to_usd_rate(&exchanges).await?;
|
||||
|
||||
if currency == Currency::USD {
|
||||
let rate = usd_zec;
|
||||
info!("Current {:?}/ZEC exchange rate: {}", currency, rate);
|
||||
Ok(Self::WithConversion { currency, rate })
|
||||
} else {
|
||||
warn!("{:?}/ZEC exchange rate is unsupported", currency);
|
||||
Ok(Self::ZecOnly)
|
||||
}
|
||||
}
|
||||
|
||||
fn format(&self, value: Zatoshis) -> String {
|
||||
match self {
|
||||
ValuePrinter::WithConversion { currency, rate } => {
|
||||
format!(
|
||||
"{} ({}{:.2})",
|
||||
format_zec(value),
|
||||
currency.symbol(),
|
||||
rate * Decimal::from_u64(value.into_u64()).unwrap()
|
||||
/ Decimal::from_u64(COIN).unwrap(),
|
||||
)
|
||||
}
|
||||
ValuePrinter::ZecOnly => format_zec(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ const DEFAULT_WALLET_DIR: &str = "./zec_sqlite_wallet";
|
|||
const KEYS_FILE: &str = "keys.toml";
|
||||
const BLOCKS_FOLDER: &str = "blocks";
|
||||
const DATA_DB: &str = "data.sqlite";
|
||||
const TOR_DIR: &str = "tor";
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub(crate) enum Network {
|
||||
|
@ -195,6 +196,14 @@ pub(crate) fn get_block_path(fsblockdb_root: &Path, meta: &BlockMeta) -> PathBuf
|
|||
meta.block_file_path(&fsblockdb_root.join(BLOCKS_FOLDER))
|
||||
}
|
||||
|
||||
pub(crate) fn get_tor_dir<P: AsRef<Path>>(wallet_dir: Option<P>) -> PathBuf {
|
||||
wallet_dir
|
||||
.as_ref()
|
||||
.map(|p| p.as_ref())
|
||||
.unwrap_or(DEFAULT_WALLET_DIR.as_ref())
|
||||
.join(TOR_DIR)
|
||||
}
|
||||
|
||||
pub(crate) async fn erase_wallet_state<P: AsRef<Path>>(wallet_dir: Option<P>) {
|
||||
let (fsblockdb_root, db_data) = get_db_paths(wallet_dir);
|
||||
let blocks_meta = fsblockdb_root.join("blockmeta.sqlite");
|
||||
|
|
|
@ -129,7 +129,7 @@ fn main() -> Result<(), anyhow::Error> {
|
|||
.await
|
||||
}
|
||||
Some(Command::Enhance(command)) => command.run(opts.wallet_dir).await,
|
||||
Some(Command::Balance(command)) => command.run(opts.wallet_dir),
|
||||
Some(Command::Balance(command)) => command.run(opts.wallet_dir).await,
|
||||
Some(Command::ListTx(command)) => command.run(opts.wallet_dir),
|
||||
Some(Command::ListUnspent(command)) => command.run(opts.wallet_dir),
|
||||
Some(Command::Propose(command)) => command.run(opts.wallet_dir).await,
|
||||
|
|
Loading…
Reference in New Issue