使用web3.js实现ERC20Permit签名授权:签名有效但allowance仍为0
ERC20Permit签名授权后allowance为0、nonce未更新的问题修复
核心问题:误用.call()调用状态变更函数
permit是修改合约状态的函数(会更新allowance和nonce),你当前用.call()仅做本地模拟调用,不会向链上发送交易,所以合约状态完全没有变更。必须改用.send()发送交易触发链上状态修改:
// 错误写法:仅模拟调用,不修改链上状态 // await contract.methods.permit(account.address, contractAddress, amount, deadline, v, r, s).call(); // 正确写法:发送交易,需用户钱包签名确认 await contract.methods.permit(account.address, contractAddress, amount, deadline, v, r, s) .send({ from: account.address });
签名构建中的关键错误
1. nonce格式错误
你将合约返回的nonce转成了十六进制字符串nonce.toString(16),但EIP712签名要求uint256类型使用十进制数字,格式不匹配会导致签名验证失败,进而permit无法生效。直接使用原始数字即可:
// 错误写法 // nonce: nonce.toString(16), // 正确写法 nonce: nonce,
2. deadline参数不一致且单位错误
- 签名构建时的
deadline计算为Date.now() + 20 * 60(仅加1200毫秒,约2分钟),而调用permit时用的是Date.now() + 20 * 60 * 1000(20分钟),两者必须完全一致; - OpenZeppelin的ERC20Permit合约中,
deadline参数是秒级时间戳,而非毫秒,你之前的毫秒时间戳会导致有效期计算错误。
统一修正:
// 计算20分钟有效期的秒级时间戳 const deadline = Math.floor(Date.now() / 1000) + 20 * 60;
3. 动态获取链ID避免硬编码
硬编码chainId: 5可能导致跨链或链ID变更时签名失效,建议动态获取链ID:
const chainId = await web3.eth.getChainId(); const domain = { name: contractName, version: "1", chainId: chainId, verifyingContract: contractAddress };
完整修正后的代码片段
调用permit部分
contract = // 你的合约实例 contractAddress = "0x..."; // 合约地址 amount = BigNumber(amount); // 已处理小数位的金额 // 统一计算秒级时间戳的20分钟有效期 const deadline = Math.floor(Date.now() / 1000) + 20 * 60; // 发送交易而非模拟调用 await contract.methods.permit(account.address, contractAddress, amount, deadline, v, r, s) .send({ from: account.address }); // 等待交易上链后查询状态 console.log(await contract.methods.allowance(account.address, contractAddress).call()); console.log(await contract.methods.nonces(account.address).call());
签名构建部分
const EIP712Domain = [ { name: "name", type: "string" }, { name: "version", type: "string" }, { name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" }, ]; const contractName = await contract.methods.name().call(); const chainId = await web3.eth.getChainId(); const domain = { name: contractName, version: "1", chainId: chainId, verifyingContract: contractAddress }; const Permit = [ { name: "owner", type: "address" }, { name: "spender", type: "address" }, { name: "value", type: "uint256" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" }, ]; const nonce = await contract.methods.nonces(account.address).call(); const deadline = Math.floor(Date.now() / 1000) + 20 * 60; const message = { owner: account.address, spender: contractAddress, value: amount.toString(), // BigNumber转字符串确保类型匹配 nonce: nonce, deadline: deadline, }; const params = JSON.stringify({ types: { EIP712Domain, Permit, }, domain, primaryType: "Permit", message, }); const _signMessage = async (userAddress, web3, dataToSign, signatureMethod = 'eth_signTypedData_v4') => { const signature = await web3.currentProvider.send(signatureMethod, [userAddress, dataToSign]); return signature; }; const signature = await _signMessage(account.address, web3, params); const getSignatureParameters = signature => { if (!web3.utils.isHexStrict(signature.result)) { throw new Error(`Given value "${signature.result}" is not a valid hex string.`); } const r = signature.result.slice(0, 66); const s = "0x" + signature.result.slice(66, 130); let v = parseInt(signature.result.slice(130, 132), 16); if (![27, 28].includes(v)) v += 27; return { r, s, v }; } const { r, s, v } = getSignatureParameters(signature);
内容的提问来源于stack exchange,提问作者Pm Rivière
相关产品推荐
相关产品推荐

