Private EOS network — Part 1: Setup

Levan Ilashvili
21 min readFeb 7, 2022

--

Step-by-step guide for setting up a private EOS network using EOSIO 2.1

Prerequisites:

According to official docs, EOSIO 2.1 supports the following operating systems:

Linux:

  • Amazon Linux 2
  • CentOS Linux 8.x
  • CentOS Linux 7.x
  • Ubuntu 20.04
  • Ubuntu 18.04

Mac OS:

  • macOS 10.14 (Mojave) or later

Make sure you are running one of those and you have root access

For this article I’ve set up a t3.2xlarge EC2 instance on AWS, running Ubuntu 18.04. So, let’s ssh to our server and get started.

Step 1: Installing EOSIO pre-built binaries

It’s always a good idea to start with

sudo apt update

Then, download the correct binaries for your OS. For Ubuntu 18.04 you can use the command:

wget https://github.com/eosio/eos/releases/download/v2.1.0/eosio_2.1.0-1-ubuntu-18.04_amd64.deb

You will find the list of all available releases on GitHub

Install EOSIO (again, the installer will have a different filename for different OS):

sudo apt install ./eosio_2.1.0-1-ubuntu-18.04_amd64.deb

Verify that everything went fine:

ubuntu@server:~$ nodeos -v

The output should be:

v2.1.0

Step 2: Install EOSIO Contract Development Toolkit

The next step is installing the Contract Development Toolkit — EOSIO.CDT v1.8.1 — you’ll find download URLs on GitHub

wget https://github.com/EOSIO/eosio.cdt/releases/download/v1.8.1/eosio.cdt_1.8.1-1-ubuntu-18.04_amd64.deb

and install it:

sudo apt install ./eosio.cdt_1.8.1-1-ubuntu-18.04_amd64.deb

Step 3: Create Development Wallet

The next step is creating our development wallet:

cleos wallet create --to-console

You will see the following output:

"/usr/opt/eosio/2.1.0/bin/keosd" launched
Creating wallet: default
Save password to use in the future to unlock this wallet.
Without password imported keys will not be retrievable.
"PW......"

Of course, you can customize your wallet name and output password in a file instead of the console. E.g. running:

cleos wallet create -n levan -f levan.pwd

will create a wallet named levan and password will be saved in a file levan.pwd

After creating a wallet if you run

cleos wallet list

you will see the following output:

Wallets:
[
"default *"
]

An asterisk next to the wallet name means that the wallet is unlocked. You can lock the wallet by running

cleos wallet lock

and unlock it again by

cleos wallet unlock

(this will prompt your wallet password)

Now, let’s create new private/public keypair and import them into our wallet:

cleos create key --to-console

you’ll see Private and Public keys in the console — something like this:

Private key: 5JtMzDTd4gnYRxBCLsmujysgKygk99VjtfGuBNau1x1VsKBp7Fj
Public key: EOS6XLDpeALieoCa4jUnWHdW86phVf3xsstny6NLWfuca1yFJ83ah

you must NEVER share your private key with anyone, and you must save them SECURELY. Since this is a tutorial, I don’t mind sharing them here 😀

Now, let’s import that key in the wallet:

cleos wallet import

After importing the key you’ll get the following message:

private key: imported private key for: EOS6XLDpeALieoCa4jUnWHdW86phVf3xsstny6NLWfuca1yFJ83ah

Step 4: Prepare files and directories:

After playing with cleos it’s time to create directories and files for our node. Let’s say we will run our genesis node in ~/eos/genesis :

cd ~
mkdir eos
cd eos
mkdir genesis
cd genesis

now, create a genesis.json file in eos directory.

cd ~/eos
touch genesis.json
nano genesis.json

paste following content in genesis.json :

{
"initial_timestamp": "2018-12-05T08:55:11.000",
"initial_key": "EOS_PUB_DEV_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": 100000,
"target_block_cpu_usage_pct": 500,
"max_transaction_cpu_usage": 50000,
"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
},
"initial_chain_id": "0000000000000000000000000000000000000000000000000000000000000000"
}

Make sure you replace EOS_PUB_DEV_KEY with the development key created in the previous step

Save and exit editor — reminder — [CTRL] + O and then [Enter] — will write out genesis.json file, [CTRL] + X will exit nano editor.

Checkpoint — let’s make sure we have directory structure set up properly:

cd ~/eos
ls -Alhr --time-style=+""

should give you the following output:

total 8.0K
-rw-rw-r-- 1 ubuntu ubuntu 904 genesis.json
drwxrwxr-x 2 ubuntu ubuntu 4.0K genesis

Let’s move on to creating a bunch of shell executable files in genesis directory:

Prepare genesis_start.sh — this will be used to start our genesis node

cd ~/eos/genesis
nano genesis_start.sh

paste following into the newly created file:

#!/bin/bash
DATADIR="./blockchain"

if [ ! -d $DATADIR ]; then
mkdir -p $DATADIR;
fi

nodeos \
--genesis-json $DATADIR"/../../genesis.json" \
--signature-provider EOS_PUB_DEV_KEY=KEY:EOS_PRIV_DEV_KEY \
--plugin eosio::producer_plugin \
--plugin eosio::producer_api_plugin \
--plugin eosio::chain_plugin \
--plugin eosio::chain_api_plugin \
--plugin eosio::http_plugin \
--plugin eosio::history_api_plugin \
--plugin eosio::history_plugin \
--data-dir $DATADIR"/data" \
--blocks-dir $DATADIR"/blocks" \
--config-dir $DATADIR"/config" \
--producer-name eosio \
--http-server-address 127.0.0.1:8888 \
--p2p-listen-endpoint 127.0.0.1:9010 \
--access-control-allow-origin=* \
--contracts-console \
--http-validate-host=false \
--verbose-http-errors \
--enable-stale-production \
--p2p-peer-address localhost:9011 \
--p2p-peer-address localhost:9012 \
>> $DATADIR"/nodeos.log" 2>&1 & \
echo $! > $DATADIR"/eosd.pid"

