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

# Quickstart: Transfer USDC on EVM chains

> Transfer USDC between wallets on EVM chains using a Node.js script

This guide walks you through transferring USDC on EVM testnets using Viem and
Node.js. You'll build a simple script that checks your balance and sends test
transfers.

## Prerequisites

Before you begin, ensure that you've:

* Installed [Node.js v22.6+](https://nodejs.org/)
* Prepared a testnet wallet on the selected chain funded with:
  * Testnet USDC for the transfer
  * Testnet native tokens for gas fees

<Note>
  You can get testnet USDC from [Circle's faucet](https://faucet.circle.com/).
</Note>

## Step 1. Set up the project

This step shows you how to prepare your project and environment.

### 1.1. Create the project and install dependencies

Create a new directory and install the required dependencies:

```bash Shell theme={null}
# Set up your directory and initialize a Node.js project
mkdir transfer-usdc-evm
cd transfer-usdc-evm
npm init -y

# Set up module type and start command
npm pkg set type=module
npm pkg set scripts.start="node --env-file=.env index.ts"

# Install runtime dependencies
npm install viem

# Install dev dependencies
npm install --save-dev typescript @types/node
```

### 1.2. Configure TypeScript (optional)

<Tip>
  This step is optional. It helps prevent missing types in your IDE or editor.
</Tip>

Create a `tsconfig.json` file:

```shell theme={null}
npx tsc --init
```

Then, update the `tsconfig.json` file:

```shell theme={null}
cat <<'EOF' > tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "types": ["node"]
  }
}
EOF
```

### 1.3. Set environment variables

Create a `.env` file in the project directory and add your wallet private key,
replacing `{YOUR_PRIVATE_KEY}` with the private key from your EVM wallet and
`{YOUR_RECIPIENT_ADDRESS}` with the address of the recipient.

```text theme={null}
PRIVATE_KEY={YOUR_PRIVATE_KEY}
RECIPIENT_ADDRESS={YOUR_RECIPIENT_ADDRESS}
```

* `PRIVATE_KEY` is the private key for the EVM wallet sending the transfer.
* `RECIPIENT_ADDRESS` is the EVM wallet address that will receive the USDC.

<Tip>
  Open `.env` in your editor rather than writing values with shell commands, and
  add `.env` to your `.gitignore`. This prevents credentials from leaking into
  your shell history or version control.
</Tip>

The `npm run start` command loads variables from `.env` using Node.js native
env-file support.

<Warning>
  This example uses one or more private keys for local testing. In production,
  use a secure key management solution and never expose or share private keys.
</Warning>

## Step 2. Create the transfer script

In this step, you'll build a script in TypeScript that transfers USDC on the
selected EVM testnet. The script derives supported chains from Viem, prompts you
to choose one at runtime, and then runs the same transfer flow for that chain.

### 2.1. Create the script file

```shell theme={null}
touch index.ts
```

### 2.2. Add the script

In `index.ts`, add the following script. It derives supported chains from the
USDC token definition and chain definitions in Viem, so the transfer flow stays
shared across EVM testnets. The script uses the client-attached ERC-20 token
actions and the USDC token definition from `viem/tokens`.

```typescript TypeScript expandable theme={null}
import { createClient, http, publicActions, walletActions } from "viem";
import * as chains from "viem/chains";
import { filterChains, isAddress, isHex } from "viem/utils";
import { privateKeyToAccount } from "viem/accounts";
import { usdc } from "viem/tokens";

import * as process from "node:process";
import * as readline from "node:readline/promises";

// 1. Ensure environment variables are set up
if (!isHex(process.env.PRIVATE_KEY) || process.env.PRIVATE_KEY.length !== 66)
  throw new Error("Set PRIVATE_KEY to a 0x-prefixed 32-byte hex string");
if (!process.env.RECIPIENT_ADDRESS || !isAddress(process.env.RECIPIENT_ADDRESS))
  throw new Error("Set RECIPIENT_ADDRESS to valid EVM address");

// 2. Setup Viem Client with `usdc` token and public/wallet actions
const client = createClient({
  account: privateKeyToAccount(process.env.PRIVATE_KEY),
  chain: await selectChain(),
  tokens: [usdc],
  transport: http(),
})
  .extend(publicActions)
  .extend(walletActions);

try {
  // 3. Read sender token balance before transfer
  const balance = await client.token.getBalance({ token: "usdc" });
  const amount = { formatted: "1" };
  const recipient = process.env.RECIPIENT_ADDRESS;

  console.log("Chain:", client.chain.name);
  console.log("Sender:", client.account.address);
  console.log("Recipient:", recipient);
  console.log("Balance:", balance.formatted, usdc.symbol);

  // 4. Ensure sender has enough balance
  if (Number(amount.formatted) > Number(balance.formatted))
    throw new Error("Insufficient balance");

  // 5. Estimate gas and apply a safety floor
  const estimatedGas = await client.token.transfer.estimateGas({
    amount,
    to: recipient,
    token: "usdc",
  });
  const buffered = estimatedGas + (estimatedGas * 50n) / 100n;
  const floor = 85_000n;

  // 6. Submit USDC transfer
  const hash = await client.token.transfer({
    amount,
    gas: buffered > floor ? buffered : floor,
    to: recipient,
    token: "usdc",
  });

  // 7. Print transaction details
  console.log("Transaction submitted.");
  console.log("Tx hash:", hash);
  if (client.chain.blockExplorers?.default)
    console.log(
      "Explorer:",
      `${client.chain.blockExplorers.default.url}/tx/${hash}`,
    );

  // 8. Wait for transaction confirmation
  const receipt = await client.waitForTransactionReceipt({ hash });
  if (receipt.status !== "success") throw new Error("Transaction reverted");

  console.log("Transfer confirmed!");
} catch (error) {
  // 9. Handle transfer errors
  console.error(
    "Transfer failed:",
    error instanceof Error ? error.message : error,
  );
  process.exit(1);
}

async function selectChain() {
  // Find supported testnet chains for `usdc`
  const supportedChains = filterChains({
    chains,
    sort: "name",
    testnet: true,
    token: usdc,
  });
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  try {
    // Prompt user to choose a chain
    const answer = await rl.question(
      `Select a chain for your ${usdc.symbol} transfer:\n${supportedChains
        .map((chain, index) => `${index + 1}. ${chain.name}`)
        .join("\n")}\n\nEnter a number: `,
    );
    // Validate selected chain
    const selected = supportedChains[Number.parseInt(answer, 10) - 1];
    if (!selected) throw new Error("Select a valid chain");
    return selected;
  } finally {
    rl.close();
  }
}
```

## Step 3. Run the script

Run the script using the following command:

```shell Shell theme={null}
npm start
```

You'll see output similar to the following:

```text theme={null}
Select a chain for your USDC transfer:
1. Arc Testnet
2. Arbitrum Sepolia
...

Enter a number: 1
Chain: Arc Testnet
Sender: 0x1A2b...7890
Recipient: 0x9F8f...1234
Balance: 250.0 USDC
Transaction submitted.
Tx hash: 0xabc123...def456
Explorer: https://testnet.arcscan.app/tx/0xabc123...def456
Transfer confirmed!
```

To verify the transfer, copy the transaction hash URL from the `Explorer:` line
and open it in your browser. This will take you to the testnet's block explorer,
where you can view full transaction details.

<Tip>
  **Tip:** If your script doesn't output a full explorer URL, you can manually
  paste the transaction hash into the testnet's block explorer.
</Tip>

## Summary

In this quickstart, you learned how to check balances and transfer USDC on EVM
testnets using one multichain Viem script in Node.js. Here are the key points to
remember:

* **Testnet only**. Testnet USDC has no real value.
* **Gas fees**. You need a small amount of the testnet's native token for gas.
* **Security**. Keep your private key in `.env`. Never commit secrets.
* **Single script**. The same script works across all supported EVM testnets.
* **Token actions**. The script uses ERC-20 token actions and the USDC token
  definition from [`viem/tokens`](https://viem.sh/tokens) instead of a
  hand-written ABI.
