Transactions

Regardless of how you are using ape, you will likely be making transactions. There are various types of transactions you can make with ape. A simple example is deploying a contract.

Deployment

Deploying a smart contract is a unique type of transaction where we don’t necessarily care about the receipt as much as we care about the contract instance. That is why the return value from the deploy method is a ContractInstance.

The following example demonstrates a simple deployment script:

from ape import accounts, project

def deploy():
    account = accounts.load("MyAccount")
    # Assume you have a contract named `MyContract` in your project's contracts folder.
    return account.deploy(project.MyContract)

Deployment from Ape Console

Deploying from ape console allows you to interact with a contract in real time. You can also use the --network flag to connect a live network.

ape console --network ethereum:sepolia:alchemy

This will launch an IPython shell:

In [1]: dev = accounts.load("dev")
In [2]: token = dev.deploy(project.Token)
In [3]: token.contract_method_defined_in_contract()

For an in depth tutorial on how to deploy, please visit ApeAcademy.

Deployment Metadata

To get the receipt of a deploy transaction, use the ContractInstance.creation_metadata property:

Note

Use ape-etherscan or a node with Otterscan for increased support for these features.

from ape import accounts, project

dev = accounts.load("dev")
contract = project.MyContract.deploy(sender=dev)

# The receipt is available on the contract instance and has the expected sender.
receipt = contract.creation_metadata.receipt
assert receipt.sender == dev

NOTE: The creation_metadata contains other information as well, such as .factory for factory-deployed contracts.

Dynamic-Fee Transactions

Before EIP-1559, all transactions used a gas_price. After the London fork of Ethereum, the gas_price got broken up into two values, max_fee and max_priority_fee. The ape framework supports both types of transactions. By default, transactions use the dynamic-fee model. Making contract calls without specifying any additional kwargs will use a dynamic-fee transaction.

Calling certain methods on a deployed-contract is one way to transact.

contract = deploy()  # Example from above, that returns a contract instance.
contract.fundMyContract(value="1 gwei", sender=sender)  # Assuming there is a method named 'fundMyContract' on MyContract.

In the example above, the call to fundMyContract() invokes a dynamic-fee transaction. To have more control of the fee-values, you can specify the max_fee, the max_priority_fee, or both.

contract.fundMyContract(value="1 gwei", max_priority_fee="50 gwei", max_fee="100 gwei", sender=sender)

The max_priority_fee cannot exceed the max_fee, as the max_fee includes both the base fee and the priority fee. The max_priority_fee, when omitted, defaults to the return value from the ProviderAPI.priority_fee method property. The max_fee, when omitted, defaults to the priority_fee (which gets its default applied beforehand) plus the latest the value returned from the ProviderAPI.base_fee method property.

Static-Fee Transactions

Static-fee transactions are the transactions that Ethereum used before the London-fork (before EIP-1559). However, some applications may still require using static-fee transactions.

One way to use a static-fee transaction is by specifying the gas_price as a key-value argument:

contract.startAuction(gas_price="100 gwei", sender=sender)

Note

Miners prioritize static-fee transactions based on the highest gas_price.

Another way to use a static-fee transaction (without having to provide gas_price) is to set the key-value argument type equal to 0x00.

contract.startAuction(type="0x0", sender=sender)

When declaring type="0x0" and not specifying a gas_price, the gas_price gets set using the provider’s estimation.

Access List Transactions

Utilizing EIP-2930, you can also make access-list transactions using Ape. Access-list transactions are static-fee transactions except you can optionally specify an access list. Access-lists make contract-interaction more predictable and optimized. You can also use Access-lists in Dynamic-fee transactions.

To automatically use access-list (type 1) transactions in Ape, specify type=1 in your call:

contract.startAuction(type=1, sender=sender)

When specifying type=1, Ape uses eth_createAccessList RPC to attach an access list to the transaction automatically.

You can also specify the access-list directly:

contract.fundMyContract(type=1, sender=sender, access_list=MY_ACCESS_LIST)

Payable Transactions

To add value to a transaction, use the value= kwarg when transacting in Ape.

contract.fundMyContract(value="1 ether", sender=sender)

The value="1 ether" part is sending 1 ETH to the contract. You would do this if fundMyContract was a "payable" method, e.g. marked @payable in Vyper.

Transaction Logs

In Ape, you can easily get all the events on a receipt. Use the .events property to access the (ContractLog) objects. Each object represents an event emitted from the call.

receipt = contract.fundMyContract(value="1 gwei", type="0x0", sender=sender)
print(receipt.events)

To only get specific log types, use the decode_logs() method and pass the event ABIs as arguments:

for log in receipt.decode_logs(contract.FooEvent.abi, contract.BarEvent.abi):
    print(log.amount)  # Assuming 'amount' is a property on the event.

You can also use the ContractEvent.from_receipt(receipt) method:

receipt = contract.fooMethod(value="1 gwei", type="0x0", sender=sender)
for log in contract.FooEvent.from_receipt(receipt):
    print(log.amount)  # Assuming 'amount' is a property on the event.

Note

If you have more than one event with the same name in your contract type’s ABI, you can access the events by using the get_event_by_signature() method:

event_type = contract.get_event_by_signature("FooEvent(uint256 bar, uint256 baz)")
receipt.decode_logs(event_type.abi)

Otherwise, you will get an AttributeError.

Transaction Acceptance Timeout

Note

For longer running scripts, you may need to increase the transaction acceptance timeout.

The default value is 2 minutes for live networks and 20 seconds for local networks. In your ape-config.yaml file, add the following:

ethereum:
  mainnet:
    transaction_acceptance_timeout: 600  # 5 minutes

Traces

Transaction traces are the steps in the contract the transaction took. Traces both power a myriad of features in Ape as well are themselves a tool for developers to use to debug transactions. To learn more about traces, see the traces userguide.

Estimate Gas Cost

To estimate the gas cost on a transaction or call without sending it, use the estimate_gas_cost() method from the contract’s transaction / call handler: (Assume I have a contract instance named contract_a that has a method named methodToCall)

txn_cost = contract_a.myMutableMethod.estimate_gas_cost(1, sender=accounts.load("me"))
print(txn_cost)

view_cost = contract_a.myViewMethod.estimate_gas_cost()
print(view_cost)