The data layer
Coverage and confidence
Know how much of the chain Roundhouse sees, and which published figures are exact.
Two separate questions decide what a figure here is worth: coverage (did we see it?) and confidence (is it what we think it is?). Blending them produces numbers that look authoritative and are not.
This page is what to believe.
Confidence: is this row really an x402 payment?#
verified_x402 is three-valued, and the three are genuinely different claims:
| Value | Claim | Evidence |
|---|---|---|
true | This was an x402 settlement | An EIP-3009 AuthorizationUsed log in the transaction receipt |
null | Unexamined | Matched a catalogued payTo; heuristic confidence is low — usually probably not |
false | Disproven | Examined, and it was a plain transfer, not an authorized payment |
The true case is the only one with proof. A USDC transfer to a known service wallet is evidence
of a payment; the EIP-3009 marker is proof that the transfer went through an authorization rather
than a plain transfer() call.
-- Say what you mean.
select count(*) from settlements
where payee = lower('0x…') and verified_x402Warning
mv_entity_rollups — and therefore every lifetime figure on a profile page —
sums all three values. The pages that use it say so. When a number needs to
mean proven, compute it yourself with the filter above. Fixing the rollups
needs new matview columns, and it is known outstanding work rather than a
subtlety we are hiding.
/stats leads with the verified population and discloses the rest in words. The splits
reconcile exactly against the blended totals, so the view is a decomposition rather than a competing
number.
Coverage: did we see it?#
Capture runs continuously against each supported chain, with a cursor per chain. Three mechanisms keep it honest, and they check different directions:
The reorg sweep asks "do we hold rows the chain no longer has?" and deletes them. Base's finality depth is set to 2 confirmations, which is only safe because the 24 blocks behind the cursor are re-verified every run. Settlements are immutable and duplicate inserts are ignored, so a row for a reorged-out block cannot be fixed by re-ingesting — it has to be removed.
The continuity audit asks the mirror-image question: "does the chain have settlements we do not hold?" and queues the range for repair. It compares distinct transactions rather than rows, because a batch relay is one transaction with several settlements and a fee-proxy collapse is two legs folded into one row. It is deliberately conservative: an empty marker set is treated as no information, not as an empty chain.
Gap repair drains the queue through the same path a live settlement takes — marker-verified, collapsed, attributed. A recovered settlement is not a lower-grade row.
Between them, the chain arbitrates our record in both directions.
What /status tells you#
/status is the honest surface, and its verdict reads the whole page rather than just
reachability:
| Signal | Means |
|---|---|
| Capture lag per chain | How far behind the tip we are |
| Open gaps | Ranges the audit found and repair has not yet drained |
Gap count null | The ledger was unreadable — never "no gaps" |
| Rollup staleness | How old the pre-computed aggregates are |
| Service freshness | When the catalog was last crawled and enriched |
Open gaps existing is not an incident: the audit finds, repair drains, that is the system working. What is worth alerting on is a range repair gave up on, or one nothing is draining.
Note the asymmetry: capture can be at the tip with a hole behind it, so lag does not imply completeness. That is exactly why the gap count is published separately.
Figures that are exact, and figures that are floors#
| Figure | Status | Why |
|---|---|---|
| Settlement counts over a bounded range | Exact | Counted directly from an index range |
| Settled volume, anywhere | A floor | amount_usd is NULL for unpriced tokens |
| Distinct payers / payees | Exact for what is indexed | Bounded by coverage, not by pricing |
| Per-entity lifetime totals | Blended confidence | Sums true, null and false |
| Service listing counts | Exact matched count | From a counting query, not a fetch window |
| "Nobody has paid this" | Not assertable | Absence of a row is not proof of absence |
Two habits behind those rows:
- A price is never guessed. A
NULLusd_ratemeans captured-but-unpriced. So a count and a sum are computed over different populations, and/statsstates the unpriced count. - A fetch window is not a dataset. A list that reads 600 rows must not print "600" as a total — the directory shows an exact matched count from a counting query and cites the indexed total separately.
What is not indexed#
- Chains we do not observe. Coverage is Base and Base Sepolia today. A payment elsewhere is invisible here, and its absence says nothing about the counterparty.
- Off-chain payments. Invoices, cards, credits. Not our ledger.
- Testnet as economic activity. Base Sepolia settlements are indexed but are not real volume.
- Anything private. There is no private tier of the dataset; paid tiers buy rate limit, not access.
Reading a number responsibly#
Three questions, and the answers are all above:
- Is it filtered on confidence? If not, it blends proven with unexamined.
- Is it a count or a sum? Sums are floors, because unpriced rows are absent from them.
- Is the coverage window clean? Check
/statusfor open gaps in the range you care about.
Answering those three is the difference between a figure you can publish and a figure that is approximately right.
Next steps#
- The data model — the columns behind these
- SQL over the index — computing it yourself
- Platform status — live coverage