How Stabledesk measures stablecoin activity on Arc. ← Back to the terminal
Stabledesk indexes on-chain Transfer events for Arc stablecoins and turns them into
time series, supply metrics and flow analytics — with a deliberate focus on real economic volume, not raw event counts.
Everything is read directly from the chain — no third-party data provider. Blocks and logs come from the
Arc public RPC (with fallback endpoints), chain ID 5042. Arc is an EVM chain where
gas is paid in USDC and blocks are ~0.5s with no reorgs.
Two Transfer logs, one movement. Arc implements EIP-7708, so a native value movement emits its own
Transfer log from the system address 0xff…fe, in addition to any ERC-20 Transfer the token contract emits. Because
gas on Arc is paid in USDC, every transaction on the chain produces one — on Arc testnet, measured in August 2026, that address emitted more Transfer logs than
the USDC contract does. Counting both would report the same money twice. We read logs filtered by token contract address, so a
native log is never seen and never counted; the discovery probe that looks for assets we do not track excludes this address by
name and says so on every run.
Supply is reported per denomination, and never summed across them. Arc carries stablecoins in more than one currency — USDC, USYC and USDT in dollars, EURC in euros, GBPA (Agant's pound) in sterling, tracked on mainnet since 3 October 2026, with cNGN, QCAD, MXNB and ZARU first seen on Arc testnet in August 2026. Adding their face values together produces a figure that is a quantity of nothing: measured here, that sum was 86% euros, so the headline number of a dollar-denominated chain was mostly not dollars, and nothing said so. Converting between currencies would require an exchange rate, and there is no price feed and no oracle anywhere in this project — the same rule that makes the fee figures directly measured rather than estimated. So each denomination is published on its own, a token's share is computed against its own currency, and a tracked asset whose denomination we have not declared is reported separately rather than folded into a total. The same applies to volume: until August 2026 the 24-hour figure was a sum of transferred amounts across currencies, so it answered no question, and every token's share was measured against that mixed total. Volume is grouped the same way now, and each share is a share of the volume moved in its own currency.
We track Arc stablecoins over their ERC-20 interface: USDC (native gas), EURC (euro), USYC (yield / MMF), GBPA (stablecoin). That list is
generated from the running configuration rather than written here, so it cannot drift from what is actually indexed — the full
set of contract addresses is in /openapi.json and on /v1/status. An asset carried by more than one
contract is measured across all of them; see What is counted as a stablecoin below. A historical indexer backfills
recent history on first run, then keeps indexing forward and persists aggregates in SQLite.
Naïve volume badly overstates real activity, for two independent reasons. We correct each with its own filter, and publish all three figures so you can see exactly what every step removes.
1 · Raw volume — every Transfer event, unfiltered.
2 · Real volume — a transaction-level filter. A single user action often emits several
Transfer events (routing hops, contract internals), so within each transaction we keep only the
largest transfer per token and discard the rest. Per token, not per transaction: a swap moves two different
assets in one transaction, and collapsing it to a single leg would erase the smaller asset's volume entirely.
3 · Adjusted volume — real volume, plus an address-level filter. Some addresses are infrastructure rather than economic actors: market-making bots, faucets, sequencers, test harnesses. Any address that sent more than 1,000 transfers or $10M in 30 days is flagged. A transfer is dropped when both of its ends are flagged — infrastructure talking to infrastructure. The thresholds come from the Visa / Allium onchain analytics methodology.
Why "both ends", not "either end". Visa drops a transfer if either end is flagged. That is right for retail-shaped chains, where most transfers run user-to-user and a flagged counterparty is a strong signal of noise. Arc testnet is hub-and-spoke — almost every transfer touches a faucet, router or sequencer — so the "either end" rule deletes genuine payments along with the churn — measured on Arc testnet in July 2026, it removed 99.9% of volume and stopped describing anything. A bot paying a user, or a user funding an exchange, is real value delivered to a real party, so we keep it. Only bot-to-bot movement is discarded.
How flags are applied. What counts is what an address sent: transfers out of it, at face value across the tracked tokens. Receiving is not activity in this sense — an exchange deposit address is sent money all day and is not a bot for it. Each address's sent total is compared with the monthly limits over the sent history we hold: under 30 days of history, with the limits whole; past 30 days, with its average monthly rate (the span, converted to days at the measured block time). On a chain younger than a month that is exactly the published rule, and it is what Visa's 30-day window does on a young chain too. The flag set is recomputed every minute, and the terminal lists the flagged addresses with how much sent history each was judged on, so the arithmetic can be redone rather than trusted.
What this corrected, on 28 September 2026. The limits used to be divided into a daily rate and applied over each address's observed span with a one-day floor, to what it moved in both directions. On history shorter than a month — every address, twelve days after mainnet launch — that held each one to a thirtieth of the monthly limit: an address that moved $400k once in a day was treated as infrastructure. Counting both directions doubled whatever passed through an address and flagged receivers. Measured over the same 30 hours of Arc mainnet, the old reading flagged 4,159 addresses and left adjusted volume at 14% of real; the rule as stated flags 62 and leaves 73%. The page said one thing and the code did another, which is the one failure this page exists to prevent.
What that leaves transitional. Sent counts did not exist before the correction. They
were re-read from the chain for as far back as the RPC still serves logs — about a day and a half — and accumulate from there, so for
the next month monthly totals cover less than a month and adjusted volume sits above where it will settle; the API states
how far back sent history reaches (sentHistorySince). Minutes indexed before the correction were scored under the old
rule and are not restated — the logs they came from are no longer served — so the adjusted series changes rule at
ruleSince, and the 24-hour figure mixes the two for one day after it. Raw and real volume are unaffected.
Flags are derived from the same history they filter, so on a cold start the initial backfill is indexed before any address has been flagged. Rather than ship a figure we know is wrong, the indexer re-reads that range once the first pass has revealed the flagged senders and rewrites the adjusted numbers.
The flag set is capped at 20,000 addresses, ordered by volume, so a pathological
window cannot load an unbounded set into memory. This is a threshold like any other and it belongs here: when more than 20,000
addresses meet the rate limits above, which addresses get flagged stops being decided by those limits and starts being
decided by the cap and the ordering. Adjusted volume is then a lower bound on what the published rule would exclude —
the lowest-volume qualifying addresses fall outside the set and their bot-to-bot transfers stay counted. Raw and real volume are
unaffected. Whether the cap is currently binding is published on every response:
/v1/addresses/filtered reports qualifying (selected by the thresholds), flagged (actually
used), cap and atCap, and carries an explicit warning while the two differ.
The problem. Circle Gateway holds one USDC balance that is spendable across every chain it supports. Instead of pre-positioning liquidity chain by chain, an operator draws it onto a chain on demand — Circle reports replenishment in under 500 ms, and roughly $410M of crosschain volume settled through Gateway between March and June 2026. On-chain, that arrives as a mint. A naïve supply tracker reads that mint as new issuance and reports that demand for USDC on this chain grew. It didn't: the same balance moved here, and it can move away again just as fast.
What we do. Gateway's contracts are in our registry, and every block range is scanned
for logs they emit. Any mint, burn or transfer in a transaction that Gateway touched is attributed to it and reported separately.
Attribution is per transaction, not per counterparty: the Transfer event of a Gateway mint names the end
recipient rather than the minter, so matching on addresses alone would miss it entirely.
Two figures, always both. netIssuance24h counts every mint minus every burn — unchanged, and what
you get if you scan the chain yourself, so our numbers stay reconcilable against yours. organicNetIssuance24h takes
the cross-chain routes out — Gateway's net, and since 18 September 2026 CCTP's net as well (next section). Where the two
diverge, the gap is the USDC that moved between chains rather than being created or destroyed on Arc. Gateway's share of real volume is published the
same way, because a deposit into Gateway is a genuine transfer — it just isn't anyone paying anyone.
Where this applies. Arc is a Gateway chain and is measured as described: Gateway's contracts are 0x77777777dcc4d5a8b6e418fd04d8997ef11000ee and 0x2222222d7164433c4c09b0b0d809a9b52c04c205. Where Gateway is not deployed, every
bridge-adjusted figure is returned as null rather than 0: nothing was measured, so nothing is claimed — a zero
here would read as "we checked and there was none".
The problem. Circle CCTP moves USDC between chains by burning it on the source chain and minting the same amount on the destination. On Arc, a dollar arriving from Ethereum shows up as a mint and a dollar leaving for Polygon as a burn. Read as issuance and redemption, that is wrong twice: nothing was created or destroyed, it changed chains. Measured on 17 September 2026, the first full day after mainnet launch, Arc's USDC supply fell 9.52M — and CCTP explained all of it: 17.85M left through CCTP and 5.56M arrived, while mints and burns outside CCTP added 2.77M. Until this date that day read here as a contraction of issuance.
What we do. Attribution is per transaction, as for Gateway: a mint or burn is CCTP when the same
transaction carries a MintAndWithdraw or DepositForBurn event from TokenMessengerV2. Matching the
Transfer event alone cannot work — an inbound mint names its recipient, never the minter. Both events are counted in
mint/burn and in their own pair (cmint/cburn), never instead of them.
When a transaction touches both CCTP and Gateway. Each mint and each burn is decided on its own: a mint is
CCTP's when CCTP minted in that transaction (MintAndWithdraw), a burn is CCTP's when CCTP burned in it
(DepositForBurn), and otherwise it is Gateway's. CCTP's event says who minted or burned; a Gateway log says where the
USDC came from or went next. Deciding per transaction got this wrong both ways. Until 30 September 2026 Gateway took
precedence, and from 29 September that mislabelled the main way USDC reaches Arc: it arrives by CCTP and is deposited straight
into the Gateway wallet. On 29 September, 13.1M was published as "Gateway mint" that CCTP had minted, while Gateway's own
minter minted next to nothing; the per-chain list, read from CCTP's events, counted it too, so the CCTP total sat below the sum
of its own sources. The fix of 30 September gave CCTP precedence for the whole transaction instead, which filed a Gateway mint
as a CCTP arrival whenever the same transaction then bridged the USDC straight out by CCTP — on 30 September, 4 transactions
and 1.545M. Both were corrected on 1 October: the affected minutes were re-read and moved as far back as the RPC still serves logs
(reroutedSince); before that the amounts involved were small — 1.5M in total between 17 and 28 September.
Net issuance with cross-chain movement taken out is unchanged either way: each mint was always subtracted exactly once.
Which chain. DepositForBurn carries the destination domain. An inbound mint carries none; the
source domain is on the MessageReceived event that MessageTransmitterV2 emits right after it, so each mint is paired
with the next unpaired MessageReceived in its transaction — which holds when a relayer batches several messages into
one transaction. Domains are named from Circle's published table; a domain missing from it is published as its number, never guessed. The inbound
amount is the event's amount plus its feeCollected, because the relayer fee is minted on Arc too.
What is not covered yet. CCTP is not USDC-only, but other assets take a different entry contract:
Circle routes EURC and registered third-party tokens through CrossChainTokenService, not TokenMessengerV2. Its Arc
address is not in Circle's published contract list as of 18 September 2026, so those flows are not attributed here: a EURC
mint arriving that way is still counted as issuance. Every CCTP figure is already published per token, so when that contract is
read, euros cannot land in a dollar total.
History. Mints and burns indexed before this measurement existed carried no route.
They are re-read once — only mints, burns and CCTP events, newest first — and measuredSince on
/v1/cctp states how far back attribution reaches. On Arc mainnet it stops at 15 September 2026,
14:07 UTC, the day before public launch: the primary RPC endpoint keeps about 70 hours of logs, and the five hours before
that were no longer served when the re-read reached them. Those hours stay counted as issuance, and measuredSince
says so. On Arc the contracts read are TokenMessengerV2 0x28b5a0e9c621a5badaa536219b3a228c8168cf5d and MessageTransmitterV2 0x81d40f21f12a8f0e3252bccb954d722d4c464b64; burned USDC passes through TokenMinterV2 0xfd78ee919681417d192449715b2594ab58f5d002.
Tracked assets are listed by contract, not by symbol, because a symbol can be deployed more than once on the same chain. Arc testnet carried, as of August 2026, two independent USYC contracts — one an EIP-1967 proxy holding 1.38M and essentially dormant, one a standalone contract holding 12.2M with all of the activity (722 transfers against 1, over the same six-day sample). Nothing on-chain says which is canonical, and we will not guess: both are measured, and the chain's USYC supply is reported as the sum. Both addresses appear in the API's token list. If Circle publishes a canonical address, the other is removed and this paragraph goes with it.
Wrapped USDC is deliberately not counted as issuance. Its supply is exactly the USDC it custodies — 68,920,819.86 on both sides when measured — so reporting it as a separate stablecoin would count the same dollars twice, once under USDC and once under WUSDC. It is in the registry instead, where the USDC it holds is measured as value locked in a contract. Same reasoning as Circle Gateway above: a wrapper moves value, it does not create it.
Native USDC transfers are a known blind spot. USDC is Arc's gas asset, so it
exists twice: as the ERC-20 contract we index, and as the chain's native balance, whose movements the protocol surfaces at the
reserved address 0xffff…fffe at 18-decimal scale. Measured over a 44-hour sample, 87% of native transfer
events are exact duplicates of an ERC-20 event we already count — same transaction, same parties, same amount — so
indexing that address would roughly double every USDC figure on this site. The remainder is real and uncounted:
24% of native transfer events, but 4.1% of native volume, and inspection of a sample showed most of it is
msg.value being forwarded between contracts inside one transaction, which is exactly what the real-volume filter
removes anyway. Plain wallet-to-wallet native payments exist and are not counted. We would rather state the gap than close it
with a number that double-counts.
Gas on Arc is paid in USDC. That makes fee metrics unusually direct: a fee figure is
already a dollar figure, with no token price, no exchange rate and no oracle in the path. We read fees from
transaction receipts — gasUsed × effectiveGasPrice, summed per block — which captures the priority
tip as well as the base fee. A median is published beside the mean. The per-transaction fee on Arc is strongly
skewed: measured on mainnet launch day over 2,002 transactions, the median was 0.048 USDC and the mean 0.082, with a
maximum of 8.35. A few heavy transactions pull the mean well above what a typical transaction pays, and because one of
them can fall in any sampled block, the mean moves with the sample. The median is read from the same sampled
transactions, counted into logarithmic buckets — forty per decade, interpolated — which costs a few rows per minute
instead of one per transaction.
Sampling. Receipts only exist per transaction, and at ~0.5s blocks fetching every one would mean ~172,000 requests a day against a rate-limited public RPC. So we sample a few blocks per poll, at a randomised offset behind the head. Each sampled block is exact; the per-day and per-window figures derived from them are extrapolations. The terminal and the API always report how many blocks a fee number rests on — an estimate is never presented as a measured total.
Cost to move $1M divides total network fees by real volume, not adjusted volume. The fee total in the numerator includes every transaction, high-frequency addresses included; dividing it by only the non-bot volume would price the whole network's cost against a fraction of its throughput and badly overstate it.
In one currency. Fees are paid in USDC, so the volume is the dollar stablecoins'
real volume only. It used to be every tracked token's volume added together, euros included: on 28 September 2026 that put
23.3M of EURC volume into a 97.0M denominator and the figure read about 24% too low. Corrected the same day; the API names the
currency in perMillionMovedDenomination.
TVL is measured as stablecoin balances held by contracts — balanceOf on each
indexed asset, for every address we have established has bytecode. On a chain where value is denominated in USDC this is what TVL
means: there is no volatile base asset to price and no LP share maths, so no per-protocol adapter is needed. On Ethereum the
same method would be worthless; here it is the direct measurement.
Only contracts count. Contract-ness comes from eth_getCode, never from a heuristic. A balance sitting in
a plain wallet is someone's money, not value locked in a protocol, and is excluded.
Per currency, never summed across currencies. TVL follows the same rule as supply and volume: each currency is totalled on its own, and every ranking and share is computed in the currency gas is paid in (USD on Arc). A euro held by Aave is not a dollar held by Aave; adding them produced a figure that was a quantity of nothing — on 28 September 2026, 106.5M of USDC and 3.7M of EURC read as "110.2M". The daily TVL series is kept per currency from that date. Earlier days summed the two and counted smart-contract wallets (below), so they are not joined onto the new series.
Smart-contract wallets are wallets. Account abstraction breaks the bytecode test: a smart account has code and is still somebody's wallet. Accounts identified as such — EIP-1967 proxies whose implementation is Circle's single-owner modular account, and since 20 October 2026 Safe multisigs, whose slot 0 holds one of Safe's canonical singletons — are named on the ecosystem page and kept out of TVL, on their own line. On 28 September 2026 two of them held $100.8M of the $211M then reported as Arc TVL; from that day the total excludes them, and earlier days are not restated.
An EIP-7702 account is not a contract at all. An ordinary account can delegate to wallet code, and it then
answers eth_getCode with a 23-byte designator, 0xef0100 followed by the delegate's address. A private key
still signs for it. Until 20 October 2026 any code at all meant "contract", so these accounts were scanned and their balances
counted as unattributed TVL. They are now classified as wallets when first seen, and those recorded earlier were re-read and moved out.
What this corrected, on 20 October 2026. Among the fifty largest unnamed holders that day, five were EIP-7702 accounts ($418k) and five were Safes ($252k, €71k and £167k — three quarters of everything then counted as sterling TVL). Both now sit outside the total. Four were Aero pools ($1.23M, €221k, £61k), now attributed to Aero by its factory. Earlier days are not restated.
Unattributed value is reported, never hidden. A contract holding a balance that no registry entry claims is counted in the chain total and listed separately as unattributed. We publish the share of TVL we can actually name, next to the total — an ecosystem page that quietly drops what it cannot label is understating the chain, and one that assigns it to a plausible protocol is inventing data.
Registry provenance. Each protocol entry records how we know its contracts: canonical (a deterministic
address identical on every EVM chain), team (the operator confirmed or published it — its docs, its address book), or
observed (we found and classified it ourselves — which is not a claim about who operates it). Only the first two are marked
verified. Two entries may never claim the same contract, because that would double-count its balance.
Contracts nobody lists one by one. Pools, vaults and smart accounts are created without a registry edit, so
rules attribute them, each on the chain's own answer. A pool belongs to a protocol when that protocol's factory, asked for the pool's
tokens and fee (getPool / getPair; for Aero's Slipstream pools, tokens and tick spacing), returns the pool's
address — a contract that only claims the factory is not attributed. A Morpho vault belongs to Morpho when Morpho's published vault factory answers true to
isVaultV2(address), from the list it keeps of the vaults it created. A proxy belongs to an entry when its EIP-1967
implementation slot points at the implementation listed for it, and a Safe when its slot 0 is exactly one of the singletons listed.
Each attributed contract carries the fact that decided it (factory:0x…, implementation:0x… or
singleton:0x…). A contract checked against the rules is checked again when they change,
rather than after the usual week.
What adding Morpho's vaults corrected, on 3 October 2026. A Morpho vault keeps the deposits it has not yet put into a market; the rest sits in Morpho's market contract, which was already counted. That idle balance was counted in the chain total but under no protocol: on 3 October, 2.00M USDC across three vaults (1.94M in one Steakhouse vault alone) and about 0.30M EURC in a Galaxy vault, against 42.8k USDC in the market contract. Morpho's own address list publishes the factory, so the vaults are now Morpho's. The chain total does not move; the share of it we can name does.
How many contracts are measured, and which. One pass reads balanceOf for every tracked asset against
every target, so the number of targets per pass is capped — at 600 — to keep a pass inside the public RPC's rate limit.
That budget is split rather than spent on the same contracts every time. Registry addresses are always read, because a listed protocol
with no measured balance is indistinguishable from one nobody looked at. The 300 highest-value contracts are re-read
every pass, because they decide the total and a stale reading there moves the headline figure. The remaining budget
rotates through every other known contract in address order, in two lanes. The fast lane carries contracts
that hold a balance, have never been scanned, or moved a tracked token within the retention week — the rows the total is made
of, plus the ones that could change it. The slow lane re-confirms contracts already verified empty and quiet, on a longer
published cycle; any activity promotes a contract back to the fast lane on the next pass. The split exists because discovery
admits every contract seen emitting a Transfer, and on a test network that universe grew tenfold in four days — a single flat
rotation was spending most of its budget re-reading zeros while actual holders queued behind them. Contracts enter that universe from two sources: addresses seen moving a tracked stablecoin, and
contracts seen emitting any Transfer on chain — the second because the first is kept to a rolling week, so a
contract that received a stablecoin and then sat still would otherwise never be measured at all. That is the wrong thing to
miss: holding value without moving it is what TVL is. A few slots per pass are reserved for the stalest readings still counted in the total,
whatever their provenance — so oldestReadingMs is bounded by construction, not by the selection policy staying
consistent with every balance the table has ever recorded.
What this replaced. Until August 2026 the same top 600 by balance were scanned on every pass, and the rest were never scanned at all — 600 of 2,105 known contracts, so the published total covered 29% of them. That is a systematic blind spot rather than a sampling one: the same contracts were missing every time, and waiting did not fix it. Simply raising the ceiling is not the remedy either — a pass already spends 90 seconds in deliberate inter-batch delay inside a five-minute cycle, and tripling the targets would make a pass outlast its own schedule.
The cost, stated. Rotating means some balances in the total were read a few cycles ago.
TVL is a level and moves slowly, so this buys complete coverage for very little accuracy — but a figure that mixes fresh and older
readings has to say so. /v1/tvl publishes coverage.cycleLength (how many passes cover everything),
coverage.oldestReadingMs (the oldest reading still counted), and coverage.atCap. Note that
atCap now means "one pass does not cover everything", not "the rest is never read" — different claims about the same
number.
Known limits. Value held in assets other than the indexed stablecoins is invisible to this
method. A protocol's own accounting may therefore be higher than ours, and where we know that to be the case the entry says so. TVL
history begins the day measurement began — nothing is back-filled or estimated. Registry submissions and corrections:
PROTOCOLS.md.
| Metric | Definition |
|---|---|
| Raw volume | Sum of every Transfer event value. |
| Real volume | Sum of the largest transfer per transaction, per token (routing hops & contract internals removed). |
| Adjusted volume | Real volume, minus transfers where both ends are flagged senders. |
| Transfers | Count of transfer events in the period. |
| Mint / Burn | Transfers from / to the zero address 0x000…000 — supply created / destroyed on-chain. |
| Net issuance | Mint − Burn over the period: net new supply minted on-chain. |
| Supply | ERC-20 totalSupply() read live from each token contract. |
| Dominance | A token's supply ÷ total tracked stablecoin supply. |
| Velocity | Real transfer volume per day ÷ supply — how often the supply turns over. Corrected on 30 October 2026: the last 24 hours of real volume were divided by all the history the per-minute aggregates hold — a rolling seven days — so every velocity published since the mainnet launch on 16 September was too low, about seven times once a week of history had built up, for every token. On Arc mainnet on 29 October, USDC showed about 0.03× where its supply was turning over about 0.22× a day. It is now divided by the 24 hours it was summed over, or by the history held while that is shorter. |
| Active addresses | Distinct addresses that sent or received in the trailing window (e.g. 1h). |
| Size distribution | Histogram of transfer amounts bucketed by magnitude, over the retained transfer window (published with the counts, since a row cap makes its span depend on throughput). |
| Avg fee / tx | Total fees ÷ transaction count over sampled blocks, in USDC. Measured from receipts, not estimated from a gas assumption. |
| Fees / day | Mean fee per sampled block × blocks per day — a run rate, not a cumulative total. |
| Cost to move $1M | Total network fees ÷ real dollar-stablecoin volume, scaled to $1M. What the network charges to move a million dollars. |
| Flagged address | An address that sent more than 1,000 transfers or $10M in 30 days — the monthly limits whole on history shorter than a month, its average monthly rate beyond. |
| TVL | Sum of indexed stablecoin balances held by contracts, read with balanceOf, per currency. A contract is an address with bytecode, except an EIP-7702 delegation designator, which marks an ordinary account; smart-contract wallets are kept out. |
| Attributed TVL | The part of TVL held by contracts a registry entry claims. The remainder is reported as unattributed. |
| Protocol "moved" | Transfer volume in and out of a protocol's contracts over the last 7 UTC days, today included, per currency; transfers are counted over the same days. Corrected on 27 October 2026: the figure was labelled a ~7-day window but was a running total per address that only reset after a week without activity, so for a busy contract it covered everything since it was first seen — Morpho showed 1.93B where its contracts had moved 107M dollars in the past week. It also added every token's face value together. It is now counted per address, token and UTC day. Daily counting began that day, so until it reaches back a week the page says "since" with the start date instead of passing a shorter span off as seven days. |
| Observed | A listed protocol we have seen hold a balance or move value. Distinguishes a live entry from one only listed. |
Arc never reorgs, so a transfer's timestamp is derived from its block number rather than by fetching a header for every block — which on a rate-limited public RPC is not affordable. Each indexed range carries the real timestamps of its own first and last block, read in the same request as the logs, and blocks in between are interpolated across them. Ranges are 500 blocks, so the error is bounded by however much the block time varies inside four minutes of chain.
What this replaced. Timestamps used to be extrapolated from a single rolling anchor at the average block time — and that average was initialised to an assumed 500 ms and only measured once the first live poll ran, which happens after the initial backfill. So a freshly-seeded history was timestamped at an assumption: on a chain running at 1s, the far end of a 20,000-block backfill landed nearly three hours from where it happened. Per-minute aggregates are additive, so that is not a figure that can be repaired later. The anchor still exists as a fallback for a range whose headers came back empty.
Which clock the windows use. Minute buckets are keyed by chain time. So every
rolling window — 24h volume, the fee window — ends at the newest minute actually measured, not at our wall clock, and never at a time
the data has not reached. On a healthy chain those are seconds apart. When they are not, windowEnd says which instant the
window ends at and clockSkewSec says how far that is from the wall clock. Anchoring to the wall clock instead would
publish "24h volume: 0" over a database holding days of transfers, which is the failure this makes visible rather than silent.
Three retentions, because the tables answer different questions:
Per-minute aggregates — rolling 7 days. A row per minute per token grows without bound, so it is a window.
Per-day aggregates — kept indefinitely. Before they are pruned, minutes are rolled into their day in the same
transaction that deletes them, so every minute is counted exactly once and the two tables never overlap. A row per day per token is
about a kilobyte a year, so there is no reason to throw it away. Ranges past 7 days are answered from this rollup, and every series
response states which table answered (source) and how far back the record reaches (recordBegan) — a 90-day
range drawn from a rollup that started last week is a one-week series wearing a 90-day label unless it says so.
Individual transfers — a 24-hour window, bounded by row count. This is what the size distribution, the per-token recent lists and an address's recent activity are read from, and the row cap means the span it covers is a function of how busy the chain is. So the span is measured and published rather than assumed. It was previously capped at 1,200 rows, which at the ~954k transfers a day Arc testnet was doing in July 2026 is 114 seconds — every one of those surfaces was describing the last two minutes while being labelled as something broader.
Largest transfers — top 100 per day per token, kept 180 days. Recorded separately for the
same reason: "the largest transfer" read out of a row-capped table only ever means "the largest of the last few minutes".
/v1/transfers/largest takes an explicit days and states the window it answered over.