Skip to content

Commit

Permalink
feat: async and periodic balances fetching
Browse files Browse the repository at this point in the history
  • Loading branch information
MishkaRogachev committed Sep 29, 2024
1 parent 817416e commit 0d49736
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 41 deletions.
5 changes: 2 additions & 3 deletions src/core/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ impl Provider {

async fn get_eth_usd_rate(&self) -> anyhow::Result<f64> {
let contrcat_address = self.chain.get_chainlink_contract_address();
let contract = Contract::from_json(self.web3.eth(), contrcat_address, CHAINLINK_ABI).unwrap();
let contract = Contract::from_json(self.web3.eth(), contrcat_address, CHAINLINK_ABI)?;

let result: PriceFeedData = contract
.query("latestRoundData", (), None, Options::default(), None)
Expand All @@ -96,8 +96,7 @@ impl Provider {
started_at,
updated_at,
answered_in_round,
})
.unwrap();
})?;

Ok(result.answer as f64 / 10f64.powi(8))
}
Expand Down
58 changes: 46 additions & 12 deletions src/service/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::{collections::HashMap, sync::Arc};
use tokio::sync::RwLock;

use crate::{core::{chain::Chain, provider::{Balance, Provider}}, persistence::db::Db};

Expand All @@ -7,14 +8,16 @@ pub struct Crypto {
db: Arc<Db>,
endpoint_url: String,
providers: HashMap<Chain, Provider>,
balances: Arc<RwLock<HashMap<web3::types::Address, Balance>>>,
}

