Deploying to StarkNet with the Universal Deployer Contract

David Barreto
Starknet Edu
Published in
6 min readDec 6, 2022

TL;DR; Stop using starknet deploy when using the CLI to deploy a smart contract to StarkNet as this type of transaction is now deprecated. Use the Universal Deployer Contract (UDC) instead in tandem with starknet invoke. Alternatively, you can use Argent X dev tools for declaring and deploying a smart contract using a UI.

The Problem We Want to Solve

Until recently, if you had asked me how to deploy a Cairo contract to StarkNet, I would have shown you a code snippet like the one below that uses cairo-lang CLI.

$ starknet deploy --contract SomeCompiledContract.json --no_wallet

Take a closer look at the last flag. No wallet? If a wallet is not assigned to the transaction then who’s paying for the deployment? The answer is “no one.” You could try removing the flag, but the result is the same, the transaction is executed for free.

StarkWare made the conscious decision to subsidize the usage of StarkNet while the network was being developed. Now, after a year of battle testing the technology stack, the focus is on decentralizing the network. Decentralization means that the subsidy must stop because, who would want to run a Sequencer if they won’t get paid for deploying smart contracts?

This is why the “deploy transaction” has been deprecated and now the “deploy syscall” is used. The deploy syscall is a function available to any Cairo smart contract that allows the Sequencer to get paid proportionally to the complexity of the deployment as with any other execution.

The interface of the deploy syscall is shown below.

func deploy{...}(
class_hash: felt,
contract_address_salt: felt,
constructor_calldata_size: felt,
constructor_calldata: felt*,
deploy_from_zero: felt,
) -> (contract_address: felt) {}

Using the path shown below, you can import this function from the starknet library that comes as part of the cairo-lang package.

from starkware.starknet.common.syscalls import deploy

This new way of deploying a smart contract means that we need a “deployer” contract that uses the syscall to deploy any smart contract we create. This creates a new challenge in the form of a tongue twister: How do you deploy a deployer without using the deploy transaction?

The Universal Deployer Contract

The folks at Open Zeppelin have been aware of the need to have a deployer smart contract that is generic enough that can be used by anyone wanting to deploy their smart contracts. They’ve called it the Universal Deployer Contract (UDC), which has a single function, deployContract.

UniversalDeployer.cairo

from starkware.starknet.common.syscalls import deploy
...

@event
func ContractDeployed(...) {}

@external
func deployContract{...}(
classHash: felt,
salt: felt,
unique: felt,
calldata_len: felt,
calldata: felt*
) -> (address: felt) {
...
let (address) = deploy(
class_hash=classHash,
contract_address_salt=_salt,
constructor_calldata_size=calldata_len,
constructor_calldata=calldata,
deploy_from_zero=from_zero,
);

ContractDeployed.emit(...);

return (address=address);
}

The public function is essentially a wrapper around the deploy syscall that also emits an event when it has finished deploying a contract.

The UDC is already available on Goerli and Mainnet using the same address, so it’s available as a public good for the StarkNet ecosystem.

To deploy your contract using the UDC, you should now perform these steps:

  1. Compile your contract using the CLI
  2. Declare your contract class on your target network (Goerli or Mainnet) and take note of the returned class hash
  3. Invoke the function deployContract of the UDC on either Goerli or Mainnet, passing the class hash of your contract and the constructor arguments, among other parameters.

Notice that the deploy transaction is never used; instead, the invoke transaction is used. The invoke transaction requires a user account (wallet) with enough wrapped Ether to charge the user for the deployment.

Using the UDC with the CLI

Let’s get our hands dirty and attempt to deploy a smart contract using the UDC and the cairo-lang CLI. I’ll be using a simple Python dev environment as we described in a previous article that would give us the project structure shown below.

$ tree . -L 1
>>>
.
├── abis
├── compiled
├── contracts
├── env
├── requirements.txt
└── setup.bash

Because this tutorial focuses on the deployment workflow and not Cairo programming, I’ll use the most straightforward smart contract I can think of for this example.

contracts/Simple.cairo

%lang starknet

@view
func return_two() -> (value: felt) {
return (value=2);
}

The first step as always is compiling our smart contract.

