CREATE3 in practice: proxy bytecode and implementation trade-offs

CREATE3 in practice: proxy bytecode and implementation trade-offs

In the second part of the CREATE3 series we’ll compare three production implementations (Solady, CreateX and the 1inch deployer) in depth. 

1. How the CREATE3 proxy bytecode works

CREATE3 removes the target's init code from the address formula by separating address selection from target deployment. A factory first deploys a fixed proxy with CREATE2. It then sends the target's init code to that proxy, which deploys the target with CREATE.

At the pinned revisions, Solady, CreateX and the current 1inch implementation use the same 16-byte proxy init code:

0x67363d3d37363d34f03d5260086018f3

Those 16 bytes contain two small programs: init code that runs once while the proxy is created and runtime code that remains at the proxy address.

1.1. Annotated bytecode

The listing below shows the byte offset, opcode byte, full instruction, mnemonic and stack after each instruction. The top of the stack is shown first; cds means call-data size and val means the native token value sent to the proxy.

/**

 * Proxy init code

 *

 * 0x67363d3d37363d34f03d5260086018f3

 *

 * offset  opcode  instruction           mnemonic          stack after

 * 0x00    0x67    0x67363d3d37363d34f0  PUSH8 runtime     runtime

 * 0x09    0x3d    0x3d                  RETURNDATASIZE    0 runtime

 * 0x0a    0x52    0x52                  MSTORE            -

 * 0x0b    0x60    0x6008                PUSH1 0x08        8

 * 0x0d    0x60    0x6018                PUSH1 0x18        24 8

 * 0x0f    0xf3    0xf3                  RETURN            -

 *

 * Proxy runtime code

 *

 * 0x363d3d37363d34f0

 *

 * offset  opcode  instruction  mnemonic          stack after

 * 0x00    0x36    0x36         CALLDATASIZE      cds

 * 0x01    0x3d    0x3d         RETURNDATASIZE    0 cds

 * 0x02    0x3d    0x3d         RETURNDATASIZE    0 0 cds

 * 0x03    0x37    0x37         CALLDATACOPY      -

 * 0x04    0x36    0x36         CALLDATASIZE      cds

 * 0x05    0x3d    0x3d         RETURNDATASIZE    0 cds

 * 0x06    0x34    0x34         CALLVALUE         val 0 cds

 * 0x07    0xf0    0xf0         CREATE            target

 */

1.2. How the init code installs the runtime

Think of proxy creation as a one-shot installer. The 16-byte program runs only while the proxy is being created; whatever it RETURNs becomes the code that stays at the proxy address.

The installer does three things:

  1. Load the future runtime as data. PUSH8 reads the next eight bytes (0x363d3d37363d34f0) onto the stack as one 256-bit value. During creation those bytes are payload, not yet a program.
  2. Park that payload in memory. RETURNDATASIZE is a cheap way to push 0: no earlier external call has return data, so the size is zero. MSTORE then writes a full 32-byte word at memory offset 0. Because the stack value is only eight meaningful bytes, those bytes land in the right side of the word — memory offsets 24 through 31 — with zeros in 0-23:

memory[0 .. 32):

  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00

  00 00 00 00 00 00 00 00  36 3d 3d 37 36 3d 34 f0

                           ^----------------------^

                           8-byte runtime at offset 24

  1. Return only those eight bytes. PUSH1 0x08 and PUSH1 0x18 tell RETURN to hand back eight bytes starting at offset 24. The EVM stores that return blob as the deployed proxy's code. After this, the 16-byte installer never runs again; only the eight-byte runtime remains.

1.3. How the runtime deploys the target

Later, the factory calls the proxy and puts the target's full init code in the call data. The eight-byte runtime's only job is: copy that call data into memory and CREATE from it.

Walk the stack arguments from the annotated listing:

  1. Copy call data into memory. CALLDATACOPY needs (destOffset, dataOffset, length). The runtime builds that as (0, 0, cds):
  • CALLDATASIZE → cds (how many bytes the factory sent)
  • two RETURNDATASIZE → two zeros (again, a cheap 0)
  • CALLDATACOPY → writes the whole call data to memory[0 .. cds)
  1. Create a contract from that memory. CREATE needs (value, memoryOffset, length). The runtime builds that as (val, 0, cds):
  • CALLDATASIZE → cds again (length of the copied init code)
  • RETURNDATASIZE → 0 (start of memory)
  • CALLVALUE → val (native token forwarded with the call, if any)
  • CREATE → runs memory[0 .. cds) as init code and leaves the new target address on the stack

