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

82
src/services/explorer.ts Normal file
View File

@@ -0,0 +1,82 @@
/**
* SolaceScan Explorer (Blockscout v2) client for Chain 138.
*
* Base URL: https://api.explorer.d-bis.org (CORS *)
* Fallback: https://explorer.d-bis.org/api/v2 (same data, different host)
*
* We hit the `api.*` subdomain by default because it returns clean JSON
* without the Next.js HTML wrapper.
*/
import { httpJson } from './http';
import { endpoints } from '../config/endpoints';
const api = (path: string) => `${endpoints.explorer.apiBaseUrl}/api/v2${path}`;
export interface ExplorerStats {
total_blocks: number;
total_transactions: number;
total_addresses: number;
latest_block: number;
average_block_time: number;
gas_prices: { average: number; fast?: number; slow?: number };
network_utilization_percentage: number;
transactions_today: number;
}
export async function getExplorerStats(): Promise<ExplorerStats> {
return httpJson<ExplorerStats>(api('/stats'));
}
export interface ExplorerBlock {
height: number;
hash: string;
timestamp: string;
tx_count: number;
gas_used: string;
gas_limit: string;
size: number;
miner: { hash: string };
}
export async function getLatestBlocks(): Promise<ExplorerBlock[]> {
return httpJson<ExplorerBlock[]>(api('/main-page/blocks'));
}
export interface ExplorerTx {
hash: string;
block_number: number;
timestamp: string;
from: { hash: string };
to: { hash: string } | null;
value: string; // wei
gas_used: string;
gas_price: string;
status: 'ok' | 'error' | null;
method: string | null;
fee: { value: string };
}
interface PagedTxResponse { items: ExplorerTx[]; next_page_params?: unknown }
export async function getLatestTransactions(limit = 20): Promise<ExplorerTx[]> {
const data = await httpJson<PagedTxResponse>(api('/transactions'));
return (data.items ?? []).slice(0, limit);
}
export async function getAddressTransactions(address: string, limit = 20): Promise<ExplorerTx[]> {
const data = await httpJson<PagedTxResponse>(api(`/addresses/${address}/transactions`));
return (data.items ?? []).slice(0, limit);
}
export function explorerTxUrl(hash: string): string {
return `${endpoints.explorer.baseUrl}/tx/${hash}`;
}
export function explorerAddressUrl(address: string): string {
return `${endpoints.explorer.baseUrl}/address/${address}`;
}
export function explorerBlockUrl(height: number): string {
return `${endpoints.explorer.baseUrl}/block/${height}`;
}