lthn.io/app/Mod/Explorer/Controllers/ExplorerController.php
Claude 333ce446de
feat(explorer): recent blocks list with clickable links
- 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>
2026-04-04 03:49:22 +01:00

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);
}
}