This guide is currently React only.
Overview
Sending USDC, or other ERC-20 tokens, is one of the most common actions taken by wallet users on Ethereum-based chains. This guide will walk you through how to send USDC using Dynamic’s embedded wallets.
Step 1: Get the USDC Contract Address
To send USDC, you’ll need the contract address for USDC. The address is different for each network, so make sure you get the correct address for the network you’re targeting.
You can go to Circle’s website to look up the USDC address on both main networks and test networks.
USDC, and other ERC-20 tokens, are smart contracts. In order to send these tokens, your transaction needs to call the transfer function on the contract, which takes in two parameters:
to: The address of the recipient
value: The amount of tokens to send
When formatting the transaction data, you must define the expected interface of the function you’re calling by providing an ABI. You can use helper packages like viem to provide the ABI and encode the function parameters.
Additionally, each ERC-20 token defines a decimals value, which is the number of decimal places for the token. For USDC, the decimals value is 6.
First, ensure you have the required dependencies installed for your platform:
Install the viem package for handling EVM transactions and ERC-20 token transfers:
npm install viem
# or
yarn add viem
# or
pnpm add viem
Step 3: Send the Transaction
You can send the transaction using Dynamic’s wallet APIs. Below are examples for React, React Native, and Swift.
Use the useDynamicContext hook with wallet clients to send USDC transactions using the writeContract method:
import { useState } from "react";
import { useDynamicContext } from "@dynamic-labs/sdk-react-core";
import { isEthereumWallet } from "@dynamic-labs/ethereum";
import { parseUnits, erc20Abi } from 'viem';
const SendUSDCComponent = () => {
const { primaryWallet } = useDynamicContext();
const [txHash, setTxHash] = useState("");
const [isLoading, setIsLoading] = useState(false);
const sendUSDC = async (recipientAddress: string, amount: string) => {
if (!primaryWallet || !isEthereumWallet(primaryWallet)) {
throw new Error("Wallet not connected or not EVM compatible");
}
setIsLoading(true);
try {
const walletClient = await primaryWallet.getWalletClient();
const publicClient = await primaryWallet.getPublicClient();
// USDC contract address (replace with your network's USDC address)
const usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // Ethereum mainnet
// Convert amount to USDC units (6 decimals)
const amountInUnits = parseUnits(amount, 6);
// Use writeContract for ERC-20 transfers
const hash = await walletClient.writeContract({
address: usdcAddress as `0x${string}`,
abi: erc20Abi,
functionName: 'transfer',
args: [recipientAddress as `0x${string}`, amountInUnits],
});
setTxHash(hash);
// Wait for transaction receipt
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log("USDC transfer successful:", receipt);
} catch (error) {
console.error("Failed to send USDC:", error);
throw error;
} finally {
setIsLoading(false);
}
};
return (
<div>
<button
onClick={() => sendUSDC("0x1234567890123456789012345678901234567890", "1")} // 1 USDC
disabled={isLoading} >
{isLoading ? "Sending..." : "Send 1 USDC"}
</button>
{txHash && (
<div>
<p>Transaction Hash: {txHash}</p>
<a href={`https://etherscan.io/tx/${txHash}`} target="\_blank" rel="noopener">
View on Etherscan
</a>
</div>
)}
</div>
);
};
Important Considerations
Gas Fees
- EVM Networks: Gas fees are paid in the native token (ETH, MATIC, etc.)
- Gasless Transactions: Some networks support gasless USDC transfers using account abstraction in React and
React Native.
USDC Decimals
- EVM USDC: 6 decimal places
- Always use
parseUnits(amount, 6) when converting amounts
Error Handling
Always implement proper error handling for failed transactions:
try {
await sendUSDC(recipient, amount);
console.log("USDC sent successfully");
} catch (error) {
if (error.message.includes("insufficient funds")) {
console.error("Insufficient USDC balance");
} else if (error.message.includes("user rejected")) {
console.error("User rejected the transaction");
} else {
console.error("Transaction failed:", error);
}
}
Best Practices
- Always validate addresses before sending transactions
- Check USDC balance before attempting transfers
- Implement proper error handling and user feedback
- Test on testnets before deploying to mainnet
- Consider gas fees when calculating transfer amounts
- Use transaction simulation to show users exactly what will happen