Skip to main content

Overview

This guide shows how to use Magic’s Embedded Wallet to send USDC on Base. Users authenticate with Magic, and your app builds and sends the ERC-20 transfer transaction through the wallet’s provider — no backend required.

Prerequisites

Before starting, ensure you have:
  1. A Magic Publishable API Key from your Magic Dashboard
  2. A Base RPC endpoint (e.g., from Alchemy or QuickNode)
  3. USDC on Base in the user’s wallet
  4. ETH on Base for gas fees (typically fractions of a cent)

How It Works

  1. User authenticates with Magic
  2. Magic SDK creates an Embedded Wallet for the user
  3. Your app builds an ERC-20 transfer call with the recipient and amount
  4. The transaction is sent via the wallet’s provider and confirmed on-chain

Setting Up the Client

Initialize Magic and create viem clients connected to Base.
TypeScript
import { Magic } from "magic-sdk";
import { createWalletClient, createPublicClient, custom, http } from "viem";
import { base } from "viem/chains";

const magic = new Magic("YOUR_PUBLISHABLE_KEY", {
  network: {
    rpcUrl: "https://base-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_KEY",
    chainId: 8453,
  },
});

// Public client for read operations
const publicClient = createPublicClient({
  chain: base,
  transport: http("https://base-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_KEY"),
});

// Wallet client for transactions (after user authenticates)
const walletClient = createWalletClient({
  chain: base,
  transport: custom(magic.rpcProvider),
});

const [userAddress] = await walletClient.getAddresses();

Contract Setup

Define the USDC address and the minimal ERC-20 ABI needed for transfers.
TypeScript
import { type Address, parseUnits, formatUnits } from "viem";

const USDC_ADDRESS: Address = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
const USDC_DECIMALS = 6;

const erc20Abi = [
  {
    name: "transfer",
    type: "function",
    inputs: [
      { name: "to", type: "address" },
      { name: "value", type: "uint256" },
    ],
    outputs: [{ type: "bool" }],
    stateMutability: "nonpayable",
  },
  {
    name: "balanceOf",
    type: "function",
    inputs: [{ name: "account", type: "address" }],
    outputs: [{ type: "uint256" }],
    stateMutability: "view",
  },
] as const;

Sending USDC

Call the ERC-20 transfer function to send USDC to any address.
TypeScript
async function sendUSDC(to: Address, amount: string) {
  const value = parseUnits(amount, USDC_DECIMALS);

  const hash = await walletClient.writeContract({
    address: USDC_ADDRESS,
    abi: erc20Abi,
    functionName: "transfer",
    args: [to, value],
    account: userAddress,
  });

  const receipt = await publicClient.waitForTransactionReceipt({ hash });
  return receipt.transactionHash;
}

// Send 10 USDC
await sendUSDC("0xRecipientAddress", "10");

Checking Balance

Read the user’s USDC balance before or after a transfer.
TypeScript
async function getUSDCBalance(address: Address) {
  const balance = await publicClient.readContract({
    address: USDC_ADDRESS,
    abi: erc20Abi,
    functionName: "balanceOf",
    args: [address],
  });

  return formatUnits(balance, USDC_DECIMALS);
}

const balance = await getUSDCBalance(userAddress);
console.log(`Balance: ${balance} USDC`);

Contract Addresses

Key addresses on Base (chain ID 8453):
TypeScript
// USDC on Base (native, issued by Circle)
const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
Base has both native USDC (0x8335...) and bridged USDbC (0xd9aA...). This guide uses native USDC, which is the standard token on Base.

Key Dependencies

PackagePurpose
magic-sdkMagic authentication and Embedded Wallet
viemEthereum client for contract interactions

Troubleshooting

Symptoms: The transfer transaction fails or reverts on-chain.Solutions:
  • Check that the user has sufficient USDC balance on Base
  • Verify the recipient address is valid (not the zero address)
  • Ensure the user has ETH on Base for gas fees
Symptoms: Tokens were sent but don’t appear as USDC in the recipient’s wallet.Solutions:
  • Confirm you’re using the native USDC address (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913), not bridged USDbC
  • Check the transaction on Basescan to verify the token contract
Symptoms: Transaction is pending for a long time.Solutions:
  • Base transactions typically confirm in 2-3 seconds
  • Check Basescan for the transaction status
  • If the network is congested, the transaction will still confirm — just wait

Resources

Magic Embedded Wallets

Learn about Magic’s Embedded Wallet product

Base Documentation

Official Base network documentation

EVM Transaction Signing

Guide for signing transactions with Magic wallets

USDC on Base

Learn about Circle’s USDC stablecoin