Skip to content

feat(api-lite): explorer API from the block recorder or RPC with a sqlite index, no Postgres - #699

Merged
nelitow merged 19 commits into
mainfrom
nj/feat/el-cheapo-explorer
Sep 3, 2026
Merged

feat(api-lite): explorer API from the block recorder or RPC with a sqlite index, no Postgres#699
nelitow merged 19 commits into
mainfrom
nj/feat/el-cheapo-explorer

Conversation

@nelitow

@nelitow nelitow commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds api-lite, a small service that serves the existing explorer GraphQL schema without Postgres so the explorer can run on a single cheap host. Blocks come from the fuel-core S3 block recorder (decoded from protobuf and checked field by field against fuel-core on real mainnet blocks) or straight from fuel-core RPC, selected by config. A sqlite index covers a retention window for account history, search, charts, predicates and minted assets; older account history falls back to fuel-core. The frontend is unchanged apart from the homepage hero, whose tiles now load independently.

Changes

Area Change
api-lite service New package packages/api-lite: graphql-yoga server on the production schema files, resolvers backed by an S3 or RPC block source, a memory and disk block cache, and a sqlite index with retention
Block decoder Protobuf recorder objects decoded to fuel-core's GraphQL JSON shape, including transaction ids, raw payloads, status, fees and gas; parity test against committed mainnet fixtures
Account history Served from the index inside the window, with a fuel-core transactionsByOwner fallback and cursor continuation beyond it
Homepage charts Daily transactions, hourly TPS and fee spent computed from per-block gas, fee and time columns in the index; ETH price from CoinGecko for USD
Frontend hero useFuelExplorerStatus split into three hooks so blocks, rolling stats and charts render as each arrives; new rollingStats sdk query
Tip and backfill Batched forward catch-up, backfill paused while the served tip lags, contiguous-only range bookkeeping, restart recovery, missing-block skip
Hardening Masked errors in production, page size validation, fetch timeouts, clamped passthrough page sizes, nginx rate limit, boot retry when fuel-core is down, disk cache cap enforced on write
Docker and deploy docker/vps: two Dockerfiles, compose for a 1 CPU / 1 GB budget, nginx envsubst template, production compose override, Railway configs, deploy.md with droplet and Railway steps, testnet env
Docs Design spec, implementation plan and load test report under docs/superpowers

@nelitow
nelitow requested a review from helciofranco as a code owner August 28, 2026 19:50
@vercel

vercel Bot commented Aug 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
fuel-explorer-v2-vite Ready Ready Preview Aug 31, 2026 12:50pm

Request Review

@cursor

cursor Bot commented Aug 28, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Introduces production deployment paths, nginx proxy/rate-limit behavior, and scheduled checks against live explorer URLs; misconfigured env or upstream DNS could break public API routing or false-alarm uptime failures.

Overview
Adds docker/vps so api-lite and the explorer SPA can run as a two-container stack on a small host (local compose with CPU/RAM caps, GHCR-based prod compose, and Railway config JSON).

api-lite gets a multi-stage Alpine image: monorepo build, minimal runtime node_modules (native better-sqlite3 compiled for musl), bundled dist, and copied GraphQL schema files. Explorer builds with Vite (network env + stripping indexer API keys from the baked .env), then serves static assets behind nginx that proxies /api/ with request-time upstream DNS, 20 r/s rate limits, and a 60s cache on /api/charts.

deploy.md documents Railway (private api + public explorer) and DigitalOcean droplet flows; .env.example documents block source (S3 vs RPC) and optional L1/Cosmos vars. .dockerignore / .railwayignore keep docker/vps/.env out of build contexts.

CI: PR workflow job tests-e2e-api-lite (prod explorer build, local fuel node, Playwright via pnpm test:e2e-api-lite); scheduled api-lite-uptime hits production /api/health (ok + lag ≤ 120), home, and /api/charts. Minor tooling ignores: Biome maxSize + test/fixtures, .superpowers/ in gitignore.

Reviewed by Cursor Bugbot for commit 8d535ae. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread docker/vps/nginx.conf.template Outdated
Comment thread packages/api-lite/src/cosmos/CosmosPoller.ts
Comment thread packages/api-lite/src/l1/L1Index.ts
Comment thread packages/api-lite/src/staking/proof.ts
Comment thread packages/api-lite/src/graphql/resolvers/transactions.ts
Comment thread packages/api-lite/src/hot/HotKeys.ts
Comment thread docker/vps/.env.example
Comment thread docker/vps/nginx.conf.template
… formatting

