Starkli: The New Starknet CLI

David Barreto
Starknet Edu
Published in
10 min readJul 14, 2023

TL;DR; Starkli is a Starknet CLI similar to cairo-lang but written in Rust. It’s easier to install and to navigate, and has no dependencies as it is distributed as a binary managed by starkliup. The tool supports associating Braavos or Agente X smart wallets for better developer experience and has embedded support for RPC endpoints to stop relying on the Sequencer’s Gateway.

# Cheat sheet

# Wallet folder structure
.
├── account.json
├── envars.sh
└── keystore.json

# Environment variables
export STARKNET_RPC=https://starknet-goerli.g.alchemy.com/v2/V0WI...
export STARKNET_ACCOUNT=~/.starkli-wallets/deployer/account.json
export STARKNET_KEYSTORE=~/.starkli-wallets/deployer/keystore.json


# Wallet commands

# Create a keystore file from an existing private key
starkli signer keystore from-key <FILE>

# Check the public key of an existing keystore file
starkli signer keystore inspect <FILE>

# Get contract class hash deployed at a certain address
starkli class-hash-at <ADDRESS>


# Starknet commands

# Declare a contract class
starkli declare <FILE>

# Calculate class hash from any contract artifacts
starkli class-hash <FILE>

# Deploy contract via the Universal Deployer Contract
starkli deploy <CLASS_HASH> [CTOR_ARGS]

# Call contract functions without sending transactions
starkli call <CONTRACT_ADDRESS> <SELECTOR> [ARGS]

# Send an invoke transaction from an account contract (single call)
starkli invoke <CONTRACT_ADDRESS> <SELECTOR> [ARGS]

Starkli is a new Command Line Interface (CLI) for Starknet created by @xJonathanLEI, written in Rust and distributed as a binary. Starkli is an alternative to cairo-lang, the original Starknet CLI developed by StarkWare, but it is easier to install, is better designed and has more functionality.

In this tutorial I’ll show you how to install Starkli, how to use it to declare, deploy and interact with a smart contract, and how to integrate it with Braavos or Argent X for a better developer experience.

Installation

Although the tool is built with Rust, you don’t need to have Rust installed on your computer to use it as Starkli provides a library manager called starkliup that allows you to fetch and install the latest released binary. This is similar to Rust where rustup is used to install, manage and upgrade rustc.

To install starkliup, simply run:

~ $ curl https://get.starkli.sh | sh
>>>
...
Run '. /home/parallels/.starkli/env' or start a new terminal session to use starkliup.
...

As the message suggests, we can either restart the terminal or execute the suggested command to activate the binary in our path.

~ $ . /home/parallels/.starkli/env
~ $ starkliup --help
>>>

starkliup (2023-06-25)
The installer for starkli.
...

Now with starkliup installed we can use it to install starkli.

~ $ starkliup
>>>

Installing the latest version of starkli...
...
Note that shell completions might not work until you start a new session.
Installation successfully completed.

Starkliup worked and now, to activate autocomplete we should restart the terminal as the message suggests. Once restarted, starkli should now be available in our terminal.

~ $ starkli --version
>>>
starkli 0.1.2 (4f1912d)

Creating a Wallet for the CLI

Declaring, deploying and invoking a smart contract with the CLI requires paying for gas fees and paying for gas fees requires a smart wallet.

Because we might have multiple smart wallets connected to Starkli in the future, I’m going to start by creating a folder in my home directory to store them all in a single place.

~ $ mkdir ~/.starkli-wallets

For each smart wallet we will have two files, a signer and an account descriptor. For this reason, I’ll create a new folder called deployer to store those files we are going to need for our smart wallet.

~ $ mkdir ~/.starkli-wallets/deployer

Starkli, much like cairo-lang, provides a way to create these files and thus a smart wallet associated with the tool, to pay for gas fees for transactions like declare, deploy_account and invoke.

The snippet below shows how to create a smart wallet that follows Open Zeppelin’s (OZ) signing model that uses the STARK friendly elliptic curve.

~ $ starkli account oz init --help
>>>
Create a new account configuration without actually deploying

Once created, this account will need to be deployed and funded with ETH following the “counterfactual” deployment procedure (fund then deploy).

~ $ starkli account deploy --help
>>>
Deploy account contract with a DeployAccount transaction

The problem with this approach is twofold. First, as the network evolves, the smart contract behind the smart wallet will need to be manually updated and two, tracking how much ETH you have left in the wallet involves interacting with a block explorer which is not a great developer experience.

A better approach would be to use a smart wallet created with Braavos or Argent X so we can use their UI to easily upgrade the implementation when needed and to track how much ETH we have left.

For this tutorial I’ll be using Braavos but the same can be done with Argent X.

Integrating a Braavos Smart Wallet

The first step for this integration is to create a new smart wallet using the Braavos browser extension that we will use ONLY for declaring and deploying smart contracts with Starkli. Do not reuse the same wallet where you hold valuable tokens or NFTs as we will be exposing the private key in order to import it to Starkli. This is a security risk that you need to be fully aware of.

Once your smart wallet has been created, funded with some ETH and deployed to Starknet’s testnet for this tutorial, it’s time to export the private key.

Exporting the public and private key from Braavos

Note: I’m no longer using this wallet so feel free to steal my private key ;)

Once we have taken note of the private key of our smart wallet, it’s time to create a signer for Starkli that uses that same private key.

Creating a Signer

Starkli allows us to create a special file known as a keystore that will act as the signer for our transactions. The main benefit of using a keystore is that our private key will not be stored in plain text in our computer. Instead, a password will be requested to create an encrypted file in the location of our choosing.

In our case, I’m going to request the CLI to create the keystore inside the folder we created for our deployer smart wallet.

~ $ starkli signer keystore from-key \
~/.starkli-wallets/deployer/keystore.json
>>>

Enter private key:
Enter password:
Created new encrypted keystore file: /home/parallels/.starkli-wallets/deployer/keystore.json
Public key: 0x0550…

Knowing the private key of a smart wallet is only half of the requirements to sign transactions. We also need to inform Starkli how our smart wallet created by Braavos or Argent X expects us to sign transactions. Is it an elliptic curve? If so, which one? That’s why we need an account descriptor file.

Creating an Account

Starkli provides a command to gather all the required information from a smart wallet by providing its on-chain address. With this information, the CLI creates a json file that can be used to sign transactions.

~ $ starkli account fetch --help
>>>

Fetch account config from an already deployed account contract
Usage: starkli account fetch [OPTIONS] <ADDRESS>

Unfortunately, this command doesn’t yet support Braavos or Argent X smart wallets. There is however an open PR that will add this functionality but in the meantime, we are going to have to create the file manually.

Update: The PRs have been merged and the fetch command is now supported. You can use it and skip to the next section of this tutorial.

~ $ touch ~/.starkli-wallets/deployer/account.json

The json file to describe the account needs to follow this structure:

{
"version": 1,
"variant": {
"type": "open_zeppelin",
"version": 1,
"public_key": "<SMART_WALLET_PUBLIC_KEY>"
},
"deployment": {
"status": "deployed",
"class_hash": "<SMART_WALLET_CLASS_HASH>",
"address": "<SMART_WALLET_ADDRESS>"
}
}

The class hash from the smart wallet can be found on a block explorer using the address or by using Starkli’s command class-hash-at , while the public key can be obtained from the keystore file we just created using Starkli.

~ $ starkli signer keystore inspect \
~/.starkli-wallets/deployer/keystore.json
>>>

Enter password:
Public key: 0x0550…

Note that the CLI asks for the encryption password in order to decrypt the file and derive the public key.

Looking again at the account file, you might be wondering, why is the type defined as “open_zeppelin” if it is a Braavos wallet? This value informs the CLI to sign transactions using Open Zeppelin’s default algorithm that just happens to be the same algorithm used by Braavos and Argent X by default.

Keep in mind that if you try to use a Braavos smart wallet with a hardware signer enabled, this will not work because a hardware signer uses a different elliptic curve to sign transactions.

Setting the Environment Variables

For most of the flags of the different commands available on Starkli we can define environment variables to make the commands shorter and easier to manage.

Because most of these commands are related to the smart wallet we want to use with Starkli, I’m going to create a file called envars.sh inside the deployer folder so I can easily source them anytime I want to use that smart wallet.

~ $ touch ~/.starkli-wallets/deployer/envars.sh

There are four main environment variables that you can set before using the CLI: the RPC provider (Infura, Alchemy or your own Starknet node), the location of the keystore file for the signer and the location of the account descriptor file.

# ~/.starkli-wallets/deployer/envars.sh
export STARKNET_RPC=https://starknet-goerli.g.alchemy.com/v2/V0WI...
export STARKNET_ACCOUNT=~/.starkli-wallets/deployer/account.json
export STARKNET_KEYSTORE=~/.starkli-wallets/deployer/keystore.json

Notice that we don’t need to specify the network explicitly because the RPC is already configured for testnet. The same is true for Infura.

To activate the environment variables related to our deployer smart wallet, we’ll need to source the file.

~ $ source ~/.starkli-wallets/deployer/envars.sh

Setting environment variables for Starkli will greatly simplify the commands we will use.

Declaring a Smart Contract

To test that we are able to sign transactions with Starkli I’m going to clone a sample Starknet smart contract written using the latest syntax for Starknet.

~ $ git clone https://github.com/starknet-edu/ownable_v2.git ownable

Before declaring the smart contract, we will need to compile it to Sierra using Scarb. You can find Scarb installation instructions here. The version of Scarb I’m using for this tutorial is shown below.

~ $ scarb --version
>>>
scarb 0.5.1 (798acce7f 2023–07–05)
cairo: 2.0.1 (https://crates.io/crates/cairo-lang-compiler/2.0.1)

Note: Make sure you use Scarb 0.5.1 at most as it includes the latest compiler supported by the testnet at the moment. Trying to use a newer version of Scarb that includes a newer version of the Cairo compiler not yet supported by the testnet will prevent you from declaring smart contracts in that network.

To compile the smart contract, we’ll need to first get into the project folder.

~ $ cd ownable
ownable $ scarb build

Let’s take a look at how the declare command should be used.

ownable $ starkli declare --help
>>>

Declare a contract class

Usage: starkli declare [OPTIONS] - account <ACCOUNT> <FILE>

Arguments:
<FILE> Path to contract artifact file

Because we already configure the account file as an environment variable, we can simply point to the compiled file from the Ownable smart contract.

ownable $ starkli declare target/dev/cairo1_v2_Ownable.sierra.json
>>>

Enter keystore password:

Class hash declared:
0x026c2f8b0cfdd268eee73581427fa723b7d245c90df30305966e5e197b2adaa6

To execute the command, Starkli will ask you for the encryption password of the keystore file and it returns, among other things, the class hash of the declared contract. If you forgot to take note of the returned class hash, you can recalculate it with the command shown below as it’ll be needed for deployment.

ownable $ starkli class-hash target/dev/cairo1_v2_Ownable.sierra.json
>>>
0x026c2f8b0cfdd268eee73581427fa723b7d245c90df30305966e5e197b2adaa6

Deploying a Smart Contract

The deploy command is a little simpler compared to declare as we can see from the command help.

ownable $ starkli deploy --help
>>>

Deploy contract via the Universal Deployer Contract

Usage: starkli deploy [OPTIONS] - account <ACCOUNT> <CLASS_HASH> [CTOR_ARGS]…

Arguments:
<CLASS_HASH> Class hash
[CTOR_ARGS]… Raw constructor arguments

To deploy we need to pass the class hash of our smart contract (0x026c…) and any argument that our constructor is expecting. In our case, the constructor is expecting an address to assign as the owner so we will pass the address of our deployer smart wallet (0x0416…).

ownable $ starkli deploy \
0x026c2f8b0cfdd268eee73581427fa723b7d245c90df30305966e5e197b2adaa6 \
0x04165454fbd4cb8a5aa68daeac101f9e7dd3e1dbff4691d9d9b3d6fb4b299cf0
>>>


The contract will be deployed at address 0x040da00a7fe2a1eee5392c33cb153bea9e3ed83878cc50ef16f9176b5f845cc8

The command works and our smart contract is successfully deployed.

Interacting with the Smart Contract

As usual, we can interact with our smart contract in two ways: call for read only functions and invoke for write functions that modify the state.

The call command is pretty straightforward to use.

ownable $ starkli call --help
>>>

Call contract functions without sending transactions

Usage: starkli call [OPTIONS] <CONTRACT_ADDRESS> <SELECTOR> [CALLDATA]…

Arguments:
<CONTRACT_ADDRESS> Contract address
<SELECTOR> Name of the function being called
[CALLDATA]… Raw function call arguments

Our smart contract has a single function that can be queried with a call command, get_owner. This function doesn’t expect any argument and returns the address of the current owner of the smart contract

ownable $ starkli call \
0x040da00a7fe2a1eee5392c33cb153bea9e3ed83878cc50ef16f9176b5f845cc8 \
get_owner
>>>


[
"0x04165454fbd4cb8a5aa68daeac101f9e7dd3e1dbff4691d9d9b3d6fb4b299cf0"
]

As you can see, it is returning the address that we passed to the constructor during deployment that corresponds to the Braavos smart wallet integrated to Starkli that we use as our deployer (0x0416…).

The invoke command requires additional flags because we need to pay for gas fees to execute a transaction.

ownable $ starkli invoke --help
>>>

Send an invoke transaction from an account contract

Usage: starkli invoke [OPTIONS] - account <ACCOUNT> [CALLS]…

Arguments:

[CALLS]… One or more contract calls. See documentation for more details

We are going to invoke the function transfer_ownership to transfer ownership of the smart contract from our deployer address (0x0416…) to another smart wallet address that I also control (0x0110…).

ownable $ starkli invoke \
0x040da00a7fe2a1eee5392c33cb153bea9e3ed83878cc50ef16f9176b5f845cc8 \
transfer_ownership \
0x011088d3cbe4289bc6750ee3a9cf35e52f4fa4e0ac9f42fb0b62e983139e135a

Once the transaction gets accepted on L2, we can verify the state transition by calling the function get_owner again to verify that we get the new wallet address.

ownable $ starkli call \
0x040da00a7fe2a1eee5392c33cb153bea9e3ed83878cc50ef16f9176b5f845cc8 \
get_owner
>>>


[
"0x011088d3cbe4289bc6750ee3a9cf35e52f4fa4e0ac9f42fb0b62e983139e135a"
]

The smart contract indeed has a new owner.

Conclusion

Starkli is for me a much better alternative to cairo-lang. The way the commands are grouped and structured is very intuitive and the fact that there’s no dependencies for its installation (besides starkliup of course) makes it easier to get started.

The ability to associate Braavos and Argent X wallets into the CLI is a big win for UX and, even though formal documentation doesn’t yet exist, the embedded help has all the information you need to understand how to use each command.

Starkli also supports using an RPC for all of its commands, making it easier to rely on your own full node or on an infrastructure provider like Infura or Alchemy. This removes a direct dependency with the Sequencer’s Gateway as we prepare for decentralizing Starknet.

Special thanks to @xJonathanLEI for frantically working on solving any minor Starkli issue I found while writing this article and to @glihm for teaching me how to properly use the tool.

--

--

David Barreto
Starknet Edu

Starknet Developer Advocate. Find me on Twitter as @barretodavid