

This is the second article in a series about deterministic deployments, where we explore the question: how can we deploy a contract at the same address in multiple chains?
In the first part we discussed three possible answers: deploying with the same private key and nonce, using Nick’s method, and pre-signing transactions. In this entry we’ll discuss two other approaches: CREATE2 factories and CREATE3.
The two methods we’ll explain in this article are based on the CREATE2 opcode, so let’s quickly review how it works.
As we saw in the previous entry, contracts can create other contracts using the CREATE and CREATE2 opcodes. The CREATE opcode behaves in essentially the same way as deployment transactions:
Some init code is executed to get the runtime code of the new contract.
The address of the created contract depends on the address and nonce of the sender. For deployment transactions, the sender is the EOA that signed the transaction. For CREATE, it’s the address of the contract executing the opcode.
The CREATE2 opcode is similar to CREATE, but the way the address is computed changes. Remember that, for CREATE and deployment transactions, the formula is:
address = keccak256(rlp([sender, nonce]))[12:]For CREATE2, on the other hand, it’s:
address = keccak256(0xff ++ sender ++ salt ++ keccak256(initcode))[12:]where salt is an extra 32-byte input passed to the opcode.
Notice which parameters affect the resulting address now. The sender is still one, but instead of the nonce we have a salt and the hash of the init code.
Suppose there is a factory contract that receives some init code and a salt, and executes the CREATE2 opcode with them. Let’s also suppose this factory is deployed in two chains, and we use both of them to deploy a contract.

Will the created contract have the same address in both cases?
As we explained in the previous section, the address will depend on:
The init code and salt passed to CREATE2
The address of the factory
Since we are deploying the same contract, the init code will be identical. And we are passing the salt, so we can just use the same value in both cases.
In other words, the address of the deployed contract will be the same as long as the address of the factory is the same in both chains. But this is exactly the thing we want to solve in the first place! It’s a chicken-and-egg problem. That being said, if we manage to solve it, we are solving the problem for everyone else, because the factory contract can then be used permissionlessly.
In the previous article we saw some approaches to tackle exactly this problem. Could we use one of those to deploy a CREATE2 factory in many chains that anyone can use? As it turns out, at least four such factories exist. Let’s briefly explore each one.
This is the earliest of the CREATE2 factories we’ll discuss: it was deployed more than five years ago. It’s a very minimal contract that just receives a salt and an init code, calls CREATE2 with them, and returns the address of the created contract (or reverts if the address already had code). Its whole runtime is just 69 bytes of low-level code.
This factory is deployed using Nick’s method.[1] The deployment transaction looks like this:
{
"to": null, // it's a deployment transaction
"input": "0x6045...0cf3", // init code
// ...other fields...
"r": "0x2222222222222222222222222222222222222222222222222222222222222222",
"s": "0x2222222222222222222222222222222222222222222222222222222222222222",
"v": "0x1b",
}Notice the values of the r and s fields. In our explanation of Nick’s method, we said that any random values have a good chance of being a valid signature, and that’s true. But the problem with using random values is that it’s impossible to prove you don’t actually have a private key that generated the signature. That’s why this transaction uses nothing-up-my-sleeve numbers like 0x2222...2222.
Unlike the previous factory, create2deployer is written in Solidity. This makes it more complex and its runtime much bigger, but in turn it means it can have some useful view functions and it’s easier to call from other contracts. This factory is deployed using a managed private key.
This is a fork of Arachnid’s Deterministic Deployment Proxy made by the Safe team. The only difference is how it’s deployed: it uses a managed private key instead of Nick’s method, allowing it to be deployed in chains that enforce replay protection.
CreateX was developed by the same author of create2deployer, and improves upon it in at least two ways:
It has even more features (like CREATE3, which we’ll explain in a minute)
It’s deployed using pre-signed transactions[2], with a managed private key kept as a backup
These four factories have been widely deployed. All of them are available in the chains listed by evmdiff. I went over the list of rollups in l2beat and had to reach the 15th listed chain to find an example of a missing factory: Morph doesn’t have create2deployer, at least at the time of writing this.
Even if some factory is missing in a given chain, chances are that CreateX will be there: it's been deployed to over 180 chains.
In our discussion of CREATE2 factories we said that we can deterministically deploy a contract as long as:
There is a CREATE2 factory at the same address in all the target chains
We use the same salt and init code each time
As we just saw, factories are widely available, so we can consider the first point as a given. And in all of these factories, the salt is provided by the caller, so we can just use the same value each time.
What about the init code? Since we want to deploy the same contract, it’s natural to assume it will also be the same each time, but this is not necessarily true. One simple counterexample is a contract that receives constructor arguments whose values might change between chains. Constructor arguments are included as part of the init code[3], meaning that the CREATE2-generated address would change.[4]
In cases like that, CREATE2 is not an option because the init code will affect the deployment address; there’s no way around that. The methods based on deployment transactions we explored in the previous article don’t have this issue, but they involve a significantly bigger effort.
If we don’t want to use deployment transactions and we can’t use CREATE2, there’s only one mechanism left to deploy a contract: the CREATE opcode.
Suppose we have a contract factory with the following characteristics:
It has a method that receives the init code of a contract and deploys it using CREATE.
It has the same address in every chain.
It has the same nonce in every chain.
The first two conditions are easy (the code of such factory is straightforward, and we can deploy it using a CREATE2 factory) but the third one is harder: we can’t control who uses this CREATE-based factory, and each time someone uses it its nonce is incremented. But what if we deploy a factory like this and, in the same transaction, we use it to deploy our contract? That’s the essence of the CREATE3 approach.[5]

