Skip to main content
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

Step 1. Configure the network

Add Arc using the EIP-3085 parameters below.
ParameterValue
chainId0x4CF4B2 (5042002)
chainNameArc 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

ElementDisplay
Fee label”Network fee” or “Gas fee”
Fee denominationUSDC (for example, “0.000042 USDC”)
Currency symbolDo 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.
StateMeaning
PendingTransaction is in the mempool, not yet mined
FinalIncluded 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:
  • Chain ID 5042002 added with correct RPC and explorer URLs
  • Native currency displays as “USDC” with 6 display decimals
  • eth_getBalance result converted from 18-decimal to 6-decimal for display
  • Single USDC balance row shown (no separate native/ERC-20 entries)
  • Imported USDC ERC-20 contract maps to the existing USDC asset (no duplicate entry)
  • No ETH references in UI labels, error messages, or fee displays
  • Transaction history uses the native Transfer event from the system emitter 0xffff...fffe (18 decimals), not the ERC-20 contract
  • Gas fees displayed in USDC
  • Send amounts validated to 6 decimals; payable contract calls (USDC as msg.value) supported
  • One confirmation treated as final (no “confirming” spinner)
  • ERC-4337 AA flows work if your wallet supports smart accounts

See also