make sure you replace EOS_PUB_DEV_KEY and EOS_PRIV_DEV_KEYwith public and private keys created in step 3.

You’ll notice that this command sets p2p peer addresses — those are not set up yet, but that won’t be a problem

Save file and exit nano — [CTRL + O] , enter , [CTRL+X]

Let’s create another file — which will stop nodeos :

cd ~/eos/genesis
nano stop.sh

paste the following:

#!/bin/bash
DATADIR="./blockchain/"

if [ -f $DATADIR"/eosd.pid" ]; then
pid=`cat $DATADIR"/eosd.pid"`
echo $pid
kill $pid
rm -r $DATADIR"/eosd.pid"
echo -ne "Stoping Node"
while true; do
[ ! -d "/proc/$pid/fd" ] && break
echo -ne "."
sleep 1
done
echo -ne "\rNode Stopped. \n"
fi

save and exit nano.

You’ll notice that in genesis_start.sh we specified --genesis-json parameter. Once nodeos initializes the blockchain database and starts producing blocks, that genesis_start.sh will be useless for starting nodeos again. So, let’s create start.sh file, which we’ll use to start the genesis node after the first run.

cd ~/eos/genesis
nano start.sh

and paste the following content:

#!/bin/bashDATADIR="./blockchain"nodeos \
--signature-provider EOS6XLDpeALieoCa4jUnWHdW86phVf3xsstny6NLWfuca1yFJ83ah=KEY:5JtMzDTd4gnYRxBCLsmujysgKygk99VjtfGuBNau1x1VsKBp7Fj \
--plugin eosio::producer_plugin \
--plugin eosio::producer_api_plugin \
--plugin eosio::chain_plugin \
--plugin eosio::chain_api_plugin \
--plugin eosio::http_plugin \
--plugin eosio::history_api_plugin \
--plugin eosio::history_plugin \
--data-dir $DATADIR"/data" \
--blocks-dir $DATADIR"/blocks" \
--config-dir $DATADIR"/config" \
--producer-name eosio \
--http-server-address 127.0.0.1:8888 \
--p2p-listen-endpoint 127.0.0.1:9010 \
--access-control-allow-origin=* \
--contracts-console \
--http-validate-host=false \
--verbose-http-errors \
--enable-stale-production \
--p2p-peer-address localhost:9011 \
--p2p-peer-address localhost:9012 \
--p2p-peer-address localhost:9013 \
>> $DATADIR"/nodeos.log" 2>&1 & \
echo $! > $DATADIR"/eosd.pid"

save and exit nano.

and finally, let’s create clear.sh script, which will simply erase all the data — that will be useful if we want to start fresh:

nano clear.sh

and paste the following:

#!/bin/bash
rm -fr blockchain
ls -al

save nano and exit.

Finally, let’s chmod all our executables:

cd ~/eos/genesis
chmod 755 *.sh

Checkpoint: let’s make sure we have all files set up properly:

cd ~/eos
ls -RAlhr --time-style=+""

should give us the following output:

total 8.0Ktotal 8.0K-rw-rw-r-- 1 ubuntu ubuntu  904  genesis.json
drwxrwxr-x 2 ubuntu ubuntu 4.0K genesis
./genesis:total 16K-rwxr-xr-x 1 ubuntu ubuntu 281 stop.sh
-rwxr-xr-x 1 ubuntu ubuntu 922 start.sh
-rwxr-xr-x 1 ubuntu ubuntu 1021 genesis_start.sh
-rwxr-xr-x 1 ubuntu ubuntu 37 clear.sh

Okay — now we are ready to start our chain:

cd ~/eos/genesis
./genesis_start.sh

rush into blockchain directory and check out nodeos.log file:

cd ~/eos/genesis/blockchain
tail -f nodeos.log

if everything went well, you should see that nodeos is running and new blocks are produced:

...info  2022-02-06T22:24:03.901 nodeos    producer_plugin.cpp:2333      produce_block        ] Produced block 5604a533b6a34d3c... #363 @ 2022-02-06T22:24:04.000 signed by eosio [trxs: 0, lib: 362, confirmed: 0]info  2022-02-06T22:24:04.400 nodeos    producer_plugin.cpp:2333      produce_block        ] Produced block 27f155791ccf3768... #364 @ 2022-02-06T22:24:04.500 signed by eosio [trxs: 0, lib: 363, confirmed: 0]info  2022-02-06T22:24:04.900 nodeos    producer_plugin.cpp:2333      produce_block        ] Produced block 48f7cbe496d0022c... #365 @ 2022-02-06T22:24:05.000 signed by eosio [trxs: 0, lib: 364, confirmed: 0]...

Step 5: Create system accounts:

We need the following system accounts

eosio.bpay
eosio.msig
eosio.names
eosio.ram
eosio.ramfee
eosio.saving
eosio.stake
eosio.token
eosio.vpay
eosio.rex

to make things easy — let’s use the same keys for all of them.

cleos create key --to-console

save generated keys in a safe place and then

cleos wallet import --private-key

paste generated private key once prompted and you should see something like this:

private key: imported private key for: EOS6AM6LW3sbeJctXe43cBB7i7khUA3oMoXGisZHXhjKk2rxQ4rQt

now, create all the accounts listed above:

cleos create account eosio eosio.bpay EOS6AM6LW3sbeJctXe43cBB7i7khUA3oMoXGisZHXhjKk2rxQ4rQt

