Introduced coingecko api switch, preparations for testnet explorer

This commit is contained in:
aglkm
2024-05-19 20:04:13 +03:00
parent 33426c1a5e
commit 38ee6500eb
5 changed files with 435 additions and 43 deletions

View File

@@ -16,6 +16,20 @@ api_secret_path = "~/.grin/main/.api_secret"
# Foreign API secret path.
foreign_api_secret_path = "~/.grin/main/.foreign_api_secret"
# Path to Grin directory
# Path to Grin directory.
grin_dir = "~/.grin"
# CoinGecko API on/off switch.
coingecko_api = "on"
# Testnet config
# ip = "127.0.0.1"
# port = "13413"
# proto = "http"
# user = "grin"
# api_secret_path = "~/.grin/test/.api_secret"
# foreign_api_secret_path = "~/.grin/test/.foreign_api_secret"
# grin_dir = "~/.grin"
# coingecko_api = "off"

View File

@@ -35,6 +35,8 @@ pub struct Dashboard {
// mempool
pub txns: String,
pub stem: String,
// coingecko api
pub cg_api: String,
}
impl Dashboard {
@@ -64,6 +66,7 @@ impl Dashboard {
breakeven_cost: String::new(),
txns: String::new(),
stem: String::new(),
cg_api: String::new(),
}
}
}
@@ -141,6 +144,7 @@ pub struct ExplorerConfig {
pub grin_dir: String,
pub api_secret: String,
pub foreign_api_secret: String,
pub coingecko_api: String,
}
impl ExplorerConfig {
@@ -155,6 +159,7 @@ impl ExplorerConfig {
grin_dir: String::new(),
api_secret: String::new(),
foreign_api_secret: String::new(),
coingecko_api: String::new(),
}
}
}

View File

@@ -25,9 +25,10 @@ fn index(dashboard: &State<Arc<Mutex<Dashboard>>>) -> Template {
let data = dashboard.lock().unwrap();
Template::render("index", context! {
route: "index",
node_ver: &data.node_ver,
route: "index",
node_ver: &data.node_ver,
proto_ver: &data.proto_ver,
cg_api: &data.cg_api,
})
}
@@ -211,7 +212,13 @@ fn soft_supply(dashboard: &State<Arc<Mutex<Dashboard>>>) -> String {
let data = dashboard.lock().unwrap();
if data.supply.is_empty() == false {
return format!("{} % ({}M/3150M)", data.soft_supply, &data.supply[..3]);
// 9 digits plus 2 commas, e.g. 168,038,400
if data.supply.len() == 11 {
return format!("{} % ({}M/3150M)", data.soft_supply, &data.supply[..3]);
// 10 digits plus 2 commas
} else if data.supply.len() == 12 {
return format!("{} % ({}M/3150M)", data.soft_supply, &data.supply[..4]);
}
}
"3150M".to_string()
@@ -491,7 +498,7 @@ fn block_weight(count: usize, blocks: &State<Arc<Mutex<Vec<Block>>>>) -> String
fn block_list_index(dashboard: &State<Arc<Mutex<Dashboard>>>) -> String {
let data = dashboard.lock().unwrap();
if data.height.is_empty() == false {
if data.height.is_empty() == false && data.height.parse::<u64>().unwrap() > 10 {
return format!("<a class='text-decoration-none' href='/block_list/{}'>
<div class='col-sm'><h2><i class='bi bi-arrow-right-square'></i></h2></div>
</a>", data.height.parse::<u64>().unwrap() - 10);

View File

@@ -36,6 +36,7 @@ lazy_static! {
"api_secret_path" => cfg.api_secret_path = value,
"foreign_api_secret_path" => cfg.foreign_api_secret_path = value,
"grin_dir" => cfg.grin_dir = value,
"coingecko_api" => cfg.coingecko_api = value,
_ => println!("{} Unknown config setting '{}'.", "[ ERROR ]".red(), name),
}
}
@@ -140,15 +141,21 @@ pub async fn get_connected_peers(dashboard: Arc<Mutex<Dashboard>>) -> Result<(),
// Collecting: supply, inflation, price_usd, price_btc, volume_usd, volume_btc, cap_usd, cap_btc.
pub async fn get_market(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), Error> {
let client = reqwest::Client::new();
let result = client.get("https://api.coingecko.com/api/v3/simple/price?ids=grin&vs_currencies=usd%2Cbtc&include_24hr_vol=true")
.send()
.await?;
let client;
let result;
let mut val = Value::Null;
if CONFIG.coingecko_api == "on" {
client = reqwest::Client::new();
result = client.get("https://api.coingecko.com/api/v3/simple/price?ids=grin&vs_currencies=usd%2Cbtc&include_24hr_vol=true").send().await?;
val = serde_json::from_str(&result.text().await.unwrap()).unwrap();
}
let val: Value = serde_json::from_str(&result.text().await.unwrap()).unwrap();
let mut data = dashboard.lock().unwrap();
// Save the setting into Dashboard structure for later use
data.cg_api = CONFIG.coingecko_api.clone();
if data.height.is_empty() == false {
// Calculating coin supply
// Adding +1 as block index starts with 0
@@ -157,28 +164,32 @@ pub async fn get_market(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), Error> {
// 31536000 seconds in a year
let inflation = (31536000.0 / (supply as f64)) * 100.0;
data.inflation = format!("{:.2}", inflation);
data.supply = supply.to_formatted_string(&Locale::en);
data.inflation = format!("{:.2}", inflation);
data.supply = supply.to_formatted_string(&Locale::en);
// https://john-tromp.medium.com/a-case-for-using-soft-total-supply-1169a188d153
data.soft_supply = format!("{:.2}",
supply.to_string().parse::<f64>().unwrap() / 3150000000.0 * 100.0);
supply.to_string().parse::<f64>().unwrap() / 3150000000.0 * 100.0);
// Check if CoingGecko API returned error
if let Some(status) = val.get("status") {
println!("{} {}.", "[ WARNING ]".yellow(),
status["error_message"].as_str().unwrap().to_string());
} else {
data.price_usd = format!("{:.3}", val["grin"]["usd"].to_string().parse::<f64>().unwrap());
data.price_btc = format!("{:.8}", val["grin"]["btc"].to_string().parse::<f64>().unwrap());
data.volume_usd = (val["grin"]["usd_24h_vol"].to_string().parse::<f64>().unwrap() as u64)
.to_formatted_string(&Locale::en);
data.volume_btc = format!("{:.2}", val["grin"]["btc_24h_vol"].to_string().parse::<f64>()
.unwrap());
data.cap_usd = (((supply as f64) * data.price_usd.parse::<f64>().unwrap()) as u64)
.to_formatted_string(&Locale::en);
data.cap_btc = (((supply as f64) * data.price_btc.parse::<f64>().unwrap()) as u64)
.to_formatted_string(&Locale::en);
if CONFIG.coingecko_api == "on" && val != Value::Null {
// Check if CoingGecko API returned error
if let Some(status) = val.get("status") {
println!("{} {}.", "[ WARNING ]".yellow(),
status["error_message"].as_str().unwrap().to_string());
} else {
data.price_usd = format!("{:.3}", val["grin"]["usd"].to_string().parse::<f64>()
.unwrap());
data.price_btc = format!("{:.8}", val["grin"]["btc"].to_string().parse::<f64>()
.unwrap());
data.volume_usd = (val["grin"]["usd_24h_vol"].to_string().parse::<f64>().unwrap() as u64)
.to_formatted_string(&Locale::en);
data.volume_btc = format!("{:.2}", val["grin"]["btc_24h_vol"].to_string().parse::<f64>()
.unwrap());
data.cap_usd = (((supply as f64) * data.price_usd.parse::<f64>().unwrap()) as u64)
.to_formatted_string(&Locale::en);
data.cap_btc = (((supply as f64) * data.price_btc.parse::<f64>().unwrap()) as u64)
.to_formatted_string(&Locale::en);
}
}
}
@@ -189,8 +200,13 @@ pub async fn get_market(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), Error> {
// Collecting: disk_usage.
pub fn get_disk_usage(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), Error> {
let mut data = dashboard.lock().unwrap();
let chain_data;
let chain_data = format!("{}/main/chain_data", CONFIG.grin_dir);
if CONFIG.coingecko_api == "on" {
chain_data = format!("{}/main/chain_data", CONFIG.grin_dir);
} else {
chain_data = format!("{}/test/chain_data", CONFIG.grin_dir);
}
data.disk_usage = format!("{:.2}", (get_size(chain_data).unwrap() as f64)
/ 1000.0 / 1000.0 / 1000.0);
@@ -204,7 +220,7 @@ pub async fn get_mining_stats(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), Er
let difficulty_window = 1440;
let height = get_current_height(dashboard.clone());
if height.is_empty() == false {
if height.is_empty() == false && height.parse::<u64>().unwrap() > 1440 {
let params1 = &format!("[{}, null, null]", height)[..];
let params2 = &format!("[{}, null, null]", height.parse::<u64>().unwrap()
- difficulty_window)[..];
@@ -227,17 +243,19 @@ pub async fn get_mining_stats(dashboard: Arc<Mutex<Dashboard>>) -> Result<(), Er
data.hashrate = format!("{:.2}", hashrate / 1000.0);
data.difficulty = net_diff.to_string();
// Calculating G1-mini production per hour
let coins_per_hour = 1.2 / hashrate * 60.0 * 60.0;
if CONFIG.coingecko_api == "on" {
// Calculating G1-mini production per hour
let coins_per_hour = 1.2 / hashrate * 60.0 * 60.0;
// Calculating production cost of 1 grin
// Assuming $0.07 per kW/h
data.production_cost = format!("{:.3}", 120.0 / 1000.0 * 0.07 * (1.0 / coins_per_hour));
// Calculating production cost of 1 grin
// Assuming $0.07 per kW/h
data.production_cost = format!("{:.3}", 120.0 / 1000.0 * 0.07 * (1.0 / coins_per_hour));
data.reward_ratio = format!("{:.2}", data.price_usd.parse::<f64>().unwrap()
/ data.production_cost.parse::<f64>().unwrap());
data.breakeven_cost = format!("{:.2}", data.price_usd.parse::<f64>().unwrap()
/ (120.0 / 1000.0 * (1.0 / coins_per_hour)));
data.reward_ratio = format!("{:.2}", data.price_usd.parse::<f64>().unwrap()
/ data.production_cost.parse::<f64>().unwrap());
data.breakeven_cost = format!("{:.2}", data.price_usd.parse::<f64>().unwrap()
/ (120.0 / 1000.0 * (1.0 / coins_per_hour)));
}
}
}
@@ -421,7 +439,7 @@ pub async fn get_txn_stats(dashboard: Arc<Mutex<Dashboard>>,
let mut blocks = Vec::<Block>::new();
let height = get_current_height(dashboard.clone());
if height.is_empty() == false {
if height.is_empty() == false && height.parse::<u64>().unwrap() > 1440 {
// get_blocks grin rpc has limit of maximum of 1000 blocks request
// https://github.com/mimblewimble/grin/blob/master/api/src/handlers/blocks_api.rs#L27
// So, collecting kernels 2 times by 720 blocks to get a day of blocks
@@ -484,7 +502,7 @@ pub async fn get_recent_blocks(dashboard: Arc<Mutex<Dashboard>>,
let mut i = 0;
let height_str = get_current_height(dashboard.clone());
if height_str.is_empty() == false {
if height_str.is_empty() == false && height_str.parse::<u64>().unwrap() > 0 {
let height = height_str.parse::<u64>().unwrap();
let mut blocks_vec = Vec::<Block>::new();

View File

@@ -4,6 +4,11 @@
<code>
{# We have different UI to display if CoinGecko API is disabled by user #}
{% if cg_api == "on" %}
{# CoinGecko API is enabled #}
<div class="d-none d-md-block"> <!-- Show on >= md screens -->
<div class="card-group mb-2">
<div class="card me-2">
@@ -456,6 +461,349 @@
</div>
{% else %}
{# CoinGecko API is disabled #}
<div class="d-none d-md-block"> <!-- Show on >= md screens -->
<div class="card-group mb-2">
<div class="card me-2">
<div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-bank"></i> MARKET</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Yearly Inflation Rate&nbsp;</div><div class="value-text text-end" hx-get="/rpc/inflation/rate" hx-trigger="load, every 10s"> %</div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Coin Supply&nbsp;</div><div class="value-text text-end" hx-get="/rpc/market/supply" hx-trigger="load, every 10s"></div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Soft Total Supply
<!-- Button trigger soft supply explanation modal -->
<button class="btn-sm shadow-none" data-bs-toggle="modal" data-bs-target="#soft_sup">
<i class="bi bi-question-circle"></i>
</button>
</div><div class="value-text text-end" hx-get="/rpc/market/soft_supply" hx-trigger="load, every 10s"></div>
</div>
</div>
</div>
<div class="card">
<div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-hammer"></i> MINING</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Hashrate&nbsp;</div><div class="value-text text-end" hx-get="/rpc/network/hashrate" hx-trigger="load, every 10s"> KG/s</div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Difficulty&nbsp;</div><div class="value-text text-end" hx-get="/rpc/network/difficulty" hx-trigger="load, every 10s"></div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Block Reward&nbsp;</div><div class="value-text text-end">ツ 60</div>
</div>
</div>
</div>
</div>
<div class="card-group mb-2">
<div class="card me-2">
<div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-grid"></i> BLOCKCHAIN</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Size&nbsp;</div><div class="value-text text-end" hx-get="/rpc/disk/usage" hx-trigger="load, every 10s"></div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Block Height&nbsp;</div><div class="value-text text-end" hx-get="/rpc/block/latest" hx-trigger="load, every 10s"></div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Time Since Last Block&nbsp;</div><div class="value-text text-end" hx-get="/rpc/block/time_since_last" hx-trigger="load, every 10s"></div>
</div>
</div>
</div>
<div class="card">
<div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-speedometer2"></i> TRANSACTIONS & FEES</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">1H Period&nbsp;</div><div class="value-text text-end" hx-get="/rpc/txns/count_1h" hx-trigger="load, every 10s"></div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">24H Period&nbsp;</div><div class="value-text text-end" hx-get="/rpc/txns/count_24h" hx-trigger="load, every 10s"></div>
</div>
</div>
</div>
</div>
<div class="card-group mb-4">
<div class="card me-2">
<div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-receipt"></i> MEMPOOL</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Transactions&nbsp;</div><div class="value-text text-end" hx-get="/rpc/mempool/txns" hx-trigger="load, every 10s"></div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Stem&nbsp;</div><div class="value-text text-end" hx-get="/rpc/mempool/stem" hx-trigger="load, every 10s"></div>
</div>
</div>
</div>
<div class="card me-2">
<div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-diagram-3"></i> CONNECTIONS</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Inbound&nbsp;</div><div class="value-text text-end" hx-get="/rpc/peers/inbound" hx-trigger="load, every 10s"></div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Outbound&nbsp;</div><div class="value-text text-end" hx-get="/rpc/peers/outbound" hx-trigger="load, every 10s"></div>
</div>
</div>
</div>
<div class="card">
<div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-pc-display-horizontal"></i> NODE</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Version&nbsp;</div><div class="value-text text-end">{{ node_ver }}</div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text me-1">Protocol&nbsp;</div><div class="value-text text-end">{{ proto_ver }}</div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text me-1">Sync Status&nbsp;</div><div class="value-text text-end" hx-get="/rpc/sync/status" hx-trigger="load, every 10s"></div>
</div>
</div>
</div>
</div>
</div>
<div class="d-md-none"> <!-- Show on < md screens-->
<div class="card mb-3">
<div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-bank"></i> MARKET</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Yearly Inflation Rate&nbsp;</div><div class="value-text text-end" hx-get="/rpc/inflation/rate" hx-trigger="load, every 10s"> %</div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Coin Supply&nbsp;</div><div class="value-text text-end" hx-get="/rpc/market/supply" hx-trigger="load, every 10s"></div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Soft Total Supply
<!-- Button trigger soft supply explanation modal -->
<button type="button" class="btn-sm" data-bs-toggle="modal" data-bs-target="#soft_sup">
<i class="bi bi-question-circle"></i>
</button>
</div><div class="value-text text-end" hx-get="/rpc/market/soft_supply" hx-trigger="load, every 10s"></div>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-body" align="left">
<div class="d-flex justify-content-between">
<div class="darkorange-text"><i class="bi bi-grid"></i> BLOCKCHAIN</div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Size&nbsp;</div><div class="value-text text-end" hx-get="/rpc/disk/usage" hx-trigger="load, every 10s"></div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Block Height&nbsp;</div><div class="value-text text-end" hx-get="/rpc/block/latest" hx-trigger="load, every 10s"></div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Time Since Last Block&nbsp;</div><div class="value-text text-end" hx-get="/rpc/block/time_since_last" hx-trigger="load, every 10s"></div>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-hammer"></i> MINING</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Hashrate&nbsp;</div><div class="value-text text-end" hx-get="/rpc/network/hashrate" hx-trigger="load, every 10s"> KG/s</div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Difficulty&nbsp;</div><div class="value-text text-end" hx-get="/rpc/network/difficulty" hx-trigger="load, every 10s"></div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Block Reward&nbsp;</div><div class="value-text text-end">ツ 60</div>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-speedometer2"></i> TRANSACTIONS & FEES</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">1H Period&nbsp;</div><div class="value-text text-end" hx-get="/rpc/txns/count_1h" hx-trigger="load, every 10s"></div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">24H Period&nbsp;</div><div class="value-text text-end" hx-get="/rpc/txns/count_24h" hx-trigger="load, every 10s"></div>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-receipt"></i> MEMPOOL</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Transactions&nbsp;</div><div class="value-text text-end" hx-get="/rpc/mempool/txns" hx-trigger="load, every 10s"></div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Stem&nbsp;</div><div class="value-text text-end" hx-get="/rpc/mempool/stem" hx-trigger="load, every 10s"></div>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-diagram-3"></i> CONNECTIONS</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Inbound&nbsp;</div><div class="value-text text-end" hx-get="/rpc/peers/inbound" hx-trigger="load, every 10s"></div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Outbound&nbsp;</div><div class="value-text text-end" hx-get="/rpc/peers/outbound" hx-trigger="load, every 10s"></div>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-body" align="left">
<div class="darkorange-text"><i class="bi bi-pc-display-horizontal"></i> NODE</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text">Version&nbsp;</div><div class="value-text text-end">{{ node_ver }}</div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text me-1">Protocol&nbsp;</div><div class="value-text text-end">{{ proto_ver }}</div>
</div>
<br>
<div class="d-flex justify-content-between">
<div class="value-text me-1">Sync Status&nbsp;</div><div class="value-text text-end" hx-get="/rpc/sync/status" hx-trigger="load, every 10s"></div>
</div>
</div>
</div>
</div>
<!-- Modals. Explanations of several dashboard stats. -->
<div class="card border-0">
<div class="modal fade" id="soft_sup" tabindex="-1" aria-labelledby="soft_sup_label" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="soft_sup_label">Soft Total Supply</h1>
<div data-bs-theme="light">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body">
Percentage of issued coins from the soft total supply (3150M) when inflation will reach <1%.
<br>
<br>
<a class="text-decoration-none" href="https://john-tromp.medium.com/a-case-for-using-soft-total-supply-1169a188d153">https://john-tromp.medium.com/a-case-for-using-soft-total-supply-1169a188d153</a>
</div>
</div>
</div>
</div>
<div class="modal fade" id="mining_cost" tabindex="-1" aria-labelledby="mining_cost_label" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="mining_cost_label">Estimated Mining Cost</h1>
<div data-bs-theme="light">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body">
Mining cost to produce 1 grin coin.<br>
Assuming that:<br>
Miner is G1-mini ASIC.<br>
Electricity cost is $0.07 per kW/h.<br>
<br>
<a class="text-decoration-none" href="https://ipollo.com/products/ipollo-g1-mini">https://ipollo.com/products/ipollo-g1-mini</a>
</div>
</div>
</div>
</div>
<div class="modal fade" id="ratio" tabindex="-1" aria-labelledby="ratio_label" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="ratio_label">Reward/Cost Ratio</h1>
<div data-bs-theme="light">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body">
Shows the result of the following formula:<br>
Price of 1 Grin (USD) / Mining Cost of 1 Grin (USD).<br>
<br>
<i class="bi bi-hand-thumbs-down"></i> - <= 1<br>
<i class="bi bi-hand-thumbs-up"></i> - from 1 to 2<br>
<i class="bi bi-emoji-sunglasses"></i> - from 2 to 3<br>
<i class='bi bi-rocket-takeoff'></i> - >= 3
</div>
</div>
</div>
</div>
<div class="modal fade" id="breakeven" tabindex="-1" aria-labelledby="breakeven_label" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="breakeven_label">Breakeven Electricity Cost</h1>
<div data-bs-theme="light">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body">
Electricity threshold cost below which mining is profitable.<br>
Assuming G1-mini ASIC as a miner device.
</div>
</div>
</div>
</div>
</div>
{% endif %}
</code>
{% endblock content%}