1. Introduction

This tutorial updates the master branch of the project https://github.com/paulojeronimo/ethereum-helloworld.

That branch was published on Mar 14, 2018 and, at the same time, I created this YouTube playlist to explain Ethereum technologies. Please see the videos of that playlist, especially video 9, to know about the technologies used by Ethereum at that moment.

Many things changed in these last four (4) years since I wrote the Ethereum "Hello World" application. The main goal of such application was to explain the fundamentals of some tools used when developing smart contracts for the Ethereum Blockchain and, also, create an environment to test it. Since so much has changed in that time, it’s time for an update.

Now, in addition to being able to use the Truffle Suite framework (famous at that time), another existing alternative, which I will present in this tutorial, is the Hardhat framework (we’ll do some basic comparisons about both after explaining them).

The main purpose of this tutorial is to update the file js/HelloWorldClient.js so it can run with newer versions of libraries and frameworks. But, it’s not just that. This tutorial will also explore these libraries in more depth and show how they are used in practice to build a web version of this application.

Let’s get started by using the web3.js library and the Truffle Suite framework. After that, we will focus on using the ethers.js library and the Hardhat framework.

2. Running the final codes developed in this tutorial …​

The final code generated until this session, without including the Git repository that you will produce by following the next steps, is available in this zip file. Let’s run it, before going further, by typing these command lines:

warm up:

$ # Extracting the final code:
$ d=~/tmp/ethereum-helloworld.$(date +%F)/; mkdir -p $d && cd $d
$ unzip ~/Downloads/final-code-1.zip

$ # Installing the required packages:
$ pnpm install

$ # Starting `ganache`:
$ ganache &> ganache.log &

$ # Getting help
$ node js/HelloWorldClient.js --help

cmd1:

$ # Creating a `HelloWorld` contract with a default message:
$ node js/HelloWorldClient.js | tee /tmp/cmd1

cmd2:

$ # Store the contract address in a variable and print it:
$ address=$(awk '/deployed at/{print $5}' /tmp/cmd1); echo $address

$ # Getting the message from an existing `HelloWorld` contract:
$ node js/HelloWorldClient.js getMessage -a $address

cmd3:

$ # Updating the message on an existing `HelloWorld` contract:
$ node js/HelloWorldClient.js setMessage -a $address -m 'Hello PJ!'

cmd4:

$ # Creating a `HelloWorld` contract and update its message:
$ node js/HelloWorldClient.js -m 'Hello contract 2'

cool down:

$ # Killing the `ganache` process:
$ kill %1

3. Verifying the software requirements to follow this tutorial

This tutorial was made to run on Ubuntu. Until this moment, I only tested it on a real machine running Ubuntu 22.04 with the following command line tools/versions installed:

$ echo $BASH_VERSION
5.1.16(1)-release

$ git --version
git version 2.34.1

$ # node was installed via `nvm` command (https://github.com/nvm-sh/nvm)
$ node --version
v16.15.0

$ npm --version
8.9.0

$ pnpm --version (https://pnpm.io/)
7.1.0

$ vim --version | head -1
VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Apr 18 2022 19:26:30)

$ # package containing the `batcat` command:
$ dpkg -l bat | grep ^ii
ii  bat            0.19.0-1     amd64        cat(1) clone with syntax highlighting and git integration

$ # package containing the `tmux` command:
$ dpkg -l tmux | grep ^ii
ii  tmux           3.2a-4build1 amd64        terminal multiplexer

For this tutorial, you will only need these command line tools and a browser (I will use Google Chrome). You will not use any IDE such as Visual Studio Code here because, for this tutorial, it can be considered "a bazooka to kill an ant" 😄.

4. Truffle Suite version

4.1. Recreating the node client (js/HelloWorldClient.js)

4.1.1. Downloading the existing code and changing to its directory

$ repo=https://github.com/paulojeronimo/ethereum-helloworld
$ mkdir -p ~/tmp && cd $_; git clone $repo && cd $(basename $repo)

4.1.2. Creating a new branch (ftecm221)

$ git checkout -b ftecm221

4.1.3. Removing and commiting the unnecessary files

$ rm -rf bin config eth_private java yarn.lock README.adoc
$ git add .
$ git commit -m 'Removed unnecessary files'

See the directory structure after this change:

