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

Hardhat使用ethers.js进行合约交互时无法切换签名者如何解决

问题根因

两个报错的核心诱因和触发逻辑非常明确:

  • 传入Signer实例时抛出invalid address or ENS name错误:本质是传入的Signer实例和当前合约实例依赖的ethers版本不兼容,合约实例没有识别到传入值是合法Signer对象,直接把它当做普通参数做地址/ENS解析才报错。90%以上的场景是项目中存在多版本ethers冲突:本地手动安装的ethers版本,和Hardhat ethers插件内置依赖的ethers版本不一致,跨版本传递实例导致类型校验失败。
  • 传入addr1.address地址字符串时抛出VoidSigner cannot sign transactions错误:这是ethers的预期行为。connect()方法传入纯地址字符串时,会自动生成一个仅绑定地址、没有签名权限的VoidSigner实例,这类实例仅支持只读的合约调用,根本无法发送需要签名的交易,这个方案本身就是错误用法。
正确实现方案

第一步:先解决版本冲突

不要在项目中单独手动安装ethers依赖,直接使用Hardhat官方ethers插件匹配的版本即可,先执行命令清理冗余依赖:

npm uninstall ethers
npm install --save-dev @nomicfoundation/hardhat-ethers

如果使用的是旧版Hardhat,替换为对应版本的@nomiclabs/hardhat-ethers即可,核心原则是不要在package.json中手动锁定ethers版本,让插件自动拉取完全兼容的版本,从根源避免多版本冲突。

第二步:按规范调用合约切换签名者

优先使用hardhat-ethers插件提供的工厂方法获取合约实例,不要自己随意手动初始化Contract实例,这类实例默认已经绑定Hardhat网络环境和签名能力,切换账户直接使用官方示例的写法即可正常运行:

// 必须从hardhat包导出ethers,不要直接从独立ethers包导入
const { ethers } = require("hardhat");

async function main() {
  // 从Hardhat本地节点/配置的网络中获取预置账户的Signer实例
  const [owner, addr1] = await ethers.getSigners();

  // 场景1:调用新部署的合约
  const GreeterFactory = await ethers.getContractFactory("Greeter");
  const greeter = await GreeterFactory.deploy("初始问候语");
  // ethers v5 用 await greeter.deployed(),v6 用下面这句
  await greeter.waitForDeployment();

  // 切换到addr1签名发起交易,和官方示例写法完全一致
  const tx = await greeter.connect(addr1).setGreeting("Hello from addr1!");
  await tx.wait(); // 等待交易上链
  console.log("交易发送地址:", addr1.address);

  // 场景2:调用已经部署的现有合约
  const deployedGreeter = await ethers.getContractAt("Greeter", "目标合约地址");
  await deployedGreeter.connect(addr1).setGreeting("调用已部署合约");
}

main().catch(err => {
  console.error(err);
  process.exitCode = 1;
})

特殊场景:手动初始化合约实例的正确写法

如果有特殊需求需要手动创建Contract实例,必须保证所有相关实例(provider、signer、Contract)都来自同一个Hardhat导出的ethers对象,禁止跨不同ethers实例传递参数:

const { ethers } = require("hardhat");
const [addr1] = await ethers.getSigners();

// 所有实例都从同一个ethers对象生成
const provider = ethers.provider;
const manualGreeter = new ethers.Contract("合约地址", 合约ABI, provider);
// 切换签名者发交易
const connectedGreeter = manualGreeter.connect(addr1);
await connectedGreeter.setGreeting("手动初始化合约测试");
避坑提醒
  • 不要给connect()传入纯地址字符串发送需要签名的交易,仅做只读call的时候可以用这种方式。
  • 不要混用require("ethers")和require("hardhat").ethers,前者是独立安装的ethers包,后者是Hardhat插件封装、绑定了网络配置和账户权限的实例,跨实例传递Signer必然触发类型不匹配错误。
  • 注意区分ethers v5和v6的API差异,比如部署合约后v5用.deployed()确认部署,v6用.waitForDeployment(),API和依赖版本不匹配也会导致实例运行异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 23:18:47