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

实现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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 14:33:25