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 ---