使用ethers库时如何获取代理合约及对应基础合约的ABI
代理合约ABI获取与逻辑合约定位方案
定位代理合约绑定的基础(逻辑)合约
- 优先使用Etherscan内置代理识别能力:调用Etherscan合约查询接口时传入
module=proxy相关参数,接口会自动返回该代理合约当前绑定的实现合约地址,已适配透明代理、UUPS等绝大多数主流可升级代理模式,无需额外处理。 - 非标准代理手动读取方案:如果Etherscan无法识别代理类型,可直接通过链上存储读取实现地址,通用存储槽规则如下:
- 符合EIP-1967标准的代理:实现地址存储在
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc槽位 - 旧版OpenZeppelin透明代理:实现地址存储在
0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3槽位
直接通过ethers的provider.getStorageAt(代理合约地址, 对应槽位)读取存储值,截掉前导12字节的0值,解析后得到的就是基础合约地址。
- 符合EIP-1967标准的代理:实现地址存储在
获取完整可用ABI的方法
- 分别拉取两份ABI做合并去重:第一份是代理合约本身的ABI(包含代理升级、权限管理等代理层方法),第二份是定位到的基础合约的ABI(包含mint等业务逻辑方法),将两个ABI数组合并,去除重复的函数/事件定义,得到的就是可完整交互的ABI。
- 注意:不要仅使用基础合约的ABI初始化实例,否则无法调用代理层自带的管理方法,会出现调用报错。
ethers库中的使用示例
合并完ABI后,直接使用代理合约地址初始化合约实例即可,所有调用会自动通过代理委托执行到逻辑合约,无需指向基础合约地址:
// 初始化provider/signer const provider = new ethers.JsonRpcProvider(RPC_ENDPOINT) const signer = await provider.getSigner() // 合并去重后的完整ABI const fullAbi = [...proxyAbi, ...implementationAbi] // 初始化合约实例,地址填代理合约地址 const nftContract = new ethers.Contract(PROXY_CONTRACT_ADDRESS, fullAbi, signer) // 直接调用mint等业务方法即可正常执行 const tx = await nftContract.mint(mintParams) await tx.wait()
如果本地有合约源码编译产物,也可以直接用基础合约的ContractFactory连接到代理地址生成实例,再手动补充代理层的方法定义即可,不需要手动拼接ABI。
内容的提问来源于stack exchange,提问作者Webby
相关产品推荐
相关产品推荐