In this diagram we have a Create3Factory contract with a deployCreate3 method.[6] The parameters in green are the ones provided by the user. How will the address of MyContract be derived?
We are assuming that Create3Factory exists at the same address in every chain. This means that the two other addresses are:
The address of the one-time-use CREATE factory. The init code for this contract is always the same, and so its address will depend only on the user-provided salt.
The address of the contract we want to deploy. Because the one-time-use factory has just been created, its nonce is guaranteed to be 1. This means that only its address will affect the address of our contract.
Putting it all together, the address of our deployed contract depends only on the address of the one-time-use CREATE factory, which itself depends only on the user-provided salt. In other words, the address of our contract will depend only on the salt we pass to deployCreate3. It won’t be affected by its init code, the sender address, or any of the nonces involved.
While this does what we want, it introduces a new problem. When we deploy our contract in some chain using a salt S, anyone can go to some other chain and use the same factory and salt to deploy any contract. This is not great. The usual workaround is to implement deployCreate3 so that the address of the deployed contract can only be obtained by a given msg.sender (there are a couple of ways to accomplish this; see [7]). In a way, this takes us back to where we started: we need to manage a private key so that the same msg.sender is used every time. But eliminating the nonce dependency is a significant improvement.
We’ve covered a lot of ground in these two articles:
Managed private keys and pre-signed transactions
Nick’s method
CREATE2-based approaches, like CREATE2 factories and CREATE3
But there’s still more. In the next and final entry of this series we’ll explore some other approaches to the problem, including the very clever ERC-7955. You can subscribe to the blog to get notified when the third part is published.
[1]: Which makes sense, because Arachnid is the Nick after which the method is named.
[2]: In fact, this is the only project I’m aware of that uses pre-signed transactions, and it’s where I learned about that approach.
[3]: In the previous article we said that the init code is executed to return the runtime code of the deployed contract. While this is correct, it’s an incomplete description. The init code also performs initialization which, in Solidity terms, means executing the constructor. The constructor arguments therefore need to be available, and the way this is done is by including them in the init code.
[4]: I’m using this as an example of something that makes the init code change, but I should point out that there’s a simple workaround: instead of initializing the contract in the constructor, the contract can have an init method that can only be called once, and it’s immediately called with the proper arguments during the same deployment transaction. If you are using CreateX, you can do this with the deployCreate2AndInit method.
[5]: CREATE3 was originally proposed as a new opcode, but it was withdrawn because there was a workaround that seemed good enough. The approach we are explaining here is a different workaround though, and the one I believe most people have in mind when they say CREATE3.
[6]: For simplicity I’m assuming there is a deterministically deployed Create3Factory with this functionality, but the pattern is still possible if there isn’t. In that case, you need to deploy a contract which calls the CREATE2 factory and then calls the one-time-use factory. This contract does not need to be deterministically deployed.
This is the second article in a series about deterministic deployments, where we explore the question: how can we deploy a contract at the same address in multiple chains?
In the first part we discussed three possible answers: deploying with the same private key and nonce, using Nick’s method, and pre-signing transactions. In this entry we’ll discuss two other approaches: CREATE2 factories and CREATE3.
The two methods we’ll explain in this article are based on the CREATE2 opcode, so let’s quickly review how it works.
As we saw in the previous entry, contracts can create other contracts using the CREATE and CREATE2 opcodes. The CREATE opcode behaves in essentially the same way as deployment transactions:
Some init code is executed to get the runtime code of the new contract.
The address of the created contract depends on the address and nonce of the sender. For deployment transactions, the sender is the EOA that signed the transaction. For CREATE, it’s the address of the contract executing the opcode.
The CREATE2 opcode is similar to CREATE, but the way the address is computed changes. Remember that, for CREATE and deployment transactions, the formula is:
address = keccak256(rlp([sender, nonce]))[12:]For CREATE2, on the other hand, it’s:
address = keccak256(0xff ++ sender ++ salt ++ keccak256(initcode))[12:]where salt is an extra 32-byte input passed to the opcode.
Notice which parameters affect the resulting address now. The sender is still one, but instead of the nonce we have a salt and the hash of the init code.
Suppose there is a factory contract that receives some init code and a salt, and executes the CREATE2 opcode with them. Let’s also suppose this factory is deployed in two chains, and we use both of them to deploy a contract.

Will the created contract have the same address in both cases?
As we explained in the previous section, the address will depend on:
The init code and salt passed to CREATE2
The address of the factory
Since we are deploying the same contract, the init code will be identical. And we are passing the salt, so we can just use the same value in both cases.
In other words, the address of the deployed contract will be the same as long as the address of the factory is the same in both chains. But this is exactly the thing we want to solve in the first place! It’s a chicken-and-egg problem. That being said, if we manage to solve it, we are solving the problem for everyone else, because the factory contract can then be used permissionlessly.
In the previous article we saw some approaches to tackle exactly this problem. Could we use one of those to deploy a CREATE2 factory in many chains that anyone can use? As it turns out, at least four such factories exist. Let’s briefly explore each one.
This is the earliest of the CREATE2 factories we’ll discuss: it was deployed more than five years ago. It’s a very minimal contract that just receives a salt and an init code, calls CREATE2 with them, and returns the address of the created contract (or reverts if the address already had code). Its whole runtime is just 69 bytes of low-level code.
This factory is deployed using Nick’s method.[1] The deployment transaction looks like this:
{
"to": null, // it's a deployment transaction
"input": "0x6045...0cf3", // init code
// ...other fields...
"r": "0x2222222222222222222222222222222222222222222222222222222222222222",
"s": "0x2222222222222222222222222222222222222222222222222222222222222222",
"v": "0x1b",
}Notice the values of the r and s fields. In our explanation of Nick’s method, we said that any random values have a good chance of being a valid signature, and that’s true. But the problem with using random values is that it’s impossible to prove you don’t actually have a private key that generated the signature. That’s why this transaction uses nothing-up-my-sleeve numbers like 0x2222...2222.
Unlike the previous factory, create2deployer is written in Solidity. This makes it more complex and its runtime much bigger, but in turn it means it can have some useful view functions and it’s easier to call from other contracts. This factory is deployed using a managed private key.
This is a fork of Arachnid’s Deterministic Deployment Proxy made by the Safe team. The only difference is how it’s deployed: it uses a managed private key instead of Nick’s method, allowing it to be deployed in chains that enforce replay protection.
CreateX was developed by the same author of create2deployer, and improves upon it in at least two ways:
It has even more features (like CREATE3, which we’ll explain in a minute)
It’s deployed using pre-signed transactions[2], with a managed private key kept as a backup
These four factories have been widely deployed. All of them are available in the chains listed by evmdiff. I went over the list of rollups in l2beat and had to reach the 15th listed chain to find an example of a missing factory: Morph doesn’t have create2deployer, at least at the time of writing this.
Even if some factory is missing in a given chain, chances are that CreateX will be there: it's been deployed to over 180 chains.
In our discussion of CREATE2 factories we said that we can deterministically deploy a contract as long as:
There is a CREATE2 factory at the same address in all the target chains
We use the same salt and init code each time
As we just saw, factories are widely available, so we can consider the first point as a given. And in all of these factories, the salt is provided by the caller, so we can just use the same value each time.
What about the init code? Since we want to deploy the same contract, it’s natural to assume it will also be the same each time, but this is not necessarily true. One simple counterexample is a contract that receives constructor arguments whose values might change between chains. Constructor arguments are included as part of the init code[3], meaning that the CREATE2-generated address would change.[4]
In cases like that, CREATE2 is not an option because the init code will affect the deployment address; there’s no way around that. The methods based on deployment transactions we explored in the previous article don’t have this issue, but they involve a significantly bigger effort.
If we don’t want to use deployment transactions and we can’t use CREATE2, there’s only one mechanism left to deploy a contract: the CREATE opcode.
Suppose we have a contract factory with the following characteristics:
It has a method that receives the init code of a contract and deploys it using CREATE.
It has the same address in every chain.
It has the same nonce in every chain.
The first two conditions are easy (the code of such factory is straightforward, and we can deploy it using a CREATE2 factory) but the third one is harder: we can’t control who uses this CREATE-based factory, and each time someone uses it its nonce is incremented. But what if we deploy a factory like this and, in the same transaction, we use it to deploy our contract? That’s the essence of the CREATE3 approach.[5]

In this diagram we have a Create3Factory contract with a deployCreate3 method.[6] The parameters in green are the ones provided by the user. How will the address of MyContract be derived?
We are assuming that Create3Factory exists at the same address in every chain. This means that the two other addresses are:
The address of the one-time-use CREATE factory. The init code for this contract is always the same, and so its address will depend only on the user-provided salt.
The address of the contract we want to deploy. Because the one-time-use factory has just been created, its nonce is guaranteed to be 1. This means that only its address will affect the address of our contract.
Putting it all together, the address of our deployed contract depends only on the address of the one-time-use CREATE factory, which itself depends only on the user-provided salt. In other words, the address of our contract will depend only on the salt we pass to deployCreate3. It won’t be affected by its init code, the sender address, or any of the nonces involved.
While this does what we want, it introduces a new problem. When we deploy our contract in some chain using a salt S, anyone can go to some other chain and use the same factory and salt to deploy any contract. This is not great. The usual workaround is to implement deployCreate3 so that the address of the deployed contract can only be obtained by a given msg.sender (there are a couple of ways to accomplish this; see [7]). In a way, this takes us back to where we started: we need to manage a private key so that the same msg.sender is used every time. But eliminating the nonce dependency is a significant improvement.
We’ve covered a lot of ground in these two articles:
Managed private keys and pre-signed transactions
Nick’s method
CREATE2-based approaches, like CREATE2 factories and CREATE3
But there’s still more. In the next and final entry of this series we’ll explore some other approaches to the problem, including the very clever ERC-7955. You can subscribe to the blog to get notified when the third part is published.
[1]: Which makes sense, because Arachnid is the Nick after which the method is named.
[2]: In fact, this is the only project I’m aware of that uses pre-signed transactions, and it’s where I learned about that approach.
[3]: In the previous article we said that the init code is executed to return the runtime code of the deployed contract. While this is correct, it’s an incomplete description. The init code also performs initialization which, in Solidity terms, means executing the constructor. The constructor arguments therefore need to be available, and the way this is done is by including them in the init code.
[4]: I’m using this as an example of something that makes the init code change, but I should point out that there’s a simple workaround: instead of initializing the contract in the constructor, the contract can have an init method that can only be called once, and it’s immediately called with the proper arguments during the same deployment transaction. If you are using CreateX, you can do this with the deployCreate2AndInit method.
[5]: CREATE3 was originally proposed as a new opcode, but it was withdrawn because there was a workaround that seemed good enough. The approach we are explaining here is a different workaround though, and the one I believe most people have in mind when they say CREATE3.
[6]: For simplicity I’m assuming there is a deterministically deployed Create3Factory with this functionality, but the pattern is still possible if there isn’t. In that case, you need to deploy a contract which calls the CREATE2 factory and then calls the one-time-use factory. This contract does not need to be deterministically deployed.
[7]: One way to ensure a target address can only be used by a given caller is to modify the salt: instead of just forwarding it to CREATE2, the factory creates a new salt by hashing the user-provided salt and msg.sender. If there aren’t other functions that let you use CREATE2 with an arbitrary salt, then this approach guarantees that the resulting address can only be used by that sender. This is not the only approach though. CreateX, for example, does something different.
[7]: One way to ensure a target address can only be used by a given caller is to modify the salt: instead of just forwarding it to CREATE2, the factory creates a new salt by hashing the user-provided salt and msg.sender. If there aren’t other functions that let you use CREATE2 with an arbitrary salt, then this approach guarantees that the resulting address can only be used by that sender. This is not the only approach though. CreateX, for example, does something different.
<100 subscribers
<100 subscribers
Share Dialog
Share Dialog
Franco Victorio
Franco Victorio
No comments yet