156 Commits
Author SHA1 Message Date
grenadeandClaude Opus 5.5 63d8d1db84 chore(infra): Follow the DC to office host moves
deploy / build (push) Successful in 11m41s
deploy / deploy-web (push) Successful in 5s
deploy / validate (push) Skipped
deploy / deploy-api (push) Successful in 16s
magrathea, caveman, nikola and frootmig moved from kosherinata (DC) to
hanzalova (office) on 2026-11-01 and were renamed accordingly:

  magrathea.hanzalova.internal  10.6.0.194  (postgres primary)
  caveman.hanzalova.internal    10.6.0.186  (minio, gitea)
  nikola.hanzalova.internal     10.6.0.173
  frootmig.hanzalova.internal   10.6.0.131

The old *.kosherinata.internal names remain as transitional DNS aliases
and will be removed once deploys use the new names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TzeoVkZAgsEiJaTpxRZ42Q
2026-11-02 11:22:40 +02:00
grenadeandClaude Opus 5.5 1b17abc1cb feat(web): put the pool swap price chart first on the NEAR Intents market page
ci / gate (push) Successful in 4m59s
deploy / validate (push) Skipped
deploy / build (push) Successful in 1m18s
deploy / deploy-api (push) Skipped
deploy / deploy-web (push) Successful in 4s
1Click fills carry no price, so the Ref pool's swap price is the
price this page has, and it was sixth down the page. It now sits
directly under the breadcrumb.

At the top, waiting for the market list to confirm a Ref market before
drawing it would drop the chart in above everything a moment after load
and push the page down. The flag is now unknown until the list answers:
the chart is drawn meanwhile and removed only if there is no Ref market.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tyzzRVjH9AdwGtYwsH5VC
2026-10-14 17:37:47 +03:00
grenadeandClaude Opus 5.5 1e2a9052fb fix: an unreadable or undescribable height no longer stops the reorg repair
ci / gate (push) Successful in 4m41s
deploy / validate (push) Skipped
deploy / build (push) Successful in 11m35s
deploy / deploy-api (push) Successful in 17s
deploy / deploy-web (push) Successful in 4s
Two gaps in 739a2f5's backfill:

- An unreadable height returned before clearing other forks' rows, and was
  claimed anyway, so a losing block's events survived whenever the winner's
  could not be read; extrinsics likewise whenever the body read failed. Rows
  naming another block now go as soon as the canonical hash is known
  (`Others::KnownOnly`). Legacy rows with no hash go only once a replacement
  is in hand.
- A stale block row whose replacement could not be described (no header, no
  author digest) made the backfill `break` at that height on every pass, for
  good. `replace_block` now tells `Transient` from `Permanent`; the second is
  logged and stepped over.

Refs #52

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKabPYLTYP2G5s7ph7oCpo
2026-10-14 08:54:49 +03:00
grenadeandClaude Opus 5.5 11e092e342 docs: the production database is reachable from roosta over mTLS
deploy / validate (push) Skipped
deploy / build (push) Successful in 1m19s
deploy / deploy-api (push) Skipped
deploy / deploy-web (push) Successful in 4s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKabPYLTYP2G5s7ph7oCpo
2026-10-14 08:36:48 +03:00
grenadeandClaude Opus 5.5 739a2f5345 fix: a reorged height takes the winner's block and events, not the loser's
ci / gate (push) Successful in 5m41s
deploy / validate (push) Skipped
deploy / build (push) Successful in 10m12s
deploy / deploy-web (push) Successful in 5s
deploy / deploy-api (push) Successful in 22s
`chain_subscribeNewHeads` reports the best head, so a fork that wins at h
arrives as its h+1 and the replacement at h is never delivered. Ingest
checked heights for gaps but never parents for continuity, so h kept the
losing block's row, and `chain_event`, upserted by index, kept its
reward. Mainnet 61041 credited baba-gorchitsa 0.31 QTC the chain paid
QUANPOOL: its "mined" read 14.97 against a balance of 14.68 that can
only ever rise.

- `record` reads the header at the hash it resolved, then compares its
  parent with the row beneath it and walks a fork back (`repair_fork`),
  re-recording each replaced block and re-reading its events.
- Event and extrinsic rows carry `block_hash` (0024); a height is cleared
  of rows from any other block before it is written.
- The backfill stays `SETTLE_DEPTH` behind the tip and re-records any
  block row that is not canonical. 0024 collapses its cursor so all
  history is re-checked.
- `RollingWindow::push` replaces a block at a height it already holds
  instead of counting both forks.

Closes #52

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKabPYLTYP2G5s7ph7oCpo
2026-10-14 08:29:07 +03:00
grenadeandClaude Opus 5.5 c36d08b6cd feat(sponsor): on, as blackbeard-observer.near, 30 bps to itself, 300 tx a day
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m46s
deploy / deploy-api (push) Successful in 42s
deploy / deploy-web (push) Successful in 5s
blackbeard-observer.near is registered (tx At7zrH8TdYbcAmPtbjFM3RnNvSxjAZEvpJYpuvVYmir3).
Its only access key is an ML-DSA-65 key generated on bob as blackbeard;
only the public half left the host. The registrar's temporary ed25519 key
was deleted in the same transaction that added it (tx
44Ex8w8gqBUpC63pddvhR6KyV2E6PtzszZ9ngprosWoF). The operator funds the
account.

Terms, as the operator set them on blackbeard/wallet#81:
- the swap fee is 30 bps of a pool swap's input, paid to the sponsor
  account itself;
- each created account gets 0.0025 NEAR for its storage;
- at most 300 transactions a UTC day, about 1 NEAR of gas at the worst.

Refs #51

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUx7SPonBnB3ofU4mH41WT
2026-10-09 18:36:04 +03:00
grenadeandClaude Opus 5.5 0b8fb054d2 fix(sponsor): a creation deposit that covers the account's storage
0.002 NEAR was below what a sponsored account needs: bbspike72.near
measures 182 bytes, and the longer b<…>.blackbeard-observer.near name
adds 28, about 0.0021 NEAR at 1e19 yoctoNEAR a byte. Creation would have
failed for lack of balance to cover state. The example now gives 0.0025.

