Skip to content

feat(release): ship QIP-55 explorer and default v3 testnet - #190

Merged
moscowchill merged 44 commits into
mainfrom
dev
Sep 21, 2026
Merged

moscowchill merged 44 commits into
mainfrom
dev

Conversation

@moscowchill

Copy link
Copy Markdown
Contributor

Summary

  • Integrate the QIP-55 64-byte address port with the current dev branch.
  • Make QRL Testnet v3 (Private) the default explorer at zondscan.com, remove the retired v2 network entry, and retain the existing v3 alias through deployment routing.
  • Qualify the connected provider's chain and genesis immediately before each contract-write request, and include the expected chain ID in the transaction.
  • Pin Connect 5.0.0 for wallet integration and the hosted dApp example.
  • Preserve the port's receipt accounting, pending transaction, contract verification provenance, and signer-bound explanation authorization changes.
  • Align CI with each Go module's declared toolchain and update browser fixtures for QIP-55 addresses and receipt-derived fees.

Validation

  • Frontend: clean npm install, zero dependency audit findings, lint with zero errors and 14 existing warnings, typecheck, 575 tests, API documentation drift check, and production build passed.
  • Production browser suite: 24 tests passed across desktop and phone layouts, appearance modes, preferences, currencies, translations, address highlighting, and v3 network navigation.
  • Backend and syncer: all unit tests, formatting, vet, and builds passed.
  • Receipt maintenance: 36 tests passed; 10 MongoDB integration cases skipped without an integration database.
  • A real desktop message signature verified through the published Connect 5.0.0 package; wrong signer, legacy-width signer, and tampered-message controls were rejected.

Release notes

This PR prepares source for the coordinated v3 cutover. Deployment must retain the v3 database and the existing v3 explorer alias, configure the canonical explorer origin for signer-bound challenges, and qualify the faucet against the selected chain. The current deployed v3 archive has no Git metadata and differs from this candidate in receipt handling and dependency files, so production source equivalence is not claimed.

The production browser suite passed without the transient router-initialization error observed under the development server. Live deployment and v2 retirement are coordinated separately.

moscowchill and others added 30 commits August 5, 2026 12:14
GET and HEAD responses now send Access-Control-Allow-Origin: * so the
read API is usable from any browser origin. POST endpoints keep the
explorer-origin allowlist (plus browser-extension schemes and the
CORS_ALLOW_ORIGINS env override): /contract/explain is Anthropic-billed
and /contract/verify plus /contract/call are abuse-sensitive, so
third-party pages must not fire them from visitors' browsers. Vary:
Origin is set unconditionally so shared caches never serve an ACAO-less
copy to a CORS request.

Claude-Session: https://claude.ai/code/session_015ADBggzM8n5R9gqApZodPv
Hand-written OpenAPI 3.1 spec (app/lib/openapi.json) covering all 43
backend routes plus /search and /faucet/claim, served at /openapi.json
with open CORS. The /api-explorer page is rebuilt from the spec: every
endpoint server-rendered and crawlable (the old accordions unmounted
all content), anchors and a sticky tag nav, parameter tables, curl
examples with copy buttons, live try-it links for GETs, accurate rate
limit and CORS documentation, and a client-side filter. Documented
pagination defaults, cache TTLs, and encoding conventions were verified
against the Go handlers. scripts/check-api-docs-drift.mjs diffs the
spec against the Go route registrations and runs in CI, so the docs can
no longer drift silently (7 endpoints were missing from the old page).

Claude-Session: https://claude.ai/code/session_015ADBggzM8n5R9gqApZodPv
…rite

New /learn section: 12 plain-English guides across three paths (QRL 2.0
basics, using the explorer, hands-on tutorials for MyQRLWallet,
QuantaPool staking, QuantaSwap atomic swaps, QRC20 creation, and
contract deploy plus verify). Every product claim was grounded in the
corresponding source repo and adversarially fact-checked. Shared
article components emit TechArticle and BreadcrumbList JSON-LD; the
index, sidebar group, footer links, sitemap rows, and layout hasPart
entries are driven by a single article registry.