$ starknet-compile contracts/Simple.cairo \
--output compiled/Simple.json

Next, we need to set the environment variables STARKNET_NETWORK and STARKNET_WALLET to inform the CLI of our target network (Goerli) and the implementation of our local dev wallet for signing transactions.

$ export STARKNET_NETWORK=alpha-goerli
$ export STARKNET_WALLET=starkware.starknet.wallets.open_zeppelin.OpenZeppelinAccount

With these environment variables set, we can now declare our smart contract on Goerli.

$ starknet declare --contract compiled/Simple.json
>>>

Sending the transaction with max_fee: 0.000000 ETH (148268138 WEI).
Declare transaction was sent.
Contract class hash: 0x2c8eccf15aa686367cb86bd3e0988cee3e09b01d3c17f8aff715ebf639348f8
Transaction hash: 0x2edccf3ae856f5e37bb69ab8cdac20c05e164f1785a74f0c017a4100c823971

Take note of the returned “class hash” from the transaction as we will need it when invoking the UDC function deployContract.

@external
func deployContract{...}(
classHash: felt,
salt: felt,
unique: felt,
calldata_len: felt,
calldata: felt*
) -> (address: felt) {...}

The value for salt can be any number you want; it’s there only to introduce randomness into the generated address for your soon-to-be-deployed smart contract.

The unique field, combined with salt, can be used to get the same address on different networks. For example, if I deploy to Goerli, passing the value 5 for salt and 0 (False) for unique, my smart contract will be deployed to a specific address; let’s say it’s 0xabc… I can now repeat the deployment process, but this time on Mainnet, and if I pass the same values for salt and unique (5 and 0), my smart contract will be deployed to the same address 0xabc… but this time on Mainnet. In my example, I’ll use the value 0 for both parameters for simplicity, not because I want to preserve the address.

Because my smart contract doesn’t even have a constructor, I’ll pass the value 0 for calldata_len and ignore calldata altogether. Finally, you can get the ABI of the UDC directly from StarkScan.

At this point we have all we need to invoke the function deployContract on the UDC (0x041a…).

Note: Before sending another transaction to the network, make sure your previous transaction (declare) is at least in the “Pending” state otherwise your second transaction will fail due to an incorrect nonce value. This happens because the network is the one tracking the current nonce value of each user account and using the CLI, this value is updated only after a transaction has entered the Pending state.

$ starknet invoke \
--address 0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf \
--abi abis/UDC.json \
--function deployContract \
--inputs 0x2c8eccf15aa686367cb86bd3e0988cee3e09b01d3c17f8aff715ebf639348f8 0 0 0
>>>

Sending the transaction with max_fee: 0.000000 ETH (3172060 WEI).
Invoke transaction was sent.
Contract address: 0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf
Transaction hash: 0x298f2b3acaacbb13cbb1679ff86eda181c3f48e2d7e5953e715aa1d9152b975

The contract address returned by the command is not the address of the smart contract you are trying to deploy. Transactions are asynchronous and they communicate back to you emitting events that are captured as part of the transaction logs. If we explore the logs of the transaction (0x298f…) we can see the ContractDeployed event.

Inspecting the ContractDeployed event to find the smart contract address

According to the event, the address of our smart contract is 0x5ebd… which we can verify on the blockchain explorer by inspecting the “Read Contract” tab.

The public interface of our deployed smart contract

We now have proof that our smart contract was deployed successfully to Goerli without ever using the deprecated deploy transaction.

Conclusion

Deploy StarkNet smart contracts using Open Zeppelin’s Universal Deployer Contract (UDC) instead of relying on the now deprecated deploy function. The UDC uses the new deploy syscall so the Sequencer can get paid when deploying smart contracts, a requirement for decentralizing the network. Using a syscall also allows us to integrate deployments into the execution of any smart contract we create.

Another way to declare and deploy a smart contract with the UDC is by using Argent X dev tools available on their browser extension. You can use their tools by opening their browser extension and then going to Settings → Developer Settings → Smart Contract Development. This is probably easier than using the CLI and has the added benefit of tracking the balance of your test Ether.

--

--

David Barreto
Starknet Edu

Starknet Developer Advocate. Find me on Twitter as @barretodavid