Deploying contracts#

Declaring contracts#

Contracts written in Cairo 0 cannot be declared while those written in Cairo 1 or higher should be declared with transaction version 3. To sign a declare transaction, you should utilize sign_declare_v3() method.

Here’s an example how to use it.

# Account.sign_declare_v3 takes a string containing a compiled contract (sierra)
# and a class hash (casm_class_hash)

resource_bounds = ResourceBoundsMapping(
    l1_gas=ResourceBounds(max_amount=int(1e5), max_price_per_unit=int(1e13)),
    l2_gas=ResourceBounds(max_amount=int(1e5), max_price_per_unit=int(1e13)),
    l1_data_gas=ResourceBounds(max_amount=int(1e5), max_price_per_unit=int(1e13)),
)
declare_transaction = await account.sign_declare_v3(
    compiled_contract=compiled_contract,
    compiled_class_hash=class_hash,
    resource_bounds=resource_bounds,
)

# 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_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_v3` 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}

resource_bounds = ResourceBoundsMapping(
    l1_gas=ResourceBounds(max_amount=int(1e5), max_price_per_unit=int(1e13)),
    l2_gas=ResourceBounds(max_amount=int(1e10), max_price_per_unit=int(1e17)),
    l1_data_gas=ResourceBounds(max_amount=int(1e5), max_price_per_unit=int(1e13)),
)
deploy_result = await Contract.deploy_contract_v3(
    account=account,
    class_hash=class_hash,
    abi=abi,  # abi is optional
    constructor_args=constructor_args,
    resource_bounds=resource_bounds,
)

# `Contract.deploy_contract_v3` method has 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.client_models import ResourceBounds, ResourceBoundsMapping
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_v3(
    deploy_call,
    resource_bounds=ResourceBoundsMapping(
        l1_gas=ResourceBounds(max_amount=int(1e5), max_price_per_unit=int(1e13)),
        l2_gas=ResourceBounds(max_amount=int(1e9), max_price_per_unit=int(1e17)),
        l1_data_gas=ResourceBounds(
            max_amount=int(1e5), max_price_per_unit=int(1e13)
        ),
    ),
)

# Or signed and send with an account
invoke_tx = await account.sign_invoke_v3(
    deploy_call,
    resource_bounds=ResourceBoundsMapping(
        l1_gas=ResourceBounds(max_amount=int(1e5), max_price_per_unit=int(1e13)),
        l2_gas=ResourceBounds(max_amount=int(1e9), max_price_per_unit=int(1e17)),
        l1_data_gas=ResourceBounds(
            max_amount=int(1e5), max_price_per_unit=int(1e13)
        ),
    ),
)
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.client_models import ResourceBounds, ResourceBoundsMapping
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_v3(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_v3(
    calls=[deploy_call, put_call],
    resource_bounds=ResourceBoundsMapping(
        l1_gas=ResourceBounds(max_amount=int(1e5), max_price_per_unit=int(1e13)),
        l2_gas=ResourceBounds(max_amount=int(1e9), max_price_per_unit=int(1e17)),
        l1_data_gas=ResourceBounds(
            max_amount=int(1e5), max_price_per_unit=int(1e13)
        ),
    ),
)

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 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 a SierraContractClass

  • compiled_class_hash is the hash obtained from CasmClass using starknet_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 v3 transaction
declare_v3_transaction = await account.sign_declare_v3(
    # compiled_contract is a string containing the content of the starknet-compile (.json file)
    compiled_contract=compiled_contract,
    compiled_class_hash=casm_class_hash,
    resource_bounds=MAX_RESOURCE_BOUNDS,
)

# Send transaction
resp = await account.client.declare(transaction=declare_v3_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_v3(
    calls=contract_deployment.call, resource_bounds=MAX_RESOURCE_BOUNDS
)
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
from starknet_py.net.client_models import ResourceBounds, ResourceBoundsMapping

declare_result = await Contract.declare_v3(
    account=account,
    compiled_contract=compiled_contract["sierra"],
    compiled_contract_casm=compiled_contract["casm"],
    resource_bounds=ResourceBoundsMapping(
        l1_gas=ResourceBounds(max_amount=int(1e5), max_price_per_unit=int(1e13)),
        l2_gas=ResourceBounds(max_amount=int(1e9), max_price_per_unit=int(1e17)),
        l1_data_gas=ResourceBounds(
            max_amount=int(1e5), max_price_per_unit=int(1e13)
        ),
    ),
)
await declare_result.wait_for_acceptance()

deploy_result = await declare_result.deploy_v3(
    constructor_args=constructor_args,
    resource_bounds=ResourceBoundsMapping(
        l1_gas=ResourceBounds(max_amount=int(1e5), max_price_per_unit=int(1e13)),
        l2_gas=ResourceBounds(max_amount=int(1e9), max_price_per_unit=int(1e17)),
        l1_data_gas=ResourceBounds(
            max_amount=int(1e5), max_price_per_unit=int(1e13)
        ),
    ),
)
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
from starknet_py.net.client_models import ResourceBounds, ResourceBoundsMapping

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_v3(
    account=account,
    class_hash=class_hash,
    abi=abi,
    constructor_args=constructor_args,
    resource_bounds=ResourceBoundsMapping(
        l1_gas=ResourceBounds(max_amount=int(1e5), max_price_per_unit=int(1e13)),
        l2_gas=ResourceBounds(max_amount=int(1e5), max_price_per_unit=int(1e13)),
        l1_data_gas=ResourceBounds(
            max_amount=int(1e5), max_price_per_unit=int(1e13)
        ),
    ),
)

await deploy_result.wait_for_acceptance()
contract = deploy_result.deployed_contract