In this workshop you will learn how to create an account contract with a single signer that uses the STARK-friendly elliptic curve to sign transactions. The final code is inspired by Open Zeppelin’s account contract.
After completing each step, run the associated script to verify it has been implemented correctly.
- Install Scarb 2.3.1 with
asdf
(instructions) - Install Starknet Foundry v0.10.2 (instructions)
curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh
snfoundryup -v 0.10.2
- Clone this repository
- Create a new file called
account.cairo
inside thesrc
folder. - Copy the following code into the file.
#[starknet::contract]
mod Account {
#[storage]
struct Storage {}
}
- Run
scarb build
to verify the project is setup correctly
Note: You'll be working on the
account.cairo
file to complete the requirements of each step. The fileprev_solution.cairo
will show up in future steps as a way to catch up with the workshop if you fall behind. Don't modify that file.
Checkout the step1
branch to enable the verification tests for this section.
git switch step1
Collect the public_key
associated with a signer that is passed to the constructor
, and make it public through a function also called public_key
.
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test
- A
public_key
is defined with a single felt
Checkout the step2
branch to enable the verification tests for this section.
git switch step2
If you fell behind, the file prev_solution.cairo
contains the solution to the previous step.
Implement the function is_valid_signature
as defined by the SNIP-6 standard.
- If the signature was created by the signer associated with the account contract the function should return the short string
'VALID'
. - If the signature was created by a signer not associated with the account contract, the function should return any other felt that is not the short string
'VALID'
.
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test
- Use the stored
public_key
to check the signature. - A "short string" is just an ascii representation of a single felt.
- You can check signatures on the STARK-friendly curve with the syscall
check_ecdsa_signature
available in theecdsa
module. - The short string
'VALID'
can be hardcoded or read from the modulestarknet::VALIDATED
.
Checkout the step3
branch to enable the verification tests for this section.
git switch step3
If you fell behind, the file prev_solution.cairo
contains the solution to the previous step.
Implement the function __validate__
as defined by the SNIP-6 standard. This function is similar to is_valid_signature
but instead of expecting the signature to be passed as an argument it verifies the transaction's signature.
- If the transaction signature was created by the signer associated with the account contract the function should return the short string
'VALID'
. - If the transaction signature was created by a signer not associated with the account contract, the transaction should be halted and reverted with an error message.
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test
- You can read the transaction details which includes the transaction signature using the syscall
get_tx_info
from thestarknet
module. - You can stop and revert a transaction with an error message using the
assert
function. - The
Call
struct can be found in the modulestarknet::account
.
Checkout the step4
branch to enable the verification tests for this section.
git switch step4
If you fell behind, the file prev_solution.cairo
contains the solution to the previous step.
Protect the __validate__
function by making it callable only by the protocol which uses the zero address.
- If the function is invoked by any other address, the transaction should be halted and reverted with an error message.
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test
- You can read who the caller is by using the syscall
get_caller_address
available in thestarknet
module.
Checkout the step5
branch to enable the verification tests for this section.
git switch step5
If you fell behind, the file prev_solution.cairo
contains the solution to the previous step.
Implement the functions __validate_declare__
and __validate_deploy__
with the exact same logic as __validate__
and make them publicly accessible. The signature of both functions is shown below.
fn __validate_declare__(
self: @ContractState,
class_hash: felt252
) -> felt252
fn __validate_deploy__(
self: @ContractState,
class_hash: felt252,
salt: felt252,
public_key: felt252
) -> felt252
- The return value of both functions is the same as
__validate__
('VALID'
or halted transaction). - Both functions should only be callable by the Starknet protocol (same as
__validate__
).
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test
- Create a private function to encapsulate the logic of
__validate__
so it can be reused by__validate_declare__
and__validate_deploy__
. - By grouping private functions into its own trait they can be called as methods of
self
and the smart contract state doesn’t need to be explicitly passed. - You can auto generate a trait from an implementation using the attribute
generate_trait
.
Checkout the step6
branch to enable the verification tests for this section.
git switch step6
If you fell behind, the file prev_solution.cairo
contains the solution to the previous step.
Implement the function __execute__
as defined by the SNIP-6 standard.
- The function should be able to handle a single contract call or multiple contract calls in sequence.
- The result of each call should be collected and returned as an array.
- If an empty array of calls is passed, the function should halt and revert the transaction.
- The function should only be called by the protocol (the zero address).
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test
- You can call other contracts using the low level syscall
call_contract_syscall
available in thestarknet
module. - You can iterate over an array by using the
loop
keyword and the array methodpop_front
.
Checkout the step7
branch to enable the verification tests for this section.
git switch step7
If you fell behind, the file prev_solution.cairo
contains the solution to the previous step.
Implement the function supports_interface
from the SNIP-5 standard for the SNIP-6 interface.
- When providing the
interface_id
of the SNIP-6 trait the function should returntrue
. - When providing any other value for
interface_id
the function should returnfalse
.
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test
- The
interface_id
of the SNIP-6 trait is1270010605630597976495846281167968799381097569185364931397797212080166453709
Checkout the step8
branch to enable the verification tests for this section.
git switch step8
If you fell behind, the file prev_solution.cairo
contains the solution to the previous step.
Limit execution of the functions __execute__
, __validate__
, __validate_declare__
and __validate_deploy__
to transactions of the latest version.
- Attempting to execute an
invoke
,declare
, anddeploy_account
transaction that is not of the latest version should result in the transaction being halted and reverted. - Simulated transactions should be supported.
When completed, execute the test suite to verify you've met all the requirements for this section.
scarb test
- Simulated transactions use the same version as their real counterpart but offset by
2^128
.
Checkout the step9
branch to enable the verification tests for this section.
git switch step9
If you fell behind, the file prev_solution.cairo
contains the solution to the previous step.
Check that you have correctly created an account contract for Starknet by running the full test suite:
scarb test
If the test suite passes, congratulations, you have created your first custom Starknet account contract thanks to account abstraction.
You can deploy your account contract to Starknet testnet in two ways, using Starkli (CLI) or using Starknet.js (SDK).
Using Starkli to deploy an account contract is a "manual" process but you can follow this tutorial to learn how to do it. On the other hand, with an SDK like Starknet.js you can automate the process of declaring, deploying and testing an account contract.
The following bonus steps will show you how to configure and use the deploy.ts
script found in the scripts
folder to deploy your account contract to Starknet testnet.
Install the dependencies required to run the deployer script.
- Install Nodejs 20 or higher on your computer. You can use
asdf
for that too. - Once you have Nodejs, install the script dependencies by running
npm install
from the project's root folder.
Create a wallet that the script can use to pay for the declaration of your account contract.
- Create a wallet on Starknet testnet using the Argent X or Braavos browser extension.
- Fund the wallet by using the Faucet or the Bridge.
- Create a file in the project's root folder called
.env
- Export the private key of the funded wallet and paste it in the
.env
file using the keyDEPLOYER_PRIVATE_KEY
.
DEPLOYER_PRIVATE_KEY=<YOUR_FUNDED_TESTNET_WALLET_PK>
Provide an RPC URL that the script can use to interact with Starknet testnet.
- Create an account on Infura.
- Create a new API Key for a Web3.
- Copy the RPC URL for Starknet's testnet.
- Paste the RPC URL in the
.env
file using the keyRPC_ENDPOINT
.
DEPLOYER_PRIVATE_KEY=<YOUR_FUNDED_TESTNET_WALLET_PK>
RPC_ENDPOINT=<YOUR_RPC_URL_FOR_STARKNET_TESTNET>
Run the script that will declare, deploy and use your account contract to send a small amount of ETH to another wallet as a test.
- From project's root folder run
npm run deploy
- Follow the instructions from the terminal
If the script finishes successfully your account contract is ready to be used on Starknet testnet.