The FAQ is rewritten as a pure server component with native
details/summary so answers are finally in the served HTML (the old
headlessui accordion unmounted them), gains FAQPage JSON-LD, and
corrects a factual error: QRL 2.0 signatures are ML-DSA-87, the old
copy said SPHINCS+. The faucet button label and address error copy now
use Quanta unit phrasing.

Claude-Session: https://claude.ai/code/session_015ADBggzM8n5R9gqApZodPv
Learn section, OpenAPI-driven API docs, server-rendered FAQ, method-split CORS
…racing

Restore private internal transaction tracing and remove the duplicate transaction confirmation badge.
Restore the on-box syncer lag check and alert when its height query becomes unusable.
Adds a visual order book page backed by a new read-only market-data
endpoint. No request parameter can change the venue, symbol, or upstream:
the QRLUSDT spot market is fixed in code.

Backend:
- marketdata: MEXC client fetching depth, recent trades, and the 24h
  ticker in parallel, with a bounded HTTP client (per-phase timeouts,
  per-endpoint body caps, redirects rejected so the fixed upstream cannot
  redirect elsewhere). All decimals are validated and normalized; trades
  with a null id get a deterministic sha256 synthetic id, which is the
  live path since MEXC's public /trades returns null ids.
- GET /market/orderbook behind a 3s TTL. Upstream failures are cached as
  values for the same window so a MEXC 429 or 5xx cannot turn sequential
  visitors into a new three-request burst; failures respond 502 with
  Retry-After.
- cache: add GetOrComputeContext, the cancelable-wait counterpart of
  GetOrCompute. Canceling one waiter never cancels the shared
  computation.

Frontend:
- /orderbook renders grouped depth, recent executions, and a play-by-play
  feed over an arena visualization driven by the live snapshot.
- Sidebar entry, sitemap entry, OpenAPI spec, and schema.org WebPage
  node for the new route.
- Move the JSON-LD script out of <head> into <body>. Next's metadata
  pipeline owns <head>, and a manual head child in that stream can cause
  a hydration mismatch in development. Search engines accept structured
  data in either section.

Verified against the live MEXC feed end to end: go test, jest 148/148,
tsc --noEmit, eslint, and next build all pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rolls up collected public executions into buy/sell volume per trade-size
band, a net-inflow series over a selectable window, and five days of
daily net inflow.

Built venue-first rather than around one exchange, since more venues are
planned. Adding one means implementing marketdata.Venue and appending it
in DefaultRegistry; storage, API, and UI need no further wiring.

Backend:
- marketdata.Venue + Registry: a venue owns its id, symbol, quote asset,
  size thresholds, and fetchers. MEXC becomes the first implementation.
- collector package polls each venue's tape every 60s and upserts it.
  The poll interval is far shorter than the tape's span (~8h for
  QRLUSDT), so a missed poll, a restart, or an hours-long outage loses no
  trades. Rows are keyed <venueId>:<tradeId>, making collection
  idempotent and safe to run from more than one process.
- Collection runs in backendAPI because the read API only fetches when a
  visitor arrives: an endpoint computing a 24h rollup on demand would
  have nothing to compute from on a quiet site.
- GET /market/fundflow?venue=&window=, cached 30s per venue and window.
  Windows are an allowlist; unknown venue or window is a 400 naming the
  accepted values.
- marketTrades is created eagerly with its indexes and a 45-day TTL.
  createIndexes skips collections that do not exist yet, so a collection
  first written at runtime would otherwise stay unindexed until some
  later restart happened to find it populated.
- Size bands are ZondScan's own classification by quote notional, sized
  from the observed tape. The API reports the thresholds so the UI can
  state them, and a row keeps the band it was classified into at write
  time, so retuning the constants cannot redraw history.

Frontend:
- Diverging column charts: 24px cap, 4px rounded data-end square at the
  baseline, hairline grid, selective direct labels, hover and focus
  tooltips, and an sr-only table twin.
- The buy/sell green and red pair measures dE 6.5 for deuteranopia,
  which is legal only with secondary encoding, so sign is carried three
  further ways: direction from the zero baseline, an explicit +/- on
  every value, and text labels. The pair is kept because the rest of the
  page already binds those hues to buy and sell.
