Deploying contracts#
Declaring contracts#
A declare transaction can be issued in version 2 or 3. Contracts written in Cairo 0 cannot be declared while those written in Cairo 1 or higher should be declared with versions 2 or 3.
To sign a declare transaction, you should utilize the sign_declare_v2()
or sign_declare_v3()
method, respectively.
Here’s an example how to use it.
# Account.sign_declare_v2 and Account.sign_declare_v3 take string containing a compiled contract (sierra)
# and a class hash (casm_class_hash)
# They return DeclareV2 and DeclareV3 respectively
declare_transaction = await account.sign_declare_v3(
compiled_contract=compiled_contract,
compiled_class_hash=class_hash,
l1_resource_bounds=ResourceBounds(
max_amount=int(1e5), max_price_per_unit=int(1e13)
),
)
# To declare a contract, send Declare transaction with Client.declare method
resp = await account.client.declare(transaction=declare_transaction)
await account.client.wait_for_tx(resp.transaction_hash)
declared_contract_class_hash = resp.class_hash
Simple deploy#
If you know the class hash of an already declared contract you want to deploy just use the deploy_contract_v1()
or deploy_contract_v3()
.
It will deploy the contract using funds from your account. Deployment is handled by UDC.
from starknet_py.contract import Contract
# To deploy contract just use `Contract.deploy_contract_v1` method
# Note that class_hash and abi of the contract must be known
# If constructor of the contract requires arguments, pass constructor_args parameter
constructor_args = {"value": 10}
deploy_result = await Contract.deploy_contract_v3(
account=account,
class_hash=class_hash,
abi=abi, # abi is optional
constructor_args=constructor_args,
l1_resource_bounds=ResourceBounds(
max_amount=int(1e5), max_price_per_unit=int(1e13)
),
)
# `Contract.deploy_contract_v1` and `Contract.deploy_contract_v3` methods have an optional parameter
# `deployer_address` that needs to be specified when using other network than mainnet or sepolia
# Read more about it in the API section
# Wait for the transaction
await deploy_result.wait_for_acceptance()
# To interact with just deployed contract get its instance from the deploy_result
contract = deploy_result.deployed_contract
# Now, any of the contract functions can be called
Using Universal Deployer Contract (UDC)#
Using UDC is a way of deploying contracts if you already have an account. starknet.py assumes that UDC uses an implementation compatible with OpenZeppelin’s UDC implementation.
There is a class responsible for the deployment (Deployer).
Short code example of how to use it:
from starknet_py.net.udc_deployer.deployer import Deployer
# If you use mainnet/sepolia there is no need to explicitly specify
# address of the deployer (default one will be used)
deployer = Deployer()
# If custom network is used address of the deployer contract is required
deployer = Deployer(deployer_address=deployer_address)
# Deployer has one more optional parameter `account_address`
# It is used to salt address of the contract with address of an account which deploys it
deployer = Deployer(account_address=account.address)
# If contract we want to deploy does not have constructor, or the constructor
# does not have arguments, abi is not a required parameter of `deployer.create_contract_deployment` method
deploy_call, address = deployer.create_contract_deployment(
class_hash=map_class_hash, salt=salt
)
contract_constructor = """
#[constructor]
fn constructor(ref self: ContractState, single_value: felt252, tuple: (felt252, (felt252, felt252)), arr: Array<felt252>, dict: TopStruct) {
let mut sum = 0;
let count = arr.len();
let mut i: usize = 0;
while i != count {
let element: felt252 = arr[i].clone();
sum += element;
i += 1;
};
self.single_value.write(single_value);
self.tuple.write(tuple);
self.arr_sum.write(sum);
self.dict.write(dict);
}
"""
# If contract constructor accepts arguments, as shown above,
# abi needs to be passed to `deployer.create_contract_deployment`
# Note that this method also returns address of the contract we want to deploy
deploy_call, address = deployer.create_contract_deployment(
class_hash=contract_with_constructor_class_hash,
abi=constructor_with_arguments_abi,
cairo_version=1,
calldata={
"single_value": 10,
"tuple": (1, (2, 3)),
"arr": [1, 2, 3],
"dict": {"value": 12, "nested_struct": {"value": 99}},
},
)
# Once call is prepared, it can be executed with an account (preferred way)
resp = await account.execute_v1(deploy_call, max_fee=int(1e16))
# Or signed and send with an account
invoke_tx = await account.sign_invoke_v1(deploy_call, max_fee=int(1e16))
resp = await account.client.send_transaction(invoke_tx)
# Wait for transaction
await account.client.wait_for_tx(resp.transaction_hash)
# After waiting for a transaction
# contract is accessible at the address returned by `deployer.create_deployment_call`
Deploying and using deployed contract in the same transaction#
Deployer is designed to work with multicalls too. It allows to deploy a contract and call its methods in the same multicall, ensuring atomicity of all operations combined.
from starknet_py.contract import Contract
from starknet_py.net.udc_deployer.deployer import Deployer
# First, create Deployer instance. For more details see previous paragraph
deployer = Deployer()
# Create contract deployment. We will be deploying the `map` contract
deploy_call, address = deployer.create_contract_deployment(
class_hash=map_class_hash
)
# Address of the `map` contract is known here, so we can create its instance!
map_contract = Contract(
address=address, abi=map_abi, provider=account, cairo_version=1
)
# And now we can prepare a call
put_call = map_contract.functions["put"].prepare_invoke_v1(key=10, value=20)
# After that multicall transaction can be sent
# Note that `deploy_call` and `put_call` are two regular calls!
invoke_tx = await account.sign_invoke_v1(
calls=[deploy_call, put_call], max_fee=int(1e16)
)
resp = await account.client.send_transaction(invoke_tx)
await account.client.wait_for_tx(resp.transaction_hash)
(value,) = await map_contract.functions["get"].call(key=10)
# value = 20
Cairo1 contracts#
Declaring Cairo1 contracts#
To declare a contract in Cairo version 1 or higher, Declare V2 or Declare V3 transaction has to be sent. You can see the structure of these transactions here.
- The main differences in the structure of the transaction from its previous version are:
contract_class
field is aSierraContractClass
compiled_class_hash
is the hash obtained fromCasmClass
usingstarknet_py.hash.compute_casm_class_hash
The SierraContractClass
in its json
format can be obtained through the compiler in Cairo1 repo.
The command used to get the class is starknet-compile
.
To get compiled_class_hash
, CasmClass
will be needed. It can be obtained in a similar way to SierraContractClass
.
Simply pluck the json
result of starknet-compile
into starknet-sierra-compile
from the Cairo1 repository.
Note
The compilation to Cairo Assembly should use the --add-pythonic-hints
flag.
Here’s an example how to declare a Cairo1 contract.
from starknet_py.common import create_casm_class
from starknet_py.hash.casm_class_hash import compute_casm_class_hash
# contract_compiled_casm is a string containing the content of the starknet-sierra-compile (.casm file)
casm_class = create_casm_class(contract_compiled_casm)
# Compute Casm class hash
casm_class_hash = compute_casm_class_hash(casm_class)
# Create Declare v2 transaction (to create Declare v3 transaction use `sign_declare_v3` method)
declare_v2_transaction = await account.sign_declare_v2(
# compiled_contract is a string containing the content of the starknet-compile (.json file)
compiled_contract=compiled_contract,
compiled_class_hash=casm_class_hash,
max_fee=MAX_FEE,
)
# Send transaction
resp = await account.client.declare(transaction=declare_v2_transaction)
await account.client.wait_for_tx(resp.transaction_hash)
sierra_class_hash = resp.class_hash
Deploying Cairo1 contracts#
After declaring a Cairo1 contract, it can be deployed using UDC.
from starknet_py.net.udc_deployer.deployer import Deployer
# Use Universal Deployer Contract (UDC) to deploy the Cairo1 contract
deployer = Deployer()
# Create a ContractDeployment, optionally passing salt and calldata
contract_deployment = deployer.create_contract_deployment(
class_hash=sierra_class_hash,
abi=abi,
cairo_version=1,
calldata=calldata,
salt=salt,
)
res = await account.execute_v1(calls=contract_deployment.call, max_fee=MAX_FEE)
await account.client.wait_for_tx(res.transaction_hash)
# The contract has been deployed and can be found at contract_deployment.address
Simple declare and deploy Cairo1 contract example#
from starknet_py.contract import Contract
declare_result = await Contract.declare_v2(
account=account,
compiled_contract=compiled_contract["sierra"],
compiled_contract_casm=compiled_contract["casm"],
max_fee=int(1e18),
)
await declare_result.wait_for_acceptance()
deploy_result = await declare_result.deploy_v1(
constructor_args=constructor_args, max_fee=int(1e18)
)
await deploy_result.wait_for_acceptance()
contract = deploy_result.deployed_contract
Simple deploy Cairo1 contract example#
from starknet_py.cairo.felt import encode_shortstring
from starknet_py.common import create_sierra_compiled_contract
from starknet_py.contract import Contract
abi = create_sierra_compiled_contract(
compiled_contract=compiled_contract
).parsed_abi
constructor_args = {
"name_": encode_shortstring("erc20_basic"),
"symbol_": encode_shortstring("ERC20B"),
"decimals_": 10,
"initial_supply": 12345,
"recipient": account.address,
}
deploy_result = await Contract.deploy_contract_v1(
account=account,
class_hash=class_hash,
abi=abi,
constructor_args=constructor_args,
max_fee=int(1e16),
cairo_version=1, # note the `cairo_version` parameter
)
await deploy_result.wait_for_acceptance()
contract = deploy_result.deployed_contract