How Stabledesk measures stablecoin activity on the Arc. ← Back to the terminal
Stabledesk indexes on-chain Transfer events for the 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.
We track the official Arc stablecoins (ERC-20 interface): USDC (native gas token), EURC and USYC (yield / tokenized MMF). 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 whose activity rate exceeds 1,000 transfers or $10M of volume per month 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 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 the testnet, 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. Our retention window is a rolling ~7 days rather than a calendar month, so the monthly thresholds are converted to a daily rate and multiplied by however many days of history we actually hold. That window is floored at one full day, so a freshly-restarted indexer can't brand an ordinary address a bot off a handful of transfers. The flag set is recomputed every minute, and the terminal lists the currently flagged addresses — the filter is auditable, not a black box.
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 high-frequency addresses and rewrites the adjusted numbers.
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.
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.
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.
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 it), 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.
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 high-frequency addresses. |
| 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. |
| 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 recently indexed transfers. |
| 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 volume, scaled to $1M. What the network charges to move a million dollars. |
| Flagged address | An address exceeding 1,000 transfers or $10M volume per month, pro-rated to the retained window. |
| TVL | Sum of indexed stablecoin balances held by addresses with bytecode, read with balanceOf. |
| Attributed TVL | The part of TVL held by contracts a registry entry claims. The remainder is reported as unattributed. |
| Protocol "moved" | Transfer volume across a protocol's contracts over the retained window (~7 days) — not 24 hours. |
| Observed | A listed protocol we have seen hold a balance or move value. Distinguishes a live entry from one only listed. |
Because Arc blocks are ~0.5s and never reorg, a transfer's timestamp is derived from its block number against a rolling anchor (a recent block → timestamp reference) rather than fetching every block header — this keeps the indexer light on the rate-limited public RPC. Aggregates are stored per minute; the live snapshot refreshes every few seconds, and the header shows a live / delayed status. History is retained on a rolling ~7-day window.