Account and Client#

Executing transactions#

To execute transactions on Starknet, use execute_v1() or execute_v3() methods from Account interface. These methods will send InvokeV1 and InvokeV3 transactions respectively. To read about differences between transaction versions please visit transaction types from the Starknet docs.

from starknet_py.hash.selector import get_selector_from_name
from starknet_py.net.client_models import Call

call = Call(
    to_addr=address, selector=get_selector_from_name("put"), calldata=[20, 20]
)

resp = await account.execute_v1(calls=call, max_fee=int(1e16))

await account.client.wait_for_tx(resp.transaction_hash)

Transaction Fee#

All methods within the Account that involve on-chain modifications require either specifying a maximum transaction fee or using auto estimation. In the case of V1 and V2 transactions, the transaction fee, denoted in Wei, is configured by the max_fee parameter. For V3 transactions, however, the fee is expressed in Fri and is determined by the l1_resource_bounds parameter. To enable auto estimation, set the auto_estimate parameter to True.

resp = await account.execute_v1(calls=call, auto_estimate=True)

Warning

It is strongly discouraged to use automatic fee estimation in production code as it may lead to an unexpectedly high fee.

The returned estimated fee is multiplied by 1.5 for V1 and V2 transactions to mitigate fluctuations in price. For V3 transactions, max_amount and max_price_per_unit are scaled by 1.5 and 1.5 respectively.

Note

It is possible to configure the value by which the estimated fee is multiplied, by changing ESTIMATED_FEE_MULTIPLIER for V1 and V2 transactions in Account. The same applies to ESTIMATED_AMOUNT_MULTIPLIER and ESTIMATED_UNIT_PRICE_MULTIPLIER for V3 transactions.

The fee for a specific transaction or list of transactions can be also estimated using the estimate_fee() of the Account class.

Creating transactions without executing them#

Account also provides a way of creating signed transaction without sending them.

from starknet_py.net.client_models import Call

# Create a signed Invoke transaction
call = Call(to_addr=address, selector=selector, calldata=calldata)
invoke_transaction = await account.sign_invoke_v1(call, max_fee=max_fee)

# Create a signed Declare transaction
declare_transaction = await account.sign_declare_v1(
    compiled_contract=compiled_contract, max_fee=max_fee
)

# Create a signed DeployAccount transaction
deploy_account_transaction = await account.sign_deploy_account_v1(
    class_hash=class_hash,
    contract_address_salt=salt,
    constructor_calldata=calldata,
    max_fee=max_fee,
)

Multicall#

There is a possibility to execute an Invoke transaction containing multiple calls. Simply pass a list of calls to execute_v1() or execute_v3() methods. Note that the nonce will be bumped only by 1.

from starknet_py.hash.selector import get_selector_from_name
from starknet_py.net.client_models import Call

increase_balance_by_20_call = Call(
    to_addr=balance_contract.address,
    selector=get_selector_from_name("increase_balance"),
    calldata=[20],
)
calls = [increase_balance_by_20_call, increase_balance_by_20_call]

# Execute one transaction with multiple calls
resp = await account.execute_v1(calls=calls, max_fee=int(1e16))
await account.client.wait_for_tx(resp.transaction_hash)

Note

If you want to create a read-only multicall that does not change on-chain state, check out this cairo contract made by Argent, that implements an endpoint allowing for such behaviour.

Warning

Do not pass arbitrarily large number of calls in one batch. Starknet rejects the transaction when it happens.

FullNodeClient usage#

Use a FullNodeClient to interact with services providing Starknet RPC interface like Pathfinder, Papyrus, Juno or starknet-devnet. Using own full node allows for querying Starknet with better performance.

from starknet_py.net.full_node_client import FullNodeClient

node_url = "https://your.node.url"
client = FullNodeClient(node_url=node_url)

call_result = await client.get_block(block_number=0)

Handling client errors#

You can use starknet_py.net.client_errors.ClientError to catch errors from invalid requests:

from starknet_py.contract import Contract
from starknet_py.net.client_errors import ClientError

try:
    contract_address = "1"  # Doesn't exist
    await Contract.from_address(address=contract_address, provider=account)
except ClientError as error:
    print(error.code, error.message)

Custom nonce logic#

By default, Account calls Starknet for nonce every time a new transaction is signed or executed. This is okay for most users, but in case your application needs to pre-sign multiple transactions for execution, deals with high amount of transactions or just needs to support different nonce logic, it is possible to do so with Account. Simply overwrite the get_nonce() method with your own logic.

from starknet_py.net.account.account import Account
from starknet_py.net.client import Client
from starknet_py.net.models import AddressRepresentation, StarknetChainId
from starknet_py.net.signer import BaseSigner
from starknet_py.net.signer.stark_curve_signer import KeyPair

class MyAccount(Account):
    def __init__(
        self,
        *,
        address: AddressRepresentation,
        client: Client,
        signer: Optional[BaseSigner] = None,
        key_pair: Optional[KeyPair] = None,
        chain: Optional[StarknetChainId] = None,
    ):
        super().__init__(
            address=address,
            client=client,
            signer=signer,
            key_pair=key_pair,
            chain=chain,
        )
        # Create a simple counter that will store a nonce
        self.nonce_counter = 0

    async def get_nonce(
        self,
        *,
        block_hash: Optional[Union[Hash, Tag]] = None,
        block_number: Optional[Union[int, Tag]] = None,
    ) -> int:
        # Increment the counter and return the nonce.
        # This is just an example custom nonce logic and is not meant
        # to be a recommended solution.
        nonce = self.nonce_counter
        self.nonce_counter += 1
        return nonce

account = MyAccount(
    address=address,
    client=client,
    key_pair=KeyPair.from_private_key(private_key),
    chain=StarknetChainId.GOERLI,
)