Working with Contracts

This guide describes how you can leverage aepp-sdk to compile, deploy and interact with aeternity smart contracts.

See also

Sophia: An Æternity Blockchain Language

The Sophia is a language in the ML family. It is strongly typed and has restricted mutable state. Check out more about sophia here.

Prerequisites

  1. An account with some initial AE
  2. A smart contract written in sophia
  3. An aeternity node
  4. A sophia aehttp compiler

Assumptions

We’re going to assume that you have working knowledge of the SDK and know how to create an Account instance, a NodeClient, and a CompilerClient.

Sample Sophia Contract

Below is the sample sophia contract that we’ll use in this guide.

We need to import the following classes to use contracts.

 aeternity.node import NodeClient, Config
 aeternity.compiler import CompilerClient
 aeternity.contract_native import ContractNative
 aeternity.signing import Account

Initializing NodeClient and Compiler

Below are the steps required to initialize the the NodeClient and Compiler. As you can see below, during the initialization of NodeClient we’re also providing the internal_url.

internal_url provides the debug endpoint to dry_run a contract method which can also be used to do static calls on deployed contracts and this is what exactly we’re going to use this for.

You can also not provide the internal_url but then you’ll have to disable the use of dry-run endpoint. We’ll see how to do that when we initialize our contract.

NODE_URL = os.environ.get('TEST_URL', 'http://127.0.0.1:3013')
NODE_INTERNAL_URL = os.environ.get('TEST_DEBUG_URL', 'http://127.0.0.1:3113')
COMPILER_URL = os.environ.get('TEST_COMPILER__URL', 'https://compiler.aepps.com')

node_cli = NodeClient(Config(
    external_url=NODE_URL,
    internal_url=NODE_INTERNAL_URL,
    blocking_mode=True,
))

compiler = CompilerClient(compiler_url=COMPILER_URL)

Generate an Account

You’ll need an account (using the Account class) to deploy the contract and also for stateful contract calls.

# genrate ALICE account (and transfer AE to alice account)
alice = Account.generate()

Read the Contract from file and initialize

You can read the contract from the stored .aes file and use it to initilaize the contract instance. If you have not provided the internal_endpoint or simple do not want to use the dry-run functionality you can disable it by passing use-dry-run=False to the ContractNative constructor.

Warning

If you DO NOT provide the internal_url during NodeClient initialization and also DID NOT disable the dry-run then the contract method calls for un-stateful methods WILL FAIL.

CONTRACT_FILE = os.path.join(os.path.dirname(__file__), "testdata/CryptoHamster.aes")

# read contract file
with open(CONTRACT_FILE, 'r') as file:
    crypto_hamster_contract = file.read()

"""
Initialize the contract instance
To disable use of dry-run endpoint use:
crypto_hamster = ContractNative(client=node_cli, compiler=compiler, account=alice, source=crypto_hamster_contract, use_dry_run=False)
"""
crypto_hamster = ContractNative(client=node_cli, compiler=compiler, account=alice, source=crypto_hamster_contract)

Compile and deploy the contract

You can compile the contract and deploy it using the deploy method. If your init method accepts any arguments then please provide them inside the deploy method. Once the contract is compiled and deployed, the signed transaction is returned.

# deploy the contract (you can also pass the args if required by the init method of the contract)
tx = crypto_hamster.deploy()

Call the contract methods

Once the contract is deployed, all the methods inside the contract are also available (with same signature) to use from the contract instance.

Note

All the methods that are NOT stateful, by default are processed using the dry-run endpoint to save gas. And therefore, a transaction hash will also not be provided for them. This functionality can be either diabled for the contract instance or per method by using use_dry_run argument.

# call the contract method (stateful)
tx_info, tx_result = crypto_hamster.add_test_value(1, 2)

print(f"Transaction Hash: {tx_info.tx_hash}")
print(f"Transaction Result/Return Data: {tx_result}")
# call contract method (not stateful)
tx_info, tx_result = crypto_hamster.get_hamster_dna("SuperCryptoHamster", None)

print(f"Transaction Result/Return Data: {tx_result}")