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
This commit is contained in:
@@ -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, };
|
||||
@@ -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, };
|
||||
@@ -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, };
|
||||
@@ -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, };
|
||||
@@ -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, };
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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}'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} />
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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'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'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'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's trades. The
|
||||
price includes the pool'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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user