Developer tooling for Arc, Circle's stablecoin-native Layer-1 where USDC is the native gas token.
Four CLIs, a Hardhat plugin, two indexer adapters, and a GitHub Action — no dependencies, no build step. Node ≥ 22.6 runs the TypeScript directly.
npx arc-evm-lint contracts/ # catch Arc-incompatible code pre-deploy
npm run arc-index -- scan --blocks 200 # index USDC without double-counting
npm run arc-agents -- count # read the ERC-8004 agent registry
npm run serve # dev console at localhost:8787
npm run mirror-docs # mirror the docs for offline search
npm test # 106 tests, all offlineCircle already ships nine sample apps covering commerce, P2P payments, escrow, nanopayments, multichain wallets, treasury, stablecoin FX, lend/borrow, and prediction markets. Building a tenth app is not where the gap is.
The gap is that Arc diverges from Ethereum in ways that make correct code look wrong and wrong code look fine, and the standard toolchain says nothing:
| Hazard | What breaks | Tool |
|---|---|---|
| Native USDC is 18 decimals, the ERC-20 view is 6 — same balance | A 1e18 amount passed to transfer() is 10¹² too large |
arc-lint |
block.prevrandao is hardcoded to 0 |
Lotteries and shuffles become fully predictable | arc-lint |
Native transfer to address(0) reverts |
Burn-by-sending patterns fail at runtime | arc-lint |
One transfer() emits two Transfer logs, from two emitters |
Indexers double-count; +67.5% phantom rows measured live | arc-index |
| Self-transfers log on the ERC-20 side only | Indexers invent balance changes that never happened | arc-index |
AgentIdentity has no totalSupply(), and its logs predate any cheap window |
No way to enumerate 885 423 registered agents | arc-agents |
Every rule and every claim above traces to a specific line in Arc's docs or to a live RPC observation. See ARC-NOTES.md for the full research trail, and ARC-DOCS-FEEDBACK.md for five gaps in Arc's own documentation, written up with reproduction for upstream.
arc-lint by Egor Galkin (npm arc-lint,
first published 2026-08-07) covers the same ground for Solidity, and does it with
solc AST analysis rather than the pattern matching here — which is more precise,
and includes a contract-level rule that reasons across functions and inheritance.
If you only need Solidity checks, look at it first.
The two overlap on five hazards (prevrandao, the decimal mix, selfdestruct, WETH wrappers, zero-address sends) and diverge either side of that:
| this repo | Ega741/arc-lint | |
|---|---|---|
| Analysis | pattern matching over stripped source | solc AST, with a degraded parse-only mode |
Deploy scripts (.ts/.js) |
yes — the fee floor and parseEther rules live there |
no |
| Foundry / Hardhat / SARIF | yes | GitHub annotations, JSON, markdown |
| Sub-second timestamp deltas | no — a real hazard, covered there as ARC002 | yes |
Blob opcodes, beacon roots, balanceOf() == 0, ether unit, Multicall3From |
yes | no |
arc-index and arc-agents have no equivalent I could find on npm.
Static analysis for Arc's protocol-level divergences from Ethereum. Reads Solidity and deploy/test scripts, needs no compiler, and every finding cites the doc page that justifies it.
npm run arc-lint -- contracts/ scripts/
npm run arc-lint -- --rules # list all 14 rules
npm run arc-lint -- . --json # CI-friendlyBad.sol
17:59 error block.prevrandao is always 0 on Arc arc/no-prevrandao
│ uint256 seed = uint256(keccak256(abi.encodePacked(block.prevrandao, …)));
→ Arc has no beacon-chain RANDAO mix, so block.prevrandao is hardcoded
to 0. Any contract deriving randomness from it is fully predictable.
https://docs.arc.io/arc/references/evm-differences
14 rules across two languages: the decimal trap, PREVRANDAO (expression and
assembly), blob opcodes, EIP-4788 beacon roots, SELFDESTRUCT value rules,
zero-address burns, balanceOf() == 0, the ether unit, redundant WETH
wrappers, Multicall3 vs Arc's Multicall3From, the 20 Gwei fee floor,
parseEther on a 6-decimal token, and localhost RPCs that cannot reproduce Arc.
Exit code is 1 on any error-severity finding (--fail-on warning|never to
change that). Suppress a line with // arc-lint-disable-next-line arc/rule-id,
or whole paths with .arclintignore.
Published as arc-evm-lint —
arc-lint on npm is Egor Galkin's linter,
covered under Prior art.
It plugs into what you already run:
npm install --save-dev arc-evm-lint
npx arc-evm-lint --foundry # reads foundry.toml
npx arc-evm-lint --sarif --out arc.sarif # GitHub code scanning
npx hardhat arc-lint # Hardhat 3 task| Integration | |
|---|---|
hardhat-arc-lint |
A Hardhat 3 plugin that lints config.paths — plus the deploy-script directories Hardhat has no config entry for, where the gas-floor and decimal rules actually fire |
--foundry |
Reads src/script/test/libs from foundry.toml, honouring FOUNDRY_PROFILE. Foundry has no plugin API, so integration means reading the same config forge does |
action.yml |
A composite GitHub Action: inline PR annotations, a step summary, or SARIF for the Security tab |
Arc logs every USDC movement from a native system emitter (18 decimals,
EIP-7708) and additionally from the ERC-20 contract (6 decimals) when the
movement went through the token interface. Both use the same Transfer topic.
arc-index treats the native emitter as the single source of truth and uses the
ERC-20 stream only to label how each movement was initiated.
npm run arc-index -- scan --blocks 150
npm run arc-index -- reconcile --address 0x… --blocks 60
npm run arc-index -- top --blocks 60 --limit 5 raw logs
native emitter (18 dec) 1589
ERC-20 emitter (6 dec) 1073
canonical movements 1589
initiated via ERC-20 1066 (logged twice by the chain)
plain native sends 523 (logged once)
sub-6-decimal dust 57 (invisible to balanceOf)
ERC-20 logs the native emitter deliberately omits
self-transfers 7 (from == to, no balance change)
what a naive indexer gets wrong
transfer rows 2662 vs 1589 real (+67.5% phantom rows)
volume, no dedup 11507.064102 USDC (+83.1%)
volume, canonical 6284.280334 USDC
reconcile proves an index rather than trusting it: replay the movements for an
address and compare against eth_getBalance. A zero residual means every wei is
accounted for; a negative residual is gas, which EIP-7708 does not log.
The dedup rule ships where people already build indexers. It lives as a pure
function over normalised logs in
arc-index/src/core.ts, with no RPC or
framework dependency, so all three consumers agree on what a movement is:
| Adapter | Shape |
|---|---|
arc-index-subsquid |
SQD hands you whole blocks, so both streams for a transaction are in one batch — movementsFromBatch(ctx.blocks) and you are done. Note SQD publishes no gateway for Arc, so ingestion is RPC-only |
arc-index-ponder |
Ponder is event-at-a-time, so there is no batch to pair in. Instead: the native emitter's events create every row, and ERC-20 events update the row they duplicate — which is safe only because Arc emits the native log first |
Each ships a complete example project, and neither depends on the framework it adapts: events and batches are accepted structurally, so both are testable offline.
Reads the ERC-8004 identity and reputation registries — the substrate for Arc's agentic economy, and Circle's stated top grant priority.
npm run arc-agents -- count
npm run arc-agents -- list --from 0 --limit 20 --resolve
npm run arc-agents -- show 1
npm run arc-agents -- reputation 4 registered agents 885423
highest token id 885422
cost 41 eth_calls
AgentIdentity is a plain ERC-721 with no totalSupply(). The registries are
busy, but the 885k agents accumulated over the chain's whole history, so counting
by log replay means backfilling ~58M blocks — 2,000–5,900 requests at the real
eth_getLogs caps. Ids are sequential, so an exponential probe plus binary
search over ownerOf answers the same question in 41 calls.
A local dev console over the same three packages, plus full-text search across 636 mirrored documentation pages (108 from Arc, 528 from the Circle Developer Platform).
npm run serve # http://127.0.0.1:8787Five tabs — chain status, the USDC index with its dedup numbers, the agent
registry, a paste-and-lint box, and offline doc search. Every tab is a thin
client over a JSON API that is equally usable from curl, an editor extension,
or CI:
curl -s localhost:8787/api/index/scan?blocks=150 | jq .stats
curl -s -X POST localhost:8787/api/lint -H 'content-type: application/json' \
-d '{"lang":"solidity","source":"…"}'Docs are mirrored ahead of time by tools/mirror-docs.ts, which starts from each
site's llms.txt and follows internal links — so pages missing from the index
still land, and search works with no network at all.
The Circle Developer Grants Program relaunched in May 2026 with milestone-based USDC funding, and names six priority areas: agentic economic activity, stablecoin FX, P2P payments, treasury management, prediction markets, and lending/borrowing.
These three tools are deliberately underneath those areas rather than inside
one of them. Anyone building stablecoin FX or a lending market on Arc hits the
decimal trap and the dual-emitter problem on day one, and arc-agents reads the
registry every agentic application has to read. Shared infrastructure with no
existing equivalent argues ecosystem impact more cleanly than a tenth sample app.
Delivered so far: the rules run in Foundry, Hardhat, and CI; the dedup rule runs in Ponder and Subsquid; and the docs are mirrored for offline search.
What would add the most next:
- Solidity AST rules via
solc --standard-json— the current rules are regex-based over comment-stripped source, which is honest but coarse. AST analysis would letarc/decimals-mixfollow a value across assignments instead of flagging every1e18in a file that touchesIERC20. - Publish to npm and the GitHub Marketplace — right now the action is used by path, and the packages by workspace reference.
- An ERC-8004 agent directory on top of
arc-agents: searchable capabilities, reputation ranking, and metadata pinning. - Decode
readFeedbackonce the canonical ERC-8004 ABI is available, and contribute the five documentation gaps back to Circle.
packages/
arc-common/ keccak256, ABI codec, rate-limited JSON-RPC client — zero deps
arc-lint/ rules, engine, CLI, SARIF, annotations, action.yml — 33 tests
hardhat-arc-lint/ Hardhat 3 plugin — 13 tests
arc-index/ core dedup rule + RPC scanner + reconciler — 10 tests
arc-index-ponder/ Ponder adapter + example project — 9 tests
arc-index-subsquid/ Subsquid adapter + example project — 10 tests
arc-agents/ ERC-8004 identity + reputation reader — 17 tests
arc-server/ dev console + JSON API + doc search — 14 tests
site/ the public site: landing plus a page per tool
tools/
mirror-docs.ts Mintlify site mirror (Arc and Circle)
deploy.sh push to vm-webserver and publish site/
.github/workflows/ arc-lint (annotate + code scanning), test (Node 22 & 24)
docs/ 108 mirrored Arc pages (generated, not committed)
docs-circle/ 528 mirrored Circle pages (generated, not committed)
ARC-NOTES.md research trail: verified chain facts, hazards, doc gaps
ARC-DOCS-FEEDBACK.md five documentation gaps reported upstream, with repro
arc-common deliberately has no dependencies — not even for keccak256, which
Node's crypto cannot provide (it ships FIPS SHA3, a different padding). Every
class declares its fields explicitly, because Node's strip-only TypeScript mode
rejects parameter properties and enums. That constraint is what keeps
node file.ts working with no install step.
docs/ and docs-circle/ are Circle's documentation, mirrored locally so the
dev console can search it offline. They are deliberately kept out of version
control — redistributing someone else's docs is not this repo's business. Run
npm run mirror-docs (and -- --site circle) to build them.
Everything here runs against Arc Testnet (chain 5042002) and was verified on
2026-08-24. Mainnet is announced for 16 September 2026; the addresses in
arc-common/rpc.ts are testnet-only and will need a mainnet set.
arc-lint runs fully offline, and every test in the repo does too — there is
nothing to install to run npm test. arc-index and arc-agents need RPC
access; both respect ARC_RPC_RPS (default 12) because Arc's public RPC sits
behind Cloudflare and answers bursts with 429.
The one dependency anywhere is hardhat itself, a dev dependency of the Hardhat
plugin so its peer dependency can be exercised. The plugin was verified against a
real Hardhat 3.14 project, not just unit-tested.