- Coverage is surfaced rather than hidden. Venues expose no historical
  trade tape, so history accumulates forward from first collection and a
  partly covered window says so instead of reading as an absence of flow.

Verified against the live MEXC feed: a real DNS timeout mid-run was
logged, skipped, and recovered on the next tick with no trades lost.
go vet, 8 Go packages, jest 175/175, tsc, eslint, next build, and the
API docs drift check all pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A UI review found the page had accumulated seams from being built in
layers. Fixes the drift and one real rendering bug.

Rendering bug: the two fund-flow charts shared a fixed 720x220 viewBox
scaled by w-full, so their height AND their label type size were both
derived from container width. The per-step chart sits in a half-width
grid column and the daily chart spans the card, so a matched pair
rendered at different sizes. Both now render in pixel space against the
measured width, giving a fixed 220px height and identical type at any
width.

Axis labels now shift to an edge anchor only when the centred text would
actually spill outside the plot. Anchoring by index detached the first
and last labels from their columns whenever bands were wide, which was
every low-count chart including the five daily bars.

Consistency with the rest of the site:
- Vertical rhythm moves to mb-6/mt-6 between stacked sections, matching
  gas, validators, and richlist.
- The arena gets the same bordered header strip and card padding as
  every other block on the page, so it stops reading as a separate
  object glued on top.
- The fund-flow window selector reuses the arena's pill chrome. Two
  controls doing the same job had nothing in common.
- Table headers adopt the site's header type (11px, tracking-[0.12em],
  font-medium) in place of a 10px tracking-wider style invented for this
  page. The values are copied rather than the table-header utility
  applied, since that utility forces px-6 and text-left and would break
  these dense right-aligned tables.
- Headline figures use the mono/tabular treatment the stat cards use,
  and both error-card titles settle on text-xl.
- Motion toggle drops to py-1.5 to match the buttons beside it.
- The arena background image had sizes="70vw" while rendering full
  width at every breakpoint, so it was served under-resolved.

jest 178/178, tsc, eslint, and next build all pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The /gas charts have never had data. UpdateGasHistory scanned blocks
oldest-first, capped at 5000, starting from an empty collection's
highwater of -1. On a chain at block ~202k with 60s slots, blocks 0-4999
are around 140 days old, so the retention sweep at the end of the same
run deleted every row the run had just written. The highwater returned
to -1 and the next run repeated the identical batch, twice an hour,
forever.

Scanning newest-first keeps written rows inside the retention window,
which is what lets the highwater advance. The scan limit is raised to
cover the 7-day retention horizon at the current 60s slot time so a cold
start fills the whole 7d view in one pass.

Adds integration coverage for the deadlock. The regression test seeds
more ancient blocks than one scan can hold, which is the condition the
bug needs: a first version seeded only 120 blocks and passed against the
broken code, because a single oldest-first scan still reached the recent
ones. The seed size now derives from the scan limit so the test stays
honest if that constant is retuned. A second test asserts the sweep
still removes rows past retention, so the fix cannot be faked by never
deleting.

Verified both directions against a live MongoDB: the regression test
fails on the old ordering with "first run stored nothing; every written
row was swept as too old", and passes on the fix with the highwater
advancing and holding across runs.

Note: the chain is currently idle, so the chart will render a truthful
flat line at zero rather than a message about missing history.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reported from the live order book: the grouped depth ladder showed
"13.93" and "92.06" beside "1,152" and "13,454", so the column read as
broken.

formatQrlQuantity picked its precision per value (value < 100 ? 2 : 0),
so whether a cell grew decimals depended on that cell alone. Any bucket
that happened to fall under 100 QRL sprouted two decimals while its
neighbours stayed whole. Precision now follows the column's purpose
rather than the individual value.

Depth ladder and other aggregates round to whole QRL: a bucket is a sum,
and its fraction is noise beside the total. Below 1 QRL the fraction is
the only information present, so it is kept, and dust that would round
to a misleading "0" renders as "<0.01" instead.

