Build your first decentralized application (aepp) on Aeternity blockchain – Sophia ML smart contract – Address Book

Sophia aeternity
Sophia aeternity
Sophia aeternity

Æternity blockchain is live after long period of waiting, the community is big and the core developers are top skilled. The project offers smart contracts development platform for decentralized applications dapps or how they call them – æpps.

In this tutorial we are going to create a simple smart contract in Sophia ML programming language – address book aepp. We will create, compile, test and deploy the smart contract.


  • Docker
  • Forgae AEproject
  • Aecli * (optional if we want to do something with the sdk like encode/decode address by publicKey or signTx)

Docker we need for running local aeternity node for compiling, testing and development environment. Assuming you already have docker installed on your machine we move on, if not – please follow the guide in their official website and then continue with the steps below.

Forgae AEproject is an æternity development framework which helps with setting up a project. The framework makes the development of smart contracts in the aeternity network pretty easily. It provides commands for the compilation of smart contracts, running a local Epoch and unit testing the contracts.

We install it globally via:

npm install -g aeproject

After installing forgae AEproject we can verify everything went well with aeproject --help which will also get us familiarize with the commands and options it has.

Usage: aeproject [options] [command]

  -V, --version      output the version number
  -h, --help         output usage information

  init [options]     Initialize AEProject
  compile [options]  Compile contracts
  test [options]     Running the tests
  node [options]     Running a local node. Without any argument node will be runned with --start argument
  deploy [options]   Run deploy script

We are almost done with the setup. The last tool we need is aecli which is æternity’s JavaScript SDK command-line interface. It is still not available as a npm package, so for us to use it we should clone the repository, install from source and then link it to our local npm manually.

git clone

We then need to enter into the cloned repository with:

cd aepp-cli-js

Run npm link in order to link the AECLI to aecli/bin/aecli.js (If you have any folder permission issues, try running with sudo sudo npm link)

npm link command will create a symlink in the global node_modules folder and expose the CLI binary to be used through the terminal.

npm link

and lastly we should runnpm install so npm update its packages and symlinks.

npm install

Now if everything went well our environment is set and ready to start developing sophia smart contracts on æternity.

Project init

As always, first we need to create a directory where our project files will be placed:

mkdir addressbook-aepp
cd addressbook-aepp

Then we initialize our project with aeproject init which will create for us all the necessary files and directories.

aeproject init
Initialization of aeternity aepp with forgae
Initialization of aeternity aepp with forgae

If we look at our project directory we will see that we now have contracts, deployment, test directories where our focus will mainly be, as well as other config files like package.json and docker-compose.yml.

forgae init project directory
forgae init project directory

Now we need to start docker and then start our local node with:

aeproject node

The node command help developers run their local network on docker. The local network contains 3 nodes. To spawn a fully functional network takes couple of minutes. At the end of this command you will be presented with accounts that you can use in your unit tests.

forgae node initialized successfully
forgae node initialized successfully
forgae node gives us preset test accounts
forgae node gives us preset test accounts

To stop the local node, simply run

aeproject node --stop

Additionally we need to start the local compiler the same way as we start the node.

aeproject compiler

Write Sophia ML smart contracts

When we initialized our project with aeproject init it automatically created for us an ExampleContract.aes file, which name speaks for what it is. Its content looks like this:

@compiler >= 4
contract ExampleContract =
    type state = ()
    function main(x : int) = x

This example contract has one function which accepts a single argument of type int and returns the value of the passed argument x.

If this is your first time looking at Sophia smart contract, you probably notice that it is quite different from Solidity for example, and looks much similar to Python.

Aeternity’s Sophia is a ML-family language. It is strongly typed and has restricted mutable state. Sophia is developed to be used for creating smart contracts on Aeternity Blockchain, so some of the conventional languages’ features are missing, but other blockchain specific primitives, types and constructions are added and supported.

Okay, we move on to the fun part. We now hit the delete button and get rid of the ExampleContract.aes.

We create our AddressBook.aes sophia smart contract in the contractsfolder and open it with our favorite editor (in my case VIM).

We define the name of our smart contract, following the example above:

contract AddressBook =

Note: Indentation is important in Sophia language, so keep that in mind.