Refs #51

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUx7SPonBnB3ofU4mH41WT
2026-10-09 18:09:10 +03:00
grenadeandClaude Opus 5.5 afa2845edc docs: shard 13's instance is synced, through the 24569 forward
deploy / build (push) Successful in 10m3s
deploy / deploy-api (push) Successful in 24s
deploy / deploy-web (push) Successful in 8s
deploy / validate (push) Failing after 11m31s
Refs blackbeard/wallet#73

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUx7SPonBnB3ofU4mH41WT
2026-10-09 14:19:56 +03:00
grenadeandClaude Opus 5.5 003a13e4ce feat: the NEAR sponsor for blackbeard wallets, off until configured
blackbeard-observer.near will create each blackbeard wallet's NEAR
account and relay its legs as NEP-366 meta-transactions, paying the gas
(blackbeard/wallet#81). This is that sponsor, built and off by default.

- blackbeard-core::sponsor judges a signed delegate action. It is decoded
  and verified by wallet-near, the code the wallet signs with, pinned by
  rev.
  - The sender must be the sponsored account of the delegate's own key.
  - Only the wallet's calls are relayed (intents.near ft_withdraw and
    mt_transfer; wrap.near ft_transfer_call, near_deposit and
    storage_deposit), each with a gas and deposit cap.
  - A pool swap to Ref DCL must pay the fee, at least ceil(amount × bps),
    in its input token to the fee account, in the same action.
  - Seven tests cover forged, misaddressed, over-limit and fee-less
    delegates.
- blackbeard-data::sponsor submits to gargravarr first, the node tracking
  the sponsor's shard, and returns at INCLUDED_FINAL.
- blackbeard-api::sponsor holds the key, one nonce counter, per-address,
  per-sender and daily limits. Routes:
  - GET /v1/near/sponsor: the terms;
  - POST /v1/near/accounts: create the account for a key, or find it;
  - POST /v1/near/relay: relay a signed delegate action.
  Without [near_sponsor] in the config, the terms say enabled: false and
  the POSTs answer 503. A configured key that will not load stops the
  daemon.
- blackbeard-cli near-sponsor-keygen writes a new ML-DSA-65 key, mode 600,
  where it will be used. It never overwrites.

The config template carries the section commented out, with the steps
to turn it on. Registering and funding the account are the operator's.

Refs #51

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUx7SPonBnB3ofU4mH41WT
2026-10-09 13:39:12 +03:00
grenadeandClaude Opus 5.5 ce347c12f3 feat(infra): a second neard on frankie for shard 13, behind s13.near
deploy / validate (push) Skipped
deploy / build (push) Successful in 10m12s
deploy / deploy-web (push) Successful in 6s
deploy / deploy-api (push) Successful in 20s
wrap.near (shard 13) had no node, and adding it to frankie's running neard
stalled that node. A fresh instance syncs it instead: neard-s13 on frankie,
RPC 24404 and P2P 24569, gargravarr's snapshot-free config with its own
addresses and node key. s13.near.blackbeard.observer now proxies to it.
It needs the hanzalova WAN to forward 24569 to frankie before state sync
can finish; CLAUDE.md says so beside the node table.