The trade tape gets its own formatter at a fixed two decimals. Rounding
an individual execution would misstate it, since 7.75 QRL is not 8, and
fixing the precision keeps that column one shape at every magnitude too.

The live book happens to hold no sub-100 buckets right now, so the tests
pin the exact values from the report rather than relying on a snapshot
that no longer reproduces it.

jest 186/186, tsc, eslint, next build all pass.

Reported-by: ChillerID
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two things from the same report.

Depth. The ladder was not truncating a deep book, the backend was only
fetching a shallow one: 100 levels a side yielded about 10 grouped
buckets per side at the 0.01 grouping, fewer than the 12 rows the ladder
already displayed. QRLUSDT actually rests around 340 bids and 1025 asks,
so the fetch moves to 500 a side, which covers the entire bid book and
the part of the ask book anyone reads. The far tail is parasitic (a bid
near zero, asks into the millions) but the ladder walks outward from the
touch, so it only appears if a reader asks for enough rows to reach it.

With real depth behind it the ladder gains a Rows control (12/25/50) and
a 0.1 grouping. Both were pointless before: at 100 levels there was
nothing further to reveal, and 0.1 collapsed each side to two rows.

The ladder's depth is now built separately from the arena's levels. They
shared one constant, so more rows would have meant more markers on a
field that crowds long before a table does.

Select popups. A native option list is drawn by the OS on its own light
surface and does not inherit the dark theme the closed control wears, so
the site's near-white text was landing on a white popup and the list was
close to unreadable. Options now carry their own background and colour.
This is site-wide rather than per page: the converter, verify-contract,
and fund-flow selects had the same problem.

jest 187/187, tsc, eslint, next build, go vet, go test, and the API docs
drift check all pass.

Reported-by: ChillerID
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The arena only ever animated one element. Every marker positioned itself
with the SVG transform attribute, which CSS cannot transition, so on each
3s poll the whole field jumped. The ball moved smoothly because it alone
used a style transform. Marker size had the same problem: radius and
sprite dimensions were attributes, so they snapped too.

Markers now carry position and scale in one CSS transform and glide on
the same 900ms easing as the ball. Geometry is drawn at a base size and
scaled, so both move together.

transform-origin is pinned to 0 0 on everything that scales. SVG defaults
to transform-box: view-box with a 50% 50% origin that resolves to the
centre of the viewBox rather than the element, which translate-only
transforms never notice. Left at the default, markers would grow towards
mid-field and the ripple would fly across the pitch.

Three nested groups so the animations compose rather than overwrite: the
outer owns the position transform, the middle fades new buckets in, and
the inner runs an idle bob. The bob matters most here, because a quiet
book left the field motionless between polls. Its timing is seeded from
the bucket price rather than the array index: a bucket keeps its React
key across polls, so its animation keeps running, and rewriting the
delay mid-flight whenever its rank shifted would jump it a frame.

Executed plays now send a ripple from the ball, coloured by direction and
neutral for a flat play, matching the play feed's existing convention.

Also corrects aria-pressed on the Motion toggle, which was inverted
against its own label and against every sibling toggle on the page. It
predates this change but was inconsequential until motion became real.

Reviewed with headless repros in Chrome 151 and Firefox 152 measuring
getBoundingClientRect across the exact nesting used here, confirming both
the transform-origin handling and that px in these transforms resolves to
SVG user-space units. WebKit is unverified. Compositor layer promotion
for the idle loop is unprofiled and is the open question if the field
ever feels heavy.

jest 187/187, tsc, eslint, next build all pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(learn): link QRL Ecosystem Index and Quantum Readiness Index

Add a Further reading section to the /learn index with cards for
qrlecosystem.com (community project directory) and qrindex.org
(independent blockchain quantum-readiness index), plus inline
references from the what-is-qrl-2 and why-post-quantum articles.

Claude-Session: https://claude.ai/code/session_013DTfZj6whJh28h4UvjfH8d

* refactor(learn): address code-review findings on external resources

Extract a shared LearnCard used by both the article grid and the new
Further reading grid, mark external cards with the standard
ArrowTopRightOnSquareIcon plus sr-only new-tab text, derive the domain
caption from the href instead of hand-maintaining it, make the section
blurb count-independent, and soften the volatile qrindex.org tier
claims to a neutral description.

