Using existing contracts#

Existing contracts#

Although it is possible to use Client to interact with contracts, it requires translating python values into Cairo values. Contract offers that and some other utilities.

Let’s say we have a contract with this interface:

abi = [
    {
        "inputs": [
            {"name": "sender", "type": "felt"},
            {"name": "recipient", "type": "felt"},
            {"name": "amount", "type": "felt"},
        ],
        "name": "transferFrom",
        "outputs": [{"name": "success", "type": "felt"}],
        "type": "function",
    },
    {
        "inputs": [{"name": "account", "type": "felt"}],
        "name": "balanceOf",
        "outputs": [{"name": "balance", "type": "felt"}],
        "stateMutability": "view",
        "type": "function",
    },
]

This is how we can interact with it:

from starknet_py.contract import Contract
from starknet_py.net.client_models import ResourceBounds

address = "0x00178130dd6286a9a0e031e4c73b2bd04ffa92804264a25c1c08c1612559f458"

# When ABI is known statically just use the Contract constructor
contract = Contract(address=address, abi=abi, provider=account, cairo_version=0)
# or if it is not known
# Contract.from_address makes additional request to fetch the ABI
contract = await Contract.from_address(provider=account, address=address)

sender = account.address
recipient = "123"

# Using only positional arguments
invocation = await contract.functions["transfer_from"].invoke_v3(
    sender,
    recipient,
    50,
    l1_resource_bounds=ResourceBounds(
        max_amount=int(1e5), max_price_per_unit=int(1e13)
    ),
)

# Using only keyword arguments
invocation = await contract.functions["transfer_from"].invoke_v3(
    sender=sender,
    recipient=recipient,
    amount=50,
    l1_resource_bounds=ResourceBounds(
        max_amount=int(1e5), max_price_per_unit=int(1e13)
    ),
)

# Mixing positional with keyword arguments
invocation = await contract.functions["transfer_from"].invoke_v3(
    sender,
    recipient,
    amount=50,
    l1_resource_bounds=ResourceBounds(
        max_amount=int(1e5), max_price_per_unit=int(1e13)
    ),
)

# Creating a prepared function call with arguments
# It is also possible to use `prepare_invoke_v3`
transfer = contract.functions["transfer_from"].prepare_invoke_v3(
    sender,
    recipient,
    amount=50,
    l1_resource_bounds=ResourceBounds(
        max_amount=int(1e5), max_price_per_unit=int(1e13)
    ),
)
invocation = await transfer.invoke()

# Wait for tx
await invocation.wait_for_acceptance()

(balance,) = await contract.functions["balance_of"].call(recipient)

Raw contract calls#

If you do not have ABI statically, but you know the interface of the contract on Starknet, you can make a raw call:

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

eth_token_address = 0x049D36570D4E46F48E99674BD3FCC84644DDD6B96F7C741B1562B82F9E004DC7

# Create a call to function "balanceOf" at address `eth_token_address`
call = Call(
    to_addr=eth_token_address,
    selector=get_selector_from_name("balanceOf"),
    calldata=[account.address],
)
# Pass the created call to Client.call_contract
account_balance = await account.client.call_contract(call)

# Note that a Contract instance cannot be used here, since it needs ABI to generate the functions

Fees#

Starknet.py requires you to specify amount of Fri that you are willing to pay when executing invoke_v3(). Alternatively, you can estimate fee automatically, as described in the Automatic fee estimation section below.

await contract.functions["put"].invoke_v3(k, v, l1_resource_bounds=ResourceBounds(
        max_amount=int(1e5), max_price_per_unit=int(1e13)
    ))

The l1_resource_bounds argument can be also defined in prepare_invoke_v3(). Subsequently, the invoke() method on a prepared call can be used either with l1_resource_bounds omitted or with its value overridden.

prepared_call = contract.function["put"].prepare_invoke_v3(k, v, l1_resource_bounds=ResourceBounds(
        max_amount=int(1e5), max_price_per_unit=int(1e13)
    ))
await prepared_call.invoke()
# or l1_resource_bounds can be overridden
await prepared_call.invoke(l1_resource_bounds=ResourceBounds(
        max_amount=int(1e5), max_price_per_unit=int(1e13)
    ))

Warning

If l1_resource_bounds is not specified at any step it will default to None, and will raise an exception when invoking a transaction, unless auto_estimate is specified and is set to True.

Please note you will need to have enough Fri in your Starknet account otherwise transaction will be rejected.

Fee estimation#

You can estimate required amount of fee that will need to be paid for transaction using PreparedFunctionInvoke.estimate_fee()

await contract.functions["put"].prepare_invoke_v3(k, v).estimate_fee()

Automatic fee estimation#

For testing purposes it is possible to enable automatic fee estimation when making a transaction. Starknet.py will then call estimate_fee() internally and use the returned value. max_amount and max_price_per_unit will be multiplied by 1.5.

await contract.functions["put"].invoke_v3(k, v, auto_estimate=True)

Warning

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

Note

It is possible to configure the value by which the estimated fee is multiplied, by changing ESTIMATED_AMOUNT_MULTIPLIER and ESTIMATED_UNIT_PRICE_MULTIPLIER.

Account and Client interoperability#

Contract methods have been designed to be compatible with Account and Client.

PreparedFunctionInvokeV3 returned by ContractFunction.prepare_invoke_v3() can be used in Account methods to create invoke transaction.

from starknet_py.net.client_models import Call, ResourceBounds

# Prepare a call through Contract
call = contract.functions["put"].prepare_invoke_v3(key=20, value=30)
assert issubclass(type(call), Call)

# Crate an Invoke transaction from call
invoke_transaction = await account.sign_invoke_v3(
    call,
    l1_resource_bounds=ResourceBounds(
        max_amount=int(1e5), max_price_per_unit=int(1e13)
    ),
)

Similarly, PreparedFunctionCall returned by ContractFunction.prepare_call() can be used in Client.call_contract()

from starknet_py.net.client_models import Call

# Prepare a call through Contract
call = contract.functions["get"].prepare_invoke_v3(key=1234)
assert issubclass(type(call), Call)

# Use call directly through Client
result = await client.call_contract(call)