Refs blackbeard/wallet#73

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUx7SPonBnB3ofU4mH41WT
2026-10-09 12:49:53 +03:00
grenadeandClaude Opus 5.5 36bea6bb7f feat(infra): our NEAR nodes public by shard, s<id>.near.blackbeard.observer
near.blackbeard.observer reaches only frankie, and a NEAR node refuses any
account on a shard it does not track. The wallet's NEAR Intents account
(blackbeard/wallet#71) needs reads on every shard its route touches, so
each shard one of our nodes tracks now has its own name on oolon:
s4/s8 to gargravarr, s7/s13 to frankie, s10/s12 to quadbrat. One vhost
and one certificate lineage cover them all, the shard-to-node map sits
in blackbeard-nodes.conf with a request ceiling per node, and
infra-setup.sh --role nodes provisions them (NEAR_SHARDS).

Already applied: DNS, the certificate (to 2027-01-07) and the vhost, all
answering status. Every name answers for an account on its shard except
s13. That needs a node for wrap.near: adding it to frankie's tracked
accounts stalled the node, as CLAUDE.md now records, and was reverted.

Refs blackbeard/wallet#73

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUx7SPonBnB3ofU4mH41WT
2026-10-09 12:41:56 +03:00
grenadeandClaude Opus 5.5 0378d0efcb fix(web): say the probe's request was refused, not that nothing was quoted
ci / gate (push) Successful in 4m24s
deploy / validate (push) Skipped
deploy / build (push) Canceled after 8s
deploy / deploy-api (push) Skipped
deploy / deploy-web (push) Skipped
The pause context showed 1Click filling two QTC buys (233 and 115 QTC,
14:18 and 14:57 UTC on 2026-10-07) while every probe was refused, from
$100 to $100k and against both USDCs. The panel's heading, "Will anyone
quote QTC?", and its "no quote" labels claimed more than the probe
shows.

It now names the request (exact input, no key, paying and receiving
inside NEAR Intents, against USDC on NEAR or on Ethereum) and calls an
outcome a refusal of that request. It points readers to the 1Click fills
in each stretch's context, which show when other requests were filled.
CLAUDE.md records the distinction.

Refs #45

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tyzzRVjH9AdwGtYwsH5VC
2026-10-07 18:01:49 +03:00
grenadeandClaude Opus 5.5 8892f0a399 feat: on-chain context for each pause in quoting, and pool settings kept
ci / gate (push) Successful in 4m32s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m51s
deploy / deploy-web (push) Successful in 6s
deploy / deploy-api (push) Successful in 17s
Each of the newest pauses now says what happened on-chain around QTC
while it ran, so a reader can tell "not quoting but still trading" from
"offline":

- pool swaps by known solvers, with the QTC they sold into and bought
  out of the pool;
- direct pool swaps by anyone else;
- 1Click fills, which a pause should have none of;
- bridge deposits arriving on NEAR;
- the largest solver QTC holding at the pause's start and end, from the
  probe's own readings;
- changes to the pool's state or whitelist.

That last needs data that was not kept: the pool's state and whitelist
were read every minute and held only in memory. ref_pool_settings
(migration 0023) records them when either changes, and only a row with
a predecessor counts as a change. The first row is how the pool was
first seen.

Context is computed for the 12 newest pauses, a few aggregate queries
each. It is counts and amounts only, and the page says it does not
explain why quotes stopped.

Refs #45

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tyzzRVjH9AdwGtYwsH5VC
2026-10-07 17:43:29 +03:00
grenadeandClaude Opus 5.5 ab2b59d178 feat: pause log, asymmetry and daily summary for 1Click quote probes
ci / gate (push) Successful in 4m46s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m39s
deploy / deploy-web (push) Successful in 15s
deploy / deploy-api (push) Successful in 25s
The probe record from 6b3bf76 says, minute by minute, whether 1Click
quoted QTC. This turns it into the episodes a reader looks for.

- Pauses: runs of consecutive unquoted minutes per direction and pair,
  found by blackbeard_core::quote_probe::pauses on the every-minute probes
  only. A quote ends a pause. An unanswered minute does not, since nothing
  is known about it, so it is counted inside. More than 15 minutes
  without answers ends a pause as unobserved, not as continuing unseen.
  Each pause carries how many of its minutes a solver alone could have
  filled it, and the pool's price at its start, at its end, and 1 and 4
  hours after. The price comes from the probe's own pool readings, not
  from any quote, and the note says a later price move is not evidence
  of why a pause happened.
- Asymmetry: per counter, buy availability minus sell availability.
- Daily summary: per UTC day over the last month, rendered as UTC days.

Pauses are computed with a day of lookback before the window, and one
already under way at the start of the record is marked as such.

Refs #45

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tyzzRVjH9AdwGtYwsH5VC
2026-10-07 17:23:09 +03:00
grenadeandClaude Opus 5.5 918d73096f fix(api): read a transaction's fee whether the decoder wrote a number or a string
ci / gate (push) Successful in 4m42s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m59s
deploy / deploy-web (push) Successful in 6s
deploy / deploy-api (push) Successful in 18s
Refs #48

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tyzzRVjH9AdwGtYwsH5VC
2026-10-07 16:21:38 +03:00
grenadeandClaude Opus 5.5 464bb498ca feat: search box in the chain header
ci / gate (push) Successful in 4m21s
A pasted hash, address or account now opens its page. On this chain one
string can be several things: 0x and 64 hex is a block hash, a
transaction hash, an account id or a miner's preimage; 64 hex without
the prefix is also a NEAR implicit account. So nothing is decided by
shape.

GET /v1/chains/{chain}/search?q= looks for every kind the string could
be, and offers a page only when the data confirms it exists:

- a block that is recorded or that the node has;
- an indexed transaction hash;
- an account with a balance or with activity;
- a miner with blocks;
- a telemetry node;
- a cached runtime version;
- a NEAR Intents account the contract knows.

A NEAR transaction hash is the one exception. It is offered as a
candidate and labelled as looked up when opened, because confirming it
would spend a public RPC call on every search.

The header box searches on Enter, never while typing. One hit opens it;
several are listed to choose from with the arrow keys, never chosen for
the reader; none says what the string was checked as, and a transaction
hash missed while the #48 backfill runs says so.

The masthead's right-hand group now takes the free width, right
aligned, so the search box no longer wraps the connection light and
theme toggle onto a second line.

Closes #49

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tyzzRVjH9AdwGtYwsH5VC
2026-10-07 16:20:50 +03:00
grenadeandClaude Opus 5.5 7c02840f91 feat: Quantus transaction page, addressed by hash, with history hashed
ci / gate (push) Successful in 4m33s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m32s
deploy / deploy-web (push) Successful in 25s
deploy / deploy-api (push) Successful in 25s
A transaction is addressed by its hash, which is what a wallet shows and
what the bridge writes into a NEAR mint. But chain_extrinsic.hash had
been written only since migration 0018: 4,045 of 285,708 mainnet
extrinsics had one. Links from the NEAR pages could therefore land only
on the block.

- Block page anchors: each extrinsic row has id x<index>. A link with
  that fragment scrolls to the row and marks it, and the fragment
  survives the height-to-hash redirect.
- Hash backfill (hashscan): the work list is the rows with no hash, and
  it only shrinks, because live ingest hashes everything it writes. Each
  block is read from our own node and hashed with the function the
  bridge links use. Verified against the known deposit 0xc4444afc at
  180,029/1, at about 6 ms a block. The client holds only our own
  endpoints, so the job waits when bob's node is down rather than
  walking history through a public fallback. Chains with no node of
  ours (the testnets) are not backfilled and are hashed from now on
  only.
- /quantus/tx/<hash> and GET /v1/chains/{chain}/tx/{hash}: the
  extrinsic as the block page decodes it, its events, its fee, and for a
  bridge deposit the NEAR mint that names it. A miss while the backfill
  is running says how many extrinsics are still unhashed, not that the
  transaction does not exist.
- Links from the NEAR transaction page, the Intents account page and the
  bridge deposits table now go to the transaction, and the block page's
  index cell links to it.

Closes #48

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tyzzRVjH9AdwGtYwsH5VC
2026-10-07 16:01:47 +03:00
grenadeandClaude Opus 5.5 6b3bf76b7e feat: probe whether 1Click will quote QTC, against what could have filled it
ci / gate (push) Successful in 4m40s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m23s
deploy / deploy-web (push) Successful in 10s
deploy / deploy-api (push) Successful in 24s
On NEAR Intents a price exists only when a solver chooses to quote, and
nothing on-chain records a refusal. Every minute the observer now asks
1Click for a keyless dry quote on QTC pairs only, both ways, against
native USDC and Ethereum USDC: the smallest size each minute and the
full grid each quarter hour, averaging about 4 requests a minute against
a published limit of 1,200. At the same moment it reads two things from
its own nodes: the Ref pool's quote for the same input, and every
solver's balance of the output token inside intents.near.

So each "No liquidity available" is stored with whether one solver
alone held enough to settle the trade. A probe that got no answer is
recorded as `failed`, never as a refusal. Only our own observations are
kept: the 1ClickSwap API Terms (s4.1(e)) forbid keeping API data past 24
hours, so a quote becomes a markup against the pool on arrival and raw
responses are pruned hourly.

The first live round, 2026-10-07: "No liquidity available" both ways.
Selling 1 QTC, the pool would have paid 101.53 USDC and three solvers
each held enough. Buying with 100 USDC, solver-ref-2.near held 2,308
QTC.

The near-intents market page shows a strip per direction and counter
token. It is an emphasis chart: the accent marks only "no quote while a
solver could fill", the other states are quiet ink told apart by
height, and the neutral token is validated against the accent in both
themes. Beside it are a summary table and a list of the latest
refusals. The pause log and the price-move statistics in #45 come later,
once there is history to support them.

Refs #45

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tyzzRVjH9AdwGtYwsH5VC
2026-10-07 13:39:52 +03:00
grenadeandClaude Opus 5.5 6fa8cf342a feat: NEAR calls and events read as sentences, JSON as the fallback
ci / gate (push) Successful in 4m37s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m31s
deploy / deploy-web (push) Successful in 15s
deploy / deploy-api (push) Successful in 31s
The transaction page showed every call's arguments and every event's
payload as pretty-printed JSON. Most of those shapes are fixed by a
standard (NEP-141, NEP-245, dip4) or are formats seen on this chain's
paths (the bridge's ft_deposit, execute_intents in both its raw and
NEP-413 encodings, Ref swap instructions). Those now read as sentences
with typed parts: amounts in token units, Intents accounts linked, and
Quantus transactions and addresses linked to this site.

A sentence is produced only when it accounts for every field. Fields
are read through a reader that records what was used. An unknown,
missing or mistyped field, an intent kind nobody wrote a case for, or a
msg in an unread format falls back to the JSON, with a note saying so.
Fields left out on purpose (signatures, nonces, gas hints, intent
hashes) are dropped by name in the formatter. The raw JSON stays one
click away behind every sentence.

Measured over the 12 transactions stored in production: every event and
all but three calls read as sentences. The three that fall back are two
private Ref callbacks and qcat.nearlytrade.near's ft_resolve_transfer,
which carries a non-standard `tax` field.

Closes #47

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tyzzRVjH9AdwGtYwsH5VC
2026-10-07 12:52:34 +03:00
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 94472f1238 fix(api): bridge history treats a failed archive read as unresolved
ci / gate (push) Successful in 4m24s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m14s
deploy / deploy-web (push) Successful in 5s
deploy / deploy-api (push) Successful in 19s
The pre-node history read found all sixteen supply changes and recorded
thirteen. Two silent paths dropped mints: a chunk read error read as an
empty chunk, and a block_at error discarded a mint already found. The
cursor was then saved, so the 0.996, 0.5 and 5,990 QTC deposits stayed
unlinked for good.

Archive reads are now bounded, with backoff, and log at warn when they
give up. Each change is linked, mismatched or unread, and unread changes
are retried in later passes. The cursor is saved only once nothing is
unread. supply_at no longer retries forever. Migration 0019 clears the
cursor so the read runs again.

/bridge/deposits and the hash job also skip the fee payer's gas top-ups,
which were counted as 337 unlinked deposits.

Closes #46

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tyzzRVjH9AdwGtYwsH5VC
2026-10-07 11:08:06 +03:00
grenadeandClaude Opus 5.5 e183fdafde fix(api): bridge mint scan reads its layout from the first height that exists
ci / gate (push) Successful in 4m48s
deploy / validate (push) Skipped
deploy / build (push) Successful in 8m58s
deploy / deploy-web (push) Successful in 11s
deploy / deploy-api (push) Successful in 22s
The backward pass sat on #218,897,633 — a height NEAR never produced — and
asked gargravarr for its shard layout on every turn: UNKNOWN_BLOCK, a quiet
sleep, the same question again. #31 fixed exactly this in the intents scan;
bridgescan was written without it. The layout now comes from the first
height in the batch that exists, any other failure is logged, and the
history job walks down past a skipped height rather than retrying it.

Refs #44

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tyzzRVjH9AdwGtYwsH5VC
2026-10-07 10:24:11 +03:00
grenadeandClaude Opus 5.5 5f30000539 fix(api): give NEAR backfills every other turn instead of none
ci / gate (push) Successful in 4m39s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m20s
deploy / deploy-web (push) Successful in 5s
deploy / deploy-api (push) Successful in 19s
The bridge's mint scan sat with its backfill stalled — 118,898 blocks
between gargravarr's oldest and where it started, unread and not moving
— because every NEAR scan went back only once it was level with the tip.
A forward pass takes longer than NEAR's 0.6 s block, so a live scan is
always a block or two behind and the backward turn never came. #44's
example deposit was waiting on exactly that stretch.

The four NEAR scans (bridge mints, intents, intents flows, pool calls) now
alternate while history remains. Forward runs at half pace only while a
backfill is pending, and every scan here keeps up with the tip at that.

Refs #44

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-07 10:03:46 +03:00
grenadeandClaude Opus 5.5 084fd5ba2c feat: link QTC bridge deposits and withdrawals across both chains
ci / gate (push) Successful in 4m32s
deploy / validate (push) Skipped
deploy / build (push) Successful in 10m22s
deploy / deploy-web (push) Successful in 12s
deploy / deploy-api (push) Successful in 30s
#44 part 3. A deposit is linked exactly: the NEAR mint is bridge-mng.near
calling omft.near.ft_deposit, whose arguments carry the Intents account
and BRIDGED_FROM's txHash — Blake2-256 of the depositor's Quantus
transfer into their deposit address, length prefix included (checked:
extrinsic 1 of block 180,029 hashes to 0xc4444afc…, the hash in mint
piPTXyoq…). `chain_extrinsic.hash` is stored from now on and filled for
the bridge's deposit transfers.

`bridgescan` reads mints from gargravarr (bridge-mng.near is on shard 8),
each confirmed executed before it is recorded. History before our nodes
is found by bisecting the token's supply — 16 changes before
gargravarr's oldest block, 204 archive calls — and a rise is accepted as
mints only when the mints around it sum to it exactly. Withdrawals
(ft_withdraw with WITHDRAW_TO) come from the intents scan, rewound to
frankie's oldest block (migration 0018); custody's payout is matched by
a strict, stated rule, as an inference, with the fee it implies — every
pre-node withdrawal paid exactly 0.002 QTC less. `/bridge/deposits` and
`/bridge/withdrawals`; a linked-transactions panel on the NEAR Intents
page.

Refs #44

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-07 09:10:33 +03:00
grenadeandClaude Opus 5.5 69050a45c1 feat: index QTC's 1Click fills, which settle as transfers, not token_diff
ci / gate (push) Successful in 4m33s
deploy / validate (push) Skipped
deploy / build (push) Successful in 8m45s
deploy / deploy-web (push) Successful in 8s
deploy / deploy-api (push) Successful in 21s
Since launch the NEAR Intents page showed no price and no settlements while
QTC traded heavily: the scan counts `token_diff` trades, and 1Click settles
no QTC swap that way. A buy is a solver transferring QTC to a one-off
1Click account, passed on to a shared one; a sell is the mirror; the user's
payment is on their own chain and never on NEAR.

The scan now keeps every executed transfer of the asset (migration 0017,
cursor wound back to before the first QTC swap; pool swaps already recorded
are not re-queued), unclassified. `/v1/chains/{chain}/intents/fills`
classifies at read time against the solvers known then — the solver table
plus every Ref pool whitelist, since solver-ref-2.near never appears in a
token_diff — so a solver found later classifies its past fills without a
re-read. Solver to account is a buy, account to solver a sell; hops between
non-solvers (the pass-on, a flat 0.002 QTC per-swap fee to 5880ad…) are not
fills. Fills are pushed live; the NEAR Intents page lists them without a
price, and shows the Ref pool's restock price in its place.

Checked against the chain: the traced buy at #218,780,964 (2,991.6053 QTC,
solver-ref-2.near to d4524d…) and the sell at #218,758,672 classify as such,
and their pass-on hops do not.

