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