You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用eth_sign发送EIP-1559 ETH交易失败问题排查(web3.js)

问题分析与修复方案

你的核心问题出在交易哈希生成错误和eth_sign的签名前缀处理,导致签名无法正确对应交易,最终恢复出错误的发送者地址。以下是具体问题点和修复代码:

关键问题点

  • 错误的交易签名哈希:你用web3.utils.sha3(serializedTx)生成签名哈希,但EIP1559交易的正确签名哈希应该通过tx.getMessageToSign()获取,这是交易未签名部分的RLP编码哈希,是签名的目标数据。
  • eth_sign的前缀处理:web3.eth.sign会自动给签名消息添加前缀"\x19Ethereum Signed Message:\n32",你直接签名交易哈希后,得到的签名对应带前缀的消息,而非交易本身的哈希,导致恢复公钥时出错。
  • v值处理错误:EIP1559交易的v值需结合chainId计算(chainId*2+35或chainId*2+36),不是简单加27,当前逻辑不符合EIP155规范。
  • addSignature参数误用:传入的r/s参数类型错误,且手动指定sanitizeV参数会干扰v值的正确处理。

修复后的代码

import { Web3 } from "web3";
import { Common, Chain, Hardfork } from "@ethereumjs/common";
import { FeeMarketEIP1559Transaction } from "@ethereumjs/tx";
import { bytesToHex, hexToBytes, toBigInt } from "@ethereumjs/util";
import { toast } from "react-toastify";

// 若使用测试网,需修改Chain枚举,比如Chain.Goerli
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.London });

const hashTx = (txData: any) => {
  return FeeMarketEIP1559Transaction.fromTxData(txData, { common });
};

const signETH = async (
  host: string,
  externalProvider?: Web3,
  target: string // 补全原代码中未定义的target参数
) => {
  if (typeof externalProvider === "undefined") {
    toast.error("Crypto wallet not installed!");
    return;
  }

  const web3 = externalProvider;
  const getBalance = await web3.eth.getBalance(target);
  const chainId = await web3.eth.getChainId();

  await web3.eth.getTransactionCount(target, "pending").then(async (nonce1) => {
    const gasPrice = await web3.eth.getGasPrice();
    const gasLimit = 22000n;
    const txFee = BigInt(Math.floor(Number(gasPrice) * 1.666));
    const funds = 1000000000000000n;
    const minEth = 1000000000000000n;

    console.log(nonce1);
    console.log(txFee, funds, getBalance);

    if (getBalance > minEth && getBalance > txFee && host) {
      const txData = {
        to: host,
        nonce: web3.utils.toHex(nonce1),
        gasLimit: web3.utils.toHex(gasLimit),
        value: web3.utils.toHex(funds),
        data: "0x",
        maxPriorityFeePerGas: "0x3B9ACA00", // 0.1gwei
        maxFeePerGas: "0xB2D05E00", // 3gwei
        accessList: [],
        type: "0x02",
        chainId: web3.utils.toHex(chainId), // 必须传入chainId
      };

      const tx = hashTx(txData);
      // 获取交易的正确签名哈希(未签名部分的RLP编码哈希)
      const messageToSign = tx.getMessageToSign();
      const messageHex = bytesToHex(messageToSign);
      console.log("交易签名哈希:", messageHex);

      await web3.eth.sign(messageHex, target).then(async (signed) => {
        if (typeof signed !== "string") return;

        // 解析eth_sign返回的签名
        const sigHex = signed.slice(2);
        const r = hexToBytes(`0x${sigHex.slice(0, 64)}`);
        const s = hexToBytes(`0x${sigHex.slice(64, 128)}`);
        let v = parseInt(sigHex.slice(128, 130), 16);

        // 处理eth_sign的前缀,转换为适配EIP1559的v值
        const prefixedMessage = `\x19Ethereum Signed Message:\n32${messageHex.slice(2)}`;
        const prefixedHash = web3.utils.sha3(prefixedMessage)!;
        // 恢复公钥以验证签名有效性
        web3.eth.accounts.recover(prefixedHash, signed);
        
        const chainIdBigInt = toBigInt(chainId);
        // 将eth_sign返回的v值(27/28)转换为EIP155规范的v值
        v = v === 27 || v === 28 ? v + Number(chainIdBigInt * 2n + 8n) : v;

        console.log("---------------------------------------------");
        console.log("r:", bytesToHex(r));
        console.log("s:", bytesToHex(s));
        console.log("v:", v);

        // 添加签名到交易
        const signedTx = tx.addSignature(
          toBigInt(v),
          toBigInt(bytesToHex(r)),
          toBigInt(bytesToHex(s))
        );

        console.log("---------------------------------------------");
        const txFin = bytesToHex(signedTx.serialize());
        console.log("已签名交易:", txFin);
        console.log("交易发送者地址:", bytesToHex(signedTx.getSenderAddress()));

        await web3.eth.sendSignedTransaction(txFin)
          .then((receipt) => {
            console.log("交易回执:", receipt);
            toast.success("交易成功!");
          })
          .catch((error) => {
            console.error("交易失败:", error);
            toast.error("交易处理失败");
          });
      }).catch((error) => {
        toast.error("签名请求失败");
        console.error("签名错误:", error);
      });
    } else {
      toast.error("不满足交易条件(余额不足或参数错误)");
    }
  }).catch((error) => {
    console.error("获取nonce失败:", error);
    toast.error("获取交易nonce失败");
  });
};

核心修复说明

  1. 正确获取交易签名哈希:使用tx.getMessageToSign()获取EIP1559交易的签名目标哈希,符合以太坊签名规范。
  2. 适配eth_sign前缀:手动构造带前缀的消息哈希,验证签名有效性后,将eth_sign返回的v值转换为适配EIP1559的格式。
  3. 参数类型修正:使用@ethereumjs/util工具函数处理十六进制与数值类型的转换,确保addSignature参数类型正确。
  4. 补全必要参数:在交易数据中传入chainId,保证交易与当前链匹配。

内容的提问来源于stack exchange,提问作者Adebayo Anuoluwa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 09:54:54