Documentation Index
Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
Permit
EIP-2612 Permit signatures enable gasless token approvals through meta-transactions. Users sign an off-chain message granting approval, which can be submitted by a relayer paying the gas.
Overview
EIP-2612 extends ERC-20 tokens with a permit function that accepts EIP-712 signed messages. This enables:
- Gasless approvals: Users don’t need ETH to approve tokens
- Single-transaction flows: Approve + transfer in one transaction
- Meta-transactions: Relayers can pay gas on behalf of users
- Better UX: No separate approval transaction needed
Types
PermitType
interface PermitType {
readonly owner: AddressType;
readonly spender: AddressType;
readonly value: Uint256Type;
readonly nonce: Uint256Type;
readonly deadline: Uint256Type;
}
PermitDomainType
interface PermitDomainType {
readonly name: string;
readonly version: string;
readonly chainId: ChainIdType;
readonly verifyingContract: AddressType;
}
Methods
createPermitSignature
Creates an EIP-712 signature for a permit message.
import * as Permit from '@tevm/primitives/Permit';
import * as Address from '@tevm/primitives/Address';
import * as Uint256 from '@tevm/primitives/Uint256';
const permit: Permit.PermitType = {
owner: Address.fromHex('0x...'),
spender: Address.fromHex('0x...'),
value: Uint256.fromBigInt(1000000n),
nonce: Uint256.fromBigInt(0n),
deadline: Uint256.fromBigInt(BigInt(Math.floor(Date.now() / 1000) + 3600)),
};
const domain: Permit.PermitDomainType = {
name: 'USD Coin',
version: '2',
chainId: 1,
verifyingContract: Address.fromHex('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'),
};
const signature = Permit.createPermitSignature(permit, domain, privateKey);
verifyPermit
Verifies a permit signature matches the permit data and domain.
const isValid = Permit.verifyPermit(permit, signature, domain);
Known Tokens
Pre-configured domain data for popular ERC-2612 tokens:
import * as Permit from '@tevm/primitives/Permit';
// USDC Mainnet
const domain = Permit.KnownTokens.USDC_MAINNET;
// DAI Mainnet
const domain = Permit.KnownTokens.DAI_MAINNET;
// Other tokens
Permit.KnownTokens.USDC_POLYGON
Permit.KnownTokens.USDC_ARBITRUM
Permit.KnownTokens.USDT_MAINNET
Permit.KnownTokens.UNI_MAINNET
Permit.KnownTokens.WETH_MAINNET
Complete Example
import * as Permit from '@tevm/primitives/Permit';
import * as Address from '@tevm/primitives/Address';
import * as Uint256 from '@tevm/primitives/Uint256';
// Setup
const owner = Address.fromHex('0x...');
const spender = Address.fromHex('0x...');
const amount = Uint256.fromBigInt(1000000n); // 1 USDC
// Create permit
const permit: Permit.PermitType = {
owner,
spender,
value: amount,
nonce: Uint256.fromBigInt(0n),
deadline: Uint256.fromBigInt(BigInt(Math.floor(Date.now() / 1000) + 3600)),
};
// Sign with known token domain
const domain = Permit.KnownTokens.USDC_MAINNET;
const signature = Permit.createPermitSignature(permit, domain, privateKey);
// Verify
const isValid = Permit.verifyPermit(permit, signature, domain);
// Submit to contract
const { r, s, v } = parseSignature(signature);
await contract.permit(
permit.owner,
permit.spender,
permit.value,
permit.deadline,
v,
r,
s
);
Security Considerations
Deadline
Always set a reasonable deadline. Expired permits can’t be used:
// 1 hour from now
const deadline = Uint256.fromBigInt(BigInt(Math.floor(Date.now() / 1000) + 3600));
Nonce Management
Each permit must use the owner’s current nonce. Replay protection:
// Query on-chain nonce
const nonce = await contract.nonces(owner);
Domain Verification
Ensure the domain matches the token contract:
// Verify domain separator
const onChainSeparator = await contract.DOMAIN_SEPARATOR();
Amount Limits
Be careful with unlimited approvals:
// Limited approval
const amount = Uint256.fromBigInt(1000000n);
// Unlimited (use with caution)
const unlimited = Uint256.MAX;
See Also
References