关键词:ERC-2612、Permit、Meta-Transaction、EIP-712、nonces、gasless approval、安全授权
📚 作者:Henry 🧱 系列:《ERC 系列标准全景图解》 · 第 4 篇 👨💻 受众:Web3 前端工程师 / 区块链开发者 / Web3入门者 👉 系列持续更新中,建议收藏专栏或关注作者
permit()
?在传统的 ERC-20 授权流程中,用户必须先调用 approve(...)
才能授权合约使用自己的 token。这个过程:
approve
必须由 msg.sender
发起)为此,ERC-2612 引入了 permit(...)
,一种离线签名 + 合约校验的授权方式,让用户无需上链即可完成授权。
适用于:
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
📌 特点说明:
v, r, s
:为用户离线签名所得参数deadline
:授权有效期,避免 replay attack┌────────────┐ off-chain signature ┌──────────────┐
│ User │ ─────────────────────────▶ │ Frontend UI │
└────────────┘ └──────────────┘
│ │
▼ ▼
交易构造 提交签名+参数
│ │
▼ ▼
┌─────────────────────────────────────────────────┐
│ Token Contract (ERC-2612) │
│ 1. 校验 owner 对消息体签名正确 │
│ 2. 校验 nonce 未被使用 │
│ 3. 校验 deadline 未过期 │
│ 4. 设置 allowance[owner][spender] = value │
└─────────────────────────────────────────────────┘
Permit(
address owner,
address spender,
uint256 value,
uint256 nonce,
uint256 deadline
)
配合 EIP-712 domain separator:
keccak256(abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(encode(Permit))
))
ERC-2612 中每个签名必须唯一,使用 nonces[owner]
控制:
mapping(address => uint256) public nonces;
每次调用 permit()
后:
nonces[owner]++;
✅ 能有效防止签名被重放
校验字段 | 意义 |
---|---|
owner != 0 |
禁止空地址签名 |
spender != 0 |
确保授权对象有效 |
block.timestamp <= deadline |
签名未过期 |
recover(hash, sig) == owner |
确认为本人签名 |
nonces |
保证签名唯一性 |
OpenZeppelin 的实现位于 ERC20Permit.sol
:
function permit(...) public override {
require(block.timestamp <= deadline, "expired");
bytes32 structHash = keccak256(abi.encode(
PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline
));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, v, r, s);
require(signer == owner, "invalid signature");
_approve(owner, spender, value);
}
模块继承关系:
ERC20 → ERC20Permit → EIP712 → Nonces
import { signTypedData } from 'viem/account'
const signature = await signTypedData({
domain: {
name: 'DAI Stablecoin',
version: '1',
chainId: 1,
verifyingContract: tokenAddress,
},
types: {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
},
message: {
owner: user,
spender,
value: amount,
nonce,
deadline,
},
})
场景 | 推荐方式 |
---|---|
创建支持 permit 的代币 | 继承 ERC20Permit , 并设置 EIP-712 域名 |
对接 DEX | 使用 permit + swap 组合交易减少 approve 步骤 |
支持 Gasless | 搭配 relayer + permit,实现无 gas 授权 |
检查 token 是否支持 | 读取合约 ABI 是否存在 permit(...) 方法 |
permit()
实现了基于签名的离线授权,不依赖 msg.sender
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!