feat(portal): wire DashboardPage to live Chain-138 RPC + SolaceScan Explorer

- Add services/{http,chain138,explorer,proxmox,dbisCore} + hooks/{useLiveChain,useOnChainBalances}
- Add BackendStatusBar + LiveNetworkPanel components on DashboardPage
- Overlay on-chain META balance on account rows carrying a walletAddress
- Normalize EIP-55 checksum in chain138.getNativeBalance so hand-typed
  sample custody addresses (e.g. 0x742d35Cc...bD38) don't silently drop
  out of the balance map
- Default RPC: https://rpc.d-bis.org (user-preferred gateway)
- proxmox.ts stays mocked (CF-Access, needs BFF); dbisCore.ts stays
  mocked (no public deployment yet)

Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
This commit is contained in:
Devin AI
2026-04-19 00:33:46 +00:00
parent 52676016fb
commit 007c79d7a9
11 changed files with 781 additions and 14 deletions

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
TrendingUp, TrendingDown, DollarSign, Activity, AlertTriangle, Clock,
@@ -6,6 +6,10 @@ import {
Landmark, FileText, Shield, CheckSquare, ChevronRight, RefreshCw
} from 'lucide-react';
import { financialSummary, sampleAccounts, treasuryPositions, complianceAlerts, recentActivity, portalModules } from '../data/portalData';
import LiveNetworkPanel from '../components/portal/LiveNetworkPanel';
import BackendStatusBar from '../components/portal/BackendStatusBar';
import { useOnChainBalances } from '../hooks/useOnChainBalances';
import { endpoints } from '../config/endpoints';
const formatCurrency = (amount: number, currency = 'USD') => {
if (Math.abs(amount) >= 1_000_000_000) return `${currency === 'USD' ? '$' : ''}${(amount / 1_000_000_000).toFixed(2)}B`;
@@ -56,12 +60,19 @@ export default function DashboardPage() {
const openAlerts = complianceAlerts.filter(a => a.status !== 'resolved');
const onChainAddresses = useMemo(
() => sampleAccounts.filter(a => !!a.walletAddress).map(a => a.walletAddress as string),
[],
);
const { balances: onChainBalances, loading: balancesLoading } = useOnChainBalances(onChainAddresses);
return (
<div className="dashboard-page">
<div className="dashboard-header">
<div className="dashboard-header-left">
<h1>Portfolio Overview</h1>
<p className="dashboard-subtitle">Solace Bank Group PLC Consolidated View</p>
<div style={{ marginTop: 10 }}><BackendStatusBar /></div>
</div>
<div className="dashboard-header-right">
<div className="time-range-selector">
@@ -154,6 +165,11 @@ export default function DashboardPage() {
</div>
<div className="dashboard-grid">
{/* Chain 138 live network health — wired to rpc-core.d-bis.org + explorer */}
<div style={{ gridColumn: '1 / -1' }}>
<LiveNetworkPanel />
</div>
{/* Asset Allocation */}
<div className="dashboard-card asset-allocation">
<div className="card-header">
@@ -217,20 +233,33 @@ export default function DashboardPage() {
<button className="card-action" onClick={() => navigate('/accounts')}>Manage <ChevronRight size={12} /></button>
</div>
<div className="accounts-list">
{sampleAccounts.filter(a => !a.parentId).slice(0, 5).map(acc => (
<div key={acc.id} className="account-row">
<div className="account-info">
<span className={`account-type-badge ${acc.type}`}>{acc.type}</span>
<span className="account-name">{acc.name}</span>
{sampleAccounts.filter(a => !a.parentId).slice(0, 5).map(acc => {
const onChain = acc.walletAddress ? onChainBalances[acc.walletAddress] : undefined;
return (
<div key={acc.id} className="account-row">
<div className="account-info">
<span className={`account-type-badge ${acc.type}`}>{acc.type}</span>
<span className="account-name">{acc.name}</span>
{acc.walletAddress && (
<span style={{ fontSize: 10, color: onChain ? '#22c55e' : balancesLoading ? '#eab308' : '#6b7280' }}>
{onChain ? `● live · chain ${endpoints.chain138.chainId}` : balancesLoading ? '○ fetching…' : '○ off-chain'}
</span>
)}
</div>
<div className="account-balance">
<span className="mono">
{acc.currency === 'BTC' ? `${acc.balance.toFixed(2)} BTC` : formatCurrency(acc.balance, acc.currency)}
</span>
<span className="account-currency">{acc.currency}</span>
{onChain && (
<span className="mono" style={{ fontSize: 10, color: '#60a5fa', marginTop: 2 }}>
on-chain: {Number(onChain.balanceEth).toFixed(4)} META
</span>
)}
</div>
</div>
<div className="account-balance">
<span className="mono">
{acc.currency === 'BTC' ? `${acc.balance.toFixed(2)} BTC` : formatCurrency(acc.balance, acc.currency)}
</span>
<span className="account-currency">{acc.currency}</span>
</div>
</div>
))}
);
})}
</div>
</div>