impl Crypto {
pub fn new(db: Arc<Db>, endpoint_url: &str) -> Self {
Self {
db,
endpoint_url: endpoint_url.to_string(),
providers: HashMap::new()
providers: HashMap::new(),
balances: Arc::new(RwLock::new(HashMap::new())),
}
}

Expand All @@ -34,9 +37,11 @@ impl Crypto {
Ok(())
}

pub fn save_active_networks(&mut self, chains: Vec<Chain>) -> anyhow::Result<()> {
pub async fn save_active_networks(&mut self, chains: Vec<Chain>) -> anyhow::Result<()> {
self.set_active_networks_impl(chains.clone())?;
self.db.save_active_networks(&chains)
self.db.save_active_networks(&chains)?;
self.invalidate_balances().await;
Ok(())
}

pub fn load_active_networks(&mut self) -> anyhow::Result<()> {
Expand All @@ -52,15 +57,44 @@ impl Crypto {
self.providers.keys().any(|chain| chain.is_test_network())
}

pub async fn get_eth_balance(&self, account: web3::types::Address) -> anyhow::Result<Balance> {
let mut summary= Balance::new(0.0, 0.0, "ETH", false);
for provider in self.providers.values() {
if let Ok(balance) = provider.get_eth_balance(account).await {
summary.value += balance.value;
summary.usd_value += balance.usd_value;
summary.from_test_network = summary.from_test_network || balance.from_test_network;
}
pub async fn get_eth_balance(&self, account: web3::types::Address) -> Option<Balance> {
let balances = self.balances.read().await;
if let Some(balance) = balances.get(&account) {
return Some(balance.clone());
}
Ok(summary)
None
}

pub async fn fetch_eth_balances(&mut self, accounts: Vec<web3::types::Address>) {
let balances = self.balances.clone();
let providers = self.providers.values().cloned().collect::<Vec<_>>();

tokio::spawn(async move {
let mut summaries: HashMap<web3::types::Address, Balance> = HashMap::new();

for provider in providers {
for account in accounts.iter() {
let response = provider.get_eth_balance(*account).await;
match response {
Ok(balance) => {
let summary = summaries.entry(*account).or_insert_with(
|| Balance::new(0.0, 0.0, "ETH", false));
summary.value += balance.value;
summary.usd_value += balance.usd_value;
summary.from_test_network = summary.from_test_network || balance.from_test_network;
}
Err(_err) => {
// eprintln!("Failed to fetch balance for {}: {:?}", account, err);
}
}
}
}
balances.write().await.extend(summaries);
});
}

pub async fn invalidate_balances(&mut self) {
let mut balances = self.balances.write().await;
balances.clear();
}
}
6 changes: 3 additions & 3 deletions src/service/crypto_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ mod tests {
Db::open(db, "12345678")
}

#[test]
fn test_crypto_chains() -> anyhow::Result<()> {
#[tokio::test]
async fn test_crypto_chains() -> anyhow::Result<()> {
let db = Arc::new(create_test_db()?);
let mut crypto = Crypto::new(db, "http://localhost:8545");
assert_eq!(crypto.get_active_networks().len(), 0);

crypto.load_active_networks()?;
assert_eq!(crypto.get_active_networks().len(), 0);

crypto.save_active_networks(vec![Chain::EthereumMainnet, Chain::ArbitrumMainnet])?;
crypto.save_active_networks(vec![Chain::EthereumMainnet, Chain::ArbitrumMainnet]).await?;
assert_eq!(crypto.get_active_networks().len(), 2);

assert!(crypto.get_active_networks().contains(&Chain::EthereumMainnet));
Expand Down
3 changes: 2 additions & 1 deletion src/tui/popups/networks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ impl Popup {
let mut crypto = self.crypto.lock().await;
let mut all_networks = self.mainnet_options.get_checked_keys();
all_networks.extend(self.testnet_options.get_checked_keys());
crypto.save_active_networks(all_networks).unwrap();
crypto.save_active_networks(all_networks)
.await.expect("Failed to save active networks");
}
}

Expand Down
63 changes: 41 additions & 22 deletions src/tui/screens/porfolio_accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,43 @@ use crate::tui::{widgets::account, app::AppScreen};
const SUMMARY_HEIGHT: u16 = 2;
const SUMMARY_TEXT: &str = "Summary balance";

const UPDATE_INTERVAL: tokio::time::Duration = tokio::time::Duration::from_millis(2000);

pub struct Screen {
session: Session,
crypto: Arc<Mutex<Crypto>>,
account: account::Account,
last_update: Option<tokio::time::Instant>,

accounts: Vec<account::Account>,
}

impl Screen {
pub fn new(session: Session, crypto: Arc<Mutex<Crypto>>) -> Self {

let account = account::Account::new(session.account);
let accounts = vec![account::Account::new(session.account)];

Self {
session,
crypto,
account,
last_update: None,
accounts,
}
}

fn get_summary_balance_str(&self) -> (String, bool) {
if let Some(balance) = &self.account.balance {
let end = format!("{:.2} USD", balance.usd_value);
if balance.from_test_network {
(format!("{} (testnet)", end), true)
} else {
(end, false)
let mut usd_summary = None;
let mut from_test_network = false;
for account in &self.accounts {
if let Some(balance) = &account.balance {
usd_summary = Some(usd_summary.unwrap_or(0.0) + balance.usd_value);
if balance.from_test_network {
from_test_network = true;
}
}
} else {
("Loading...".to_string(), false)
}
match usd_summary {
Some(usd_summary) => (
format!("{} {:.2} USD", if from_test_network {"(Testnet) "} else { "" }, usd_summary),
from_test_network
),
None => (String::from("Loading.."), false),
}
}
}
Expand All @@ -52,12 +61,16 @@ impl AppScreen for Screen {
}

async fn update(&mut self) {
let crypto = self.crypto.lock().await;
if self.account.balance.is_none() {
let balance = crypto.get_eth_balance(self.session.account)
.await
.expect("Failed to get balance");
self.account.balance = Some(balance);
let mut crypto = self.crypto.lock().await;

for account in &mut self.accounts {
account.balance = crypto.get_eth_balance(account.address).await;
}

if self.last_update.is_none() || self.last_update.unwrap().elapsed() > UPDATE_INTERVAL {
let accounts = self.accounts.iter().map(|account| account.address).collect();
crypto.fetch_eth_balances(accounts).await;
self.last_update = Some(tokio::time::Instant::now());
}
}

Expand Down Expand Up @@ -91,7 +104,13 @@ impl AppScreen for Screen {
frame.render_widget(summary_label, summary[0]);
frame.render_widget(balances_label, summary[1]);

// TODO: Several accounts
self.account.render(frame, content_layout[1]);
let accounts_layout = Layout::default()
.direction(Direction::Vertical)
.constraints(self.accounts.iter().map(|_| Constraint::Length(3)).collect::<Vec<_>>().as_slice())
.split(content_layout[1]);
for (account, account_layout) in self.accounts.iter_mut().zip(accounts_layout.iter()) {
account.render(frame, *account_layout);
}
}
}

3 changes: 3 additions & 0 deletions src/tui/widgets/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ impl CheckBox {
}
}

#[allow(dead_code)]
pub fn disabled(mut self) -> Self {
self.disabled = true;
self
Expand All @@ -51,6 +52,7 @@ impl CheckBox {
self
}

#[allow(dead_code)]
pub fn primary(mut self) -> Self {
self.color = Color::White;
self
Expand Down Expand Up @@ -136,6 +138,7 @@ where T: Eq + std::hash::Hash + Clone {
Self { options }
}

#[allow(dead_code)]
pub fn option(&mut self, key: T) -> Option<&mut CheckBox> {
self.options.get_mut(&key)
}
Expand Down

0 comments on commit 0d49736

Please sign in to comment.