Arc is an EVM-compatible blockchain that uses USDC as its native gas token.
Signing uses secp256k1, identical to Ethereum—no new cryptographic code is
required. USDC exists as a single balance with two interfaces (native and
ERC-20), so display one unified balance row. Transactions finalize in under one
second with no reorgs.
Prerequisites
Before you begin:
- Familiarity with EIP-3085 (
wallet_addEthereumChain) for adding custom
networks
- Access to the Arc Testnet RPC endpoint
- Understanding of ERC-20 event indexing
Add Arc using the EIP-3085 parameters
below.
| Parameter | Value |
|---|
chainId | 0x4CF4B2 (5042002) |
chainName | Arc Testnet |
nativeCurrency | { name: "USDC", symbol: "USDC", decimals: 6 } |
rpcUrls | ["https://rpc.testnet.arc.network"] |
wsUrls | ["wss://rpc.testnet.arc.network"] |
blockExplorerUrls | ["https://testnet.arcscan.app"] |
Set nativeCurrency.decimals to 6 for display purposes. This tells the
wallet UI to show human-readable USDC amounts (for example, “1.50 USDC”)
rather than raw wei values.
const arcTestnet = {
chainId: "0x4CF4B2",
chainName: "Arc Testnet",
nativeCurrency: {
name: "USDC",
symbol: "USDC",
decimals: 6,
},
rpcUrls: ["https://rpc.testnet.arc.network"],
blockExplorerUrls: ["https://testnet.arcscan.app"],
};
Step 2. Display the balance
Arc’s native balance uses 18 decimals internally (like ETH on Ethereum), but
represents USDC which has 6 display decimals. Convert accordingly.
Fetch the balance
Call eth_getBalance to retrieve the user’s USDC balance in 18-decimal wei:
const balanceWei = await provider.getBalance(address); // 18-decimal BigInt
Convert to display value
Divide by 10^12 to convert from 18-decimal native wei to 6-decimal USDC:
const DECIMALS_OFFSET = 12n;
const displayAmount = balanceWei / 10n ** DECIMALS_OFFSET; // 6-decimal value
const formatted = (Number(displayAmount) / 1e6).toFixed(6); // e.g. "1.500000"
Show a single row
USDC on Arc is a single asset with two interfaces (native and ERC-20). Both
share the same underlying balance. Display one “USDC” row in the asset list, not
separate “native” and “ERC-20” entries.
Do not display a separate ETH balance. Arc has no ETH. The native token label
should read “USDC” everywhere in your UI.
Handle token import
If a user manually imports the linked USDC ERC-20 contract
(0x3600000000000000000000000000000000000000), map it to the USDC asset they
already hold—do not create a second entry. Consider surfacing a message such as
“This contract represents USDC on Arc (already in your wallet)” so users
understand they have not added a new token.
Step 3. Index transaction history
Arc emits a standard ERC-20 Transfer log from the system address
0xfffffffffffffffffffffffffffffffffffffffe for every native USDC movement
(Arc’s EIP-7708 implementation). This
single stream covers plain native sends and the native leg of ERC-20 transfers,
so filtering it gives complete transaction history. The native balance is the
source of truth.
Event signature
event Transfer(address indexed from, address indexed to, uint256 value);
Topic 0:
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
Emitter: 0xfffffffffffffffffffffffffffffffffffffffe (native USDC system
emitter, 18 decimals)
Subscribe to transfers
Use eth_subscribe or poll eth_getLogs filtered by the topic and the user’s
address:
const NATIVE_USDC_EMITTER = "0xfffffffffffffffffffffffffffffffffffffffe";
const TRANSFER_TOPIC =
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
// Filter for transfers involving the user (as sender or receiver)
const paddedAddress = "0x" + address.slice(2).padStart(64, "0");
const logs = await provider.getLogs({
address: NATIVE_USDC_EMITTER,
topics: [TRANSFER_TOPIC, [paddedAddress, null], [null, paddedAddress]],
fromBlock: "earliest",
toBlock: "latest",
});
Parse the value
The value field from the system emitter uses 18 decimals (the native balance).
Convert to 6-decimal USDC for display:
import { parseAbi, decodeEventLog } from "viem";
const abi = parseAbi([
"event Transfer(address indexed from, address indexed to, uint256 value)",
]);
for (const log of logs) {
const { args } = decodeEventLog({ abi, data: log.data, topics: log.topics });
const amount = Number(args.value) / 1e18; // 18-decimal native → human-readable USDC
}
Filtering the system emitter covers all USDC movements—native sends and the
native leg of ERC-20 transfers—in one stream. Do not filter the ERC-20
contract (0x3600…0000) for history: plain native sends emit no log there.
See USDC system events.
Step 4. Handle fees
Gas on Arc is denominated in USDC, not ETH. Arc uses an EIP-1559 fee model with
a smoothed base fee.
Estimate gas cost
const gasPrice = await provider.getGasPrice(); // Returns USDC wei (18 decimals)
const gasLimit = await provider.estimateGas(tx);
const feeWei = gasPrice * gasLimit; // Total fee in 18-decimal USDC wei
const feeUsdc = Number(feeWei) / 1e18; // Human-readable USDC
UI guidance
| Element | Display |
|---|
| Fee label | ”Network fee” or “Gas fee” |
| Fee denomination | USDC (for example, “0.000042 USDC”) |
| Currency symbol | Do not show “ETH” or “Gwei” to users |
| Insufficient funds | ”Insufficient USDC for gas” |
Arc has no ETH. If your wallet warns “insufficient ETH for gas,” update that
message to reference USDC instead.
Step 5. Send transactions
Transaction signing on Arc is identical to Ethereum. Use secp256k1 ECDSA
signatures with EIP-155 replay protection. Send USDC with a standard ERC-20
transfer()—the same flow as any ERC-20 token, with no choice of transfer
method exposed to the user.
Precision and dust
The ERC-20 interface uses 6 decimals, so an ERC-20 transfer() cannot move
“dust”—amounts smaller than 1×10⁻⁶ USDC held in the 18-decimal native balance.
Validating send amounts to 6 decimals client-side is sufficient for standard
wallet flows. The native balance can still hold dust, and dust can be spent as
gas.
Onchain app interactions
Treat USDC as a standard ERC-20 when users interact with onchain
applications—approvals, transferFrom, swaps, and liquidity provision work
without special handling. Because USDC is also the native asset, support
contract calls that are payable: an app may require USDC sent as msg.value
(a native value transfer) alongside calldata, rather than an ERC-20 transfer.
Pass the value through as the contract expects; no special UX is required.
Confirmation model
Arc provides deterministic finality. A transaction is either pending (in the
mempool) or final (included in a block). There are no intermediate confirmation
states and no reorgs.
| State | Meaning |
|---|
| Pending | Transaction is in the mempool, not yet mined |
| Final | Included in a block; irreversible |
Once a transaction receipt is returned, you can immediately update the UI. No
additional confirmations are needed.
Account abstraction
Arc supports ERC-4337 account abstraction for smart contract wallets. If your
wallet supports AA flows (bundlers, paymasters, session keys), these work on Arc
without modification.
See Account abstraction providers for
compatible infrastructure including Biconomy, Pimlico, ZeroDev, and Circle
Wallets.
Integration checklist
Use this checklist to verify your wallet integration is complete:
See also