只需 30 分钟,你就能在 Sonic EVM Layer-1 区块链上完成一次去中心化的数字美元转账体验。
项目概览
使用 Circle 原生发行的 USDC,通过 Viem + React + TypeScript 生态,无需后端服务器,即可完成“钱包连接 → 查询余额 → 转账 USDC → 回执确认”的全流程。下文会以 Sonic Blaze 测试网 为主要环境,避免真实资金风险。
关键词:USDC、Sonic 区块链、React、钱包连接、EVM、Blaze 测试网、CCTP、数字美元
开发前准备
1. 工具与环境
- Node.js ≥16,内置 npm
- MetaMask 浏览器插件
测试资产
S:Sonic Blaze 测试网的 gas 代币(领取方式见🔗 社区频道)- 测试 USDC:用 CCTP V2 Sample App 从 Ethereum Sepolia 桥接至 Blaze
初学者常见问题
Q1. Blaze 测试网水龙头在哪里?
官方文档每 24 小时可领取 0.2 S;若遇高峰期,可加入社区 Discord 额外申请。
Q2. 为何不建议用主网直接测试?
Blaze 测试网与主网数据隔离,链速度更快,费率极低,适合连续调试。
步骤合理拆解
| 阶段 | 关键词 | 预估耗时 |
|---|---|---|
| 项目初始化 | Vite、React | 5 分钟 |
| 链端配置 | Sonic Blaze、Viem | 5 分钟 |
| 合约调用 | USDC ABI、转账方法 | 10 分钟 |
| 界面封装 | React Hooks、事件驱动 | 10 分钟 |
动手实践:从零到转账
2. 创建并初始化项目
# 创建目录
mkdir usdc-dapp && cd usdc-dapp
npm init -y
# 安装依赖
npm install react react-dom \
typescript vite @vitejs/plugin-react \
viem @types/react @types/react-dom修改 package.json,确保出现:
{
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}运行 npm run dev,浏览器打开 http://localhost:5173 立即预览空壳页面。
👉 单击立刻查看模板代码配套最佳实践,无缝对接区块链行情数据
3. 配置链端客户端与 USDC 合约信息
3.1 创建 src/clients.ts
import { http, createPublicClient, createWalletClient, custom } from 'viem'
import { sonicBlazeTestnet } from 'viem/chains'
declare global {
interface Window { ethereum?: any }
}
export const publicClient = createPublicClient({
chain: sonicBlazeTestnet,
transport: http(),
})
export const walletClient = createWalletClient({
chain: sonicBlazeTestnet,
transport: custom(window.ethereum),
})3.2 定义 USDC 合约常量 src/constants.ts
export const USDC_CONTRACT_ADDRESS =
'0xA4879Fed32Ecbef99399e5cbC247E533421C4eC6'
export const USDC_ABI = [
{
constant: false,
inputs: [
{ name: '_to', type: 'address' },
{ name: '_value', type: 'uint256' },
],
name: 'transfer',
outputs: [{ name: '', type: 'bool' }],
type: 'function',
},
{
constant: true,
inputs: [{ name: '_owner', type: 'address' }],
name: 'balanceOf',
outputs: [{ name: 'balance', type: 'uint256' }],
type: 'function',
},
] as const常见问题
Q3. 我可以用其他工具包吗?
当然可以。Web3.js、Ethers.js 也能完成,但 Viem 对 TypeScript 类型支持更完备,大幅提升开发体验。
Q4. ABI 不完整会影响转账吗?
如果我们仅用到 balanceOf 与 transfer 两条函数,现有 ABI 足够;如需 Metamask 额外签名提示,再补充编码即可。
4. 构建 React 交互界面 src/App.tsx
核心状态与流程:
- 点击 连接钱包
- 读取 USDC 余额
- 填写 接收地址与金额(USDC)
- 点击 转账 → 等待 TransactionHash → 读取 Receipt
import { useEffect, useState } from 'react'
import { publicClient, walletClient } from './clients'
import { USDC_CONTRACT_ADDRESS, USDC_ABI } from './constants'
import { formatUnits, parseUnits } from 'viem'
export default function App() {
const [account, setAccount] = useState<`0x${string}`>()
const [balance, setBalance] = useState('0')
const [recipient, setRecipient] = useState('')
const [amount, setAmount] = useState('')
const [hash, setHash] = useState<string>()
const [receipt, setReceipt] = useState<any>()
const [transferring, setTransferring] = useState(false)
// 连接钱包
const connect = async () => {
const [addr] = await walletClient.requestAddresses()
setAccount(addr)
}
// 读取余额
const fetchBalance = async () => {
if (!account) return
const raw = await publicClient.readContract({
address: USDC_CONTRACT_ADDRESS,
abi: USDC_ABI,
functionName: 'balanceOf',
args: [account],
})
setBalance(formatUnits(raw, 6))
}
// 发起转账
const handleTransfer = async (e: React.FormEvent) => {
e.preventDefault()
if (!account || !recipient || !amount) return
try {
setTransferring(true)
const value = parseUnits(amount, 6)
// 模拟交易
const { request } = await publicClient.simulateContract({
account,
address: USDC_CONTRACT_ADDRESS,
abi: USDC_ABI,
functionName: 'transfer',
args: [recipient as `0x${string}`, value],
})
const txHash = await walletClient.writeContract(request)
setHash(txHash)
const rcpt = await publicClient.waitForTransactionReceipt({ hash: txHash })
setReceipt(rcpt)
// 余额及时刷新
await fetchBalance()
} finally {
setTransferring(false)
}
}
// 每次账户变化后刷新余额
useEffect(() => {
fetchBalance()
}, [account])
return (
<main style={{ fontFamily: 'Segoe UI', margin: 40 }}>
<h1>USDC 转账演示</h1>
{!account ? (
<button onClick={connect}>Connect Wallet</button>
) : (
<section>
<p>账户:{account}</p>
<p>余额:{balance} USDC</p>
<form onSubmit={handleTransfer}>
<div>
<label>接收地址 </label>
<input
type="text"
required
placeholder="0x..."
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
/>
</div>
<div style={{ marginTop: 10 }}>
<label>金额 (USDC) </label>
<input
type="number"
required
min="0.01"
step="0.01"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
</div>
<button type="submit" disabled={transferring}>
{transferring ? '处理中' : '立即转账'}
</button>
</form>
{hash && <p>Tx Hash: {hash}</p>}
{receipt && <p>Status: {receipt.status}</p>}
</section>
)}
</main>
)
}5. 启动与验证
创建
index.html并挂载 React 根节点:<!doctype html> <head> <meta charset="utf-8" /> <title>USDC 区块链转账 Demo</title> </head> <body> <div id="root"></div> <script type="module" src="/src/main.tsx"></script> </body>创建
src/main.tsx:import ReactDOM from 'react-dom/client' import App from './App' ReactDOM.createRoot(document.getElementById('root')!).render(<App />)- 重新执行
npm run dev,浏览器控制台应打印状态日志即可开始调试。
踩坑记录与调试技巧
- “Error: insufficient funds for gas”:确认 Blaze 测试网 gas 代币充足。
- 转移 1 USDC 显示 balance ≈
1e18:去掉formatUnits(raw, 6)的 Bug,保持与 USDC 精度一致即可解决。 - 钱包不弹出签名 → 检查
window.ethereum?.isMetaMask && walletClient.writeContract(...)是否在同一上下文中调用。
常见问题(FAQ)
Q5. 如何安全保存 USDC?
主网请使用硬件钱包或移动端多重签名方案。测试网可大胆尝试,但务必别把私钥塞进开源仓库。
Q6. React 能否拓展成多链支持?
通过配置 <RainbowKit> 或 <WagmiConfig>,耦合 10 余条 EVM 链只需数行变更。
Q7. 将来主网迁移要注意什么?
- USDC 合约地址替换
- 环境变量
.env.production里放主网 RPC 端点 - 关闭 Remix Console.log,减少 gas 浪费
结语
至此,你已经完成了在 Sonic 区块链上使用 原生 USDC 构建前端转账 DApp 的全部流程:环境检查、合约交互、UI 封装、调试验证。把这段模板继续拓展,就能添加 Token Swap、NFT 展示或其它 DeFi 功能。祝编码愉快!