Files
observer/web/src/components/RefDclMarket.tsx
T
grenadeandClaude Opus 5.5 b85e74f1ca
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
feat: index QTC's Ref DCL pool — its swaps, its ladder, and how much we see
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

286 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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>
)}
</>
)
}