- rate-limit key from X-Forwarded-For behind the Railway edge
- Cosmos poller keeps its cursor on non-2xx responses
- staking event pagination orders on (block_height, _id)
- withdraw proof cache no longer stores failed responses
- DataCache bounded to 1000 entries with LRU eviction
- hot-key decay timer catches sqlite errors
- per-minute TPS buckets, GET /api/charts with Cache-Control and nginx proxy_cache, home charts fetch it first
- fee chart USD values use convertToUsd formatting and full-hour buckets
- biome format on loadtest scripts
Comment thread docker/vps/nginx.conf.template Outdated
- empty environment variables are treated as unset by loadConfig
- nginx listens on IPv6 as well
- rate limit keys on the TCP peer by default; RATE_LIMIT_KEY=$xff_last opts into the last X-Forwarded-For hop behind a proxy
- assets, contracts and predicates are exempt from index retention; asset lookups fall back to fuel-core assetDetails and the verified-assets registry; registry assets are seeded on boot
- suspicious flag derived from subId collisions with the registry
- USD values for base-asset fees, inputs, outputs, mints and balances from the ETH price
- exact 1-based counts on account, contract and block transaction lists and on minted assets
- transactionsByOwner no longer falls back to fuel-core on forward paging past the index; fallback block rendering is parallel and bounded
- search returns null and logs on malformed input or lookup failures
- dashboard and chart series serve numbers, timestamps in milliseconds, per-bucket max tps and gas
- utxos cap 2500 like production
- scheduled uptime probe workflow for the explorer
Comment on lines +10 to +28
runs-on: ubuntu-latest
timeout-minutes: 5
env:
EXPLORER_URL: ${{ vars.API_LITE_EXPLORER_URL || 'https://explorer-production-f355.up.railway.app' }}
MAX_LAG_BLOCKS: "120"
steps:
- name: Health and index lag
run: |
body=$(curl -fsS --max-time 20 "$EXPLORER_URL/api/health")
echo "$body"
ok=$(echo "$body" | jq -r '.ok')
lag=$(echo "$body" | jq -r '.lag')
test "$ok" = "true"
test "$lag" -le "$MAX_LAG_BLOCKS"
- name: Home page
run: curl -fsS --max-time 20 -o /dev/null "$EXPLORER_URL/"
- name: Charts endpoint
run: |
curl -fsS --max-time 20 "$EXPLORER_URL/api/charts" | jq -e '.statistics and .tps' > /dev/null
- Playwright suite under tests/api-lite: home, block, transaction, account, search and staking pages render real data from api-lite in RPC mode against the local node
- playwright-api-lite.config.ts boots api-lite on 3004 and the vite preview on 3000
- tests-e2e-api-lite job in pr-tests.yml
Comment on lines +211 to +275
name: E2E Tests api-lite
runs-on: warp-ubuntu-latest-x64-4x
timeout-minutes: 14
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
TURBO_REMOTE_ONLY: true
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Cache turbo build setup
uses: actions/cache@v4
with:
path: .turbo
# Distinguish between a normal build and a E2E build, which disables code splitting for faster builds
key: ${{ runner.os }}-e2e-turbo-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-e2e-turbo-

- name: Setup Node
uses: FuelLabs/github-actions/setups/node@master
with:
node-version: 20.15.1
pnpm-version: 9.10.0

- name: Create .env file explorer
run: cp packages/app-explorer/.env.example packages/app-explorer/.env

- name: Build production version of explorer
env:
NODE_ENV: test
run: pnpm build:prod

- name: Setup Docker
uses: FuelLabs/github-actions/setups/docker@master
with:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Start Test Node
timeout-minutes: 7
run: pnpm node:start

- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps chromium

- name: Run E2E Tests
run: xvfb-run --auto-servernum -- pnpm test:e2e-api-lite
env:
PLAYWRIGHT_JSON_OUTPUT_NAME: playwright-report/api-lite/results.json
PLAYWRIGHT_FORCE_TTY: true
PLAYWRIGHT_FORCE_COLOR: true

- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-api-lite
path: packages/e2e-tests/playwright-report/api-lite/
retention-days: 30

- name: Stop Test Node
run: pnpm node:stop
Comment thread packages/api-lite/src/graphql/resolvers/transactions.ts
Comment thread packages/api-lite/src/graphql/resolvers/transactions.ts Outdated
Comment thread packages/api-lite/src/graphql/resolvers/assetEnrich.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

There are 5 total unresolved issues (including 3 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a9d2751. Configure here.

Comment thread packages/api-lite/src/graphql/resolvers/transactions.ts
Comment thread packages/api-lite/src/index/Index.ts
- fuel-core fallback pages number from the oldest end instead of reusing the newest ranks
- a failed fallback render is backfilled from the remaining items instead of shrinking the page
- list counts capped at 1001 and bounded to the contiguous index window
- asset registry matches compare lowercase ids
- REST errors distinguish client from upstream: ValidationError maps to 400, a missing event to 404, everything else to 502 with a server-side log
- index writes happen before the disk-cache write, so a crash between them re-fetches instead of leaving a pinned block unindexed
- backfill clamps indexed_from to the retention floor when retention advanced it mid-fetch (real race found by the new regression test)
- backfill gaps surfaced in /health
- a 200 cosmos response without a tx_responses array no longer advances the cursor
- long-lived viem client, single index.db path constant, decoder policies trimmed to the schema-backed four
@cursor

cursor Bot commented Aug 31, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

- block cache accounting is heap-aware: a measured 2.25x object-vs-JSON multiplier (applied as 2.5x) counts against the budget
- 768 MB heap in the image with the memory math documented; the 960 MB compose tier overrides the cache to 48 MB
- backfill batch size wired to config instead of a silent 20
- eviction under pinning terminates and logs once per minute
- Railway restart policy retries 100 times instead of stranding the service after a crash loop
@nelitow

nelitow commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

👍

@nelitow
nelitow merged commit 0c41d0e into main Sep 3, 2026
27 checks passed
@nelitow
nelitow deleted the nj/feat/el-cheapo-explorer branch September 3, 2026 17:05
@nelitow
nelitow restored the nj/feat/el-cheapo-explorer branch September 3, 2026 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants