ERC721合约无法设置授权及转移所有权问题求助
ERC721合约授权问题排查与修复
问题场景与报错
- 需求:
- NFT铸造完成后所有权归属市场合约
- 用户购买时将NFT所有权转移给购买者
- 触发的报错:
ERC721: approve caller is not token owner nor approved for allERC721: 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
相关产品推荐
相关产品推荐

