如何使用Web3.js转移ERC1155代币?调用Monkeys合约getMyToken函数触发“ERC1155: caller is not owner nor approved”错误的问题排查
咱们一步一步拆解你遇到的问题,先搞清楚错误根源,再给你具体的修复步骤:
核心错误原因分析
你遇到的ERC1155: caller is not owner nor approved错误,主要来自三个关键点:
1. 前端调用方式错误:用了只读的.call()而非交易.send()
你的前端代码里用.call()调用getMyToken(注意你代码里写的mintMyMonkey是笔误,合约里实际方法名是getMyToken),但这个函数是修改链上状态的操作(转移代币)。.call()只能用于只读视图函数,不会发起真实交易,不仅无法完成代币转移,还会因为执行上下文错误触发权限校验失败。
2. 授权对象搞反了
你给接收账户做了setApprovalForAll授权,但这个授权是让接收账户能操作owner的代币吗?不对——你的isApproved函数检查的是owner()是否授权了接收账户,但调用getMyToken的是前端连接的用户账户,这个账户并没有被owner授权可以转移owner的代币。
ERC1155的safeTransferFrom规则是:调用者必须是from(这里是owner)本人,或者是被from授权的operator(通过setApprovalForAll(owner, 调用者账户))。你现在授权的是接收账户,而非执行转移操作的调用者,自然会触发权限错误。
3. 合约方法的权限逻辑缺失
你的getMyToken函数没有任何权限修饰,任何人都能调用,但如果调用者不是owner也没被授权,就会触发ERC1155内置的权限校验。
具体修复方案
方案一:修正前端调用逻辑
先把前端代码的两个错误改掉:
const selectedAccountIsApproved = await monkeysContract.methods.isApproved(selectedAccount).call(); console.log('IS APPROVED RESULT: ', selectedAccountIsApproved); // 注意:这里的isApproved检查的是接收账户是否被owner授权,实际我们需要的是调用者是否被owner授权 // 如果你想让当前连接账户能调用getMyToken,应该检查isApprovedForAll(owner, 当前账户) const callerIsApproved = await monkeysContract.methods.isApprovedForAll(ownerAddress, currentConnectedAccount).call(); if(callerIsApproved) { // 1. 修正方法名:mintMyMonkey → getMyToken // 2. 用send()代替call(),发起真实交易 const result = await monkeysContract.methods.getMyToken(selectedAccount, 0).send({ from: currentConnectedAccount }); console.log('Transfer Result: ', result); } else { console.log('Caller account not approved by owner for transfers'); }
方案二:调整合约权限逻辑(更安全)
如果你希望getMyToken只能由owner调用,或者跳过调用者权限校验,可以修改合约:
选项A:给getMyToken添加onlyOwner修饰符
这样只有合约部署者能调用这个函数转移代币:
function getMyToken(address to, uint256 id) public onlyOwner { transfer(owner(), to, id, 1, 'test'); }
选项B:改用内部函数_safeTransferFrom跳过调用者权限检查
内部函数_safeTransferFrom不会校验调用者的权限,只检查from的代币余额,适合合约内部发起的转移:
function getMyToken(address to, uint256 id) public { _safeTransferFrom(owner(), to, id, 1, 'test'); }
方案三:正确授权调用者账户
由合约owner执行以下代码,给前端连接的用户账户授权:
// 用owner账户发起这个交易 await monkeysContract.methods.setApprovalForAll(currentConnectedAccount, true).send({ from: ownerAddress });
授权完成后,调用者账户就有权限执行getMyToken函数了。
额外提醒
- 你在Etherscan上能成功转移,是因为你用owner账户直接操作的,满足
safeTransferFrom的调用者是owner的条件; - 后续测试时,一定要区分调用者账户和接收账户的权限差异,不要搞混授权对象。
内容的提问来源于stack exchange,提问作者XCEPTION