$ tree
.
|-- contracts
|   `-- HelloWorld.sol
|-- js
|   `-- HelloWorldClient.js
`-- package.json

2 directories, 3 files

Remember that our final goal for this entire session is to recreate the existing functionalities that exists in js/HelloWorldClient.js.

4.1.4. Updating the contract (HelloWorld.sol)

Edit contracts/HelloWorld.sol and update it according to this diff file:

diff --git a/contracts/HelloWorld.sol b/contracts/HelloWorld.sol
index f3d4d31..d25f02b 100644
--- a/contracts/HelloWorld.sol
+++ b/contracts/HelloWorld.sol
@@ -1,17 +1,14 @@
-pragma solidity ^0.4.0;
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.8.0 <0.9.0;
 
 contract HelloWorld {
-    string message;
-    
-    function HelloWorld() public {
-        setMessage("Hello world!");
+    string public message;
+
+    constructor () {
+        setMessage("Hello World!");
     }
-    
-    function setMessage(string _message) public {
+
+    function setMessage(string memory _message) public {
         message = _message;
     }
-    
-    function getMessage() public constant returns (string) {
-        return message;
-    }
 }

You can easily do these changes by copying the content above into a file /tmp/1.diff and running the following command:

$ git apply /tmp/1.diff

4.1.5. Testing the contract in Remix IDE

Access the Remix IDE and test if the contract compiles successfully.

4.1.6. Updating the npm packages

Verifiy the npm outdated dependencies:

$ npm outdated
Package      Current  Wanted  Latest  Location  Depended by
ganache-cli  MISSING  6.12.2  6.12.2  -         ethereum-helloworld
solc         MISSING  0.4.26  0.8.13  -         ethereum-helloworld
web3         MISSING  0.20.7   1.7.3  -         ethereum-helloworld

Update the packages solc and web3:

$ pnpm add solc@0.8.13 web3@1.7.3

The latest version of web3.js documentation is here: https://web3js.readthedocs.io/. Also, here is the latest version of the solc documentation: https://solidity.readthedocs.io.

4.1.7. Installing Ganache

Remove the package ganache-cli because we’ll use the new package ganache instead.

$ pnpm remove ganache-cli

Install ganache globally (with the latest version):

$ pnpm add -g ganache

4.1.8. Commiting our contract and package updates

$ git add .
$ git commit -m "Updated 'contracts/HelloWorld.sol' and npm packages"

After the changes above, your directory tree should be this:

$ tree
.
|-- contracts
|   `-- HelloWorld.sol
|-- js
|   `-- HelloWorldClient.js
|-- node_modules
|   |-- solc -> .pnpm/solc@0.8.13/node_modules/solc
|   `-- web3 -> .pnpm/web3@1.7.3/node_modules/web3
|-- package.json
`-- pnpm-lock.yaml

5 directories, 4 files

4.1.9. Writing a new code to compile the contract

The first change that we need to note is about the compilation of the contract.

By using the new solc package the process was changed. Previously, the compilation could be done using only these two lines presented by output of the batcat command:

$ batcat -r 17:18 js/HelloWorldClient.js
batcat.1

In order to provide more features and flexibility, the compilation is now a little more complex …​

Create the file js/compile.js with the following content:

const path = require('path')
const fs = require('fs')
const solc = require('solc')

const source = fs.readFileSync(
  path.resolve(__dirname, '../contracts', 'HelloWorld.sol'), 'utf8')

const input = {
  language: 'Solidity',
  sources: {
    'HelloWorld.sol': {
      content: source,
    },
  },
  settings: {
    outputSelection: {
      '*': {
        '*': ['*'],
      },
    },
  },
}

module.exports = JSON.parse(
  solc.compile(JSON.stringify(input))).contracts['HelloWorld.sol'].HelloWorld
You can use an editor (I use vim) or copy and paste the code above directly into the terminal by putting it just below this command line:
$ cat > js/compile.js <<'EOF'
In this case, you will need to close this command with a line where you will write the word EOF (press the key Enter ather this).

4.1.10. Learning the API and testing it using the node REPL

Test the code written in the last session by using REPL.

In the following example, we will compile the contract and present its Abstract Binary Interface (ABI).

$ node
Welcome to Node.js v16.15.0.
Type ".help" for more information.
> let helloWorldContract = require('./js/compile')
undefined
> helloWorldContract.abi
[
  { inputs: [], stateMutability: 'nonpayable', type: 'constructor' },
  {
    inputs: [],
    name: 'message',
    outputs: [ [Object] ],
    stateMutability: 'view',
    type: 'function'
  },
  {
    inputs: [ [Object] ],
    name: 'setMessage',
    outputs: [],
    stateMutability: 'nonpayable',
    type: 'function'
  }
]
Testing the web3 connection

On another terminal, start ganache

I like to use tmux to do this.
$ ganache

In the opened session for node in the last session, type the following commands:

> let Web3 = require('web3')
undefined
> let web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
undefined

You can see that, initially, we are not connected:

> web3.currentProvider.connected
false

In order to check if we can get a connection with the current provider configured, we can execute this code:

> .editor
// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)

Copy the following content do node terminal and, after that, press Ctrl+D:

web3.eth.net.isListening()
  .then(() => console.log('web3 is connected!'))
  .catch((error) => console.log(error))

Now, verify the connection again:

> web3.currentProvider.connected
true
Testing the web3 accounts

Many functions in web3 returns a JavaScript Promisse:

> web3.eth.getAccounts().then(console.log)
Promise {
  <pending>,
  [Symbol(async_id_symbol)]: 423,
  [Symbol(trigger_async_id_symbol)]: 418,
  [Symbol(destroyed)]: { destroyed: false }
}
> [
  '0x5B27B438fD22fEB8be6C7ae6eBeC83cA802dC28b',
  '0x14cBbE6De85D62014BeCc749032B961842C4f532',
  '0x08Ec339d8be941Cc024a59356aB25c0C0097b485',
  '0x195309AB75670aF0e37c0cf89C003F6cf9dFC279',
  '0x7b06A31127e8cb3431e3dD8d1892F038a3E82bDe',
  '0x2819967ff90421d5926E4c7BFF4eeD14d551a802',
  '0x1d3D3ec6a3507Bc0F47e8Ef55Eb94c48888B3A6f',
  '0xb9997Ace054E3f309d4DDb0F9397aFf8E4c3a0A9',
  '0x5aE75fBb47C0A16137d30a7f9f47bbf317545D86',
  '0xbaC64723bB149Aa48173eaEF0c7e2b616B9d7e23'
]

So we can get its result by using the await keyword, like this:

> let accounts = await web3.eth.getAccounts()
> accounts
Deploying the contract
> let {abi, evm} = helloWorldContract
> .editor
// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)
let helloWorld = await new web3.eth.Contract(abi)
  .deploy({data: evm.bytecode.object, arguments: []})
  .send({from: accounts[0], gas: '1000000'})
  .on('receipt', console.log)
Getting the message in helloWorld
> await helloWorld.methods.message().call()
'Hello Ethereum World!'
Setting a new message in helloWorld
> let tx = await helloWorld.methods.setMessage('Hello Paulo Jeronimo!').send({from: accounts[0]})
> let { transactionHash, gasUsed, blockNumber } = tx
> console.log(`\tTransaction: ${transactionHash}\n\tGas usage: ${gasUsed}\n\tBlock number: ${blockNumber}`)
> await helloWorld.methods.message().call()
'Hello Paulo Jeronimo!'
Closing the node REPL
> .exit
You can also type Ctrl+D to close the node REPL.

4.1.11. Putting it all together (in js/HelloWorldClient.js)

Getting a connection and print the accounts

Before starting to join the puzzle, commit the code that we create for js/compile.js:

$ git add js/compile.js
$ git commit -m "Added 'js/compile.js'"

Now, let’s create our minimal and functional code in order to evolve it. Copy the following contents to the file js/HelloWorldClient.js:

const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))

async function afterConnect() {
  console.log(`web3 is connected!\nweb3.version: ${web3.version}`)
  const accounts = await web3.eth.getAccounts()
  console.log("accounts:\n", accounts)
}

web3.eth.net.isListening()
  .then(() => afterConnect())
  .catch((error) => console.log(error))

Test the code:

$ node js/HelloWorldClient.js
web3 is connected!
web3.version: 1.7.3
accounts:
 [
  '0x5e79A7bA6298844d5E96fbC0529dbAe338f50e95',
  '0xd2B3760Bbf721CfBCc01a0BB06AA6BFc0D8d746B',
  '0xF353D59A44bc9E3Ef3a9f9cab82216BC5fb76AAF',
  '0x27b53d80f3A3B303fC2f12D8486b742f33B49076',
  '0xb1B7990B63FC150bf3dD356D4E6F92d8142EDe95',
  '0x75c12Dc8a595C0d1d2d8935220ce448f26458e5b',
  '0x59a0205118506c81dEAF4a25270C5094415993F1',
  '0x410Cfa7EC1Ce2B0d0fb22257F571727de4915e1C',
  '0xe311B409465A29F90c2620a3D2E64978E29F97ac',
  '0x097F51fc450d1cDDAA156B88D675ddC137F7EB34'
]

Stage this file:

$ git add .
Deploying the contract

Update the code according to this diff:

diff --git a/js/HelloWorldClient.js b/js/HelloWorldClient.js
index b49981b..52a4bbe 100644
--- a/js/HelloWorldClient.js
+++ b/js/HelloWorldClient.js
@@ -1,10 +1,27 @@
 const Web3 = require('web3');
 const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
+const { abi, evm } = require('./compile')
+
+function printTx({transactionHash, gasUsed, blockNumber}) {
+  console.log(`\tTransaction: ${transactionHash}\n\tGas usage: ${gasUsed}\n\tBlock number: ${blockNumber}`)
+}
+
+async function deploy(accounts) {
+  console.log('A HelloWorld contract will be deployed')
+  let tx = null
+  const helloWorld = await new web3.eth.Contract(abi)
+    .deploy({data: evm.bytecode.object, arguments: []})
+    .send({from: accounts[0], gas: '1000000'})
+    .on('receipt', (receipt) => tx = receipt)
+  printTx(tx, helloWorld)
+  console.log('HelloWorld successfully deployed at', helloWorld.options.address)
+  return helloWorld
+}
 
 async function afterConnect() {
   console.log(`web3 is connected!\nweb3.version: ${web3.version}`)
   const accounts = await web3.eth.getAccounts()
-  console.log("accounts:\n", accounts)
+  const helloWorld = await deploy(accounts)
 }
 
 web3.eth.net.isListening()

Restart ganache.

Test the code:

$ node js/HelloWorldClient.js

Stage this file:

$ git add .
Getting the contract’s message

Update the code according to this diff:

diff --git a/js/HelloWorldClient.js b/js/HelloWorldClient.js
index 52a4bbe..1a6188a 100644
--- a/js/HelloWorldClient.js
+++ b/js/HelloWorldClient.js
@@ -18,10 +18,16 @@ async function deploy(accounts) {
   return helloWorld
 }
 
+async function getMessage(helloWorld) {
+  const message = await helloWorld.methods.message().call()
+  console.log(`Message: ${message}`)
+}
+
 async function afterConnect() {
   console.log(`web3 is connected!\nweb3.version: ${web3.version}`)
   const accounts = await web3.eth.getAccounts()
   const helloWorld = await deploy(accounts)
+  await getMessage(helloWorld)
 }
 
 web3.eth.net.isListening()

Test the code:

$ node js/HelloWorldClient.js

Stage this file:

$ git add .
Setting the message in contract

Update the code according to this diff:

diff --git a/js/HelloWorldClient.js b/js/HelloWorldClient.js
index 1a6188a..03657eb 100644
--- a/js/HelloWorldClient.js
+++ b/js/HelloWorldClient.js
@@ -18,6 +18,14 @@ async function deploy(accounts) {
   return helloWorld
 }
 
+async function setMessage(helloWorld, message, accounts, callback) {
+  console.log(`Calling setMessage("${message}")`)
+  const tx = await helloWorld.methods.setMessage(message)
+    .send({from: accounts[0]})
+  printTx(tx, helloWorld)
+  callback(helloWorld)
+}
+
 async function getMessage(helloWorld) {
   const message = await helloWorld.methods.message().call()
   console.log(`Message: ${message}`)
@@ -28,6 +36,7 @@ async function afterConnect() {
   const accounts = await web3.eth.getAccounts()
   const helloWorld = await deploy(accounts)
   await getMessage(helloWorld)
+  setMessage(helloWorld, 'Hello Paulo Jeronimo!', accounts, getMessage)
 }
 
 web3.eth.net.isListening()

Restart ganache.

Let’s test and see the output until now:

$ node js/HelloWorldClient.js

Also, note the output generated by the ganache.

Note that each time we run this client we will create a new contract on the Blockchain. That’s not what the original program did. It offered the opportunity to change the message of an existing contract. So now let’s tweak the code for that. But first, commit your work:

$ git add .
$ git commit -m "Added 'js/HelloWorldClient.js' (almost finished)"

4.1.12. Doing the final adjustments

Looking for an existing contract and print its message

Update the code according to this diff:

diff --git a/js/HelloWorldClient.js b/js/HelloWorldClient.js
index 03657eb..a64c77d 100644
--- a/js/HelloWorldClient.js
+++ b/js/HelloWorldClient.js
@@ -34,6 +34,22 @@ async function getMessage(helloWorld) {
 async function afterConnect() {
   console.log(`web3 is connected!\nweb3.version: ${web3.version}`)
   const accounts = await web3.eth.getAccounts()
+  if (process.argv.length >= 3) {
+    if (process.argv.length == 3) {
+      if (process.argv[2].startsWith('0x')) {
+        try {
+          const address = process.argv[2]
+          console.log(`Looking for a HelloWorld contract at ${address}`)
+          await getMessage(new web3.eth.Contract(abi, address))
+        } catch (error) {
+          console.log('Contract not found!')
+        }
+      } else {
+        console.log('the address does not seem to be valid!')
+      }
+    }
+    return
+  }
   const helloWorld = await deploy(accounts)
   await getMessage(helloWorld)
   setMessage(helloWorld, 'Hello Paulo Jeronimo!', accounts, getMessage)

Test the code by passing an address for a deployed contract. If the contract is found at the given address, the program output will look something like this:

$ address=0x2ed6e8350081e2f5bcf6acf343a56dc6044de14e

$ node js/HelloWorldClient.js $address
web3 is connected!
web3.version: 1.7.3
Looking for a HelloWorld contract at
0x2ed6e8350081e2f5bcf6acf343a56dc6044de14e
Message: Hello Paulo Jeronimo!

If the address is malformed the output should be this:

$ address=invalid

$ node js/HelloWorldClient.js $address
web3 is connected!
web3.version: 1.7.3
the address does not seem to be valid!

If the address seem valid but the contract not found, the output should be close to this:

$ address=0xaaaaabbbbbccccc

$ node js/HelloWorldClient.js $address
web3 is connected!
web3.version: 1.7.3
Looking for a HelloWorld contract at 0xaaaaabbbbbccccc
Contract not found!

Save your work:

$ git add .
$ git commit -m "Updated 'js/HelloWorldClient.js' to do a simple command line processing"
Updating the message for an existing contract

We need a more effective way to process the command line arguments. In addition to searching for an existing contract, we also want to update the message in it.

So, we have to process two (2) command line options:

  • option1 → the address

  • option2 → the message

Also, like we tested in beginning of this tutorial, we have three (3) commands (+1 combination) that can use the options above:

  • [cmd1]deploy: with this command (which is the default) we can specify a message option (to update the message in contract after its creation ([cmd4])).

  • [cmd2]getMessage: with this command the address option is required.

  • [cmd3]setMessage: with this command, the address option is required and, also, the message option.

Parsing command line commands and options can be very complicated. We’ll start to meet the requirements above by writing the following code (in js/processArgs.js):

const argv = process.argv.slice(2)
const arg = (min, index) => argv.length >= min ? argv[index] : undefined

let command = 'deploy'
let address = arg(1, 0)
let message = arg(2, 1)
let validationErrors = []

if (address && !address.startsWith('0x')) {
  validationErrors.push(`The address ${address} is invalid!`)
}
if (message?.trim() === '') {
  validationErrors.push('The message should be not empty!')
}

if (validationErrors.length > 0) {
  validationErrors.forEach((error) => console.log(error))
  process.exit(1)
}

module.exports = { command, address, message }

In order to use this module, we need to update js/HelloWordClient.js according to this diff:

diff --git a/js/HelloWorldClient.js b/js/HelloWorldClient.js
index a64c77d..e948160 100644
--- a/js/HelloWorldClient.js
+++ b/js/HelloWorldClient.js
@@ -31,30 +31,27 @@ async function getMessage(helloWorld) {
   console.log(`Message: ${message}`)
 }
 
-async function afterConnect() {
+async function afterConnect({ command, address, message }) {
   console.log(`web3 is connected!\nweb3.version: ${web3.version}`)
   const accounts = await web3.eth.getAccounts()
-  if (process.argv.length >= 3) {
-    if (process.argv.length == 3) {
-      if (process.argv[2].startsWith('0x')) {
-        try {
-          const address = process.argv[2]
-          console.log(`Looking for a HelloWorld contract at ${address}`)
-          await getMessage(new web3.eth.Contract(abi, address))
-        } catch (error) {
-          console.log('Contract not found!')
-        }
-      } else {
-        console.log('the address does not seem to be valid!')
+  console.log(`command: ${command}`)
+  switch (command) {
+    case 'deploy':
+      helloWorld = await deploy(accounts)
+      await getMessage(helloWorld)
+      if (message) {
+        setMessage(helloWorld, message, accounts, getMessage)
       }
-    }
-    return
+      break
+    case 'getMessage':
+      // will get a message looking for a contract with an especified address
+      break
+    case 'setMessage':
+      // will set a message in a specfic address using accounts[0]
+      break
   }
-  const helloWorld = await deploy(accounts)
-  await getMessage(helloWorld)
-  setMessage(helloWorld, 'Hello Paulo Jeronimo!', accounts, getMessage)
 }
 
 web3.eth.net.isListening()
-  .then(() => afterConnect())
+  .then(() => afterConnect(require('./processArgs')))
   .catch((error) => console.log(error))

Restart ganache.

Start testing by typing the following commands:

$ # Test 1:
$ node js/HelloWorldClient.js

$ # Test 2:
$ address=invalid
$ node js/HelloWorldClient.js $address

$ # Test 3 ("valid" address and invalid message):
$ address=0x000000000
$ message='  '
$ node js/HelloWorldClient.js $address "$message"

Comit your work:

$ git add .
$ git commit -m "Updated 'js/HelloWorldClient.js' to start the 'update message' feature"
Adding the yargs support

The code in the last session isn’t so flexible to process the command line. So, in order to gain this flexibility, we will use the yargs project.

Add the yargs package:

$ pnpm add yargs

Apply this patch to js/processArgs.js:

diff --git a/js/processArgs.js b/js/processArgs.js
index fe7946f..d81e1ef 100644
--- a/js/processArgs.js
+++ b/js/processArgs.js
@@ -1,21 +1,45 @@
-const argv = process.argv.slice(2)
-const arg = (min, index) => argv.length >= min ? argv[index] : undefined
+const yargs = require('yargs')
+const argv = yargs
+  .command('$0', 'Deploys a HelloWorld contract')
+  .command('setMessage', 'Sets a message in a contract')
+  .command('getMessage', 'Gets a message in a contract')
+  .option('address', {
+      alias: 'a',
+      description: 'The HelloWorld contract address',
+      type: 'string'
+  })
+  .option('message', {
+      alias: 'm',
+      description: 'The message to be configured in the HelloWorld contract',
+      type: 'string'
+  })
+  .help()
+  .alias('help', 'h').argv;
 
 let command = 'deploy'
-let address = arg(1, 0)
-let message = arg(2, 1)
-let validationErrors = []
+let address = argv.address
+let message = argv.message
+let errors = []
 
-if (address && !address.startsWith('0x')) {
-  validationErrors.push(`The address ${address} is invalid!`)
-}
-if (message?.trim() === '') {
-  validationErrors.push('The message should be not empty!')
+const errorIfEmpty = (option, value) => value === '' &&
+  errors.push(`The "${option}" option should not be empty!`)
+const errorIfUndefinedOrEmpty = (option, value) => {
+  value === undefined && errors.push(`Please, define the "${option}" option!`)
+  errorIfEmpty(option, value)
 }
+const errorIfNotAddress = (address) =>
+  (address && address.startsWith('0x')) ||
+  errors.push(`The address "${address}" is not valid!`)
 
-if (validationErrors.length > 0) {
-  validationErrors.forEach((error) => console.log(error))
-  process.exit(1)
+if (argv._.includes('setMessage')) {
+  command = 'setMessage'
+  errorIfNotAddress(address)
+  errorIfUndefinedOrEmpty('message', message)
+} else if (argv._.includes('getMessage')) {
+  command = 'getMessage'
+  errorIfNotAddress(address)
+} else {
+  errorIfEmpty('message', message)
 }
 
-module.exports = { command, address, message }
+module.exports = { command, address, message, errors }

Also, apply this patch to js/HelloWorldClient.js:

diff --git a/js/HelloWorldClient.js b/js/HelloWorldClient.js
index e948160..7abc7e6 100644
--- a/js/HelloWorldClient.js
+++ b/js/HelloWorldClient.js
@@ -44,14 +44,20 @@ async function afterConnect({ command, address, message }) {
       }
       break
     case 'getMessage':
-      // will get a message looking for a contract with an especified address
+      console.log(`getMessageAt({address: "${address}"})`)
       break
     case 'setMessage':
-      // will set a message in a specfic address using accounts[0]
+      console.log(`setMessageAt({address: "${address}", message: "${message}", account: "${accounts[0]}")`)
       break
   }
 }
 
+const args = require('./processArgs')
+if (args.errors.length > 0) {
+  args.errors.forEach((error) => console.log(error))
+  process.exit(1)
+}
+
 web3.eth.net.isListening()
-  .then(() => afterConnect(require('./processArgs')))
+  .then(() => afterConnect(args))
   .catch((error) => console.log(error))

Stop ganache.

Test the following command line:

$ node js/HelloWorldClient.js --help

All of the next command lines are INVALID (check the reason) and will force the application to terminate its execution! Test them!

$ node js/HelloWorldClient.js -m

$ node js/HelloWorldClient.js getMessage

$ node js/HelloWorldClient.js getMessage -a

$ node js/HelloWorldClient.js getMessage -a invalid

$ node js/HelloWorldClient.js setMessage -a 0xvalid

$ node js/HelloWorldClient.js setMessage -m -a 0xvalid

Start ganache.

Test the VALID commands: [cmd1], [cmd2], [cmd3] and [cmd4].

Note that, despite having valid calls, the setMessage and getMessage commands are still not working. We will implement them next.

Commit your work:

$ git add .
$ git commit -m 'Added the yargs support'
Implementing the functions getMessageAt and setMessageAt

Apply the following patch to js/HelloWorldClient.js:

diff --git a/js/HelloWorldClient.js b/js/HelloWorldClient.js
index 7abc7e6..934e91e 100644
--- a/js/HelloWorldClient.js
+++ b/js/HelloWorldClient.js
@@ -29,6 +29,26 @@ async function setMessage(helloWorld, message, accounts, callback) {
 async function getMessage(helloWorld) {
   const message = await helloWorld.methods.message().call()
   console.log(`Message: ${message}`)
+  return helloWorld
+}
+
+async function getMessageAt(address) {
+  let helloWorld = null
+  try {
+    console.log(`Looking for a HelloWorld contract at ${address}`)
+    helloWorld = await getMessage(new web3.eth.Contract(abi, address))
+  } catch (error) {
+    console.log('HelloWorld not found!')
+  }
+  return helloWorld
+}
+
+async function setMessageAt(address, message, accounts) {
+  console.log(`Setting the message "${message}" for a HelloWorld contract"`)
+  const helloWorld = await getMessageAt(address)
+  if (helloWorld !== null) {
+    await setMessage(helloWorld, message, accounts, getMessage)
+  }
 }
 
 async function afterConnect({ command, address, message }) {
@@ -44,10 +64,10 @@ async function afterConnect({ command, address, message }) {
       }
       break
     case 'getMessage':
-      console.log(`getMessageAt({address: "${address}"})`)
+      await getMessageAt(address)
       break
     case 'setMessage':
-      console.log(`setMessageAt({address: "${address}", message: "${message}", account: "${accounts[0]}")`)
+      await setMessageAt(address, message, accounts)
       break
   }
 }

Go to the [running-final-code-1] and test all the commands presented by this session.

Commit your work:

$ git commit -am "Implemented the functions {get,set}MessagetAt in 'js/HelloWorldClient.js'"

Congratulations! 😀 We’re done with this session!

4.2. Using @truffle/hdwallet-provider

Under development.

4.3. Deploy to Rinkeby

Under development.

4.4. Accessing the contract through a web client

Under development.

5. Hardhat version

Under development.