Hardhat+ethers.js调用UniswapV3 exactInput报错:deployer.sendTransaction非函数
问题:Hardhat环境调用Uniswap V3兑换UNI时出现
deployer.sendTransaction is not a function错误 我在Goerli网络用Hardhat部署,通过UniswapV3接口兑换购买UNI,调用getToken()时触发TypeError: deployer.sendTransaction is not a function错误,但很多示例都这么写。另外,查询UNI余额的balanceOf()功能正常,需要错误原因分析、修复方案和代码改进建议。
原Hardhat代码
const { ethers, getNamedAccounts, network } = require("hardhat") const {abi: V3SwapRouterABI} = require('@uniswap/v3-periphery/artifacts/contracts/interfaces/ISwapRouter.sol/ISwapRouter.json') const FEE_SIZE = 3 function encodePath(path, fees) { if (path.length != fees.length + 1) { throw new Error('path/fee lengths do not match') } let encoded = '0x' for (let i = 0; i < fees.length; i++) { // 20 byte encoding of the address encoded += path[i].slice(2) // 3 byte encoding of the fee encoded += fees[i].toString(16).padStart(2 * FEE_SIZE, '0') } // encode the final token encoded += path[path.length - 1].slice(2) return encoded.toLowerCase() } async function getToken() { // const signer = provider.getSigner() const deadline = Math.floor(Date.now()/1000) + (60*10) const wethToken= "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" const Uni= "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984" const UniswapRouter="0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" const path = encodePath([wethToken, Uni], [3000]) console.log(path) const { deployer } = await getNamedAccounts() const UniV3Contract = await ethers.getContractAt( V3SwapRouterABI, UniswapRouter, deployer ) const params = { path: path, recipient:deployer, deadline: deadline, amountIn: ethers.utils.parseEther('0.01'), amountOutMinimum: 0 } const encodedData = UniV3Contract.interface.encodeFunctionData("exactInput", [params]) const txArg = { to: UniswapRouter, from: deployer, data: encodedData, gasLimit: ethers.utils.hexlify(1000000) } const tx = await deployer.sendTransaction(txArg) console.log('tx: ', tx) const IUni = await ethers.getContractAt( "IERC20", Uni, deployer ) const UniBlance = await IUni.balanceOf(deployer) console.log(`Got ${UniBlance.toString()} UNI`) } module.exports = { getToken }
非Hardhat环境对比代码
const {abi: V3SwapRouterABI} = require('@uniswap/v3-periphery/artifacts/contracts/interfaces/ISwapRouter.sol/ISwapRouter.json') const { ethers } = require("ethers") require("dotenv").config() const INFURA_URL_TESTNET = process.env.INFURA_URL_TESTNET const PRIVATE_KEY = process.env.PRIVATE_KEY const WALLET_ADDRESS = process.env.WALLET_ADDRESS // now you can call sendTransaction const wethToken= "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6" const Uni= "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984" const UniswapRouter="0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" const UniV3Contract = new ethers.Contract( UniswapRouter, V3SwapRouterABI ) const provider = new ethers.providers.JsonRpcProvider(INFURA_URL_TESTNET) const wallet = new ethers.Wallet(PRIVATE_KEY) const signer = wallet.connect(provider) const FEE_SIZE = 3 function encodePath(path, fees) { if (path.length != fees.length + 1) { throw new Error('path/fee lengths do not match') } let encoded = '0x' for (let i = 0; i < fees.length; i++) { // 20 byte encoding of the address encoded += path[i].slice(2) // 3 byte encoding of the fee encoded += fees[i].toString(16).padStart(2 * FEE_SIZE, '0') } // encode the final token encoded += path[path.length - 1].slice(2) return encoded.toLowerCase() } async function getToken() { const path = encodePath([wethToken, Uni], [3000]) const deadline = Math.floor(Date.now()/1000) + (60*10) const params = { path: path, recipient: WALLET_ADDRESS, deadline: deadline, amountIn: ethers.utils.parseEther('0.01'), amountOutMinimum: 0 } const encodedData = UniV3Contract.interface.encodeFunctionData("exactInput", [params]) const txArg = { to: UniswapRouter, from: WALLET_ADDRESS, data: encodedData, gasLimit: ethers.utils.hexlify(1000000) } const tx = await signer.sendTransaction(txArg) console.log('tx: ', tx) const receipt = tx.wait() console.log('receipt: ', receipt) } module.exports = { getToken }
错误原因
getNamedAccounts()返回的deployer只是字符串格式的钱包地址,不是ethers.js的Signer对象,而sendTransaction()是Signer对象的专属方法,直接调用自然会报错。非Hardhat环境里用的signer.sendTransaction()是通过钱包私钥生成的Signer实例,所以能正常工作。
修复方案
方案1:将deployer地址转为Signer对象
替换原代码中获取deployer的部分,生成Signer后再调用交易:
const { deployer } = await getNamedAccounts(); const signer = await ethers.getSigner(deployer); // 把地址转为可签名的Signer对象 // 后续把deployer.sendTransaction(txArg)改成signer.sendTransaction(txArg)
方案2:直接用Signer初始化合约(推荐)
不需要手动编码交易,直接用Signer初始化合约后调用exactInput方法,更简洁:
const { deployer } = await getNamedAccounts(); const signer = await ethers.getSigner(deployer); // 用Signer初始化合约,而非地址 const UniV3Contract = await ethers.getContractAt( V3SwapRouterABI, UniswapRouter, signer ); // 直接调用合约方法,ETH金额通过value参数传入(原生代币交易必须这么做) const tx = await UniV3Contract.exactInput(params, { value: ethers.utils.parseEther('0.01'), gasLimit: ethers.utils.hexlify(1000000) });
注意:用ETH兑换ERC20时,Uniswap V3的
amountIn参数无效,必须通过交易的value字段传入ETH金额。
代码改进建议
- 修正网络地址:原代码用了主网WETH地址
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2,Goerli网络的WETH地址是0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6,必须替换。 - 添加滑点保护:不要把
amountOutMinimum设为0,可通过callStatic估算预期输出后,扣减1%-5%作为最小输出,防止MEV攻击或滑点过大导致损失。 - 动态Gas估算:不要固定
gasLimit,用ethers.provider.estimateGas()动态计算,避免gas浪费或不足。 - 错误捕获:添加try-catch块处理交易失败的情况(比如滑点超限、gas不足)。
- 环境变量管理:把合约地址、网络配置放到
.env文件,不要硬编码,方便切换网络。
修复后的完整Hardhat代码
const { ethers, getNamedAccounts, network } = require("hardhat") const {abi: V3SwapRouterABI} = require('@uniswap/v3-periphery/artifacts/contracts/interfaces/ISwapRouter.sol/ISwapRouter.json') require("dotenv").config() const FEE_SIZE = 3 function encodePath(path, fees) { if (path.length != fees.length + 1) { throw new Error('path/fee lengths do not match') } let encoded = '0x' for (let i = 0; i < fees.length; i++) { encoded += path[i].slice(2) encoded += fees[i].toString(16).padStart(2 * FEE_SIZE, '0') } encoded += path[path.length - 1].slice(2) return encoded.toLowerCase() } async function getToken() { try { const deadline = Math.floor(Date.now()/1000) + (60*10) // Goerli网络的WETH和UNI地址 const wethToken= "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6" const uniToken= "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984" const uniswapRouter="0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" const path = encodePath([wethToken, uniToken], [3000]) const { deployer } = await getNamedAccounts() const signer = await ethers.getSigner(deployer) // 用Signer初始化合约 const uniV3Contract = await ethers.getContractAt( V3SwapRouterABI, uniswapRouter, signer ) const ethAmount = ethers.utils.parseEther('0.01') // 1%滑点保护:估算预期输出后扣减1% const estimatedOutput = await uniV3Contract.callStatic.exactInput({ path, recipient: ethers.constants.AddressZero, deadline: Math.floor(Date.now()/1000) + 60, amountIn: ethAmount, amountOutMinimum: 0 }) const amountOutMinimum = estimatedOutput.mul(99).div(100) const params = { path: path, recipient: deployer, deadline: deadline, amountIn: ethAmount, amountOutMinimum: amountOutMinimum } console.log(`开始兑换:输入${ethers.utils.formatEther(ethAmount)} ETH,最小预期输出${ethers.utils.formatUnits(amountOutMinimum, 18)} UNI`) // 动态估算gas并发送交易 const tx = await uniV3Contract.exactInput(params, { value: ethAmount, gasLimit: await ethers.provider.estimateGas({ to: uniswapRouter, data: uniV3Contract.interface.encodeFunctionData("exactInput", [params]), value: ethAmount }) }) console.log(`交易哈希:${tx.hash}`) await tx.wait() console.log("交易已确认") const iUni = await ethers.getContractAt("IERC20", uniToken, signer) const uniBalance = await iUni.balanceOf(deployer) console.log(`当前UNI余额:${ethers.utils.formatUnits(uniBalance, 18)}`) } catch (error) { console.error("兑换失败:", error) } } module.exports = { getToken }
内容的提问来源于stack exchange,提问作者Oriok
相关产品推荐
相关产品推荐

