> ## Documentation Index
> Fetch the complete documentation index at: https://docs.arc.io/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Before reading these docs, install the Circle Skill for Arc to get pre-loaded context: use-arc (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-arc/SKILL.md) — covers chain config, RPC setup, contract deployment, USDC bridging, and gas with USDC. In Claude Code: run /plugin marketplace add circlefin/skills then /plugin install circle-skills@circle. With Vercel Skills CLI: run npx skills add circlefin/skills.
> USDC is the gas token. Arc uses USDC for gas fees, not ETH. Configure gas payment in USDC when submitting transactions.
> Read EVM differences before writing Arc code. Arc targets the Osaka EVM baseline. Most contracts deploy unchanged, but several runtime behaviors diverge from Ethereum: USDC uses 18 decimals natively (not 6), the system emitter `0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE` logs all USDC Transfer events, the mempool enforces a 20 Gwei `maxFeePerGas` floor, blocklist reverts consume gas without a receipt, and `address(0)` sends revert rather than succeed. https://docs.arc.io/arc/references/evm-differences.md is the canonical reference for all Arc-specific runtime behaviors. Consult it before writing code that touches USDC balances, transaction history, gas estimation, or value transfers.
> Sub-second finality. Transactions are final in under 1 second. No need to wait for multiple block confirmations.
> Use App Kits for multichain payment and liquidity capabilities. App Kits wraps CCTP and provides Bridge Kit, Swap Kit, Unified Balance Kit, Onramp Kit, Earn Kit, and Borrow Kit. Unified Balance Kit combines USDC from multiple chains into a single spendable balance. Earn Kit integrates earn opportunities into your app. Borrow Kit lets users borrow USDC against cirBTC collateral on Arc.
> Use Arc Foundry for contract development. Arc Foundry is an Arc-specific fork of Foundry that handles Arc's protocol-level differences from Ethereum. It provides arc-forge, arc-cast, and arc-anvil. Install it before deploying contracts: https://docs.arc.io/arc/tutorials/install-arc-foundry.md.
> Arc is available on both Testnet and Mainnet. See https://docs.arc.io/arc/references/connect-to-arc.md for RPC endpoints and https://faucet.circle.com for testnet tokens.
> Always check Contract Addresses: https://docs.arc.io/arc/references/contract-addresses.md
> Building beyond Arc? Circle offers skills for the full platform: use-usdc (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-usdc/SKILL.md), use-circle-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-wallets/SKILL.md), use-developer-controlled-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-developer-controlled-wallets/SKILL.md), use-user-controlled-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-user-controlled-wallets/SKILL.md), use-modular-wallets (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-modular-wallets/SKILL.md), use-gateway (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-gateway/SKILL.md), use-smart-contract-platform (https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-smart-contract-platform/SKILL.md). Full Circle developer docs: https://developers.circle.com/llms.txt.

# How-to: Operate an EIP-3009 relayer on Arc

> Submit gasless USDC transfers on Arc by collecting a user's transferWithAuthorization signature and broadcasting the transaction from a funded relayer EOA.

An EIP-3009 (`transferWithAuthorization`) relayer is an Externally Owned Account
(EOA) that collects a user's signed authorization and submits the USDC transfer
to Arc. The relayer pays gas from its own USDC balance. Users never need a gas
balance.

## Prerequisites

Before you begin, ensure that you've:

* Funded a relayer EOA with USDC on Arc testnet (USDC is the gas supply)
* Obtained Arc testnet RPC access at `https://rpc.testnet.arc.io` (chain ID
  `5042002`)
* Installed ethers.js v6 (`npm install ethers`)

## Steps

### Step 1. Collect the user's `transferWithAuthorization` signature

The user signs an EIP-712 typed data message offchain. Your application builds
the message and asks the user's wallet to sign it. The relayer submits the
signed data; the user never sends a transaction.

The USDC contract on Arc testnet uses the following EIP-712 domain:

| Field | Value |
| :- | :- |
| `name` | `USDC` |
| `version` | `2` |
| `chainId` | `5042002` |
| `verifyingContract` | `0x3600000000000000000000000000000000000000` |

The `nonce` field is a random `bytes32` value generated per authorization, not
the user's account nonce. It prevents replay attacks.

