How To Setup EOS Testnet?

Maciej Janiszewski
26 April 2020 · 13 min read

In this post, I will go into details on how to set up your own EOS testnet. It pieces together bits of info from StackOverflow, official docs and personal experience. If you’re in the hurry, you can skip ahead and just use our Docker image, which is available here. At Ulam Labs, we took on the task of running our custom EOS testnet. There are two reasons behind that:

  1. Those pesky faucets - we’ll be making a lot of new accounts and submitting transactions. Since token supply on the network is limited, testnets such as Jungle make use of faucets to distribute them. If you’re not familiar with faucets, it’s basically a website where you can enter your address and a small amount of coins will be sent to you. While we could use that, it would slow us down. On a custom testnet we control all the coins and we can do whatever we want with them.
  2. Testing - we want to test our code against a real RPC node, while also not relying on an external service. We’d ideally like to have the same initial state of the blockchain on each run for the tests to be reproducible. It’d be annoying to deal with build failures caused by, for example, insufficient resources on our testing account. We can solve this by utilizing a Docker image of a custom testnet in our CI pipeline.

Apart from the network itself, it would also be nice to have a working block explorer and a wallet. Fortunately, the EOS community has us covered. There is an excellent explorer called, developed by EOS Cafe Block and HKEOS block producers. You can simply choose “Local testnet” from the menu, enter the URL, port and you’re done. It integrates well with another app, called Scatter. It’s an open-source wallet app supporting multiple blockchains, including EOS.

How to setup EOS testnet? - Adding custom network#max500#
How to setup EOS testnet? - Adding custom network

Which EOS version should I use?

At the time of writing this post, I recommend using EOS v1.8.x. Newer versions of contracts built for EOS 2.0 have a classic chicken and egg problem. Contracts depend on the WTMSIG_BLOCK_SIGNATURES protocol feature, but you can’t enable it unless you deployed the system contract first. The workaround, suggested by the docs, is to deploy the old version first, enable required protocol features and then upgrade. It doesn’t help that you need to build these contracts against two different versions of development toolkit. There is a pull request aiming to streamline this process. Since both versions of EOS are currently supported, we can simply use the older one.

Preparing our EOS testnet

To make things easier to follow regardless of the platform you’re using, we’ll use Docker. While themselves no longer provide Docker images, the team behind EOS Studio does a great job with that. The first step is to generate keys for the eosio account. For that, we need the cleos command line tool. Let’s start by running an interactive shell in the Docker container.

$ docker run -it eostudio/eos:v1.8.13 /bin/bash

The process itself is quite straightforward. First, create a new wallet:

root@4442c7dfe631:/$ cleos wallet create --to-console

Then, create the keypair:

root@4442c7dfe631:/$ cleos wallet create_key

And export it, you’ll be asked for the password from step one.

