DEV Community

Alfred Zhang
Alfred Zhang

Posted on

CREATE2 in Payments: Deterministic Merchant Addresses on Base L2

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:

  1. Generate random addresses: Simple, but the card/terminal needs to know the merchant's address at tap time
  2. Use a central registry: Works, but adds a lookup step and a centralization point
  3. 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)
))));
Enter fullscreen mode Exit fullscreen mode

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:

  1. Card signs transaction data (includes merchant ID)
  2. ClearingVault receives the transaction
  3. ClearingVault computes the merchant's receiver address via CREATE2
  4. If receiver doesn't exist yet, deploy it
  5. 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)));
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Use CREATE2 with entity ID as salt
  2. Keep receiver contracts minimal
  3. Consider lazy deployment (only deploy on first receipt)
  4. Use Base L2 for sub-cent gas costs

This pattern works for: payment settlement, subscription billing, marketplace escrow, royalty distribution.


OpenPasskey | @OpenPasskey

Top comments (0)