Closes #43

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 20:38:34 +03:00
grenadeandClaude Opus 5.5 025116fd2d feat(api): log the pool's non-swap events, to learn whether it logs orders
ci / gate (push) Successful in 4m21s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m9s
deploy / deploy-web (push) Successful in 10s
deploy / deploy-api (push) Successful in 26s
#41 asks whether `dcl.ref` logs limit-order events — which would say who
posts and withdraws each bid, where the book alone only shows that it
changed. The contract's source is not public where it was looked for, so
the chain is asked instead: `refdcl::fetch` already reads every direct
call in full, and now logs the first sighting of each non-swap `dcl.ref`
event per process with a sample of its data.

Refs #41

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 19:49:13 +03:00
grenadeandClaude Opus 5.5 6988368d6e fix(api): fetch relay swaps first, and never idle while the queue has work
ci / gate (push) Successful in 4m32s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m35s
deploy / deploy-api (push) Successful in 42s
deploy / deploy-web (push) Successful in 10s
After launch the Ref fetch loop slept ten seconds between batches of
twenty, oldest first: 1,574 relay swaps — real 1Click trades through the
pool — waited behind thousands of an arbitrage bot's direct calls, which
the re-read had queued and which mostly fail, draining at about six a
minute. The page's pool swaps and price chart ran hours behind.

The queue now serves the relay's entries first, then the least-retried,
newest first, and loops straight on while batches (now fifty) come back
full; FastNEAR's pacing is the only limit, about a hundred a minute.

Refs #37, #39

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 19:24:36 +03:00
grenadeandClaude Opus 5.5 b3dc4ef4f6 feat: list Ref's classic exchange for pools holding the asset
ci / gate (push) Successful in 4m7s
deploy / validate (push) Skipped
deploy / build (push) Failing after 11m44s
deploy / deploy-api (push) Skipped
deploy / deploy-web (push) Skipped
#39 item 2. Every pool on v2.ref-finance.near is listed every ten
minutes — nine thousand-pool pages, three seconds on quadbrat, which
tracks the exchange's shard and is asked first while at the tip — and
each pool naming the asset is reported on the Ref market page, empty or
not. Today that is pool 8900, the empty QTC/QTEST test pool #39 found by
hand. A listing that fails part way publishes nothing.

The exchange's own balance of the asset joins the supply reconciliation
as a named holder, so a venue opening there is never "unaccounted".

Refs #39

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 19:13:08 +03:00
grenadeandClaude Opus 5.5 6152ba85c2 fix(api): queue only direct pool calls about the asset, and re-read the rest
ci / gate (push) Successful in 4m5s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m0s
deploy / deploy-web (push) Successful in 8s
deploy / deploy-api (push) Successful in 19s
The direct-call scan matched any transaction reaching the DCL contract,
which hosts hundreds of pools: 4,881 strangers' swaps queued in the first
hour after launch, from 176 signers, each to be fetched from a
rate-limited public endpoint — enough on its own to keep the market page
starved. A call now counts only when it moves the asset itself or its
arguments name the asset's contract, which every pool id holding it does;
a buy with any token names the pool, a sell is a transfer of the asset.

ref_pending keeps no arguments, so the bad entries cannot be filtered in
place: migration 0016 drops every entry not signed by the relay and the
scan's cursor, and the scan reads the span again from our node.

Refs #39

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 19:01:29 +03:00
grenadeandClaude Opus 5.5 6116954f13 feat: QTC into and out of NEAR Intents, block by block
ci / gate (push) Successful in 4m23s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m6s
deploy / deploy-web (push) Successful in 8s
deploy / deploy-api (push) Successful in 20s
#39 item 4, as replaced: the original check (intents.near's balance against
indexed events) could not see the trades it was for, because a trade moves
QTC inside intents.near and leaves its total alone. What the balance does
move with is deposits and withdrawals, so that is what is recorded.