Sophia uses Python-style layout rules to group declarations and statements. A layout block with more than one element must start on a separate line and be indented more than the currently enclosing layout block. Blocks with a single element can be written on the same line as the previous token.

Each element of the block must share the same indentation and no part of an element may be indented less than the indentation of the block.

Okay, the first thing we do after that is defining our person and state records and create the initfunction.

@compiler >= 4
contract AddressBook =

   record person =
     { first_name : string
     , last_name : string
     , age : int }

   record state  = { people : map(address, person) }

   entrypoint init() = { people = {} }

Let us focus on some info from the sophia documentation so we know what we’ve done so far:

Sophia does not have arbitrary mutable state, but only a limited form of state associated with each contract instance.

  • Each contract defines a type state encapsulating its mutable state.
  • The initial state of a contract is computed by the contract’s init function. The init function is pure and returns the initial state as its return value. At contract creation time, the init function is executed and its result is stored as the contract state.
  • The value of the state is accessible from inside the contract through an implicitly bound variable state.
  • State updates are performed by calling a function put : state => ().
  • Aside from the put function (and similar functions for transactions and events), the language is purely functional.
  • Functions modifying the state need to be annotated with the stateful keyword.

A contract may define a type state encapsulating its local state. The state must be initialised when instantiating a contract. This is done by the init function which can take arbitrary arguments and is called on contract instance creation.

sophia documentation

We now have to create setter function for saving person’s details, and getter functions for getting them.

   stateful entrypoint add_person(person_address: address, first_name': string, last_name': string, age: int) =
      let new_person : person =
         { first_name = first_name',
           last_name = last_name',
           age = age }
      put(state{ people[person_address] = new_person })

For our getter functions we will need a helper function which will allow us to easily search in the map we‘ve created.

function lookupByAddress(k : address, m, v) =
      switch(Map.lookup(k, m))
         None    => v
         Some(x) => x

and our getters:

   entrypoint get_person(person_address : address) : person = 
      switch(Map.lookup(person_address, state.people))
         None => abort("No data for that person")
         Some(person_found) => person_found

Now we are done with the smart contract. It should look like this:

AddressBook.aes – aeternity smart contract

Compile Sophia ML contracts

Compiling sophia smart contracts is done via:

aeproject compile

And if everything went well we should see a similar output

Compiling sophia smart contract
Compiling sophia smart contract

The compile command compiles Sophia contract. It’s recommended to use .aes file extension. Default directory is $projectDir/contracts. The result of the compilation is the contract bytecode printed in the console. Additional --path parameter is available, which can specify the path to the contract to be compiled.

Writing Unit Tests

First, we need to edit our deployment script, so it knows which contracts should be using. We only replace the ExampleContract with our own AddressBook in ./deployments/deploy.js. It should now look like this:

AddressBook.aes deployment script

You probably have noticed that we pass the address as 0xe9bbf604e611b5460a3b3999e9771b6f60417d73ce7c5519e12f7e127a1225ca instead of aeternity’s ak_2mwRmUeYmfuW93ti9HMSUJzCk1EYcQEfikVSzgo6k2VghsWhgU. That is because sophia is using the bytes hex representation of our base58 encoded address. We can use aecli to decode our address and prepend 0x afterwards :

aecli crypto decode ak_2mwRmUeYmfuW93ti9HMSUJzCk1EYcQEfikVSzgo6k2VghsWhgU

We should receive this output:

Decoded address (hex): e9bbf604e611b5460a3b3999e9771b6f60417d73ce7c5519e12f7e127a1225ca

aecli crypto decode aeternity address
aecli crypto decode aeternity address

Writing unit test for aeternity sophia smart contract is similar to writing unit tests for Ethereum solidity smart contract.

The interesting and thing is passing arguments to functions. As you can see, we are now passing the our argsaddress,first_name,last_name, and age as tuple string.

      args: `(${address}, "${first_name}", "${last_name}", ${age})`,