(don’t forget to replace the public key EOS6AM6LW3sbeJctXe43cBB7i7khUA3oMoXGisZHXhjKk2rxQ4rQt with the one you generated)

You should see the output similar to this:

executed transaction: 10bbb0781d618ff8a5a885c43540edacb8baf5d24d5a724fb6f4bc6ad7ffdfdb  200 bytes  227 us#         eosio <= eosio::newaccount            {"creator":"eosio","name":"eosio.bpay","owner":{"threshold":1,"keys":[{"key":"EOS6AM6LW3sbeJctXe43cB...warning: transaction executed locally, but may not be confirmed by the network yet       ]

now, repeat this for all accounts in the list

eosio.bpay
eosio.msig
eosio.names
eosio.ram
eosio.ramfee
eosio.saving
eosio.stake
eosio.token
eosio.vpay
eosio.rex

Step 6: Build the system contracts

If we want to have a functional EOSIO chain, we need the following system contracts: eosio.system , eosio.msig , eosio.token and eosio.boot . You’ll find the first three of those in eosio.contracts repository, and eosio.boot in eos repository.

Before we build anything, we need to make sure we have something to build with:

sudo apt-get install build-essential cmake

Let’s start with eosio.contracts repo:

cd ~
git clone https://github.com/EOSIO/eosio.contracts.git
cd eosio.contracts/
git checkout release/1.9.x

and run build.sh :

./build.sh

After lots of warnings, you should see something similar:

[100%] Linking CXX executable eosio.system.wasm
[100%] Built target eosio.system
[ 77%] No install step for 'contracts_project'
[ 88%] No test step for 'contracts_project'
[100%] Completed 'contracts_project'
[100%] Built target contracts_project

let’s make sure our build/contracts directory has everything we need:

cd ~/eosio.contracts/build/contracts
ls -RAlhr --time-style=+""

and make sure eosio.token , eosio.system , eosio.msig directories have wasm and abi files.

Next — let’s build eosio.boot :

cd ~/
mkdir source
cd source
git clone https://github.com/EOSIO/eos.git
cd eos
git checkout release/2.1.x
cd contracts/contracts/eosio.boot/
mkdir build
cd build
cmake ..
make

you should see something similar at the end of the output:

-- Build files have been written to: /home/ubuntu/source/eos/contracts/contracts/eosio.boot/build

If you followed the same folder structure like me, you should have contracts built in ~/eosio.contracts/build/contracts directory and eosio.boot built in ~/source/eos/contracts/contracts/eosio.boot/build . I’ll refer to contracts one as EOSIO_CONTRACTS_DIR and boot as EOSIO_BOOT_DIR

Step 7: Deploy contracts

Let’s start with eosio.token

cleos set contract eosio.token ~/eosio.contracts/build/contracts/eosio.token

the output should look similar to this:

Reading WASM from /home/ubuntu/eosio.contracts/build/contracts/eosio.token/eosio.token.wasm...Publishing contract...executed transaction: ba3d2cf5580017e6347779ae08e57386c3cf4deca164ee3d3f424a121178c1ce  19368 bytes  2817 us#         eosio <= eosio::setcode               {"account":"eosio.token","vmtype":0,"vmversion":0,"code":"0061736d010000000180022860000060037f7f7f01...#         eosio <= eosio::setabi                {"account":"eosio.token","abi":"0e656f73696f3a3a6162692f312e320008076163636f756e7400010762616c616e63...warning: transaction executed locally, but may not be confirmed by the network yet         ]

now — eosio.msig

cleos set contract eosio.msig ~/eosio.contracts/build/contracts/eosio.msig

which should result in:

Reading WASM from /home/ubuntu/eosio.contracts/build/contracts/eosio.msig/eosio.msig.wasm...Publishing contract...executed transaction: 830bb1a4e2a1f1747618f85416c479724bcb137030573066163f638ee4281d3d  23256 bytes  3224 us#         eosio <= eosio::setcode               {"account":"eosio.msig","vmtype":0,"vmversion":0,"code":"0061736d0100000001b8022f60000060037f7f7f017...#         eosio <= eosio::setabi                {"account":"eosio.msig","abi":"0e656f73696f3a3a6162692f312e32001006616374696f6e0004076163636f756e740...warning: transaction executed locally, but may not be confirmed by the network yet         ]

Now we can create and allocate SYS tokens:

cleos push action eosio.token create '[ "eosio", "10000000000.0000 SYS" ]' -p eosio.token@active

this will create SYS token with max. supply of 10000000000.0000 SYS. The output should look like this:

executed transaction: 808b5ce23b22dcde5ccf6a9554cb30165f51eb071d3e2eaa73f20d98dde7beb1  120 bytes  297 us#   eosio.token <= eosio.token::create          {"issuer":"eosio","maximum_supply":"10000000000.0000 SYS"}warning: transaction executed locally, but may not be confirmed by the network yet         ]

After creating SYS tokens we can issue them:

cleos push action eosio.token issue '[ "eosio", "1000000000.0000 SYS", "memo" ]' -p eosio@active

the output should look like this:

executed transaction: 48f032dbd12b23e5e2794c0869c2a24c48b8065059df597df191f0673d6a2da4  128 bytes  203 us#   eosio.token <= eosio.token::issue           {"to":"eosio","quantity":"1000000000.0000 SYS","memo":"memo"}warning: transaction executed locally, but may not be confirmed by the network yet         ]

Let’s resume deploying system contracts:

According to EOSIO docs,

All of the protocol upgrade features introduced in v1.8 and on subsequent versions require a special protocol feature (codenamed PREACTIVATE_FEATURE) to be activated and for an updated version of the system contract that makes use of the functionality introduced by that feature to be deployed.

So, let’s activate it:

