Skip to content

About

Lumina — Next.js explorer frontend for the Stellar event indexer

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

100 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Lumina Frontend

Next.js explorer UI for Lumina, an open-source event indexer and GraphQL data layer for the Stellar network.

Part of the Lumina project, split across three repos:

Getting Started

npm install
npm run dev

Runs against http://localhost:4000/graphql by default — start lumina-backend's GraphQL server first (or point at a deployed one, see below).

Environment variables

Variable Used by Default
GRAPHQL_URL Server Components (runtime, not exposed to the browser) http://localhost:4000/graphql
NEXT_PUBLIC_GRAPHQL_URL Client-side queries (LiveFeed seed + polling fallback, GraphQL Playground) — build-time inlined http://localhost:4000/graphql
NEXT_PUBLIC_GRAPHQL_WS_URL GraphQL subscriptions (LiveFeed, account Live Activity) — build-time inlined NEXT_PUBLIC_GRAPHQL_URL with http→ws / https→wss
NEXT_PUBLIC_REGISTRY_CONTRACT_ID Registry + Stats pages, reading the Lumina Registry directly via Soroban RPC the deployed testnet registry (CAYUDQPV3RKPM3EXDFGI3457FV677JLUCJ4OLKWGCUBPRIHYKXK3WFAZ)
NEXT_PUBLIC_SOROBAN_RPC_URL Same https://soroban-testnet.stellar.org
NEXT_PUBLIC_NETWORK_PASSPHRASE Same, and the Registry page's wallet-signed transactions testnet passphrase
NEXT_PUBLIC_REGISTRY_READ_ACCOUNT Any funded account used only for read-only simulation (no secret key needed) a funded testnet account
NEXT_PUBLIC_ERROR_REPORTING_URL Optional browser error ingestion endpoint — build-time inlined unset (reporting off)
ERROR_REPORTING_URL Optional source-map upload endpoint used after a production build unset (maps not generated or uploaded)
ERROR_REPORTING_TOKEN Optional bearer token for source-map uploads; never exposed to the browser unset

| NEXT_PUBLIC_SITE_URL | Canonical URLs, og:image and the sitemap — build-time inlined | http://localhost:3000 |

NEXT_PUBLIC_VAPID_PUBLIC_KEY enables the optional push notifications on /watch. It is unset by default and push stays off unless someone opts in — see docs/PUSH_NOTIFICATIONS.md for the backend endpoint the delivery path still needs.

NEXT_PUBLIC_* values must be set as Docker build ARGs (see Dockerfile), not runtime env vars — Next.js inlines them into the client bundle at build time, so setting them only at container-run time has no effect.

NEXT_PUBLIC_SITE_URL is the one that has to be right in production. Every canonical link, share-card URL and sitemap entry is resolved against it at build time, so a site served from another origin while this still says localhost emits absolute URLs that point nowhere.

Crawling and link previews

lib/routes.ts is the single inventory of the site's routes — path, nav label, description and crawl policy — and everything else reads from it: the navbar, sitemap.ts, robots.txt and the per-route metadata. Adding a route means adding one entry there, not editing four files and hoping they agree.

Indexed. The eight stable routes (/, /explorer, /transactions, /search, /events, /graphql, /registry, /stats) are in sitemap.xml and each carries its own title, description, canonical URL and 1200×630 share card.

Crawl-blocked, still linkable.

Path Why
/accounts/* One page per Stellar address, unbounded and unguessable, so there is no finite set for a crawler to walk. A link shared into a chat still previews correctly, because /accounts/[address] has its own generateMetadata and its own generated image.
/transactions/* One page per transaction hash, unbounded for the same reason. A shared link still previews correctly from the hash in the URL alone.
/*? Filter and search views — ?status=failed, ?contractId=…, ?q=…. They are the same page as the route they filter, not separate pages, and each one declares the bare route as its canonical.

app/robots.ts carries the reasoning inline, as a reason on each entry in CRAWL_EXCLUDED, so the next person to touch the policy can see why it is what it is instead of re-deriving it. Account pages are blocked in robots.txt but not noindexed: blocking a page in robots prevents a crawler from reading the noindex in the first place, and these pages are meant to be reachable from external links — a noindex would drop them from search results for exactly the people who linked them.

Share images are generated rather than stored: app/opengraph-image.tsx draws the site card, and app/accounts/[address]/opengraph-image.tsx draws one per account from the address in the URL alone. The account card deliberately never waits on GraphQL — a preview bot does not retry, so a card that depends on the indexer being up is a card that is missing exactly when it is most often shared.

Live updates

LiveFeed on the home page and the Live Activity section on an account page stream over GraphQL subscriptions rather than polling. lib/subscriptions.ts speaks the graphql-transport-ws protocol directly over a WebSocket — the same wire format graphql-ws servers implement, so it interoperates with the backend's Subscription type without adding a client-bundle dependency.

One socket is shared by the whole tab: two live panels on one page do not mean two connections and two independent opinions about whether the server is up.

Connection state is visible, not silent. The badge reads LIVE, CONNECTING, RECONNECTING, or POLLING, so a feed that has stopped moving because the server went away never keeps claiming to be live. Drops reconnect with exponential backoff and full jitter — the jitter matters more than the growth, since without it every open tab reconnects in lockstep the moment the server returns — and active subscriptions are replayed on the new socket, because the server has no memory of the previous one.

After the retry budget is exhausted, or where WebSocket is unavailable at all (a restrictive proxy, a server-rendered pass), the feed falls back to the original 30-second polling rather than going dead.

Watching accounts

/watch is where several accounts are followed at once, and it is the reason the shared socket above is shared. A watch list of eight accounts means one WebSocket carrying eight multiplexed subscriptions, not eight connections — so the connection count does not grow with the list, and the server has one view of whether the tab is connected rather than eight disagreeing ones. Adding a watch sends one more subscription frame on the open socket; it does not reconnect.

Each watch carries its own filter (lib/filterModel.ts), defaulting to payments so a quiet watch stays quiet. Filters are part of the watch, not the page, which is what lets one watch alert on large payments while another takes everything.

New activity on a watch surfaces in two places. The ActivityBell in the navbar is the always-available one: it survives navigation and reloads, marks the count read when opened, and links through to the account. A system notification is raised only when the tab is actually in the background, and only after someone opts in by clicking — permission is never requested on page load. See docs/PUSH_NOTIFICATIONS.md for the VAPID key and the backend endpoint that delivery still requires.

/watch is deliberately excluded from the sitemap (WATCHES.indexable = false) so a per-person list never ends up indexed, while staying linked from the navbar for people who want it.

Wallet-connected Registry page

/registry lets a user register a contract on the Lumina Registry directly from the browser, via @creit.tech/stellar-wallets-kit (Freighter, xBull, Albedo, Rabet, and Lobstr are wired in — not locked to a single wallet). Connecting opens a wallet-picker modal; registering builds a register_contract invocation with @stellar/stellar-sdk, simulates it, has the chosen wallet sign it, and submits it via Soroban RPC. The "Recently Registered" list reads the live registry the same way — no wallet needed for that part.

Structure

app/            App Router pages
components/     React components
lib/            GraphQL client + formatters

See docs/ARCHITECTURE.md for the server/client component split, the data-fetching patterns and the wallet/transaction pipeline.

License

MIT

Contributing

See CONTRIBUTING.md for setup, where new code goes, the staged-file checks, and what a pull request is expected to contain.

About

Lumina — Next.js explorer frontend for the Stellar event indexer

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages