Account Abstraction on Starknet, Part III
On part I of this series [1] I showed you the standards associated with Account Abstraction while on part II [2] I walked you through the creation of a new account contract from scratch. In this article, the last of the series, I’m going to show you how to use Starkli to deploy to testnet the account contract we created on part II and how to use it to interact with other smart contracts.
To follow along with this tutorial, make sure to have Starkli [3] and Scarb [4] installed.
# Cheatsheet: Steps to declare, deploy and use a custom account contract
# [1] Set environment variables in envars.sh
export STARKNET_KEYSTORE=~/.starkli-wallets/custom/keystore.json
export STARKNET_ACCOUNT=~/.starkli-wallets/custom/account.json
export STARKNET_RPC=https://starknet-goerli.g.alchemy.com/v2/V0WI...
# [2] Activate environment variables
source ~/.starkli-wallets/custom/envars.sh
# [3] Create keystore.json file
starkli signer keystore new ~/.starkli-wallets/custom/keystore.json
# [4] Create account.json file
starkli account oz init ~/.starkli-wallets/custom/account.json
# [5] Compile account contract
scarb build
# [6] Derive class hash
starkli class-hash target/dev/aa_Account.sierra.json
# [7] Update account.json file
{
...
"deployment": {
...
"class_hash": "<REAL_CLASS_HASH>",
...
}
}
# [8] Activate deployer wallet
source ~/.starkli-wallets/deployer/envars.sh
# [9] Declare account contract with deployer
starkli declare target/dev/aa_Account.sierra.json
# [10] Activate custom wallet
source ~/.starkli-wallets/custom/envars.sh
# [11] Deploy account contract
starkli account deploy ~/.starkli-wallets/custom/account.json
# [12] Send ETH to given address
# [13] Use account contract for testing
starkli invoke eth transfer 0x1234 u256:100
# [bonus] Suggested folder structure
.
├── account.json
├── envars.sh
└── keystore.json
Configuration Files
When dealing with an account contract, Starkli requires two configuration files: an encrypted file named keystore.json to store the associated private key and an unencrypted file called account.json that describes its public attributes like public key, class hash and address.
There’s an optional third file that can be created for each account contract to simplify all of Starkli’s commands and that is a shell file to source the environment variables expected by the tool. I normally call this file envars.sh.
Because I have multiple wallets associated with Starkli, and each one has three configuration files, I like to create a folder for each wallet to store the aforementioned files and then group them all in a single hidden folder called starkli-wallets in my home directory.
The proposed folder structure looks like this:
~ $ tree ~/.starkli-wallets
>>>
.starkli-wallets
├── wallet-a
│ ├── account.json
│ ├── envars.sh
│ └── keystore.json
└── wallet-b
├── account.json
├── envars.sh
└── keystore.json
For this tutorial, I’m going to create a new folder for my smart wallet called custom inside the starkli-wallets folder.
~ $ mkdir ~/.starkli-wallets/custom
We can now use Starkli to generate the two mandatory configuration files while we will manually create the environment variables file.
Creating a Keystore File
Starkli allows you to auto generate a private key, store it in a keystore.json file inside our custom folder and encrypt the file with a password all with a single command. The only argument expected by the command is the location to create the configuration file.
~ $ starkli signer keystore new ~/.starkli-wallets/custom/keystore.json
>>>
Enter password:
Created new encrypted keystore file: /Users/david/.starkli-wallets/custom/keystore.json
Public key: 0x01ad...
The command first asks you for an encryption password and then it creates the file. The location of our keystore.json file will be the first environment variable to set in our envars.sh file.
~ $ touch ~/.starkli-wallets/custom/envars.sh
#!/bin/bash
export STARKNET_KEYSTORE=~/.starkli-wallets/custom/keystore.json
To activate the environment variable in our terminal we can source it.
~ $ source ~/.starkli-wallets/custom/envars.sh
We can now create the other mandatory configuration file for our account contract: account.json.
Creating an Account File
Our account contract validates signatures in exactly the same way as OpenZeppelin’s (OZ) default account contract [5]. Both account contracts expect a single signer and use the STARK-friendly elliptic curve. For this reason we are going to use a Starkli command that’s normally reserved for an OZ account even though we have created ours from scratch.
~ $ starkli account oz init ~/.starkli-wallets/custom/account.json
>>>
Enter keystore password:
Created new account config file: /Users/david/.starkli-wallets/custom/account.json
Once deployed, this account will be available at:
0x07c4...
Deploy this account by running:
starkli account deploy /Users/david/.starkli-wallets/custom/account.json
Our account contract is similar to OZ but it’s not the same. This means that our account contract will have a different class hash than OZ’s implementation and because the class hash is used to calculate the deployment address, the value shown by the tool (0x07c4…) is not correct.
Let’s take a look at the content of the generated file account.json.
~ $ cat ~/.starkli-wallets/custom/account.json
>>>
{
"version": 1,
"variant": {
"type": "open_zeppelin",
"version": 1,
"public_key": "0x01ad...",
"legacy": true
},
"deployment": {
"status": "undeployed",
"class_hash": "0x48dd...",
"salt": "0x238a..."
}
}
First, notice that the property legacy is set to true by default. Starkli is assuming that we want to deploy the Cairo 0 version of OZ’s account contract (legacy) instead of its more modern counterpart written in Cairo. We need to manually set this flag to false so Starkli uses the right serialization mechanism when sending transactions to Starknet. If we leave this property with its default value, we will be able to deploy our account contract but we won’t be able to use it.
Second, and as mentioned before, the class hash defined in this configuration is OZ’s class hash, not ours. To find our class hash we will need to compile our project and use Starkli once again to find the real class hash.
Finding the Class Hash
If you have been following this tutorial series in order, you should already have a folder called aa (for Account Abstraction) inside your home folder where the Cairo code for our account contract is defined. If not, you can always clone the repository and continue from there.
~ $ git clone git@github.com:starknet-edu/aa-workshop.git aa
In order to compile our account contract to Sierra we need to first get into the project folder and then use Scarb.
~ $ cd aa
aa $ scarb build
>>>
Compiling aa v0.1.0 (/Users/david/aa/Scarb.toml)
Finished release target(s) in 2 seconds
The compiled version of our account contract now lives inside the target/dev folder of our project.
We can derive the class hash of our account contract using Starkli.
aa $ starkli class-hash target/dev/aa_Account.sierra.json
>>>
0x056bd...
Let’s now modify the account.json file of our account contract using the real class hash.
aa $ code ~/.starkli-wallets/custom/account.json
{
"version": 1,
"variant": {
"type": "open_zeppelin",
"version": 1,
"public_key": "0x01ad...",
"legacy": false
},
"deployment": {
"status": "undeployed",
"class_hash": "0x056bd...",
"salt": "0x238a..."
}
}
At this point our account.json file has all the correct information and we can proceed to create a new environment variable with its path.
aa $ code ~/.starkli-wallets/custom/envars.sh
#!/bin/bash
export STARKNET_KEYSTORE=~/.starkli-wallets/custom/keystore.json
export STARKNET_ACCOUNT=~/.starkli-wallets/custom/account.json
To activate the new environment variable in our terminal we can source the file.
aa $ source ~/.starkli-wallets/custom/envars.sh
Next step is to declare the account contract on testnet.
Configuring an RPC
Declaring an account contract means sending a transaction to Starknet and we can only do so through an RPC provider. I’ll be using Alchemy [6] but you can use Infura [7] or even your own node.
If you decide to use Alchemy or Infura, you’ll need to first create an account with them, create a project for Starknet Goerli, copy the RPC’s URL created for you and place it in the envars.sh file as an environment variable.
aa $ code ~/.starkli-wallets/custom/envars.sh
#!/bin/bash
export STARKNET_KEYSTORE=~/.starkli-wallets/custom/keystore.json
export STARKNET_ACCOUNT=~/.starkli-wallets/custom/account.json
export STARKNET_RPC=https://starknet-goerli.g.alchemy.com/v2/V0WI...
I’m not showing the full URL of my RPC endpoint to protect my API key.
Declaring an Account Contract
To declare our account contract we are going to need a different account contract that is already deployed and funded to pay for the associated gas fees.
In another article called “Starkli: The New Starknet CLI” [8] I showed how to configure Starkli to use either a Braavos or Argent X wallet as a deployer. To use this wallet with Starkli to declare our account contract, I’m going to source its envars.sh file.
aa $ source ~/.starkli-wallets/deployer/envars.sh
We can now declare our account contract by paying the gas fees with the deployer account.
aa $ starkli declare target/dev/aa_Account.sierra.json
>>>
Enter keystore password:
Sierra compiler version not specified. Attempting to automatically decide version to use...
Network detected: goerli-1. Using the default compiler version for this network: 2.2.0. Use the --compiler-version flag to choose a different version.
Declaring Cairo 1 class: 0x056bd...
Compiling Sierra class to CASM with compiler version 2.1.0...
CASM class hash: 0x05f5...
Contract declaration transaction: 0x07f1...
Class hash declared: 0x056bd...
Once the declare transaction gets to the state “Accepted on L2” we can reactivate the environment variables of our account contract to perform the counter factual deployment.
aa $ source ~/.starkli-wallets/custom/envars.sh
aa $ starkli account deploy ~/.starkli-wallets/custom/account.json
>>>
Enter keystore password:
The estimated account deployment fee is 0.000004330000051960 ETH. However, to avoid failure, fund at least:
0.000006495000077940 ETH
to the following address:
0x71d8...
Press [ENTER] once you've funded the address.
Starkli will wait for us until we send enough funds to the specified address where our account contract will be deployed so it can send the deploy_account transaction. We can use the faucet for that purpose [9].
Once the funds arrive at the address, we can click “Enter” to continue with the deployment process.
...
Account deployment transaction: 0x00b9...
Waiting for transaction 0x00b9... to confirm. If this process is interrupted, you will need to run `starkli account fetch` to update the account file.
Transaction 0x04b7... confirmed
Our account contract is now deployed to Starknet’s testnet and it’s ready to be used.
Using the Account Contract
An easy way to verify that our account contract is working is to use it to send 100 gwei to another wallet (0x1234) by sending an invoke transaction to execute the transfer function on the WETH smart contract on Starknet’s testnet [10].
Because interacting with the WETH smart contract is such a common operation, Starkli provides shorthands to make the command more compact as you can see below [11].
aa $ starkli invoke eth transfer 0x1234 u256:100
>>>
Enter keystore password:
Invoke transaction: 0x0321...
The transaction is executed successfully proving that our account contract was able to verify the transaction signature, execute our transfer action and pay for the associated gas fees.
Conclusion
As good as Starkli is at providing all the commands you need to deploy your own account contract it’s still a complex process that requires multiple steps. This is in part because deploying a custom account contract is not nearly as common as deploying a regular smart contract.
I was able to use Starkli for my account contract using the Open Zeppelin setting because I used the same signature for the constructor and __declare_deploy__ functions as OZ uses. Had I modified either of those function signatures I would not have been able to use Starkli to deploy my account contract. If that’s your case, you’ll need to use an SDK like starknet.js [12] instead of Starkli as those tools are more flexible but at the same time more complex to use.
Very soon we should see Argent and Braavos replacing their current account contracts written in Cairo 0 with a newer version using modern Cairo. This will of course require the user to upgrade their wallets once again in preparation for Regenesis [13].
Keep those wallets up to date.
References
[1] Account Abstraction on Starknet, Part I
[2] Account Abstraction on Starknet, Part II
[4] Scarb’s Installation Instructions
[5] Open Zeppelin Cairo Account Contract
[7] Infura’s Website
[8] Starkli: The New Starknet CLI
[10] WETH Address on Starkscan
[11] Starkli book: Simplifying invoke commands
[12] Starknet.js Website