> ## 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.

# 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();
```

<Warning>
  A `transferWithAuthorization` that fully drains a brand-new sender account
  (zero balance, zero nonce, no code) currently reverts on Arc. Before relaying,
  verify that `message.from` has either a non-zero nonce (using
  `provider.getTransactionCount`) or deployed code (using `provider.getCode`).
  Accounts that meet either condition drain normally. A fix is planned for a
  future release.
</Warning>

### 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.