`intentsflow` reads intents.near's `ft_balance_of` on qtc.omft.near at
every NEAR block from gargravarr, which tracks the token's shard, forward
to its final block and back to the oldest it holds, and records each
change (migration 0015). `view_at` treats only UNKNOWN_BLOCK as a skipped
height: a garbage-collected block is an error, never "nothing changed".
`/v1/chains/{chain}/intents/flows` sums deposits and withdrawals over a
span; the NEAR Intents market page shows it after the bridge.

Verified on the live chain through a tunnel to gargravarr: the scan's
balance at #218,805,000 (36,044.097 QTC) and at #218,811,064 (72,671.542)
match direct reads, and across 1,044 rows every balance less its change
is the previous balance.

Refs #39

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 18:54:40 +03:00
grenadeandClaude Opus 5.5 0851b244ba fix(api): read the pools already known while discovery is rate-limited
ci / gate (push) Successful in 4m23s
deploy / validate (push) Skipped
deploy / build (push) Successful in 8m47s
deploy / deploy-web (push) Successful in 9s
deploy / deploy-api (push) Successful in 22s
After the last deploy every pool listing came back FastNEAR 429 for
twenty minutes, and with nothing discovered nothing was read: the market
page had no ladder at all. The pools swaps have been recorded in certainly
hold the asset, so the loop now starts from those and reads them while
discovery retries; discovery still finds any pool nobody has swapped in.

Refs #37

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 18:36:44 +03:00
grenadeandClaude Opus 5.5 5361dec713 feat: record the Ref pool's bid side over time
ci / gate (push) Successful in 4m7s
deploy / validate (push) Skipped
deploy / build (push) Successful in 8m55s
deploy / deploy-web (push) Successful in 5s
deploy / deploy-api (push) Successful in 18s
Each pool read now writes a snapshot — totals, swap volumes, and what
selling 10, 100, 1,000 and 5,000 of the asset would fetch from the
pool's own `quote` view — and the order book whenever it changed
(migration 0014). `/v1/chains/{chain}/ref/bids` turns that into the
series #41 asks for: posted orders, money held otherwise, net swapped
in, the quotes; the book now against N hours ago; and every level
change, classified by `dcl::level_changes` — a shrink the pool's payout
in the same minute accounts for is "taken by a swap" (consistent with
one, not proof), the rest "withdrawn", allocated nearest the price
first. The `ref-dcl` page shows it as four one-hue sparkline tiles and
two tables, never one chart in several colours.

Also records in CLAUDE.md why three deploys were cancelled: Gitea
cancels runs by commit when a branch at that commit is deleted.

Refs #41

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 18:17:11 +03:00
grenadeandClaude Opus 5.5 1d25689e3d feat(data): ask our own NEAR node for view calls while it is at the tip
FastNEAR kept answering 429 after the pacer shipped: its limit is per
address, and every host behind the office IP shares it. gargravarr tracks
the pool's and the token's shards, so `pool_rpc_urls` now also serves
view calls first — pool state, order book, quotes, supply, balances — but
only while its newest block is under two minutes old and it is not
syncing, re-checked every 30 s. Behind the tip it would answer `final`
from its own final block: on 2026-10-06, 11,536 blocks behind, it gave
qtc.omft.near's supply as of three hours earlier, which would have
replaced "unreadable" with a figure that looked current and was not. Any
call it cannot answer falls back to the public endpoint.

Refs #39

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 18:17:11 +03:00
grenadeandClaude Opus 5.5 26dfdc956c fix: pace public NEAR view calls, and never publish "no pools" from a failed read
ci / gate (push) Successful in 4m30s
deploy / validate (push) Skipped
deploy / build (push) Canceled after 7s
deploy / deploy-api (push) Skipped
deploy / deploy-web (push) Skipped
From 17:32 every Ref pool read came back FastNEAR 429, minute after minute:
a restart fires discovery pages, pool and order-book views, supply and
balance reads and inventory at once, from one office address, against an
endpoint measured at about two requests a second sustained. Every call to a
public view endpoint now passes one process-wide gate, 600 ms apart.

Worse than the outage, the pool loop read an empty pool list after a failed
discovery and published it, so the market page said no Ref DCL pool holds
QTC — "could not list" turned into "there are none". The slot now stays
unread until a discovery has succeeded; one that succeeds and finds none is
still published as none.

Refs #37, #39

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 17:56:42 +03:00
grenadeandClaude Opus 5.5 1afb99503b fix(bridge): alarm on a shortfall only when both figures describe one moment
ci / gate (push) Successful in 4m20s
A deposit locks in custody before NEAR mints, and a withdrawal burns on NEAR
before custody releases, so between a movement and the next NEAR supply
read (every five minutes) `locked_less_minted` can sit either side of zero
with nothing wrong. `gap_settled` says whether the supply was read at least
a minute after the newest custody movement; the panel raises its alert only
then, and explains a transient gap instead of alarming on it.

Refs #39

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 17:50:11 +03:00
grenadeandClaude Opus 5.5 8eb812a19a feat: read the NEAR bridge from the Quantus side — custody against minted
`/v1/chains/{chain}/bridge` sets the bridge's custody account against
qtc.omft.near's supply on NEAR, and lists every movement through custody,
newest first and paged. The bridge's two accounts, custody and the fee payer
that funds each deposit, are named in `[chains.bridge]` with the evidence
beside them and validated as addresses at startup; neither is inferred.

`blackbeard_core::bridge::classify` tells a deposit by who and when: a
transfer into custody from an address the fee payer funded at most
`max_sweep_lag` blocks before (every mainnet deposit so far: one). Not by
amount — the funding varies (0.0010166025, 0.0010169025 before the 150k
sweep, 0.0010168025 before the 5,990) — and not by one-time addresses,
which #39 assumed: one address deposited three times. Anything else into
custody is an other inflow; locked is custody's balance less those, which
derives the 5 QTC seed instead of configuring it. Tested against every real
transfer on both accounts.

Each transfer is counted once. Every ordinary transfer emits both
`Balances::Transfer` and `Wormhole::NativeTransferred`; the query takes the
first, plus the second only for a hidden-sender exit.

