Your Mainnet Season 1 on gno.land, unwrapped.
Paste a g1… address (or connect Adena) and get a recap of the season:
when you arrived, the realms you called, your swaps and gas, a season persona, achievements,
and a share card for X and Discord. It follows the chain live, so new activity shows up within seconds.
Note
Gno Wrapped is an independent community project, not an official gno.land product. It reads only public on-chain data.
- Season persona: one of eight, picked by rules checked in order, with the reason in your own numbers and your rank among addresses with the same persona.
- Achievements: eight to collect, from Early Arrival to Citizen (a GovDAO or GnoSwap governance vote).
- Next up: while the season is live, the achievements still in reach and how far along you are (e.g. 77 of 100 transactions, 2 of 3 days in a row).
- Clock: your transactions by hour of day in your own time zone, the busiest hour, and whether you're an Early Bird, an Afternoon or Evening Regular, or a Night Owl.
- Arrival and activity: your mainnet day and arrival order, a season timeline, transactions, percentile and success rate.
- Calendar: every day of the season and your longest streak; click a day for its first 100 transactions, linked to Gnoscan.
- Realms, GnoSwap and governance: your top realms and your place among each one's callers (#37 of 1,204, by first call); swaps by week, your favorite pair, positions, fees and rewards; votes, proposals and GNS delegations.
- Builder report: each realm you deployed, and how many other addresses called it.
- Story mode: the recap as full-screen cards, one at a time. Add
?storyto a recap link. - Sharing: a post for X and Discord, a 1200×630 preview card and a 1080×1920 image. Share links never carry your address (Privacy).
- Compare: two share links side by side, with neither address shown.
- Several wallets: up to five of your addresses added up on one page (
/s1/wallets/<a>,<b>): transactions, days, hours, realms, GnoSwap and gas combined, personas and achievements per wallet. - Since your last visit: what changed since you last opened your recap, kept in your browser only.
- Season page:
/season, the same recap for the whole chain. - Live, then final: a countdown to the season's end; after it, recaps are marked final.
- English and Korean: English by default, switched in the top bar.
Every chart reads out the day under the pointer or the arrow keys and has a Table view. How personas
and achievements are decided is on the site's /personas page and in batch/build.go
and batch/achievements.go.
Requires Docker.
cp .env.example .env # optional: every setting has a default
docker compose up -d --build ingest api web # live batch, api on :8080, web on :3000
docker compose logs -f ingest # follow the chainOpen http://localhost:3000. The first start fetches the whole season from the indexer (about 180 range queries, a few minutes, ~20 MB); after that only new blocks.
Set SHARE_BASE_URL in .env to the public origin used in share links and preview cards, then
rebuild the web image (docker compose up -d --build web).
Back up data/share-ids.json: share links depend on it, and it can't be regenerated.
docker compose run --rm batch stats # distributions for tuning persona and bot rules (read-only)
docker compose stop ingest && docker compose run --rm batch build # rebuild data/out from the cache
docker compose stop ingest && docker compose run --rm batch verify # check the cache against the chain, repair, rebuildfetch, verify, build and all take a lock on the data directory, so they refuse to run while
ingest is up. (ingest verifies the cache itself on every start.)
┌──────────── HTTP catch-up ────────────┐
tx-indexer ──────┤ ├──▶ batch (ingest) ──▶ data/out ──▶ api ──▶ web ──▶ browser
(gnoland-1) └──── GraphQL WebSocket subscription ────┘
- batch (Go) catches up with range queries, then follows new blocks over the tx-indexer's GraphQL subscriptions. It applies a block once all of its transactions have arrived, rewrites changed per-address JSON every couple of seconds, and refills any gap (dropped connection, incomplete block) with range queries. Users never hit the indexer.
- Every range is checked against its blocks' tx counts before it's cached (an incomplete answer is
retried, never stored). On start, and when a check every 10 minutes finds the cache's tx count
differing from the chain's
total_txs, the batch verifies every cached chunk and fetches again the ones that don't add up. - When the chain passes the season's last day, recaps are marked final and dated that day; earlier
seasons' output stays in
data/out/<season>/. - api (Go) serves the batch output read-only and reloads a file when the batch rewrites it.
- web (Next.js) renders the recap,
/personas,/season, share links and the preview card, reading only the api.
The indexer has no address index: any filter other than block height or transaction hash scans every transaction. The batch only ever queries by height range.
The site's /privacy page tells visitors the same in plain words, in English and Korean. It sets no
cookies and loads no third-party scripts; the browser talks only to the site itself.
All data is public on-chain data. Share links still never carry the address:
- Each address gets a random share ID (
/s1/s/<id>). IDs are random, not derived from the address, so the code being public doesn't let anyone map an ID back to an address. - Behind a share link the batch serves a redacted recap: no address in any form (the short
g1xxxx…xxxxform alone identifies an address), no transaction hash, no exact arrival order, and realm paths that contain the address are masked. There is no Clock (an hour-by-hour pattern gives away a time zone), no Next up, and no place among a realm's callers (only how many callers it has). - The several-wallets page has no share link; its own link lists the addresses, like a recap's. The calendar shows only active days per week (a day-by-day pattern can be matched against the chain) and has no day list (transaction hashes lead to the address), and Realm Pioneer doesn't say which realms. The share card and image never show the address either.
- Persona ranks are shown only on the owner's own recap. There is no public leaderboard.
Exact stats are still derived from public data, so a determined person could match a shared recap against the chain. Share links prevent casual exposure; they don't make a recap anonymous.
Environment variables for the batch, api and web, and the api's endpoints
docker compose reads .env. Outside compose, each service reads its own environment:
batch
| Env | Default | |
|---|---|---|
INDEXER_URL |
https://indexer.gno.land/graphql/query |
tx-indexer GraphQL endpoint (gnoland-1) |
INDEXER_WS_URL |
INDEXER_URL with https→wss |
GraphQL WebSocket endpoint (graphql-transport-ws) |
CHUNK_BLOCKS |
10000 |
blocks per range query |
REQUEST_DELAY |
3s |
minimum gap between range queries |
DATA_DIR |
../data |
raw cache, share IDs and output |
Any public tx-indexer for gnoland-1 works: the batch only asks for fields in the upstream schema.
api
| Env | Default | |
|---|---|---|
DATA_DIR |
../data/out |
batch output |
LISTEN_ADDR |
:8080 |
Endpoints: GET /v1/{season}/{address}, /v1/{season}/share/{id}, /v1/{season}/personas,
/v1/{season}/season, /v1/{season}/{address}/days/{date}, /v1/{season}, /healthz. Logs one line per request (docker compose logs -f api).
web
| Env | |
|---|---|
WRAPPED_API_URL |
api base URL (default http://localhost:8080; http://api:8080 in compose) |
NEXT_PUBLIC_SHARE_BASE_URL |
public origin for share links and og:image (build time) |
WRAPPED_FIXTURES, NEXT_PUBLIC_WRAPPED_FIXTURES |
1 serves the design fixtures (?fixture=whale) for development and tests |
Launch placeholders (share origin, hashtag, file names) are in web/src/lib/config.ts.
batch/ Go: indexer → raw cache → per-address JSON (live or one-off)
api/ Go: read-only HTTP API over the batch output
web/ Next.js: recap, /personas, share pages, preview card
data/ local output (git-ignored)
# Go (batch/ and api/)
gofmt -l . && go vet ./... && go test ./...
# web/
npm ci
npm test # display rules, fixtures, and every address in data/out if present
npm run typecheck && npm run lint
npm run e2e # Playwright with system Chrome; starts the api and a dev server
docker compose --profile dev up web-dev # dev server with hot reload on :3001With WRAPPED_FIXTURES=1, http://localhost:3001/s1/g1wrappedsampleqqqqqqqqqqqqqqqqqqqqqqqqq?fixture=full
renders a full recap without any indexer data.
- Read CONTRIBUTING.md before opening a pull request.
- Found a bug or have an idea? Open an issue.
MIT. Third-party assets listed under Credits keep their own licenses.
- Fonts: Archivo, JetBrains Mono, Silkscreen (SIL Open Font License), via Fontsource.
- Adena icon from onbloc/adena-wallet, Gnoscan mark from onbloc/gnoscan, GnoSwap mark from gnoswap-labs/gnoswap-interface (all GPL-3.0).
- Favicon and gno.land mark from gnolang/gno (GNO Network General Public License).
- X, Discord and GitHub icons from Simple Icons (CC0).
- Data from the gno.land tx-indexer; explorer links to Gnoscan.






