commit 03d13e99bff807a4706a341bf45c164f8613cbfb
parent b9a7350b3d86a13808087afc1408fbbb93a3b99f
Author: Joris Hartog <jorishartog@hotmail.com>
Date: Mon, 3 Aug 2026 11:59:34 +0200
Add burn claim metrics screen
Diffstat:
3 files changed, 267 insertions(+), 17 deletions(-)
diff --git a/src/adapters/chain_store.rs b/src/adapters/chain_store.rs
@@ -30,10 +30,13 @@ CREATE TABLE IF NOT EXISTS block_metrics (
transaction_count INTEGER NOT NULL,
transfer_count INTEGER NOT NULL,
burn_count INTEGER NOT NULL,
+ burn_claim_count INTEGER NOT NULL DEFAULT 0,
mine_count INTEGER NOT NULL,
burned_amount INTEGER NOT NULL,
total_burned_amount INTEGER NOT NULL,
fees_amount INTEGER NOT NULL,
+ burn_claim_fee_share_count INTEGER NOT NULL DEFAULT 0,
+ burn_claim_fee_share_amount INTEGER NOT NULL DEFAULT 0,
reward_amount INTEGER NOT NULL,
vdf_rounds INTEGER NOT NULL,
finalizer_rank INTEGER NOT NULL
@@ -52,10 +55,13 @@ pub struct BlockMetricRow {
pub transaction_count: u64,
pub transfer_count: u64,
pub burn_count: u64,
+ pub burn_claim_count: u64,
pub mine_count: u64,
pub burned_amount: Amount,
pub total_burned_amount: Amount,
pub fees_amount: Amount,
+ pub burn_claim_fee_share_count: u64,
+ pub burn_claim_fee_share_amount: Amount,
pub reward_amount: Amount,
pub vdf_rounds: u64,
pub finalizer_rank: u32,
@@ -83,6 +89,7 @@ impl SqliteChainStore {
connection
.execute_batch(SCHEMA)
.context("failed to initialize chain database schema")
+ .and_then(|_| ensure_block_metric_columns(connection))
})?;
Ok(store)
}
@@ -185,8 +192,9 @@ ON CONFLICT(id) DO UPDATE SET
r#"
SELECT height, block_hash, timestamp_ms, block_time_ms, mine_difficulty_bits,
circulating_supply, transaction_count, transfer_count, burn_count,
- mine_count, burned_amount, total_burned_amount, fees_amount, reward_amount,
- vdf_rounds, finalizer_rank
+ burn_claim_count, mine_count, burned_amount, total_burned_amount, fees_amount,
+ burn_claim_fee_share_count, burn_claim_fee_share_amount, reward_amount, vdf_rounds,
+ finalizer_rank
FROM block_metrics
ORDER BY height ASC
"#,
@@ -204,13 +212,16 @@ ORDER BY height ASC
transaction_count: row.get(6)?,
transfer_count: row.get(7)?,
burn_count: row.get(8)?,
- mine_count: row.get(9)?,
- burned_amount: row.get(10)?,
- total_burned_amount: row.get(11)?,
- fees_amount: row.get(12)?,
- reward_amount: row.get(13)?,
- vdf_rounds: row.get(14)?,
- finalizer_rank: row.get(15)?,
+ burn_claim_count: row.get(9)?,
+ mine_count: row.get(10)?,
+ burned_amount: row.get(11)?,
+ total_burned_amount: row.get(12)?,
+ fees_amount: row.get(13)?,
+ burn_claim_fee_share_count: row.get(14)?,
+ burn_claim_fee_share_amount: row.get(15)?,
+ reward_amount: row.get(16)?,
+ vdf_rounds: row.get(17)?,
+ finalizer_rank: row.get(18)?,
})
})
.context("failed to load block metrics")?;
@@ -248,6 +259,32 @@ PRAGMA synchronous = NORMAL;
}
}
+fn ensure_block_metric_columns(connection: &Connection) -> Result<()> {
+ let mut statement = connection
+ .prepare("PRAGMA table_info(block_metrics)")
+ .context("failed to inspect block metrics schema")?;
+ let columns = statement
+ .query_map([], |row| row.get::<_, String>(1))
+ .context("failed to read block metrics schema")?
+ .collect::<std::result::Result<std::collections::BTreeSet<_>, _>>()
+ .context("failed to collect block metrics columns")?;
+ for (name, definition) in [
+ ("burn_claim_count", "INTEGER NOT NULL DEFAULT 0"),
+ ("burn_claim_fee_share_count", "INTEGER NOT NULL DEFAULT 0"),
+ ("burn_claim_fee_share_amount", "INTEGER NOT NULL DEFAULT 0"),
+ ] {
+ if !columns.contains(name) {
+ connection
+ .execute(
+ &format!("ALTER TABLE block_metrics ADD COLUMN {name} {definition}"),
+ [],
+ )
+ .with_context(|| format!("failed to add block metrics column {name}"))?;
+ }
+ }
+ Ok(())
+}
+
fn replace_metrics(
transaction: &rusqlite::Transaction<'_>,
metrics: &[BlockMetricRow],
@@ -259,10 +296,10 @@ fn replace_metrics(
r#"
INSERT INTO block_metrics (
height, block_hash, timestamp_ms, block_time_ms, mine_difficulty_bits,
- circulating_supply, transaction_count, transfer_count, burn_count, mine_count,
- burned_amount, total_burned_amount, fees_amount, reward_amount, vdf_rounds,
- finalizer_rank
-) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16)
+ circulating_supply, transaction_count, transfer_count, burn_count, burn_claim_count,
+ mine_count, burned_amount, total_burned_amount, fees_amount, burn_claim_fee_share_count,
+ burn_claim_fee_share_amount, reward_amount, vdf_rounds, finalizer_rank
+) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19)
"#,
params![
metric.height,
@@ -274,10 +311,13 @@ INSERT INTO block_metrics (
metric.transaction_count,
metric.transfer_count,
metric.burn_count,
+ metric.burn_claim_count,
metric.mine_count,
metric.burned_amount,
metric.total_burned_amount,
metric.fees_amount,
+ metric.burn_claim_fee_share_count,
+ metric.burn_claim_fee_share_amount,
metric.reward_amount,
metric.vdf_rounds,
metric.finalizer_rank,
@@ -314,10 +354,18 @@ fn metrics_from_snapshot(snapshot: &ChainSnapshot) -> Result<Vec<BlockMetricRow>
for block in &snapshot.blocks {
let mut transfer_count = 0_u64;
let mut burn_count = 0_u64;
+ let mut burn_claim_count = 0_u64;
let mut mine_count = 0_u64;
let mut burned_amount = 0_u64;
let mut mine_issued_amount = 0_u64;
let mut fees_amount = 0_u64;
+ let burn_claim_fee_share_count = block.fee_shares.len() as u64;
+ let burn_claim_fee_share_amount =
+ block.fee_shares.iter().try_fold(0_u64, |total, share| {
+ total
+ .checked_add(share.amount)
+ .context("block metric burn claim fee shares overflow")
+ })?;
for transaction in &block.transactions {
fees_amount = fees_amount
@@ -338,7 +386,7 @@ fn metrics_from_snapshot(snapshot: &ChainSnapshot) -> Result<Vec<BlockMetricRow>
.and_then(|amount| amount.checked_add(transaction.fee()))
.context("block metric mine issuance overflow")?;
}
- Transaction::BurnClaim { .. } => {}
+ Transaction::BurnClaim { .. } => burn_claim_count += 1,
}
}
@@ -379,10 +427,13 @@ fn metrics_from_snapshot(snapshot: &ChainSnapshot) -> Result<Vec<BlockMetricRow>
transaction_count: block.transactions.len() as u64,
transfer_count,
burn_count,
+ burn_claim_count,
mine_count,
burned_amount,
total_burned_amount,
fees_amount,
+ burn_claim_fee_share_count,
+ burn_claim_fee_share_amount,
reward_amount: block.reward,
vdf_rounds: block.vdf_rounds,
finalizer_rank: block.finalizer_rank,
@@ -418,6 +469,7 @@ fn unix_ms() -> u64 {
mod tests {
use std::collections::BTreeMap;
+ use rusqlite::Connection;
use tempfile::tempdir;
use crate::domain::{BLOCK_REWARD, GenesisBurn, Ledger, Wallet};
@@ -483,8 +535,11 @@ mod tests {
assert_eq!(metrics.last().unwrap().height, 1);
assert_eq!(metrics.last().unwrap().burn_count, 1);
+ assert_eq!(metrics.last().unwrap().burn_claim_count, 0);
assert_eq!(metrics.last().unwrap().burned_amount, 2);
assert_eq!(metrics.last().unwrap().fees_amount, 1);
+ assert_eq!(metrics.last().unwrap().burn_claim_fee_share_count, 0);
+ assert_eq!(metrics.last().unwrap().burn_claim_fee_share_amount, 0);
assert_eq!(metrics.last().unwrap().circulating_supply, BLOCK_REWARD + 7);
store.clear_metrics().unwrap();
@@ -492,6 +547,58 @@ mod tests {
}
#[test]
+ fn sqlite_chain_store_migrates_burn_claim_metric_columns() {
+ let dir = tempdir().unwrap();
+ let path = dir.path().join("chain.sqlite3");
+ Connection::open(&path)
+ .unwrap()
+ .execute_batch(
+ r#"
+CREATE TABLE chain_snapshots (
+ id INTEGER PRIMARY KEY CHECK (id = 1),
+ height INTEGER NOT NULL,
+ tip_hash TEXT NOT NULL,
+ snapshot_json TEXT NOT NULL,
+ updated_at_ms INTEGER NOT NULL
+);
+CREATE TABLE block_metrics (
+ height INTEGER PRIMARY KEY,
+ block_hash TEXT NOT NULL,
+ timestamp_ms INTEGER NOT NULL,
+ block_time_ms INTEGER,
+ mine_difficulty_bits INTEGER NOT NULL,
+ circulating_supply INTEGER NOT NULL,
+ transaction_count INTEGER NOT NULL,
+ transfer_count INTEGER NOT NULL,
+ burn_count INTEGER NOT NULL,
+ mine_count INTEGER NOT NULL,
+ burned_amount INTEGER NOT NULL,
+ total_burned_amount INTEGER NOT NULL,
+ fees_amount INTEGER NOT NULL,
+ reward_amount INTEGER NOT NULL,
+ vdf_rounds INTEGER NOT NULL,
+ finalizer_rank INTEGER NOT NULL
+);
+INSERT INTO block_metrics (
+ height, block_hash, timestamp_ms, block_time_ms, mine_difficulty_bits,
+ circulating_supply, transaction_count, transfer_count, burn_count, mine_count,
+ burned_amount, total_burned_amount, fees_amount, reward_amount, vdf_rounds,
+ finalizer_rank
+) VALUES (1, 'hash', 1000, 500, 12, 100, 2, 0, 1, 0, 1, 1, 2, 2, 99, 0);
+"#,
+ )
+ .unwrap();
+
+ let store = SqliteChainStore::open(&path).unwrap();
+ let metrics = store.load_metrics().unwrap();
+
+ assert_eq!(metrics.len(), 1);
+ assert_eq!(metrics[0].burn_claim_count, 0);
+ assert_eq!(metrics[0].burn_claim_fee_share_count, 0);
+ assert_eq!(metrics[0].burn_claim_fee_share_amount, 0);
+ }
+
+ #[test]
fn sqlite_chain_store_roundtrips_vdf_round_metrics_above_legacy_u32_limit() {
let dir = tempdir().unwrap();
let store = SqliteChainStore::open(dir.path().join("chain.sqlite3")).unwrap();
@@ -512,10 +619,13 @@ mod tests {
transaction_count: 0,
transfer_count: 0,
burn_count: 0,
+ burn_claim_count: 0,
mine_count: 0,
burned_amount: 0,
total_burned_amount: 0,
fees_amount: 0,
+ burn_claim_fee_share_count: 0,
+ burn_claim_fee_share_amount: 0,
reward_amount: 0,
vdf_rounds,
finalizer_rank: 0,
diff --git a/src/adapters/http.rs b/src/adapters/http.rs
@@ -276,6 +276,7 @@ struct MetricsResponse {
#[serde(rename_all = "camelCase")]
struct MetricsChart {
id: &'static str,
+ section: &'static str,
title: &'static str,
unit: &'static str,
value_kind: MetricsValueKind,
@@ -1249,6 +1250,7 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
charts: vec![
metrics_chart(
"block-time",
+ "chain",
"Time per block",
"s",
MetricsValueKind::Seconds,
@@ -1261,6 +1263,7 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"difficulty",
+ "chain",
"Difficulty",
"bits",
MetricsValueKind::Number,
@@ -1269,6 +1272,7 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"supply",
+ "chain",
"IUNA in circulation",
"IUNA",
MetricsValueKind::Iuna,
@@ -1277,6 +1281,7 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"transactions",
+ "chain",
"Transactions",
"tx",
MetricsValueKind::Number,
@@ -1285,6 +1290,7 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"burn-count",
+ "chain",
"Burn transactions",
"burns",
MetricsValueKind::Number,
@@ -1293,6 +1299,7 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"burn-amount",
+ "chain",
"Burn amount",
"IUNA",
MetricsValueKind::Iuna,
@@ -1301,6 +1308,7 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"total-burn",
+ "chain",
"Total burn",
"IUNA",
MetricsValueKind::Iuna,
@@ -1309,6 +1317,7 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"fees",
+ "chain",
"Fees",
"IUNA",
MetricsValueKind::Iuna,
@@ -1317,6 +1326,7 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"mine-actions",
+ "chain",
"Mine actions",
"mine",
MetricsValueKind::Number,
@@ -1325,18 +1335,47 @@ fn metrics_response(enabled: bool, rows: Vec<BlockMetricRow>) -> MetricsResponse
),
metrics_chart(
"vdf-rounds",
+ "chain",
"VDF rounds",
"rounds",
MetricsValueKind::Number,
&rows,
|row| (row.vdf_rounds > 0).then_some(row.vdf_rounds as f64),
),
+ metrics_chart(
+ "burn-claims",
+ "burnClaims",
+ "Burn claims",
+ "claims",
+ MetricsValueKind::Number,
+ &rows,
+ |row| Some(row.burn_claim_count as f64),
+ ),
+ metrics_chart(
+ "burn-claim-payouts",
+ "burnClaims",
+ "Claim payouts",
+ "outputs",
+ MetricsValueKind::Number,
+ &rows,
+ |row| Some(row.burn_claim_fee_share_count as f64),
+ ),
+ metrics_chart(
+ "burn-claim-payout-amount",
+ "burnClaims",
+ "Claim payout amount",
+ "IUNA",
+ MetricsValueKind::Iuna,
+ &rows,
+ |row| Some(micro_iuna_as_iuna(row.burn_claim_fee_share_amount)),
+ ),
],
}
}
fn metrics_chart(
id: &'static str,
+ section: &'static str,
title: &'static str,
unit: &'static str,
value_kind: MetricsValueKind,
@@ -1345,6 +1384,7 @@ fn metrics_chart(
) -> MetricsChart {
MetricsChart {
id,
+ section,
title,
unit,
value_kind,
@@ -2656,6 +2696,8 @@ const INDEX_HTML: &str = r#"<!doctype html>
.metrics-head h2 { margin: 0; }
.metrics-range { flex: 0 0 auto; }
.metrics-range button { padding: 5px 9px; font-size: 12px; white-space: nowrap; }
+ .metrics-section-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding-top: 4px; border-top: 1px solid #2a3035; }
+ .metrics-section-head h3 { margin: 0; color: #e8edf0; font-size: 15px; font-weight: 850; }
.metrics-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
.metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 430px), 1fr)); gap: 12px; }
.metric-chart-card { display: grid; gap: 10px; min-width: 0; border: 1px solid #2a3035; border-radius: 8px; padding: 12px; background: #181b1f; }
@@ -2889,7 +2931,7 @@ const INDEX_HTML: &str = r#"<!doctype html>
.block-card { flex-basis: 108px; }
}
</style>
- <script defer src="/assets/iuna-ui.js?v=76"></script>
+ <script defer src="/assets/iuna-ui.js?v=77"></script>
<script defer src="/assets/alpine.min.js"></script>
</head>
<body x-data="iunaApp()" x-init="init()" @keydown.window.escape="closeModals()" x-cloak>
@@ -3383,7 +3425,53 @@ const INDEX_HTML: &str = r#"<!doctype html>
</div>
<div class="metrics-empty" x-show="metricsCharts().length === 0">No metrics collected yet</div>
<div class="metrics-grid">
- <template x-for="chart in metricsCharts()" :key="chart.id">
+ <template x-for="chart in metricsChainCharts()" :key="chart.id">
+ <article class="metric-chart-card">
+ <div class="metric-chart-head">
+ <h3 class="metric-chart-title" x-text="chart.title"></h3>
+ <div class="metric-chart-value" x-text="metricLatestValueLabel(chart)"></div>
+ </div>
+ <div class="metric-chart-frame">
+ <div class="metric-chart-y-axis">
+ <template x-for="tick in metricYAxisTicks(chart)" :key="`${chart.id}-y-${tick}`">
+ <span class="metric-chart-axis-label" :style="metricYAxisLabelStyle(chart, tick)" x-text="metricAxisValueLabel(chart, tick)"></span>
+ </template>
+ </div>
+ <div class="metric-chart-plot" @mousemove="setMetricHoverFromPlot(chart, $event)" @mouseleave="clearMetricHover(chart)">
+ <svg class="metric-chart-svg" viewBox="0 0 300 148" preserveAspectRatio="none" role="img" :aria-label="chart.title">
+ <path class="metric-chart-gridline" :d="metricGridPath(chart)"></path>
+ <line class="metric-chart-axis" x1="4" y1="8" x2="4" y2="132"></line>
+ <line class="metric-chart-axis" x1="4" y1="132" x2="296" y2="132"></line>
+ <polyline class="metric-chart-line" :points="metricChartPoints(chart)"></polyline>
+ </svg>
+ <div class="metric-chart-points">
+ <template x-for="marker in metricChartPointMarkers(chart)" :key="`${chart.id}-point-${marker.height}`">
+ <button class="metric-chart-point-hit" type="button" :class="{ 'is-active': metricHover?.chartId === chart.id && metricHover?.height === marker.height }" :style="metricPointStyle(marker)" :title="marker.label" @focus="setMetricHover(chart, marker)" @blur="clearMetricHover(chart)" :aria-label="marker.label"></button>
+ </template>
+ </div>
+ <template x-if="metricHover?.chartId === chart.id">
+ <div class="metric-chart-tooltip" :style="metricTooltipStyle(chart)" x-text="metricTooltipLabel(chart)"></div>
+ </template>
+ </div>
+ <div class="metric-chart-x-axis">
+ <template x-for="tick in metricXAxisTicks(chart)" :key="`${chart.id}-x-${tick}`">
+ <span class="metric-chart-axis-label" :style="metricXAxisLabelStyle(chart, tick)" x-text="`#${tick}`"></span>
+ </template>
+ </div>
+ </div>
+ </article>
+ </template>
+ </div>
+ <div class="metrics-section-head">
+ <h3>Burn claims</h3>
+ </div>
+ <div class="metrics-summary">
+ <div class="metric"><div class="label">Claims</div><div class="value" x-text="metricsLatest().burnClaimCount ?? '-'"></div></div>
+ <div class="metric"><div class="label">Payouts</div><div class="value" x-text="metricsLatest().burnClaimFeeShareCount ?? '-'"></div></div>
+ <div class="metric"><div class="label">Payout amount</div><div class="value" x-text="metricAmountLabel(metricsLatest().burnClaimFeeShareAmount)"></div></div>
+ </div>
+ <div class="metrics-grid">
+ <template x-for="chart in metricsBurnClaimCharts()" :key="chart.id">
<article class="metric-chart-card">
<div class="metric-chart-head">
<h3 class="metric-chart-title" x-text="chart.title"></h3>
@@ -4728,10 +4816,13 @@ mod tests {
transaction_count: 0,
transfer_count: 0,
burn_count: 0,
+ burn_claim_count: 0,
mine_count: 0,
burned_amount: 0,
total_burned_amount: 0,
fees_amount: 0,
+ burn_claim_fee_share_count: 0,
+ burn_claim_fee_share_amount: 0,
reward_amount: 0,
vdf_rounds,
finalizer_rank: 0,
@@ -4780,8 +4871,41 @@ mod tests {
}
#[test]
+ fn metrics_response_includes_burn_claim_charts() {
+ let mut row = metric_row(7, Some(600_000), 120);
+ row.burn_claim_count = 2;
+ row.burn_claim_fee_share_count = 3;
+ row.burn_claim_fee_share_amount = 4_500_000;
+ let response = super::metrics_response(true, vec![row]);
+
+ let burn_claims = response
+ .charts
+ .iter()
+ .find(|chart| chart.id == "burn-claims")
+ .expect("burn claim count chart should exist");
+ assert_eq!(burn_claims.section, "burnClaims");
+ assert_eq!(burn_claims.points[0].value, 2.0);
+
+ let payouts = response
+ .charts
+ .iter()
+ .find(|chart| chart.id == "burn-claim-payouts")
+ .expect("burn claim payout count chart should exist");
+ assert_eq!(payouts.section, "burnClaims");
+ assert_eq!(payouts.points[0].value, 3.0);
+
+ let payout_amount = response
+ .charts
+ .iter()
+ .find(|chart| chart.id == "burn-claim-payout-amount")
+ .expect("burn claim payout amount chart should exist");
+ assert_eq!(payout_amount.section, "burnClaims");
+ assert_eq!(payout_amount.points[0].value, 4.5);
+ }
+
+ #[test]
fn metrics_screen_includes_block_range_filter() {
- assert!(super::INDEX_HTML.contains("iuna-ui.js?v=76"));
+ assert!(super::INDEX_HTML.contains("iuna-ui.js?v=77"));
assert!(super::INDEX_HTML.contains("aria-label=\"Metrics block range\""));
assert!(super::INDEX_HTML.contains("setMetricsRange(100)"));
assert!(super::INDEX_HTML.contains("setMetricsRange(1000)"));
@@ -4789,6 +4913,14 @@ mod tests {
}
#[test]
+ fn metrics_screen_includes_burn_claim_section() {
+ assert!(super::INDEX_HTML.contains("metricsBurnClaimCharts()"));
+ assert!(super::INDEX_HTML.contains("Burn claims</h3>"));
+ assert!(super::INDEX_HTML.contains("burnClaimCount"));
+ assert!(super::INDEX_HTML.contains("burnClaimFeeShareAmount"));
+ }
+
+ #[test]
fn initial_setup_includes_node_mode_choices() {
assert!(super::INDEX_HTML.contains("aria-label=\"Initial node mode\""));
assert!(super::INDEX_HTML.contains("selectSetupNodeMode('wallet')"));
diff --git a/www/assets/iuna-ui.js b/www/assets/iuna-ui.js
@@ -1171,6 +1171,14 @@ window.iunaApp = function iunaApp() {
return Array.isArray(this.blockchainMetrics?.charts) ? this.blockchainMetrics.charts : [];
},
+ metricsChainCharts() {
+ return this.metricsCharts().filter((chart) => chart.section !== "burnClaims");
+ },
+
+ metricsBurnClaimCharts() {
+ return this.metricsCharts().filter((chart) => chart.section === "burnClaims");
+ },
+
metricsLatest() {
return this.blockchainMetrics?.latest || {};
},