7 Commits
Author SHA1 Message Date
grenadeandClaude Opus 5.5 21ea353942 feat: NEAR transaction and Intents account pages
ci / gate (push) Successful in 4m40s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m5s
deploy / deploy-web (push) Successful in 10s
deploy / deploy-api (push) Successful in 19s
Adds /quantus/near/tx/<hash> and /quantus/near/intents/<account>, the
last two parts of #44.

A transaction is assembled from our own nodes where possible. The node
tracking the signer's shard returns the transaction. Each receipt it
lacks is fetched from the node tracking that receipt's receiver, using
EXPERIMENTAL_receipt and a light client proof. A public node is used
only when ours cannot serve all of it. The page says which source it
used. Once a transaction is complete and every block it touched is
final, it is stored in near_tx (migration 0020) as raw JSON and decoded
on every read, so decoder fixes apply to stored transactions too.

The decoder in blackbeard-core::near_tx orders the receipt tree,
decodes function-call arguments, parses EVENT_JSON lines into amounts
tied to their tokens, and reads the BRIDGED_FROM, WITHDRAW_TO and refund
memos. It is tested against the bridge mint piPTXyoq and the failed
swap with refund E4ysnVht.

The account page shows balances in intents.near, read live from
frankie, the account's intent signing keys, and the QTC activity this
observer indexes. It says that activity is not the account's whole
history.

The bridge, fills and swap tables now link to these pages instead of
nearblocks. Both pages still link out to nearblocks.

Closes #44

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tyzzRVjH9AdwGtYwsH5VC
2026-10-07 12:22:25 +03:00
grenadeandClaude Opus 5.5 b85e74f1ca feat: index QTC's Ref DCL pool — its swaps, its ladder, and how much we see
ci / gate (push) Successful in 5m7s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m3s
deploy / deploy-api (push) Successful in 42s
deploy / deploy-web (push) Successful in 12s
On 2026-10-06 the team bridged 150,000 QTC to NEAR and put almost all of it in
a Ref Finance DCL pool, not into solver balances, so the market page saw none
of it. The pool contract (dclv2.ref-labs.near) is on shard 8 and our node
tracks only intents.near's shard 7, so its swap events are never in anything
the scan reads.

What the scan does see is the relay transaction: the Verifier's outcome logs
an ft_withdraw from the solver to the pool. Those are queued (ref_pending,
migration 0012, persisted because the process restarts) and fetched whole
from the public endpoint 20 blocks later, once the pool's receipt has run;
its dcl.ref swaps are recorded with the solver as trader, since the pool
itself records intents.near. Swaps made by calling the pool directly are
missed — and the pool's own cumulative counters say by how much, so the page
reports coverage instead of a disclaimer: today 99.75% of QTC bought out and
100% of QTC sold in, the gap one 0.000498 QTC buy.

Pool state comes from view calls once a minute: price at the current point,
holdings, whitelist, counters, and the liquidity ladder from
blackbeard_core::dcl — discrete sums over points, with the current point split
exactly by liquidity_x; tested against the pool's own totals (QTC to 0.01, and
USDC short by exactly the LP fees total_x also holds). Pools are found by
paging list_pools.

It is its own market, /:chain/market/ref-dcl, never summed with NEAR Intents
trades: a user's buy is a solver fill there and, after it, the solver's
restock here. The pools' whitelists join the inventory poll, which now finds
solver-ref-2.near holding 0.288 QTC. Migration 0013 pulls QTC's scan back to
NEAR 218,700,000, before the pool, so the swaps production already passed are
found on the re-read.

Refs #37

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 09:50:34 +03:00
grenadeandClaude Opus 5.5 40bc2f67d1 fix(core): one swap per taker leg, and solver hops are never trades
ci / gate (push) Successful in 4m36s
deploy / validate (push) Skipped
deploy / build (push) Successful in 8m11s
deploy / deploy-web (push) Successful in 5s
deploy / deploy-api (push) Successful in 18s
A 3,000-block survey of intents.near from our own node (350 outcomes, 1,026
legs) showed the referral leg is the user — per-quote implicit accounts, none
on a solver — and that its own diff mirrors the rest of the outcome to within
the protocol fee in all 576 amounts checked. So each taker leg is now its own
trade, priced from what that user gave and got.