Verified end to end against the live chain (custody balance read from bob)
and NEAR (the listing's supply): locked 155,994.701, minted 155,994.698,
locked − minted +0.003 QTC; 13 deposits from 11 addresses, 3 withdrawals.

The market page shows it beside the backing ceiling, reporting flows and
saying what they cannot establish: an address is not a person, and summed
flows are only the whole history if the index covers it.

Refs #39

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 17:49:05 +03:00
grenadeandClaude Opus 5.5 9becf2629e fix(data): count each visible transfer once, not once per pallet
ci / gate (push) Successful in 4m29s
Every visible transfer emits `Balances::Transfer` and
`Wormhole::NativeTransferred` with the same payload in the same extrinsic
(193,686 of 193,691 on mainnet), and `account_flows`, `flows_for` and
`wormhole_routes` added the two: every account page's sent and received,
the distribution table's `sent on`, and the wormhole page's visible routes
were doubled. A `Balances::Transfer` now counts only when it has no such
twin, which keeps the five that stand alone. Checked on production data:
the bridge custody account's `received` goes from 372,003.992 over 30
receipts to 186,001.996 over 15.

Closes #42

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 17:42:57 +03:00
grenadeandClaude Opus 5.5 b55d323456 fix: no unaccounted figure until the whitelists are known; no mining row on market pages
ci / gate (push) Successful in 4m30s
deploy / validate (push) Skipped
deploy / build (push) Canceled after 8s
deploy / deploy-api (push) Skipped
deploy / deploy-web (push) Skipped
The listing pass runs at startup, a minute before the first pool read, so
its named set lacked every whitelisted account and it reported the sum as
complete: production read 0.3695 QTC unaccounted against a true 0.269 —
exactly liq.selfevident.near's 0.1005 — until the next pass. Absent now
until the whitelists have been read.

The market pages drop the hashrate/difficulty/block time/miners/height row,
as the wormhole page already did: they are about the token on another
chain, and mining figures above them read as though they bore on a price.
Keyed on `sectionOf`, so the index and every market page agree.

Refs #39, #41

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 17:32:56 +03:00
grenadeandClaude Opus 5.5 35c7a2cf97 fix: show the Ref pool's limit-order bids in its ladder
deploy / validate (push) Skipped
deploy / build (push) Canceled after 20s
deploy / deploy-api (push) Skipped
deploy / deploy-web (push) Skipped
ci / gate (push) Successful in 4m7s
The ladder was built from `get_liquidity_range` alone, so no limit order
appeared in it: with 109,988 USDC posted in ten bid levels from $20 down to
$0.50, it showed about 1,001 USDC of bids and told a QTC seller there was
almost nothing to sell into. `get_pointorder_range` is now read on the same
pass; each order becomes a one-point rung (`dcl::order_rungs`, filled at its
own point's price, tested against #41's hand-worked levels), and every rung
carries its `source` — liquidity, or an order its owner can cancel at any
moment — and `asset_to_clear`, the QTC it absorbs. Orders plus liquidity
reconcile to the pool's `total_x` less its uncollected fees (110,989.60 of
110,991.62 USDC, read 2026-10-06).

Refs #41

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 17:20:09 +03:00
grenadeandClaude Opus 5.5 016c0424cc feat: reconcile QTC's NEAR supply against every holder we name
ci / gate (push) Successful in 4m32s
deploy / validate (push) Skipped
deploy / build (push) Successful in 8m58s
deploy / deploy-api (push) Successful in 23s
deploy / deploy-web (push) Successful in 11s
The listing pass now reads `ft_balance_of` for intents.near, each Ref DCL
pool contract and every account on a pool's whitelist, and reports
`unaccounted` — supply less all of them — beside the backing ceiling. It is
#39's first blind spot: nothing noticed QTC moving to an account or venue
this observer does not name. Absent unless every balance was read, because
a partial sum overstates it while looking exact. Checked by hand against the
chain before shipping: 0.269 QTC, the figure #39 reconciled this morning.

