Introduction to CREATE2
The CREATE2 opcode was introduced in Ethereum's Constantinople hard fork (February 28, 2019) as part of EIP-1014. Originally designed for state channels, it has broader applications—including solving exchange deposit account challenges.
Why CREATE2 Matters for Exchanges
Exchanges need unique Ethereum addresses for users to deposit funds ("deposit addresses"). These addresses must:
- Be generated without immediate contract deployment
- Enable secure token aggregation to a hot wallet
- Eliminate private key storage risks
Suboptimal Solutions
1. Direct Use of Ethereum Addresses
Pros:
- Simple implementation
- Low gas costs for transfers
Cons:
- Requires private key storage
- Security vulnerabilities if keys are compromised
2. Individual Smart Contracts per User
Pros:
- No private key management
- Programmable transfers
Cons:
- Addresses unavailable pre-deployment
- Wasted funds on unused accounts
The CREATE2 Solution
How CREATE2 Works
The address formula:
keccak256(0xff ++ creator_addr ++ salt ++ keccak256(init_code))[12:]Where:
creator_addr: Factory contract addresssalt: User-derived value (e.g.,hash(user_id))init_code: Wallet contract bytecode
Implementation Steps
- Precompute Addresses
Generate deposit addresses offline using the formula above. - Monitor Deposits
Watch forTransferevents to precomputed addresses. Deploy On-Demand
When funds arrive:function deployWallet(uint256 salt) public { bytes memory bytecode = type(Wallet).creationCode; address newAddr; assembly { newAddr := create2(0, add(bytecode, 32), mload(bytecode), salt) } }Auto-Aggregate Funds
Wallet constructor executes:constructor() { token.transfer(hotWallet, token.balanceOf(address(this))); selfdestruct(address(0)); }
Key Benefits
- Gas Efficiency: Self-destruct refunds deployment costs
- Security: No private keys stored
- Flexibility: Deploy contracts only when needed
Full Code Example
pragma solidity ^0.8.0;
interface IERC20 {
function transfer(address to, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
contract Wallet {
address private immutable token;
address private immutable hotWallet;
constructor(address _token, address _hotWallet) {
token = _token;
hotWallet = _hotWallet;
IERC20(token).transfer(
hotWallet,
IERC20(token).balanceOf(address(this))
);
selfdestruct(payable(address(0)));
}
}
contract WalletFactory {
function createWallet(
uint256 salt,
address token,
address hotWallet
) public returns (address) {
bytes memory bytecode = abi.encodePacked(
type(Wallet).creationCode,
abi.encode(token, hotWallet)
);
address wallet;
assembly {
wallet := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
return wallet;
}
}FAQ Section
Q1: Can CREATE2 addresses be reused?
Yes. Self-destructing a contract resets the nonce, allowing redeployment to the same address.
Q2: What's the gas cost for this solution?
Approximately equal to a standard transfer() call after accounting for gas refunds.
Q3: How are salts generated securely?
Use keccak256(user_id) to ensure deterministic yet unique values.
👉 Explore advanced Solidity patterns
Q4: Is this production-ready?
The provided code is simplified. Production implementations should optimize bytecode and add access controls.
👉 Learn about secure wallet management
This Markdown document adheres to SEO best practices with:
- Hierarchical headings
- Natural keyword integration ("CREATE2", "deposit addresses", "smart contracts")
- Structured FAQs
- Engaging anchor texts