It also showed the pooling rule counted solver routing as trading: in 45% of
outcomes with a taker the solvers traded through a pair no taker touched,
usually USDT on Ethereum, and each such leg on the tracked asset became a
phantom trade against a token the user never saw. Two takers on one pair —
an account round-tripping wNEAR, in the case captured — were merged into one
trade with a nonsense price. Now an outcome with any taker yields only the
takers' trades; only an outcome with no referral at all (solvers trading with
each other) is pooled, as `kind: unattributed`, with no side.

`kind` is stored (migration 0010, no re-scan: production held no QTC rows) and
on the wire, and the panel says how many of a figure's trades were users'.
Tests run on the three captured outcomes. Checked live against near.internal
with wNEAR: 18 trades from 18 outcomes, every taker an implicit account, and
no counter that was only a hop.

Closes #30

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-03 21:24:53 +03:00
grenadeandClaude Opus 5.5 8128201ad9 feat(api): index swaps settled by NEAR Intents, off by default
The price of a token on NEAR Intents is the ratio inside a trade that
`intents.near` actually executed; there is no pool, oracle or stored price,
and a 1Click quote is an offer. This reads `execute_intents` transactions on
the contract's shard, keeps the `token_diff` legs whose receipt succeeded and
whose intent is in `intents_executed`, and records one trade per asset/counter
pair per outcome, counting each unit once whichever side the taker was on.

- core: event parsing, trade building, shard lookup, price and VWAP, tested
  against the logs of mainnet tx 85uy9uCF (USDT on Tron for ZEC).
- data: a NEAR client over the existing RpcClient, live then archival;
  migration 0009 (intents_trade, intents_scan, near_token).
- api: a contiguous-cursor scan and a supply/listing poll per chain, and
  GET /v1/chains/{chain}/intents/{swaps,price}. Prices are in counter-token
  units; no USD until a stablecoin is named by contract.

Gated on `[chains.intents]`, which the config template leaves commented out:
FastNEAR's free RPC sustains about two requests a second against ~1.7 needed
to follow the tip, and neardata allows 30 blocks a minute. It waits for our
own node. A test renders the template and fails if the block is enabled.

Verified end to end against mainnet with ZEC configured: the trade at NEAR
218,261,895 was recorded and served by both routes.

Refs #27

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-03 18:35:32 +03:00
grenadeandClaude Opus 5 2bbe8e4d5d feat: a vesting route — when 99.5% of the supply actually arrives
deploy / build (push) Successful in 9m23s
deploy / deploy-web (push) Successful in 7s
deploy / deploy-api (push) Successful in 16s
The network page said 5,669,940 QTC sits in the vesting pot and stopped there,
which is the largest economic fact about this chain rendered as a single
number. `/:chain/vesting` now carries the schedules, the release curve and the
evidence.

It is the only forward-looking page on the site and it is exact rather than
projected: the runtime vests nothing before a grant's cliff, the whole grant
from its end, and linearly between, so summing that across the schedules is
arithmetic on chain state. `blackbeard-core::vesting` restates those three
branches with tests, because until 2027 nothing on chain will contradict them —
covering the cliff, the mid-window, the `end` branch that makes the final
figure exactly `total` rather than `total` less a rounding crumb, the
`start < cliff` case that unlocks a chunk at once, and a grant large enough
that `total × elapsed` would wrap a `u128` if the product were not split.

Decoded by field name against the metadata, never by offset. The figures that
justified building this were read positionally out of 89 bytes and summed to
the pot exactly — good evidence the layout was guessed right, and exactly the
shortcut this repository forbids elsewhere. A captured mainnet entry now pins
the names, so a reordered struct fails the build rather than quietly paying the
wrong address.

It also retires a weak claim. "Held in the vesting pot" rested on an eight-byte
`PalletId` spelling `qvesting` and a `type PalletId` line read from a source
tree that may not be the deployed commit. `matches_pot` replaces that with
arithmetic: the grants sum to the pot's balance to within the existential
deposit, and if a grant is ended or the pot topped up the page says the two
have parted company rather than going on describing one as the other.

Measured: 48 grants, 5,669,940.000 QTC scheduled against 5,669,940.001 held,
nothing claimed, first unlock 2027-09-09, fully vested 2030-09-08.

