From 8df1ec45174ffc63025a1651f9503dc662509369 Mon Sep 17 00:00:00 2001 From: Simon Morley Date: Sat, 25 Apr 2026 15:20:00 +0100 Subject: [PATCH] front: render validator_prefs + era_exposure sections for Polkadot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PolkadotChainSections now renders three Sections when chain_data is populated: 1. Active Set (existing): currently_elected + observed_at_block 2. Validator Preferences (new): commission % + accepting-nominations (red 'No (blocked)' when validator_prefs.blocked is true) 3. Era Exposure (new): era_index, total_planck, own_planck, nominator_count with planckToDot conversion via BigInt to avoid Number's 53-bit precision ceiling Each section gated on the corresponding chain_data field being non-null — graceful degradation when the worker hasn't populated yet (deploy gap) or when the storage row doesn't exist. --- src/components/ChainDataSections.tsx | 86 ++++++++++++++++++++++------ src/types/api.ts | 20 +++++++ 2 files changed, 89 insertions(+), 17 deletions(-) diff --git a/src/components/ChainDataSections.tsx b/src/components/ChainDataSections.tsx index 2c9ea42..f297169 100644 --- a/src/components/ChainDataSections.tsx +++ b/src/components/ChainDataSections.tsx @@ -728,6 +728,23 @@ function EthereumChainSections({ ); } +/// Convert planck (u128 wire-string) to a human-readable DOT figure. +/// Uses BigInt to avoid Number's 53-bit precision ceiling — top +/// validators have stake well above 2^53 planck. +function planckToDot(planckStr: string): string { + try { + const planck = BigInt(planckStr); + const denom = 10_000_000_000n; // 10^10 + const whole = planck / denom; + const frac = planck % denom; + // Show 4 fractional digits — beyond that the value is noise for UI. + const fracStr = (frac / 1_000_000n).toString().padStart(4, '0'); + return `${whole.toLocaleString()}.${fracStr}`; + } catch { + return planckStr; + } +} + function PolkadotChainSections({ data, isMobile, @@ -735,28 +752,63 @@ function PolkadotChainSections({ data: PolkadotChainData; isMobile: boolean; }) { - // Replaces the demoted dot_not_elected feed event. Badge renders - // only when the worker has populated chain_data.is_elected — during - // the deploy gap window where worker is_elected hasn't reached prod - // yet, undefined would render falsy and incorrectly show "No". Better - // to render nothing than to show wrong data. + // Replaces the demoted dot_not_elected feed event. Render only when + // the worker has populated chain_data.is_elected — during the deploy + // gap window where worker is_elected hasn't reached prod yet, + // undefined would render falsy and incorrectly show "No". Better to + // render nothing than to show wrong data. if (typeof data.is_elected !== 'boolean') { return null; } return ( -
- - {data.is_elected ? 'Yes' : 'No'} - - } - /> - {data.observed_at_block != null && ( - + <> +
+ + {data.is_elected ? 'Yes' : 'No'} + + } + /> + {data.observed_at_block != null && ( + + )} +
+ + {data.validator_prefs && ( +
+ + + {data.validator_prefs.blocked ? 'No (blocked)' : 'Yes'} + + } + /> +
)} -
+ + {data.era_exposure && ( +
+ + + + +
+ )} + ); } diff --git a/src/types/api.ts b/src/types/api.ts index 76f500f..cb9499b 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -483,9 +483,29 @@ export interface EthereumChainData { slashed: boolean; } +export interface PolkadotValidatorPrefs { + /// Commission expressed in basis points (0..=10000) — converted from + /// the chain's Perbill so the wire format matches Solana's commission + /// field. Frontend formats as `bps / 100` to display percent. + commission_bps: number; + blocked: boolean; +} + +export interface PolkadotEraExposure { + era_index: number; + /// Planck values are emitted as strings — JS Number can't represent + /// u128 losslessly. Frontend converts to DOT via BigInt division by + /// 10^10. + total_planck: string; + own_planck: string; + nominator_count: number; +} + export interface PolkadotChainData { is_elected: boolean; observed_at_block: number; + validator_prefs: PolkadotValidatorPrefs | null; + era_exposure: PolkadotEraExposure | null; } // --- Scan Analysis ---