第十三讲:DApp 转账与收款功能实现
本讲概述
这一讲将会介绍如何在 DApp 中实现通过区块链转账和收款功能。转账是区块链最基础的操作之一,我们将学习如何使用 wagmi 和 Ant Design Web3 来构建一个完整的转账功能。1
1. 区块链转账基础概念
1.1 什么是区块链转账
在区块链环境中,转账是指在参与者之间转移资产的行为,这些资产可以是加密货币(如比特币、以太坊等)或者其他基于区块链的数字资产(如代币、NFTs 等)。这些转账活动被记录在区块链上,并受到网络共识机制的安全保护。1
1.2 转账的关键要素
转账涉及以下几个要点:1
- 源地址(发送方):需要转出资产的区块链账户地址
- 目标地址(接收方):将接收资产的区块链账户地址
- 资产数量:想要转账的资产的确切数量或价值
- 交易费用:通常由执行交易的矿工或验证节点收取的费用
- 网络:完成转账的区块链网络(例如比特币网络、以太坊网络)
1.3 转账流程
区块链转账的过程大致包括以下步骤:1
- 发起方用其私钥对转账信息进行签名,并将这些信息广播到网络
- 网络中的节点(矿工或验证者)接收到交易请求,并验证签名和交易的有效性
- 一旦验证无误,转账会被打包到其他交易中构成一个新的区块
- 这个区块经过网络共识机制的确认后,被添加到区块链上
- 交易完成后,目标地址上的资产余额更新,体现了转账的结果
2. 实现转账功能
2.1 创建转账页面
首先,我们创建一个新的转账页面。新建一个 pages/transaction/index.tsx 文件:1
import React from "react";
import { MetaMask, WagmiWeb3ConfigProvider} from "@ant-design/web3-wagmi";
import { createConfig, http } from "wagmi";
import { injected } from "wagmi/connectors";
import { mainnet, sepolia } from "wagmi/chains";
import { ConnectButton, Connector } from '@ant-design/web3';
import { SendEth } from "../../components/SendEth";
const config = createConfig({
chains: [mainnet, sepolia],
transports: {
[mainnet.id]: http(),
[sepolia.id]: http(),
},
connectors: [
injected({
target: "metaMask",
}),
],
});
const TransactionDemo: React.FC = () => {
return (
<WagmiWeb3ConfigProvider
config={config}
eip6963={{
autoAddInjectedWallets: true,
}}
wallets={[MetaMask()]}
>
<Connector>
<ConnectButton />
</Connector>
<SendEth />
</WagmiWeb3ConfigProvider>
);
};
export default TransactionDemo;
2.2 创建转账组件
接下来,我们创建 SendEth 组件来实现具体的转账功能。新建 components/SendEth.tsx 文件:1
import * as React from "react";
import { Button, Form, type FormProps, Input, message } from "antd";
import {
type BaseError,
useSendTransaction,
useWaitForTransactionReceipt,
} from "wagmi";
import { parseEther } from "viem";
type FieldType = {
to: `0x${string}`;
value: string;
};
export const SendEth: React.FC = () => {
const {
data: hash,
error,
isPending,
sendTransaction,
} = useSendTransaction();
const { isLoading: isConfirming, isSuccess: isConfirmed } =
useWaitForTransactionReceipt({ hash });
const onFinish: FormProps<FieldType>["onFinish"] = (values) => {
console.log("Success:", values);
try {
sendTransaction({
to: values.to,
value: parseEther(values.value)
});
} catch (err) {
message.error('转账失败,请检查输入信息');
}
};
const onFinishFailed: FormProps<FieldType>["onFinishFailed"] = (
errorInfo
) => {
console.log("Failed:", errorInfo);
message.error('请填写完整的转账信息');
};
// 监听交易状态变化
React.useEffect(() => {
if (isConfirmed) {
message.success('转账成功!');
}
}, [isConfirmed]);
React.useEffect(() => {
if (error) {
message.error(`转账失败: ${(error as BaseError).shortMessage || error.message}`);
}
}, [error]);
return (
<div style={{ maxWidth: 600, margin: '20px auto', padding: '20px' }}>
<h2>ETH 转账</h2>
<Form
name="sendEth"
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
layout="horizontal"
>
<Form.Item<FieldType>
label="接收地址"
name="to"
rules={[
{ required: true, message: "请输入接收地址!" },
{
pattern: /^0x[a-fA-F0-9]{40}$/,
message: "请输入有效的以太坊地址!"
}
]}
>
<Input placeholder="0x..." />
</Form.Item>
<Form.Item<FieldType>
label="转账金额"
name="value"
rules={[
{ required: true, message: "请输入转账金额!" },
{
pattern: /^\d+(\.\d+)?$/,
message: "请输入有效的数字!"
}
]}
>
<Input placeholder="0.01" suffix="ETH" />
</Form.Item>
<Form.Item wrapperCol={{ offset: 6, span: 18 }}>
<Button
type="primary"
htmlType="submit"
loading={isPending || isConfirming}
disabled={isPending || isConfirming}
>
{isPending ? "确认中..." : isConfirming ? "等待确认..." : "发送转账"}
</Button>
</Form.Item>
</Form>
{/* 交易状态显示 */}
{hash && (
<div style={{ marginTop: 20, padding: 16, background: '#f5f5f5', borderRadius: 8 }}>
<h4>交易信息</h4>
<p><strong>交易哈希:</strong> {hash}</p>
{isConfirming && <p style={{ color: '#1890ff' }}>⏳ 等待区块链确认...</p>}
{isConfirmed && <p style={{ color: '#52c41a' }}>✅ 交易已确认</p>}
</div>
)}
</div>
);
};
3. 核心 Hooks 详解
3.1 useSendTransaction Hook
useSendTransaction 是 wagmi 提供的核心 Hook,用于发送交易:2
const {
data: hash, // 交易哈希
error, // 错误信息
isPending, // 是否正在发送
sendTransaction, // 发送交易函数
} = useSendTransaction();
sendTransaction 参数说明:2
sendTransaction({
to: "0xCe06B0A53b08C10fa508BF16D02bBdDc6961E3B3", // 收款地址或ENS域名
value: parseEther('0.01'), // 转账金额
gasPrice: parseGwei("10"), // Gas 价格
maxFeePerGas: parseGwei("10"), // 最大 Gas 费用
maxPriorityFeePerGas: parseGwei("10"), // 最大优先 Gas
});
3.2 useWaitForTransactionReceipt Hook
用于等待交易确认:
const {
isLoading: isConfirming,
isSuccess: isConfirmed
} = useWaitForTransactionReceipt({ hash });
4. 高级转账功能
4.1 代币转账组件
除了 ETH 转账,我们还可以实现 ERC-20 代币转账:
import React from 'react';
import { Button, Form, Input, Select, message } from 'antd';
import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
import { parseUnits } from 'viem';
// ERC-20 标准 ABI(简化版)
const ERC20_ABI = [
{
name: 'transfer',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: [{ name: '', type: 'bool' }]
}
] as const;
// 常用代币配置
const TOKENS = {
USDC: {
address: '0xA0b86a33E6441b8435b662f0E2d0B8C8C8C8C8C8' as `0x${string}`,
decimals: 6,
symbol: 'USDC'
},
USDT: {
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' as `0x${string}`,
decimals: 6,
symbol: 'USDT'
}
};
type TokenTransferForm = {
token: keyof typeof TOKENS;
to: `0x${string}`;
amount: string;
};
export const SendToken: React.FC = () => {
const { writeContract, data: hash, isPending } = useWriteContract();
const { isLoading: isConfirming, isSuccess: isConfirmed } =
useWaitForTransactionReceipt({ hash });
const onFinish = (values: TokenTransferForm) => {
const token = TOKENS[values.token];
const amount = parseUnits(values.amount, token.decimals);
writeContract({
address: token.address,
abi: ERC20_ABI,
functionName: 'transfer',
args: [values.to, amount]
});
};
return (
<div style={{ maxWidth: 600, margin: '20px auto', padding: '20px' }}>
<h2>代币转账</h2>
<Form onFinish={onFinish} layout="horizontal" labelCol={{ span: 6 }}>
<Form.Item
label="选择代币"
name="token"
rules={[{ required: true, message: '请选择代币' }]}
>
<Select placeholder="选择要转账的代币">
{Object.entries(TOKENS).map(([key, token]) => (
<Select.Option key={key} value={key}>
{token.symbol}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label="接收地址"
name="to"
rules={[
{ required: true, message: '请输入接收地址' },
{ pattern: /^0x[a-fA-F0-9]{40}$/, message: '请输入有效地址' }
]}
>
<Input placeholder="0x..." />
</Form.Item>
<Form.Item
label="转账数量"
name="amount"
rules={[
{ required: true, message: '请输入转账数量' },
{ pattern: /^\d+(\.\d+)?$/, message: '请输入有效数字' }
]}
>
<Input placeholder="100" />
</Form.Item>
<Form.Item wrapperCol={{ offset: 6 }}>
<Button
type="primary"
htmlType="submit"
loading={isPending || isConfirming}
>
{isPending ? '确认中...' : isConfirming ? '等待确认...' : '发送代币'}
</Button>
</Form.Item>
</Form>
{hash && (
<div style={{ marginTop: 20, padding: 16, background: '#f5f5f5' }}>
<p><strong>交易哈希:</strong> {hash}</p>
{isConfirming && <p style={{ color: '#1890ff' }}>等待确认...</p>}
{isConfirmed && <p style={{ color: '#52c41a' }}>转账成功!</p>}
</div>
)}
</div>
);
};
4.2 批量转账功能
实现一次性向多个地址转账:
import React, { useState } from 'react';
import { Button, Form, Input, Table, message, Space } from 'antd';
import { useSendTransaction, useWaitForTransactionReceipt } from 'wagmi';
import { parseEther } from 'viem';
interface TransferItem {
key: string;
address: string;
amount: string;
}
export const BatchTransfer: React.FC = () => {
const [transferList, setTransferList] = useState<TransferItem[]>([]);
const [currentIndex, setCurrentIndex] = useState(0);
const [isProcessing, setIsProcessing] = useState(false);
const { sendTransaction, data: hash, isPending } = useSendTransaction();
const { isLoading: isConfirming, isSuccess: isConfirmed } =
useWaitForTransactionReceipt({ hash });
// 添加转账项
const addTransferItem = (values: { address: string; amount: string }) => {
const newItem: TransferItem = {
key: Date.now().toString(),
address: values.address,
amount: values.amount
};
setTransferList([...transferList, newItem]);
};
// 删除转账项
const removeTransferItem = (key: string) => {
setTransferList(transferList.filter(item => item.key !== key));
};
// 执行批量转账
const executeBatchTransfer = async () => {
if (transferList.length === 0) {
message.warning('请先添加转账项');
return;
}
setIsProcessing(true);
setCurrentIndex(0);
for (let i = 0; i < transferList.length; i++) {
const item = transferList[i];
setCurrentIndex(i);
try {
await sendTransaction({
to: item.address as `0x${string}`,
value: parseEther(item.amount)
});
// 等待当前交易确认后再进行下一个
// 注意:实际应用中可能需要更复杂的状态管理
} catch (error) {
message.error(`第 ${i + 1} 笔转账失败`);
break;
}
}
setIsProcessing(false);
};
const columns = [
{
title: '接收地址',
dataIndex: 'address',
key: 'address',
},
{
title: '金额 (ETH)',
dataIndex: 'amount',
key: 'amount',
},
{
title: '操作',
key: 'action',
render: (_: any, record: TransferItem) => (
<Button
type="link"
danger
onClick={() => removeTransferItem(record.key)}
>
删除
</Button>
),
},
];
return (
<div style={{ maxWidth: 800, margin: '20px auto', padding: '20px' }}>
<h2>批量转账</h2>
{/* 添加转账项表单 */}
<Form onFinish={addTransferItem} layout="inline" style={{ marginBottom: 20 }}>
<Form.Item
name="address"
rules={[
{ required: true, message: '请输入地址' },
{ pattern: /^0x[a-fA-F0-9]{40}$/, message: '无效地址' }
]}
>
<Input placeholder="接收地址" style={{ width: 300 }} />
</Form.Item>
<Form.Item
name="amount"
rules={[
{ required: true, message: '请输入金额' },
{ pattern: /^\d+(\.\d+)?$/, message: '无效金额' }
]}
>
<Input placeholder="金额" style={{ width: 150 }} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
添加
</Button>
</Form.Item>
</Form>
{/* 转账列表 */}
<Table
columns={columns}
dataSource={transferList}
pagination={false}
style={{ marginBottom: 20 }}
/>
{/* 执行按钮 */}
<Space>
<Button
type="primary"
size="large"
onClick={executeBatchTransfer}
loading={isProcessing}
disabled={transferList.length === 0}
>
执行批量转账
</Button>
<Button
onClick={() => setTransferList([])}
disabled={isProcessing}
>
清空列表
</Button>
</Space>
{/* 进度显示 */}
{isProcessing && (
<div style={{ marginTop: 20, padding: 16, background: '#f5f5f5' }}>
<p>正在执行第 {currentIndex + 1} / {transferList.length} 笔转账...</p>
</div>
)}
</div>
);
};
5. 收款功能实现
5.1 收款二维码组件
import React, { useState } from 'react';
import { Card, Input, Button, QRCode, message, Space, Typography } from 'antd';
import { useAccount } from 'wagmi';
import { CopyOutlined } from '@ant-design/icons';
const { Text, Title } = Typography;
export const ReceivePayment: React.FC = () => {
const { address, isConnected } = useAccount();
const [amount, setAmount] = useState('');
const [description, setDescription] = useState('');
// 生成支付链接
const generatePaymentLink = () => {
if (!address) return '';
const params = new URLSearchParams();
if (amount) params.append('value', amount);
if (description) params.append('data', description);
return `ethereum:${address}${params.toString() ? '?' + params.toString() : ''}`;
};
// 复制地址
const copyAddress = async () => {
if (address) {
await navigator.clipboard.writeText(address);
message.success('地址已复制到剪贴板');
}
};
// 复制支付链接
const copyPaymentLink = async () => {
const link = generatePaymentLink();
if (link) {
await navigator.clipboard.writeText(link);
message.success('支付链接已复制到剪贴板');
}
};
if (!isConnected) {
return (
<Card title="收款功能">
<p>请先连接钱包以使用收款功能</p>
</Card>
);
}
return (
<div style={{ maxWidth: 600, margin: '20px auto' }}>
<Card title="收款" style={{ marginBottom: 20 }}>
<Space direction="vertical" style={{ width: '100%' }} size="large">
{/* 钱包地址 */}
<div>
<Title level={5}>我的钱包地址</Title>
<Space>
<Text code copyable={{ onCopy: copyAddress }}>
{address}
</Text>
<Button
icon={<CopyOutlined />}
size="small"
onClick={copyAddress}
>
复制
</Button>
</Space>
</div>
{/* 收款金额 */}
<div>
<Title level={5}>收款金额 (可选)</Title>
<Input
placeholder="输入收款金额"
suffix="ETH"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
</div>
{/* 收款说明 */}
<div>
<Title level={5}>收款说明 (可选)</Title>
<Input.TextArea
placeholder="输入收款说明"
value={description}
onChange={(e) => setDescription(e.target.value)}
rows={3}
/>
</div>
{/* 二维码 */}
<div style={{ textAlign: 'center' }}>
<Title level={5}>收款二维码</Title>
<QRCode
value={generatePaymentLink()}
size={200}
style={{ margin: '16px 0' }}
/>
<br />
<Button onClick={copyPaymentLink}>
复制支付链接
</Button>
</div>
</Space>
</Card>
</div>
);
};
5.2 收款记录组件
import React, { useEffect, useState } from 'react';
import { Table, Card, Tag, Button, DatePicker, Space } from 'antd';
import { useAccount, usePublicClient } from 'wagmi';
import { formatEther } from 'viem';
import dayjs from 'dayjs';
interface Transaction {
hash: string;
from: string;
to: string;
value: string;
timestamp: number;
status: 'success' | 'pending' | 'failed';
}
export const PaymentHistory: React.FC = () => {
const { address } = useAccount();
const publicClient = usePublicClient();
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [loading, setLoading] = useState(false);
const [dateRange, setDateRange] = useState<[dayjs.Dayjs, dayjs.Dayjs] | null>(null);
// 获取交易记录
const fetchTransactions = async () => {
if (!address || !publicClient) return;
setLoading(true);
try {
// 这里需要使用区块链浏览器 API 或者事件日志来获取交易记录
// 示例代码,实际需要根据具体需求实现
const latestBlock = await publicClient.getBlockNumber();
const fromBlock = latestBlock - BigInt(1000); // 查询最近1000个区块
// 获取转入交易(这里简化处理)
const logs = await publicClient.getLogs({
address: address,
fromBlock,
toBlock: 'latest'
});
// 处理日志数据转换为交易记录
// 实际实现需要更复杂的逻辑
} catch (error) {
console.error('获取交易记录失败:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchTransactions();
}, [address]);
const columns = [
{
title: '交易哈希',
dataIndex: 'hash',
key: 'hash',
render: (hash: string) => (
<a
href={`https://etherscan.io/tx/${hash}`}
target="_blank"
rel="noopener noreferrer"
>
{hash.slice(0, 10)}...{hash.slice(-8)}
</a>
),
},
{
title: '发送方',
dataIndex: 'from',
key: 'from',
render: (from: string) => (
<span>{from.slice(0, 6)}...{from.slice(-4)}</span>
),
},
{
title: '金额',
dataIndex: 'value',
key: 'value',
render: (value: string) => `${formatEther(BigInt(value))} ETH`,
},
{
title: '时间',
dataIndex: 'timestamp',
key: 'timestamp',
render: (timestamp: number) =>
dayjs(timestamp * 1000).format('YYYY-MM-DD HH:mm:ss'),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => {
const colorMap = {
success: 'green',
pending: 'orange',
failed: 'red'
};
return <Tag color={colorMap[status as keyof typeof colorMap]}>{status}</Tag>;
},
},
];
return (
<Card title="收款记录">
<Space style={{ marginBottom: 16 }}>
<DatePicker.RangePicker
value={dateRange}
onChange={setDateRange}
placeholder={['开始日期', '结束日期']}
/>
<Button onClick={fetchTransactions} loading={loading}>
刷新
</Button>
</Space>
<Table
columns={columns}
dataSource={transactions}
loading={loading}
rowKey="hash"
pagination={{
pageSize: 10,
showSizeChanger: true,
showQuickJumper: true,
}}
/>
</Card>
);
};
6. 错误处理和用户体验优化
6.1 常见错误处理
import { message } from 'antd';
export const handleTransactionError = (error: any) => {
const errorMessage = error?.shortMessage || error?.message || '未知错误';
// 常见错误类型处理
if (errorMessage.includes('insufficient funds')) {
message.error('余额不足,请检查账户余额');
} else if (errorMessage.includes('user rejected')) {
message.warning('用户取消了交易');
} else if (errorMessage.includes('gas')) {
message.error('Gas 费用不足或设置过低');
} else if (errorMessage.includes('nonce')) {
message.error('交易序号错误,请重试');
} else {
message.error(`交易失败: ${errorMessage}`);
}
};
// 网络错误处理
export const handleNetworkError = (chainId: number) => {
const networkNames: { [key: number]: string } = {
1: '以太坊主网',
11155111: 'Sepolia 测试网',
137: 'Polygon 主网',
};
const networkName = networkNames[chainId] || `网络 ${chainId}`;
message.error(`请切换到 ${networkName}`);
};
6.2 交易状态跟踪
import React from 'react';
import { Steps, Card, Button, Space } from 'antd';
import { CheckCircleOutlined, LoadingOutlined, CloseCircleOutlined } from '@ant-design/icons';
interface TransactionTrackerProps {
hash?: string;
isPending: boolean;
isConfirming: boolean;
isConfirmed: boolean;
error?: Error;
onRetry?: () => void;
}
export const TransactionTracker: React.FC<TransactionTrackerProps> = ({
hash,
isPending,
isConfirming,
isConfirmed,
error,
onRetry
}) => {
const getStepStatus = (step: number) => {
if (error) return 'error';
if (step === 0 && isPending) return 'process';
if (step === 1 && isConfirming) return 'process';
if (step === 2 && isConfirmed) return 'finish';
if (step === 0 && hash) return 'finish';
if (step === 1 && hash && !isConfirming) return 'finish';
return 'wait';
};
const getStepIcon = (step: number) => {
if (error) return <CloseCircleOutlined />;
const status = getStepStatus(step);
if (status === 'process') return <LoadingOutlined />;
if (status === 'finish') return <CheckCircleOutlined />;
return undefined;
};
const steps = [
{
title: '发起交易',
description: isPending ? '正在发送交易...' : '交易已发送',
status: getStepStatus(0),
icon: getStepIcon(0)
},
{
title: '等待确认',
description: isConfirming ? '等待区块链确认...' : '等待确认',
status: getStepStatus(1),
icon: getStepIcon(1)
},
{
title: '交易完成',
description: isConfirmed ? '交易已成功确认' : '等待完成',
status: getStepStatus(2),
icon: getStepIcon(2)
}
];
return (
<Card title="交易状态" style={{ marginTop: 20 }}>
<Steps
current={isPending ? 0 : isConfirming ? 1 : isConfirmed ? 2 : -1}
status={error ? 'error' : 'process'}
items={steps}
/>
{hash && (
<div style={{ marginTop: 16, padding: 12, background: '#f5f5f5', borderRadius: 6 }}>
<p><strong>交易哈希:</strong></p>
<p style={{ wordBreak: 'break-all', fontSize: '12px' }}>{hash}</p>
<a
href={`https://etherscan.io/tx/${hash}`}
target="_blank"
rel="noopener noreferrer"
>
在 Etherscan 上查看
</a>
</div>
)}
{error && (
<div style={{ marginTop: 16, textAlign: 'center' }}>
<p style={{ color: '#ff4d4f', marginBottom: 12 }}>
{error.message}
</p>
{onRetry && (
<Button type="primary" onClick={onRetry}>
重试
</Button>
)}
</div>
)}
</Card>
);
};
7. 安全注意事项
7.1 输入验证
// 地址验证
export const isValidAddress = (address: string): boolean => {
return /^0x[a-fA-F0-9]{40}$/.test(address);
};
// 金额验证
export const isValidAmount = (amount: string): boolean => {
const num = parseFloat(amount);
return !isNaN(num) && num > 0 && num < 1000000; // 设置合理上限
};
// Gas 价格验证
export const isValidGasPrice = (gasPrice: string): boolean => {
const num = parseFloat(gasPrice);
return !isNaN(num) && num >= 1 && num <= 1000; // Gwei 范围
};
7.2 交易确认
import React from 'react';
import { Modal, Descriptions, Button, Space, Alert } from 'antd';
import { formatEther, formatGwei } from 'viem';
interface TransactionConfirmModalProps {
visible: boolean;
onConfirm: () => void;
onCancel: () => void;
transaction: {
to: string;
value: bigint;
gasPrice?: bigint;
gasLimit?: bigint;
};
}
export const TransactionConfirmModal: React.FC<TransactionConfirmModalProps> = ({
visible,
onConfirm,
onCancel,
transaction
}) => {
const estimatedFee = transaction.gasPrice && transaction.gasLimit
? transaction.gasPrice * transaction.gasLimit
: BigInt(0);
return (
<Modal
title="确认交易"
open={visible}
onCancel={onCancel}
footer={[
<Button key="cancel" onClick={onCancel}>
取消
</Button>,
<Button key="confirm" type="primary" onClick={onConfirm}>
确认发送
</Button>
]}
>
<Alert
message="请仔细核对交易信息"
description="交易一旦发送将无法撤销,请确保信息正确"
type="warning"
style={{ marginBottom: 16 }}
/>
<Descriptions column={1} bordered>
<Descriptions.Item label="接收地址">
<span style={{ wordBreak: 'break-all' }}>{transaction.to}</span>
</Descriptions.Item>
<Descriptions.Item label="转账金额">
{formatEther(transaction.value)} ETH
</Descriptions.Item>
{transaction.gasPrice && (
<Descriptions.Item label="Gas 价格">
{formatGwei(transaction.gasPrice)} Gwei
</Descriptions.Item>
)}
{estimatedFee > 0 && (
<Descriptions.Item label="预估手续费">
{formatEther(estimatedFee)} ETH
</Descriptions.Item>
)}
</Descriptions>
</Modal>
);
};
8. 本讲总结
在这一讲中,我们学习了:
- 区块链转账基础概念 - 了解转账的基本原理和流程
- 基础转账功能 - 使用 wagmi 实现 ETH 转账
- 代币转账 - 实现 ERC-20 代币转账功能
- 批量转账 - 一次性向多个地址转账
- 收款功能 - 生成收款二维码和管理收款记录
- 错误处理 - 处理各种交易错误情况
- 安全措施 - 输入验证和交易确认
通过本讲的学习,你已经掌握了在 DApp 中实现完整转账和收款功能的方法。这些功能是大多数 DApp 的核心组成部分,为用户提供了便捷的资产管理体验。
下一讲预告
下一讲我们将学习 “DApp 数据存储与状态管理”,包括:
- 本地存储管理
- 区块链数据缓存
- 状态持久化
- 离线数据同步
敬请期待!
2546

被折叠的 条评论
为什么被折叠?