Note: If you are passing string, do not forget to add quotes (") around the string too (("Some string")). More than one parameter can be passed separated by coma (("Some string", 123, 45, "Other string")))deploy

Another quite handy thing is that the function call result comes with built in decode() function, which you can use for decoding the function output by passing the type you want to decode it to e.g. primitives like int , string or bool. This is quite important for our unit tests so we know how we are going to assert the results.

    const addPersonResult = await addPerson.decode('bool');
AddressBook aepp unit tests

Now if we run aeproject test we should see our unit tests output:

aeproject test
forgae test output - addressbook aepp
forgae test output – addressbook aepp


Writing smart contracts on Sophia is very intuitive if you have previous experience writing smart contracts or experience with programming languages like Python.

We will continue this series with some more sophisticated examples which will include deployment on testnet and mainnet and more soon. If you don’t want to miss the new content – join our newsletter to receive all our new content.

Useful links


Also published on Medium.

About the author

Founder & CEO

Milen Radkov has experience building and delivering successful complex software systems and projects for both big enterprises and small startups. Being actively developing across different blockchain protocols, platforms and projects for the past 5 years, he has gained extensive experience in blockchain development, architectures, consensus algorithms and token economics. Milen is a well-known figure in the blockchain space.


Let's build the decentralized future together!

Subscribe for updates from the blog


  1. Great tutorial! I was wondering how aeternity blockchain smart contracts will look like. Thank you for sharing this!

  2. Hello, I have been trying to install this on mac but keep having error at the first stage (npm install -g forgae ). google but still can’t resolve the issue. Thank you.

  3. Pingback: WOW
  4. Good afternoon I have a problem with this example when I run the test unit, I attach the result on the screen, thankful

    AddressBook Contract
        1) "before all" hook
      0 passing (168ms)
      1 failing
      1) AddressBook Contract
           "before all" hook:
         TypeError: Cannot read property 'indexOf' of undefined
          at e.exports (node_modules/@aeternity/aepp-sdk/dist/aepp-sdk.js:1:24543)
          at e.exports (node_modules/@aeternity/aepp-sdk/dist/aepp-sdk.js:1:9268)
          at t (node_modules/@aeternity/aepp-sdk/dist/aepp-sdk.js:1:2939)
          at Object.urlFor (node_modules/@aeternity/aepp-sdk/dist/aepp-sdk.js:1:102860)
          at /root/aeproject/addressbook-aepp_aaa/node_modules/@aeternity/aepp-sdk/dist/aepp-sdk.js:1:101999
          at e.f (node_modules/@aeternity/aepp-sdk/dist/aepp-sdk.js:1:5850)
          at e.@@transducer/step (node_modules/@aeternity/aepp-sdk/dist/aepp-sdk.js:1:23656)
          at /root/aeproject/addressbook-aepp_aaa/node_modules/@aeternity/aepp-sdk/dist/aepp-sdk.js:1:7422
          at e.exports (node_modules/@aeternity/aepp-sdk/dist/aepp-sdk.js:1:7532)
          at /root/aeproject/addressbook-aepp_aaa/node_modules/@aeternity/aepp-sdk/dist/aepp-sdk.js:1:5821
    (node:13216) UnhandledPromiseRejectionWarning: 1
    (node:13216) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
    (node:13216) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
    1. Hello Marco,

      The tutorial was outdated and was missing the latest developments in the sophia syntax and the tools and sdks around aeternity. It was updated now and everything should be fine if you follow the steps.


  5. Wonderful tutorial, thanks but I keep on getting the error below after running “aeproject node” command
    Error: Command failed: export COLUMNS=1000 && docker-compose -f /home/jesulonimi/Web Projects/AnotherReactAepp/docker-compose.yml ps
    No such command: Projects/AnotherReactAepp/docker_compose.yml

    Any help is appreciated.

  6. Good evening Milen Radkov, I already did the tutorial run and everything worked, just some configuration details but they were resolved. Very grateful for your support, regards

    ===== Starting Tests =====

    Address Book Contract
    ✓ Deploy AddressBook Contract (5128ms)
    ✓ Should store provided person correctly (136ms)
    ✓ Should read person correctly (5205ms)

  7. :aeproject node

    Error: Command failed: docker-compose -f docker-compose.yml -f docker-compose.compiler.yml ps

    thanks so much

    1. Make sure you have docker installed and running on the machine you are trying this.

      Try using aeproject env instead for spinning up a local environment.

Leave a Reply

Your email address will not be published.