Starkli 101
In this tutorial, you will be guided from scratch to deploy contracts on the Starknet testnet.
- Preparing your cryptographic keys and account.
- Compiling your Cairo contract with a simple example contract.
- Declaring a new class on Starknet.
- Deploying a contract instance of a class on Starknet.
ℹ️ Note
To make it easier to get started, this tutorial skips the step of choosing a provider and uses the
sepolia
network fallback with free RPC vendor, but relying on the network fallback is discouraged in practice.Make sure to visit the providers page to learn more once you finish the tutorial.
Prepare a signer and an account
To interact with the network, you need an account to sign transactions. This tutorial assumes that you have no signer or account setup.
Initialize a signer
The signer is in charge of signing transactions. You can create a new pair of cryptographic key, or import an existing private key. Check out the signers page for more details.
Let's create a new signer here.
$ starkli signer keystore new /path/to/key.json
This will prompt you to enter a password and save your encrypted private key into the key.json
keystore file.
To then use this keystore at each command, you can export the environment variable like:
$ export STARKNET_KEYSTORE="/path/to/key.json"
Initialize an account
We then initialize a new account, using OpenZeppelin class already declared on Starknet:
$ starkli account oz init /path/to/account.json
ℹ️ Note
Note that we didn't need to pass the
--keystore
option since theSTARKNET_KEYSTORE
environment variable is already set.
Fund and deploy the account
Up to this point, only the parameters for deploying the account have been generated, but the account itself hasn't been deployed yet. You must deploy the account to be able to use it.
To deploy the account, run:
$ starkli account deploy /path/to/account.json
The command would then displays a message similar to this:
The estimated account deployment fee is 0.000004323000964029 ETH. However, to avoid failure, fund at least:
0.000006484501446043 ETH
to the following address:
0x077c********************************************************3f8a
Press [ENTER] once you've funded the address.
ℹ️ Note
The address above is redacted just in case you accidentally send funds to it.
As instructed, you must pre-fund the address with ETH
to be able to continue. Send enough ETH
to the address, and then press the Enter key.
💡 Tips
You can use the
starkli balance
command (probably in a separate terminal session) to check whether the destinated account address has been successfully funded.
An account deployment transaction will be sent out. Once the transaction is confirmed, your account will be ready to use.
Again, to avoid having to pass the account file to each command invocation, we can export the STARKNET_ACCOUNT
variable:
$ export STARKNET_ACCOUNT="/path/to/account.json"
For more details about accounts, please refer to the accounts page.
Compile your Cairo contract
The next step is to compile a Cairo contract. There are a few options for compiling Cairo contracts, but in this tutorial, we'll use Scarb.
ℹ️ Note
This tutorial uses Scarb
v0.6.1
.
First, create a Scrab project:
$ mkdir my_contract
$ cd ./my_contract/
$ scarb init
Update the Scarb.toml
file to include the starknet
dependency and add the starknet-contract
target:
[package]
name = "my_contract"
version = "0.1.0"
[dependencies]
starknet = "=2.1.0"
[[target.starknet-contract]]
Then, replace the ./src/lib.cairo
file with the following content:
#![allow(unused)] fn main() { // ** ./src/lib.cairo ** #[starknet::interface] trait MyContractInterface<T> { fn name_get(self: @T) -> felt252; fn name_set(ref self: T, name: felt252); } #[starknet::contract] mod my_contract { #[storage] struct Storage { name: felt252, } #[event] #[derive(Drop, starknet::Event)] enum Event { NameChanged: NameChanged, } #[derive(Drop, starknet::Event)] struct NameChanged { previous: felt252, current: felt252, } #[constructor] fn constructor(ref self: ContractState, name: felt252) { self.name.write(name); } #[external(v0)] impl MyContract of super::MyContractInterface<ContractState> { fn name_get(self: @ContractState) -> felt252 { self.name.read() } fn name_set(ref self: ContractState, name: felt252) { let previous = self.name.read(); self.name.write(name); self.emit(NameChanged { previous, current: name }); } } } }
Now build the contract:
$ scarb build
and the built contract artifact (aka. Sierra class) will be available at ./target/dev/my_contract_my_contract.sierra.json
.
Declare the new class
Contract classes are like blueprints where actual contract instances can be deployed from. Now it's time to declare the new Sierra class. To declare, simply run:
$ starkli declare --watch ./target/dev/my_contract_my_contract.sierra.json
ℹ️ Note
If the class hash already exists, Starkli will not send out a declaration transaction.
This is not considered an error, but if you want to try a declaration anyways, you can do so by making your class slightly different by changing the Cairo code.
Starkli will then output the Cairo 1 class hash (which can also be obtained using the starkli class-hash <FILE.json>
command). You'll need this class hash for deploying the contract.
If you followed the exact same steps with the exact same tooling versions, you should be getting the class hash of 0x0756ea65987892072b836b9a56027230bbe8fbed5e0370cff22778d071a0798e
. It's normal that you arrive at a different hash, so don't worry about that.
Deploy your contract
Once your new class is declared, you can deploy an instance of your contract. Concrete contract instances hold state (storage values) while classes (what you declared at the last step) define the logic.
With Starkli, this is done via the deploy
command. To deploy, you will need the class hash obtained in the last step when declaring. Apart from the hash, you will also need to pass the constructor arguments to your contract:
$ starkli deploy <CLASS_HASH> <CTOR_ARGS>
In the case of my_contract
we declared above, the contract is expecting a single felt252
to be used as the name. A common way to represent strings as felt252
is to use the Cairo short string format. For example, you can find the Cairo short string representation of "starkli"
with the to-cairo-string
command:
$ starkli to-cairo-string starkli
0x737461726b6c69
You can use this value directly as an argument to deploy the contract:
$ starkli deploy --watch 0x0756ea65987892072b836b9a56027230bbe8fbed5e0370cff22778d071a0798e 0x737461726b6c69
💡 Tips
You can leverage argument resolution to simplify the argument list:
$ starkli deploy --watch 0x0756ea65987892072b836b9a56027230bbe8fbed5e0370cff22778d071a0798e str:starkli
Note how
0x737461726b6c69
is replaced withstr:starkli
. Learn more about argument resolution here.
Starkli prints the deployed contract address in the command output. You can use the address for interacting with the deployed contract.
Here we will use the address 0x06d8e1f3ed72fc87aa896639a0f50a4b9e59adb24de8a42b477957e1a7996e1b
. You will get a different address when you deploy the contract yourself. Simply replace the addresses in the following commands with your own.
Let's query the current name set for the contract:
$ starkli call 0x06d8e1f3ed72fc87aa896639a0f50a4b9e59adb24de8a42b477957e1a7996e1b name_get
[
"0x00000000000000000000000000000000000000000000000000737461726b6c69"
]
which is the Cairo short string representation of the text "starkli"
.
Let's change it to "starknet"
instead:
$ starkli invoke --watch 0x06d8e1f3ed72fc87aa896639a0f50a4b9e59adb24de8a42b477957e1a7996e1b name_set str:starknet
Now query the name again:
$ starkli call 0x06d8e1f3ed72fc87aa896639a0f50a4b9e59adb24de8a42b477957e1a7996e1b name_get
[
"0x000000000000000000000000000000000000000000000000737461726b6e6574"
]
And the name returned has changed. We've successfully modified the state of our contract.