use crate::pools::{self, Ledger};
use std::sync::{Arc, Mutex};
const NETWORK_STATS: &str = "https://ergo.herominers.com/api/stats";
const NANO: f64 = 1e9;
pub const BLOCK_REWARD_ERG: f64 = 3.0;
pub const BLOCK_TIME_S: f64 = 120.0;
#[derive(Clone, Default)]
pub struct PoolState {
pub inner: Arc<Mutex<PoolInfo>>,
}
pub struct PoolInfo {
pub querying: bool,
pub ok: bool,
pub balance_erg: f64, pub pending_erg: f64, pub paid_erg: f64, pub hashrate_24h_mhs: f64, pub threshold_erg: f64, pub difficulty: f64, pub price_usd: f64, pub error: Option<String>,
}
impl Default for PoolInfo {
fn default() -> Self {
PoolInfo {
querying: false,
ok: false,
balance_erg: 0.0,
pending_erg: 0.0,
paid_erg: 0.0,
hashrate_24h_mhs: 0.0,
threshold_erg: 0.5,
difficulty: 0.0,
price_usd: 0.0,
error: None,
}
}
}
impl PoolState {
pub fn fetch(&self, address: String, idx: usize) {
{
let mut p = self.inner.lock().unwrap();
p.querying = true;
p.error = None;
}
let inner = self.inner.clone();
std::thread::spawn(move || {
let fresh = snapshot(&address, idx);
let mut p = inner.lock().unwrap();
*p = fresh;
});
}
}
pub fn snapshot(address: &str, idx: usize) -> PoolInfo {
let pool = pools::get(idx);
let mut p = PoolInfo { threshold_erg: pool.payout_erg, ..Default::default() };
match match pool.ledger {
Ledger::Herominers => herominers(address),
Ledger::TwoMiners => two_miners(address),
Ledger::K1Pool => k1pool(address),
Ledger::None => Err("this pool has no in-app ledger".into()),
} {
Ok(l) => {
p.ok = true;
p.balance_erg = l.balance;
p.pending_erg = l.pending;
p.paid_erg = l.paid;
p.hashrate_24h_mhs = l.hashrate_mhs;
if l.threshold > 0.0 {
p.threshold_erg = l.threshold;
}
}
Err(e) => p.error = Some(e),
}
if let Ok((difficulty, price)) = network() {
p.difficulty = difficulty;
p.price_usd = price;
}
p
}
struct LedgerRead {
balance: f64,
pending: f64,
paid: f64,
hashrate_mhs: f64,
threshold: f64,
}
fn num(v: Option<&serde_json::Value>) -> f64 {
v.and_then(|v| v.as_f64().or_else(|| v.as_str().and_then(|s| s.parse().ok())))
.unwrap_or(0.0)
}
fn herominers(address: &str) -> Result<LedgerRead, String> {
let url = format!(
"https://ergo.herominers.com/api/stats_address?address={}&longpoll=false",
address.trim()
);
let json = get_json(&url)?;
let stats = json.get("stats").cloned().unwrap_or_default();
let pending = json
.get("unconfirmed")
.and_then(|v| v.as_array())
.map(|bs| bs.iter().map(|b| num(b.get("reward"))).sum::<f64>())
.unwrap_or(0.0)
/ NANO;
Ok(LedgerRead {
balance: num(stats.get("balance")) / NANO,
pending,
paid: num(stats.get("paid")) / NANO,
hashrate_mhs: num(stats.get("hashrate_24h")) / 1e6,
threshold: 0.0, })
}
fn two_miners(address: &str) -> Result<LedgerRead, String> {
let url = format!("https://erg.2miners.com/api/accounts/{}", address.trim());
let json = get_json(&url)?;
let stats = json.get("stats").cloned().unwrap_or_default();
Ok(LedgerRead {
balance: num(stats.get("balance")) / NANO,
pending: num(stats.get("immature")) / NANO,
paid: 0.0, hashrate_mhs: num(json.get("hashrate")) / 1e6,
threshold: num(json.get("config").and_then(|c| c.get("minPayout"))) / NANO,
})
}
fn k1pool(address: &str) -> Result<LedgerRead, String> {
let url = format!("https://k1pool.com/api/miner/erg/{}", address.trim());
let json = get_json(&url)?;
let m = json.get("miner").cloned().unwrap_or_default();
Ok(LedgerRead {
balance: num(m.get("pendingBalance")),
pending: num(m.get("immatureBalance")),
paid: num(m.get("paidBalance")),
hashrate_mhs: num(m.get("dayHashrate")) / 1e6,
threshold: num(m.get("payoutThreshold")),
})
}
fn network() -> Result<(f64, f64), String> {
let json = get_json(NETWORK_STATS)?;
let difficulty = num(json.get("network").and_then(|n| n.get("difficulty")));
let price = num(
json.get("pool")
.and_then(|p| p.get("price"))
.and_then(|p| p.get("usd")),
);
Ok((difficulty, price))
}
fn get_json(url: &str) -> Result<serde_json::Value, String> {
ureq::get(url)
.timeout(std::time::Duration::from_secs(15))
.call()
.map_err(|e| format!("pool: {e}"))?
.into_json()
.map_err(|e| format!("pool decode: {e}"))
}