Interfaces
Explorer API
The public read-only API contract: core and indexed capabilities, exact routes, pagination, response models, precision rules, index states, CORS, errors, and frontend integration.
This page documents the implemented alpha. Executable source, interoperability vectors, and tests remain authoritative if prose and code ever diverge.
API role
The Explorer API is a public, read-only view of one full node’s canonical chain. It powers block explorers, status pages, transaction lookups, address views, and aggregate dashboards.
It has no transaction submission, wallet secret, mining, peer-control, or administrative route. It never reads or exposes rpc.token.
The service has two capability tiers:
| Tier | Data source | Availability |
|---|---|---|
| core | canonical block files and RocksDB indexes | enabled with the API |
| indexed | optional denormalized SQLite projection | only after index reaches ready state |
The core tier keeps an ordinary full node useful without paying SQLite storage and backfill cost. Collection-wide history, rankings, and aggregates require the index.
Startup and discovery
A standard testnet node starts the core API on loopback:
luracoin node --network testnet
Default service URLs:
Swagger UI http://127.0.0.1:18000/docs
ReDoc http://127.0.0.1:18000/redoc
OpenAPI http://127.0.0.1:18000/openapi.json
Health http://127.0.0.1:18000/health
Status http://127.0.0.1:18000/api/v1/status
Network ports:
| Network | Default explorer port |
|---|---|
| mainnet | 8000 |
| testnet | 18000 |
| devnet | 28000 |
Mainnet’s port is reserved while mainnet initialization remains unavailable.
Route capability matrix
Core routes work without SQLite:
| Method | Route | Result |
|---|---|---|
GET |
/health |
process, network, chain ID, API version |
GET |
/api/v1/status |
canonical tip, node state, index state, capabilities |
GET |
/api/v1/features |
compact core/index availability |
GET |
/api/v1/blocks |
newest-first canonical block summaries |
GET |
/api/v1/blocks/{height-or-hash} |
one block summary |
GET |
/api/v1/blocks/{height-or-hash}/transactions |
paginated transactions in one block |
GET |
/api/v1/transactions/{txid} |
one confirmed transaction |
GET |
/api/v1/addresses/{address} |
current confirmed balance and nonce |
Indexed routes require a ready SQLite projection:
| Method | Route | Result |
|---|---|---|
GET |
/api/v1/transactions |
global confirmed transaction feed |
GET |
/api/v1/addresses |
addresses ranked by balance |
GET |
/api/v1/addresses/{address}/transactions |
full indexed address history |
GET |
/api/v1/stats |
global counts and monetary aggregates |
The exact machine-readable contract is generated at /openapi.json from response models and parameter bounds.
JSON precision
All monetary fields are decimal strings, including:
balance, fee, value, reward, total_fees, total_received,
total_sent, total_transferred, total_rewards
Example:
{
"value": "18446744073709551615",
"fee": "4294967295"
}
This is deliberate. JavaScript’s number cannot represent the entire uint64 range exactly. UI formatting should parse with BigInt, then divide into LURA/lurashi units without floating-point consensus arithmetic.
Health and status
GET /health:
{
"ok": true,
"network": "testnet",
"chain_id": 1,
"api_version": 1
}
GET /api/v1/status:
{
"network": "testnet",
"chain_id": 1,
"api_version": 1,
"initialized": true,
"height": 2499,
"tip_id": "000000...",
"ready": true,
"syncing": false,
"index": {
"enabled": true,
"ready": false,
"state": "building",
"indexed_height": 1249,
"target_height": 2499,
"progress": 50.0
},
"capabilities": {
"block_list": true,
"block_lookup": true,
"block_transactions": true,
"transaction_lookup": true,
"address_state": true,
"transaction_feed": false,
"address_ranking": false,
"address_history": false,
"aggregate_stats": false
}
}
ready refers to the full node. index.ready refers to the SQLite projection. They answer different questions and may differ.
An uninitialized canonical chain reports height -1 and omits tip_id.
Feature state
GET /api/v1/features returns:
{
"api_version": 1,
"features": {
"core": { "enabled": true, "ready": true },
"explorer_index": {
"enabled": false,
"ready": false,
"state": "disabled"
}
}
}
Clients should inspect capabilities or feature state before showing an indexed view. Do not treat a 503 as an empty transaction feed.
Block summary model
Every block list/detail item contains:
| Field | Type | Meaning |
|---|---|---|
height |
integer | canonical height |
prev_block_hash |
string | 64-hex predecessor ID |
miner |
string | 34-character reward address |
id |
string | 64-hex block ID |
nonce |
integer | PoW nonce |
version |
integer | block format version |
timestamp |
integer | Unix seconds |
bits |
string | four compact-target bytes as eight hex characters |
transaction_count |
integer | coinbase plus normal transactions |
size_bytes |
integer | 118 + transaction_count × 213 |
total_fees |
decimal string | sum of non-coinbase fees |
reward |
decimal string | coinbase value |
confirmations |
integer | local depth including the block itself |
Block detail deliberately omits full transaction bodies; use its transactions subroute.
Listing blocks
GET /api/v1/blocks?limit=10&offset=0&before_height=2500
Constraints:
limit: 1–10, default 10;offset: 0–2,147,483,647;before_height: optional 0–uint32.
Items are newest first. The server loads and discards one block at a time to avoid materializing up to ten 8 MiB blocks in memory.
Response:
{
"network": "testnet",
"items": [],
"pagination": {
"limit": 10,
"offset": 0,
"count": 10,
"total": 2500,
"before_height": null,
"next_before_height": 2490
}
}
For stable backward traversal, pass next_before_height into the next call. This avoids a growing offset while the tip advances. before_height is exclusive: an anchor of 2490 begins below it.
Resolving one block
Both are valid:
GET /api/v1/blocks/42
GET /api/v1/blocks/000000...64 hex characters...
A decimal reference must fit uint32. Hash input accepts either hex case and is normalized for lookup. Any other syntax returns invalid_block_reference; a valid reference with no canonical object returns block_not_found.
Response envelope:
{
"network": "testnet",
"block": { "height": 42, "id": "..." }
}
The actual block object follows the complete block-summary model above.
Transaction model
Explorer transactions contain:
| Field | Type | Meaning |
|---|---|---|
id |
string | canonical transaction ID |
chain |
integer | network chain ID |
nonce |
integer | sender nonce or coinbase block height |
fee |
decimal string | lurashis |
value |
decimal string | lurashis |
from_address |
string | sender or zero coinbase sentinel |
to_address |
string | recipient |
unlock_sig |
string/null | 128 bytes as hex when included |
block_height |
integer | containing canonical height |
block_id |
string | containing block ID |
block_timestamp |
integer | containing block Unix time |
transaction_index |
integer | zero-based position |
confirmations |
integer | local canonical depth |
is_coinbase |
boolean, optional | projection/core classification |
direction |
string, optional | address-history context |
Core transaction responses may include the unlocking field because the canonical transaction object contains it. Treat it as public signature/key material, never as a secret.
Block transactions
GET /api/v1/blocks/{block_ref}/transactions?limit=25&offset=0
limit is 1–100, default 25. The response includes a compact block reference, items, and total transaction count:
{
"network": "testnet",
"block": { "height": 42, "id": "..." },
"items": [],
"pagination": {
"limit": 25,
"offset": 0,
"count": 25,
"total": 80
}
}
Transaction indices reflect their absolute positions even on later pages. Coinbase is index zero.
Transaction lookup and feed
Confirmed lookup is core:
GET /api/v1/transactions/{64-hex-txid}
It validates the ID, resolves the RocksDB transaction location, loads the block, bounds the transaction index, and rechecks that the transaction at that location has the requested ID. Stale/mismatched lookup state does not produce a false result.
The global feed is indexed:
GET /api/v1/transactions?limit=25&offset=0
It supports limit 1–100 and bounded non-negative offset. Results are confirmed transactions only.
Address state
Core route:
GET /api/v1/addresses/{address}
Response:
{
"network": "testnet",
"address": "L...",
"exists": true,
"balance": "700000000",
"nonce": 3
}
A syntactically valid address with no canonical account returns exists: false, zero balance, and nonce zero. This is distinct from an invalid address (400).
This public view is confirmed-only. It does not expose private RPC’s Redis-derived spendable balance or pending nonce.
Address summary and ranking
Indexed ranking:
GET /api/v1/addresses?limit=25&offset=0
Each summary contains:
address
balance
nonce
total_received
total_sent
total_fees
transaction_count
first_seen_height
last_seen_height
All monetary totals are strings. Ranking is derived from the current SQLite projection, not a consensus commitment.
Address history
Indexed route:
GET /api/v1/addresses/{address}/transactions?limit=25&offset=0
The envelope includes network, address, transaction items, and pagination. A transaction involving the address can include an optional contextual direction; do not use that label as a consensus field.
Aggregate statistics
GET /api/v1/stats returns:
{
"network": "testnet",
"stats": {
"network": "testnet",
"chain_id": 1,
"indexed_height": 2499,
"indexed_block_id": "...",
"blocks": 2500,
"transactions": 8120,
"addresses": 445,
"total_fees": "10000",
"total_transferred": "920000000000",
"total_rewards": "12500000000000"
}
}
Statistics describe the indexed canonical prefix. Consumers should compare indexed_height and block ID with status rather than assuming they equal the live tip.
Index states and 503 behavior
Normal index states:
| State | Enabled | Ready | Meaning |
|---|---|---|---|
disabled |
no | no | index not requested |
building |
yes | no | backfill or catch-up in progress |
ready |
yes | yes | reached observed target |
error |
yes | no | latest indexing/status operation failed |
Indexed routes return one of:
{ "error": "explorer_index_disabled", "index": { "state": "disabled", "enabled": false, "ready": false } }
{ "error": "explorer_index_building", "index": { "state": "building", "enabled": true, "ready": false } }
{ "error": "explorer_index_error", "index": { "state": "error", "enabled": true, "ready": false } }
{ "error": "explorer_index_unavailable" }
All use HTTP 503. Core routes remain available.
Other errors
| Status | Error/case |
|---|---|
| 400 | invalid_block_reference |
| 400 | invalid_transaction_id |
| 400 | invalid_address |
| 404 | block_not_found |
| 404 | transaction_not_found |
| 422 | FastAPI parameter validation failure |
| 503 | index state/unavailable errors above |
FastAPI’s 422 body follows its validation-error schema. Clients should tolerate additional fields in future compatible API responses.
CORS and publication
The service enables cross-origin GET requests from any origin, without credentials, with a 600-second preflight cache. It remains bound to 127.0.0.1 by default.
Publishing it requires an explicit host change and an external reverse proxy with:
- TLS;
- per-IP and global rate limits;
- request/response size controls;
- upstream timeouts and concurrency bounds;
- caching policy for immutable block/transaction lookups;
- health checks and logs;
- abuse and disk-pressure monitoring.
The application itself provides no authentication, rate limiting, or SQLite quota.
Website configuration
The Astro explorer reads a public build-time base origin:
PUBLIC_EXPLORER_API_URL=https://api.luracoin.com npm run build
Configure only the origin/base URL. The browser appends /api/v1/status, /api/v1/blocks, and other routes.
If this variable is absent, the current public website stays in a transparent preview/placeholder state rather than inventing chain data. That is the correct behavior until a stable public API endpoint exists.
Confirmation disclaimer
The API calculates:
confirmations = local_chain_height - block_height + 1
This is depth in one node’s linear canonical history. Because the alpha has no cumulative-work fork choice or reorganization support, confirmations are not public-network finality. Explorer UI must retain that distinction.
Source anchors
Primary implementation files used for this chapter:
