在去中心化应用(dapp)开发中,处理以太坊虚拟机(EVM)交易是核心功能之一。无论是使用 Wagmi 框架还是原生 JavaScript,开发者都需要掌握发送交易、实时跟踪状态、估算燃气成本以及处理错误等关键技能。本文将详细介绍这些功能的实现方法,并提供代码示例。
使用 Wagmi 处理交易
Wagmi 是一个流行的 React Hooks 库,专门用于简化以太坊交互。它提供了丰富的钩子函数,帮助开发者轻松发送交易并跟踪其状态。
基础交易示例
以下示例展示了如何使用 Wagmi 发送一笔简单的 ETH 转账交易:
import { parseEther } from "viem"
import { useSendTransaction, useWaitForTransactionReceipt } from "wagmi"
function SendTransaction() {
const {
data: hash,
error,
isPending,
sendTransaction
} = useSendTransaction()
const {
isLoading: isConfirming,
isSuccess: isConfirmed
} = useWaitForTransactionReceipt({
hash
})
async function handleSend() {
sendTransaction({
to: "0x...",
value: parseEther("0.1") // 0.1 ETH
})
}
return (
<div>
<button onClick={handleSend} disabled={isPending}>
{isPending ? "Confirming..." : "Send 0.1 ETH"}
</button>
{hash && (
<div>
<div>Transaction Hash: {hash}</div>
{isConfirming && <div>Waiting for confirmation...</div>}
{isConfirmed && <div>Transaction confirmed!</div>}
</div>
)}
{error && <div>Error: {error.message}</div>}
</div>
)
}带燃气估算的高级交易
对于更复杂的交易,预先估算燃气可以提升用户体验和交易成功率:
import { parseEther } from "viem"
import {
useSendTransaction,
useWaitForTransactionReceipt,
useEstimateGas
} from "wagmi"
function AdvancedTransaction() {
const transaction = {
to: "0x...",
value: parseEther("0.1"),
data: "0x..." // 可选合约交互数据
}
// 估算燃气
const { data: gasEstimate } = useEstimateGas(transaction)
const { sendTransaction } = useSendTransaction({
...transaction,
gas: gasEstimate,
onSuccess: (hash) => {
console.log("Transaction sent:", hash)
}
})
return <button onClick={() => sendTransaction()}>Send with Gas Estimate</button>
}使用原生 JavaScript 处理交易
对于不使用前端框架的项目,可以直接通过以太坊提供商的 JSON-RPC 方法处理交易。
基础交易实现
基础交易涉及三个核心 RPC 方法:eth_requestAccounts、eth_sendTransaction 和 eth_getTransactionReceipt。
async function sendTransaction(recipientAddress, amount) {
try {
// 获取当前账户
const accounts = await ethereum.request({
method: "eth_requestAccounts"
});
const from = accounts[0];
// 将 ETH 金额转换为 wei(十六进制)
const value = `0x${(amount * 1e18).toString(16)}`;
// 准备交易对象
const transaction = {
from,
to: recipientAddress,
value,
// 燃气字段可选 - MetaMask 会自动估算
};
// 发送交易
const txHash = await ethereum.request({
method: "eth_sendTransaction",
params: [transaction],
});
return txHash;
} catch (error) {
if (error.code === 4001) {
throw new Error("Transaction rejected by user");
}
throw error;
}
}
// 跟踪交易状态
function watchTransaction(txHash) {
return new Promise((resolve, reject) => {
const checkTransaction = async () => {
try {
const tx = await ethereum.request({
method: "eth_getTransactionReceipt",
params: [txHash],
});
if (tx) {
if (tx.status === "0x1") {
resolve(tx);
} else {
reject(new Error("Transaction failed"));
}
} else {
setTimeout(checkTransaction, 2000); // 每2秒检查一次
}
} catch (error) {
reject(error);
}
};
checkTransaction();
});
}前端界面集成示例
<div>
<input type="text" id="recipient" placeholder="Recipient Address">
<input type="number" id="amount" placeholder="Amount (ETH)">
<button onclick="handleSend()">Send ETH</button>
<div id="status"></div>
</div>
<script>
async function handleSend() {
const recipient = document.getElementById("recipient").value;
const amount = document.getElementById("amount").value;
const status = document.getElementById("status");
try {
status.textContent = "Sending transaction...";
const txHash = await sendTransaction(recipient, amount);
status.textContent = `Transaction sent: ${txHash}`;
// 等待确认
status.textContent = "Waiting for confirmation...";
await watchTransaction(txHash);
status.textContent = "Transaction confirmed!";
} catch (error) {
status.textContent = `Error: ${error.message}`;
}
}
</script>带燃气估算的高级实现
通过 eth_estimateGas 方法可以预先估算交易所需燃气,并添加安全缓冲:
async function estimateGas(transaction) {
try {
const gasEstimate = await ethereum.request({
method: "eth_estimateGas",
params: [transaction]
});
// 添加20%的安全缓冲
return BigInt(gasEstimate) * 120n / 100n;
} catch (error) {
console.error("Gas estimation failed:", error);
throw error;
}
}交易处理最佳实践
交易安全性
- 验证输入数据:在发送交易前始终验证所有输入参数
- 检查资金余额:确保用户有足够的资金覆盖交易金额和燃气费
- 确认地址有效性:验证接收地址格式正确且属于目标网络
错误处理策略
- 处理常见错误:包括用户拒绝交易、资金不足等情况
- 提供清晰错误信息:让用户理解问题所在并提供解决方案
- 实现错误恢复流程:在交易失败时提供重试或其他选项
- 考虑网络拥堵:在高网络活动期间适当增加燃气估算缓冲
用户体验优化
- 显示加载状态:在交易处理期间提供明确的视觉反馈
- 实时展示进度:让用户了解交易当前处于哪个阶段
- 提供详细信息:显示交易哈希、燃气使用量等有用数据
常见交易错误代码及处理
| 错误代码 | 描述 | 解决方案 |
|---|---|---|
4001 | 用户拒绝交易 | 显示重试选项和清晰的错误信息 |
-32603 | 资金不足 | 在发送交易前检查余额 |
-32000 | 燃气设置过低 | 增加燃气限制或为估算添加缓冲 |
-32002 | 请求已在进行中 | 防止多个并发交易 |
常见问题
如何处理用户拒绝交易的情况?
当用户拒绝交易时(错误代码4001),应该友好地提示用户交易已被取消,并提供重新发起交易的选项。避免使用技术性过强的术语,保持提示信息简洁明了。
为什么需要为燃气估算添加缓冲?
网络条件和区块空间需求不断变化,添加20%左右的缓冲可以确保交易有足够燃气顺利执行,避免因燃气不足导致的交易失败。这在网络拥堵时期尤为重要。
如何准确跟踪交易状态?
使用 eth_getTransactionReceipt 方法定期检查交易收据。当收据可用时,检查 status 字段是否为 "0x1"(成功)或 "0x0"(失败)。建议设置合理的轮询间隔,如每2秒检查一次。
什么时候应该使用燃气估算?
对于简单转账,MetaMask 可以自动处理燃气估算。但对于合约交互或复杂交易,预先估算燃气可以提供更准确的成本预测和更好的用户体验,特别是在显示交易确认界面之前。
如何处理资金不足的错误?
在发送交易前检查用户余额,确保其有足够的主链币(如ETH)覆盖交易金额和燃气费用。如果余额不足,应该提前提示用户并阻止交易发送,而不是等待交易失败。
如何优化交易的用户体验?
提供清晰的交易状态反馈、实时进度更新和详细的交易信息。使用加载动画、状态提示和错误恢复选项来增强用户体验,让用户在整个过程中感到 informed 和掌控。
通过遵循这些最佳实践和代码示例,开发者可以构建出既安全又用户友好的交易处理功能,提升去中心化应用的整体质量。