The price response's `reads` now says Confidential Intents settle on NEAR's
private shard and are in no figure here (#39 item 6).

Refs #39

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 17:07:40 +03:00
grenadeandClaude Opus 5.5 e78e627152 feat(api): read direct calls to the asset's pools on the shards they are signed on
ci / gate (push) Successful in 4m31s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m14s
deploy / deploy-api (push) Successful in 31s
deploy / deploy-web (push) Successful in 14s
The relay's pool swaps are found on intents.near's shard; a whitelisted
account calling the pool itself signs on its own shard, which nothing read —
#37's coverage figure measured the gap at 99.75% of buys. `poolscan` follows
the pools' and the token's shards on gargravarr (`pool_rpc_urls`), which
tracks shards 4 and 8, and queues every transaction that calls a pool or
`ft_transfer_call`s tokens into one. `refdcl::fetch` reads each whole, as it
does the relay's, so a swap is parsed one way and recorded once whichever
road it came by.

Reads only what the node holds; history before it synced stays the coverage
figure's to account for. Records the three-node shard split and the
Prometheus neard scrape config (installed on golgafrinchans) in the repo.

Refs #39

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 16:38:21 +03:00
grenadeandClaude Opus 5.5 c483d51dc7 fix(web): mark a section's nav link on its subjects' pages, not only its index
ci / gate (push) Successful in 4m26s
deploy / validate (push) Skipped
deploy / build (push) Successful in 1m26s
deploy / deploy-api (push) Skipped
deploy / deploy-web (push) Successful in 6s
`/quantus/market/near-intents` marked nothing in the section nav, nor did a
block, account or runtime page: the nav tested the index plus a hand-kept list
of exceptions that every newer section had missed. `sectionOf` in
lib/routes.ts now answers it once, over every subject field. Block zero stays
with Genesis alone.

Closes #40

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 14:10:10 +03:00
grenadeandClaude Opus 5.5 7cd2197997 feat: chart the Ref DCL pool's swap price on its own market page
ci / gate (push) Successful in 4m22s
deploy / validate (push) Skipped
deploy / build (push) Successful in 8m45s
deploy / deploy-web (push) Successful in 5s
deploy / deploy-api (push) Successful in 22s
`/v1/chains/{chain}/ref/series?counter=nep141:…` buckets the latest pool
swaps exactly as `/intents/series` buckets intents trades, through one shared
`price_series`, and `ref-dcl` draws it with the same plot and figures table
(`SeriesBody`, pulled out of `PriceChart`). It is a separate chart, never a
second line on the intents one: a pool swap is usually a solver restocking
after an intents fill, and one line over both would draw one demand twice.

`RefPool::counter_token` was documented as a contract but carries the
`nep141:` prefix; the doc now says so — the first cut of the chart prefixed
it again and drew nothing.

Tracking shard 8 (option B), the other half left open on #37, is item 5 of
#39 and waits for the new NEAR host.

Closes #37

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 11:26:08 +03:00
grenadeandClaude Opus 5.5 f6b1e8ad14 test(web): assert dates and hours, not one ICU build's word order
ci / gate (push) Successful in 4m30s
deploy / validate (push) Skipped
deploy / build (push) Successful in 9m11s
deploy / deploy-web (push) Successful in 34s
deploy / deploy-api (push) Successful in 18s
The runner's Node carries English-only ICU and formats en-GB as en-US
("Oct 5", "EDT"); browsers carry full ICU. What #38 guards is which day
and which hour, so that is what the test checks.

Refs #38

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 11:05:36 +03:00
grenadeandClaude Opus 5.5 21ec54bd24 fix(web): label local times with their zone and keep UTC days on their own date
ci / gate (push) Failing after 4m31s
Instants (blocks, swaps, node starts) render in the viewer's zone and say
which: tables carry the zone, charts name it once, titles give UTC. Daily
buckets the API cut at UTC midnight render in UTC and are headed "(UTC)" —
FlowChart formatted them as local dates, putting every bar a day early
anywhere west of Greenwich.

All date formatting goes through lib/format.ts (when, utc, clock, utcDay,
utcMonth, zone), one locale and a 24-hour clock. `pnpm test` runs Node's
built-in runner under America/New_York, where the old formatter turns
5 Oct into 4 Oct, and is part of the shared CI gate.

Closes #38

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-06 10:58:52 +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 d6654ac4b0 feat(api): flag QTC on NEAR that exceeds what exists outside the vesting pot
ci / gate (push) Successful in 4m26s
deploy / validate (push) Skipped
deploy / build (push) Successful in 8m35s
deploy / deploy-web (push) Successful in 7s
deploy / deploy-api (push) Successful in 20s
qtc.omft.near is minted only against QTC locked on Quantus by the bridge, so
its supply can never legitimately exceed issuance outside the vesting pot —
221,838 QTC today. The listing poll now reads that ceiling every five minutes
through one helper, issuance_outside_pot, using the same storage reads and the
same subtraction the network page uses, rescales it to the NEAR token's
decimals (rounding down, so the check can only get stricter), and compares it
with ft_total_supply. A breach is a logged warning and, on the market page, a
sentence before any figure: unbacked, or one of the two misread. Either read
failing gives no verdict rather than a false all-clear.

The panel also shows the ceiling itself as "most there could be". Checked live:
QTC at 0 against 221,837.97 with no breach, and the breach path forced by
pointing the dev config at wNEAR (35.4M on NEAR), which logged and rendered the
warning as intended.

In place before QTC's announced NEAR Intents launch on 2026-10-05.

Closes #36

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-04 17:12:02 +03:00
grenadeandClaude Opus 5.5 a13a6ef1a5 fix(api): pace the intents backfill only for someone else's endpoint
ci / gate (push) Successful in 4m22s
deploy / validate (push) Skipped
deploy / build (push) Successful in 8m22s
deploy / deploy-web (push) Successful in 5s
deploy / deploy-api (push) Successful in 18s
The one-second pause between backfill blocks was set when every read went to
FastNEAR's free RPC or its archive. Since #27 was enabled the live endpoint is
our own node and start_height the oldest block it holds, so the whole backfill
was served by a host we run and the pause only stretched it to about six
hours — stalling the tip side for each descending batch, so live pushes were
missed meanwhile.

The pause now follows who serves the read: heights at or above the live
endpoint's earliest block go to rpc_urls, older ones to the archive, and only
a set that is not ours is paced. Ours is decided from the URL host — a
`.internal` name, localhost, or a loopback, private, link-local or unique-local
address — and a set counts as ours only when every endpoint in it is, since
failover can reach any of them.

Measured against near.internal: the backfill walked about 15,000 blocks in
60 s (≈250/s, up from 1/s) while the top end kept pace with the tip.

Closes #35

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-03 23:34:56 +03:00
grenadeandClaude Opus 5.5 a07a3bc580 fix(api): sort solver inventory with sort_by_key
ci / gate (push) Successful in 4m22s
deploy / validate (push) Skipped
deploy / build (push) Successful in 8m23s
deploy / deploy-web (push) Successful in 5s
deploy / deploy-api (push) Successful in 18s
The lint the previous commit was pushed with; caught before CI ran.

Refs #34

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-03 23:12:46 +03:00
grenadeandClaude Opus 5.5 2d80325c3b feat(api): solver inventory per side of each pair, read from our node
ci / gate (push) Failing after 1m34s
NEAR Intents has no order book: solvers quote privately per request and
nothing on chain carries a price level. What is on chain is the inventory
they fill from, inside intents.near — and intents.near is on the shard our
node tracks, so mt_batch_balance_of is answered locally in about 40 ms.

The scan now records every solver it sees filling any pair — a named account
with no referral, which the #30 survey found to be solvers without exception
— in intents_solver (migration 0011), so the list survives restarts and is
there before QTC has a trade of its own. Once a minute the observer asks
intents.near what each holds of the asset and every counter traded against
it, and serves it at /intents/inventory: the asset as the side that serves
buyers, each counter as the side that serves sellers, with how many solvers
hold it and the largest holder.

The market page shows it as a table, the asset and the ten counters most
solvers hold with the rest folded, under a note that says what it is not: no
price levels, shared across pairs so the rows do not add up, and not a
ceiling. Checked live against our node with wNEAR configured: 11 solvers,
74 tokens read in one pass. Before QTC is listed the panel's job is its one
row — when solvers start stocking QTC, it shows here before the first trade.

Closes #34

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015pkwBsR54NRmZyU4QVhvfj
2026-10-03 23:12:32 +03:00