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:
82
src/services/explorer.ts
Normal file
82
src/services/explorer.ts
Normal 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}`;
|
||||
}
|
||||
Reference in New Issue
Block a user