Web3 基础

100 阅读3分钟

1、前端如何处理交易回执(Transaction Receipt)

答:交易回执就是区块链上交易执行完成后返回的一份“证明单据”。

  • 你发了一笔交易(比如转账/调用合约方法),链上会先给你一个交易哈希(txHash)
  • 然后你需要等交易被矿工打包、执行完,链才会给你一份Receipt,里面有这笔交易的结果(成功/失败)、Gas用了多少、事件日志(Logs)等。
// ethers.js 发送交易
const tx = await contract.transfer(to, amount);
console.log('tx', tx);
console.log('交易哈希:', tx.hash);  // 这里可以拿到交易hash
// 等待链上确认,得到回执
const receipt = await tx.wait();
console.log('交易回执:', receipt);
console.log('区块号:', receipt.blockNumber);
console.log('Gas 用量:', receipt.gasUsed.toString());
console.log('事件日志:', receipt.logs);

注意:

  • 只拿txHash就当交易完成
    • txHash只是“受理单号”,交易可能还在排队,甚至可能失败。一定要等receipt
  • 忘记处理失败交易
    • receipt里有个status字段:1 = 成功
    • 有些链上环境(比如测试网)Gas不够时,交易会失败,但还是能拿到receipt
  • 事件解析
    • Receipt里的logs只是原始数据,需要用ABI解码才能拿到可读的事件参数。

2、如何处理链上交易失败或被拒绝?

答:链上交易失败/被拒绝有几种情况:

  • 用户拒绝签名(在钱包里点了“拒绝”) → 前端要捕获异常。
  • 交易未打包(Gas 设置不合理,或者节点不同步) → 可能卡在pending状态。
  • 链上执行失败(比如合约 require 条件没满足,或者余额不足) → 会有回执,但 status !==1

解决方式:

  • 捕获异常(try/catch)。
  • 检查交易回执的status
  • 给用户友好的提示(比如 “余额不足”、“Gas不够”、“合约执行失败”)。

常见坑点:

  • 没区分拒绝签名和链上失败:用户点“拒绝”报的是error.code === 4001,不是交易失败。
  • 只看tx,不看receipttx.hash并不能保证交易执行成功,必须等receipt并检查status
  • 忘记处理pending过久:可以用provider.waitForTransaction(tx.hash, timeout)设置超时,否则用户会一直等。
  • 错误提示不明确:不要只提示“交易失败”,最好能告诉用户可能原因:Gas不够、余额不足、合约条件未满足。
try {
  // 1. 发送交易
  const tx = await contract.transfer(to, amount);
  // 2. 等待回执
  const receipt = await tx.wait();
  // 3. 检查交易状态
  if (receipt.status === 1) {
    console.log('✅ 交易成功:', receipt.transactionHash);
  } else {
    console.error('❌ 交易失败:', receipt);
  }
}catch (error) {
  // 捕获钱包拒绝签名 或 RPC 报错
  if (error.code === 4001) {
    console.error('用户拒绝签名');
  } else {
    console.error('交易异常:', error);
  }
}

3、前端如何防止用户签名被恶意使用?

答:区块链里用户签名(signature)= 用户用私钥对一段消息做的确认

问题:

  • 如果你让用户随便签一段字符串(比如空白签名),黑客可能拿去伪造请求。
  • 如果签名消息没有“上下文”,别人截获后可能在别处复用(重放攻击)。

方法:

  • 明确签名目的(不要让用户签空白消息)。
  • 加入唯一性(比如随机数nonce、时间戳)。
  • 限制作用范围(指定链 ID、DApp 域名)。
  • 采用标准协议(比如 EIP-712 typed data)。

常见坑点

  • 空白签名:千万不要用 "personal_sign" 让用户签空白字符串。
  • 缺少nonce:没有随机数/一次性标识,签名可能被重放。(确保每次签名都是唯一的,可以防止重复签名)
  • 没绑定domain/chainId:签名可能被其他DApp或链复用。
  • 误导性签名:提示要清楚(比如“确认登录”,而不是“签个字”),否则用户体验差。
  • 前端存储签名:不要把签名直接存localStorage,避免被盗用。
// 普通签名
const signature = await signer.signMessage(''); // 签名空字符串 得到 “0x”

// 验证签名
const recovered = verifyMessage(message, signature);
console.log('签名者:', recovered); // 签名地址
// EIP-712签名
const domain = {
  name: 'XXX APP',
  version: '2',
  chainId: 1,
  verifyingContract: '0x833589fCD6eDb...54bdA02913'
};
const types = {
  Login: [
    { name: 'user', type: 'string' },
    { name: 'nonce', type: 'uint256' },
    { name: 'timestamp', type: 'uint256' }
  ]
};
const value = {
  user: 'XXX',
  nonce: 42,
  timestamp: Date.now()
};
const signature = await signer.signTypedData(domain, types, value);

// 验证签名
const recoveredAddress = verifyTypedData(domain, types, value, signature);
console.log('签名者:', recoveredAddress);