curl --request POST \
--url http://127.0.0.1:8888/v1/producer/schedule_protocol_feature_activations \
-d '{"protocol_features_to_activate": ["0ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd"]}'

you should get a simple response:

{"result":"ok"

Now, set the eosio.boot contract. First — move to ~/source/eos/contracts/contracts/eosio.boot/build/ create eosio.boot and copy wasm and abi files there:

cd ~/source/eos/contracts/contracts/eosio.boot/build/
mkdir eosio.boot
cp eosio.boot.abi eosio.boot
cp eosio.boot.wasm eosio.boot
cd eosio.boot/
ls

after verifying that both files are there, you can run:

cleos set contract eosio ~/source/eos/contracts/contracts/eosio.boot/build/eosio.boot

the result will look like this:

Reading WASM from /home/ubuntu/source/eos/contracts/contracts/eosio.boot/build/eosio.boot/eosio.boot.wasm...Publishing contract...executed transaction: 05f778a5c6f66d385f78c17f765966b3edf01f64da84b8e1b8e8d797c47e03db  2752 bytes  644 us#         eosio <= eosio::setcode               {"account":"eosio","vmtype":0,"vmversion":0,"code":"0061736d01000000013e0c60000060027f7f0060017e0060...#         eosio <= eosio::setabi                {"account":"eosio","abi":"0e656f73696f3a3a6162692f312e32001008616374697661746500010e666561747572655f...

Now let’s activate all of the recommended protocol features:

# KV_DATABASE
cleos push action eosio activate '["825ee6288fb1373eab1b5187ec2f04f6eacb39cb3a97f356a07c91622dd61d16"]' -p eosio

# ACTION_RETURN_VALUE
cleos push action eosio activate '["c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead45071"]' -p eosio

# CONFIGURABLE_WASM_LIMITS
cleos push action eosio activate '["bf61537fd21c61a60e542a5d66c3f6a78da0589336868307f94a82bccea84e88"]' -p eosio

# BLOCKCHAIN_PARAMETERS
cleos push action eosio activate '["5443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b4"]' -p eosio

# GET_SENDER
cleos push action eosio activate '["f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d"]' -p eosio

# FORWARD_SETCODE
cleos push action eosio activate '["2652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25"]' -p eosio

# ONLY_BILL_FIRST_AUTHORIZER
cleos push action eosio activate '["8ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a405"]' -p eosio

# RESTRICT_ACTION_TO_SELF
cleos push action eosio activate '["ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c43"]' -p eosio

# DISALLOW_EMPTY_PRODUCER_SCHEDULE
cleos push action eosio activate '["68dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428"]' -p eosio

# FIX_LINKAUTH_RESTRICTION
cleos push action eosio activate '["e0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff526"]' -p eosio

# REPLACE_DEFERRED
cleos push action eosio activate '["ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99"]' -p eosio

# NO_DUPLICATE_DEFERRED_ID
cleos push action eosio activate '["4a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0f"]' -p eosio

# ONLY_LINK_TO_EXISTING_PERMISSION
cleos push action eosio activate '["1a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241"]' -p eosio

# RAM_RESTRICTIONS
cleos push action eosio activate '["4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d67"]' -p eosio

# WEBAUTHN_KEY
cleos push action eosio activate '["4fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2"]' -p eosio

# WTMSIG_BLOCK_SIGNATURES
cleos push action eosio activate '["299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707"]' -p eosio

and finally, let’s deploy eosio.system contract:

cleos set contract eosio ~/eosio.contracts/build/contracts/eosio.system

the output should look like this:

Reading WASM from /home/ubuntu/eosio.contracts/build/contracts/eosio.system/eosio.system.wasm...Publishing contract...executed transaction: af2fad9a9b7d807c7a299667073a6f720b22c6c6b0d207a059af6fac56e0bc80  89832 bytes  11252 us#         eosio <= eosio::setcode               {"account":"eosio","vmtype":0,"vmversion":0,"code":"0061736d010000000193044a60000060037f7f7f017f6003...#         eosio <= eosio::setabi                {"account":"eosio","abi":"0e656f73696f3a3a6162692f312e320117626c6f636b5f7369676e696e675f617574686f72...

Let’s have a look at the genesis node logs:

tail -f ~/eos/genesis/blockchain/nodeos.log

If you didn’t break anything, you should see node running and producing blocks 😀

As a result, we have

  • A single block producer running and producing blocks
  • HTTP API exposed on 127.0.0.1:8888

let’s verify that everything is fine:

cleos get info 

should output something similar:

{"server_version": "26a4d285","chain_id": "15cc2a4b6ffe0d0cb878e6bc13eb8e8cb559a744436502ad984940a80ea98e50","head_block_num": 10177,"last_irreversible_block_num": 10176,"last_irreversible_block_id": "000027c025ff3f66ea647dc696b5a438f8ae27cf9040910bd29cff628fe4feee","head_block_id": "000027c12f76f01ec8d45c513e1992d8ba27e05ed3cc9b0a4e383581e5df102d","head_block_time": "2022-02-06T23:45:57.500","head_block_producer": "eosio","virtual_block_cpu_limit": 100000000,"virtual_block_net_limit": 1048576000,"block_cpu_limit": 99900,"block_net_limit": 1048576,"server_version_string": "v2.1.0","fork_db_head_block_num": 10177,"fork_db_head_block_id": "000027c12f76f01ec8d45c513e1992d8ba27e05ed3cc9b0a4e383581e5df102d","server_full_version_string": "v2.1.0-26a4d285d0be1052d962149e431eb81500782991","last_irreversible_block_time": "2022-02-06T23:45:57.000"}

Almost perfect (couple more hours till perfection 😀 )

Step 8: Take a break, let’s see our blockchain in action

Let’s expose our node to the public. Again — this is just for fun — I don’t recommend exposing the producer node to the public.

Let’s stop nodeos , tweak start.sh script a bit and start it again:

cd ~/eos/genesis
./stop.sh
nano start.sh

find http-server-address , p2p-listen-endpoint and update the values to

--http-server-address 0.0.0.0:8888 \
--p2p-listen-endpoint 0.0.0.0:9010 \

Also, if you’re using AWS, make sure your security group’s inbound rules allow connections on ports 8888 and 9010.

Inbound Rules on AWS

Now, open your favorite EOS wallet (Anchor 😀 ) and set up a new chain:

Click `Manage Blockchains`

Then, click Add/Remove:

and Custom Blockchain:

Fill out the form:

Chain ID — is the chain_id we got after running cleos get info . Default node for this blockchain = <public IP of your node>:8888 (or the port you specified in http-server-address parameter).

Hit Save and enable your chain in the list.

Save by clicking Enable Chains at the beginning of the list. Now in the chains’ dropdown, you should see your chain. Click on it and you’ll be connected:

At this moment we don’t have any accounts or tokens to play with. But first, let’s make sure our blockchain doesn’t have only one block producer.

Back to the terminal 😀

Step 9: Single node is not enough — go multinode

Right now we are running a single node with eosio signing all the blocks. Let’s make sure we have multiple producer nodes

Unlock the wallet and run the command:

cleos push action eosio setpriv '["eosio.msig", 1]' -p eosio@active

Now we have two privileged accounts — eosio and eosio.msig

Initialize system account with

cleos push action eosio init '["0", "4,SYS"]' -p eosio@active

Now let’s create new accounts, buy RAM and stake tokens. First, generate new keypairs:

cleos create key --to-console

import private key (console output) into the wallet:

cleos wallet import

and then create a new account producer1 associated with the public key EOS6B41Z7SuCryZiyHXxKN6QnVELBQvnoAu1CZJ7zxZWuTNWcKEWX (don’t forget to replace this with the public key you generated in the previous step), stake 100000000 SYS tokens for CPU and NET and buy 8192 kb RAM. Of course, you can use a different name 😆

cleos system newaccount eosio --transfer producer1 EOS6B41Z7SuCryZiyHXxKN6QnVELBQvnoAu1CZJ7zxZWuTNWcKEWX --stake-net "100000000.0000 SYS" --stake-cpu "100000000.0000 SYS" --buy-ram-kbytes 8192

and then create a new account producer1 associated with the public key EOS6B41Z7SuCryZiyHXxKN6QnVELBQvnoAu1CZJ7zxZWuTNWcKEWX (don’t forget to replace this with the public key you generated in the previous step), stake 100000000 SYS tokens for CPU and NET and buy 8192 kb RAM. Of course, you can use a different name 😆

cleos system newaccount eosio --transfer producer1 EOS6B41Z7SuCryZiyHXxKN6QnVELBQvnoAu1CZJ7zxZWuTNWcKEWX --stake-net "100000000.0000 SYS" --stake-cpu "100000000.0000 SYS" --buy-ram-kbytes 8192

the output should look like this:

executed transaction: d711ac95f7b9a70da7df27e710dd55866390ee3a0e42d18cf2e5c5279c360819  336 bytes  554 us#         eosio <= eosio::newaccount            {"creator":"eosio","name":"producer1","owner":{"threshold":1,"keys":[{"key":"EOS6B41Z7SuCryZiyHXxKN6...#         eosio <= eosio::buyrambytes           {"payer":"eosio","receiver":"producer1","bytes":8388608}#         eosio <= eosio::delegatebw            {"from":"eosio","receiver":"producer1","stake_net_quantity":"100000000.0000 SYS","stake_cpu_quantity...#   eosio.token <= eosio.token::transfer        {"from":"eosio","to":"eosio.ram","quantity":"122.0851 SYS","memo":"buy ram"}#   eosio.token <= eosio.token::transfer        {"from":"eosio","to":"eosio.ramfee","quantity":"0.6135 SYS","memo":"ram fee"}#         eosio <= eosio.token::transfer        {"from":"eosio","to":"eosio.ram","quantity":"122.0851 SYS","memo":"buy ram"}#     eosio.ram <= eosio.token::transfer        {"from":"eosio","to":"eosio.ram","quantity":"122.0851 SYS","memo":"buy ram"}#         eosio <= eosio.token::transfer        {"from":"eosio","to":"eosio.ramfee","quantity":"0.6135 SYS","memo":"ram fee"}#  eosio.ramfee <= eosio.token::transfer        {"from":"eosio","to":"eosio.ramfee","quantity":"0.6135 SYS","memo":"ram fee"}#   eosio.token <= eosio.token::transfer        {"from":"eosio","to":"eosio.stake","quantity":"200000000.0000 SYS","memo":"stake bandwidth"}#         eosio <= eosio.token::transfer        {"from":"eosio","to":"eosio.stake","quantity":"200000000.0000 SYS","memo":"stake bandwidth"}#   eosio.stake <= eosio.token::transfer        {"from":"eosio","to":"eosio.stake","quantity":"200000000.0000 SYS","memo":"stake bandwidth"}

Now, you can run the following command and see our newly created account:

cleos get account producer1

the result looks like this:

created: 2022-02-07T00:24:37.500permissions:owner     1:    1 EOS6B41Z7SuCryZiyHXxKN6QnVELBQvnoAu1CZJ7zxZWuTNWcKEWX
active 1: 1 EOS6B41Z7SuCryZiyHXxKN6QnVELBQvnoAu1CZJ7zxZWuTNWcKEWX
memory:quota: 8.001 MiB used: 3.475 KiBnet bandwidth:staked: 100000000.0000 SYS (total stake delegated from account to self)
delegated: 0.0000 SYS (total staked delegated to account from others)
used: 0 bytes
available: 164.8 TiB
limit: 164.8 TiB
cpu bandwidth:staked: 100000000.0000 SYS (total stake delegated from account to self)
delegated: 0.0000 SYS (total staked delegated to account from others)
used: 0 us
available: 4800 hr
limit: 4800 hr

LOOOOOOOTS of resources. Nice.

Now we can register producer1 as a block producer:

cleos system regproducer producer1 EOS6B41Z7SuCryZiyHXxKN6QnVELBQvnoAu1CZJ7zxZWuTNWcKEWX https://levan.dev/producer1

(again, don’t forget to change the public key. I won’t remind you again 😝 ) The result should look like this:

executed transaction: 0145a2c5318e5b69c92b23f6153e3985a4e4892baaa46d7bde73b72871a24fd4  168 bytes  219 us#         eosio <= eosio::regproducer           {"producer":"producer1","producer_key":"EOS6B41Z7SuCryZiyHXxKN6QnVELBQvnoAu1CZJ7zxZWuTNWcKEWX","url"...warning: transaction executed locally, but may not be confirmed by the network yet         ]

This doesn’t mean that producer1 is already a producer. It’s just a candidate for now.

Let’s create producer2 too — with a different key. All we need to do is repeat last few steps — create new keypair, import it in the wallet, create new account with resources and register it as a producer. After registering producer2 , you can run:

cleos system listproducers

and see the following output:

Producer      Producer key                                              Url                                                         Scaled votesproducer1     EOS6B41Z7SuCryZiyHXxKN6QnVELBQvnoAu1CZJ7zxZWuTNWcKEWX     https://levan.dev/producer1                                 0.0000producer2     EOS8LwVvgx2Ais7t69zKFJLDBCuMbsSJP1X7a4ndRtHZx41sgJJZT     https://levan.dev/producer2                                 0.0000

Now it’s time to set up node for producer1 :

cd ~/eos
mkdir producer1
cd producer1/

Note before proceeding: make sure that PRODUCER_PUB_KEY and PRODUCER_PRIV_KEY are properly set in all files.

Create genesis_start.sh

nano genesis_start.sh

and paste the following content:

#!/bin/bash
DATADIR="./blockchain"
CURRENTDIR=${PWD##*/}
PRODUCER_PUB_KEY=""
PRODUCER_PRIV_KEY=""
HTTP_PORT=8011
P2P_PORT=9011
if [ ! -d $DATADIR ]; then
mkdir -p $DATADIR;
fi

nodeos \
--genesis-json $DATADIR"/../../genesis.json" \
--signature-provider $PRODUCER_PUB_KEY=KEY:$PRODUCER_PRIV_KEY \
--plugin eosio::producer_plugin \
--plugin eosio::producer_api_plugin \
--plugin eosio::chain_plugin \
--plugin eosio::chain_api_plugin \
--plugin eosio::http_plugin \
--plugin eosio::history_api_plugin \
--plugin eosio::history_plugin \
--data-dir $DATADIR"/data" \
--blocks-dir $DATADIR"/blocks" \
--config-dir $DATADIR"/config" \
--producer-name $CURRENTDIR \
--http-server-address 127.0.0.1:$HTTP_PORT \
--p2p-listen-endpoint 127.0.0.1:$P2P_PORT \
--access-control-allow-origin=* \
--contracts-console \
--http-validate-host=false \
--verbose-http-errors \
--enable-stale-production \
--p2p-peer-address localhost:9010 \
--p2p-peer-address localhost:9012 \
>> $DATADIR"/nodeos.log" 2>&1 & \
echo $! > $DATADIR"/eosd.pid"

also, create start.sh with content

#!/bin/bash
DATADIR="./blockchain"
CURRENTDIR=${PWD##*/}
PRODUCER_PUB_KEY=""
PRODUCER_PRIV_KEY=""
HTTP_PORT=8011
P2P_PORT=9011
if [ ! -d $DATADIR ]; then
mkdir -p $DATADIR;
fi

nodeos \
--signature-provider $PRODUCER_PUB_KEY=KEY:$PRODUCER_PRIV_KEY \
--plugin eosio::producer_plugin \
--plugin eosio::producer_api_plugin \
--plugin eosio::chain_plugin \
--plugin eosio::chain_api_plugin \
--plugin eosio::http_plugin \
--plugin eosio::history_api_plugin \
--plugin eosio::history_plugin \
--data-dir $DATADIR"/data" \
--blocks-dir $DATADIR"/blocks" \
--config-dir $DATADIR"/config" \
--producer-name $CURRENTDIR \
--http-server-address 127.0.0.1:$HTTP_PORT \
--p2p-listen-endpoint 127.0.0.1:$P2P_PORT \
--access-control-allow-origin=* \
--contracts-console \
--http-validate-host=false \
--verbose-http-errors \
--enable-stale-production \
--p2p-peer-address localhost:9010 \
--p2p-peer-address localhost:9012 \
>> $DATADIR"/nodeos.log" 2>&1 & \
echo $! > $DATADIR"/eosd.pid"

and hard_start.sh with content

#!/bin/bash
DATADIR="./blockchain"
CURRENTDIR=${PWD##*/}
PRODUCER_PUB_KEY=""
PRODUCER_PRIV_KEY=""
HTTP_PORT=8011
P2P_PORT=9011
if [ ! -d $DATADIR ]; then
mkdir -p $DATADIR;
fi

nodeos \
--signature-provider $PRODUCER_PUB_KEY=KEY:$PRODUCER_PRIV_KEY \
--plugin eosio::producer_plugin \
--plugin eosio::producer_api_plugin \
--plugin eosio::chain_plugin \
--plugin eosio::chain_api_plugin \
--plugin eosio::http_plugin \
--plugin eosio::history_api_plugin \
--plugin eosio::history_plugin \
--data-dir $DATADIR"/data" \
--blocks-dir $DATADIR"/blocks" \
--config-dir $DATADIR"/config" \
--producer-name $CURRENTDIR \
--http-server-address 127.0.0.1:$HTTP_PORT \
--p2p-listen-endpoint 127.0.0.1:$P2P_PORT \
--access-control-allow-origin=* \
--contracts-console \
--http-validate-host=false \
--verbose-http-errors \
--enable-stale-production \
--p2p-peer-address localhost:9010 \
--p2p-peer-address localhost:9012 \
--hard-replay-blockchain \
>> $DATADIR"/nodeos.log" 2>&1 & \
echo $! > $DATADIR"/eosd.pid"

We can re-use stop.sh and clear.sh scripts from our genesis folder:

cd ~/eos
cp genesis/clear.sh producer1/
cp genesis/stop.sh producer1/

Now, repeat the same steps and create directory for producer2 . Another important node — make sure you set ports and peer addresses correctly — producer2 should run on HTTP_PORT 8012 and P2P_PORT 9012 , and it should peer with localhost:9010 and localhost:9011

Let’s chmod our executables:

cd ~/eos
chmod 755 producer1/*.sh
chmod 755 producer2/*.sh

and start them!

cd producer1/
./genesis_start.sh
tail -f blockchain/nodeos.log

you’ll see that your node will syncronize with genesis node

...
info 2022-02-07T01:17:31.040 net-1 net_plugin.cpp:1717 operator() ] requesting range 102 to 201, from ip-172-31-85-179:9010 - c447998
info 2022-02-07T01:17:31.067 net-0 net_plugin.cpp:1717 operator() ] requesting range 202 to 301, from ip-172-31-85-179:9010 - c447998info 2022-02-07T01:17:31.094 net-1 net_plugin.cpp:1717 operator() ] requesting range 302 to 401, from ip-172-31-85-179:9010 - c447998
...

and then starts receiving blocks

...info  2022-02-07T01:19:09.901 nodeos    producer_plugin.cpp:421       on_incoming_block    ] Received block bfcf246bf66877f3... #21231 @ 2022-02-07T01:19:10.000 signed by eosio [trxs: 0, lib: 21230, conf: 0, latency: -98 ms]...

We can stop producer1 node by running

cd ~/eos/producer1/
./stop.sh

and if you want to start it again — DO NOT use genesis_start.sh , use start.sh

Let’s start producer2 as well:

cd ~/eos/producer2
./genesis_start.sh

Now we have 3 nodes running 🤘

Lets now vote for our producer1 and producer2 . First, let’s vote from producer1 account:

cleos system voteproducer prods producer1 producer1 producer2

now, from producer2 account:

cleos system voteproducer prods producer2 producer1 producer2

Perfect. If you check producer list by running cleos system listproducers , you should see the following output:

Producer      Producer key                                              Url                                                         Scaled votesproducer1     EOS6B41Z7SuCryZiyHXxKN6QnVELBQvnoAu1CZJ7zxZWuTNWcKEWX     https://levan.dev/producer1                                 0.5000producer2     EOS8LwVvgx2Ais7t69zKFJLDBCuMbsSJP1X7a4ndRtHZx41sgJJZT     https://levan.dev/producer2                                 0.5000

Now we can resign eosio account and leave only one privileged — eosio.msig account :

cleos push action eosio updateauth '{"account": "eosio", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio.prods", "permission": "active"}}]}}' -p eosio@ownercleos push action eosio updateauth '{"account": "eosio", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio.prods", "permission": "active"}}]}}' -p eosio@active

and also do the same for all system accounts we created previously — here’s the list:

eosio.bpay
eosio.msig
eosio.names
eosio.ram
eosio.ramfee
eosio.saving
eosio.stake
eosio.token
eosio.vpay
eosio.rex

and here are the commands:

cleos push action eosio updateauth '{"account": "eosio.bpay", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.bpay@ownercleos push action eosio updateauth '{"account": "eosio.bpay", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.bpay@active

cleos push action eosio updateauth '{"account": "eosio.msig", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.msig@owner
cleos push action eosio updateauth '{"account": "eosio.msig", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.msig@active

cleos push action eosio updateauth '{"account": "eosio.names", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.names@owner
cleos push action eosio updateauth '{"account": "eosio.names", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.names@active

cleos push action eosio updateauth '{"account": "eosio.ram", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.ram@owner
cleos push action eosio updateauth '{"account": "eosio.ram", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.ram@active

cleos push action eosio updateauth '{"account": "eosio.ramfee", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.ramfee@owner
cleos push action eosio updateauth '{"account": "eosio.ramfee", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.ramfee@active

cleos push action eosio updateauth '{"account": "eosio.saving", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.saving@owner
cleos push action eosio updateauth '{"account": "eosio.saving", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.saving@active

cleos push action eosio updateauth '{"account": "eosio.stake", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.stake@owner
cleos push action eosio updateauth '{"account": "eosio.stake", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.stake@active

cleos push action eosio updateauth '{"account": "eosio.token", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.token@owner
cleos push action eosio updateauth '{"account": "eosio.token", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.token@active

cleos push action eosio updateauth '{"account": "eosio.vpay", "permission": "owner", "parent": "", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.vpay@owner
cleos push action eosio updateauth '{"account": "eosio.vpay", "permission": "active", "parent": "owner", "auth": {"threshold": 1, "keys": [], "waits": [], "accounts": [{"weight": 1, "permission": {"actor": "eosio", "permission": "active"}}]}}' -p eosio.vpay@active

Now if you run

cleos get info

multiple times, you’ll see that some blocks are produced by producer1 , some— by producer2

{"server_version": "26a4d285","chain_id": "15cc2a4b6ffe0d0cb878e6bc13eb8e8cb559a744436502ad984940a80ea98e50","head_block_num": 22785,"last_irreversible_block_num": 22770,"last_irreversible_block_id": "000058f2ae5331d7854b269d0b1b500cdcbb88168e1713855a1c629f35e35351","head_block_id": "00005901bdf2154d473d039b4d716b311ddb542b4d000ac4306eb74e6fe3efac","head_block_time": "2022-02-07T01:32:07.000","head_block_producer": "producer2", <--------------------------"virtual_block_cpu_limit": 100000000,"virtual_block_net_limit": 1048576000,"block_cpu_limit": 99900,"block_net_limit": 1048576,"server_version_string": "v2.1.0","fork_db_head_block_num": 22785,"fork_db_head_block_id": "00005901bdf2154d473d039b4d716b311ddb542b4d000ac4306eb74e6fe3efac","server_full_version_string": "v2.1.0-26a4d285d0be1052d962149e431eb81500782991","last_irreversible_block_time": "2022-02-07T01:31:59.500"}

__

{"server_version": "26a4d285","chain_id": "15cc2a4b6ffe0d0cb878e6bc13eb8e8cb559a744436502ad984940a80ea98e50","head_block_num": 22798,"last_irreversible_block_num": 22782,"last_irreversible_block_id": "000058feff76ef3a66310085bac57d423c653dce558c18bf5e9c2c5613d76445","head_block_id": "0000590e61e5c741e1a1b48ed0509061f079fcd4fbbc54b264502cfdec583f72","head_block_time": "2022-02-07T01:32:13.500","head_block_producer": "producer1", <--------------------------"virtual_block_cpu_limit": 100000000,"virtual_block_net_limit": 1048576000,"block_cpu_limit": 99900,"block_net_limit": 1048576,"server_version_string": "v2.1.0","fork_db_head_block_num": 22798,"fork_db_head_block_id": "0000590e61e5c741e1a1b48ed0509061f079fcd4fbbc54b264502cfdec583f72","server_full_version_string": "v2.1.0-26a4d285d0be1052d962149e431eb81500782991","last_irreversible_block_time": "2022-02-07T01:32:05.500"}

Step 10: Now what?

Now we have private EOS network running with two block producers and you know how you can set up nodes. Now let’s create few accounts, issue custom token, import all of this in Anchor and send them

Generate new keypair:

cleos create key --to-consolePrivate key: 5JFWM62D6bgoVh6gyMJwRpHVNCjPRviqx1sYyoZg1b8r3dWZj2u
Public key: EOS767B7FxjMNiUiht95ZCnYBtCUZiMTsYua3hR4XR8DqxkyRMYCr

now import this private key in our default wallet using:

cleos wallet import 

Next, create new account with our public key and give it some resources:

cleos system newaccount eosio --transfer foobartokens EOS767B7FxjMNiUiht95ZCnYBtCUZiMTsYua3hR4XR8DqxkyRMYCr --stake-net "100000000.0000 SYS" --stake-cpu "100000000.0000 SYS" --buy-ram-kbytes 8192

Remember that contracts we had built? Let’s set eosio.token contract for our foobartokens account.

cleos set contract foobartokens ~/eosio.contracts/build/contracts/eosio.token/

Now, create new token — let’s call it BAR and set max.supply to 1000000000.0000 BAR:

cleos push action foobartokens create '[ "foobartokens", "1000000000.0000 BAR"]' -p foobartokens

Issue our shiny BAR tokens:

cleos push action foobartokens issue '[ "foobartokens", "1000000000.0000 BAR", "Issued BAR Tokens!"]' -p foobartokens

Let’s create accounts levan and toby (generate new keys for each)

cleos system newaccount eosio --transfer levan EOS6XNwKxBdDwv6TFyduRsdBDfcqikZy8fA72XpQwQo8pd9jhFCzY --stake-net "100000.0000 SYS" --stake-cpu "100000.0000 SYS" --buy-ram-kbytes 8192cleos system newaccount eosio --transfer toby EOS5N7LbpTwkpsJRwrhJ7j8yvo1jm9UcqUmQD2wphQVUPAQtaVcUV --stake-net "100000.0000 SYS" --stake-cpu "100000.0000 SYS" --buy-ram-kbytes 8192

and transfer some BAR tokens to account levan :

cleos push action foobartokens transfer '["foobartokens", "levan", "100000.0000 BAR", "Welcome!"]' -p foobartokens@owner

That’s it. We’re done with the terminal, let’s move to Anchor:

First, import toby and levan in Anchor wallet:

Hit Import Private Key and import levan and toby ‘s private keys.

Next, track our BAR token — go to Tokens , click Manage Tracked Tokens and Add Custom Token

Now return to Tokens tab and you’ll see those 100 000 BAR tokens we’ve sent to account levan

Let’s send some to Toby:

Woof Woof! because Toby is my Rottweiler 😆

and here it is — Toby got his treats.

Conclusion

Congrats! Now you know how to set up your very own EOS private chain with multiple block producers, how to issue custom tokens, how to connect wallet and how to have fun with custom tokens.

In Part 2 of this article (yeah, it will have Part 2) we will set up Hyperion History API.

Updated: Private EOS Network: Part 2:Hyperion

Have fun 🤘

Reference(s):

EOSIO Docs — BIOS Boot Sequence: https://developers.eos.io/welcome/latest/tutorials/bios-boot-sequence/

--

--

Levan Ilashvili
Levan Ilashvili

Written by Levan Ilashvili

Hooman of two doggos, software engineer

No responses yet