Contracts

You can interact with contracts pythonically using ape! First, we need to obtain a contract instance. One way to do this is to deploy a contract. The other way is to initialize an already-deployed contract using its address.

From Deploy

Deploy contracts from your project using the project root-level object. You deploy contracts using Python functions such as AccountAPI.deploy or ContractContainer.deploy.

Note

You can run Ape’s deploy functions anywhere you run Python!

You need both an account and a contract in order to deploy a contract, as the deployment process requires a transaction to submit the contract data to the blockchain. To learn about accounts and how to use them, see the Accounts Guide. You also need the contract. You can access contract types from Ape’s root-level project object (e.g. project.MyContract) and their types are ContractContainer.

Let’s assume you have a Vyper contract like this:

contract MySmartContract:
    owner: public(address)
    balance: public(uint256)

    @public
    @payable
    @public
    def __init__(arg1: uint256, arg2: address):
        self.owner = arg2
        self.balance = arg1

Before you can deploy this contract, you must ensure it was compiled. To learn about compiling in Ape, please see this guide.

After it is compiled, you can deploy it. Here is a basic example of Python code to deploy a contract:

from ape import accounts, project

# You need an account to deploy, as it requires a transaction.
account = accounts.load("<ALIAS>")  # NOTE: <ALIAS> refers to your account alias!
contract = project.MyContract.deploy(1, account, sender=account)

# NOTE: You can also do it this way:
contract2 = account.deploy(project.MyContract, 1, account)

The arguments to the constructor (1, account) can be in Python form. Ape will automatically convert values in your transactions, thus allowing you to provide higher-level objects or abstractions as input types. That is why, as you can see, the second argument is an AccountAPI object for the type address in the contract.

Notice in the example, we use project.MyContract to access the contract type. To avoid naming collisions with other properties on the project object, you can alternatively use the get_contract() method to retrieve contract containers.

from ape import project

contract = project.get_contract("MyContract")  # Same as `project.MyContract`.

Notice when deploying, we have to specify the sender= kwarg because deploy operations are transactions. To learn more about contract interaction via transactions, see the Contract Interaction section below and the guide on transactions.

Deploy Scripts

Often time, the deployment process may be unique or complex. Or possibly, you need to run the deploy-logic from CI or in a repeatable fashion. Or perhaps, you just want to avoid having to invoking Python directly. In those cases, you can use Ape’s scripting system to save time and store your deployment logic. Simply copy your Python logic into an Ape script and run it via:

ape run <my-deploy-script>

Learn how to do this and scripting in its entirety by reviewing the scripting user-guide.

There is no root ape command to deploy contracts; only the scripting-system, the console, or merely using Ape as a Python library.

If your deployment process is simple or only needs to happen once, it is easy to use ape console to achieve a deployment. More information on how to use ape console can be found here.

Publishing

You can also publish the contract source code to an explorer upon deployment using the publish= kwarg on the deploy methods. More information on publishing contracts can be found in this guide.

From Project Contract Address

You can also use the at() method from the same top-level project manager when you know the address of an already-deployed contract:

from ape import project

contract = project.MyContract.at("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45")

From Any Address

If you already know the address of a contract, you can create instances of it using the Contract top-level factory:

from ape import Contract

contract = Contract("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45")

It will fetch the contract-type using the explorer plugin from the active network, such as ape-etherscan.

If you have the ENS plugin installed, you can use .eth domain names as the argument:

from ape import Contract

contract = Contract("v2.registry.ychad.eth")

From ABIs

You can load contracts using their ABIs:

from ape import Contract

address = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"

# Using a JSON str:
contract = Contract(
    address, abi='[{"name":"foo","type":"fallback", "stateMutability":"nonpayable"}]'
)

# Using a JSON file path:
contract = Contract(address, abi="abi.json")

# Using a Python dictionary from JSON:
contract = Contract(
    address,
    abi=[{"name":"foo","type":"fallback", "stateMutability":"nonpayable"}]
)

This will create the Contract instance from the given ABI.

From Previous Deployment

Ape keeps track of your deployments for you so you can always refer back to a version that you deployed previously. On live networks, this history of deployments is saved; on local networks, this history lasts for the duration of your script.

Let’s say you previously deployed a smart contract called MyContract on the rinkeby test network. You could then refer back to it like so:

from ape import project, chain