`Field` becomes one component instead of four. Going to add a fourth copy found
that the three had already parted company — two rendered `note` as a visible
caption, the third passed it to `title` as a hover — so the shared one takes
both as separate props rather than silently changing a page. The same lesson
`isStandings` records, found the same way.

Closes #21

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 16:31:53 +03:00
grenadeandClaude Opus 5 edce856263 feat: decode extrinsics, and show them
deploy / build (push) Successful in 7m17s
deploy / deploy-web (push) Successful in 4s
deploy / deploy-api (push) Successful in 15s
An event is the chain's account of what happened; an extrinsic is the request
that caused it, and carries what no event does — who signed, what they called,
the nonce and tip, and whether it worked. A history built from events alone
omits every failed attempt and never names a submitter.

The same oracle does both. The metadata's extrinsic type carries four
parameters and the registry names all of them, including
`qp_dilithium_crypto::types::DilithiumSignatureScheme` — the part that looked
like it would need hand-written post-quantum knowledge and does not. The chain
describes its own signature scheme, so `decode_extrinsic` reads a Quantus
transaction without a line of code that knows Quantus exists.

Two facts about this chain's extrinsics, both now in CLAUDE.md. The first byte
is not the version: the top two bits are a type tag, and mainnet carries `0x84`
(signed, v4) and `0x05` (bare, v5) in the same block while the metadata declares
version 4 — check the byte against the metadata and you reject every timestamp
inherent on the chain. And a signature is 5.3 KiB, two orders of magnitude
larger than the call it authorises; it is decoded to find where the call begins
and then discarded, keeping only the scheme's name and the byte count.

Both halves of a block index in one pass, off calls already being made. Whether
a dispatch succeeded comes from the `ExtrinsicSuccess`/`ExtrinsicFailed` events
in that same decoded list — read, used, not stored.

The block page grows a table below the facts: call, signer, arguments, and the
signature's size. The account page merges extrinsics into the history it already
had, ordered by block with the request above the results it caused. `source` is
1 for an extrinsic and 0 for an event so that every part of the sort key
descends, which lets the paging cursor be a plain row comparison rather than a
mixed-direction one Postgres cannot express.

Nested calls are why this had to be generic. A `Utility::batch_all` carries whole
calls in its arguments, so a transfer's recipient can be three levels down — and
because the decoder collects account ids wherever they appear, that batched
transfer shows on the recipient's page tagged *received* even though they signed
nothing. Verified on mainnet 12,616: the batch renders both its transfers with
destinations and values, above the two `Balances::Transfer` events they produced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jp6a8EDar9ueEhAxzep4V5
2026-09-09 18:27:54 +03:00
grenadeandClaude Opus 5 eb26a7bb3c feat: decode events against the runtime's own metadata
A header says who mined a block, not what they were paid: the amount is
remaining supply over an emission divisor plus fees, quantized with dust
carried forward, so only the `MinerRewarded` event knows it. Reading it means
decoding SCALE against the registry of the runtime that produced the block, and
this chain's shapes differ from vanilla Substrate because of the post-quantum
signatures.

So the chain describes itself. `state_getMetadata` at a block hash executes
`Metadata_metadata` against the runtime WASM in that block's state; the v14
registry that comes back drives a decoder that knows nothing about mining,
rewards or transfers. `INDEXED_EVENTS` is the only place a name appears —
adding transfers or anything the chain grows next is one line and a query, not
a migration, a struct and a decoder.

Metadata is cached per spec_version, because a node that starts pruning would
otherwise make history undecodable. Blocks are tried against the tip's runtime
first, which costs no extra call; `decode_events` refuses a partial read, so a
block from before an upgrade fails loudly rather than decoding into plausible
nonsense, and that failure is what asks which runtime actually produced it.

`backfill` walks outward from the tip — to the tip first, then toward genesis —
because live indexing alone leaves holes an account page would show: a restart,
an RPC blip, a gap fill that ran before the metadata was cached. Progress is a
cursor rather than `max(height)`, since a block with no indexed event and a
block never read are otherwise the same answer.

Verified on mainnet: block 13160's preimage derives, through the same Poseidon2
the chain runs, to exactly the `miner` field of its own reward event — so the
header author and the payee join with no lookup table.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jp6a8EDar9ueEhAxzep4V5
2026-09-09 16:47:10 +03:00