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
This commit is contained in:
2026-10-06 09:50:34 +03:00
co-authored by Claude Opus 5.5
parent d6654ac4b0
commit b85e74f1ca
33 changed files with 2442 additions and 11 deletions
+86
View File
@@ -0,0 +1,86 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { BigUintDec } from "./BigUintDec";
import type { RefRung } from "./RefRung";
/**
* A Ref Finance DCL pool holding the tracked asset, read now.
*
* State, not trades: the price is the point the next swap would start at, not
* a price anyone paid, and the ladder is what is offered there. Swaps are in
* `/ref/swaps`. blackbeard/observer#37.
*/
export type RefPool = {
/**
* The pool contract, e.g. `dclv2.ref-labs.near`.
*/
contract: string,
/**
* `token_x|token_y|fee`.
*/
pool_id: string,
/**
* The counter token's contract.
*/
counter_token: string,
/**
* Its own symbol, when read. Display only.
*/
counter_symbol: string | null,
/**
* Its decimals.
*/
counter_decimals: number,
/**
* Fee, in hundredths of a basis point as the contract keeps it (2000 = 0.2%).
*/
fee: number,
/**
* `Running`, or whatever the contract reports.
*/
state: string,
/**
* The current point.
*/
current_point: number,
/**
* The asset's price at it, in the counter token.
*/
price: number,
/**
* The asset held by the pool, base units.
*/
asset_held: BigUintDec,
/**
* The counter token held, base units — including fees owed to LPs.
*/
counter_held: BigUintDec,
/**
* The ladder, cheapest asset first.
*/
rungs: Array<RefRung>,
/**
* Accounts the pool lets swap. `intents.near` among them is how solvers
* reach it through NEAR Intents.
*/
whitelist: Array<string>,
/**
* The pool's own cumulative counters, base units, from the asset's side.
*/
asset_out: BigUintDec,
/**
* Asset that has come into the pool.
*/
asset_in: BigUintDec,
/**
* Of the asset bought out of the pool, how much this observer has
* indexed as swaps — what the blind spot is measured against.
*/
indexed_asset_out: BigUintDec,
/**
* Of the asset sold into the pool, how much has been indexed.
*/
indexed_asset_in: BigUintDec,
/**
* Swaps indexed in this pool.
*/
indexed_swaps: number, };
+36
View File
@@ -0,0 +1,36 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ChainId } from "./ChainId";
import type { RefPool } from "./RefPool";
/**
* The tracked asset's Ref DCL pools.
*/
export type RefPools = {
/**
* Which chain's token.
*/
chain: ChainId,
/**
* The asset, as a multi-token id.
*/
asset: string,
/**
* Its own symbol, when read.
*/
asset_symbol: string | null,
/**
* Its decimals, when read.
*/
asset_decimals: number | null,
/**
* When the pools were read.
*/
checked_at: string,
/**
* Every pool found holding the asset.
*/
pools: Array<RefPool>,
/**
* What is read, and what is not, in words.
*/
reads: string, };
+31
View File
@@ -0,0 +1,31 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* One part of a DCL pool's liquidity ladder, from the tracked asset's side.
*/
export type RefRung = {
/**
* The asset's price at the cheap end, in the counter token.
*/
price_from: number,
/**
* At the dear end.
*/
price_to: number,
/**
* The asset held across these points, whole units: what a buyer can take.
*/
asset: number,
/**
* The counter token held, whole units: what a seller of the asset can be paid.
*/
counter: number,
/**
* Counter token needed to take all the asset here.
*/
counter_to_clear: number,
/**
* `asks` (holds the asset), `bids` (holds the counter) or `current`
* (the point the next swap starts at, which holds both).
*/
kind: string, };
+43
View File
@@ -0,0 +1,43 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { BigUintDec } from "./BigUintDec";
/**
* One swap in a Ref DCL pool.
*/
export type RefSwap = {
/**
* NEAR block the relay transaction was included in.
*/
near_height: number,
/**
* That block's timestamp.
*/
at: string,
/**
* The relay transaction.
*/
tx_hash: string,
/**
* `token_x|token_y|fee`.
*/
pool_id: string,
/**
* The solver whose intent it was. The pool itself records `intents.near`.
*/
trader: string,
/**
* `buy` (the asset came out of the pool) or `sell`.
*/
side: string,
/**
* The asset that changed hands, base units.
*/
asset_amount: BigUintDec,
/**
* The counter token, base units.
*/
counter_amount: BigUintDec,
/**
* Counter token per whole asset, fee included, when decimals are known.
*/
price: number | null, };
+32
View File
@@ -0,0 +1,32 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ChainId } from "./ChainId";
import type { RefSwap } from "./RefSwap";
/**
* A page of pool swaps, newest first.
*/
export type RefSwaps = {
/**
* Which chain's token.
*/
chain: ChainId,
/**
* The asset.
*/
asset: string,
/**
* Its own symbol, when read.
*/
asset_symbol: string | null,
/**
* Its decimals, when read.
*/
asset_decimals: number | null,
/**
* The swaps.
*/
swaps: Array<RefSwap>,
/**
* Cursor for the next older page, `near_height:tx_hash`.
*/
next: string | null, };
+17
View File
@@ -22,6 +22,8 @@ import type { IntentsPrice } from './generated/IntentsPrice'
import type { IntentsSeries } from './generated/IntentsSeries'
import type { IntentsSwaps } from './generated/IntentsSwaps'
import type { Markets } from './generated/Markets'
import type { RefPools } from './generated/RefPools'
import type { RefSwaps } from './generated/RefSwaps'
import type { ReversibleState } from './generated/ReversibleState'
import type { RecentBlock } from './generated/RecentBlock'
import type { MinerDetail } from './generated/MinerDetail'
@@ -332,3 +334,18 @@ export async function fetchIntentsInventory(
): Promise<IntentsInventory | null> {
return get<IntentsInventory | null>(`/chains/${chain}/intents/inventory`, signal)
}
/** The asset's Ref DCL pools: state, ladder, coverage. Null until first read. */
export async function fetchRefPools(chain: string, signal?: AbortSignal): Promise<RefPools | null> {
return get<RefPools | null>(`/chains/${chain}/ref/pools`, signal)
}
/** Swaps in the asset's Ref DCL pools, newest first. */
export async function fetchRefSwaps(
chain: string,
before: string | null,
signal?: AbortSignal,
): Promise<RefSwaps> {
const cursor = before ? `?before=${encodeURIComponent(before)}` : ''
return get<RefSwaps>(`/chains/${chain}/ref/swaps${cursor}`, signal)
}
+23 -2
View File
@@ -16,17 +16,18 @@ import { Link } from 'react-router-dom'
import type { IntentsSwap } from '../api/generated/IntentsSwap'
import type { IntentsSwaps } from '../api/generated/IntentsSwaps'
import { RequestFailed, fetchSwapsNewest } from '../api/rest'
import { RequestFailed, fetchMarkets, fetchSwapsNewest } from '../api/rest'
import { height as fmtHeight } from '../lib/format'
import { useObserverInstance } from '../lib/observer-context'
import { href } from '../lib/routes'
import { IntentsPanel } from './IntentsPanel'
import { InventoryPanel } from './InventoryPanel'
import { PriceChart } from './PriceChart'
import { RefDclMarket } from './RefDclMarket'
import { SwapHead, SwapRow } from './SwapRow'
/** The venues this page knows how to show. */
const KNOWN = new Set(['near-intents'])
const KNOWN = new Set(['near-intents', 'ref-dcl'])
/** Lines the live feed keeps. Older ones are in the history below it. */
const LIVE_LINES = 12
@@ -55,6 +56,9 @@ export function MarketPage({
</section>
)
}
if (market === 'ref-dcl') {
return <RefDclMarket chain={chain} decimals={decimals} symbol={symbol} />
}
return <NearIntents chain={chain} decimals={decimals} symbol={symbol} />
}
@@ -77,6 +81,14 @@ function NearIntents({
// duplicates and stops honouring the list's own length cap.
const nextKey = useRef(0)
const observer = useObserverInstance()
const [refMarket, setRefMarket] = useState(false)
useEffect(() => {
const controller = new AbortController()
fetchMarkets(chain, controller.signal)
.then((m) => setRefMarket(m.markets.some((x) => x.id === 'ref-dcl')))
.catch(() => {})
return () => controller.abort()
}, [chain])
useEffect(() => {
const controller = new AbortController()
@@ -133,6 +145,15 @@ function NearIntents({
<Link to={href({ chain, index: 'market' })}>Markets</Link> › NEAR Intents
</p>
{refMarket && (
<p className="panel-note" style={{ marginBottom: 12 }}>
<strong>{symbol}&apos;s liquidity on NEAR is also in a Ref DCL pool.</strong> Solvers
restock from it after filling users here; those swaps are on the{' '}
<Link to={href({ chain, market: 'ref-dcl' })}>Ref Finance DCL market</Link>, and are not
counted on this one, so the same demand is never counted twice.
</p>
)}
<IntentsPanel chain={chain} decimals={decimals} symbol={symbol} tiles={8} table />
<PriceChart chain={chain} symbol={symbol} />
+1 -1
View File
@@ -244,7 +244,7 @@ function Placeholder({
? 'Reading…'
: bridged === '0'
? `The chart starts with the first settled ${asset} trade. None has: no ${asset} is bridged to NEAR yet, so none can have traded there.`
: `The chart starts with the first settled ${asset} trade with a price. None has settled in the blocks read.`
: `The chart starts with the first ${asset} trade settled through NEAR Intents' solvers. None has in the blocks read — a solver restocking from a Ref pool is a pool swap, on that market, not a trade here.`
return (
<div
style={{
+285
View File
@@ -0,0 +1,285 @@
/**
* The asset's Ref Finance DCL pools: what they offer, and what has traded.
*
* Kept apart from the NEAR Intents market on purpose. A user who buys through
* 1Click is filled by a solver (a trade on that market), and the solver then
* restocks from this pool (a swap here). Both are real, and summing them would
* count the same demand twice — so they are two markets and never one volume.
*
* The pool contract is on a shard our node does not track, so its state is
* read from a public endpoint and its swaps are found on intents.near's shard
* and fetched whole. A swap made by calling the pool directly is not read, and
* the coverage row says exactly how much of the pool's own recorded volume
* that leaves out (blackbeard/observer#37).
*/
import { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import type { RefPools } from '../api/generated/RefPools'
import type { RefSwap } from '../api/generated/RefSwap'
import type { RefSwaps } from '../api/generated/RefSwaps'
import { RequestFailed, fetchRefPools, fetchRefSwaps } from '../api/rest'
import { ago, height as fmtHeight, price as fmtPrice, tokens, when } from '../lib/format'
import { href } from '../lib/routes'
import { Field } from './Field'
const EXPLORER = 'https://nearblocks.io/txns/'
const REFRESH_MS = 60_000
/** A share as a percentage, or a dash where there is nothing to divide. */
function pct(part: string, whole: string): string {
const w = Number(whole)
if (!w) return '—'
return `${((Number(part) / w) * 100).toFixed(2)}%`
}
/** How each kind of rung reads in the table. */
const KIND: Record<string, string> = {
asks: 'offers the asset',
bids: 'bids for the asset',
current: 'the current point',
}
export function RefDclMarket({
chain,
decimals,
symbol,
}: {
chain: string
decimals: number
symbol: string
}) {
const [pools, setPools] = useState<RefPools | null | undefined>(undefined)
const [swaps, setSwaps] = useState<RefSwaps | null>(null)
const [older, setOlder] = useState<RefSwap[]>([])
const [error, setError] = useState<string | null>(null)
useEffect(() => {
let controller = new AbortController()
const load = () => {
controller = new AbortController()
Promise.all([
fetchRefPools(chain, controller.signal),
fetchRefSwaps(chain, null, controller.signal),
])
.then(([p, s]) => {
setPools(p)
setSwaps(s)
setError(null)
})
.catch((e: unknown) => {
if (controller.signal.aborted) return
setError(e instanceof RequestFailed ? e.message : 'Could not reach the observer.')
})
}
setOlder([])
load()
const id = window.setInterval(load, REFRESH_MS)
return () => {
window.clearInterval(id)
controller.abort()
}
}, [chain])
const asset = pools?.asset_symbol ?? swaps?.asset_symbol ?? symbol
const assetDecimals = pools?.asset_decimals ?? swaps?.asset_decimals ?? decimals
const cursor = swaps?.next ?? null
const loadOlder = () => {
if (!cursor) return
fetchRefSwaps(chain, cursor)
.then((page) => {
setOlder((rows) => [...rows, ...page.swaps])
setSwaps((s) => (s ? { ...s, next: page.next } : page))
})
.catch(() => {})
}
const rows = swaps ? [...swaps.swaps, ...older] : []
return (
<>
<p className="panel-note" style={{ marginBottom: 12 }}>
<Link to={href({ chain, index: 'market' })}>Markets</Link> › Ref Finance DCL
</p>
{error && <p className="empty">{error}</p>}
{!error && pools === undefined && <p className="empty">Reading the pools…</p>}
{!error && pools === null && (
<p className="empty">
Not read yet — the first read is a minute after the observer starts.
</p>
)}
{pools && pools.pools.length === 0 && <p className="empty">No Ref DCL pool holds {asset}.</p>}
{pools?.pools.map((p) => {
const counter = p.counter_symbol ?? p.counter_token
const amount = (raw: string, d: number) => tokens(raw, d)
return (
<section key={p.pool_id} className="panel" style={{ marginBottom: 26 }}>
<div className="panel-head">
<h2 className="panel-title">
{asset}/{counter} pool
</h2>
<span className="eyebrow">
{p.contract} · {(p.fee / 10_000).toFixed(2)}% fee · {p.state} · read{' '}
{ago(pools.checked_at)}
</span>
</div>
<div className="node-grid">
<Field
label="Pool price"
value={`${fmtPrice(p.price)} ${counter}`}
note="where the next swap starts — not a trade"
/>
<Field
label={`${asset} in the pool`}
value={`${amount(p.asset_held, assetDecimals)} ${asset}`}
note="what buyers can take"
/>
<Field
label={`${counter} in the pool`}
value={`${tokens(p.counter_held, 6)} ${counter}`}
note="what sellers can be paid, and LP fees"
title={`${p.counter_held} base units`}
/>
<Field
label="Swaps indexed"
value={fmtHeight(p.indexed_swaps)}
note={`${pct(p.indexed_asset_out, p.asset_out)} of ${asset} bought out, ${pct(p.indexed_asset_in, p.asset_in)} sold in`}
/>
</div>
<div className="scroll-x">
<table className="board">
<caption className="visually-hidden">The pool&apos;s liquidity ladder</caption>
<thead>
<tr>
<th scope="col" className="left">
Price of {asset}
</th>
<th scope="col" className="left">
This range
</th>
<th scope="col">{asset}</th>
<th scope="col">{counter}</th>
<th scope="col">
{counter} to take all its {asset}
</th>
</tr>
</thead>
<tbody>
{p.rungs.map((r) => (
<tr key={`${r.price_from}-${r.price_to}-${r.kind}`}>
<td className="left numeral">
{r.kind === 'current'
? fmtPrice(r.price_from)
: `${fmtPrice(r.price_from)} – ${fmtPrice(r.price_to)}`}
</td>
<td className="left">{KIND[r.kind] ?? r.kind}</td>
<td className="numeral">
{r.asset > 0
? r.asset.toLocaleString('en-US', { maximumFractionDigits: 3 })
: '—'}
</td>
<td className="numeral">
{r.counter > 0
? r.counter.toLocaleString('en-US', { maximumFractionDigits: 4 })
: '—'}
</td>
<td className="numeral">
{r.counter_to_clear > 0
? r.counter_to_clear.toLocaleString('en-US', {
maximumFractionDigits: 0,
})
: '—'}
</td>
</tr>
))}
</tbody>
</table>
</div>
<p className="panel-note">
The ladder is the pool&apos;s liquidity, range by range: what it holds at each price
and what it would take to clear it. A range above the current price offers {asset};
one below it bids for {asset}. These are standing amounts, not trades — and{' '}
<strong>
“swaps indexed” is measured against the pool&apos;s own cumulative counters
</strong>
: anything under 100% is volume made by calling the pool directly, which this observer
does not read. Accounts allowed to swap here: {p.whitelist.join(', ')}.
</p>
</section>
)
})}
{pools && pools.pools.length > 0 && (
<section className="panel" style={{ marginBottom: 26 }}>
<div className="panel-head">
<h2 className="panel-title">Pool swaps</h2>
<span className="eyebrow">newest first</span>
</div>
{rows.length === 0 ? (
<p className="empty">No swap in these pools has been indexed yet.</p>
) : (
<div className="scroll-x">
<table className="board">
<caption className="visually-hidden">Swaps in the pools, newest first</caption>
<thead>
<tr>
<th scope="col" className="left">
Settled
</th>
<th scope="col" className="left">
Solver
</th>
<th scope="col" className="left">
Side
</th>
<th scope="col">{asset}</th>
<th scope="col">Price</th>
<th scope="col" className="left">
Tx
</th>
</tr>
</thead>
<tbody>
{rows.map((s) => (
<tr key={`${s.tx_hash}-${s.near_height}-${s.asset_amount}`}>
<td className="left numeral" title={s.at}>
{when(s.at)}
</td>
<td className="left">{s.trader}</td>
<td className="left">{s.side === 'buy' ? 'bought' : 'sold'}</td>
<td className="numeral">
{tokens(s.asset_amount, assetDecimals)} {asset}
</td>
<td className="numeral">{fmtPrice(s.price)}</td>
<td className="left">
<a href={`${EXPLORER}${s.tx_hash}`} target="_blank" rel="noreferrer">
{s.tx_hash.slice(0, 8)}…
</a>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{cursor && (
<div className="pager">
<button className="segmented panel-button" onClick={loadOlder}>
Older →
</button>
</div>
)}
<p className="panel-note">
A pool swap is a solver trading with the pool — typically restocking after filling a
user on NEAR Intents, which is why these are not added to that market&apos;s trades. The
price includes the pool&apos;s fee. The solver is named from its own intent; the pool
itself records the swapper as intents.near.
</p>
<p className="panel-note">What is read: {pools.reads}</p>
</section>
)}
</>
)
}