Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Where Arc's first dollars came from

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.

What launch day looked like

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's TokenMinterV2 and the GatewayMinter are 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.

Why it is shaped this way

  • One project, one chain, four sources. Chainplot indexes one chain per project; every dataset is in scope for every query, so models/inflows.sql joins across sources without any external data.
  • Pairing mints to messages. receiveMessage() mints (emitting MintAndWithdraw) before it emits MessageReceived, so the mint for a message is the nearest preceding MintAndWithdraw in the same transaction. Every one of the 29,494 mints paired with exactly one message. log_index is exported as text, so the model casts it before comparing.
  • Rails by transaction membership, not arithmetic. models/native_rails.sql tags each native mint by whether its transaction also emitted MintAndWithdraw (CCTP) or AttestationUsed (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.amount is 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…fffe as an ERC-20-shaped Transfer, so indexed_1: [0x0] is the whole of "all USDC minted on Arc". Values are 18-decimal there and 6-decimal everywhere else; queries/rails.sql integer-divides by 10^12 before comparing.
  • Domains are a model, not a lookup service. models/domains.sql is the CCTP v2 domain table from Circle's docs. An unknown domain renders as domain N instead 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, and apply resumes.
  • release_mode: dataset_referenced. About 100k rows in total, so the parquet ships beside the page and a fork can recompute every number.

Run it

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 --json

Then open http://127.0.0.1:4173.

Published release

The release is live on the production bucket behind the custom domain:

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 --json

fork 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.

Files

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

Sources

About

Arc launch-day USDC inflows: a worked Chainplot project

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages