Ethereum addresses

The simplest solution is to generate ethereum addresses for new users. These addresses will be wallets. To transfer tokens from a wallet to a hotwallet, you need to sign a transaction with the transfer() function call with the wallet private key from the backend.

This approach has the following advantages:

It is simple

The price of transferring tokens from a wallet to a hotwallet is equal to the price of the transfer() function call

However, we decided not to choose this approach because it has one significant disadvantage: you need to store private keys somewhere. And it’s not just that they can be lost, but also that you need to carefully manage access to these keys. If at least one of them is compromised, then the tokens of a particular user will not reach the hotwallet.

Create a separate smart contract for each user

Deploying a separate smart contract for each user allows not to store private keys from the wallets on the server. The exchange will call this smart contract to transfer tokens to hotwallet.

However, we also decided not to use this solution, since it is impossible for the user to show the address of his/her wallet without deploying a smart contract (it is actually possible, but in a pretty complicated way with other drawbacks that will not be discussed here). At the exchange, a user can create as many accounts as he/she wants and each one needs their own wallet. That means that we need to waste money on contract deployment without even being sure that the user will use an account.

CREATE2 opcode

In order to eliminate the problem of the previous version, we decided to use CREATE2 opcode. CREATE2 allows you to pre-calculate the address at which the smart contract will be deployed. The address is calculated by the following formula:

keccak256 (0xff ++ address ++ salt ++ keccak256 (init_code)) [12:]

● address — the address of the smart contract that will invoke CREATE2

● salt — random value

● init_code — smart contract’s bytecode to be deployed

Thus, it is guaranteed that the address that we provide to the user will indeed contain the desired bytecode. Besides, this smart contract can be deployed when we need it. For example, when the user decides to use his/her wallet.

Moreover, you can calculate the smart contract address every time instead of storing it since:

● address in the formula is constant, as this is the address of our factory wallet

● salt is hash of user_id

● init_code is constant since we deploy the same wallet.

More improvement

The previous solution still has one drawback: you need to pay for the deployment of a smart contract. However, you can get rid of it. To do this, you can call the transfer() function and then selfdestruct() in the wallet constructor. And then the gas for the deployment of the smart contract will be refunded. Contrary to a popular misconception, you can deploy a smart contract at the same address several times with CREATE2 opcode. This is because CREATE2 checks that the nonce of the target address is zero (it is set to one at the beginning of the constructor). In this case, the selfdestruct() function resets nonce of the address every time. Thus, if you call CREATE2 with the same arguments again, the check on nonce will pass.