That is the whole proxy: it is not a store of logic about salts or factories. It is a tiny "deploy whatever bytes you just sent me" program.

One consequence of the call shape: the target constructor sees the proxy as msg.sender, not the factory owner, so pass an owner or recipient in the constructor arguments when the target needs one. The proxy forwards any native token value it receives, but only if the factory API actually sends that value in the call.

2. How Solady, CreateX and 1inch differ

The three implementations share the proxy bytecode above at their current pinned revisions. They diverge in how they package that mechanism, who may trigger a deployment and how the user salt becomes the salt that actually enters CREATE2. Those three choices decide address compatibility more than any shared CREATE3 label.

2.1. Form factor

Solady, CreateX and 1inch are not three factories of the same kind.

Solady ships CREATE3 as an internal Solidity library. Nothing of Solady itself is deployed. The integrator writes a wrapper; that wrapper address is the factory address in the CREATE3 formula. Adoption cost is code and review; network coverage is whatever the integrator deploys.

CreateX is a shared public factory already deployed at a canonical address (0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed) on many networks. Callers use the existing singleton; they do not maintain their own CREATE3 factory. The deployment surface is permissionless and broad: CREATE, CREATE2 and CREATE3 families live on one contract.

1inch is a per-project Ownable factory wrapping a vendored CREATE3 library, plus a Rust vanity-salt miner in the same repository. Each project deploys its own factory. The surface is small — essentially deploy and addressOf — and the operating model is owner-controlled deployment with bundled mining.

The practical fork is: build around a library (Solady), reuse a public singleton (CreateX), or own a private factory and miner (1inch).

2.2. Front-run protection

CREATE3 addresses are claimable: once the factory address, effective salt and proxy init code are fixed, whoever first succeeds at that salt wins the address. The three implementations put the guard in different places.

CreateX leaves the factory open to anyone and puts protection in the salt. Its _guard path can bind msg.sender and/or block.chainid into the effective salt. A salt whose first twenty bytes equal the caller's address is the recommended pattern against cross-chain proxy frontruns; a stranger cannot reproduce the same effective salt from another account. Section 2.3 details the salt transformations.

1inch leaves the salt raw and puts protection in ownership. Only the factory owner may call deploy. An outsider cannot claim a salt through that factory at all. The ownership model also fits how projects actually operate: the account that deploys may change over time — a new EOA, a multisignature wallet, a hand-off between teams — and transferOwnership keeps factory access with the current owner without rebinding salts to a fixed deployer. Because the salt stays raw, mined vanity salts survive that ownership change; CreateX-style sender-guarded salts would not.

Solady has no access control in the library. Protection is whatever the integrator's wrapper adds — ownership, allowlists or nothing. A wrapper that exposes raw-salt deploy publicly lets any caller claim salts under that wrapper's address space.

2.3. Salt handling

Salt is the sharpest address difference. Solady and 1inch use the user salt as the CREATE2 salt. CreateX almost never does.

Three consequences follow.

  1. Same user salt, different target. Even if 1inch and CreateX somehow shared a factory address and the same proxy init code, CreateX deployment addresses would be different.
  2. Prediction helpers do not take the same input. Solady and 1inch take the raw user salt. CreateX's takes the salt after _guard. The two inputs do not match.
  3. Off-chain mining and scripts must match the factory rule. A vanity salt mined for a raw-salt factory is not the CreateX input unless the miner reproduces _guard. Conversely, treating a CreateX user salt as if it were Solady's CREATE2 salt predicts the wrong address.

Solady and the current 1inch implementation can produce matching predictions when the factory address and raw salt match. CreateX is a different salt machine. The shared proxy does not make the salts interchangeable.

2.4. Comparison summary

The matrix below collects the differences above with the remaining API and tooling choices.

Use Solady when you want a library primitive and will build and audit its wrapper. Use CreateX when you want an existing permissionless factory and can reproduce its _guard transformation off-chain; the repository lists 195 deployment records at the pinned revision, but this article did not verify them on-chain. Use the 1inch repository when deployment should remain owner-controlled and the workflow needs bundled vanity mining; you must still deploy the factory at the required address on every chain.

Address compatibility requires the same factory address, effective salt and proxy init code. Solady and the current 1inch implementation produce matching predictions when given the same factory address and raw salt. CreateX transforms user salts, while older 1inch factories use different proxy init code. The shared CREATE3 label does not imply matching target addresses.

In the third and final part of this series, we’ll explore how these constraints can be turned into an executable 1inch workflow: deploy the factory, mine and verify a vanity salt, build the target init code and deploy the target at the predicted address.

Stay tuned for more 1inch tech content.