Making 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)
To get the receipt of a deploy
transaction, use the ContractInstance.receipt property:
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.receipt
assert receipt.sender == dev
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:goerli: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.
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.fundMyContract(value="1 gwei", 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.fundMyContract(value="1 gwei", type="0x0", sender=sender)
When declaring type="0x0"
and not specifying a gas_price
, the gas_price
gets set using the provider’s estimation.
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 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
If you are using a provider that is able to fetch transaction traces, such as the ape-hardhat provider, you can call the ReceiptAPI.show_trace()
method.
from ape import accounts, project
owner = accounts.load("acct")
contract = project.Contract.deploy(sender=owner)
receipt = contract.methodWithoutArguments()
receipt.show_trace()
NOTE: If your provider does not support traces, you will see a NotImplementedError
saying that the method is not supported.
The trace might look something like:
Call trace for '0x43abb1fdadfdae68f84ce8cd2582af6ab02412f686ee2544aa998db662a5ef50'
txn.origin=0x1e59ce931B4CFea3fe4B875411e280e173cB7A9C
ContractA.methodWithoutArguments() -> 0x00..7a9c [469604 gas]
├── SYMBOL.supercluster(x=234444) -> [
│ [23523523235235, 11111111111, 234444],
│ [
│ 345345347789999991,
│ 99999998888882,
│ 345457847457457458457457457
│ ],
│ [234444, 92222229999998888882, 3454],
│ [
│ 111145345347789999991,
│ 333399998888882,
│ 234545457847457457458457457457
│ ]
│ ] [461506 gas]
├── SYMBOL.methodB1(lolol="ice-cream", dynamo=345457847457457458457457457) [402067 gas]
│ ├── ContractC.getSomeList() -> [
│ │ 3425311345134513461345134534531452345,
│ │ 111344445534535353,
│ │ 993453434534534534534977788884443333
│ │ ] [370103 gas]
│ └── ContractC.methodC1(
│ windows95="simpler",
│ jamaica=345457847457457458457457457,
│ cardinal=ContractA
│ ) [363869 gas]
├── SYMBOL.callMe(blue=tx.origin) -> tx.origin [233432 gas]
├── SYMBOL.methodB2(trombone=tx.origin) [231951 gas]
│ ├── ContractC.paperwork(ContractA) -> (
│ │ os="simpler",
│ │ country=345457847457457458457457457,
│ │ wings=ContractA
│ │ ) [227360 gas]
│ ├── ContractC.methodC1(windows95="simpler", jamaica=0, cardinal=ContractC) [222263 gas]
│ ├── ContractC.methodC2() [147236 gas]
│ └── ContractC.methodC2() [122016 gas]
├── ContractC.addressToValue(tx.origin) -> 0 [100305 gas]
├── SYMBOL.bandPractice(tx.origin) -> 0 [94270 gas]
├── SYMBOL.methodB1(lolol="lemondrop", dynamo=0) [92321 gas]
│ ├── ContractC.getSomeList() -> [
│ │ 3425311345134513461345134534531452345,
│ │ 111344445534535353,
│ │ 993453434534534534534977788884443333
│ │ ] [86501 gas]
│ └── ContractC.methodC1(windows95="simpler", jamaica=0, cardinal=ContractA) [82729 gas]
└── SYMBOL.methodB1(lolol="snitches_get_stiches", dynamo=111) [55252 gas]
├── ContractC.getSomeList() -> [
│ 3425311345134513461345134534531452345,
│ 111344445534535353,
│ 993453434534534534534977788884443333
│ ] [52079 gas]
└── ContractC.methodC1(windows95="simpler", jamaica=111, cardinal=ContractA) [48306 gas]
Additionally, you can view the traces of other transactions on your network.
from ape import networks
txn_hash = "0x053cba5c12172654d894f66d5670bab6215517a94189a9ffc09bc40a589ec04d"
receipt = networks.provider.get_receipt(txn_hash)
receipt.show_trace()
In Ape, you can also show the trace for a call.
Use the show_trace=
kwarg on a contract call and Ape will display the trace before returning the data.
token.balanceOf(account, show_trace=True)
NOTE: This may not work on all providers, but it should work on common ones such as ape-hardhat
or ape-geth
.
Gas Reports
To view the gas report of a transaction receipt, use the ReceiptAPI.show_gas_report()
method:
from ape import networks
txn_hash = "0x053cba5c12172654d894f66d5670bab6215517a94189a9ffc09bc40a589ec04d"
receipt = networks.provider.get_receipt(txn_hash)
receipt.show_gas_report()
It will output tables of contracts and methods with gas usages that look like this:
DAI Gas
Method Times called Min. Max. Mean Median
────────────────────────────────────────────────────────────────
balanceOf 4 1302 13028 1302 1302
allowance 2 1377 1377 1337 1337
│ approve 1 22414 22414 22414 22414
│ burn 1 11946 11946 11946 11946
│ mint 1 25845 25845 25845 25845
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)