Every USDC bridge-in and bridge-out on Arc mainnet (chain 5042) during its public launch day, 2026-09-16 00:00 to 23:59:59 UTC, as a proven, forkable Chainplot release.
This repository is a complete Chainplot
project, published as a worked reference for building one: a chainplot.yaml
that declares the chain, the sources and the block range, events-only ABIs,
SQL models and queries, and a Compose file for the ingest runtime. Nothing
here is Chainplot itself — it is the ~20 files a project of your own would
contain.
Chain: Arc mainnet · Range: blocks 21,068,653–21,239,146 (170,494
blocks, ~0.5 s each, pinned, finalized) · Sources: CCTP v2
MessageTransmitterV2 + TokenMessengerV2, Circle Gateway GatewayMinter,
and Arc's EIP-7708 system emitter filtered to native mints.
Exact figures from the built release (blocks 21,068,653–21,239,146, coverage proven complete):
| USDC minted natively on Arc that day | 537,967,083.735531 |
| of which minted onto the chain, not bridged | 435,982,583.20 across 32 mints, 27 of them to one address, largest 192,393,761.73 at 00:26 |
| of which CCTP v2 | 101,570,558.7132 across 29,702 messages from 26 origin chains |
| of which Gateway | 413,941.822331 across 714 mints from 10 origin chains |
| USDC bridged out via CCTP | 20,447,866.771103 across 2,509 burns to 20 chains |
| Net CCTP inflow | 81,122,691.942097 |
| Inbound messages attested at fast finality | 71.4% by count, 24.6% by value |
-
Base sent the most transfers, Ethereum sent the most dollars. Base: 16,135 transfers (54%) but 10.3M USDC. Ethereum: 6,171 transfers, 48.2M USDC. Polygon PoS is second by value (35.5M) on 553 transfers, almost all of it one recipient taking 198 slices of 999,999 USDC roughly hourly.
-
Four out of five minted dollars never crossed a bridge. CCTP and Gateway together explain 19% of native mints. The rest was minted straight onto the chain, in bursts at 00:00 and 12:00–18:00 UTC.
Checked against the chain outside this dataset: all 32 of those mints are
mint(address,uint256)calls on the USDC contract, sent by two accounts that hold its minter role, with allowances of 962.2M and 9,482 USDC. The three addresses they minted to hold that role as well, each with its allowance spent to zero. CCTP'sTokenMinterV2and theGatewayMinterare configured minters too, which is why their events reconcile against the native stream; an ordinary bridge-in recipient is not one. So every route onto Arc runs through a configured minter — the bridges as contracts, this one as accounts.None of those addresses carries a public label on the Arc explorer and none exists on Ethereum, so who operates them is not established here. The minter role belongs to the issuer by the contract's design, but that is the role, not an identification.
-
Three streams agree to the unit. Native mints inside CCTP transactions equal MintAndWithdraw amount + fee exactly; native mints inside Gateway transactions equal AttestationUsed values exactly.
-
Money flowed in, not out. 12:1 inbound to outbound by count, 5:1 by value. Outflows went mostly back to Ethereum (15.0M) and Base (2.8M).
-
CCTP carried more than dollars. 208 of the 29,702 inbound messages minted no USDC, and all 208 are accounted for: they minted cirBTC (80), EURC (77) and WETH (51) instead, through a message handler separate from
TokenMessengerV2. They are counted here as inbound messages with a zero USDC amount, which is what this dataset measures.
- One project, one chain, four sources. Chainplot indexes one chain per
project; every dataset is in scope for every query, so
models/inflows.sqljoins across sources without any external data. - Pairing mints to messages.
receiveMessage()mints (emittingMintAndWithdraw) before it emitsMessageReceived, so the mint for a message is the nearest precedingMintAndWithdrawin the same transaction. Every one of the 29,494 mints paired with exactly one message.log_indexis exported as text, so the model casts it before comparing. - Rails by transaction membership, not arithmetic.
models/native_rails.sqltags each native mint by whether its transaction also emittedMintAndWithdraw(CCTP) orAttestationUsed(Gateway). What is left was minted onto the chain directly, and the rails table shows each bridge's own total beside the native mints found in its transactions. The minter is the transaction sender, which is not part of the event, so it is not indexed here. amount + feeCollected.MintAndWithdraw.amountis the net credited to the recipient; the fast-transfer fee is minted separately to Circle's fee recipient. The sum is what the message created on Arc, and is what reconciles against the native stream.- The native mint stream is one indexed filter. Arc logs every native
USDC movement from
0xffff…fffeas an ERC-20-shapedTransfer, soindexed_1: [0x0]is the whole of "all USDC minted on Arc". Values are 18-decimal there and 6-decimal everywhere else;queries/rails.sqlinteger-divides by 10^12 before comparing. - Domains are a model, not a lookup service.
models/domains.sqlis the CCTP v2 domain table from Circle's docs. An unknown domain renders asdomain Ninstead of disappearing, and a fork can extend the list. block_budget: 200000. Launch day is 170,494 blocks. One job covers it; the 30 min wall clock still applies, andapplyresumes.release_mode: dataset_referenced. About 100k rows in total, so the parquet ships beside the page and a fork can recompute every number.
Needs an archive-capable Arc mainnet RPC (a Chainstack Arc node works; the
public rpc.mainnet.arc.io caps eth_getLogs at 2000 results and
rate-limits, so it will not finish). Build the producer image once from a
Chainplot checkout (see compose.yaml), then:
# fill in RPC_URL in .env (already created from .env.example)
docker compose up -d
./ingest-loop.sh # plan -> apply until coverage closes the range
docker compose exec producer chainplot build --json
docker compose exec producer chainplot serve --host 0.0.0.0 --port 4173 --jsonThen open http://127.0.0.1:4173.
The release is live on the production bucket behind the custom domain:
- Dashboard: https://chainplot.chainstacklabs.com/arc-inflows/
- Pointer it reads: https://chainplot.chainstacklabs.com/arc-inflows/latest.json
Each publish uploads a new immutable release directory and moves the pointer; earlier releases stay readable at their own URLs. The dashboard link above is the publish root, which forwards to whichever release the pointer names, so it keeps working across republishes — a link straight to a release directory does not, since the directory is named after its content.
It was published in dataset_referenced mode, so the five parquet snapshots
(~18 MB) sit beside the page with checksums in the release. Anyone can fork
it and recompute, or add queries, with no RPC and no credentials:
git clone https://github.com/chainstacklabs/chainplot && cd chainplot && pnpm install && pnpm build
node dist/cli/main.js fork --from https://chainplot.chainstacklabs.com/arc-inflows --output ../arc-fork --json
cd ../arc-fork && node ../chainplot/dist/cli/main.js build --jsonfork verifies every file against the release checksums, pulls the parquet
in, strips the chain sources and publish targets, and leaves a dataset-only
project. Rebuilding it reproduces every published result file byte-for-byte in row
content, on any machine in any timezone; a query dropped into queries/ and
listed in chainplot.yaml runs against the same data on the next build.
Ranked queries carry explicit tie-breakers so equal amounts order the same
way everywhere.
To republish after a change: docker compose exec producer chainplot publish --publish-target r2 --json
(needs the R2 key pair in .env; the target is the first entry in
publish_targets).
If plan refuses the finalized block tag on your endpoint, switch the
chain source to finality: { policy: confirmation_depth, depth: 64 }; the
range is a week old, so either policy proves the same blocks.
| File | Purpose |
|---|---|
chainplot.yaml |
Four sources, five datasets, four models, twenty queries, one dashboard |
abis/ |
Events-only ABIs for MessageTransmitterV2, TokenMessengerV2, GatewayMinter, and the native Transfer |
models/domains.sql |
CCTP v2 domain id → chain name |
models/inflows.sql |
One row per inbound message with its minted USDC, chain and finality mode |
models/hourly.sql |
Per-UTC-hour inbound/outbound value and counts |
models/native_rails.sql |
Every native mint tagged cctp / gateway / direct |
queries/ |
KPIs, per-chain bars, hourly series, largest bridge-ins, top recipients, mints by rail per hour, the rails reconciliation, largest direct mints |
ingest-loop.sh |
Drives plan → apply until the pinned range is covered |
- Arc contract addresses: https://docs.arc.io/arc/references/contract-addresses
- Arc USDC system events (EIP-7708 emitter): https://docs.arc.io/arc/references/usdc-system-events
- CCTP v2 domains: https://developers.circle.com/cctp/cctp-supported-blockchains
- CCTP v2 contracts: https://github.com/circlefin/evm-cctp-contracts (
src/v2/) - Gateway contracts: https://github.com/circlefin/evm-gateway-contracts (
src/modules/minter/Mints.sol)