What the latency numbers measure
CoRelayer will publish latency figures in four places: on every intent, in each account's latency report, per shard in the network document, and on the status site. None of them exists yet — nothing has run in production, so there is nothing measured to show. This page fixes what each figure means, so that when numbers appear they can be read precisely and checked.
It describes measurements, not objectives. An objective is published only once there is enough measured data behind it, and it is then shown next to its measured value. (Status and SLOs)
Five timestamps
Every relayed transaction carries up to five timestamps, each in Unix milliseconds:
| Timestamp | The moment | Whose clock |
|---|---|---|
receivedAtMs | The API received your POST /v1/relay. | CoRelayer's |
cosignedAtMs | The signer released the relayer signature — the commit point. | CoRelayer's |
broadcastAtMs | The first gateway acknowledged our hash. | CoRelayer's |
executedBlockTsMs | The timestamp of the block that executed the transaction. | The chain's |
finalizedAtMs | That block became final. | CoRelayer's observation of the chain |
A timestamp that is genuinely unknown is null, never estimated. That happens for rows restored by
orphan recovery after a crash, where the original receive time was never journaled.
CoRelayer's hosts are designed to keep their clocks synchronised against several time sources, to report themselves not ready when the offset grows too large, and to stop minting leases on an unsynchronised clock. That is what bounds the error in the figures below that subtract a chain timestamp from one of ours.
Seven derived figures
Every figure is a difference of two of those timestamps, or a count derived from one:
| Figure | Formula | What it isolates |
|---|---|---|
acceptToCosignMs | cosignedAtMs − receivedAtMs | Our validation, reservation and signing. |
cosignToBroadcastMs | broadcastAtMs − cosignedAtMs | Our path to the network. |
addedMs | broadcastAtMs − receivedAtMs | Everything CoRelayer adds before the network has the transaction. |
inclusionMs | executedBlockTsMs − receivedAtMs | From your request to execution — ours plus the network's round. |
finalityMs | finalizedAtMs − executedBlockTsMs | The network's finality. |
totalMs | finalizedAtMs − receivedAtMs | End to end. |
roundsToInclusion | ceil(inclusionMs / roundDurationMs) | Inclusion counted in chain rounds — roundDurationMs is in GET /v1/network. |
Plus crossShard: whether the transaction left the sender's shard, because a cross-shard effect
takes a further step and belongs in a different distribution.
The split is the point. The first three figures are the part CoRelayer controls; inclusion and
finality are the network's. A service that published only totalMs could hide its own queueing
inside the chain's variance, and a round of 600 ms makes that variance large next to anything a
relayer adds.
Where each figure appears
On one intent
GET /v1/relay/{id} and GET /v1/intents/{sender}/{nonce} return the timestamps and a latency
object with the derived figures. So do the per-intent event stream and the Transactions screen
of the dashboard. This is a record of what happened to one transaction.
For your account
GET /v1/usage/latency reports percentiles over your transactions:
| Parameter or member | |
|---|---|
window | 1h, 24h, 7d or 30d. |
groupBy | shard, relayer, sender, or gateway — the gateway that acknowledged first. |
groups[].added, groups[].inclusion | p50Ms, p95Ms, p99Ms of addedMs and inclusionMs. |
groups[].bins | A server-side histogram of inclusionMs, each bin a count up to leMs; the last bin is the overflow. |
groups[].samples | At most 48 sampled transactions per group, for a strip plot, each with its hash. |
sloTargetInclusionP99Ms | null until an objective is published. |
It is private — native-auth or a read key — because it is our measurement of you. The percentiles
are computed on the server from rollups, not from the samples.
Per shard, in the network document
GET /v1/network carries shards[]: for each shard, addedP50Ms, addedP99Ms,
inclusionP50Ms, inclusionP95Ms, inclusionP99Ms, the number of samples and the windowMs
they cover, the time of the last block, whether the shard is stalled, and relays24h.
On the status site
GET /v1/status, and the status site's own /feed/telemetry.json, carry:
| Member | Meaning |
|---|---|
latency30d[] | Per shard, the same percentiles over the last 30 days — or since measuredFromMs, while the service is younger than that. |
availabilityDaily90[] | One entry per UTC day: the measured availabilityPct and the number of published incidents that touched the day. null for a day without data. |
availability30d | The measured share of valid relay requests that got a BROADCAST or a correct 4xx. null until 30 days of data exist. |
relays24h | Customer relays in the last 24 hours. |
The status site is a separate deployment that reads three feed files — status, incidents, telemetry — so it keeps working when the API does not.
What is excluded
- Synthetic probes. A monitoring host sends a small number of real relayed transactions of its
own, from dedicated low-value sender wallets, to check the whole path end to end. Those rows are
marked internal and are excluded from every public figure —
relays24h,latency30d, the shard percentiles and the marketing site. - Rejected requests. A request that never reached the commit point has no
cosignedAtMsand contributes nothing to latency. It counts in availability only as what it was: a correct 4xx or not. - Unknown timestamps. A
nullstays out of the percentile it would have fed; it is never replaced by an estimate.
Reading a percentile correctly
A percentile over your own transactions is a fact about what happened to you, in a window, with a sample size next to it. It is not a forecast and it is not a promise. When the sample is small the number is noisy, which is why every figure is published with the count or window it came from.