2026-04-19 00:33:46 +00:00
|
|
|
/**
|
|
|
|
|
* 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> {
|
2026-04-19 09:53:44 +00:00
|
|
|
const raw = await httpJson<ExplorerStats>(api('/stats'));
|
|
|
|
|
// Blockscout returns `average_block_time` in milliseconds; normalize to seconds
|
|
|
|
|
// so callers can display `${value.toFixed(1)}s` directly. Chain-138 block time
|
|
|
|
|
// is ~4s, so a raw value > 60 is a reliable signal that it is still in ms.
|
|
|
|
|
const average_block_time =
|
|
|
|
|
typeof raw.average_block_time === 'number' && raw.average_block_time > 60
|
|
|
|
|
? raw.average_block_time / 1000
|
|
|
|
|
: raw.average_block_time;
|
|
|
|
|
return { ...raw, average_block_time };
|
2026-04-19 00:33:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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}`;
|
|
|
|
|
}
|