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 = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
|
||||||
zcash_address = "0.3.2"
|
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_client_sqlite = { version = "0.10.2", features = ["unstable", "orchard"] }
|
||||||
zcash_keys = { version = "0.2", features = ["unstable", "orchard"] }
|
zcash_keys = { version = "0.2", features = ["unstable", "orchard"] }
|
||||||
zcash_primitives = "0.15"
|
zcash_primitives = "0.15"
|
||||||
|
@ -34,6 +34,10 @@ zcash_proofs = "0.15"
|
||||||
zcash_protocol = "0.1"
|
zcash_protocol = "0.1"
|
||||||
zip321 = "0.0.0"
|
zip321 = "0.0.0"
|
||||||
|
|
||||||
|
# Currency conversion
|
||||||
|
iso_currency = { version = "0.4", features = ["with-serde"] }
|
||||||
|
rust_decimal = "1"
|
||||||
|
|
||||||
# TUI
|
# TUI
|
||||||
crossterm = { version = "0.27", optional = true, features = ["event-stream"] }
|
crossterm = { version = "0.27", optional = true, features = ["event-stream"] }
|
||||||
ratatui = { version = "0.26", optional = true }
|
ratatui = { version = "0.26", optional = true }
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use gumdrop::Options;
|
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_client_sqlite::WalletDb;
|
||||||
|
use zcash_protocol::value::{Zatoshis, COIN};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
data::{get_db_paths, get_wallet_network},
|
data::{get_db_paths, get_tor_dir, get_wallet_network},
|
||||||
error,
|
error,
|
||||||
ui::format_zec,
|
ui::format_zec,
|
||||||
MIN_CONFIRMATIONS,
|
MIN_CONFIRMATIONS,
|
||||||
|
@ -13,13 +19,16 @@ use crate::{
|
||||||
|
|
||||||
// Options accepted for the `balance` command
|
// Options accepted for the `balance` command
|
||||||
#[derive(Debug, Options)]
|
#[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 {
|
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 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 db_data = WalletDb::for_path(db_data, params)?;
|
||||||
let account_id = *db_data
|
let account_id = *db_data
|
||||||
.get_account_ids()?
|
.get_account_ids()?
|
||||||
|
@ -30,6 +39,12 @@ impl Command {
|
||||||
.get_current_address(account_id)?
|
.get_current_address(account_id)?
|
||||||
.ok_or(error::Error::InvalidRecipient)?;
|
.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())? {
|
if let Some(wallet_summary) = db_data.get_wallet_summary(MIN_CONFIRMATIONS.into())? {
|
||||||
let balance = wallet_summary
|
let balance = wallet_summary
|
||||||
.account_balances()
|
.account_balances()
|
||||||
|
@ -45,17 +60,20 @@ impl Command {
|
||||||
(*progress.numerator() as f64) * 100f64 / (*progress.denominator() as f64)
|
(*progress.numerator() as f64) * 100f64 / (*progress.denominator() as f64)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
println!(" Balance: {}", format_zec(balance.total()));
|
println!(" Balance: {}", printer.format(balance.total()));
|
||||||
println!(
|
println!(
|
||||||
" Sapling Spendable: {}",
|
" Sapling Spendable: {}",
|
||||||
format_zec(balance.sapling_balance().spendable_value())
|
printer.format(balance.sapling_balance().spendable_value()),
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
" Orchard Spendable: {}",
|
" Orchard Spendable: {}",
|
||||||
format_zec(balance.orchard_balance().spendable_value())
|
printer.format(balance.orchard_balance().spendable_value()),
|
||||||
);
|
);
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
println!(" Unshielded: {}", format_zec(balance.unshielded()));
|
println!(
|
||||||
|
" Unshielded: {}",
|
||||||
|
printer.format(balance.unshielded()),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
println!("Insufficient information to build a wallet summary.");
|
println!("Insufficient information to build a wallet summary.");
|
||||||
}
|
}
|
||||||
|
@ -63,3 +81,45 @@ impl Command {
|
||||||
Ok(())
|
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 KEYS_FILE: &str = "keys.toml";
|
||||||
const BLOCKS_FOLDER: &str = "blocks";
|
const BLOCKS_FOLDER: &str = "blocks";
|
||||||
const DATA_DB: &str = "data.sqlite";
|
const DATA_DB: &str = "data.sqlite";
|
||||||
|
const TOR_DIR: &str = "tor";
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub(crate) enum Network {
|
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))
|
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>) {
|
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 (fsblockdb_root, db_data) = get_db_paths(wallet_dir);
|
||||||
let blocks_meta = fsblockdb_root.join("blockmeta.sqlite");
|
let blocks_meta = fsblockdb_root.join("blockmeta.sqlite");
|
||||||
|
|
|
@ -129,7 +129,7 @@ fn main() -> Result<(), anyhow::Error> {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
Some(Command::Enhance(command)) => command.run(opts.wallet_dir).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::ListTx(command)) => command.run(opts.wallet_dir),
|
||||||
Some(Command::ListUnspent(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,
|
Some(Command::Propose(command)) => command.run(opts.wallet_dir).await,
|
||||||
|
|
Loading…
Reference in New Issue