Arc is Circle's USDC-native L1. I wanted to know what it actually takes to put a real token on it — not a testnet walkthrough, but mainnet, with a verified contract and a live market. So I did it and wrote down every step.
Total cost from an empty wallet to a verified, indexed, tradeable token: about 11 cents.
What shipped
-
Noor (NUR) — an ERC-20 on Arc mainnet, chain ID 5042. Supply is fixed at 1,000,000,000 in the constructor, so
cap()equalstotalSupply()andremainingMintable()returns0. - Contract:
0x82c9411BBDdafF86F0Ee8E95275256AE136F8c1f— source verified as an exact match (solc 0.8.28, evm cancun, optimizer 200 runs). - A real Uniswap v2 pool:
0xa8dC7Eba119949500F8e18fD086a0bF151095910. - A payment app on GitHub Pages with no backend and no custody, reading balances and price straight from the chain.
The real cost
| Step | Cost |
|---|---|
| Contract deployment (1.82M gas at ~21 gwei) | $0.088 |
| Circle CCTP bridge, Base to Arc | $0.016 |
| One swap to trigger DEX indexing | $0.006 |
| Total | ~$0.11 |
Getting USDC onto Arc was the awkward part. Arc has no faucet, so the USDC has to come from somewhere real. I bridged from Base with Circle CCTP V2 and used the Forwarding Service so Circle submitted the destination mint itself — which matters, because on Arc the gas token is USDC and a fresh wallet has none.
Six things that cost me hours
1. On Arc the gas token IS the bridged token, and they share one balance
This one broke my bridge script on the first run.
depositForBurnWithHook reverted: "ERC20: transfer amount exceeds balance"
The script read the USDC balance, sent an approve, then tried to transfer that same balance. But the approve consumed gas — and on Arc gas is paid in USDC, out of the very same balance. The number it read was already stale.
The fix: re-read the balance after the approve, estimate the burn transaction's gas, and hold that back before computing the amount.
const gasUnits = await messenger.depositForBurnWithHook.estimateGas(...);
const feeData = await srcProv.getFeeData();
const gasWei = gasUnits * (feeData.maxFeePerGas ?? 0n) * 2n;
const reserve = gasWei / 10n ** 12n + 10_000n; // native 18dp -> USDC 6dp
const burnAmount = freshBalance - reserve;
I only found it because I ran the whole bridge on testnet first. On mainnet it would have been the first attempt, with real money.
2. Aggregators index a pair only after its FIRST SWAP
I created the pool, confirmed it on chain, and then could not find it anywhere. It was invisible on DexScreener and GeckoTerminal.
DexScreener's own documentation is explicit: "DexScreener automatically indexes every token that completes at least one swap on a supported decentralized exchange... Pool creation alone is not sufficient."
One 0.05 USDC swap fixed it, and both aggregators picked the pair up within the hour. Creating the pool and opening the market are two different events.
3. token0() returns a 32-byte padded address
My payment app printed a price of $9.6e+29 and the reserves were swapped. The cause:
const t0 = await rpcCall(PAIR, '0x0dfe1681'); // 0x0000...00003600...0000
const usdcIsT0 = t0.toLowerCase() === USDC.toLowerCase(); // always false
token0() returns an ABI-encoded address, so it comes back left-padded to 32 bytes. Comparing it to a bare checksummed address silently fails — no revert, no warning, just inverted numbers.
const t0 = '0x' + t0raw.slice(-40);
4. explorer.arc.io sits behind Cloudflare, so verification must come from a browser
POST /api?module=contract&action=verifysourcecode works, but the parameters have to go in the query string — a form body is ignored. And the URL-length limit means the standard-JSON compiler input (about 150 KB) gets a 414 Request-URI Too Large from Cloudflare.
The working path is the Blockscout v2 endpoint, as multipart/form-data, with the compiler input as a file part named files:
POST /api/v2/smart-contracts/{address}/verification/via/standard-input
compiler_version v0.8.28+commit.7893614a
license_type mit
autodetect_constructor_args true
constructor_arguments (empty)
files the standard-json-input file, as an upload
Sending that JSON as a plain string field is rejected with a bare 400 Bad Request and no explanation.
5. 20 gwei is a protocol floor on Arc, not a suggestion
maxFeePerGas below 20 gwei can leave a transaction pending indefinitely rather than failing quickly. I set maxFeePerGas to twice the observed base fee and a 1 gwei tip.
6. Mainnet is open — verify that before you plan around credentials
The docs mention a permissioned phase, so I tested the write path before funding anything: sign a transaction, send it, read the error.
{"error":{"code":-32003,"message":"insufficient funds for gas * price + value"}}
That is a normal EVM simulation failure, which means the node accepted and fully validated the transaction. No allowlist, no API key. Worth checking yourself rather than assuming.
The numbers that do not flatter it
- The pool holds $2.55. Not a typo.
- A $1 buy moves the price about 40%. A $5 buy moves it about 200%.
- The $1,040 that aggregators print is arithmetic on those $2.55. It is not a valuation and nobody paid it.
- No utility, no community, no exchange listing.
Deploying a token is easy and costs cents. That was never the hard part, and any write-up that stops at "look, it deployed" is hiding the interesting question.
What I deliberately did not do
No volume bots. No wash trading between wallets I control. No paid boosts. No "buy before it moons".
There is exactly one swap on the pool, it is 0.05 USDC, and it exists because DexScreener's documentation requires it to index the pair at all. It is labelled as an activation step in the repository, at the exact line where it happens.
It would have been easy to make the numbers look better. That is precisely why I did not.
Links
- Repository, every script and every on-chain record: https://github.com/jadjbara3-cpu/noor
- Project page: https://jadjbara3-cpu.github.io/noor/
- The pool, if you want to see what a $2.55 market looks like up close: https://dexscreener.com/arc/0xa8dc7eba119949500f8e18fd086a0bf151095910
Happy to go deeper on any of the six points above — they cost me a few hours each, and I would rather they cost you nothing.
Top comments (0)