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

ERC721无效Token ID及NFT铸造后Owner异常问题求助

NFT Marketplace合约异常排查与修复

问题描述

开发NFT Marketplace时,采用两个合约实现功能:调用NFT合约铸造Token后,调用setApprovalForAll()授权市场合约转移该Token。但出现异常:新铸造的Token归属显示为address(0),交易回滚,合约返回错误ERC721: invalid token ID。

核心问题分析

1. NFTMarketplace错误继承ERC721合约

NFTMarketplace合约无需自身作为ERC721代币发行方,继承ERC721会导致关键逻辑混淆:

  • buyNFT函数中调用ERC721.ownerOf(tokenId)时,实际查询的是Marketplace合约自身的代币所有者,而非用户铸造NFT的合约。由于Marketplace从未铸造过该Token ID,返回address(0),触发invalid token ID错误。

2. Marketplace合约owner变量未初始化

updateListPrice函数中使用owner变量校验权限,但合约构造函数未对owner赋值,导致该函数永远无法正常调用。

3. listToken函数逻辑冗余

listToken中传入的price参数未被使用,直接硬编码为listPrice,与函数参数设计矛盾。

修复方案

1. 移除NFTMarketplace的ERC721继承

Marketplace仅需作为交易平台与NFT合约交互,无需继承ERC721,修改合约定义:

contract NFTMarketplace is ReentrancyGuard {
    // ... 原有代码
}

2. 修正buyNFT中的ownerOf调用

从目标NFT合约而非Marketplace自身查询Token所有者:

function buyNFT(address nftContract, uint256 itemId) public payable nonReentrant {
    uint price = idToToken[itemId].price;
    uint tokenId = idToToken[itemId].tokenId;
    // 修正:从对应NFT合约获取所有者
    address seller = IERC721(nftContract).ownerOf(tokenId);
    address buyer = msg.sender;
    require(msg.value > 0, "You need to send some ether");
    require(buyer != seller,"You already own this nft");
    require(msg.value == price, "Please submit the asking price in order to complete the purchase");

    idToToken[itemId].isListed = false;
    idToToken[itemId].owner = payable(buyer);
    payable(seller).transfer(price);

    _itemsSold.increment();

    IERC721(nftContract).transferFrom(seller, buyer, tokenId);
}

3. 初始化Marketplace的owner变量

在构造函数中设置合约部署者为owner:

constructor() {
    owner = payable(msg.sender);
}

4. 修正listToken的price参数逻辑

若坚持固定listing fee,移除冗余的price参数;若需自定义定价,替换硬编码的listPrice为传入参数。示例(固定fee版本):

function listToken(address nftContract, uint256 currentTokenId, string memory tokenURI, string memory name) public payable {
    require(msg.value > 0, "Price must be at least 1 wei");
    require(msg.value == listPrice, "Price must be equal to listing price");

    idToToken[currentTokenId] = Token(
        currentTokenId,
        tokenURI,
        nftContract,
        name,
        payable(address(this)),
        listPrice, 
        true
    );
  
    emit TokenListedSuccess(
        currentTokenId,
        nftContract,
        msg.sender,
        listPrice, 
        true
    );
}

修改后的完整合约代码

NFT合约(无改动)

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "./@openzeppelin/contracts/utils/Counters.sol";
import "./@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "./@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "./@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract NFT is ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;
    address contractAddress;

    constructor(address marketplaceAddress) ERC721("NFTMarketplace", "NFTM") {
        contractAddress = marketplaceAddress;
    }

    event TokenMinted (
        uint256 indexed tokenId,
        string tokenURI
    );

    function createToken(string memory tokenURI) public returns (uint) {
        uint256 currentTokenId = _tokenIds.current();
        _safeMint(msg.sender, currentTokenId);
        _setTokenURI(currentTokenId, tokenURI);
        setApprovalForAll(contractAddress, true);
        _tokenIds.increment(); 

        emit TokenMinted(currentTokenId, tokenURI);
        return currentTokenId;
    }

    function getCurrentToken() public view returns (uint256) {
        return _tokenIds.current();
    }
}

NFTMarketplace合约(修复后)

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "./@openzeppelin/contracts/utils/Counters.sol";
import "./@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "./@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract NFTMarketplace is ReentrancyGuard {

    using Counters for Counters.Counter;
    Counters.Counter private _itemsSold;
    address payable owner;
    uint256 listPrice = 0.01 ether;

    constructor() {
        owner = payable(msg.sender);
    }

    struct Token {
        uint256 tokenId;
        string tokenURI;
        address nftContract;
        string name;
        address payable owner;
        uint256 price;
        bool isListed;
    }

    event TokenListedSuccess (
        uint256 indexed tokenId,
        address nftContract,
        address owner,
        uint256 price,
        bool isListed
    );

    mapping(uint256 => Token) private idToToken;

    function updateListPrice(uint256 _listPrice) public payable {
        require(owner == msg.sender, "Only owner can update listing price");
        listPrice = _listPrice;
    }

    function listToken(address nftContract, uint256 currentTokenId, string memory tokenURI, string memory name) public payable {
        require(msg.value > 0, "Price must be at least 1 wei");
        require(msg.value == listPrice, "Price must be equal to listing price");

        idToToken[currentTokenId] = Token(
            currentTokenId,
            tokenURI,
            nftContract,
            name,
            payable(address(this)),
            listPrice, 
            true
        );
  
        emit TokenListedSuccess(
            currentTokenId,
            nftContract,
            msg.sender,
            listPrice, 
            true
        );
    }

    function buyNFT(address nftContract, uint256 itemId) public payable nonReentrant {
        uint price = idToToken[itemId].price;
        uint tokenId = idToToken[itemId].tokenId;
        address seller = IERC721(nftContract).ownerOf(tokenId);
        address buyer = msg.sender;
        
        require(msg.value > 0, "You need to send some ether");
        require(buyer != seller,"You already own this nft");
        require(msg.value == price, "Please submit the asking price in order to complete the purchase");

        idToToken[itemId].isListed = false;
        idToToken[itemId].owner = payable(buyer);
        payable(seller).transfer(price);

        _itemsSold.increment();

        IERC721(nftContract).transferFrom(seller, buyer, tokenId);
    }

    /* ...rest of smart contract */
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 11:20:37