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

Chainlink VRF部署报错:无效地址或ENS名称问题求助

我们在部署集成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.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 20:40:34