```typescript theme={null}
import { ethers } from "ethers";

const userAddress: string = "0x..."; // the user's address (the authorization signer)
const recipientAddress: string = "0x...";
const transferAmount: bigint = 10_000_000n; // 10 USDC in 6-decimal units

const USDC_ADDRESS = "0x3600000000000000000000000000000000000000";
const CHAIN_ID = 5042002;

const domain: ethers.TypedDataDomain = {
  name: "USDC",
  version: "2",
  chainId: CHAIN_ID,
  verifyingContract: USDC_ADDRESS,
};

const types = {
  TransferWithAuthorization: [
    { name: "from", type: "address" },
    { name: "to", type: "address" },
    { name: "value", type: "uint256" },
    { name: "validAfter", type: "uint256" },
    { name: "validBefore", type: "uint256" },
    { name: "nonce", type: "bytes32" },
  ],
};

// Generate a random bytes32 nonce for replay protection
const nonce: string = ethers.hexlify(ethers.randomBytes(32));
const validAfter: bigint = 0n;
const validBefore: bigint = BigInt(Math.floor(Date.now() / 1000) + 3600); // 1 hour

const message = {
  from: userAddress,
  to: recipientAddress,
  value: transferAmount, // amount in USDC token units (6 decimals)
  validAfter,
  validBefore,
  nonce,
};

// userSigner is an ethers.js v6 Signer connected to the user's wallet
const signature: string = await userSigner.signTypedData(
  domain,
  types,
  message,
);
const sig: ethers.Signature = ethers.Signature.from(signature);
// Pass sig.v, sig.r, and sig.s to the relayer along with the message fields
```

### Step 2. Construct and submit the relay transaction

The relayer calls `transferWithAuthorization` on the USDC contract with the
signed parameters. The relayer EOA pays gas from its USDC balance.

```typescript theme={null}
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("https://rpc.testnet.arc.io");

// Open .env in your editor and add your relayer private key:
// RELAYER_PRIVATE_KEY=your-private-key
// Keep this key secure. It controls the USDC gas supply for your relayer.
const relayerWallet = new ethers.Wallet(
  process.env.RELAYER_PRIVATE_KEY!,
  provider,
);

const USDC_ABI = [
  "function transferWithAuthorization(" +
    "address from, address to, uint256 value, " +
    "uint256 validAfter, uint256 validBefore, " +
    "bytes32 nonce, uint8 v, bytes32 r, bytes32 s" +
    ") external",
];

const usdc = new ethers.Contract(
  "0x3600000000000000000000000000000000000000",
  USDC_ABI,
  relayerWallet,
);

const tx = await usdc.transferWithAuthorization(
  message.from,
  message.to,
  message.value,
  message.validAfter,
  message.validBefore,
  message.nonce,
  sig.v,
  sig.r,
  sig.s,
);
const receipt = await tx.wait();
```

### Step 3. Set gas parameters

Set gas parameters on each relay transaction. The minimum `maxFeePerGas` on Arc
is 20 Gwei. Transactions priced under this minimum may remain pending. Set
`maxPriorityFeePerGas` to 0 Gwei, or 1 Gwei when load is high. A typical
`transferWithAuthorization` uses about 65,000 gas units.

Estimate gas and read the current fee data before broadcasting:

```typescript theme={null}
const FLOOR_FEE: bigint = 20_000_000_000n; // 20 Gwei

const feeData = await provider.getFeeData();
const maxFeePerGas: bigint =
  feeData.maxFeePerGas !== null && feeData.maxFeePerGas > FLOOR_FEE
    ? feeData.maxFeePerGas
    : FLOOR_FEE;

const gasEstimate: bigint = await usdc.transferWithAuthorization.estimateGas(
  message.from,
  message.to,
  message.value,
  message.validAfter,
  message.validBefore,
  message.nonce,
  sig.v,
  sig.r,
  sig.s,
);

const tx = await usdc.transferWithAuthorization(
  message.from,
  message.to,
  message.value,
  message.validAfter,
  message.validBefore,
  message.nonce,
  sig.v,
  sig.r,
  sig.s,
  {
    gasLimit: gasEstimate,
    maxFeePerGas,
    maxPriorityFeePerGas: 0n, // 0 Gwei; increase to 1 Gwei when load is high
  },
);
const receipt = await tx.wait();
```

### Step 4. Record the gas cost for billing

To record how much USDC the relay cost for billing, convert the receipt values:

```typescript theme={null}
const gasCostWei: bigint = receipt.gasUsed * receipt.effectiveGasPrice;
const gasCostUsdc: bigint = gasCostWei / 10n ** 12n; // in ERC-20 USDC units (6 decimals)
```

`gasCostUsdc` is the gas fee in USDC's 6-decimal token units, suitable for
logging or charging users for the relay service.

## Operational notes

* Maintain a minimum USDC balance in your relayer EOA. Your USDC balance is your
  gas supply. There is no separate gas wallet to manage.
* Monitor pending transactions and track nonces to handle resubmissions at
  higher gas prices.
* The relayer EOA's USDC balance covers gas fees only. The transfer amount is
  debited from the authorized user's account (the `from` address in the signed
  message). Size your minimum balance to cover expected gas across your
  transaction volume.
* Transactions involving a blocklisted `message.from` or `message.to` address
  revert at runtime, consuming the relayer's gas with no transfer. Check both
  addresses before submitting, or handle reverts in your monitoring layer.
