Files
observer/crates/blackbeard-data/migrations/0021_quote_probe.sql
T
grenadeandClaude Opus 5.5 6b3bf76b7e
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
feat: probe whether 1Click will quote QTC, against what could have filled it
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

54 lines
2.5 KiB
SQL

-- Whether 1Click would quote the tracked asset, against whether it could have.
-- blackbeard/observer#45.
--
-- One row per probe: a keyless dry quote for one direction, size and counter
-- token, beside what this observer read for itself at the same moment, which
-- is what the pool would pay on-chain for the same trade and what the solvers
-- held of the output token inside intents.near.
--
-- Only our own observations are kept here. The 1ClickSwap API Terms (§4.1(e))
-- forbid storing API data for more than 24 hours, so a quoted amount is
-- reduced to `markup_bps` against the pool when it arrives, and the response
-- itself goes to `quote_probe_raw`, which is emptied after a day.
create table quote_probe (
chain text not null references chain (id) on delete cascade,
asset text not null,
at timestamptz not null,
-- `sell` gives the asset for the counter token; `buy` the reverse.
direction text not null check (direction in ('sell', 'buy')),
counter text not null,
-- The input, base units of whichever token is given.
size numeric not null,
-- `failed` is the probe not getting an answer (transport, 429, 5xx): a
-- fact about the probe, never about the solvers.
outcome text not null
check (outcome in ('quoted', 'no_liquidity', 'refused', 'failed')),
message text,
latency_ms integer,
-- What the reference pool's own `quote` view gives for the same input, read
-- from our node at the same moment. Null when no pool could be asked.
pool_out numeric,
-- For a quote: how much less than `pool_out` it paid, in basis points.
markup_bps integer,
-- The solver holding the most of the output token inside intents.near,
-- and how much. Solvers do not pool inventory, so one holder is the test.
best_solver text,
best_held numeric,
-- How many solvers each held at least `pool_out` of the output token.
solvers_able integer,
primary key (chain, asset, at, direction, counter, size)
);
create index quote_probe_recent on quote_probe (chain, asset, at desc);
-- The responses themselves, for a day, for debugging the probe. Deleted by
-- the probe loop; never read by the API.
create table quote_probe_raw (
at timestamptz not null,
direction text not null,
counter text not null,
size numeric not null,
status integer,
body jsonb not null,
primary key (at, direction, counter, size)
);