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:
- Node.js and npm installed (Download Node.js or use nvm).
- MetaMask installed and configured for the Ethereum Sepolia testnet (Download MetaMask).
Testnet tokens:
- Get Sepolia ETH from a public faucet.
- Obtain Sepolia USDC via the Circle Faucet.
Project Setup
Initialize the Project
mkdir usdc-transfer-app
cd usdc-transfer-app
npm init -yInstall 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.5Add viem
npm install viemCore 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:
- Your wallet has enough Sepolia ETH for gas.
- The recipient address is valid.
- Youโve approved the correct USDC contract address.
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
- USDC transfers require gas fees in the native token (e.g., ETH).
- Always verify contract addresses for your target chain.
- ๐ Optimize your Web3 projects with reliable infrastructure.
### Keywords:
- USDC transfer
- EVM chains
- viem framework
- Ethereum Sepolia
- smart contract
- MetaMask
- testnet tokens