Detect USDC deposits on Arc by generating addresses, monitoring the native USDC
Transfer event from the system emitter 0xffff…fffe (Arc’s
EIP-7708 implementation), crediting
after a single block confirmation (deterministic finality guarantees no reorgs),
and sweeping funds into a hot wallet.
Prerequisites
Before you begin, ensure that you’ve:
- Obtained access to an Arc RPC endpoint (
https://rpc.testnet.arc.io) or
WebSocket (wss://rpc.testnet.arc.io)
- Installed an HD wallet library for generating deposit addresses (for example,
ethers or viem)
- Familiarized yourself with Ethereum JSON-RPC methods and event log filtering
- Set up a database to track processed deposits and prevent double-crediting
Steps
Step 1. Generate deposit addresses
Arc uses standard Ethereum addresses (0x-prefixed, 20 bytes, EIP-55 checksum).
Derive deposit addresses using the same HD wallet approach as Ethereum—one
unique address per user.
Store the mapping between user IDs and their derived address index. Never expose
the mnemonic or private keys in client-side code.
Step 2. Subscribe to new blocks
Use eth_subscribe("newHeads") over WebSocket for real-time block
notifications, or poll eth_blockNumber over HTTP as a fallback.
Step 3. Detect incoming transfers with the native USDC Transfer event
Every native USDC movement emits a standard ERC-20 Transfer log from the
system address 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE (Arc’s
EIP-7708 implementation). This single
stream covers plain native sends and the native leg of ERC-20 transfers, with
values in 18 decimals. Filter it to catch every deposit, including native
sends that emit no event on the ERC-20 contract.
Do not filter the ERC-20 USDC contract (0x3600…0000) for deposit detection.
Its Transfer events cover only ERC-20-interface activity, so a plain native
send produces no log there and the deposit is missed. Filter the system
emitter (0xffff…fffe) instead. See USDC system
events for the full event matrix.
Event signature:
Topic0: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
Native USDC system emitter: 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE
Use eth_getLogs to filter for transfers to your deposit addresses:
Do not credit the same deposit twice. An ERC-20 transfer() emits a log from
both the system emitter (0xffff…fffe, 18 decimals) and the ERC-20 contract
(0x3600…0000, 6 decimals); filter only the system emitter. Likewise, do not
reconcile eth_getBalance against Transfer events—they represent the same
balance.
Step 4. Confirm the deposit
Arc provides deterministic finality—once a transaction is included in a block,
it is final with no possibility of reorg. You can safely credit deposits after 1
confirmation.
Since Arc has deterministic finality, you do not need to wait for multiple
confirmations. Credit the user once the block containing their transaction is
produced.
Step 5. Credit deposits at full precision
The native system Transfer event emits values in 18-decimal native USDC. Store
and credit the raw 18-decimal value. Don’t truncate to 6-decimal ERC-20 units:
amounts smaller than 1×10⁻⁶ USDC are valid and spendable as gas. Truncating them
to 6-decimal ERC-20 units under-credits the user’s balance.
The native system Transfer event and eth_getBalance use 18 decimals. The
ERC-20 contract’s Transfer and balanceOf use 6. Don’t convert 18-decimal
deposit amounts to 6-decimal before crediting. Amounts smaller than 1×10⁻⁶
USDC are under-credited if truncated.
Step 6. Sweep deposits to a hot wallet
Consolidate deposited funds from individual user addresses into your hot wallet.
Use EIP-1559 transactions with Arc’s minimum base fee of 20 Gwei.
Common mistakes
- Filtering the wrong emitter: Plain native USDC sends emit no
Transfer on
the ERC-20 contract (0x3600…0000). Filter the system emitter (0xffff…fffe)
so you do not miss native deposits.
- Double-counting: An ERC-20
transfer() logs from both emitters. Filter
only the system emitter, and do not reconcile eth_getBalance against
Transfer events for the same address.
- Decimal mismatch: The system emitter’s values use 18 decimals. Store and
credit them as 18-decimal native USDC. Converting to 6-decimal before
crediting truncates sub-6-decimal amounts, resulting in under-crediting.
- Waiting for multiple confirmations: Arc has deterministic finality.
Waiting for 6+ confirmations adds unnecessary latency with no security
benefit.
- Hardcoded keys: Never embed private keys in source code. Use environment
variables or a secrets manager for sweep wallet credentials.
- Using
tx.from for attribution: For EIP-3009 relayer-submitted deposits,
tx.from is the relayer’s address. Crediting from it assigns the deposit to
the relayer, not the depositor. Read the from field from the system
emitter’s Transfer event instead.
Check receipt.status === 0 in any send or sweep flow. A transfer to or from
a blocklisted address reverts at runtime: the transaction is included in the
block and gas is consumed, but state changes roll back.
- Fee floor: Transactions with
maxFeePerGas lower than 20 Gwei are
rejected with no error receipt and never appear in a block. Set maxFeePerGas
to at least 20 Gwei when building sweep transactions.