实现ERC721代币购买功能遇报错:caller is not token owner or approved
问题解决:ERC721转移权限错误
错误原因
你遇到的ERC721: caller is not token owner or approved错误,是因为合约调用transferFrom(ownerAddress, msg.sender, _tokenId)时,合约本身并没有获得该NFT的转移授权,也不是代币所有者,触发了ERC721标准的权限检查逻辑。
解决办法
核心思路是让合约获得对应NFT的转移权限,下面提供两种落地方案:
方案1:要求卖家预先授权合约
让NFT所有者手动调用ERC721标准接口的approve(你的合约地址, _tokenId),或者setApprovalForAll(你的合约地址, true),将目标代币的转移权限授权给你的合约。完成授权后,合约就能正常调用transferFrom转移代币。
这种方案的缺点是需要卖家额外操作,用户体验较差,更推荐下面的集成挂售流程方案。
方案2:集成挂售流程,卖家主动发起授权
新增一个挂售函数,让卖家主动调用该函数设置NFT售价并完成合约授权,后续购买流程即可自动完成代币转移:
新增挂售函数
function listShirtNFT(uint256 _tokenId, uint256 price) public { // 验证调用者是NFT所有者 require(ownerOf(_tokenId) == msg.sender, "仅所有者可挂售"); require(price > 0, "售价必须大于0"); // 将NFT转移权限授权给合约 approve(address(this), _tokenId); // 保存售价信息 tokenIdToShirtInfo[_tokenId].price = price; }
修改购买函数
修正支付检查逻辑(允许刚好等于售价的支付),并在购买完成后清空售价避免重复交易:
function buyShirtNFT(uint256 _tokenId) public payable { require(tokenIdToShirtInfo[_tokenId].price > 0, "该NFT需处于待售状态"); uint256 cost = tokenIdToShirtInfo[_tokenId].price; address ownerAddress = ownerOf(_tokenId); // 允许支付金额等于或大于售价 require(msg.value >= cost, "你的以太币余额不足"); // 合约已获得授权,可正常转移NFT transferFrom(ownerAddress, msg.sender, _tokenId); // 清空售价,标记为已售出 tokenIdToShirtInfo[_tokenId].price = 0; address payable ownerAddressPayable = payable(ownerAddress); ownerAddressPayable.transfer(cost); // 退还超额支付的以太币 if(msg.value > cost) { payable(msg.sender).transfer(msg.value - cost); } }
额外注意事项
- 重入风险:虽然
transfer函数会限制调用gas,降低重入风险,但如果合约涉及更复杂的逻辑,建议使用OpenZeppelin的ReentrancyGuard修饰器增强安全性。 - 权限校验:挂售函数必须严格验证调用者是NFT所有者,防止恶意用户挂售不属于自己的资产。
内容的提问来源于stack exchange,提问作者nacho
相关产品推荐
相关产品推荐

