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

ERC721合约无法设置授权及转移所有权问题求助

ERC721合约授权问题排查与修复

问题场景与报错

  • 需求:
    1. NFT铸造完成后所有权归属市场合约
    2. 用户购买时将NFT所有权转移给购买者
  • 触发的报错:
    • ERC721: approve caller is not token owner nor approved for all
    • ERC721: approve to caller

核心问题分析

1. 铸造流程中的冗余授权

原createToken函数先将NFT铸造给用户,再通过_create转移到合约,之后又调用setApprovalForAll(address(this), true)——此时用户已不再是NFT所有者,该授权操作完全无效且多余。

2. 购买函数中的授权逻辑错误

buyNFT里的approve(msg.sender, tokenId)和setApprovalForAll(msg.sender, true)均由购买者(msg.sender)发起,但此时NFT所有者是合约本身,非所有者无权执行授权操作,直接触发ERC721规则报错。此外还存在重复转账的逻辑错误,会导致ETH余额不足。

3. Token ID返回错误

原createToken返回的是自增后的ID,而非实际铸造的currentTokenId,逻辑不符预期。

修复后的合约代码

修正铸造流程(createToken与_create)

// 首次创建并上架NFT
function createToken(string memory tokenURI, string memory name, uint256 price) public payable returns (uint) {
    uint256 currentTokenId = _tokenIds.current();

    // 直接铸造到市场合约地址,省去中转步骤
    _safeMint(address(this), currentTokenId);

    // 绑定Token URI
    _setTokenURI(currentTokenId, tokenURI);

    // 更新数据并触发事件
    _create(currentTokenId, tokenURI, name, price);

    // 自增Token ID计数器
    _tokenIds.increment();
    return currentTokenId; // 返回实际铸造的Token ID
}

function _create(uint256 tokenId, string memory tokenURI, string memory name, uint256 price) private {
    require(msg.value == listPrice, "Listing fee mismatch");
    require(price > 0, "Price must be positive");

    // 更新Token映射,新增creator字段记录NFT创建者(用于后续转账)
    idToToken[tokenId] = Token(
        tokenId,
        tokenURI,
        name,
        payable(address(this)),
        payable(msg.sender), // 新增:记录创建者地址
        price,
        true
    );

    emit TokenListedSuccess(
        tokenId,
        address(this),
        price,
        true
    );
}

修正购买流程(buyNFT)

function buyNFT(uint256 tokenId) public payable {
    require(msg.value > 0, "ETH amount cannot be zero");
    Token storage token = idToToken[tokenId];
    
    require(msg.value == token.price, "Incorrect ETH amount");
    require(token.isForSale, "Token is not for sale");
    require(token.owner == address(this), "Token not owned by market");

    // 合约作为所有者,直接安全转移NFT给购买者
    _safeTransfer(address(this), msg.sender, tokenId, "");

    // 将ETH转账给NFT创建者
    payable(token.creator).transfer(msg.value);

    // 更新Token状态
    token.owner = payable(msg.sender);
    token.isForSale = false;
    _itemsSold.increment();

    emit TokenPurchasedSuccess(tokenId, msg.sender, token.price);
}

注意:需同步更新Token结构体,新增creator字段:

struct Token {
    uint256 tokenId;
    string tokenURI;
    string name;
    address payable owner;
    address payable creator; // 新增字段
    uint256 price;
    bool isForSale;
}

客户端代码优化

const buyToken = (...args) => {
    const [tokenId] = args
    return new Promise(async (resolve, reject) => {
        try {
            // 先从合约获取当前NFT的售价,避免前端价格偏差
            const token = await contract.idToToken(tokenId);
            const price = token.price.toString();
            
            let transaction = await contract.buyNFT(tokenId, { 
                gasLimit: 5500000, 
                value: price
            });
            await transaction.wait();
            
            // 监听购买成功事件,同步前端状态
            contract.on("TokenPurchasedSuccess", (id, buyer, price) => {
                if (id.toString() === tokenId.toString()) {
                    console.log(`NFT ${id} 已成功出售给 ${buyer}`);
                }
            });
            
            resolve();
        } catch (e) {
            console.error(e);
            reject(e);
        }
    })
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 15:20:43