Quickstart Guide: How to Set Up and Transfer USDC on EVM Chains

ยท

USDC enables seamless digital dollar transfers across public blockchains using smart contracts. This guide walks you through building a simple app with viem to connect a wallet and execute USDC transactions on EVM-compatible chains like Ethereum Sepolia.

Prerequisites

Before starting, ensure you have:

  1. Node.js and npm installed (Download Node.js or use nvm).
  2. MetaMask installed and configured for the Ethereum Sepolia testnet (Download MetaMask).
  3. Testnet tokens:

Project Setup

Initialize the Project

mkdir usdc-transfer-app
cd usdc-transfer-app
npm init -y

Install Dependencies

npm install react@^18.2.0 react-dom@^18.2.0 @types/react@^18.0.27 @types/react-dom@^18.0.10 @vitejs/plugin-react@^3.1.0 typescript@^5.0.3 vite@^4.4.5

Add viem

npm install viem

Core Application Logic

1. Configure the Public Client

import { http, createPublicClient } from "viem";
import { sepolia } from "viem/chains";

const publicClient = createPublicClient({
  chain: sepolia,
  transport: http(),
});

2. Set Up the Wallet Client

import { createWalletClient } from "viem";
import { sepolia } from "viem/chains";

const walletClient = createWalletClient({
  chain: sepolia,
  transport: custom(window.ethereum!),
});

3. Define USDC Contract Details

const USDC_CONTRACT_ADDRESS = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238";
const USDC_ABI = [
  {
    constant: false,
    inputs: [
      { name: "_to", type: "address" },
      { name: "_value", type: "uint256" },
    ],
    name: "transfer",
    outputs: [{ name: "", type: "bool" }],
    type: "function",
  },
];

4. Wallet Connection Function

const connect = async () => {
  const [address] = await walletClient.requestAddresses();
  setAccount(address);
};

5. USDC Transfer Function

const data = encodeFunctionData({
  abi: USDC_ABI,
  functionName: "transfer",
  args: [to, valueInWei],
});

const hash = await walletClient.sendTransaction({
  account,
  to: USDC_CONTRACT_ADDRESS,
  data,
});

6. Transaction Confirmation

useEffect(() => {
  (async () => {
    if (hash) {
      const receipt = await publicClient.waitForTransactionReceipt({ hash });
      setReceipt(receipt);
    }
  })();
}, [hash]);

Complete Sample App

index.tsx

import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom/client';
import {
  http,
  type Address,
  type Hash,
  type TransactionReceipt,
  createPublicClient,
  createWalletClient,
  custom,
  stringify,
  encodeFunctionData,
} from 'viem';
import { sepolia } from 'viem/chains';
import 'viem/window';

const publicClient = createPublicClient({
  chain: sepolia,
  transport: http(),
});

const walletClient = createWalletClient({
  chain: sepolia,
  transport: custom(window.ethereum!),
});

const USDC_CONTRACT_ADDRESS = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238';
const USDC_ABI = [
  {
    constant: false,
    inputs: [
      { name: '_to', type: 'address' },
      { name: '_value', type: 'uint256' },
    ],
    name: 'transfer',
    outputs: [{ name: '', type: 'bool' }],
    type: 'function',
  },
];

function Example() {
  const [account, setAccount] = useState<Address>();
  const [hash, setHash] = useState<Hash>();
  const [receipt, setReceipt] = useState<TransactionReceipt>();

  const addressInput = React.createRef<HTMLInputElement>();
  const valueInput = React.createRef<HTMLInputElement>();

  const connect = async () => {
    const [address] = await walletClient.requestAddresses();
    setAccount(address);
  };

  const sendTransaction = async () => {
    if (!account) return;
    const to = addressInput.current!.value as Address;
    const value = valueInput.current!.value as `${number}`;
    const valueInWei = BigInt(value) * BigInt(10 ** 6); // USDC uses 6 decimals

    const data = encodeFunctionData({
      abi: USDC_ABI,
      functionName: 'transfer',
      args: [to, valueInWei],
    });

    const hash = await walletClient.sendTransaction({
      account,
      to: USDC_CONTRACT_ADDRESS,
      data,
    });
    setHash(hash);
  };

  useEffect(() => {
    (async () => {
      if (hash) {
        const receipt = await publicClient.waitForTransactionReceipt({ hash });
        setReceipt(receipt);
      }
    })();
  }, [hash]);

  if (account) {
    return (
      <>
        <div>Connected: {account}</div>
        <div>
          <input ref={addressInput} placeholder="Recipient Address" />
          <input ref={valueInput} placeholder="Amount (USDC)" />
          <button onClick={sendTransaction}>Send</button>
        </div>
        {receipt && (
          <pre>
            Receipt: <code>{stringify(receipt, null, 2)}</code>
          </pre>
        )}
      </>
    );
  }
  return <button onClick={connect}>Connect Wallet</button>;
}

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <Example />
);

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>USDC Transfer Sample App</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/index.tsx"></script>
  </body>
</html>

FAQs

1. How do I get testnet USDC?

Visit the Circle Faucet and request Sepolia USDC after connecting your MetaMask wallet.

๐Ÿ‘‰ Need more testnet tokens?

2. Why is my transaction failing?

Ensure:

3. Can I use this on mainnet?

Yes! Replace the Sepolia USDC contract address with the mainnet USDC address.

๐Ÿ‘‰ Explore advanced blockchain tools

4. What are USDC decimals?

USDC uses 6 decimals (1 USDC = 1,000,000 units on-chain).

Key Takeaways


### Keywords: 
- USDC transfer 
- EVM chains 
- viem framework 
- Ethereum Sepolia 
- smart contract 
- MetaMask 
- testnet tokens