A modern implementation of ERC-20 token with EIP-3009 (Transfer with Authorization) and EIP-2612 (Permit) support, built with Foundry.
- ERC-20: Standard token functionality (transfer, approve, transferFrom)
- EIP-2612: Gasless approvals using permit signatures
- EIP-3009: Gasless transfers using signed authorizations
transferWithAuthorization: Execute transfers with signed authorizationreceiveWithAuthorization: Receive transfers (payee must be caller)cancelAuthorization: Cancel unused authorizations
- Solidity 0.8.x: Modern Solidity with built-in overflow protection
- Comprehensive Tests: Full test coverage for all functionality
EIP-3009 enables gasless token transfers by allowing users to authorize transfers via signatures. Instead of calling transfer() directly and paying gas, users can sign a transfer authorization off-chain, and a relayer can submit it on-chain.
- Gasless Transfers: Users don't need ETH to transfer tokens
- Flexible Timing: Authorizations can have validity windows
- Replay Protection: Uses random nonces to prevent replay attacks
- Cancellation: Users can cancel authorizations before they're used
├── src/
│ ├── EIP3009Token.sol # Main token contract
│ └── lib/
│ ├── EIP3009.sol # EIP-3009 implementation
│ ├── EIP2612.sol # EIP-2612 permit implementation
│ ├── EIP712.sol # EIP-712 typed data signing
│ ├── EIP712Domain.sol # Domain separator
│ ├── ECRecover.sol # Safe signature recovery
│ └── IERC20Internal.sol # Internal interface
├── test/
│ └── EIP3009Token.t.sol # Comprehensive test suite
└── script/
└── DeployEIP3009Token.s.sol # Deployment script
This project uses Foundry. If you don't have it installed:
curl -L https://foundry.paradigm.xyz | bash
foundryupInstall dependencies:
forge installforge buildRun all tests:
forge testRun tests with verbosity:
forge test -vvRun specific test:
forge test --match-test test_TransferWithAuthorization -vvvvGas report:
forge test --gas-reportforge script script/DeployEIP3009Token.s.sol:DeployEIP3009Token --fork-url http://localhost:8545 --broadcastforge script script/DeployEIP3009Token.s.sol:DeployEIP3009Token \
--rpc-url <your_rpc_url> \
--private-key <your_private_key> \
--broadcast \
--verifySet environment variables to customize the token:
export TOKEN_NAME="My Token"
export TOKEN_VERSION="1"
export TOKEN_SYMBOL="MTK"
export TOKEN_DECIMALS=18
export TOKEN_INITIAL_SUPPLY=1000000000000000000000000
forge script script/DeployEIP3009Token.s.sol:DeployEIP3009Token --broadcasttoken.transfer(recipient, amount);// Off-chain: User signs authorization
const authorization = {
from: userAddress,
to: recipientAddress,
value: amount,
validAfter: Math.floor(Date.now() / 1000) - 100,
validBefore: Math.floor(Date.now() / 1000) + 3600,
nonce: ethers.utils.randomBytes(32)
};
const signature = await signer._signTypedData(domain, types, authorization);
const { v, r, s } = ethers.utils.splitSignature(signature);
// On-chain: Relayer submits transaction
await token.transferWithAuthorization(
authorization.from,
authorization.to,
authorization.value,
authorization.validAfter,
authorization.validBefore,
authorization.nonce,
v, r, s
);// Off-chain: User signs permit
const permit = {
owner: userAddress,
spender: spenderAddress,
value: amount,
nonce: await token.nonces(userAddress),
deadline: Math.floor(Date.now() / 1000) + 3600
};
const signature = await signer._signTypedData(domain, types, permit);
const { v, r, s } = ethers.utils.splitSignature(signature);
// On-chain: Anyone can submit permit
await token.permit(
permit.owner,
permit.spender,
permit.value,
permit.deadline,
v, r, s
);The test suite covers:
- ✅ Basic ERC-20 functionality
- ✅ EIP-2612 permit functionality
- ✅ EIP-3009 transfer with authorization
- ✅ EIP-3009 receive with authorization
- ✅ Authorization cancellation
- ✅ Signature validation
- ✅ Replay protection
- ✅ Time-based validation
- ✅ Edge cases and security scenarios
- ✅ Fuzz testing
- Nonce Management: EIP-3009 uses random nonces (bytes32) for better UX than sequential nonces
- Signature Validation: All signatures are validated using EIP-712 typed data
- Replay Protection: Used authorizations are tracked to prevent replay attacks
- Time Windows: Authorizations can specify validity periods
- Malleability Protection: ECRecover library prevents signature malleability
This implementation modernizes the old Solidity 0.6.12 contract:
- ✅ Updated to Solidity ^0.8.20 (built-in overflow protection)
- ✅ Removed SafeMath dependency (no longer needed)
- ✅ Updated to OpenZeppelin v5.x conventions
- ✅ Replaced
nowwithblock.timestamp - ✅ Removed
publicfrom constructor - ✅ Added comprehensive Foundry tests
- ✅ Improved code organization and documentation
MIT