StarkNet/Cairo development toolbelt
Project description
OpenZeppelin Nile ⛵
Navigate your StarkNet projects written in Cairo.
Getting started
Create a folder for your project and cd
into it:
mkdir myproject
cd myproject
Create a virtualenv and activate it:
python3 -m venv env
source env/bin/activate
Install nile
:
pip install cairo-nile
Use nile
to quickly set up your development environment:
nile init
...
✨ Cairo successfully installed!
...
🗄 Creating project directory tree
⛵️ Nile project ready! Try running:
Usage
node
Run a local starknet-devnet
node:
nile node [--host HOST] [--port PORT] [--seed SEED] [--lite_mode]
optional arguments:
--host HOST Specify the address to listen at; defaults to
127.0.0.1 (use the address the program outputs on
start)
--port PORT Specify the port to listen at; defaults to 5050
--seed SEED Specify the seed for randomness of accounts to be
deployed
--lite-mode Applies all lite-mode optimizations by disabling
features such as block hash and deploy hash
calculation
nile node
Account #0
Address: 0x877b050406a54adb5940227e51265a201e467e520ca85dc7f024abd03dcc61
Public key: 0x256b8dc218586160ef80d3454a7cd51046271fbf091bd6779e3513304f22156
Private key: 0xb204ff062d85674b467789f07826bb2
...
Initial balance of each account: 1000000000000000000000 WEI
Seed to replicate this account sequence: 2128506880
WARNING: Use these accounts and their keys ONLY for local testing. DO NOT use them on mainnet or other live networks because you will LOSE FUNDS.
* Listening on http://127.0.0.1:5050/ (Press CTRL+C to quit)
compile
Compile Cairo contracts. Compilation artifacts are written into the artifacts/
directory.
nile compile # compiles all contracts under contracts/
nile compile --directory my_contracts # compiles all contracts under my_contracts/
nile compile contracts/MyContract.cairo # compiles single contract
nile compile contracts/MyContract.cairo --disable-hint-validation # compiles single contract with unwhitelisted hints
As of cairo-lang v0.8.0, account contracts (contracts with the __execute__
method) must be compiled with the --account_contract
flag. Nile automatically inserts the flag if the contract's name ends with Account
i.e. Account.cairo, EthAccount.cairo. Otherwise, the flag must be included by the user.
nile compile contracts/NewAccountType.cairo --account_contract # compiles account contract
Example output:
$ nile compile
Creating artifacts/abis/ to store compilation artifacts
🤖 Compiling all Cairo contracts in the contracts/ directory
🔨 Compiling contracts/Account.cairo
🔨 Compiling contracts/Initializable.cairo
🔨 Compiling contracts/Ownable.cairo
✅ Done
deploy
NOTICE: this method doesn't use an account, which will be deprecated very soon as StarkNet makes deployments from accounts mandatory.
nile deploy contract --alias my_contract
🚀 Deploying contract
🌕 artifacts/contract.json successfully deployed to 0x07ec10eb0758f7b1bc5aed0d5b4d30db0ab3c087eba85d60858be46c1a5e4680
📦 Registering deployment as my_contract in localhost.deployments.txt
A few things to notice here:
nile deploy <contract_name>
looks for an artifact with the same name- This created a
localhost.deployments.txt
file storing all data related to my deployment - The
--alias
parameter lets me create a unique identifier for future interactions, if no alias is set then the contract's address can be used as identifier - By default Nile works on local, but you can use the
--network
parameter to interact withmainnet
,goerli
, and the defaultlocalhost
. - By default, the ABI corresponding to the contract will be registered with the deployment. To register a different ABI file, use the
--abi
parameter.
setup
Deploy an Account associated with a given private key.
To avoid accidentally leaking private keys, this command takes an alias instead of the actual private key. This alias is associated with an environmental variable of the same name, whose value is the actual private key.
You can find an example .env
file in example.env
. These are private keys only to be used for testing and never in production.
nile setup <private_key_alias>
🚀 Deploying Account
⏳ ️Deployment of Account successfully sent at 0x07db6b52c8ab888183277bc6411c400136fe566c0eebfb96fffa559b2e60e794
🧾 Transaction hash: 0x17
📦 Registering deployment as account-0 in localhost.deployments.txt
A few things to note here:
nile setup <private_key_alias>
looks for an environment variable with the name of the private key alias- This creates or updates
localhost.accounts.json
file storing all data related to accounts management - The creates or updates
localhost.deployments.txt
file storing all data related to deployments
send
Execute a transaction through the Account
associated with the private key provided. The syntax is:
nile send <private_key_alias> <contract_identifier> <method> [PARAM_1, PARAM2...]
For example:
nile send <private_key_alias> ownable0 transfer_ownership 0x07db6...60e794
Invoke transaction was sent.
Contract address: 0x07db6b52c8ab888183277bc6411c400136fe566c0eebfb96fffa559b2e60e794
Transaction hash: 0x1c
Some things to note:
- This sends the transaction to the network by default, but you can use the
--estimate_fee
flag to estimate the fee without sending the transaction, or the--simulate
flag to get a traceback of the simulated execution. max_fee
defaults to0
. Add--max_fee <max_fee>
to set the maximum fee for the transactionnetwork
defaults to thelocalhost
. Add--network <network>
to change the network for the transaction
declare
Very similar to send
, but for declaring a contract based on its name through an account.
nile declare <private_key_alias> contract --alias my_contract
🚀 Declaring contract
⏳ Successfully sent declaration of contract as 0x07ec10eb0758f7b1bc5aed0d5b4d30db0ab3c087eba85d60858be46c1a5e4680
🧾 Transaction hash: 0x7222604b048632326f6a016ccb16fbdea7e926cd9e2354544800667a970aee4
📦 Registering declaration as my_contract in localhost.declarations.txt
A few things to notice here:
nile declare <private_key_alias> <contract_name>
looks for an artifact with name<contract_name>
- This creates or updates a
localhost.declarations.txt
file storing all data related to your declarations - The
--alias
parameter lets you create a unique identifier for future interactions, if no alias is set then the contract's address can be used as identifier - By default Nile works on local, but you can use the
--network
parameter to interact withmainnet
,goerli
, and the defaultlocalhost
.
call
Using call
, we can perform read operations against our local node or the specified public network. The syntax is:
nile call <contract_identifier> <method> [PARAM_1, PARAM2...]
Where <contract_identifier>
is either our contract address or alias, as defined on deploy
.
nile call my_contract get_balance
1
Please note:
network
defaults to thelocalhost
. Add--network <network>
to change the network for the transaction
run
Execute a script in the context of Nile. The script must implement a run(nre)
function to receive a NileRuntimeEnvironment
object exposing Nile's scripting API.
# path/to/script.py
def run(nre):
address, abi = nre.deploy("contract", alias="my_contract")
print(abi, address)
Then run the script:
nile run path/to/script.py
Please note:
localhost
is the default network. Add--network <network>
to change the network for the script
clean
Deletes the artifacts/
directory for a fresh start ❄️
nile clean
🚮 Deleting localhost.deployments.txt
🚮 Deleting artifacts directory
✨ Workspace clean, keep going!
version
Print out the Nile version
nile version
debug
Use locally available contracts to make error messages from rejected transactions more explicit.
nile debug <transaction_hash> [CONTRACTS_FILE, NETWORK]
For example, this transaction returns the very cryptic error message:
An ASSERT_EQ instruction failed: 0 != 1.
starknet tx_status \
--hash 0x57d2d844923f9fe5ef54ed7084df61f926b9a2a24eb5d7e46c8f6dbcd4baafe \
--error_message
[...]
Error in the called contract (0x5bf05eece944b360ff0098eb9288e49bd0007e5a9ed80aefcb740e680e67ea4):
Error at pc=0:1360:
An ASSERT_EQ instruction failed: 0 != 1.
Cairo traceback (most recent call last):
Unknown location (pc=0:1384)
Unknown location (pc=0:1369)
This can be made more explicit with:
nile debug 0x57d2d844923f9fe5ef54ed7084df61f926b9a2a24eb5d7e46c8f6dbcd4baafe
⏳ Querying the network to check transaction status and identify contracts...
🧾 Found contracts: ['0x05bf05eece944b360ff0098eb9288e49bd0007e5a9ed80aefcb740e680e67ea4:artifacts/Evaluator.json']
⏳ Querying the network with contracts...
🧾 Error message:
[...]
Error in the called contract (0x5bf05eece944b360ff0098eb9288e49bd0007e5a9ed80aefcb740e680e67ea4):
[path_to_file]:179:5: Error at pc=0:1360:
assert permission = 1
^*******************^
An ASSERT_EQ instruction failed: 0 != 1.
Cairo traceback (most recent call last):
[path_to_file]:184:6
func set_teacher{
^*********^
[path_to_file]:189:5
only_teacher()
^************^
In case of pending transaction states, the command will offer to continue probing the network unless it is terminated prematurely. This example also shows how accepted transactions are handled.
⏳ Querying the network to check transaction status and identify contracts...
🕒 Transaction status: NOT_RECEIVED. Trying again in a moment...
🕒 Transaction status: RECEIVED. Trying again in a moment...
🕒 Transaction status: PENDING. Trying again in a moment...
✅ Transaction status: ACCEPTED_ON_L2. No error in transaction.
Finally, the command will use the local network.deployments.txt
files to fetch the available contracts.
However, it is also possible to override this by passing a CONTRACTS_FILE
argument, formatted as:
CONTRACT_ADDRESS1:PATH_TO_COMPILED_CONTRACT1.json
CONTRACT_ADDRESS2:PATH_TO_COMPILED_CONTRACT2.json
...
get-accounts
Retrieves a list of ready-to-use accounts which allows for easy scripting integration. Before using get-accounts
:
-
store private keys in a
.env
PRIVATE_KEY_ALIAS_1=286426666527820764590699050992975838532 PRIVATE_KEY_ALIAS_2=263637040172279991633704324379452721903 PRIVATE_KEY_ALIAS_3=325047780196174231475632140485641889884
-
deploy accounts with the keys therefrom like this:
nile setup PRIVATE_KEY_ALIAS_1 ... nile setup PRIVATE_KEY_ALIAS_2 ... nile setup PRIVATE_KEY_ALIAS_3 ...
Next, write a script and call get-accounts
to retrieve and use the deployed accounts.
def run(nre):
# fetch the list of deployed accounts
accounts = nre.get_accounts()
# then
accounts[0].send(...)
# or
alice, bob, *_ = accounts
alice.send(...)
bob.send(...)
Please note that the list of accounts includes only those that exist in the local
<network>.accounts.json
file. In a recent release we added a flag to the command, to get predeployed accounts if the network you are connected to is a starknet-devnet instance.
get-accounts --predeployed (only starknet-devnet)
This flag retrieves the predeployed accounts if the network you are connecting to is a starknet-devnet instance.
You can use it either from the cli:
nile get-accounts --predeployed
Or from the nile runtime environment for scripting:
def run(nre):
# fetch the list of pre-deployed accounts from devnet
accounts = nre.get_accounts(predeployed=True)
# then
accounts[0].send(...)
# or
alice, bob, *_ = accounts
alice.send(...)
bob.send(...)
get-nonce
Retrieves the nonce for the given contract address (usually an account).
nile get-nonce <contract_address>
get_declaration
(NRE only)
Return the hash of a declared class. This can be useful in scenarios where a contract class is already declared with an alias prior to running a script.
def run(nre):
predeclared_class = nre.get_declaration("alias")
Note that this command is only available in the context of scripting in the Nile Runtime Environment.
Short string literals
From cairo-lang docs: A short string is a string whose length is at most 31 characters, and therefore can fit into a single field element.
In Nile, arguments to contract calls (calldata) that are neither int nor hex, are treated as short strings and converted automatically to the corresponding felt representation. Because of this, you can run the following from the CLI:
nile deploy MyToken 'MyToken name' 'MyToken symbol' (...)
And this is equivalent to passing the felt representation directly like this:
nile deploy MyToken 0x4d79546f6b656e206e616d65 0x4d79546f6b656e2073796d626f6c (...)
Note that if you want to pass the token name as a hex or an int, you need to provide the felt representation directly because these values are not interpreted as short strings. You can open a python terminal, and import and use the str_to_felt
util like this:
>>> from nile.utils import str_to_felt
>>>
>>> str_to_felt('any string')
460107418789485453340263
Extending Nile with plugins
Nile has the possibility of extending its CLI and NileRuntimeEnvironment
functionalities through plugins. For developing plugins for Nile fork this plugin example boilerplate and implement your desired functionality with the provided instructions.
How it works
This implementation takes advantage of the native extensibility features of click. Using click and leveraging the Python entrypoints we have a simple manner of handling extension natively on Python environments through dependencies. The plugin implementation on Nile looks for specific Python entrypoints constraints for adding commands.
In order for this implementation to be functional, it is needed by the plugin developer to follow some development guidelines defined in this simple plugin example extending Nile for a dummy greet extension. In a brief explanation the guidelines are as follows:
-
Define a Python module that implements a click command or group:
# First, import click dependency import click # Decorate the method that will be the command name with `click.command` @click.command() # You can define custom parameters as defined in `click`: https://click.palletsprojects.com/en/7.x/options/ def my_command(): # Help message to show with the command """ Subcommand plugin that does something. """ # Done! Now implement your custom functionality in the command click.echo("I'm a plugin overriding a command!")
-
Define the plugin entrypoint. In this case using Poetry features in the pyproject.toml file:
# We need to specify that click commands are Poetry entrypoints of type `nile_plugins`. Do not modify this [tool.poetry.plugins."nile_plugins"] # Here you specify you command name and location <command_name> = <package_method_location> "greet" = "nile_greet.main.greet"
-
Done!
How to decide if I want to use a plugin or not? Just install / uninstall the plugin dependency from your project :smile:
Finally, after the desired plugin is installed, it will also be automatically available through the nre
. The plugin developer should be aware of this and design the interface accordingly.
Contribute
OpenZeppelin Nile exists thanks to its contributors. There are many ways you can participate and help build high quality software. Check out the contribution guide!
Hacking on Nile
Nile uses tox to manage development tasks. Here are some hints to play with the source code:
- Install a development version of the package with
python -m pip install .
- Install tox for development tasks with
python -m pip install tox
- Get a list of available tasks with
tox -av
- Build the package with
tox -e build
- Format all files with
tox -e format
- Check files formatting with
tox -e lint
Testing
To run tests:
- Install testing dependencies with
python -m pip install .[testing]
- Run all tests with
tox
- Run unit tests only with
tox -e unit
- To run a specific set of tests, point to a module and/or function, e.g.
tox tests/test_module.py::test_function
- Other
pytest
flags must be preceded by--
, e.g.tox -- --pdb
to runtests in debug mode
License
Nile is released under the MIT License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for cairo_nile-0.10.0-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | e4a03bedfd6d173e860c1fb5cce97f4af913933b7d12f09d0c890df970bae8b8 |
|
MD5 | 8ba5a6e1ae6feb10cc19c87c142570f8 |
|
BLAKE2b-256 | 275a80f450d2c5854310dc18e22675a2ef1bd73a61ed5e5b54465288d53ccc2a |