Claude-Session: https://claude.ai/code/session_013DTfZj6whJh28h4UvjfH8d
New /learn article on validity proofs, why the QRL 2.0 virtual machine verifies hash-based STARKs, the QuantaStark measurements on local 64-byte networks and the layer 2 arithmetic derived from them; matching FAQ entry; wider basics blurb. Review findings applied in the second commit.

https://claude.ai/code/session_011eAxRYrxF7ruiQDme9tYHx
Adds /staking-calculator, an estimator that runs the qrysm base-reward
formula (sqrt(total stake) issuance, Altair duty weights, integer square
root) against live validator stats and circulating supply. Inputs: own
validators, network validators, uptime, network participation, priority
fee, supply, price. Outputs gross/net/effective APR, real yield after
issuance dilution, duty breakdown, payout schedule, and the dilution curve
against network size.

Real yield is share-of-supply growth, so it reads 0% at a 100% staking
ratio by construction; the page explains that and also reports the
operator's share of new issuance and of the 105M-cap emission pool
(QIP-016 policy, not enforced by the testnet issuance formula yet).

Wires the page into the sidebar, footer, and sitemap. Unit tests pin the
consensus constants, the published ROI band, and the dilution cases.

Claude-Session: https://claude.ai/code/session_01DVxL6ZPjibv7uH2tj5nvsK
Preserve exact transaction amounts while keeping list columns readable.
feat(ui): add horizontal navigation and explorer preferences
fix(ui): expand phone navigation below the header
QRL Testnet v3 in the explorer network menu now opens v3.zondscan.com
instead of showing a disabled Upcoming badge. The network catalog carries
an href per network; the build's own network is selected with
NEXT_PUBLIC_EXPLORER_NETWORK (default testnet-v2) and marked active, other
live networks render as links in the desktop menu and the phone
navigation, and their badge reads Devnet in all four locales.

Claude-Session: https://claude.ai/code/session_01FNiKphhAvq1TjaQ1ESiEBP
moscowchill and others added 14 commits September 11, 2026 16:47
go-qrl reports a revert without return data as JSON-RPC error -32000
"execution reverted" and only uses code 3 when revert data is present.
Contract type detection accepted code 3 alone, so every contract without
ERC-165 (each plain ERC-20) turned the supportsInterface probe into a
retryable error, the block's companion processing failed, and the syncer
retried that block forever. Found on the 64-byte devnet at block 704.

The classifier now accepts both forms; state and transport failures stay
retryable. Tests cover the classifier table and the detection fall-through.

Claude-Session: https://claude.ai/code/session_01FNiKphhAvq1TjaQ1ESiEBP
The three-segment QIP-55 fingerprint wrapped the home page's latest
transactions onto two rows. Compact lists go back to the two-segment
form; detail pages keep the fingerprint through AddressFingerprint.

Claude-Session: https://claude.ai/code/session_01FNiKphhAvq1TjaQ1ESiEBP
…network

The header subtitle, the gas price tooltip and the pending transaction
page's chain id were fixed to QRL Testnet v2 (0x539). They now follow
NEXT_PUBLIC_EXPLORER_NETWORK, so the v3 explorer reads QRL Testnet v3
and reports chain 0x301825.

Claude-Session: https://claude.ai/code/session_01FNiKphhAvq1TjaQ1ESiEBP
QRL Testnet v3 in the explorer network menu now opens v3.zondscan.com
instead of showing a disabled Upcoming badge. The network catalog carries
an href per network; the build's own network is selected with
NEXT_PUBLIC_EXPLORER_NETWORK (default testnet-v2) and marked active, other
live networks render as links in the desktop menu and the phone
navigation, and their badge reads Devnet in all four locales.

Claude-Session: https://claude.ai/code/session_01FNiKphhAvq1TjaQ1ESiEBP
fix(ui): align explorer labels with the selected network
feat(explorer): release QIP-55 as the default testnet
@moscowchill
moscowchill merged commit 94f5db9 into main Sep 21, 2026
3 checks passed
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.

1 participant