- Explorer index shows last 15 blocks with height, type, time, diff, hash - All heights and hashes link to block detail page - Block detail: prev hash links to previous block, reward shown - Stats cards link to block/aliases pages - Previous/Next navigation with block numbers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
134 lines
3.6 KiB
PHP
134 lines
3.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Mod\Explorer\Controllers;
|
|
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Routing\Controller;
|
|
use Mod\Chain\Services\DaemonRpc;
|
|
|
|
/**
|
|
* Block explorer web controller.
|
|
*
|
|
* GET /explorer — blockchain overview + latest blocks
|
|
* GET /explorer/block/{id} — block details by height or hash
|
|
* GET /explorer/tx/{hash} — transaction details
|
|
* GET /explorer/aliases — alias directory
|
|
* GET /explorer/assets — confidential assets browser
|
|
* GET /explorer/charts — chain charts
|
|
*/
|
|
class ExplorerController extends Controller
|
|
{
|
|
public function __construct(
|
|
private readonly DaemonRpc $rpc,
|
|
) {}
|
|
|
|
/**
|
|
* GET /explorer
|
|
*/
|
|
public function index(): \Illuminate\View\View
|
|
{
|
|
$info = $this->rpc->getInfo();
|
|
$height = ($info['height'] ?? 1) - 1;
|
|
|
|
// Fetch last 15 blocks
|
|
$blocks = [];
|
|
for ($i = $height; $i >= max(0, $height - 14); $i--) {
|
|
$block = $this->rpc->getBlockByHeight($i);
|
|
if (! empty($block['block_header'])) {
|
|
$blocks[] = $block['block_header'];
|
|
}
|
|
}
|
|
|
|
return view('explorer::index', [
|
|
'info' => $info,
|
|
'lastBlock' => $blocks[0] ?? [],
|
|
'blocks' => $blocks,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* GET /explorer/block/{id}
|
|
*/
|
|
public function block(string $id): \Illuminate\View\View
|
|
{
|
|
$block = is_numeric($id)
|
|
? $this->rpc->getBlockByHeight((int) $id)
|
|
: $this->rpc->getBlockByHash($id);
|
|
|
|
return view('explorer::block', [
|
|
'block' => $block['block_header'] ?? [],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* GET /explorer/tx/{hash}
|
|
*/
|
|
public function transaction(string $hash): \Illuminate\View\View
|
|
{
|
|
$tx = $this->rpc->getTransaction($hash);
|
|
|
|
return view('explorer::transaction', [
|
|
'tx' => $tx,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* GET /explorer/aliases
|
|
*/
|
|
public function aliases(Request $request): \Illuminate\View\View
|
|
{
|
|
$result = $this->rpc->getAllAliases();
|
|
$aliases = $result['aliases'] ?? [];
|
|
$filter = $request->get('filter', 'all');
|
|
|
|
if ($filter === 'premium') {
|
|
$aliases = array_filter($aliases, fn($a) => strlen($a['alias'] ?? '') < 6);
|
|
}
|
|
|
|
return view('explorer::aliases', [
|
|
'aliases' => $aliases,
|
|
'total' => count($result['aliases'] ?? []),
|
|
'filter' => $filter,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* GET /explorer/charts
|
|
*/
|
|
public function charts(): \Illuminate\View\View
|
|
{
|
|
return view('explorer::charts');
|
|
}
|
|
|
|
/**
|
|
* GET /explorer/search?q={query}
|
|
*/
|
|
public function search(Request $request): \Illuminate\Http\RedirectResponse
|
|
{
|
|
$query = trim((string) $request->get('q'));
|
|
|
|
if (is_numeric($query)) {
|
|
return redirect("/explorer/block/{$query}");
|
|
}
|
|
|
|
if (strlen($query) === 64 && ctype_xdigit($query)) {
|
|
// Could be block hash or tx hash — try block first
|
|
$block = $this->rpc->getBlockByHash($query);
|
|
if (! empty($block['block_header'])) {
|
|
return redirect("/explorer/block/{$query}");
|
|
}
|
|
|
|
return redirect("/explorer/tx/{$query}");
|
|
}
|
|
|
|
// Might be an alias name
|
|
$alias = $this->rpc->getAliasByName($query);
|
|
if ($alias) {
|
|
return redirect("/explorer/aliases?search={$query}");
|
|
}
|
|
|
|
return redirect('/explorer')->with('error', 'Nothing found for: ' . $query);
|
|
}
|
|
}
|