One of the lesser-discussed technical decisions in our payment architecture: using Ethereum's CREATE2 opcode to generate deterministic merchant receiver addresses.
The Problem
When settling card payments on-chain, each merchant needs a unique address to receive funds. You could:
- Generate random addresses: Simple, but the card/terminal needs to know the merchant's address at tap time
- Use a central registry: Works, but adds a lookup step and a centralization point
- Use CREATE2: Compute the address deterministically from the merchant ID
We chose option 3.
How CREATE2 Works
CREATE2 computes a contract address from:
- The deployer address (our ClearingVault)
- A salt (derived from the merchant's ID)
- The bytecode of the receiver contract
address receiver = address(uint160(uint256(keccak256(
abi.encodePacked(bytes1(0xff), clearingVault, salt, bytecodeHash)
))));
This means:
- The receiver address is known before deployment
- Any party can compute it from the merchant ID
- The contract only needs to be deployed when first receiving funds
- No central registry or lookup needed
Why This Matters for Card Payments
At tap time:
- Card signs transaction data (includes merchant ID)
- ClearingVault receives the transaction
- ClearingVault computes the merchant's receiver address via CREATE2
- If receiver doesn't exist yet, deploy it
- Route stablecoins to the receiver
The merchant's address is deterministic. The card doesn't need to store it. The terminal doesn't need to look it up. It's computed on the fly.
The Receiver Contract
Each merchant's receiver is a minimal contract:
contract MerchantReceiver {
address public merchant;
address public clearingVault;
function withdraw(address token) external {
require(msg.sender == merchant);
IERC20(token).transfer(merchant, IERC20(token).balanceOf(address(this)));
}
}
Merchants can withdraw at any time. Funds accumulate between withdrawals.
Gas Efficiency
On Base L2:
- Computing a CREATE2 address: ~100 gas
- First-time deployment: ~50,000 gas (~$0.001)
- Subsequent settlements: ~30,000 gas (~$0.0005)
Compare this to Visa's 0.3-0.8% per transaction. On a $5 coffee, Visa takes $0.015-$0.04. Our on-chain cost is less than $0.001.
For Developers
If you're building any system that needs deterministic, per-entity addresses:
- Use CREATE2 with entity ID as salt
- Keep receiver contracts minimal
- Consider lazy deployment (only deploy on first receipt)
- Use Base L2 for sub-cent gas costs
This pattern works for: payment settlement, subscription billing, marketplace escrow, royalty distribution.
Top comments (0)