Deploying contracts#

Declaring contracts#

A declare transaction can be issued in version 1, 2 or 3. Contracts written in Cairo 0 should be declared using version 1, 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_v1(), sign_declare_v2() or sign_declare_v3() method, respectively.

Here’s an example how to use it.

# Account.sign_declare_v1 takes contract source code or compiled contract and returns DeclareV1 transaction
# Similarly, Account.sign_declare_v2 and Account.sign_declare_v3 return DeclareV2 and DeclareV3 respectively
declare_transaction = await account.sign_declare_v1(
    compiled_contract=contract_compiled, max_fee=int(1e16)
)

# 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 declare and deploy#

The simplest way of declaring and deploying contracts on the Starknet is to use the Contract class. Under the hood, this flow first sends Declare transaction and then sends Invoke through Universal Deployment Contract (UDC) to deploy a contract.

from starknet_py.contract import Contract


# To declare through Contract class you have to compile a contract and pass it
# to Contract.declare_v1 or Contract.declare_v3
declare_result = await Contract.declare_v1(
    account=account, compiled_contract=compiled_contract, max_fee=int(1e18)
)
# Wait for the transaction
await declare_result.wait_for_acceptance()

# After contract is declared it can be deployed
deploy_result = await declare_result.deploy_v1(max_fee=int(1e18))
await deploy_result.wait_for_acceptance()

# You can pass more arguments to the `deploy` method. Check `API` section to learn more

# 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

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_v1(
    account=account,
    class_hash=class_hash,
    abi=abi,
    constructor_args=constructor_args,
    max_fee=int(1e16),
)

# `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, goerli 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/goerli/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
    func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
        single_value: felt, tuple: (felt, (felt, felt)), arr_len: felt, arr: felt*, dict: TopStruct
    ) {
        let (arr_sum) = array_sum(arr, arr_len);
        storage.write((single_value, tuple, arr_sum, dict));
        return ();
    }
"""

# 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=contract_with_constructor_abi,
    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)

# 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 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 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,
    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