def main():
  my_contract = chain.contracts.get_deployments(project.MyContract)[-1]

or

from ape import project

def main():
  my_contract = project.MyContract.deployments[-1]

my_contract will be of type ContractInstance. get_deployments returns a list of deployments you made of that contract type.

Contract Interaction

Then, after you have a contract instance, you can call methods on the contract. For example, let’s say you have a Vyper contract containing some functions:

wdAmount: public(uint256)

@pure
@external
def get_static_list() -> DynArray[uint256, 3]:
    return [1, 2, 3]

@external
def set_number(num: uint256):
    assert msg.sender == self.owner, "!authorized"
    self.prevNumber = self.myNumber
    self.myNumber = num

@external
@payable
def withdraw():
    self.wdAmount = msg.value

Notice the contract has an external pure method, an external method that modifies state, and an external payable method that also modifies state using the given msg.value. In EVM languages, methods that modify state require a transaction to execute because they cost money. Modifying the storage of a contract requires gas and thus requires a sender with enough funding. Methods that accept value are payable (e.g. msg.value in Vyper); provide additional value (e.g. Ether) to these methods. Contract calls, on the other hand, are read-operations and do not cost anything. Calls are never payable. Thus, calls do not require specifying a sender= in Ape.

At the RPC level, Ethereum calls are performed using the eth_call RPC and transactions are performed using the eth_sendTransaction or eth_sendRawTransaction RPCs.

The following sub-sections show how, using Ape, we can invoke or call the methods defined above.

Transactions

The following example demonstrates invoking a contract’s method in Ape as a transaction. Remember: transactions cost money, whether they are payable or not. Payable transactions cost more money, because the contract-logic requires additional value (e.g. Ether) to be forwarded with the call.

Before continuing, take note that there is a separate guide which fully covers transactions in Ape at a more granular level. For this guide, assume we are using the default transaction type in Ape for Ethereum-based networks.

from ape import accounts, Contract

account = accounts.load("<ALIAS>")
contract = Contract("0x...")  # Assume is deployed version of code above

# Transaction: Invoke the `set_number()` function, which costs Ether
receipt = contract.set_number(sender=account)
assert not receipt.failed

# The receipt contains data such as `gas_used`.
print(receipt.gas_used)

To provider additional value to a payable method, use the value= kwarg:

receipt = contract.withdraw(sender=account, value=123)
print(receipt.gas_used)

# NOTE: You can also use "smart" values such as `"0.1 ether"` or `"100 gwei"`:
_ = contract.withdraw(sender=account, value="0.1 ether")
_ = contract.withdraw(sender=account, value="100 gwei")
_ = contract.withdraw(sender=account, value="1 wei")

Notice that transacting returns a ReceiptAPI object which contains all the receipt data, such as gas_used.

Note

If you need the return_value from a transaction, you have to either treat transaction as a call (see the section below!) or use a provider with tracing-features enabled (such as ape-foundry or ape-node) and access the return_value property on the receipt.

assert receipt.return_value == 123

Transactions may also fail, known as a “revert”. When a transaction reverts, Ape (by default) raises a subclass of TransactionError, which is a Python exception. To learn more reverts, see the reverts guide.

For more general information on transactions in the Ape framework, see this guide.

Calls

In the Vyper code at the beginning of this section, the function get_static_list() is decorated as @pure indicating that it’s read-only. (Also in Vyper, @view methods are read-only). Since get_static_list() is read-only, we can successfully call it without a sender= kwarg; no funds are required. Here is an example of making a call by checking the result of get_static_list():

from ape import accounts, Contract

account = accounts.load("<ALIAS>")
contract = Contract("0x...")

# CALL: A sender is not required for calls!
assert contract.get_static_list() == [1, 2, 3]

Calling Transactions and Transacting Calls

You can treat transactions as calls and vice-versa.

For example, let’s say we have a Solidity function:

function addBalance(uint256 new_bal) external returns(uint256) {
    balances[msg.sender] = new_bal;
    return balances[msg.sender];
}

To simulate the transaction without actually modifying any state, use the .call method from the contract transaction handler:

from ape import Contract

contract = Contract("0x...")

result = contract.addBalance.call(123)
assert result == "123"  # The return value gets forwarded from the contract.

Similarly, you may want to measure a call as if it were a transaction, in which case you can use the .transact attribute on the contract call handler:

Given the Solidity function:

function getModifiedBalance() external view returns(uint256) {
    return balances[msg.sender] + 123;
}

You can treat it like a transaction by doing:

from ape import accounts, Contract

account = accounts.load("<ALIAS>")
contract = Contract("0x...")

receipt = contract.getModifiedBalance.transact(sender=account)
assert not receipt.failed  # Transactions return `ReceiptAPI` objects.
print(receipt.gas_used)  # Analyze receipt gas from calls.

Default, Fallback, and Direct Calls

To directly call an address, such as invoking a contract’s fallback or receive method, call a contract instance directly:

from ape import Contract, accounts

sender = accounts.load("<ALIAS>")  # NOTE: <ALIAS> refers to your account alias!
contract = Contract("0x123...")

# Call the contract's fallback method.
receipt = contract(sender=sender, gas=40000, data="0x123")

Private Transactions

If you are using a provider that allows private mempool transactions, you are able to use the private=True kwarg to publish your transaction into a private mempool. For example, EVM providers likely will use the eth_sendPrivateTransaction RPC to achieve this.

To send a private transaction, do the following:

receipt = contract.set_number(sender=dev, private=True)

The private=True is available on all contract interactions.

Decoding and Encoding Inputs

If you want to separately decode and encode inputs without sending a transaction or making a call, you can achieve this with Ape. If you know the method you want to use when decoding or encoding, you can call methods encode_input() or decode_input() on the method handler from a contract:

from ape import Contract

# HexBytes(0x3fb5c1cb00000000000000000000000000000000000000000000000000000000000000de)
contract = Contract("0x...")
bytes_value = contract.my_method.encode_input(0, 1, 2)

In the example above, the bytes value returned contains the method ID selector prefix 3fb5c1c. Alternatively, you can decode input:

from eth_pydantic_types import HexBytes
from ape import Contract

contract = Contract("0x...")
selector_str, input_dict = contract.my_method.decode_input(HexBytes("0x123..."))

In the example above, selector_str is the string version of the method ID, e.g. my_method(unit256,uint256). The input dict is a mapping of input names to their decoded values, e.g {"foo": 2, "owner": "0x123..."}. If an input does not have a name, its key is its stringified input index.

If you don’t know the method’s ABI and you have calldata, you can use a ContractInstance or ContractContainer directly:

import ape

# Fetch a contract
contract = ape.Contract("0x...")

# Alternative, use a contract container from ape.project
# contract = ape.project.MyContract

# Only works if unique amount of args.
bytes_value = contract.encode_input(0, 1, 2, 4, 5)
method_id, input_dict = contract.decode_input(bytes_value)

Contract Interface Introspection

There may be times you need to figure out ABI selectors and method or event identifiers for a contract. A contract instance provides properties to make this easy. For instance, if you have a 4-byte hex method ID, you can return the ABI type for that method:

import ape

usdc = ape.Contract("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")

# ABI type for a hex method ID
assert usdc.identifier_lookup['0x70a08231'].selector == 'balanceOf(address)'

# Also, selectors from method and event signatures
assert usdc.selector_identifiers["balances(address)"] == "0x27e235e3"

# Or dump all selectors and IDs
for identifier, abi_type in usdc.identifier_lookup.items():
    print(identifier, abi_type)
    # 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef type='event' name='Transfer' inputs=...
    # ...

These include methods and error IDs, as well as event topics.

Multi-Call and Multi-Transaction

The ape_ethereum core plugin comes with a multicall module containing tools for interacting with the multicall3 smart contract. Multicall allows you to group function calls and transactions into a single call or transaction.

Here is an example of how you can use the multicall module:

import ape
from ape_ethereum import multicall

ADDRESSES = ("0xF4b8A02D4e8D76070bD7092B54D2cBbe90fa72e9", "0x80067013d7F7aF4e86b3890489AcAFe79F31a4Cb")
POOLS = [ape.project.IPool.at(a) for a in ADDRESSES]

def main():
    # Use multi-call.
    call = multicall.Call()
    for pool in POOLS:
        call.add(pool.getReserves)

    print(list(call()))

    # Use multi-transaction.
    tx = multicall.Transaction()
    for pool in POOLS:
        tx.add(pool.ApplyDiscount, 123)

    acct = ape.accounts.load("signer")
    for result in tx(sender=acct):
        print(result)