Chainlink VRF部署报错:无效地址或ENS名称问题求助
部署集成Chainlink VRF的KartoCarsNFT合约时出现「invalid address or ENS name with Chainlink VRF」错误的排查
我们在部署集成Chainlink VRF的KartoCarsNFT智能合约时,触发错误提示:Error: invalid address or ENS name with Chainlink VRF。尝试修改地址格式等操作后仍未解决,不确定是部署脚本还是合约配置问题,附上相关代码寻求技术排查帮助。
智能合约代码
// SPDX-License-Identifier: MIT pragma solidity 0.8.17; import "erc721a/contracts/ERC721A.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol"; contract KartoCarsNFT is ERC721A, Ownable, VRFConsumerBaseV2 { event RequestSent(uint256 requestID, uint32 numWords); event RequestFulfilled(uint256 requestID, uint256[] randomWords, uint256 payment); struct RequestStatus { uint256 paid; bool fulfilled; uint256[] randomWords; } enum SaleStatus { PAUSED, WHITELIST, PUBLIC } using Strings for uint256; // ~~~~~~~~~~~~~~~~~~ Set Sale as PAUSED on DEPLOY ~~~~~~~~~~~~~~~~~~ SaleStatus public saleStatus = SaleStatus.PAUSED; string private preRevealURI; string private postRevealBaseURI; // ~~~~~~~~~~~~~~~~~~ Sale Settings ~~~~~~~~~~~~~~~~~~ uint256 public PRICE_KC = 0.07 ether; uint256 private constant MAX_KC = 10000; uint256 public publicPerWallet = 3; uint256 public wlPerWallet = 3; //~~~~~~~~~~~~~~~~~~ Chainlink Settings ~~~~~~~~~~~~~~~~~~ uint256[] public requestIds; uint256 public lastRequestId; uint32 callbackGasLimit = 100000; uint16 requestConfirmations = 3; uint32 numWords = 2; VRFCoordinatorV2Interface COORDINATOR; address[] private teamAddress; uint[] private teamSplit; // ~~~~~~~~~~~~~~~~~~ Chainlink Sub ID ~~~~~~~~~~~~~~~~~~ uint64 s_subscriptionId; bytes32 public whitelistMerkleRoot; mapping(address => uint256) public wlMintedAmt; mapping (address => uint256) public publicMintAmt; mapping(uint256 => RequestStatus) public s_requests; // ~~~~~~~~~~~~~~~~~~ Reveal ~~~~~~~~~~~~~~~~~~ bool public revealed; uint256 public tokenOffset; // ~~~~~~~~~~~~~~~~~~ Chainlink VRF ~~~~~~~~~~~~~~~~~~ bytes32 public chainlinkKeyHash; constructor( address[] memory _team, uint[] memory _split, string memory _preRevealURI, address _vrfCoordinator, bytes32 _chainlinkKeyHash, uint64 _subscriptionId ) ERC721A("Karto Cars NFT", "KARTOCARS") VRFConsumerBaseV2(0x271682DEB8C4E0901D1a1550aD2e64D568E69909) { addTeam(_team, _split); preRevealURI = _preRevealURI; chainlinkKeyHash = _chainlinkKeyHash; COORDINATOR = VRFCoordinatorV2Interface(_vrfCoordinator); s_subscriptionId = _subscriptionId; } // ~~~~~~~~~~~~~~~~~~ Prevent Bots ~~~~~~~~~~~~~~~~~~ modifier callerIsUser() { require(tx.origin == msg.sender, "The caller is another contract"); _; } // ~~~~~~~~~~~~~~~~~~ Metadata Functions ~~~~~~~~~~~~~~~~~~ function setPreRevealURI(string memory _URI) external onlyOwner { preRevealURI = _URI; } function setPostRevealBaseURI(string memory _URI) external onlyOwner { postRevealBaseURI = _URI; } // ~~~~~~~~~~~~~~~~~~ Token URI ~~~~~~~~~~~~~~~~~~ // Before reveal, return same pre-reveal URI // After reveal, return post-reveal URI with random token offset from Chainlink function tokenURI(uint256 _tokenId) public view override returns (string memory) { require(_exists(_tokenId), "ERC721Metadata: URI query for nonexistent token"); if (!revealed) return preRevealURI; uint256 shiftedTokenId = (_tokenId + tokenOffset) % totalSupply(); return string(abi.encodePacked(postRevealBaseURI, shiftedTokenId.toString())); } // ~~~~~~~~~~~~~~~~~~ Sale State Function ~~~~~~~~~~~~~~~~~~ function setSaleStatus(SaleStatus _status) external onlyOwner { saleStatus = _status; } // ~~~~~~~~~~~~~~~~~~ Setting Merkle Root Function ~~~~~~~~~~~~~~~~~~ function setMerkleRoots(bytes32 _whitelistMerkleRoot) external onlyOwner { whitelistMerkleRoot = _whitelistMerkleRoot; } function processMint(uint256 _quantity) internal { require(msg.value == PRICE_KC * _quantity, "INCORRECT ETH SENT"); require(totalSupply() + _quantity <= MAX_KC, "MAX CAP OF KC EXCEEDED"); _mint(msg.sender, _quantity); } // ~~~~~~~~~~~~~~~~~~ Whitelist Sale Function ~~~~~~~~~~~~~~~~~~ function whitelistSale(uint256 _quantity, bytes32[] memory _proof) external payable callerIsUser { require(saleStatus == SaleStatus.WHITELIST, "WL SALE NOT ACTIVE"); require( MerkleProof.verify(_proof, whitelistMerkleRoot, keccak256(abi.encodePacked(msg.sender))), "MINTER IS NOT ON WHITELIST" ); require(_quantity <= wlPerWallet, "SURPASSED WL TX LIMIT"); require(_quantity + wlMintedAmt[msg.sender] <= wlPerWallet , "MAX WL MINTED"); wlMintedAmt[msg.sender]+= _quantity; processMint(_quantity); } // ~~~~~~~~~~~~~~~~~~ Public Sale Function ~~~~~~~~~~~~~~~~~~ function publicSale( uint256 _quantity ) external payable callerIsUser { require(saleStatus == SaleStatus.PUBLIC, "PUBLIC SALE NOT LIVE"); require(_quantity <= publicPerWallet, "QUANTITY SURPASSES PER-TXN LIMIT"); require(_quantity + publicMintAmt[msg.sender] <= publicPerWallet, "MAX PUBLIC MINTED"); publicMintAmt[msg.sender]+= _quantity; processMint(_quantity); } // ~~~~~~~~~~~~~~~~~~ Airdrop Function ~~~~~~~~~~~~~~~~~~ function airDrop( uint256 _quantity, address _receiver ) public onlyOwner { _mint(_receiver, _quantity); } // ~~~~~~~~~~~~~~~~~~ Edit Chainlink Configuration ~~~~~~~~~~~~~~~~~~ function setChainlinkConfig(bytes32 _keyhash) external onlyOwner { chainlinkKeyHash = _keyhash; } // ~~~~~~~~~~~~~~~~~~ Request Token Offset ~~~~~~~~~~~~~~~~~~ // NOTE: contract must be approved for and own LINK before calling this function function startReveal(string memory _newURI) external onlyOwner returns (uint256 requestId) { require(!revealed, "ALREADY REVEALED"); postRevealBaseURI = _newURI; requestId = COORDINATOR.requestRandomWords( chainlinkKeyHash, s_subscriptionId, requestConfirmations, callbackGasLimit, numWords ); return requestId; } // ~~~~~~~~~~~~~~~~~~ CHAINLINK CALLBACK FOR TOKEN OFFSET ~~~~~~~~~~~~~~~~~~ function fulfillRandomWords(uint256 _requestId, uint256[] memory randomWords) internal override { require(!revealed, "ALREADY REVEALED"); revealed = true; } // ~~~~~~~~~~~~~~~~~~ onlyOwner Functions ~~~~~~~~~~~~~~~~~~ function changePrice(uint256 _price) public onlyOwner { PRICE_KC = _price; } function changePublicAmt(uint256 _amt) public onlyOwner { publicPerWallet = _amt; } function changeWhitelistAmt(uint256 _wlAmt) public onlyOwner { wlPerWallet = _wlAmt; } function numberMinted(address _owner) public view returns (uint256) { return _numberMinted(_owner); } function getOwnershipData(uint256 _tokenId) external view returns (TokenOwnership memory) { return _ownershipOf(_tokenId); } function addTeam(address[] memory _team, uint[] memory _split) public onlyOwner { require(_team.length == _split.length, "Address and Shares must equal"); teamAddress = _team; teamSplit = _split; } //~~~~~~~~~~~~~~~~~~ Withdraw Functions ~~~~~~~~~~~~~~~~~~ function internalWithdrawal(uint _amount) internal onlyOwner { for(uint i = 0; i < teamAddress.length; i++) { uint split = teamSplit[i]; (bool os, ) = payable(teamAddress[i]).call{value: _amount * split / 100}(''); require(os); } } function teamWithdraw() public onlyOwner { internalWithdrawal(address(this).balance); } function emergencyWithdraw() public onlyOwner { (bool os, ) = payable(owner()).call{value: address(this).balance}(''); require(os); } }
部署脚本代码
async function main() { const [deployer] = await ethers.getSigners(); vrfCoordinator = 0x271682deb8c4e0901d1a1550ad2e64d568e69909; // SET FOR MAINNET, WILL NEED TO CHANGE TO TESTNET IF WANTED TO TEST FOR ALL 3. chainlinkKeyHash = 0x8af398995b04c28e9951adb9721ef74c74f93e6a478f39e7e0777be13527e7ef; subID = 0x67388e9228301fce52e39196537fa6a6ee0a1f91; console.log("Deploying contract with the account: ", deployer.address); console.log("Account Balance: ", (await deployer.getBalance()).toString()); const Token = await ethers.getContractFactory("KartoCarsNFT"); console.log("Deploying Contract...Stand by...."); const token = await Token.deploy([0x8faA2802aACa47AEe59f02A96E29225436eEF9A8,0x012D8E2cE2716B260718abE39062A76be15570B3],[98,2],"test",vrfCoordinator, chainlinkKeyHash, subID); console.log("Contract deployed to: ", token.address); } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); });
排查要点
- 订阅ID类型不匹配:合约中
s_subscriptionId是uint64类型,但部署脚本中subID使用的十六进制字符串0x67388e9228301fce52e39196537fa6a6ee0a1f91超出了uint64的取值范围(最大值为0xffffffffffffffff)。需将订阅ID转换为对应的uint64数值传入,而非长十六进制字符串。 - VRF Coordinator地址重复初始化:合约构造函数同时在
VRFConsumerBaseV2继承初始化时传入固定地址,又通过参数_vrfCoordinator初始化COORDINATOR变量,若两者地址不一致会导致冲突。建议统一使用参数传入的地址初始化VRFConsumerBaseV2。 - 地址格式校验:检查部署脚本中所有地址是否为标准20字节十六进制格式,无大小写错误或字符缺失/冗余。
- Chainlink网络配置匹配:确认
vrfCoordinator地址、chainlinkKeyHash和订阅ID属于同一网络(主网/测试网),若部署到测试网需替换为对应测试网的Chainlink VRF参数。
内容的提问来源于stack exchange,提问作者carl.
相关产品推荐
相关产品推荐