root@4442c7dfe631:/$ cleos wallet private_keys
password: [[

Save it somewhere, we’ll need this later and we won’t be using that container anymore. The first string, prefixed with EOS is a public key, the other one is a private key. Now that we have the keys, you can exit the container and we can prepare a genesis.json file. This file defines initial parameters for our network, such as the key for eosio account, timestamp of first block etc. Contents of this file will be hashed to generate a unique chain ID. This identifier is used to tell it apart from other networks.

  "initial_timestamp": "2018-03-02T12:00:00.000",
  "initial_key": "your public key",
  "initial_configuration": {
    "max_block_net_usage": 1048576,
    "target_block_net_usage_pct": 1000,
    "max_transaction_net_usage": 524288,
    "base_per_transaction_net_usage": 12,
    "net_usage_leeway": 500,
    "context_free_discount_net_usage_num": 20,
    "context_free_discount_net_usage_den": 100,
    "max_block_cpu_usage": 200000,
    "target_block_cpu_usage_pct": 10000,
    "max_transaction_cpu_usage": 150000,
    "min_transaction_cpu_usage": 100,
    "max_transaction_lifetime": 3600,
    "deferred_trx_expiration_window": 600,
    "max_transaction_delay": 3888000,
    "max_inline_action_size": 4096,
    "max_inline_action_depth": 4,
    "max_authority_depth": 6

Don’t forget to replace “your public key” with the key from the previous step. You can leave the other parameters as-is. The other file that we need to provide is config.ini. This will tell nodeos which plugins should be enabled etc. While all of these can be passed as arguments, we’ll end up with quite a long list of options. Simply mounting a file to our container is just more convenient, especially if we wanted to adjust them later.

# enabled plugins
plugin = eosio::chain_api_plugin
plugin = eosio::history_plugin
plugin = eosio::history_api_plugin
plugin = eosio::http_plugin
plugin = eosio::producer_plugin
plugin = eosio::producer_api_plugin

# chain plugin
chain-state-db-size-mb = 65536
reversible-blocks-db-size-mb = 65536
contracts-console = true

# producer_plugin
enable-stale-production = true
producer-name = eosio
trusted-producer = eosio
max-transaction-time = 150000
subjective-cpu-leeway-us = 150000
private-key = ["your public key","your private key"]

# http_plugin
http-max-response-time-ms = 1000
access-control-allow-origin = *
http-validate-host = false
http-server-address =
verbose-http-errors = true

# history_plugin
filter-on = *

With both files in our working directory, we can start our genesis node.

$ docker run -d \
    -v $PWD/genesis.json:/genesis.json \
    -v $PWD/config.ini:/root/config.ini \
    -p 8888:8888 \
    --name eosio \
    eostudio/eos:v1.8.12 \
    nodeos --config-dir /root \

This will spin up a fresh EOS node with our config. It should be up and listening on port 8888. You can use explorer to verify that it works. Clicking through the explorer, you can also notice a couple of things. First of all, there is only 1 account, called eosio. There is no EOS cryptocurrency nor resource limits. It’s a clean slate.

EOS testnet - You can use explorer to verify if fresh EOS node with our config works.#max500#
Setting up EOS testnet - You can use explorer to verify if fresh EOS node with our config works.

Bootstrapping the network

Time to get our hands dirty. What’s different about EOS compared to other blockchains is that it takes smart contracts to a whole new level. A lot of basic functionality that you take for granted on either production network or Jungle Testnet is simply not there by default. You’ll have to jump through a couple more hoops to get that. Let’s take for example the EOS digital currency itself. This functionality is provided by eosio.token smart contract. You have to build and deploy all the basic contracts by yourself. Spawn a shell inside a container:

$ docker exec -it eosio /bin/bash

To be able to build the contracts, we need to install EOSIO.CDT. CDT stands for Contract Development Toolkit. For eosio 1.8.x, the recommended version of CDT is 1.6.3. Installing it is as simple as downloading a .deb package from GitHub and using apt to install it.

root@30f251b8d83b:/$ cd /tmp
root@30f251b8d83b:/tmp$ wget
root@30f251b8d83b:/tmp$ apt install ./eosio.cdt_1.6.3-1-ubuntu-18.04_amd64.deb

To be able to build the contracts, we will also need a couple of tools such as Git, cmake and gcc compiler, these can also be installed using apt. Smart contracts on the EOS blockchain are developed using C++ and compiled to WebAssembly.

root@30f251b8d83b:/tmp$ apt -yq update && apt -yq install git cmake g++

Now we need to clone the contracts git repository. Recommended version is 1.8.3. As I mentioned earlier, newer versions require this one to be built and deployed first, as they depend on a protocol feature that can only be enabled by calling a method of system contract.

root@30f251b8d83b:/tmp$ git clone --branch v1.8.3 --single-branch
root@30f251b8d83b:/tmp$ cd eosio.contracts
root@30f251b8d83b:/tmp/eosio.contracts$ ./ -y

You will encounter some warnings, these can be safely ignored. The contracts depend on the PREACTIVATE_FEATURE protocol feature, so before we deploy them, we need to enable it:

root@30f251b8d83b:/tmp/eosio.contracts$ curl -X POST -d '{"protocol_features_to_activate": \["0ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd"]}

On EOS, a smart contract is always attached to the account. Aside from eosio account itself, we need to also create these:

  • eosio.token - manages tokens on EOS blockchain,
  • eosio.bpay - pays block producers for the blocks which they mined,
  • eosio.vpay - pays block producers per vote,
  • eosio.msig - handles multi-signing, essentially allows you to use your key to accept or refuse a proposed transaction,
  • eosio.names - handles premium name bidding. Premium account names can be shorter than 12 characters and be used as a prefix, similar to eosio;
  • eosio.ram - holds all the EOS which was spent on RAM bytes,
  • eosio.rex - EOS Resource Exchange, allows you to borrow and lend resources such as CPU, NET and RAM bytes,
  • eosio.ramfee - holds all the EOS which was spent on RAM fees in case it was acquired through REX,
  • eosio.saving - account funded through “inflation” mechanism built into the EOS protocol. Each time EOS supply is increased, a certain percentage of that amount is sent to this account;
  • eosio.stake - holds all staked resources,
  • eosio.wrap - allows block producers to execute privileged actions, requires about 70% of them to agree.

To do that, we need to import the private key which we created earlier:

root@30f251b8d83b:/tmp/eosio.contracts$ cleos wallet create -f /tmp/pass
root@30f251b8d83b:/tmp/eosio.contracts$ cleos wallet import --private-key [your private key]

Now we can use cleos to create all of these accounts. I recommend assigning your public key to a variable, because we’ll be using that one a lot. Every account on EOS needs to have at least one key assigned to it.

root@30f251b8d83b:/tmp/eosio.contracts$ for x in eosio.bpay eosio.msig eosio.names eosio.ram eosio.ramfee eosio.saving eosio.stake eosio.token eosio.vpay eosio.rex eosio.wrap;
    cleos create account eosio ${x} [your public key];

We also need to make the eosio.msig account privileged.

root@30f251b8d83b:/tmp/eosio.contracts$ cleos push action eosio setpriv '["eosio.msig", 1]' -p eosio@active

With accounts being ready, we can begin deploying smart contracts. The first one is a system contract. There are two example system contract implementations, eosio.bios, which provides just the basic functionality, such as activating protocol features, and eosio.system. We’ll use the second one, as it has all the bells and whistles such as resource management, name bidding etc.

root@30f251b8d83b:/tmp/eosio.contracts$ cleos set contract -x 3600 eosio ./build/contracts/eosio.system/

You might encounter an error stating that the transaction took too long. It’s really rare (~2% of cases) and unfortunately means you have to start again. More on that here. Remove the contents of /data, restart the container and start again. Fortunately once you’re past this point, the remaining contracts should go smoothly.

root@30f251b8d83b:/tmp/eosio.contracts$ for x in eosio.msig eosio.token eosio.wrap; 
    cleos set contract -x 3600 ${x} ./build/contracts/${x}/;

Optionally, you can enable these protocol features:

  • ONLY_BILL_FIRST_AUTHORIZER - resources will be deducted from the first authorizers account:
cleos push action eosio activate '["8ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a405"]' -p eosio@active
  • RAM_RESTRICTIONS - resolves the issue with unprivileged contracts being able to increase other accounts RAM usage:
cleos push action eosio activate '["4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d67"]' -p eosio@active
  • FIX_LINKAUTH_RESTRICTION - fixes bug in linkauth action:
cleos push action eosio activate '["e0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff526"]' -p eosio@active
  • ONLY_LINK_TO_EXISTING_PERMISSION - disallows linking to non-existing permission using linkauth action:
cleos push action eosio activate '["1a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241"]' -p eosio@active
  • DISALLOW_EMPTY_PRODUCER_SCHEDULE - disallows proposing empty producer schedule:
cleos push action eosio activate '["68dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428"]' -p eosio@active

While not required, these features are enabled on the mainnet, and we want ours to be as close to it as possible. Now that all the contracts and protocol features are in place, we can create our system token. It will be used for voting and buying resources. EOS protocol is based on a delegated proof-of-stake algorithm. Users can stake their cryptocurrency on either CPU or NET bandwidth, which buys them access to the network. This limit is reset every 24 hours. The other use of staked tokens is voting. Users use their staked tokens to vote for block producers, parties which are allowed to mine blocks. The more tokens you stake, the more power your vote has. We will create a new token with an initial supply of 90000000000.0000 tokens and call it EOS. You can also use a custom name such as BREAD.

root@30f251b8d83b:/$ cleos push action eosio.token create '[ "eosio", "90000000000.0000 EOS" ]' -p eosio.token@active
root@30f251b8d83b:/$ cleos push action eosio.token issue '[ "eosio", "90000000000.0000 EOS", "hello world" ]' -p eosio@active

Calling init method on eosio contract sets our token as a system token. The other parameter is precision - in our case it’s up to 4 decimal places.

root@30f251b8d83b:/$ cleos push action eosio init '["0", "4,EOS"]' -p eosio@active

With that in place, we’re basically up and running, but I’d like to add one last finishing touch. Currently, our chain is not activated, which means we can’t unstake EOS. For it to be considered activated, 15% of system tokens supply must be staked and participating in voting.

root@30f251b8d83b:/$ cleos system newaccount eosio --transfer billionaire1 your public key --stake-net "6750000000.0000 EOS" --stake-cpu "6750000000.0000 EOS" --buy-ram-kbytes 8192
root@30f251b8d83b:/$ cleos system regproducer eosio your public key
root@30f251b8d83b:/$ cleos system voteproducer prods billionaire1 eosio

Please bear in mind that the combined amount of tokens staked by the accounts has a direct impact on CPU and NET bandwidth prices.

Final notes

To be able to see transaction history in either the block explorer or to query it through the API, you need to enable the history plugin, which is deprecated and it’s not recommended to use it. The problem with the plugin is that it stores the entire blockchain history in memory. You could modify the “filter-on” parameter in config.ini. This tells the history plugin which actions it should index, which can reduce the memory usage. If you’re willing to go one step further, Greymass maintains a forked version of EOS with their custom history plugin. The fork not only stores the data on your hard drive instead of in RAM, it also allows you to define your custom data retention policy. You can read more on that in a post on their steemit.

Join us

Related blogposts:

How To Setup A Custom Bitcoin Testnet?

Podcast on The Differences Between Public Blockchains

The Challenges of Custom Cryptocurrency Wallet Development

Share on
Related posts
5 Best White Label Cryptocurrency Exchanges: A Comparison for Business Owners

5 Best White Label Cryptocurrency Exchanges: A Comparison for Business Owners

A cryptocurrency software development company has the uphill task of designing and building products for the burgeoning, open financial sector. Nevertheless, there’s a mushrooming of companies and…
8 min read
Best AML Software - Comparison for Crypto Businesses

Best AML Software - Comparison for Crypto Businesses

Money laundering is a grave concern for regulators and law enforcement agencies worldwide. According to a United Nations report, the total money laundered every year equals anywhere between 2% to…
8 min read

Tell us about your project

Get in touch and let’s build your project!