Beginning Ethereum Smart Contracts Programming
Beginning Ethereum Smart Contracts Programming
Smart Contracts
Programming
With Examples in Python, Solidity,
and JavaScript
Second Edition
Wei-Meng Lee
Beginning Ethereum Smart Contracts Programming: With Examples in Python,
Solidity, and JavaScript
Wei-Meng Lee
Ang Mo Kio, Singapore
Introduction�����������������������������������������������������������������������������������������������������������xvii
iii
Table of Contents
iv
Table of Contents
v
Table of Contents
vi
Table of Contents
viii
Table of Contents
Index��������������������������������������������������������������������������������������������������������������������� 361
ix
About the Author
Wei-Meng Lee is the founder of Developer Learning
Solutions, a technology company specializing in hands-on
training of blockchain and other emerging technologies.
He has many years of training expertise and his courses
emphasize a learn-by-doing approach. He is a master at
making learning a new programming language or technology
less intimidating and more fun. He can be found speaking
at conferences worldwide such as NDC, and he regularly
contributes to online and print publications such as Medium
(https://weimenglee.medium.com) and CoDe Magazine.
He is active on social media, on his blog calendar.learn2develop.net, on Facebook
(www.facebook.com/DeveloperLearningSolutions), on Twitter as @weimenglee, and
on LinkedIn (linkedin.com/leeweimeng).
xi
About the Technical Reviewer
Prasanth Sahoo is a Blockchain Certified Professional,
Professional Scrum Master, and Microsoft Certified
Trainer who is passionate about helping others
learn how to use and gain benefits from the latest
technologies. He is a thought leader and practitioner
in blockchain, cloud, and Scrum. He also handles the
Agile methodology, cloud, and blockchain technology
community initiatives within TransUnion through
coaching, mentoring, and grooming techniques.
Prasanth is an adjunct professor and a technical speaker. He was selected as a speaker
at the China International Industry Big Data Expo 2018 by the Chinese government and
also to the International Blockchain Council by the governments of Telangana and Goa.
He also received accolades for his presentation at China International Industry Big Data
Expo 2018 by the Chinese government. Prasanth has published a patent titled "Digital
Educational Certificate Management System using IPFS Based Blockchain."
To date, Prasanth has reached over 50,000 students, mostly within the technical
domain. He is a working group member of the CryptoCurrency Certification
Consortium, Scrum Alliance, Scrum Organization, and International Institute of
Business Analysis.
xiii
Acknowledgments
Writing a book is immensely exciting, but along with it comes long hours of hard work
and responsibility, straining to get things done accurately and correctly. To make a book
possible, a lot of unsung heroes work tirelessly behind the scenes.
For this, I would like to take this opportunity to thank a number of special people
who made this book possible. First, I want to thank my acquisitions editor, Joan Murray,
for giving me this opportunity. Thanks for suggesting that I update this book with the
latest happenings in the crypto world!
Next, a huge thanks to Jill Balzano, my associate editor, who was always very patient
with me, even though I missed several of my deadlines for the revision of this book.
Thanks, Jill, for your guidance. I could not finish the book without your encouragement
and help!
Equally important is my project coordinator, Shobana Srinivasan. Shobana has been
very patient with me during the whole project while I struggle between work and writing.
Thanks, Shobana, for the assistance rendered during the project!
Last, but not least, I want to thank my parents and my wife, Sze Wa, for all
the support they have given me. They have selflessly adjusted their schedules to
accommodate my busy schedule when I was working on this book. I love you all!
xv
Introduction
Welcome to Beginning Ethereum Smart Contracts Programming, Second Edition!
This book is a quick guide to getting started with Ethereum smart contracts
programming. It starts off with a discussion of blockchain and the motivations behind it.
You will learn what a blockchain is, how blocks in a blockchain are chained together, and
how blocks get added to a blockchain. You will also understand how mining works and
discover the various types of nodes in a blockchain network. Since the publication of the
first edition of this book, a lot of things have changed. In particular, Ethereum has been
updated to use Proof of Stake (PoS) (instead of Proof of Work) as its consensus algorithm.
This book has been updated to include a discussion of how PoS works.
Once that is out of the way, you dive into the Ethereum blockchain. You will learn
how to use an Ethereum client (Geth) to create a private Ethereum blockchain and
perform simple transactions such as sending Ethers to another account.
The next part of this book discusses smart contract programming, a unique feature of
the Ethereum blockchain. You will jumpstart on smart contracts programming without
needing to wade through tons of documentation. The learn-by-doing approach of this
book makes you productive in the shortest amount of time. By the end of this book,
you should be able to write smart contracts, test them, deploy them, and create web
applications to interact with them. In this second edition, I have added more examples
to make it easy for you to explore more complex smart contracts.
The last part of this book touches on tokens and DeFi (decentralized finance),
something that has taken the cryptocurrency market by storm. You will be able to create
your own tokens, launch your own ICO, and write token contracts that allow buyers
to buy tokens using Ethers. As a bonus, I show you how to write a DEX (decentralized
exchange) smart contract to exchange two different tokens!
This book is designed for those who want to get started quickly with Ethereum smart
contracts programming. Basic programming knowledge and an understanding of Python
or JavaScript are recommended.
I hope you enjoy working on the sample projects as much as I enjoyed creating them!
xvii
CHAPTER 1
Understanding the
Science Behind
Blockchain: Cryptography
The reason you are reading this book is because you want to understand what a
blockchain is, how it works, and how you can write smart contracts on it to do cool
things. And while I perfectly understand that you are excited to get started in this first
chapter, we need to take a step back and look at one fundamental technology that makes
blockchain possible: cryptography.
In this chapter, I will explain what cryptography is, the different types of
cryptographic algorithms, how they work, and how they play a vital role in the world
of blockchain. I will also show you how to experiment with the various cryptographic
algorithms using the Python programming language. Even if you are familiar with
cryptography, I suggest scanning through this chapter so that you have a firm foundation
for the subsequent chapters.
What Is Cryptography?
Whether you are trying to build a web application to store users’ credentials or writing
a network application to securely transmit encrypted messages, or even trying to
understand how blockchain works, you need to understand one important topic:
cryptography.
So, what exactly is cryptography? Put simply, cryptography (or cryptology) is the
practice and study of hiding information. It is the science of keeping information secret
and safe.
1
© Wei-Meng Lee 2023
W.-M. Lee, Beginning Ethereum Smart Contracts Programming, https://doi.org/10.1007/978-1-4842-9271-6_1
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
One of the simplest and most widely known cryptographic algorithms is the Caesar
Cipher. It is a very simple algorithm in which each letter in the plaintext is replaced by a letter
a fixed number of positions down the alphabet. Consider the example shown in Figure 1-1.
As you can observe, each character in the alphabet is shifted down three positions.
A becomes D, B becomes E, and so on. If you want to send a sentence (known as the
plaintext), say ATTACK, to your recipient, you map each of the characters in the sentence
using the above algorithm and derive the encrypted sentence (known as the ciphertext):
DWWDFN. When the recipient receives the ciphertext, they reverse the process to obtain
the plaintext. While this algorithm may seem impressive (especially in the early days
of cryptography), it no longer works as intended as soon as someone knows how the
messages are encrypted. Nevertheless, this is a good illustration of the attempt by early
inventors of cryptography to hide information. Today, the cryptographic algorithms we
use are much more sophisticated and secure.
In the following sections, I will explain the main types of cryptographic functions
and how they are used.
Types of Cryptography
There are three main types of cryptography:
• Hash functions
• Symmetric cryptography
• Asymmetric cryptography
In the following sections, I will go through each of the above types in more detail.
2
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
Hash Functions
Hashing is the process in which you convert a block of data of arbitrary size to a
fixed-size value. The function that performs this process is known as a hash function .
Figure 1-2 shows the hashing process.
Tip A commonly-used hash function is SHA256. SHA stands for Secure Hash
Algorithms.
For example, the SHA256 hash function converts a block of text into a 256-bit
hash output. The resultant hash is usually written in hexadecimal, and since each
hexadecimal takes up 4 bits, a 256-bit hash will have 64 characters. To experience how
hashing works, go to https://emn178.github.io/online-tools/sha256.html, type in a
sentence, and observe the result (see Figure 1-3).
3
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
• Deterministic: The same block of text will always produce the same
hash output.
Another important feature of hashing is that a single change in the original text will
cause a totally different hash to be generated. This is also known as the avalanche effect.
For example, a change in a single character in the input shown in Figure 1-4 will have a
totally different output.
4
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
Figure 1-4. A single change in the input will cause a totally different output hash
Uses of Hashing
Hashing fulfils some very important roles in computing. For one, websites use hashing
to store your password, instead of storing it in plaintext. Storing your password as hashes
prevents hackers from reversing the hashes and obtaining your original password (which
may very likely be used on other websites as well).
Hashing also plays a very crucial role in blockchain, where each block is “chained”
to the previous block using the hash of the previous block. Any modifications to a block
will invalidate the hash stored in the next block, and the rest of the blocks will hence be
invalid.
Tip Some commonly used hashing algorithms are MD5, SHA256, SHA512, and
Keccak-256.
5
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
Note To install Python on your computer, the easiest way is to download the
Anaconda package (www.anaconda.com/products/distribution). If you do
not want to install Python on your computer, you can use Google Colab
(https://colab.research.google.com).
In Python, you can use the hashlib module to perform hashing. The following code
snippet uses the sha256() function to perform hashing on a string:
import hashlib
result = hashlib.sha256(
bytes("The quick brown fox jumps over the lazy dog",'utf-8'))
Note that the string to be hashed must be passed to the sha256() function as a
byte array. And so you use the bytes() function to convert the string into a byte array.
Alternatively, in Python, you can prefix the string with a b to denote a bytes string literal:
result = hashlib.sha256(
b'The quick brown fox jumps over the lazy dog')
The sha256() function returns a sha256 hash object. To get the resultant hash in
hexadecimal, you can call the hexdigest() function of the sha256 hash object:
print(result.hexdigest())
d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592
If you make a small change to the original string, the output is drastically different
from the previous hash:
result = hashlib.sha256(
b'The quick brown fox jumps over the lazy dag')
print(result.hexdigest())
# output:
# 559cc2cb0e1998182b4b6343e38611b3757e8a6279d43e9914d74dfb7e7089e6
6
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
Symmetric Cryptography
In symmetric cryptography, you use the same cryptographic key (commonly referred to
as the shared key) for both the encryption of plaintext and the decryption of ciphertext.
Figure 1-5 shows the use of the shared key for both encryption and decryption.
Symmetric cryptography is fast and simple, but the main problem is how to ensure
that the key is kept secret. For example, if Tom wants to send a secret message to Susan,
Tom can encrypt the message using the shared key and Susan can decrypt the encrypted
message using the same shared key. The problem here is how can Tom securely send
Susan the shared key? Can Tom email Susan? Send it through SMS or WhatsApp? How
about through the traditional post office? All these methods are not absolutely safe and
are subject to eavesdropping. Moreover, there is this popular saying, “Three may keep a
secret if two of them are dead.” This means, if more than one person knows the secret, it
is no longer a secret.
Having said that, symmetric cryptography has its uses and applications. It is useful
when you want to protect your private data. For example, say you have some confidential
data on your computer that you want to prevent others from seeing. Using symmetric
cryptography, you can encrypt and decrypt the data using the same key, which is only
known to you and no one else.
7
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
Let’s generate a shared key in Python. To do so, use the Fernet class:
The generate_key() function returns a shared key in binary format and it is base64
encoded.
8
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
You can then use the encrypt() function to encrypt your data:
Then, create an instance of the Fernet class using the shared key and call the
decrypt() function to decode the ciphertext:
9
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
Asymmetric Cryptography
Unlike symmetric cryptography, which uses a single shared key, asymmetric
cryptography uses a key-pair, one public and one private.
A public key algorithm generates two keys that are mathematically linked:
• One public and one private: The public key, as the name implies,
should be made public. The private key, on the other hand,
absolutely must be kept a secret.
• You can encrypt data with a public key and decrypt with the private
key. For example, if Tom wants to send a secret message to Susan,
Tom could encrypt the message using Susan’s public key and only
Susan can decrypt the secret message with her private key.
• You can encrypt data with a private key and decrypt with the public
key. At first, this sounds counterintuitive. If one could decrypt using
the public key (which is supposed to be public), what’s the point of
this? Actually, this is useful. Suppose Tom encrypts a message using
his own private key and sends it to Susan. When Susan receives
the message, she can try to decrypt it using Tom’s public key. If the
message can be decrypted, this means that the message has not been
tampered with and that it indeed comes from Tom. On the other
hand, if the message has been tampered with, Susan would not be
able to decrypt the message using Tom’s public key. This technique is
used in creating a digital signature.
10
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
Figure 1-6 shows the first approach, that of encrypting the data using the public key
and then decrypting it using the private key.
Figure 1-6. Encrypting data using the public key and then decrypting the
ciphertext using the private key
Figure 1-7 shows the second approach of encrypting the data using the private key
and then decrypting it using the public key.
11
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
Figure 1-7. Encrypting data using the private key and then decrypting the
ciphertext using the public key
12
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
In this code snippet, you are using the RSA algorithm to first generate a private key.
Using the private key, you can then derive its corresponding public key. Once the keys
are generated, it’s useful to serialize (flatten) them to files:
13
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
14
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
15
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
• The hash is then encrypted using the private key and turned into a
digital signature (this also includes the information on the hashing
algorithm used).
• The original text, together with the digital signature, are then sent to
the recipient. This is known as the signed message.
Figure 1-9 shows what happens when the signed message is received by the
recipient.
• When the signed message is received, the receiver uses the sender’s
public key to decrypt the hash from the digital signature.
• The receiver also hashes the received text and compares it with the
hash that was decrypted in the previous step.
• If the two hashes match, this means the text has not been
tampered with.
16
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
private_key.encrypt(...) # ERROR
# AttributeError: '_RSAPrivateKey' object has no attribute 'encrypt'
import base64
Here you use the private key to sign the message. The sign() function returns the
digital signature of the string. It returns the digital signature as a byte array, and in the
above code snippet you encode it using base64 encoding and then converted it to string.
The output looks like this:
aNUZixxLUiRRpDjm+nqkcaZo5URklvIA/hiSECR+DoLmS+oVb650Ic5/vg6ADmCvi91CSwiXRY
kknDBEr2qTWaK+Fe9UPqukDFx8WwyW7K2NacjS8TiKqAfPPSH4t2l9ohexwTqfih9oZXli57zf
Z4LKaY63iQxXlWKE9S5OZ0hWyGUfygEInY8OZerGKWFnmxuXHjWNCpDmzSngP04MYBBnfoPVps
Dg7vgKL0gpaz1dn2Qg+Ra2GFLmznqjYKq2qP43zLrdYSmzH3MmPAkO0AIh8XaRnHc+q0XYyUGhT
Bm9iIa7rS8eYaB7MD9G18j0HA7lWWVQjqujnFCQNm8Npg==
When you transmit the message (plaintext), you also send the digital signature
along with it.
17
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
try:
public_key.verify(
signed,
plaintext, # from the previous section
padding.PSS(
mgf = padding.MGF1(hashes.SHA256()),
salt_length = padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
print('Signature is valid!')
except InvalidSignature:
print('Signature is invalid!')
Note that you have to catch the exception raised by the verify() function. If there is
no exception, the signature is deemed to be correct; otherwise, it is invalid.
18
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
The following sections discuss how the various cryptographic algorithms are used in
blockchain. If you are new to blockchain, feel free to skip the following sections and read
the next chapter. Come back to the next few sections after you have read the following
chapters:
• Chapter 2: Hashing is used to “chain” the blocks in a blockchain.
H
ashing
As mentioned briefly, hashing is used to “chain” the blocks in a blockchain. Each block
in a blockchain contains the hash of the previous block. Doing so allows you to ensure
that data recorded on the blockchain are immutable and thus prevent tampering.
Another good use case of hashing is when storing data on the blockchain. Since all
data on public blockchains are open to scrutiny, you should not store private data on the
public blockchain. If you need to store private data on a public blockchain for proofing
purposes, you should instead store the hash of the data, since it is not reversible.
Chapter 7 provides a good example of this.
19
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
Using this private key, a public key is generated using ECDSA (Elliptic Curve Digital
Signature Algorithm). This public key is then used to generate the account address using
the Keccak256 hashing algorithm. The last 20 bytes of this output is used as the address
of the account.
At the same time, remember that when you create an account you must supply
a password. This password is used to encrypt your private key using symmetric
cryptography.
D
igital Signature
In blockchain, a digital signature is used when creating transactions. In Chapter 4, you
will learn how to create a simple transaction by sending some Ethers from one account
to another.
When you create a transaction, the transaction is signed using your account’s private
key to derive the digital signature. The digital signature, together with the details of the
transaction, are then broadcasted to the various miners/validators in the blockchain
network (see Figure 1-11).
20
Chapter 1 Understanding the Science Behind Blockchain: Cryptography
Figure 1-11. A digital signature is created for a transaction to prove the identity of
the creator
S
ummary
In this chapter, you learned about the main science behind blockchain: cryptography.
You learned about hashing, symmetric cryptography, and asymmetric cryptography. If
you are familiar with Python, I strongly suggest you try out the code samples so that you
get first-hand experience with the various cryptographic algorithms. With that, you are
now ready to dive into the world of blockchain. See you in Chapter 2!
21
CHAPTER 2
Understanding Blockchain
One of the hottest technologies of late is blockchain. But what exactly is a blockchain?
And how does it actually work? In this chapter, you will explore the concept of
blockchain, how the concept was conceived, and what problems it aims to solve. By the
end of this chapter, the idea and motivation behind blockchain will be crystal clear.
Tip For the clearly impatient, a blockchain is a digital transaction of records that
is arranged in chunks of data called blocks. These blocks link with each other
through a cryptographic validation known as a hashing function. Linked together,
these blocks form an unbroken chain, a blockchain. A blockchain is programmed
to record not only financial transactions but virtually everything of value. Another
common name for blockchain is distributed ledger.
Hold on tight, as I’m going to discuss a lot of concepts in this chapter. But if you
follow along closely, you’ll understand the concepts of blockchain and be on your way to
creating some really creative applications on the Ethereum blockchain in the upcoming
chapters!
23
© Wei-Meng Lee 2023
W.-M. Lee, Beginning Ethereum Smart Contracts Programming, https://doi.org/10.1007/978-1-4842-9271-6_2
Chapter 2 Understanding Blockchain
Placement of Trust
All of this boils down to one key concept: the placement of trust. And that is, we place
our trust in a central body. Think about it. In our everyday life, we place our trust in
banks and we place our trust in our governments.
Even for simple mundane day-to-day activities, we place our trust in central bodies.
For example, when you go to the library to borrow a book, you trust that the library will
maintain a proper record of the books you have borrowed and returned.
The key theme is that we trust institutions but don’t trust each other. We trust our
government, banks, even our library, but we just don’t trust each other. As an example,
consider the following scenario. Imagine you work at a cafe, and someone walks up to
you and offers you a $10 bill for two cups of coffee. Another person offers to pay you
for the two cups of coffee using a handwritten note saying he owes you $10. Which one
would you trust? The answer is pretty obvious, isn’t it? Naturally you would trust the
$10 bill as opposed to the handwritten note. This is because you understand that you
can use the $10 bill elsewhere for other goods or services and that it is backed by the US
government. In contrast, the handwritten note is not backed by anyone, except perhaps
the person who wrote it, and hence it has literally no value.
Now let’s take the discussion a bit further. Again, imagine you are trying to sell
something. Someone comes up to you and suggests paying for your goods using the
currencies shown in Figure 2-1.
24
Chapter 2 Understanding Blockchain
Would you accept the currencies shown in the figure? Here, you have two different
currencies, one from Venezuela and one from Zimbabwe. In this case, the first thing you
consider is whether these currencies are widely accepted. Then you consider your trust
in these governments. You might have read in the news about the hyperinflation in these
two countries, and that these currencies might not retain their value over time. So, would
you accept these currencies as payment?
Trust Issues
Earlier, I mentioned that people trust institutions and don’t trust each other. But even
established economies can fail, such as in the case of the financial crisis of the United
States in 2007–2008. Investment bank Lehman Brothers collapsed in September 2008
because of the subprime mortgage market. So, if banks from established economies
can collapse, how can people in less-developed countries trust their banks and
governments? Even if the banks are trusted, your deposits may be monitored by the
government, and they could arrest you based on your transactions.
As you saw in the example in the previous section, there are times when people don’t
trust institutions, especially if the political situation in that country is not stable.
25
Chapter 2 Understanding Blockchain
This brings us to the next key issue: even though people trust institutions,
institutions can fail. And when people lose trust in institutions, people turn to
cryptocurrencies. In the next section, I will discuss how to solve the trust issues using
decentralization, a fundamental concept behind cryptocurrency.
Example of Decentralization
To understand how decentralization solves the trust issue, let’s consider a real-life
example.
Imagine a situation where you have three persons with DVDs that they want to share
with one another (see Figure 2-2).
26
Chapter 2 Understanding Blockchain
The first thing they need to do is to have someone keep track of the whereabouts
of each DVD. Of course, the easiest is for each person to keep track of what they have
borrowed and what they have lent, but since people inherently do not trust each other,
this approach is not very popular among the three people.
To solve this issue, they decided to appoint one person, say B, to keep a ledger of the
whereabouts of each DVD (see Figure 2-3).
This way, there is a central body to keep track of the whereabouts of each DVD. But
wait, isn’t this the problem with centralization? What happens if B is not trustworthy?
Turns out that B has the habit of stealing DVDs, and he can easily modify the ledger to
erase the record of DVDs that he has borrowed. So, there must be a better way.
Then someone has an idea! Why not let everyone keep a copy of the ledger?
Whenever someone borrows or lends a DVD, the record is broadcast to everyone, and
everyone records the transaction. See Figure 2-4.
27
Chapter 2 Understanding Blockchain
So now the record keeping is decentralized. Three people now hold the same ledger.
But wait a minute. What if A and C conspire to change the ledger so that they can steal
the DVDs from B? Since majority wins, as long as there is more than 50% of the people
with the same records, the others would have to listen to the majority. And because there
are only three people in this scenario, it is extremely easy to get more than 50% of the
people to conspire.
The solution is to have a lot more people to hold the ledger, especially people who
are not related to the DVD-sharing business (see Figure 2-5).
28
Chapter 2 Understanding Blockchain
Figure 2-5. Getting a group of unrelated people to help keep the records
This way, it makes it more difficult for one party to alter the records in the ledger. In
order to alter a record, it would need to involve a number of people to do so all at the
same time, which is a time-consuming affair. And this is the key idea behind a distributed
ledger, commonly known as blockchain.
29
Chapter 2 Understanding Blockchain
Figure 2-6. Transactions form a block, and blocks are then chained
30
Chapter 2 Understanding Blockchain
Figure 2-7. Every blockchain has a beginning block known as the genesis block
The blocks are connected to each other cryptographically, the details of which
I will discuss in the sections ahead. The first block in a blockchain is known as the
genesis block.
So, the next important questions is, how do you chain the blocks together?
Tip Chapter 1 discussed hashing in more detail. Be sure to read it first before
continuing with this section.
31
Chapter 2 Understanding Blockchain
Recall that a hash function is a function that maps data of arbitrary size to data of
fixed size. By altering a single character in the original string, the resultant hash value is
totally different from the previous one. Most importantly, observe that a single change
in the original message results in a completely different hash, making it difficult to know
that the two original messages are similar.
A hash function has the following characteristics:
You are now ready to learn how blocks in a blockchain are chained together. To chain
the blocks together, the content of each block is hashed and then stored in the next block
(see Figure 2-8). This way, if any transaction in a block is altered, this will invalidate the
hash of the current block, which is stored in the next block, which in turn invalidates the
hash of the next block, and so on.
32
Chapter 2 Understanding Blockchain
Observe that when hashing the content of a block, the hash of the previous block is
hashed together with the transactions. However, do take note that this is a simplification
of what is in a block. Later on, you will dive into the details of a block and see exactly how
transactions are represented in a block.
Storing the hash of the previous block in the current block assures the integrity of
the transactions in the previous block. Any modifications to the transaction(s) within a
block causes the hash in the next block to be invalidated and also affects the subsequent
blocks in the blockchain. If a hacker wants to modify a transaction, not only must they
modify the transaction in a block but all other subsequent blocks in the blockchain. In
addition, they need to synchronize the changes to all other computers on the network,
which is a computationally expensive task to do. Hence, data stored in the blockchain
is immutable, for they are hard to change once the block they are in is added to the
blockchain.
At this juncture, you have a pretty good idea of what a blockchain looks like. In
the real world, the blockchain is stored on multiple computers (called nodes) usually
scattered geographically around the world (see Figure 2-9).
33
Chapter 2 Understanding Blockchain
Each of these nodes are connected to each other in a peer-to-peer fashion. Nodes
that store the entire blockchain are known as full nodes.
Immutability of Blockchains
In a blockchain, each block is chained to its previous block through the use of a
cryptographic hash. A block’s identity changes if the parent’s identity changes. This in
turn causes the current block’s children to change, which affects the grandchildren, and
so on. A change to a block forces a recalculation of all subsequent blocks, which requires
enormous computation power. This makes the blockchain immutable, a key feature of
cryptocurrencies like Bitcoin and Ethereum.
34
Chapter 2 Understanding Blockchain
Tip In general, once a block has six or more confirmations, it’s deemed infeasible
for it to be reversed. Therefore, the data stored in the blockchain is immutable.
Consensus Protocols
When a transaction is performed, the transaction is broadcasted to the network to
be collated into a block so that subsequently the block can be added to the existing
blockchain (see Figure 2-11).
35
Chapter 2 Understanding Blockchain
So who actually collates the transactions? And since there may be multiple
transactions going on at the same time, who gets to add the block to the blockchain? This
is where consensus protocols (also known as consensus algorithms) come in.
A consensus protocol, as its name implies, is a method for all the nodes in a
blockchain to reach an agreement. Since transactions are broadcasted to the network,
there must be some sort of agreement among all the nodes to decide which transactions
go into each block. If not, the blockchain would make no sense.
In the world of blockchain, there are quite a few consensus protocols in use. Here are
some of them:
36
Chapter 2 Understanding Blockchain
For this chapter, I will only focus on the first two consensus protocols stated above.
Bitcoin’s implementation of blockchain uses Proof of Work. Ethereum initially used
Proof of Work but transitioned in September 2022 to a much more energy-efficient
consensus protocol, Proof of Stake.
Proof of Work
In PoW, nodes that collate all the transactions are known as mining nodes (or miner
nodes). When a transaction is performed, the transaction is broadcasted to all mining
nodes. Figure 2-12 shows four transactions created by different users on the network
broadcasting them to the various mining nodes.
Figure 2-12. Transactions are broadcasted to mining nodes, which assemble them
into blocks to be mined
Due to network latency, each mining node may receive these transactions at
different timings. As a node receives transactions, it will try to include them in a block.
Observe that each node is free to include whatever transactions it wants in a block. In
practice, which transactions get included in a block depends on several factors, such
37
Chapter 2 Understanding Blockchain
as transaction fees, transaction size, order of arrival, and so on. The aim of the miner is
to try to fill the block with transactions so that it can proceed to the next step, which is
adding the block to the blockchain.
At this point, transactions that are included in a block but are not yet added to
the blockchain are known as unconfirmed transactions. Once a block is filled with
transactions, a mining node will attempt to add the block to the blockchain.
Now here comes the problem: with so many miners out there, who gets to add the
block to the blockchain first?
Figure 2-13. Hashing the block to meet the network difficulty target
38
Chapter 2 Understanding Blockchain
In order to successfully add a block to the blockchain, a miner hashes the content
of a block and checks that the hash meets the criteria set by the difficulty target. For
example, the resultant hash must start with five zeros and so on.
As more miners join the network, the difficultly level increases. For example, the
hash must now start with six zeros and so on. This allows the blocks to be added to the
blockchain at a consistent rate.
Note For Bitcoin, the difficulty is adjusted every 2,016 blocks (or every 2 weeks
approximately) so that the average time between each block remains 10 minutes.
But wait a minute! The content of a block is fixed, and so no matter how you hash it,
the resultant hash is always the same. So how do you ensure that the resultant hash can
meet the difficulty target? To do that, miners add a nonce to the block, which stands for
number used once (see Figure 2-14).
Figure 2-14. Adding a nonce to change the content of the block in order to meet
the network difficulty target
39
Chapter 2 Understanding Blockchain
The first miner who meets the target gets to claim the reward and add the block to
the blockchain. They do so by broadcasting the block to other nodes so that they can
verify the claim and stop working on their current work of mining their own blocks.
These miners drop their current work, and the process of mining a new block starts all
over again. The transactions that were not included in the block that was successfully
mined are added to the next block to be mined.
In the case of Bitcoin, the block reward initially was 50 BTC and halved every 210,000 blocks.
At the time of writing, the block reward is currently at 6.25 BTC, and it will eventually be
reduced to 0 after 64 halving events. For Ethereum, prior to the Merge, the reward for mining
a block is 2 ETH (Ether). After the Merge, the rewards are slightly more complicated. You can
visit https://ethereum.org/en/developers/docs/consensus-mechanisms/pos/
rewards-and-penalties/ for more details.
For Bitcoin, the network adjusts the difficulty of the puzzles so that a new block is mined
roughly every 10 minutes. For Ethereum using PoW, a block is mined approximately every 14
seconds.
Why is this process called Proof of Work? Well, because it takes a lot of work to find
the proof! It is difficult to find the proof but easy to validate the proof. A good example of
PoW is cracking a combination lock. It takes a lot of time to find the right combination,
but it is easy to verify once the combination is found.
40
Chapter 2 Understanding Blockchain
DOUBLE-SPENDING
Double-spending is a problem that arises when you try to spend the same cryptocurrency
twice. Double-spending is not a problem when dealing with fiat-currency. Your $100 bill in
your hand can not be at another place at the same time. However, it is a potential problem
in the crypto world. Suppose you have 1 Bitcoin in your wallet, and you send it to a friend.
Immediately after this and before the transaction is confirmed, you send the same Bitcoin to
another friend. Essentially, you are trying to spend the same Bitcoin twice. Fortunately, with the
consensus protocol in place, miners are able to ensure that the second transaction is not valid
because you already spent it in an earlier transaction.
PoW uses tremendous computing resources. GPUs are required (as well as ASICs,
application-specific integrated circuits, for some blockchains), while CPU speed is
not important. It also uses a lot of electricity, because miners are doing the same work
repeatedly, that of finding the nonce to meet the network difficulty for the block.
A common question is why you need to use a powerful GPU instead of CPU for
mining. Well, as a simple comparison, a CPU core can execute 4 32-bit instructions per
clock, whereas a GPU like the Radeon HD 5970 can execute 3200 32-bit instructions per
clock. In short, the CPU excels at doing complex manipulations to a small set of data,
whereas the GPU excels at doing simple manipulations to a large set of data. And since
mining is all about performing hashing and finding the nonce, it is a highly repetitive
task, something that the GPU excels in.
Tip When a miner has successfully mined a block, they earn mining fees as well
as transaction fees. That’s what keeps miners motivated to invest in mining rigs
and keep them running 24/7, thereby incurring substantial electricity bills.
In PoS, what used to be called miners are now known as validators. These validators
mint (or forge) new blocks. To become a validator, a node must stake (hence its name
Proof of Stake) at least 32 Ethers. The size of Ethers staked will affect the chance of a
node being selected to be the next validator to mint the next block.
When a node becomes a validator, it will perform the same tasks that miners do:
validate all the transactions in the block to ensure that they are valid. When everything
checks out, the validator signs off the block, adds it to the blockchain, and then
synchronize the blockchain across the network. This validator receive the transaction
fees from the block they minted.
Compared with PoW where every miner is doing the same work, PoS seems to be
more efficient because only one validator is required. But this raises another issue:
what happens if the validator is not trustworthy and purposely allowed fraudulent
transactions to be added to a block? This is where the stake that the validator has placed
comes in. If it is later found that the validator has allowed fraudulent transactions to be
added to the block, the validator will lose a part of his stake. As long as the stake is higher
than the transaction fees that the validator can earn, we can trust that the validator will
do their job correctly. If the validator does not do their job properly, they will lose more
than they gain.
When the validator ceases to be one, all transaction fees plus their stake are released
back to them at a later date. This gives the network time to punish the validator in the
event that some of the minted blocks were later found to be fraudulent.
• In PoW, every miner node mines! In PoS, only a selected node gets to
forge new blocks.
42
Chapter 2 Understanding Blockchain
• PoS makes easier to forge a new block and doesn't need expensive
hardware, unlike PoW. This encourages more validators and hence
increases the security of the network.
• A block header
• A timestamp
• A nonce
Note that the block header contains the Merkle root and not the transactions (see
Figure 2-15). The transactions are collectively represented as a Merkle root, the details of
which will be discussed in the next few sections.
43
Chapter 2 Understanding Blockchain
Figure 2-15. A block contains the block header, which in turns contains the
Merkle root of the transactions
Types of Nodes
Before I address the rationale for storing the Merkle root in the block header, I need to
talk about the types of nodes in a blockchain network. Figure 2-16 shows the different
types of nodes in a blockchain network.
44
Chapter 2 Understanding Blockchain
Tip Full nodes are not necessarily mining/validator nodes. However, a mining/
validator node needs to be a full node.
The purpose of a full node is to ensure the integrity of the blockchain, and people
running full nodes do not get rewards. On the other hand, mining/validator nodes are
rewarded when they add a block to the blockchain.
An example of a full node is a desktop wallet, which allows users to perform
transactions using the cryptocurrency.
Each full node has a copy of the entire blockchain. Full nodes also validate every
block and transactions presented to it.
45
Chapter 2 Understanding Blockchain
Besides full nodes, there are also light nodes. Light nodes help to verify transactions
using a method called simplified payment verification (SPV). SPV allows a node to verify
if a transaction has been included in a block, without needing to download the entire
blockchain. Using SPV, light nodes connect to full nodes and transmit transactions to the
full nodes for verifications.
Light nodes only need to store the block headers of all the blocks in the blockchain.
An example of a light node is a mobile wallet, such as the Coinbase mobile app for
iOS and Android. Using a mobile wallet, a user can perform transactions on the
mobile device.
• Full node:
• Visit the following sites to see the current number of full nodes for
the following blockchains:
• Bitcoin: https://bitnodes.earn.com
• Ethereum: www.ethernodes.org/network/1
Finally, you’ll see the use of representing the transactions as a Merkle root in the
block header in the next section.
46
Chapter 2 Understanding Blockchain
Figure 2-17. How the Merkle root is derived from the Merkle tree
As you can see, each transaction is hashed. The hash of each transaction is hashed
together with the hash of another node. For example, the hash of transaction A (HA) is
combined with the hash of transaction B (HB) and hashed to derive HAB. This process
is repeated until there’s only one resultant hash. This final hash is known as the Merkle
root. In this example, because HE doesn’t have another node to pair with, it’s hashed with
itself. The same applies to HEE.
The Merkle root is stored in the block header, and the rest of the transactions are
stored in the block as a Merkle tree. In earlier discussion, I mentioned full nodes. Full
nodes download the entire blockchain. There’s another type of node (known as a light
node) that downloads only the blockchain headers. Because light nodes don’t download
the entire blockchain, they’re easier to maintain and run. Using a method called
simplified payment verifications (SPV), a light node can query a full node to verify a
transaction. Examples of light nodes are cryptographic wallets.
47
Chapter 2 Understanding Blockchain
• The light node queries a full node for the following hashes: HD, HAB,
and HEEEE (see Figure 2-18).
• Because the light node can compute HC, it can then compute HCD
with HD supplied.
• Because the light node has the Merkle root of the block, it can
now check to see if the two Merkle roots match. If they match, the
transaction is verified.
As you can see from this simple example, to verify a single transaction out of five
transactions, only three hashes need to be retrieved from the full node. Mathematically,
for n transactions in a block, it takes log2n hashes to verify that a transaction is in a block.
For example, if there are 1,024 transactions in a block, a light node only needs to request
10 hashes to verify the existence of a transaction in the block.
48
Chapter 2 Understanding Blockchain
Figure 2-18. How the Merkle tree and Merkle root are used to validate a
transaction
Summary
In this chapter, you learned the motivations behind blockchain and the problems that it
aims to solve. You explored how blocks are added to the blockchain through a process
known as mining. In the next chapter, you will learn how to build your own blockchain
using Python so that you can see and understand the inner workings of a blockchain.
49
CHAPTER 3
51
© Wei-Meng Lee 2023
W.-M. Lee, Beginning Ethereum Smart Contracts Programming, https://doi.org/10.1007/978-1-4842-9271-6_3
Chapter 3 Implementing Your Own Blockchain Using Python
To keep things simple, each block in your blockchain will contain the following
components:
• Hash of the previous block: The hash result of the previous block.
As shown in Figure 3-1, the hash is the result of hashing the content
of the block consisting of the timestamp, index, hash of the previous
block, nonce, and all the transactions.
52
Chapter 3 Implementing Your Own Blockchain Using Python
Note For simplicity, you are not going to worry about representing the
transactions in a Merkle tree, nor are you going to separate a block into block
header and content.
The network difficulty level is fixed at four zeros; that is, in order to derive the nonce,
the result of the hash of the block must start with four zeros.
Tip Refer to Chapter 2 for the idea behind the nonce and how it relates to
network difficulty level.
53
Chapter 3 Implementing Your Own Blockchain Using Python
Once the nonce is found, the block gets appended to the last block in the blockchain,
with the timestamp added (see Figure 3-3).
54
Chapter 3 Implementing Your Own Blockchain Using Python
Figure 3-3. Once a blocked is mined, it will be appended to the blockchain with
the timestamp added to the block
Installing Flask
For your conceptual blockchain, you will run it as a REST API so that you can interact
with it through REST calls. For this, you will use the Flask microframework. To install
Flask, type the following commands in Terminal:
Tip Flask is a web framework that makes building web applications easy
and rapid.
55
Chapter 3 Implementing Your Own Blockchain Using Python
import sys
import hashlib
import json
import requests
from urllib.parse import urlparse
class Blockchain(object):
difficulty_target = "0000"
def __init__(self):
# stores all the blocks in the entire blockchain
self.chain = []
# temporarily stores the transactions for the
# current block
self.current_transactions = []
# create the genesis block with a specific fixed hash
# of previous block genesis block starts with index 0
genesis_hash = self.hash_block("genesis_block")
56
Chapter 3 Implementing Your Own Blockchain Using Python
self.append_block(
hash_of_previous_block = genesis_hash,
nonce = self.proof_of_work(0, genesis_hash, [])
)
• The __init__() function is the constructor for the class. Here, you
store the entire blockchain as a list. Because every blockchain has a
genesis block, you need to initialize the genesis block with the hash
of the previous block, and in this example, you simply use a fixed
string called genesis_block to obtain its hash. Once the hash of the
previous block is found, you need to find the nonce for the block
using the method named proof_of_work() (which you will define in
the next section).
The proof_of_work() method (detailed next) will return a nonce that will result in a
hash that matches the difficulty target when the content of the current block is hashed.
For simplicity, you fix the difficulty_target to a hash result that starts with four
zeros (“0000”).
Tip The source code for your blockchain is shown at the end of this chapter. For
the impatient, you may wish to look at the code while you go through the various
concepts in this chapter.
# use PoW to find the nonce for the current block
def proof_of_work(self, index, hash_of_previous_block,
transactions):
# try with nonce = 0
nonce = 0
57
Chapter 3 Implementing Your Own Blockchain Using Python
# try hashing the nonce together with the hash of the
# previous block until it is valid
while self.valid_proof(index, hash_of_previous_block,
transactions, nonce) is False:
nonce += 1
return nonce
The proof_of_work() function starts with zero for the nonce and checks if the
nonce together with the content of the block produces a hash that matches the difficulty
target. If not, it increments the nonce by one and then tries again until it finds the
correct nonce.
The next method, valid_proof(), hashes the content of a block and checks to see if
the block’s hash meets the difficulty target:
58
Chapter 3 Implementing Your Own Blockchain Using Python
'timestamp': time(),
'transactions': self.current_transactions,
'nonce': nonce,
'hash_of_previous_block': hash_of_previous_block
}
# reset the current list of transactions
self.current_transactions = []
# add the new block to the blockchain
self.chain.append(block)
return block
When the block is added to the blockchain, the current timestamp is also added to
the block.
Adding Transactions
The next method to add to the Blockchain class is the add_transaction() method:
This method adds a new transaction to the current list of transactions. It then gets
the index of the last block in the blockchain and adds one to it. This new index is the
block that the current transaction will be added to.
To obtain the last block in the blockchain, define a property called last_block in the
Blockchain class:
@property
def last_block(self):
# returns the last block in the blockchain
return self.chain[-1]
59
Chapter 3 Implementing Your Own Blockchain Using Python
app = Flask(__name__)
Performing Mining
You also need to create a route to allow miners to mine a block so that it can be added to
the blockchain:
@app.route('/mine', methods=['GET'])
def mine_block():
blockchain.add_transaction(
sender="0",
recipient=node_identifier,
amount=1,
)
60
Chapter 3 Implementing Your Own Blockchain Using Python
When a miner manages to mine a block, they must receive a reward for finding the
proof. Here, you add a transaction to send one unit of reward to the miner to signify the
reward for successfully mining the block.
When mining a block, you need to find the hash of the previous block and use it
together with the content of the current block to find the nonce for the block. Once the
nonce is found, you append it to the blockchain.
Adding Transactions
Another route that you want to add to the API is the ability to add transactions to the
current block:
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
# get the value passed in from the client
61
Chapter 3 Implementing Your Own Blockchain Using Python
values = request.get_json()
# check that the required fields are in the POST'ed data
required_fields = ['sender', 'recipient', 'amount']
if not all(k in values for k in required_fields):
return ('Missing fields', 400)
# create a new transaction
index = blockchain.add_transaction(
values['sender'],
values['recipient'],
values['amount']
)
response = {'message':
f'Transaction will be added to Block {index}'}
return (jsonify(response), 201)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=int(sys.argv[1]))
In this implementation, you allow the user to run the API based on the specified
port number.
To start the first node, type the following command in terminal:
62
Chapter 3 Implementing Your Own Blockchain Using Python
Your first blockchain on the first node is now running. It is also listening at port 5000,
where you can add transactions to it and mine a block.
In another terminal window, type the following command to view the contents of the
blockchain running on the node:
$ curl http://localhost:5000/blockchain
{
"chain": [{
"hash_of_previous_block": "181cfa3e85f3c2a7aa9fb74f992d0d061d3e4a6
d7461792413aab3f97bd3da95",
"index": 0,
"nonce": 61093,
"timestamp": 1560757569.810427,
"transactions": []
}],
"length": 1
}
Let’s try mining a block to see how it affects the blockchain. Type the following
command in terminal:
$ curl http://localhost:5000/mine
{
"hash_of_previous_block": "0e8431c4a7fe132503233bc226b1f68c9d2bd4d30
af24c115bcdad461dda48a0",
"index": 1,
"message": "New Block Mined",
63
Chapter 3 Implementing Your Own Blockchain Using Python
"nonce": 24894,
"transactions": [{
"amount": 1,
"recipient": "084f17b6e5364cde86a231d1cc0c9991",
"sender": "0"
}]
}
Note Observe that the block contains a single transaction, which is the reward
given to the miner.
You can now issue the command to obtain the blockchain from the node:
$ curl http://localhost:5000/blockchain
You will see that the newly mined block is in the blockchain:
{
"chain": [{
"hash_of_previous_block": "181cfa3e85f3c2a7aa9fb74f992d0d061d3e4a
6d7461792413aab3f97bd3da95",
"index": 0,
"nonce": 61093,
"timestamp": 1560757569.810427,
"transactions": []
}, {
"hash_of_previous_block": "0e8431c4a7fe132503233bc226b1f68c9d2bd4d
30af24c115bcdad461dda48a0",
"index": 1,
"nonce": 24894,
"timestamp": 1560759370.988651,
"transactions": [{
"amount": 1,
"recipient": "084f17b6e5364cde86a231d1cc0c9991",
"sender": "0"
}]
64
Chapter 3 Implementing Your Own Blockchain Using Python
}],
"length": 2
}
Caution Windows does not support single quotes (') when using curl in the
command line. Hence, you need to use double quotes and use the slash character
(\) to turn off the meaning of double quotes (") in your doublequoted string. The
preceding command in Windows would be
$ curl http://localhost:5000/mine
65
Chapter 3 Implementing Your Own Blockchain Using Python
{
"hash_of_previous_block": "282991fe48ec07378da72823e6337e13be8524ced51
00d55c591ae087631146d",
"index": 2,
"message": "New Block Mined",
"nonce": 61520,
"transactions": [{
"amount": 5,
"recipient": "cd0f75d2367ad456607647edde665d6f",
"sender": "04d0988bfa799f7d7ef9ab3de97ef481"
}, {
"amount": 1,
"recipient": "084f17b6e5364cde86a231d1cc0c9991",
"sender": "0"
}]
}
This result shows that Block 2 has been mined and it contains two transactions: one
that you added manually and another that is the reward for the miner.
You can examine the contents of the blockchain by issuing this command:
$ curl http://localhost:5000/blockchain
You will see the newly added block containing the two transactions:
{
"chain": [{
"hash_of_previous_block": "181cfa3e85f3c2a7aa9fb74f992d0d061d3e4a6
d7461792413aab3f97bd3da95",
"index": 0,
"nonce": 61093,
"timestamp": 1560757569.810427,
"transactions": []
}, {
"hash_of_previous_block": "0e8431c4a7fe132503233bc226b1f68c9d2bd4d
30af24c115bcdad461dda48a0",
66
Chapter 3 Implementing Your Own Blockchain Using Python
"index": 1,
"nonce": 24894,
"timestamp": 1560759370.988651,
"transactions": [{
"amount": 1,
"recipient": "084f17b6e5364cde86a231d1cc0c9991",
"sender": "0"
}]
}, {
"hash_of_previous_block": "282991fe48ec07378da72823e6337e13be8524
ced5100d55c591ae087631146d",
"index": 2,
"nonce": 61520,
"timestamp": 1560760629.10675,
"transactions": [{
"amount": 5,
"recipient": "cd0f75d2367ad456607647edde665d6f",
"sender": "04d0988bfa799f7d7ef9ab3de97ef481"
}, {
"amount": 1,
"recipient": "084f17b6e5364cde86a231d1cc0c9991",
"sender": "0"
}]
}],
"length": 3
}
Synchronizing Blockchains
In real life, a blockchain network consists of multiple nodes maintaining copies of the
same blockchain. So, there must be a way for the nodes to synchronize so that every
single node is referring to the same identical blockchain.
When you use Python to run the blockchain.py application, only one node is
running. The whole idea of blockchain is decentralization. There should be multiple
nodes maintaining the blockchain, not just a single one.
67
Chapter 3 Implementing Your Own Blockchain Using Python
For your example, you will modify it so that each node can be made aware of
neighboring nodes on the network (see Figure 3-4).
To do that in your example, add a number of methods to the Blockchain class. First,
add a nodes member to the constructor of the Blockchain class and initialize it to an
empty set:
def __init__(self):
self.nodes = set()
# stores all the blocks in the entire blockchain
self.chain = []
...
68
Chapter 3 Implementing Your Own Blockchain Using Python
This nodes member stores the address of other nodes. Next, add a method called
add_node() to the Blockchain class:
This method allows a new node to be added to the nodes member. For example, if
“http://192.168.0.5:5000” is passed to the method, the IP address and port number
“192.168.0.5:5000” will be added to the nodes member.
The next method to add to the Blockchain class is valid_chain():
69
Chapter 3 Implementing Your Own Blockchain Using Python
• It goes through each block in the blockchain and hashes each block
and verifies that the hash of each block is correctly recorded in the
next block.
def update_blockchain(self):
# get the nodes around us that has been registered
neighbours = self.nodes
new_chain = None
# for simplicity, look for chains longer than ours
max_length = len(self.chain)
# grab and verify the chains from all the nodes in our
# network
for node in neighbours:
# get the blockchain from the other nodes
response = \
requests.get(f'http://{node}/blockchain')
if response.status_code == 200:
length = response.json()['length']
chain = response.json()['chain']
# check if the length is longer and the chain
# is valid
if length > max_length and \
self.valid_chain(chain):
max_length = length
new_chain = chain
# replace our chain if we discovered a new, valid
# chain longer than ours
if new_chain:
self.chain = new_chain
return True
return False
70
Chapter 3 Implementing Your Own Blockchain Using Python
@app.route('/nodes/add_nodes', methods=['POST'])
def add_nodes():
# get the nodes passed in from the client
values = request.get_json()
nodes = values.get('nodes')
if nodes is None:
return "Error: Missing node(s) info", 400
for node in nodes:
blockchain.add_node(node)
response = {
'message': 'New nodes added',
'nodes': list(blockchain.nodes),
}
return jsonify(response), 201
@app.route('/nodes/sync', methods=['GET'])
def sync():
updated = blockchain.update_blockchain()
if updated:
response = {
'message':
'The blockchain has been updated to the latest',
'blockchain': blockchain.chain
}
71
Chapter 3 Implementing Your Own Blockchain Using Python
else:
response = {
'message': 'Our blockchain is the latest',
'blockchain': blockchain.chain
}
return jsonify(response), 200
Tip You now have two nodes running: one listening at port 5000 and another
at 5001.
Let’s mine two blocks in the first node (5000) by typing the following commands in
another terminal window:
$ curl http://localhost:5000/mine
{
"hash_of_previous_block": "ac46b1f492997e27612a8b5750e0fe340a217aae89e
5c0efd56959d87127b4d3",
"index": 1,
"message": "New Block Mined",
"nonce": 92305,
"transactions": [{
"amount": 1,
"recipient": "db9ef69db7764331a6f4f23dbb8acd68",
"sender": "0"
}]
}
72
Chapter 3 Implementing Your Own Blockchain Using Python
$ curl http://localhost:5000/mine
{
"hash_of_previous_block": "790ed48f5d52b3eacd2f419e6fdfb2f6b3142
bcfc31943e4857b7ba4df48bd98",
"index": 2,
"message": "New Block Mined",
"nonce": 224075,
"transactions": [{
"amount": 1,
"recipient": "db9ef69db7764331a6f4f23dbb8acd68",
"sender": "0"
}]
}
$ curl http://localhost:5000/blockchain
{
"chain": [{
"hash_of_previous_block": "181cfa3e85f3c2a7aa9fb74f992d0d061d3e4a6
d7461792413aab3f97bd3da95",
"index": 0,
"nonce": 61093,
"timestamp": 1560823108.2946198,
"transactions": []
}, {
"hash_of_previous_block": "ac46b1f492997e27612a8b5750e0fe340a217
aae89e5c0efd56959d87127b4d3",
"index": 1,
"nonce": 92305,
"timestamp": 1560823210.26095,
"transactions": [{
"amount": 1,
"recipient": "db9ef69db7764331a6f4f23dbb8acd68",
"sender": "0"
}]
73
Chapter 3 Implementing Your Own Blockchain Using Python
}, {
"hash_of_previous_block": "790ed48f5d52b3eacd2f419e6fdfb2f6b3142bcf
c31943e4857b7ba4df48bd98",
"index": 2,
"nonce": 224075,
"timestamp": 1560823212.887074,
"transactions": [{
"amount": 1,
"recipient": "db9ef69db7764331a6f4f23dbb8acd68",
"sender": "0"
}]
}],
"length": 3
}
Since you have not done any mining on the second node (5001), there is only one
block in this node:
$ curl http://localhost:5001/blockchain
{
"chain": [{
"hash_of_previous_block": "181cfa3e85f3c2a7aa9fb74f992d0d061d3e4a6d
7461792413aab3f97bd3da95",
"index": 0,
"nonce": 61093,
"timestamp": 1560823126.898498,
"transactions": []
}],
"length": 1
}
74
Chapter 3 Implementing Your Own Blockchain Using Python
To tell the second node that there is a neighbor node, use the following command:
Tip This command registers a new node with the node at port 5001 that there is
a neighboring node listening at port 5000.
To tell the first node that there is a neighbor node, use the following command:
Tip This command registers a new node with the node at port 5000 that there is
a neighboring node listening at port 5001.
Figure 3-5 shows the two nodes aware of each other’s existence.
75
Chapter 3 Implementing Your Own Blockchain Using Python
Figure 3-5. The current states of the two nodes in your blockchain network
With the first node aware of the existence of the second node (and vice versa),
synchronize the blockchain starting from the first node:
$ curl http://localhost:5000/nodes/sync
{
"blockchain": [{
"hash_of_previous_block": "181cfa3e85f3c2a7aa9fb74f992d0d061d3e4a6d
7461792413aab3f97bd3da95",
"index": 0,
"nonce": 61093,
"timestamp": 1560823108.2946198,
"transactions": []
}, {
"hash_of_previous_block": "ac46b1f492997e27612a8b5750e0fe340a217aae
89e5c0efd56959d87127b4d3",
"index": 1,
"nonce": 92305,
"timestamp": 1560823210.26095,
"transactions": [{
76
Chapter 3 Implementing Your Own Blockchain Using Python
"amount": 1,
"recipient": "db9ef69db7764331a6f4f23dbb8acd68",
"sender": "0"
}]
}, {
"hash_of_previous_block": "790ed48f5d52b3eacd2f419e6fdfb2f6b3142
bcfc31943e4857b7ba4df48bd98",
"index": 2,
"nonce": 224075,
"timestamp": 1560823212.887074,
"transactions": [{
"amount": 1,
"recipient": "db9ef69db7764331a6f4f23dbb8acd68",
"sender": "0"
}]
}],
"message": "Our blockchain is the latest"
}
As the result shows, the first node has the longest chain (three blocks), so the
blockchain is the latest and it remains intact. You can synchronize from the second node:
$ curl http://localhost:5001/nodes/sync
{
"blockchain": [{
"hash_of_previous_block": "181cfa3e85f3c2a7aa9fb74f992d0d061d3e4
a6d7461792413aab3f97bd3da95",
"index": 0,
"nonce": 61093,
"timestamp": 1560823108.2946198,
"transactions": []
}, {
"hash_of_previous_block": "ac46b1f492997e27612a8b5750e0fe340a21
7aae89e5c0efd56959d87127b4d3",
"index": 1,
"nonce": 92305,
77
Chapter 3 Implementing Your Own Blockchain Using Python
"timestamp": 1560823210.26095,
"transactions": [{
"amount": 1,
"recipient": "db9ef69db7764331a6f4f23dbb8acd68",
"sender": "0"
}]
}, {
"hash_of_previous_block": "790ed48f5d52b3eacd2f419e6fdfb2f6
b3142bcfc31943e4857b7ba4df48bd98",
"index": 2,
"nonce": 224075,
"timestamp": 1560823212.887074,
"transactions": [{
"amount": 1,
"recipient": "db9ef69db7764331a6f4f23dbb8acd68",
"sender": "0"
}]
}],
"message": "The blockchain has been updated to the latest"
}
As the second node’s blockchain only has one block, it is therefore deemed outdated.
It now replaces its blockchain from that of the first node.
import hashlib
import json
78
Chapter 3 Implementing Your Own Blockchain Using Python
import requests
from urllib.parse import urlparse
class Blockchain(object):
difficulty_target = "0000"
def __init__(self):
self.nodes = set()
# stores all the blocks in the entire blockchain
self.chain = []
# temporarily stores the transactions for the
# current block
self.current_transactions = []
# create the genesis block with a specific fixed hash
# of previous block genesis block starts with index 0
genesis_hash = self.hash_block("genesis_block")
self.append_block(
hash_of_previous_block = genesis_hash,
nonce = self.proof_of_work(0, genesis_hash, [])
)
# use PoW to find the nonce for the current block
def proof_of_work(self, index, hash_of_previous_block,
transactions):
# try with nonce = 0
nonce = 0
# try hashing the nonce together with the hash of the
# previous block until it is valid
while self.valid_proof(index, hash_of_previous_block,
transactions, nonce) is False:
nonce += 1
return nonce
79
Chapter 3 Implementing Your Own Blockchain Using Python
80
Chapter 3 Implementing Your Own Blockchain Using Python
def update_blockchain(self):
# get the nodes around us that has been registered
neighbours = self.nodes
new_chain = None
# for simplicity, look for chains longer than ours
max_length = len(self.chain)
# grab and verify the chains from all the nodes in our
# network
for node in neighbours:
# get the blockchain from the other nodes
response = \
requests.get(f'http://{node}/blockchain')
if response.status_code == 200:
length = response.json()['length']
chain = response.json()['chain']
# check if the length is longer and the chain
# is valid
if length > max_length and \
self.valid_chain(chain):
max_length = length
new_chain = chain
# replace our chain if we discovered a new, valid
# chain longer than ours
if new_chain:
self.chain = new_chain
return True
return False
81
Chapter 3 Implementing Your Own Blockchain Using Python
@property
def last_block(self):
# returns the last block in the blockchain
return self.chain[-1]
app = Flask(__name__)
82
Chapter 3 Implementing Your Own Blockchain Using Python
'length': len(blockchain.chain),
}
return jsonify(response), 200
@app.route('/mine', methods=['GET'])
def mine_block():
blockchain.add_transaction(
sender="0",
recipient=node_identifier,
amount=1,
)
# obtain the hash of last block in the blockchain
last_block_hash = \
blockchain.hash_block(blockchain.last_block)
# using PoW, get the nonce for the new block to be added
# to the blockchain
index = len(blockchain.chain)
nonce = blockchain.proof_of_work(index, last_block_hash,
blockchain.current_transactions)
# add the new block to the blockchain using the last block
# hash and the current nonce
block = blockchain.append_block(nonce, last_block_hash)
response = {
'message': "New Block Mined",
'index': block['index'],
'hash_of_previous_block':
block['hash_of_previous_block'],
'nonce': block['nonce'],
'transactions': block['transactions'],
}
return jsonify(response), 200
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
# get the value passed in from the client
values = request.get_json()
83
Chapter 3 Implementing Your Own Blockchain Using Python
# check that the required fields are in the POST'ed data
required_fields = ['sender', 'recipient', 'amount']
if not all(k in values for k in required_fields):
return ('Missing fields', 400)
# create a new transaction
index = blockchain.add_transaction(
values['sender'],
values['recipient'],
values['amount']
)
response = {'message':
f'Transaction will be added to Block {index}'}
return (jsonify(response), 201)
@app.route('/nodes/add_nodes', methods=['POST'])
def add_nodes():
# get the nodes passed in from the client
values = request.get_json()
nodes = values.get('nodes')
if nodes is None:
return "Error: Missing node(s) info", 400
for node in nodes:
blockchain.add_node(node)
response = {
'message': 'New nodes added',
'nodes': list(blockchain.nodes),
}
return jsonify(response), 201
@app.route('/nodes/sync', methods=['GET'])
def sync():
updated = blockchain.update_blockchain()
if updated:
response = {
'message':
'The blockchain has been updated to the latest',
84
Chapter 3 Implementing Your Own Blockchain Using Python
'blockchain': blockchain.chain
}
else:
response = {
'message': 'Our blockchain is the latest',
'blockchain': blockchain.chain
}
return jsonify(response), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=int(sys.argv[1]))
Summary
In this chapter, you learned how to build your own blockchain using Python. Through
this exercise, you learned the following:
In the next chapter, you will learn how to connect to the real blockchain, the
Ethereum blockchain.
85
CHAPTER 4
87
© Wei-Meng Lee 2023
W.-M. Lee, Beginning Ethereum Smart Contracts Programming, https://doi.org/10.1007/978-1-4842-9271-6_4
Chapter 4 Creating Your Own Private Ethereum Test Network
There are two main Ethereum clients that you can use to interact with the Ethereum
blockchain:
Note The preceding clients are all CLI (command line interface) clients.
For this chapter, you will use the Geth client. Geth is available for three main
platforms:
• Linux
• macOS
• Windows
If you do not have Brew, you need to install it first. To do so, in terminal, type the
following command to install Brew:
88
Chapter 4 Creating Your Own Private Ethereum Test Network
$ brew update
$ brew upgrade
Tip To upgrade Geth to the latest version, use the command brew upgrade
Ethereum. To find the current version of Geth installed on your computer, use the
command geth help.
If you do not want to install Geth from terminal, the second way to install Geth is to
go to https://geth.ethereum.org/downloads/ and download Geth for macOS. Once
downloaded, unzip the file and move the Geth file onto your home directory.
Tip For Windows users, if you encounter problems running any of the commands
in this book, you should use PowerShell instead of the command prompt.
89
Chapter 4 Creating Your Own Private Ethereum Test Network
Alternatively, you can also install Geth through terminal. In terminal, type the
following commands:
90
Chapter 4 Creating Your Own Private Ethereum Test Network
These three nodes will form your own test network where you can do things like
mining, transferring Ethers to another account, and (in the next chapter) deploying your
smart contracts.
$ cd ~
$ mkdir MyTestNet
$ cd MyTestNet
To create the genesis block, create a file named genesisblock.json and populate it
as follows:
{
"config": {
"chainId": 10,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0,
"eip150Block": 0,
"eip150Hash": "0x00000000000000000000000000000000000000000000000
00000000000000000"
},
"alloc" : {},
91
Chapter 4 Creating Your Own Private Ethereum Test Network
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x0000000000000042",
"mixhash" : "0x000000000000000000000000000000000000000000000000000
0000000000000",
"parentHash" : "0x000000000000000000000000000000000000000000000000000
0000000000000",
"timestamp" : "0x00"
}
Tip The value of the difficulty key sets the rate at which new blocks will
be mined. If you want to slow down the rate of mining, set this value to a
higher number.
$ cd ~/MyTestNet
$ mkdir data
MyTestNet
data
92
Chapter 4 Creating Your Own Private Ethereum Test Network
This code creates a node named node1 and saves all its data in the node1 directory.
You should see the following response:
Figure 4-3 shows the content of node1 after this command is run.
93
Chapter 4 Creating Your Own Private Ethereum Test Network
MyTestNet
data
node1
geth
chaindata
lightchaindata
keystore
The Geth directory contains two folders for storing the blockchains, chaindata and
lightchaindata, while the keystore directory contains accounts information (more on
this later).
Let’s create another node called node2:
MyTestNet
data
node1
geth
chaindata
lightchaindata
keystore
node2
geth
chaindata
lightchaindata
keystore
Figure 4-4. The node2 directory is now added to the data directory
94
Chapter 4 Creating Your Own Private Ethereum Test Network
Once node1 is started up, you will see the Geth JavaScript Console:
instance: Geth/v1.10.26-stable/darwin-arm64/go1.19.3
at block: 0 (Thu Jan 01 1970 07:30:00 GMT+0730 (+0730))
datadir: /Users/weimenglee/MyTestNet/data/node1
modules: admin:1.0 debug:1.0 engine:1.0 eth:1.0 ethash:1.0 miner:1.0
net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
Tip The Geth JavaScript Console provides an interactive console for you to
interact with the Ethereum blockchain using the JavaScript language.
The console 2 option basically redirects the output to a file (named console1.log in
this example). Without this redirection, Geth would continually generate a lot of output
and the Geth JavaScript Console would not be usable.
Creating Accounts
Now that node1 is started up, you can create a new account in your node using the
personal.newAccount() function:
> personal.newAccount()
Passphrase: <password>
Repeat passphrase: <password>
"0x6e05a61f9414d2ecbe79ae9c70fd4a7f234be7cb"
Tip The web3 object (from the web3.js library) allows you to programmatically
interact with the Ethereum blockchain. It also exposes the eth object, which
itself also exposes the personal object. Hence the full name for the personal.
newAccount() function is actually web3.eth.personal.newAccount(). The
personal object allows you to interact with the Ethereum node’s accounts.
95
Chapter 4 Creating Your Own Private Ethereum Test Network
You will be asked to enter a password for the new account. Once that is done, the
public address of the account will be displayed.
To show the list of accounts in your node, use the eth.accounts property:
> eth.accounts
["0x6e05a61f9414d2ecbe79ae9c70fd4a7f234be7cb"]
The list of accounts will be shown as an array. In the example here, there is only one
account.
Once the account is created, the account details are stored in a file (named
beginning with the UTC word) in the ~/MyTestNet/data/node1/keystore directory. I
will talk more about this later in this chapter.
> eth.getBalance(eth.accounts[0])
0
Tip As mentioned, the eth object is derived from the web3 object, so the
preceding function is equivalent to web3.eth.getBalance(web3.eth.
accounts[0]).
Apparently, at this moment you have no Ethers, so you will see a 0. However, note
that the unit displayed for the balance is Wei, where 1 Ether is 1000000000000000000 Wei
(1 followed by 18 zeros). You may prefer to see the units displayed in Ether. To make your
life easier, you can use the web3.fromWei() function, like the following command, to
convert the balance in Wei to Ether:
96
Chapter 4 Creating Your Own Private Ethereum Test Network
Wei 1 Wei 1
Kwei (babbage) 103 Wei 1,000
Mwei (lovelace) 106 Wei 1,000,000
Gwei (shannon) 109 Wei 1,000,000,000
Microether (szabo) 1012 Wei 1,000,000,000,000
Milliether (finney) 1015 Wei 1,000,000,000,000,000
Ether 1018 Wei 1,000,000,000,000,000,000
> exit
In this command, you have specified the --port option and set the port to 30304.
This is because Geth by default uses port 30303, and if you have multiple nodes running
on the same computer, each node must use a unique port number. By setting this
to port 30304, it will prevent a conflict with another node using the default port.
The --nodiscover option means that peers will not automatically discover each other
and that they need to be added manually. The --networkid option specifies the network
id so that other nodes can attach to the network with the same network id. Also note
the --authrpc.port option. For each node, this must be set to a unique number other
than the default port number 8551.
97
Chapter 4 Creating Your Own Private Ethereum Test Network
Notice that node1 is started with the --networkid option with the value of 2345. This
is required so that it can be added as a peer to node2 later on. Figure 4-5 shows the state
of the two nodes at this moment.
Figure 4-5. The two nodes currently running within the same computer
> admin.nodeInfo
{
enode: "enode://970900b3e4c76e9ce2d0a9d592f2f85d89fdd341cf513a352822
d49f62240a33c9f61423a2793e186b4abb54f6640c07cb90e366df49223888c8de56e
[email protected]:30303",
enr: "enr:-KO4QG1bMANDGX-m3Ncxs7exhot0WiFMImfymqs27saQ3hZsF7MVWByWplenrpw
YQmL7ix-xOla0eJ5OE1n1sGy78t-GAYGFN_fag2V0aMfGhHEB0w2AgmlkgnY0gmlwhHRXSn6
Jc2VjcDI1NmsxoQOXCQCz5MdunOLQqdWS8vhdif3TQc9ROjUoItSfYiQKM4RzbmFwwIN0Y3CC
dl-DdWRwgnZf",
98
Chapter 4 Creating Your Own Private Ethereum Test Network
id: "d5b2504c731b3899c7f92941c0d703477b84c79fd9c0b68d9ebad4e145799079",
ip: "116.87.74.126",
listenAddr: "[::]:30303",
name: "Geth/v1.10.19-stable/darwin-amd64/go1.18.3",
ports: {
discovery: 30303,
listener: 30303
},
protocols: {
eth: {
config: {
chainId: 10,
eip150Block: 0,
eip150Hash: "0x0000000000000000000000000000000000000000000000000000
000000000000",
eip155Block: 0,
eip158Block: 0,
homesteadBlock: 0
},
difficulty: 131072,
genesis: "0x5e1fc79cb4ffa4739177b5408045cd5d51c6cf766133f23f7cd72ee1f
8d790e0",
head: "0x5e1fc79cb4ffa4739177b5408045cd5d51c6cf766133f23f7cd72ee
1f8d790e0",
network: 2345
},
snap: {}
}
}
Tip The admin object is derived from the web3 object. Hence the full name of
admin.addPeer() is web3.admin.addPeer(). The admin object allows you to
interact with the underlying blockchain.
99
Chapter 4 Creating Your Own Private Ethereum Test Network
You will find a whole bunch of information. In particular, take note of the enode key
(bolded for emphasis). At the end of the enode value, observe the port number 30303
(which port node 1 is using).
Tip An enode describes a node in the Ethereum network in the form of an URI.
> admin.addPeer("enode://970900b3e4c76e9ce2d0a9d592f2f85d89fdd341cf51
3a352822d49f62240a33c9f61423a2793e186b4abb54f6640c07cb90e366df49223888c8de5
[email protected]:30303")
In this command, the bolded portion is the value of the enode key of node1. The
admin.addPeer() function adds a peer to the current node using the peer’s enode value.
To verify that the peer is added successfully, use the admin.peers property:
> admin.peers
[{
caps: ["eth/66", "eth/67", "snap/1"],
enode: "enode://970900b3e4c76e9ce2d0a9d592f2f85d89fdd341cf513a352822
d49f62240a33c9f61423a2793e186b4abb54f6640c07cb90e366df49223888c8de56e
[email protected]:30303",
id: "d5b2504c731b3899c7f92941c0d703477b84c79fd9c0b68d9ebad4e145799079",
name: "Geth/v1.10.19-stable/darwin-amd64/go1.18.3",
network: {
inbound: false,
localAddress: "127.0.0.1:58369",
100
Chapter 4 Creating Your Own Private Ethereum Test Network
remoteAddress: "127.0.0.1:30303",
static: true,
trusted: false
},
protocols: {
eth: {
difficulty: 131072,
head: "0x5e1fc79cb4ffa4739177b5408045cd5d51c6cf766133f23f7cd7
2ee1f8d790e0",
version: 67
},
snap: {
version: 1
}
}
}]
If the peer is added successfully, you should see the preceding output. From the
output, you can see that the node1’s IP address is 127.0.0.1, which is the local IP address
of my computer. Figure 4-6 shows the current state of the private test network.
Figure 4-6. The current state of the private test network, with two peered nodes
101
Chapter 4 Creating Your Own Private Ethereum Test Network
So far you have been pairing the nodes within the same computer. How do you pair
nodes from another computer?
Note I leave the creation of the third node as an exercise for the reader.
Suppose you have another node called node3 running on another computer. On
the Geth JavaScript Console on that node, you can add node1 as a peer by using the
following command:
>admin.addPeer("enode://970900b3e4c76e9ce2d0a9d592f2f85d89fdd341cf513a35282
2d49f62240a33c9f61423a2793e186b4abb54f6640c07cb90e366df49223888c8de56ec693
[email protected]:30303")
You just need to replace the IP address and port number of the node with that of the
node you are trying to add to. In this example, node1 is running on port 30303 and its IP
address is 192.168.1.116. Figure 4-7 summarizes the state of the nodes at this moment.
Figure 4-7. The private test network with three connected nodes
102
Chapter 4 Creating Your Own Private Ethereum Test Network
Performing Mining
With all the nodes connected, you can start to perform some mining operations! Before
you do that, let’s verify the block numbers for the current blockchain. In any of the
nodes, you can use the eth.blockNumber property to display the latest block number in
the blockchain:
> eth.blockNumber
0
As expected, you should see 0. This is because at this moment the blockchain has
only one block: the genesis block. To start mining on node1, use the miner.start()
function:
> miner.start(1)
null
The number you pass into the start() function is the number of threads you want
to use for the mining operation. Don’t be alarmed with the null result. The null simply
means that the function has nothing to return to you; it does not indicate the failure of
the mining operation.
The node1 will now start the mining operation. On some computers, it will take a
few minutes to mine the first block, while on some slower machines, it will take a much
longer time. So be patient.
You can verify that a block has been mined by checking the result of the eth.
blockNumber property. If the block number is more than 0, you have mined your first
block! Congratulations!
And since node2 is connected to node1, you can also verify the block number in
node2. You should see the same block number.
Caution If node2 is not seeing the same block number as node1, it means that
the two are not paired up correctly.
103
Chapter 4 Creating Your Own Private Ethereum Test Network
If you need to stop the mining, you can use the miner.stop() function. For now,
leave the mining on.
Tip This is a good time to check the balance of your account. If you have
managed to mine a block, you should have some Ethers in your account now.
Examining a Block
You can examine the contents of a block by using the eth.getBlock() function (e.g.,
eth.getBlock(22)):
{
difficulty: 132352,
extraData: "0xd983010a13846765746888676f312e31382e338664617277696e",
gasLimit: 3209750,
gasUsed: 0,
hash: "0xa7a89c8552d7ab22064468fd8ded5ba4759734d9f877a45351e1b00048564e4c",
logsBloom: "0x0000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000",
miner: "0x6e05a61f9414d2ecbe79ae9c70fd4a7f234be7cb",
mixHash: "0x938bb57f4f1c68a1760c689028ef02d0ee464ea45ddd7be777205a90a91a9bd9",
nonce: "0x43b3f9c38f75c685",
number: 22,
parentHash: "0xa31e844e09b21b9e2e00c9337b0c127d64b848c71fac5165cfd9b730
30bf460c",
receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622f
b5e363b421",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd
40d49347",
104
Chapter 4 Creating Your Own Private Ethereum Test Network
size: 537,
stateRoot: "0xa911ecd43f895f2e01538910d71734cdc149b1b9e0f88815c88438bb
138e81cc",
timestamp: 1655798565,
totalDifficulty: 3028096,
transactions: [],
transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc0016
22fb5e363b421",
uncles: []
}
The eth.getBlock() function takes in a number representing the block number that
you want to examine. One particular interesting point to note: the miner key indicates
the account that successfully mined the block.
> miner.start(1)
Error: etherbase missing: etherbase must be explicitly specified
at web3.js:6365:9(45)
at send (web3.js:5099:62(34))
at <eval>:1:12(4)
Why? In order to perform mining, you need to have at least one account in your node
for the rewards to be deposited into. To solve this problem, create a new account using
the personal.newAccount() function. Once the account is created, you can use the
miner.start() function again.
You now have two miners mining at the same time and competing for rewards. To
know which is the miner of the latest block, you can use the eth.getBlock() function
and check its miner property like this: eth.getBlock(eth.blockNumber).miner. The
result is the address of the account that managed to mine the latest block.
105
Chapter 4 Creating Your Own Private Ethereum Test Network
> eth.accounts
["0x6e05a61f9414d2ecbe79ae9c70fd4a7f234be7cb",
"0xbb6f2406a8a49746f25e5b2230af7ee9d3196e9c"]
> eth.getBalance(eth.accounts[0])
3.376328125e+22
> eth.getBalance(eth.accounts[1])
0
To transfer some Ethers from one account to another account, you can use the eth.
sendTransaction() function. But before you use it to transfer Ethers, you need to unlock
the source account first, using the personal.unlockAccount() function:
> personal.unlockAccount(eth.accounts[0])
Unlock account 0x530e822163471b0e65725cbd85dc141ff6b24d59
Passphrase: <password>
true
Once the account is unlocked, you can now transfer the Ether:
> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value:
web3.toWei(5,"ether")})
"0xbac74dffae71c5532d83ed8ae37ff97d68dd2ab8b7f62fa2b1032f88df8d543c"
Tip If you want to transfer Ethers to another node on another computer, simply
specify the address of the account you want to send to, enclosed with a pair of
double quotes, like this:
eth.sendTransaction({from: eth.accounts[0], to: "0x9ba6f3c9cce2b172d
0a85a50101ae05f3b4c8731", value: web3.toWei(5,"ether")})
106
Chapter 4 Creating Your Own Private Ethereum Test Network
In this example, you are transferring five Ethers from the first account to the second
account within the same node. The output of the function is the transaction ID. If you
now check the balance of the two accounts and realize that they are still the same, then
one of the following causes is likely:
• If you are currently mining, it is likely that the transactions have not
be confirmed yet. Wait a while and check the balance again.
After a while, you should see five Ethers in the second account:
> eth.getBalance(eth.accounts[1])
5000000000000000000
Tip If you check the balance of the first account, you are likely to see it has less
than five Ethers deducted. This is because in spite of the five Ethers deducted, it is
also earning rewards doing the mining. Hence, it is easier to verify the balance of
the second account.
Managing Accounts
Earlier in this chapter you learned about creating accounts in your node. I also
mentioned that the account details are stored in a file with the name starting with “UTC”
and saved in the ~/MyTestNet/data/node1/keystore directory. Let’s dissect the content
of this UTC file:
{
"address": "6e05a61f9414d2ecbe79ae9c70fd4a7f234be7cb",
"crypto": {
"cipher": "aes-128-ctr",
"ciphertext":
"1f35569e89100cd6d03c7de8780a913f54129cdbce0290a3b39a798391bd2674",
"cipherparams": {
107
Chapter 4 Creating Your Own Private Ethereum Test Network
"iv": "0ffbe973059db7a0226cd6fced92e50d"
},
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"n": 262144,
"p": 1,
"r": 8,
"salt": "d2c73712f61b236beb22c783502a50166049a46b0aa29503a90d2f8a4227741c"
},
"mac": "432ab5429c423c3c9e545397f53c64ddc6a900a6ab7dc255a58cf5da9f629242"
},
"id": "b9e36890-b2ff-4fdd-af90-f157f352e6bc",
"version": 3
}
This shows the contents of the first account in node1 with the file name U TC-
-2022-06-21T07-44-59.385557000Z--6e05a61f9414d2ecbe79ae9c70fd4a7f234be7cb.
It contains the following:
• The public key is not stored in the JSON file as it can be derived
from the private key. The corresponding public key for the private
key is derived from the private key using the ECDSA (Elliptic Curve
Signature) algorithm.
108
Chapter 4 Creating Your Own Private Ethereum Test Network
Tip Curious about your private key? Import the JSON file into MetaMask and your
account will be imported into MetaMask. You can now export the private key of this
imported account in MetaMask.
Removing Accounts
Once you use the personal.newAccount() function, the account is created. There is
no equivalent function to remove the account. The easiest way to delete the account
is to go to the ~/MyTestNet/data/node1/keystore directory and delete the UTC file
corresponding to the account that you want to delete.
> eth.coinbase
"0x6e05a61f9414d2ecbe79ae9c70fd4a7f234be7cb"
109
Chapter 4 Creating Your Own Private Ethereum Test Network
To change the coinbase, you can use the miner.setEtherbase() function. Let’s
try it now.
First, print out the accounts you have in node1:
> eth.accounts
["0x6e05a61f9414d2ecbe79ae9c70fd4a7f234be7cb",
"0xbb6f2406a8a49746f25e5b2230af7ee9d3196e9c"]
> miner.setEtherbase(eth.accounts[1])
true
Once this is done, you can verify if the coinbase has indeed been changed to the
second account:
> eth.coinbase
"0xbb6f2406a8a49746f25e5b2230af7ee9d3196e9c"
Summary
In this chapter, you learned how to use Geth to create your own private Ethereum test
network. You learned how to create accounts in your node, connect to other nodes,
transfer Ethers between nodes, and more. Deploying your own test network is much
more efficient than using one of Ethereum’s test network. What’s more, it allows you to
experiment and have a deeper understanding of Ethereum.
110
CHAPTER 5
What Is MetaMask?
MetaMask is a very useful tool that plays a pivotal role in allowing you to make your foray
into the world of blockchain. Rather than attempt to define what exactly MetaMask is in
one paragraph, I will talk about the role played by MetaMask as we go along.
First and foremost, MetaMask is an Ethereum crypto-wallet. It allows you to do the
following:
• Maintain the private keys for your accounts so you can export them
or import new accounts
111
© Wei-Meng Lee 2023
W.-M. Lee, Beginning Ethereum Smart Contracts Programming, https://doi.org/10.1007/978-1-4842-9271-6_5
Chapter 5 Using the MetaMask Crypto-Wallet
Note Besides being an Ethereum wallet, MetaMask also allows you to interact
with the Ethereum blockchain by injecting a JavaScript library called web3.js,
developed by the Ethereum core team. I will discuss this more in the next chapter
when I discuss smart contracts.
112
Chapter 5 Using the MetaMask Crypto-Wallet
When MetaMask connects to one of the Ethereum networks, it connects to one of the
respective INFURA nodes through their RPC URLs:
Caution Prior to the Merge (where Ethereum switched from using PoW to PoS),
most developers used the Ropsten test network. However, the Ropsten test
network has been deprecated after the Merge. You should use the Goerli test
network instead.
Installing MetaMask
The easiest way to install MetaMask is to use the Chrome browser and install MetaMask
as a Chrome extension. To install the MetaMask extension,
Note You can use the MetaMask extension on Chrome, Mozilla Firefox, Brave,
and Microsoft Edge browsers.
• You should see the MetaMask extension in the search result. Click
Add to Chrome (see Figure 5-2).
• Once MetaMask is added to Chrome, you will see the screen shown
in Figure 5-4. Click Get Started.
114
Chapter 5 Using the MetaMask Crypto-Wallet
Read through the terms and conditions and if you are agreeable, click I agree. Next,
create a password to secure your account. Click Create once you are done with this step
(see Figure 5-6).
115
Chapter 5 Using the MetaMask Crypto-Wallet
You can watch a short video to understand how your account can be recovered if you
ever forget your password. Once you are done, click Next.
You will be shown your Secret Recovery Phrase. The Secret Recovery Phrase is a set
of words that allows you to recover and restore all your accounts. This set of words is
something that you need to keep under lock and key. I personally suggest you print it out
and lock it in a secure place.
To reveal your Secret Recovery Phrase, click the lock icon (see Figure 5-7).
116
Chapter 5 Using the MetaMask Crypto-Wallet
You can see your Secret Recovery Phrase. Ensure that no one except you knows these
12 words.
Tip The 12-word secret phrase allows you to recover your account if you
forget your password. This 12-word secret phrase is known as the BIP39 (Bitcoin
Improvement Proposals) seed phrase. MetaMask uses the BIP39 protocol to
figure out how to use a 12-word secret phrase to get a seed. This seed is used to
generate a root key for each cryptocurrency (in the case of Ethereum, it is Ether).
The root key is then hashed to generate a private key, which is used to generate a
public key, which in turn is used to generate the public address for an account. To
generate a second account (and third account, and so on), the first private key is
used to derive a set of child keys, which are then used to generate their respective
public keys, and so on.
117
Chapter 5 Using the MetaMask Crypto-Wallet
Using this method, the seed works with a deterministic algorithm to generate an
unlimited series of addresses for your wallet.
The list of words used in the secret phrase can be found here: https://github.
com/bitcoin/bips/blob/master/bip-0039/english.txt. It is based on a
list of 2048 words, with 12 words selected in a particular order.
You will be asked to confirm your Secret Recovery Phrase (see Figure 5-8). Click on
the words at the bottom of the page in the order that they are presented to you. The order
of the words selected is important. If the order is incorrect, drag the words around to
rearrange them.
Click Confirm when you are done arranging the words. That’s it! Your account is
now set up!
118
Chapter 5 Using the MetaMask Crypto-Wallet
Click the pin icon displayed next to the MetaMask icon (see Figure 5-10).
119
Chapter 5 Using the MetaMask Crypto-Wallet
The MetaMask icon (the icon with the fox) will be pinned on the browser. Click it and
you will see the default Account 1 (see Figure 5-11).
120
Chapter 5 Using the MetaMask Crypto-Wallet
MetaMask allows you to connect to either of these test networks (see Figure 5-12).
Tip By default, the test networks are hidden in MetaMask. To view the test
networks, click the icon displayed next to the Ethereum Mainnet dropdown list,
select Settings | Advanced, and turn on the Show test networks option.
In addition, MetaMask also allows you to connect to a local Ethereum node listening
at port 8545. It also allows you to connect to a custom RPC host.
121
Chapter 5 Using the MetaMask Crypto-Wallet
For this chapter, you will use the Goerli Test Network, so select it in MetaMask.
122
Chapter 5 Using the MetaMask Crypto-Wallet
Figure 5-13. Get free Ethers for your account using the Goerli Faucet page
123
Chapter 5 Using the MetaMask Crypto-Wallet
• Paste the account address into the Goerli Faucet page (see
Figure 5-15). Click the Send Me ETH button.
• At the bottom of the Goerli Faucet page, you will see a transaction
hyperlink (see Figure 5-16). Clicking it allows you to see the status of
the transaction on Etherscan.
124
Chapter 5 Using the MetaMask Crypto-Wallet
• After a while, you should see 0.05 Ethers deposited into your account
(see Figure 5-17).
Tip You can only request 0.05 free Ethers every 24 hours (this is the policy at the
time of writing). If you want more, try setting up MetaMask on multiple computers
and create additional Alchemy accounts.
125
Chapter 5 Using the MetaMask Crypto-Wallet
It is important to remember that the amount of Ethers in your account is valid only
for the particular Ethereum network that you have obtained it from. For example, if you
switch to another test network, say the Sepolia Test Network, you should see 0 for your
account balance.
126
Chapter 5 Using the MetaMask Crypto-Wallet
• Click the colored icon located at the top right corner of MetaMask
(see Figure 5-18).
• You will see the new account created (Account 2, see Figure 5-19).
127
Chapter 5 Using the MetaMask Crypto-Wallet
Tip You can create as many accounts as you want. In fact, for testing your smart
contracts, you should create at least three accounts for testing purposes.
• To switch between accounts, click the colored icon (see Figure 5-20)
and select the account you want to switch to.
128
Chapter 5 Using the MetaMask Crypto-Wallet
Transferring Ethers
MetaMask allows you to transfer Ethers from one account to another very easily. You
can transfer to another account within MetaMask or an external account using its public
address.
To transfer Ethers from one account to another,
• Switch to Account 1.
129
Chapter 5 Using the MetaMask Crypto-Wallet
130
Chapter 5 Using the MetaMask Crypto-Wallet
Tip To send Ethers to an external account, enter the account address of the
recipient, or click the QR code icon and you will be able to use your Webcam to
scan the QR code of the public address of the external account.
• Specify how many Ethers you want to send (0.01 in this example; see
Figure 5-23). Click Next.
131
Chapter 5 Using the MetaMask Crypto-Wallet
• You will see the total transaction amount that you will incur for this
transaction (see Figure 5-24). Click Confirm (you need to scroll down
the page).
132
Chapter 5 Using the MetaMask Crypto-Wallet
Note The total transaction amount is the amount of Ethers you are transferring
plus the transaction fee. The transaction fee is also known as the gas fee.
133
Chapter 5 Using the MetaMask Crypto-Wallet
GAS FEES
Gas is the internal pricing for running a transaction or contract in Ethereum. Instead of
pricing transaction fees using Ether, Ethereum deliberately uses the concept of gas as the
unit for pricing transactions. This is because gas more accurately conveys the complexity of
computation, while the price of Ether fluctuates because of market forces. The price of gas
is decided by the market demand for making transactions. Miner/validators often process
transactions based on the gas price people are paying; the higher the gas price you are
paying, the faster your transactions get confirmed.
When you perform a transaction, MetaMask automatically specifies the maximum amount of
gas you want to spend, and all unspent gas is refunded to your account. In addition, MetaMask
also calculates the amount of gas needed for a transaction and suggests a gas price for your
transaction.
134
Chapter 5 Using the MetaMask Crypto-Wallet
• You can verify that the Ethers have been transferred to Account 2 by
switching to Account 2.
Recovering Accounts
MetaMask has an inbuilt system that allows you to recover your accounts safely and
securely. There are a few possible scenarios when you would need to recover your accounts:
135
Chapter 5 Using the MetaMask Crypto-Wallet
In either case, MetaMask allows you to very easily recover your existing accounts
provided you have backed up the 12-word pass phrase. The beauty of using the 12-word
pass phrase is that all your accounts can be recovered. Say you created three accounts in
MetaMask. All accounts can be recovered as long as you have the 12-word pass phrase.
To recover your accounts,
• Click the Forgot password? link in the MetaMask login screen (see
Figure 5-26).
Figure 5-26. Recovering your account(s) using the 12-word secret seed phrase
136
Chapter 5 Using the MetaMask Crypto-Wallet
• Enter the 12-word seed phrase in the exact order provided to you (see
Figure 5-27). You also need to assign a new password to protect your
account. Click Restore.
137
Chapter 5 Using the MetaMask Crypto-Wallet
• You will now find your original Account 1 and Account 2. In fact,
all accounts you created earlier that had a non-zero balance are
recovered automatically.
Note You should see the Account 1 and Account 2 with the same amount of
Ethers you had earlier.
Exporting Accounts
To export an account,
• Select the … displayed on the right of the account name and select
Account Details (see Figure 5-28).
138
Chapter 5 Using the MetaMask Crypto-Wallet
139
Chapter 5 Using the MetaMask Crypto-Wallet
140
Chapter 5 Using the MetaMask Crypto-Wallet
• Enter your MetaMask password and click Confirm (see Figure 5-30).
• You should now see your private key. Click it to copy it to the
clipboard and save it securely to a text file.
141
Chapter 5 Using the MetaMask Crypto-Wallet
Importing Accounts
To import an account into MetaMask,
• Click the colored icon located at the top right corner of MetaMask
(see Figure 5-31). Click Import Account.
• Paste the private key into the text box as shown in Figure 5-32. Click
Import. The account is now be imported into MetaMask.
142
Chapter 5 Using the MetaMask Crypto-Wallet
Tip You can only import a new account based on a private key that is not already
in your MetaMask. If not, the import will not be successful. Also, for accounts that
you have imported using their private keys, they are not recoverable using the 12-
word secret phrase. To restore these accounts, you have to use their private key or
JSON file.
143
Chapter 5 Using the MetaMask Crypto-Wallet
Tip There are two ways to import accounts into MetaMask: using private key or
JSON File. In Chapter 4, you learned about the accounts stored in the JSON files.
This is what you can use to import the accounts in Geth into the MetaMask.
Summary
In this chapter, you saw how to use the MetaMask Chrome extension to manage your
Ethereum accounts. You learned the basics: how to create accounts, transfer Ethers
between accounts, and export and import accounts into MetaMask. In the following
chapters, you have more opportunities to see MetaMask in action and how it helps you
to run dapps (decentralized apps) in your web browsers.
144
CHAPTER 6
145
© Wei-Meng Lee 2023
W.-M. Lee, Beginning Ethereum Smart Contracts Programming, https://doi.org/10.1007/978-1-4842-9271-6_6
Chapter 6 Getting Started with Smart Contracts
• Functions that write values onto the blockchain and hence change
the state of the blockchain
146
Chapter 6 Getting Started with Smart Contracts
Note When you call a smart contract function that writes to the blockchain, you
are essentially performing a transaction that changes the state of the storage on
the blockchain.
When you call a function that changes the state of the blockchain (such as saving
the examination result of a student onto the blockchain), all miners/validators will
execute the smart contract function (using the EVM; more on this in the sidebar) and
add the state changes onto the block so that it can be prepared for mining/validation and
ultimately be added onto the blockchain.
Note Essentially, when you call a function that changes the state of the
blockchain, multiple nodes will run the same function and try to add the state
changes into the block. Ultimately, only the winning miner/validator will add the
block to the blockchain.
For functions that read from the blockchain and do not make any state changes to
the blockchain, the node that is connected to the caller of the function executes the
function and directly reads the value from its local copy of the blockchain and then
returns the value back to the caller. This is more straightforward and only involve
one node.
The EVM is a sandboxed execution environment on nodes that run smart contracts. The EVM
is turing-complete, which means that it is capable of running any programs that can (for the
most part) solve any reasonable computational problems.
Today, you will often hear the phrase EVM compatibility. It means that a particular blockchain
supports smart contracts that are compatible with Ethereum virtual machines. This allows your
smart contract written in Solidty (or any other languages supported by Ethereum) to run on
other blockchains without modifications.
147
Chapter 6 Getting Started with Smart Contracts
Tip Visual Studio Code has several Solidity extensions that you can install to
make the writing of smart contracts easy.
My personal favorite is the Remix IDE. The Remix IDE is a suite of tools for
interacting with the Ethereum blockchain. It can compile your smart contract into
bytecode, generate the ABI (application binary interface), and deploy your contracts into
the various Ethereum test networks (as well as the real Ethereum blockchain). The ability
to compile the contract on the fly makes it a very handy tool for learning smart contract
programming. Hence, I strongly recommend that you use it to write your contracts.
For this book, use the Remix IDE for writing smart contracts.
Note To compile a Solidity smart contract, you need to use the solc compiler.
But if you use the Remix IDE, you don’t have to explicitly use it to compile your
contract.
148
Chapter 6 Getting Started with Smart Contracts
Figure 6-2. Using the Remix IDE to create your smart contract
Tip All contracts created using the Remix IDE are stored locally in your
browser cache.
To create a new contract, right-click the contracts item and select New File (see
Figure 6-3). You will be asked to give a new name to your newly created contract. Name it
as calculator.sol and press Enter.
149
Chapter 6 Getting Started with Smart Contracts
Let’s now create a simple contract named Calculator. Populate the Remix IDE with
the following:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
contract Calculator {
function arithmetics(uint num1, uint num2) public
pure returns (uint sum, uint product) {
sum = num1 + num2;
product = num1 * num2;
}
Let’s dissect the code and see how it works, and then you can run it and see
the result.
150
Chapter 6 Getting Started with Smart Contracts
The first line in the contract specifies the type of license the contract uses. This
was introduced in Solidity 0.6.8. The MIT License is a permissive free software
license originating at the Massachusetts Institute of Technology in the late 1980s. As a
permissive license, it puts only very limited restrictions on reuse and has, therefore, high
license compatibility.
Next, the pragma solidity statement is a directive that tells the compiler that the
source code is written for a specific version of Solidity. In this case, it is compatible with
version 0.8.x (e.g., versions 0.8.1, 0.8.17, etc.). However, it is not compatible with versions
such as 0.7 or 0.9.
Next, the contract keyword (think of it as the class keyword in languages like C#
and Java) defines the contract named Calculator. It contains two functions:
Notice that both functions have the pure keyword in their declaration. The pure
keyword indicates that the function will not access nor change the value of state
variables. The use of this keyword is important; as no modification is made to the
blockchain, values can be returned without network verifications. As such, it is also free
to call this function without needing any gas.
Note State variables are storage on the blockchain used to store values, such as
the variables you declare in your contract. I will discuss more about state variables
in the next chapter.
151
Chapter 6 Getting Started with Smart Contracts
Figure 6-4. The Remix IDE automatically compiles your code as you type
If there is a syntax error, an error will appear under the Compile tab (see Figure 6-5).
Clicking the error brings you to the code. In this example, a statement is missing a
semicolon (;) at the end of the line, which can be fixed easily.
152
Chapter 6 Getting Started with Smart Contracts
153
Chapter 6 Getting Started with Smart Contracts
Figure 6-6. The Remix VM allows you to test your code directly
154
Chapter 6 Getting Started with Smart Contracts
Tip Notice that there is a Remix VM (London) and Remix VM (Berlin). They are
flavors of the JavaScript VM built into the Remix IDE. Berlin was released earlier
than London and hence Remix VM (London) is much more supported.
Select Remix VM (London) so you can test the contract without deploying it to a
blockchain.
Next, click the Deploy button (see Figure 6-7). You should see your contract under
the Deployed Contracts section.
155
Chapter 6 Getting Started with Smart Contracts
Figure 6-7. Deploying and testing your smart contract in the Remix IDE
Click the arrow icon displayed to the left of the Calculator contract to reveal the two
functions in your contract, each displayed in a blue box.
Enter the text as shown in Figure 6-8 and click the arithmetics button. The result will
be displayed underneath it.
156
Chapter 6 Getting Started with Smart Contracts
Tip The color of the button serves a purpose. A blue button means that it is free
to call the function. A red button, on the other hand, means that you need to incur
gas to call it. You will see a red button in the next chapter.
Enter the numbers for the next button and click it (Figure 6-9). You will see its output.
157
Chapter 6 Getting Started with Smart Contracts
In the Compile tab, you will see two icons representing ABI and bytecode (see
Figure 6-10). If you click the ABI icon, the ABI will be copied to the clipboard.
Figure 6-10. The ABI and Bytecode buttons provide easy access to the ABI and
bytecode of the contract
158
Chapter 6 Getting Started with Smart Contracts
[
{
"inputs": [
{
"internalType": "uint256",
"name": "num1",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "num2",
"type": "uint256"
}
],
"name": "arithmetics",
"outputs": [
{
"internalType": "uint256",
"name": "sum",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "product",
"type": "uint256"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
159
Chapter 6 Getting Started with Smart Contracts
"name": "num1",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "num2",
"type": "uint256"
}
],
"name": "multiply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "pure",
"type": "function"
}
]
Likewise, click the Bytecode button and paste it onto a text editor. It will look like
Figure 6-11.
160
Chapter 6 Getting Started with Smart Contracts
Specifically, the value of the object key contains the actual bytecode (think of it as
machine language) of the contract that will be deployed onto the blockchain. Figure 6-12
shows the steps performed by the Remix IDE when it compiles your smart contract.
Figure 6-12. How the Remix IDE compiles your smart contract
For your reference, the bytecode for your smart contract looks like this:
608060405234801561001057600080fd5b50610281806100206000396000f3fe6080604052
34801561001057600080fd5b50600436106100365760003560e01c8063165c4a161461003
b5780638c12d8f01461006b575b600080fd5b6100556004803603810190610050919061011
3565b61009c565b6040516100629190610162565b60405180910390f35b61008560048036
038101906100809190610113565b6100b2565b60405161009392919061017d565b604051
80910390f35b600081836100aa91906101d5565b905092915050565b60008082846100c19
190610217565b915082846100cf91906101d5565b90509250929050565b600080fd5b60008
19050919050565b6100f0816100dd565b81146100fb57600080fd5b50565b6000813590506
1010d816100e7565b92915050565b6000806040838503121561012a576101296100d8565b5b
6000610138858286016100fe565b9250506020610149858286016100fe565b91505092509
29050565b61015c816100dd565b82525050565b60006020820190506101776000830184610
153565b92915050565b60006040820190506101926000830185610153565b61019f602083
0184610153565b9392505050565b7f4e487b7100000000000000000000000000000000000
000000000000000000000600052601160045260246000fd5b60006101e0826100dd565b9
1506101eb836100dd565b92508282026101f9816100dd565b91508282048414831517610
2105761020f6101a565b5b5092915050565b6000610222826100dd565b915061022d8361
00dd565b9250828201905080821115610245576102446101a6565b5b9291505056fea26
161
Chapter 6 Getting Started with Smart Contracts
4697066735822122004d196373364650f382fd98a5ee0b1fecb87b1868f9231
59b9aa09076d53beb164736f6c63430008110033
When you change the environment in the Remix IDE, MetaMask will prompt you to
connect to your account (see Figure 6-14). This is to ensure that when you deploy your
smart contract to the Goerli testnet, the account will be used to pay for the transaction
fee. Click Connect in MetaMask to connect the account to the Remix IDE.
162
Chapter 6 Getting Started with Smart Contracts
Figure 6-14. Connect one or more accounts in MetaMask to the Remix IDE
If MetaMask is connected successfully to the Remix IDE, you will see the address of
the account displayed under the ACCOUNT section (see Figure 6-15).
163
Chapter 6 Getting Started with Smart Contracts
Figure 6-15. The Remix IDE will display the account that is connected to it
Tip If you don’t see the account address displayed, refresh the Remix IDE page.
• Your account has some test Ethers (at least 0.02 Ethers).
Click the Deploy button in the Remix IDE to deploy the contract onto the Goerli
testnet. MetaMask will automatically calculate the amount of gas you have to pay and
prompt you (see Figure 6-16). Click Confirm.
164
Chapter 6 Getting Started with Smart Contracts
Note The amount of gas to pay for the deployment of your smart contract is
dependent on the size and complexity of your contract.
When the contract is deployed, you will be able to interact with your contract just like
you did earlier.
165
Chapter 6 Getting Started with Smart Contracts
Summary
In this chapter, you explored what a smart contract looks like and how it works. You
learned how to use the Remix VM to test the contract locally, without deploying to the
blockchain. You also learned how to make use of MetaMask to deploy to the Goerli
testnet. In summary, use the Remix VM option to quickly test your smart contract for its
functionality and then deploy it to the Goerli testnet for a more realistic feel of how it will
work in real life. I strongly suggest that you use the Remix IDE for developing and testing
your smart contracts because it makes your life so much simpler.
In the next few chapters, you will dive into the details of a smart contract and the
various ways you can interact with it.
166
CHAPTER 7
{
"id": "1234567",
"result": {
"math": "A",
"science": "B",
"english": "A"
}
}
167
© Wei-Meng Lee 2023
W.-M. Lee, Beginning Ethereum Smart Contracts Programming, https://doi.org/10.1007/978-1-4842-9271-6_7
Chapter 7 Storing Proofs Using Smart Contracts
The idea is to store the examination result onto the blockchain. However, it is not
advisable to store the JSON string directly onto the blockchain for two key reasons:
Considering the privacy concerns, you should instead store the hash of the
credentials onto the blockchain.
Caution Never store encrypted data on the blockchain as they are susceptible to
hacking. Storing the hash of the data is much safer.
To verify that the education credential is authentic, pass in the JSON string
representing the result and check if the hash exists on the blockchain. If it exists, then the
result is authentic.
ewogICJpZCI6ICIxMjM0NTY3IiwKICAicmVzdWx0IjogewogICAgIm1hdGgiOiAiQSIsCiAg
ICAic2NpZW5jZSI6ICJCIiwKICAgICJlbmdsaXNoIjogIkEiCiAgfQp9Cg==
Note Recall that the Goerli testnet is the testnet that you should use after the
Merge (when Ethereum transitioned from Proof of Work to Proof of Stake). Other
testnets such as Ropsten have since been deprecated.
168
Chapter 7 Storing Proofs Using Smart Contracts
Using the Remix IDE, right-click the contracts item and select New File (see
Figure 7-1).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
contract EduCredentialsStore {
//---store the hash of the strings and their
// corresponding block number---
//--------------------------------------------------
// Store a proof of existence in the contract state
//--------------------------------------------------
169
Chapter 7 Storing Proofs Using Smart Contracts
//----------------------------------------------
// Calculate and store the proof for a document
//----------------------------------------------
function storeEduCredentials(string calldata
document) external {
// call storeProof() with the hash of the string
storeProof(proofFor(document));
}
//--------------------------------------------
// Helper function to get a document's sha256
//--------------------------------------------
// Takes in a string and returns the hash of the
// string
function proofFor(string calldata document) private
pure returns (bytes32) {
// converts the string into bytes array and
// then hash it
return sha256(bytes(document));
}
//-----------------------------------------------
// Check if a document has been saved previously
//-----------------------------------------------
function checkEduCredentials(string calldata
document) public view returns (uint){
// use the hash of the string and check the
// proofs mapping object
return proofs[proofFor(document)];
}
170
Chapter 7 Storing Proofs Using Smart Contracts
The contract is now much more significantly complex that the one you saw in the
previous chapter. Here is a summary of this contract:
171
Chapter 7 Storing Proofs Using Smart Contracts
With all the different keywords for function access modifiers and parameter
declarations, which should you use? Here is a rule of thumb:
• Use calldata for parameter declaration if the data passed into the
function need not be modified. Declaring a function with a calldata
parameter will save on gas fees.
• Use external instead of public if there is no need for your functions
to be called by subclasses of the contract. Due to the way arguments
in public functions are accessed, declaring functions as external will
incur lesser gas fees.
Figure 7-2 shows the conceptual flow of how your smart contract can be used.
Figure 7-2. How the smart contract interacts with the user
172
Chapter 7 Storing Proofs Using Smart Contracts
Figure 7-3 shows how the smart contract stores the hash of the exam results onto the
blockchain. The state variable is a mapping object containing key/value pairs. The keys
are the hashes and the values are the block numbers in which the hashes are written
onto the blockchain.
Figure 7-3. How the mapping object records the hashes and block numbers
173
Chapter 7 Storing Proofs Using Smart Contracts
C
ompiling the Contract
With the smart contract written, it is now time to compile it. In the Remix IDE, click the
Compiler tab (see Figure 7-4, step 1) and check Auto compile. Doing so will enable the
Remix IDE to automatically compile your code every time you make changes to it.
174
Chapter 7 Storing Proofs Using Smart Contracts
At the bottom, you will see two items: ABI and Bytecode. Clicking ABI will copy the
application binary interface (ABI) to the clipboard. Paste it onto a text editor and it will
look like this:
[
{
"inputs": [
{
"internalType": "string",
"name": "document",
"type": "string"
}
],
"name": "checkEduCredentials",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "document",
"type": "string"
}
],
175
Chapter 7 Storing Proofs Using Smart Contracts
"name": "storeEduCredentials",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
If you remove the formatting of the ABI and store it as a single line, you
will see the following:
D
eploying the Contract
You are now ready to deploy the smart contract to the Goerli testnet. In the Remix IDE,
click the Deploy icon (see Figure 7-5, step 1). Make sure the environment is Injected
Provider – Metamask (step 2). By selecting MetaMask, this means that the contract
will deploy to whichever network MetaMask is connected to (make sure MetaMask is
connected to Goerli). Finally, click the Deploy button (step 3).
176
Chapter 7 Storing Proofs Using Smart Contracts
MetaMask will prompt you to pay for the transaction. Click Confirm and wait a while
for the transaction to confirm.
Note To call a smart contract on the blockchain, your dapp needs the address of
the contract as well as its ABI.
Let’s now try to store the hash of the base64-encoded JSON string representing the
result of a student onto the blockchain. Once the base64-encoded string is pasted into
the textbox, click the storeEduCredentials button (see Figure 7-6).
177
Chapter 7 Storing Proofs Using Smart Contracts
Tip Remember I mentioned the need to perform a base64 encoding on the JSON
string before computing its hash? If you directly copy and paste the JSON string
into the text box next to the storeEduCredentials button, this creates problem
as keys and string values in JSON are double-quoted, and the entire JSON string
needs to be double-quoted before passing into the function in the smart contract.
To solve this problem, it is easier to simply base64 encode your JSON string and
then pass the function the base64-encoded result.
178
Chapter 7 Storing Proofs Using Smart Contracts
MetaMask will prompt you to pay for the transaction. Click Confirm (see Figure 7-7).
After a while, the transaction will be confirmed. You can now paste the same base64-
encoded string into the textbox displayed next to the checkEduCredentials button (see
Figure 7-8). Clicking the checkEduCredentials button will display the block number
(7881763, in this example) in which the hash of the base64-encoded string was stored
on the blockchain. If you see a result of 0, it means that the hash was not found on the
blockchain.
179
Chapter 7 Storing Proofs Using Smart Contracts
Figure 7-8. Verifying that the educational credential was previously stored on the
blockchain
Tip What is the difference between the colors of the buttons? Orange buttons
mean that you need to pay transaction fees (because you are modifying the state
of the blockchain) while blue buttons mean that you don’t have to pay (you are just
reading data off the blockchain).
180
Chapter 7 Storing Proofs Using Smart Contracts
contract EduCredentialsStore {
// store the owner of the contract
address owner = msg.sender;
The owner variable automatically stores the address of the account (msg.sender)
that deploys it. Then, in the storeEduCredentials() function, add in the require()
function, as follows:
The require() function first checks that whoever is calling this function (msg.
sender) must be the owner, or else it returns an error message ("Only the owner
of contract can store the credentials"). If the condition is met, execution will
continue; if not, the execution halts.
In the Remix IDE, you can try out the above modifications by first deploying the
contract using Account 1. After the contract is deployed, switch to another account in
MetaMask and try to call the storeEduCredentials() function. You will see an error, as
shown in Figure 7-9.
181
Chapter 7 Storing Proofs Using Smart Contracts
Caution Note that a smart contract is not alterable. Once it is deployed, you
won’t be able to make any changes to it. Hence when you redeploy a contract, a
new contract is stored on the blockchain; you will not be able to access any of the
state variables of the old contract in the new one.
182
Chapter 7 Storing Proofs Using Smart Contracts
// use the hash of the string and check the proofs mapping object
return proofs[proofFor(document)];
}
At the same time, add the require() function to indicate that the caller must send in
1000 Wei (represented in msg.value).
Table 7-1 shows the various denominations in Ethereum. For example, 1 Ether is
equal to 1,000,000,000,000,000,000 Wei (18 zeros) and 1 Ether is equal to 0.001 Kether.
183
Chapter 7 Storing Proofs Using Smart Contracts
Tip Some of the denominations in Ether are named after famous people in the
crypto world. For example, Finney was named after Hal Finney, who was an early
Bitcoin contributor and received the first bitcoin transaction from Bitcoin’s creator,
Satoshi Nakamoto. Szabo was named after Nick Szabo, who first came out with
the concept of smart contracts. Wei was named after Wei Dai, a cryptographer who
came up with “b-money,” a concept referenced in section 2 of the Bitcoin paper.
In Solidity, the variable msg.value is always expressed in Wei. In the above statement, you
could also rewrite the comparison simply as
msg.value == 1000
However, Solidity allows you to compare units of Ether using the special syntax used above:
msg.value == 1000 wei
This syntax is useful when you are comparing larger amounts of Ether. For example, if you
want to check if the incoming amount is 1 Ether, instead of doing this:
msg.value == 1000000000000000000
// OR
msg.value == 1e18
In Solidity 0.7 onwards, you can do this for the following units: Gwei, Ether, and Wei.
184
Chapter 7 Storing Proofs Using Smart Contracts
When you deploy this contract, observe that the checkEduCredentials button is now
red (see Figure 7-10). This is due to the payable keyword in the checkEduCredentials()
function.
Tip If a function button is red, it indicates that besides paying for transaction
fees, it may also require you to send Ethers to it.
As you did previously, go ahead and paste the base64-encoded string into the texbox
next to the checkEduCredentials button and then click the storeEduCredentials
button. When you click the checkEduCredential button, you will see the error message
shown in Figure 7-11.
185
Chapter 7 Storing Proofs Using Smart Contracts
Figure 7-11. Remix refuses the transaction becuase the function expects Ethers to
be sent to it
Apparently, this is because you did not send 1000 Wei to the contract. To fix this in
the Remix IDE, specify 1000 Wei before you click the checkEduCredential button, as
shown in Figure 7-12.
186
Chapter 7 Storing Proofs Using Smart Contracts
187
Chapter 7 Storing Proofs Using Smart Contracts
Note Etherscan is a blockchain explorer that allows you to view all the detailed
transaction information that happened on the Ethereum blockchain (including the
various testnets).
Figure 7-13 shows that the contract has a balance of 0.000000000000001 Ether (which
is 1000 Wei). This proves that a smart contract can hold Ethers.
188
Chapter 7 Storing Proofs Using Smart Contracts
function is now performing a transaction, so it is not capable of returning you the result
immediately (it needs to wait for the block to be added to the blockchain). So how do
you solve this issue? You can solve this using events.
In Solidity, a function can return a value back to the caller either directly or through
events. Events are usually used by smart contracts to keep front-end applications
updated on what is happening to the smart contract.
Let’s define an event for your contract using the following statements in bold:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
contract EduCredentialsStore {
// store the owner of the contract
address owner = msg.sender;
//---define an event---
event Result(
address from,
string document,
uint blockNumber
);
These statements defined an event named Result with three parameters: from, hash,
and blockNumber. To fire this event, use the emit keyword. You will fire this event in the
checkEduCredentials() function:
//-----------------------------------------------
// Check if a document has been saved previously
//-----------------------------------------------
function checkEduCredentials(string calldata document) public payable {
require(msg.value == 1000 wei ,
"This call requires 1000 wei");
189
Chapter 7 Storing Proofs Using Smart Contracts
Note There is now no need to return a value from this function, so modify the
signature of the checkEduCredentials() function and comment out the
return statement.
To try out the updated smart contract, deploy it and then call its
checkEduCredentials() function (remember to send it 1000 Wei). The Remix IDE will
listen for the event and you will be able to see it after the transaction is confirmed (see
Figure 7-14).
190
Chapter 7 Storing Proofs Using Smart Contracts
Figure 7-14. You can use the Remix IDE to examine the events fired by smart
contracts
Cashing Out
Now that your smart contract holds Ether, you have a problem. The Ethers are stuck
forever in the contract because you did not make any provisions to transfer them out. To
be able to get Ethers out of a contract, there are two main ways:
191
Chapter 7 Storing Proofs Using Smart Contracts
For this example, you will use the second approach by adding a new cashOut()
function to the contract:
Figure 7-15. Etherscan records the internal transfers of Ethers to another account
192
Chapter 7 Storing Proofs Using Smart Contracts
Destroying a Contract
There will be a time when your smart contract has reached its end of life and needs to be
shut down. To disable your smart contract so that it is no longer callable, you can use the
selfdestruct() function.
Let’s add a kill() function to the contract now:
As usual, you need to ensure that only a specific user (usually the owner of the
contract) can kill the contract. The selfdestruct() function sends all remaining Ethers
stored in the contract to a designated address, which in this case is owner.
Finally, deploy the contract and observe the new kill button (see Figure 7-16).
Figure 7-16. The kill() function deletes the contract from the blockchain
193
Chapter 7 Storing Proofs Using Smart Contracts
After you call the selfdestruct() function on the smart contract, it will continue to accept
transactions, but no processing will be done and the transaction status is always a success.
For example, if you now call the storeEduCredentials() function, it can still be called and
you still need to pay a gas fee. However, nothing gets stored on the blockchain. Also, you can
still send Ethers to the contract and it will hold the Ethers sent. However, you will no longer be
able to get the Ethers out from the contract.
After you have deployed this contract, record its deployed contract address and
ABI. You will make use of these two pieces of information in the next chapter when you
build a Web3 dapp (a front end to the smart contract).
Summary
In this chapter, you learned how to create a smart contract to store proofs of educational
credentials. You also learned a few tricks that you can implement in your Solidity smart
contracts, such as accepting payments and the ability to kill a smart contract. In the next
chapter, you will learn how to build a front-end Web3 dapp to interact with the smart
contract.
194
CHAPTER 8
What Is web3.js?
web3.js is a collection of libraries that allows you to interact with a local or remote
Ethereum node using HTTP, WebSocket, or IPC. Through the web3.js APIs, your front
end can then interact with the smart contracts. The web3.js APIs contain the following
modules:
• web3-eth: For the Ethereum blockchain and smart contracts
For this book, you will only focus on the first module, web3-eth.
195
© Wei-Meng Lee 2023
W.-M. Lee, Beginning Ethereum Smart Contracts Programming, https://doi.org/10.1007/978-1-4842-9271-6_8
Chapter 8 Using the web3.js APIs
Installing web3.js
Installing web3.js requires Node.js. Specifically, you will make use of npm to download
the web3.js APIs onto your local computer.
Tip The easiest way to install Node.js is to use nvm (Node Version Manager).
To learn how to use nvm to install Node.js, check out my article at
https://bit.ly/3QqyEBR.
For this chapter, create a folder named web3projects to store the web3.js APIs. In
terminal, type the following commands:
$ cd ~
$ mkdir web3projects
$ cd web3projects
Before you download the web3.js APIs, you want to create an empty Node.js project:
This command creates a file named package.json. This file contains the
dependencies required by a Node.js application. To download the web3.js APIs, type the
following command:
Tip Creating the package.json file will prevent npm (Node Package Manager)
from showing pages of warning and error messages when you install web3.js.
The --save option informs npm to modify the package.json file and add the web3.js
as a dependency for the application.
The web3projects folder should now have a folder named node_modules. Within this
node_modules folder, you will see several folders, all of which make up the suites of APIs
that is web3.js.
196
Chapter 8 Using the web3.js APIs
<!DOCTYPE html>
<html lang="en">
<script src="./node_modules/web3/dist/web3.min.js"></script>
<body>
<script>
async function loadWeb3() {
//---if MetaMask is available on your web browser---
if (window.ethereum) {
web3 = new Web3(window.ethereum);
//---connect to account---
const account = await window.ethereum.request(
{method: 'eth_requestAccounts'});
console.log(account);
} else {
//---set the provider you want from Web3.providers---
web3 = new Web3(
new Web3.providers.HttpProvider(
"http://localhost:8545"));
}
}
197
Chapter 8 Using the web3.js APIs
load();
</script>
</body>
</html>
Tip Installing the serve application globally using the -g option may require
sudo permission. Alternatively, you can install it locally within the current directory
without using the -g option.
This command installs a web server on the local computer. Typing the serve
command in any directory enables the directory to serve its content through the
web server.
In the web3projects folder, type the following command:
$ cd ~/web3projects
$ serve
Using the Chrome browser (with MetaMask installed), load the following URL:
http://localhost:3000/TestWeb3.html.
198
Chapter 8 Using the web3.js APIs
You will see the alert shown in Figure 8-1. Select the account that you want to
connect in MetaMask, click Next, and then Connect.
Figure 8-1. You need to give permission to the page to allow it to access your
MetaMask account(s)
You should now see the alert showing the address of the account that is connected to
your web page, as shown in Figure 8-2.
199
Chapter 8 Using the web3.js APIs
Let’s understand how this works. In your JavaScript code, you first define a function
named loadWeb3:
//---connect to account---
const account = await window.ethereum.request(
{method: 'eth_requestAccounts'});
console.log(account);
} else {
//---set the provider you want from Web3.providers---
web3 = new Web3(
new Web3.providers.HttpProvider(
"http://localhost:8545"));
}
}
If you load this page through HTTP on a browser with MetaMask installed,
MetaMask automatically injects an API through window.ethereum. This window.
ethereum allows you to interact with an Ethereum node. In this case, it connects to the
200
Chapter 8 Using the web3.js APIs
201
Chapter 8 Using the web3.js APIs
In this function, you fetch all the accounts from the connected node (either
MetaMask or a node like Ganache) and return the first account.
Finally, you define the load() function so that you can call the loadWeb3() and
getCurrentAccount() functions asynchronously:
body {
background-color:#F0F0F0;
padding: 2em;
font-family: 'Raleway','Source Sans Pro', 'Arial';
}
.container {
width: 90%;
margin: 0 auto;
}
202
Chapter 8 Using the web3.js APIs
label {
display:block;
margin-bottom:10px;
}
input {
padding:10px;
width: 100%;
margin-bottom: 1em;
}
button {
margin: 2em 0;
padding: 1em 4em;
display:block;
}
#result {
padding:1em;
background-color:#fff;
margin: 1em 0;
}
This file serves as the CSS (Cascading Style Sheet) for the web front end that you will
build next.
Create a new text file and name it as index.html (save it in the web3projects folder).
Populate it with the following statements:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" type="text/css" href="main.css">
<script src="./node_modules/web3/dist/web3.min.js"></script>
</head>
203
Chapter 8 Using the web3.js APIs
<body>
<div class="container">
<h1>Educational Credentials Notarizer</h1>
<label for="document" class="col-lg-2 control-label">
Credential to store
</label>
<input id="document" type="text">
<button id="btnStore">Store</button>
<label for="document2" class="col-lg-2 control-label">
Check Credential
</label>
<input id="document2" type="text">
<button id="btnCheck">Check</button>
<label for="document2" class="col-lg-2 control-label">
Status
</label>
<h2 id="result"></h2>
</div>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script>
async function loadWeb3() {
//---if MetaMask is available on your web browser---
if (window.ethereum) {
web3 = new Web3(window.ethereum);
//---connect to account---
const account = await window.ethereum.request(
{method: 'eth_requestAccounts'});
console.log(account)
} else {
//---set the provider you want from Web3.providers---
web3 = new Web3(
new Web3.providers.HttpProvider(
"http://localhost:8545"));
}
}
204
Chapter 8 Using the web3.js APIs
address = '0x450FdF943afec4036787f4deDA11A34526c53921'
return await new web3.eth.Contract(abi,address);
}
205
Chapter 8 Using the web3.js APIs
206
Chapter 8 Using the web3.js APIs
In terminal, ensure that the serve command is still running (if not, type serve in the
web3projects folder). Load the Chrome web browser with the following URL: http://
localhost:3000/index.html. You should see the page shown in Figure 8-4.
Figure 8-4. The web front end to interact with the smart contract
207
Chapter 8 Using the web3.js APIs
Figure 8-5. Confirming the transaction to send the credential to the smart
contract for notarization
Click Confirm to confirm the transaction. When the transaction is sent, you
will immediately see the transaction hash displayed at the bottom of the page (see
Figure 8-6).
208
Chapter 8 Using the web3.js APIs
Figure 8-6. The transaction hash is displayed at the bottom of the page
Once the block containing the transaction is mined, you will be able to type the same
credential in the second text box and then click the Check button to verify if the same
credential was previously stored on the blockchain (see Figure 8-7).
209
Chapter 8 Using the web3.js APIs
If the credential was stored previously, you should see Credential is valid at the
bottom of the screen (see Figure 8-8).
Figure 8-8. The result of the validation of the credential is shown at the bottom of
the page
210
Chapter 8 Using the web3.js APIs
address = ‘0x450FdF943afec4036787f4deDA11A34526c53921’
Tip Change the highlighted contract address with your own deployed contract
address.
In the load() function, you call the loadContract() function and at the same time
listen for the events that will be fired by the smart contract when it returns the result of
the checks of credentials:
211
Chapter 8 Using the web3.js APIs
console.log(event.returnValues[0]); // from
console.log(event.returnValues[1]); // text
console.log(event.returnValues[2]); // blocknumber
Using the smart contract loaded, you can now call the storeEduCredentials()
function of the smart contract through the Store button:
Notice that after the storeEduCredentials() function you use the send() function.
The send() function is used when you need to perform a transaction on the smart
contract (e.g., when the smart contract changes state variables). When the transaction
is performed, you display the transaction hash onto the label named #result on the
web page.
212
Chapter 8 Using the web3.js APIs
When the result is returned to you via events, you display it in the label:
Recall that all function calls on the smart contract that require payments (either
required by the smart contract or required due to the smart contract making changes to
state variables) are transactional, so you need to use the send() function when calling
these smart contract functions. What happens if you call non-transactional functions? In
this case, you can use the call() function.
213
Chapter 8 Using the web3.js APIs
// In EduCredentialsStore.sol
//-----------------------------------------------
// Check if a document has been saved previously
//-----------------------------------------------
function checkEduCredentials(string calldata
document) public view returns (uint){
// use the hash of the string and check the
// proofs mapping object
return proofs[proofFor(document)];
}
To call this function using the web3.js API, use the call() function, like this:
notarizer.methods.checkEduCredentials($("#document2").val())
.call(function(error, result) {
if(!error) {
console.log("result is " + result);
if (result > 0) {
$("#result").html("Credential is valid");
} else {
$("#result").html("Credential is NOT valid");
}
} else
console.error(error);
});
Summary
In this chapter, you learned how to use the web3.js APIs to interact with your smart
contracts. You learned how to use the web3.js APIs to build a web front end and how
to interact with different types of smart contract functions. In the next chapter, you will
learn how to interact with smart contracts using Python.
214
CHAPTER 9
215
© Wei-Meng Lee 2023
W.-M. Lee, Beginning Ethereum Smart Contracts Programming, https://doi.org/10.1007/978-1-4842-9271-6_9
Chapter 9 Developing Web3 dapps using Python
But what if the dapp you are creating is not web-based? How do you pay for the
transaction then? This is the topic I will address in this chapter.
Tip web3.py is inspired by web3.js, so you will find many functions similar to
those you see in web3.js.
216
Chapter 9 Developing Web3 dapps using Python
When you develop a Python dapp, you do not have the luxury of connecting to
MetaMask to access your accounts and use it to sign your transactions. Instead, you need
to import your own accounts, sign your own transactions, and then connect it to a full
node (such as Infura) yourself, as shown in Figure 9-2.
In the following sections, you will learn how to use the web.py library to connect to
the Erherum blockchain through Infura.
217
Chapter 9 Developing Web3 dapps using Python
Once you have verified your email, you will be able to log into Infura. Create your
first project (make sure you select Ethereum under PRODUCT) and give your project a
name (see Figure 9-4).
218
Chapter 9 Developing Web3 dapps using Python
You will now be given your project ID, project secret, and endpoints for your
application to connect to. For this article, select Görli as the endpoint (see Figure 9-5).
219
Chapter 9 Developing Web3 dapps using Python
Connecting to Infura
For the following sections, you will be using Jupyter Notebook. Go ahead a create a new
Jupyter Notebook.
Tip If you are new to Jupyter Notebook, check out this introduction: https://
jupyter.org/try-jupyter/retro/notebooks/?path=notebooks/
Intro.ipynb.
220
Chapter 9 Developing Web3 dapps using Python
With the Infura endpoint URL obtained, see if you can connect it using the web3.py
library:
w3 = Web3(Web3.HTTPProvider(
'https:// goerli.infura.io/v3/<Project_ID>'))
w3.isConnected()
The Web3 class returns an instance of a Web3 provider (a full node). The Web3.
HTTPProvider class is a convenience API to access an RPC HTTP provider (which in this
case is Infura).
If you see True as the output, it means you have connected to Infura successfully.
Fetching a Block
Using the Web3 object, let’s now check the latest block number in the Goerli testnet:
w3.eth.get_block_number()
# 7978182
At the time of writing, the latest block number in the Goerli testnet is 7978182.
You can also fetch the last block from the Goerli test network:
w3.eth.get_block('latest')
You will see the something like the following (an array of transaction hashes):
AttributeDict({'baseFeePerGas': 86766563290,
'difficulty': 0,
'extraData': HexBytes('0x'),
'gasLimit': 30000000,
221
Chapter 9 Developing Web3 dapps using Python
'gasUsed': 7186794,
'hash': HexBytes('0x6ab97e687ce253a6e4ada4c05166a47315e60447263e510bb1e5
ad85b9eab8b4'),
'logsBloom': HexBytes('0x20040c00c0000824028000180240000400004
8402020101200000000000204100400004002011200400985
00008200000000011800000240000002898624005000515020
04200c084800000808088000002000c00016821001102000408
04040020410012200108020010381000108940c1000500041120
400001011800100800824008480204010000210000010090000
20140028000081000800800020004002038000c000021000002
87006100080240020000090000404400052108000200840d20a1
00228042e001c00180000002182000400408800408600000000
602019102108024002000013002800000018000800084800c01
20040000000000008'),
'miner': '0x4D496CcC28058B1D74B7a19541663E21154f9c84',
'mixHash': HexBytes('0x8710279fe41af813c450d778c81cff9401e429974249f98cd7
fa02f9788f7c25'),
'nonce': HexBytes('0x0000000000000000'),
'number': 7969293,
'parentHash': HexBytes('0xe1de6d5fe21da88f730faef1c39c6fcf58d3899729218c6e
8abf58b7163322ad'),
'receiptsRoot': HexBytes('0x4be7e89e6fe203768fa2899c7363944d913d7b915d7
5b382a5f871e7d4d39f0f'),
'sha3Uncles': HexBytes('0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a741
3f0a142fd40d49347'),
'size': 109385,
'stateRoot': HexBytes('0x78c30965c1020934b6a3cfe784b00a3fbf25106c79818a66
fc21af676b608ca4'),
'timestamp': 1668684708,
'totalDifficulty': 10790000,
'transactions': [HexBytes('0x8fef9975fcc704e4a518370517d10563ea0a3d3edfa31
b5c1bb71fa42fd0a91d'),
HexBytes('0x0a195f8876d642bbef524c4aa09df48c01471809df9614ea47a692e
9f4832f7f'),
222
Chapter 9 Developing Web3 dapps using Python
HexBytes('0x5fa6e3cb6230c97555391c85d449b6af60af1d4473cfb98a6cb19
4b97668110c'),
... HexBytes('0xe60ae4a1b51cb89bd238499c471f1df6e34eaac64ab39b7c7a0258c
3c5996bfa')],
'transactionsRoot': HexBytes('0x8e20604657f9bf48b2ed24ed38c27a6f507dda3e6d
8b72e56e47754954398dcf'),
'uncles': []})
Once the python-dotenv module is installed, create a file named .env and save it in
the same directory as your Jupyter notebook. Populate it with the following:
account1_private_key = '<private_key_of_account1>'
To get the private key of Account 1, go to MetaMask and follow the steps outlined in
Figure 9-6.
223
Chapter 9 Developing Web3 dapps using Python
Caution When dealing with the private keys of your accounts, ensure that no one
else has access to them.
Once the private key is obtained, you can paste it inside the .env file.
Next, use the following code snippet to set up the details for Account 1 and 2:
load_dotenv()
# Account 1
account1_address = '0xa18A8E5c8242EF0DF08538C4870C638dD4667815'
account1_private_key = os.environ.get('account1_private_key')
# Account 2
account2_address = '0x1cc025d9A1741b51FD5dE6003884dc264F149AdC'
You are only going to use Account 1 to sign transactions later, so you only need to
load up the private key for Account 1.
224
Chapter 9 Developing Web3 dapps using Python
Ensure that Account 1 and Account 2 already have some test Ethers in the Goerli test
network. If not, go to https://goerlifaucet.com/ to get some test Ethers.
w3.eth.get_balance(account1_address)
You will get the result in Wei. Suppose you have 6.2468 ETH. You will get the
following output (in Wei):
6246772509923908581
nonce = w3.eth.get_transaction_count(account1_address)
tx = {
'nonce': nonce, # transaction count
'to': account2_address, # who to send the ETH to
'value': w3.toWei(1000, 'wei'), # the amount to transfer
'gasPrice': w3.eth.gas_price, # get the price of gas
}
225
Chapter 9 Developing Web3 dapps using Python
In this code, you are transferring 1000 Wei from Account 1 to Account 2. You use the
eth.gas_price attribute to fetch the current gas price.
• Next, estimate how much gas is needed for this transaction using the
eth.estimate_gas() function and then insert the amount into the
transaction dictionary:
gas = w3.eth.estimate_gas(tx)
tx['gas'] = gas
print(tx)
{
'nonce': 4,
'to': '0x1cc025d9A1741b51FD5dE6003884dc264F149AdC',
'value': 1000,
'gasPrice': 114287036513,
'gas': 21000
}
• You can now sign the transaction with the private key using the eth.
account.sign_transaction() function:
signed_tx = w3.eth.account.sign_transaction(tx,account1_
private_key)
tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
print(w3.toHex(tx_hash))
0xc23d15f14f20f51b083fe6dab8f953e76b80ce6a3b4c43bfe336d02576eba1da
226
Chapter 9 Developing Web3 dapps using Python
• The transaction will take some time to confirm. If you want to wait
for the transaction to complete, use the eth.wait_for_transaction_
receipt() function (this is a blocking call):
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(w3.eth.get_balance(account1_address))
print(w3.eth.get_balance(account2_address))
The entire code snippet to transfer 1 ETH from Account 1 to Account 2 looks like this:
nonce = w3.eth.get_transaction_count(account1_address)
tx = {
'nonce': nonce,
'to': account2_address,
'value': w3.toWei(1000, 'wei'),
'gasPrice': w3.eth.gas_price,
}
gas = w3.eth.estimate_gas(tx)
tx['gas'] = gas
signed_tx = w3.eth.account.sign_transaction(tx,account1_private_key)
tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
print(w3.toHex(tx_hash))
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
227
Chapter 9 Developing Web3 dapps using Python
contract EduCredentialsStore {
// store the owner of the contract
address owner = msg.sender;
//---define an event---
event Result(
address from,
string document,
uint blockNumber
);
//--------------------------------------------------
// Store a proof of existence in the contract state
//--------------------------------------------------
function storeProof(bytes32 proof) private {
// use the hash as the key
proofs[proof] = block.number;
}
//----------------------------------------------
// Calculate and store the proof for a document
//----------------------------------------------
function storeEduCredentials(string calldata
document) external {
require(msg.sender == owner,
"Only the owner of contract can store the credentials");
228
Chapter 9 Developing Web3 dapps using Python
//--------------------------------------------
// Helper function to get a document's sha256
//--------------------------------------------
// Takes in a string and returns the hash of the
// string
function proofFor(string calldata document) private
pure returns (bytes32) {
// converts the string into bytes array and
// then hash it
return sha256(bytes(document));
}
//-----------------------------------------------
// Check if a document has been saved previously
//-----------------------------------------------
function checkEduCredentials(string calldata document) public payable {
require(msg.value == 1000 wei ,
"This call requires 1000 wei");
}
229
Chapter 9 Developing Web3 dapps using Python
Tip If you can’t remember the address of this contract you previously deployed
on the Goerli testnet, deploy it one more time and take note of its address.
address = '0xC1B4338a54bE22067260bbA1e0B6F3d5c1E2E330'
230
Chapter 9 Developing Web3 dapps using Python
Base64 Encoding
Remember the educational credentials must be base64-encoded before passing to the
smart contract. Here you will define a helper function named base64encode():
import base64
def base64encode(message):
message_bytes = message.encode('ascii')
base64_bytes = base64.b64encode(message_bytes)
return base64_bytes.decode('ascii')
231
Chapter 9 Developing Web3 dapps using Python
"math": "A",
"science": "B",
"english": "A"
}
}
'''
exam_result = base64encode(exam_result)
232
Chapter 9 Developing Web3 dapps using Python
233
Chapter 9 Developing Web3 dapps using Python
V
erifying the Result
With the exam results firmly recorded on the blockchain, let’s now verify the result.
Here’s the code snippet:
exam_result = '''
{
"id": "1234567",
"result": {
"math": "A",
"science": "B",
"english": "A"
}
}
'''
exam_result = base64encode(exam_result)
nonce = w3.eth.getTransactionCount(account1_address)
234
Chapter 9 Developing Web3 dapps using Python
import time
def handle_event(event):
receipt = \
w3.eth.wait_for_transaction_receipt(event['transactionHash'])
result = result_event.processReceipt(receipt)
235
Chapter 9 Developing Web3 dapps using Python
else:
print('Result not found on blockchain.')
return True
block_filter = w3.eth.filter(
{
'fromBlock' : 'latest',
'address' : address # address of contract
})
log_loop(block_filter, 2)
The first part of this code snippet is similar to the previous code snippet. But here’s
what is happening in this code snippet:
• You estimate how much gas fee is needed when you call the
checkEduCredentials () function together with a value of 1000 Wei
(the function needs this amount):
236
Chapter 9 Developing Web3 dapps using Python
transaction = eduCredentialsStore.functions.checkEduCredentials(
exam_result).buildTransaction(
{
'gas' : estimated_gas,
'gasPrice' : w3.eth.gas_price,
'from' : account1_address,
'nonce' : nonce,
'value' : w3.toWei(1000, 'wei'), # amount to
send to the
}) # function
• You then create an instance of the Result event so that you can listen
for this event firing from the smart contract:
• To listen for the Result event, you need to implement your own
looping mechanism. Here, you first use the w3.eth.filter()
function to listen for specific events from the contract. You then
use an infinite loop to keep listening for the events using the get_
new_entries() function of the event filter. You use the wait_for_
transaction_receipt() function to listen only for events related
to this particular transaction. When an event is received, you call
the processReceipt() function of the event to get the details of the
event. In this case, as soon as the Result event is fetched, you stop
listening for future events.
237
Chapter 9 Developing Web3 dapps using Python
• The event returned by the contract will look something like the
following:
(AttributeDict({'args': AttributeDict({'from':
'0xAF8b6CA21023A595F0C4919b8B4a9d1F0c1773e7', 'document':
'CnsKICAiaWQiOiAiMTIzNDU2NyIsCiAgInJlc3VsdCI6IHsKICAgI
CJtYXRoIjogIkEiLAogICAgInNjaWVuY2UiOiAiQiIsCiAgICAiZW5nb
GlzaCI6ICJBIgogIH0KfQo=', 'blockNumber': 7973034}),
'event': 'Result', 'logIndex': 301, 'transactionIndex':
177, 'transactionHash': HexBytes('0x85c4ceb53e1f4c3e7844e
ba51f8d33d291a325ac191fa99a0d554ccc1e9f5864'), 'address':
'0xC1B4338a54bE22067260bbA1e0B6F3d5c1E2E330', 'blockHash':
HexBytes('0x752f3634b1970ca613e51c468e28e86186dd3603f215751f
bc727392c9581460'), 'blockNumber': 7973064}),)
• The event returned is a tuple. You get the first element in the tuple,
then look for the args key, and from there look for the blockNumber
key, which will allow you to know if the examination result is
authentic:
if result[0]['args']['blockNumber'] != 0:
print('Result is verified.')
else:
print('Result not found on blockchain.')
When you run this code, you will see the output like the following
when the Result event is fired:
0x85c4ceb53e1f4c3e7844eba51f8d33d291a325ac191fa99a0d554ccc1e9f5864
(AttributeDict({'args': AttributeDict({'from':
'0xAF8b6CA21023A595F0C4919b8B4a9d1F0c1773e7', 'document':
'CnsKICAiaWQiOiAiMTIzNDU2NyIsCiAgInJlc3VsdCI6IHsKICAg
ICJtYXRoIjogIkEiLAogICAgInNjaWVuY2UiOiAiQiIsCiAgICAi
ZW5nbGlzaCI6ICJBIgogIH0KfQo=', 'blockNumber': 7973034}),
'event': 'Result', 'logIndex': 301, 'transactionIndex':
177, 'transactionHash': HexBytes('0x85c4ceb53e1f4c3e7844e
ba51f8d33d291a325ac191fa99a0d554ccc1e9f5864'), 'address':
238
Chapter 9 Developing Web3 dapps using Python
Note that for smart contract functions that do not perform any transaction, you can simply
call them using the call() function. For example, suppose the checkEduCredentials()
function does not require any payment and thus it does not perform any transaction. In this
case, you can call it like this:
eduCredentialsStore.functions.checkEduCredentials(exam_result).call()
Summary
Overall, building a Web3 dapp using Python and web3.py is like building one using
web3.js. The key difference is that for a Python dapp, you need to get intimate with the
transactions yourself: connecting to a full node, signing the transactions, estimating
the required gas fee needed, setting the gas price, and then finally waiting for the
transactions to confirm and handle the events fired.
239
CHAPTER 10
241
© Wei-Meng Lee 2023
W.-M. Lee, Beginning Ethereum Smart Contracts Programming, https://doi.org/10.1007/978-1-4842-9271-6_10
Chapter 10 Project: Online Lottery
There are a total of five players. Each player will place a bet on a number. For
example, player 1 bets on the number 1 using 2 Ethers, and player 2 bets on the number
2 with 4 Ethers, and so on. The betting will stop when the maximum number of players
is reached. Once the betting has stopped, the owner of the contract will set the winning
number and the payouts to the winners will be processed.
Suppose the winning number is 2. Based on the example, players 2 and 4 have bet on
the winning number. The amount won by each winner is proportional to how much they
have bet. The calculation is shown in Figure 10-2.
Your contract will automatically transfer the payout to the winners. If there is no
winner for the game, all the Ethers will be retained by the contract and can be transferred
to the owner of the contract.
242
Chapter 10 Project: Online Lottery
The playerDetailsMapping is a mapping object where the keys are the account
addresses of all players. The value for each key is a structure named Player with two
members: amountWagered (the amount of Wei bet), and numberWagered (the number that
the player is betting on). Figure 10-3 shows the data structure that you will use to store
the details of the game.
243
Chapter 10 Project: Online Lottery
Figure 10-4. The playerAddressesArray variable stores all of the players’ addresses
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import "@openzeppelin/contracts/utils/Strings.sol";
contract Betting {
}
244
Chapter 10 Project: Online Lottery
contract Betting {
// record the owner of the contract
address owner = msg.sender;
//---define an event---
event Winner(
address winner,
uint amount
);
245
Chapter 10 Project: Online Lottery
• Winner and Status are events that are used to notify client
applications.
Betting a Number
Next, let’s define the bet() function within the contract. The bet() function allows a
player to bet on a number:
246
Chapter 10 Project: Online Lottery
// ensure the max number of players has not been reached
require(numberOfPlayers < MAX_NUMBER_OF_PLAYERS,
"Maximum number of players reached");
// CHECK #2
// check to ensure caller has never betted
require(playerDetailsMapping[msg.sender].numberWagered == 0,
"You have already betted");
// CHECK #3
// check the range of numbers allowed for betting
require(number >=1 && number <= MAX_NUMBER_TO_BET,
string.concat("You need to bet between 1 and ",
Strings.toString(MAX_NUMBER_TO_BET)));
// CHECK #4
// ensure min. bet (note that msg.value is in wei)
require( msg.value >= MIN_WAGER,
string.concat("Minimum bet is ",
Strings.toString(MIN_WAGER), " wei"));
numberOfPlayers++;
totalWager += msg.value;
Note Observe that the bet() function has the payable keyword. This means
that when a player bets on a number, they must also send in Ethers.
247
Chapter 10 Project: Online Lottery
In this function, you need to perform a few checks. First, you need to check if the
maximum number of players has been exceeded:
// CHECK #1
// ensure the max number of players has not been reached
require(numberOfPlayers < MAX_NUMBER_OF_PLAYERS,
"Maximum number of players reached");
Next, ensure that each player can only bet once by retrieving the Player structure in
the playerDetailsMapping object and checking its numberWagered value. If it is zero, it
means that the player has not bet previously:
// CHECK #2
// check to ensure caller has never betted
require(playerDetailsMapping[msg.sender].numberWagered == 0,
"You have already betted");
Next, you need to check that the number wagered falls within the range allowed:
// CHECK #3
// check the range of numbers allowed for betting
require(number >=1 && number <= MAX_NUMBER_TO_BET,
string.concat("You need to bet between 1 and ",
Strings.toString(MAX_NUMBER_TO_BET)));
You also need to check that the amount wagered is at least the minimum amount:
// CHECK #4
// ensure min. bet (note that msg.value is in wei)
require( msg.value >= MIN_WAGER,
string.concat("Minimum bet is ",
Strings.toString(MIN_WAGER), " wei"));
248
Chapter 10 Project: Online Lottery
Once all these checks are cleared, you need to record the number and amount
wagered by the player (msg.sender is the address of the player):
You also increment the number of wagers as well as sum up all the amount
wagered so far:
numberOfPlayers++;
totalWager += msg.value;
winningBetNumber = winningNumber;
249
Chapter 10 Project: Online Lottery
250
Chapter 10 Project: Online Lottery
Tip In this implementation, you pass in a winning number to the function. In real
life, you may want to connect to a real lottery feed to fetch the winning number.
You can do so via Oracles. Refer to my article called “Smart Contracts — Fetching
Data From External Sources using Oracles” (https://blog.cryptostars.is/
smart-contracts-fetching-data-from-external-sources-using-
oracles-bfd73f362375) for more details.
You first check to ensure that only the owner of this contract can invoke this function:
require(msg.sender == owner,
"Only the owner can announce the winner");
You next create an array in memory to store all the winning players, plus
two variables for storing the number of winners as well as the total amount of
winning wagers:
You iterate through all the players using the playerAddressesArray array and check
if the number they wagered on is the winning number. The winning players are then
added to the winners array.
You then calculate the winnings for each player and transfer the winnings to them
using the transfer() function:
Finally, reset all the variables so that a new game can start:
252
Chapter 10 Project: Online Lottery
Obviously, you need to ensure that only the owner (the one that deploys the
contract) of the contract can cash out.
253
Chapter 10 Project: Online Lottery
Figure 10-5. Ten accounts each with 100 Ethers are created for your testing use
254
Chapter 10 Project: Online Lottery
When the contract is successfully deployed, you will see the various functions shown
in Figure 10-6.
255
Chapter 10 Project: Online Lottery
Then, click the getGameStatus button and you should see a result of 1 and 3 (see
Figure 10-8). The 1 means one player has wagered and 3 means the maximum number
of players.
256
Chapter 10 Project: Online Lottery
1 (owner) 1 2000
2 5 1000
3 5 2000
257
Chapter 10 Project: Online Lottery
Tip You don’t have to wait for all three players to bet before announcing the
winner. Anytime you want to end the game, you can call the announceWinners
button. You can end the game even if only two players have wagered.
At the bottom of the Remix IDE, you should see the events fired by the smart contract
(see Figure 10-10).
258
Chapter 10 Project: Online Lottery
Figure 10-10. Events fired by the smart contract containing the winner(s)
addresses as well as the amount won
Since there two players who wagered on 5, there are two winners. The winning
amount for the second and third players are 1666 and 3333, respectively. The
calculations for the winnings are as follows:
Note that due to rounding, there is a balance of 1 Wei left in the contract (see
Figure 10-11).
259
Chapter 10 Project: Online Lottery
Click the getGameStatus button and you will see that the number of players has
been reset to 0.
260
Chapter 10 Project: Online Lottery
Figure 10-12. Click the ABI button to get the ABI of the contract
Paste the ABI onto a text file so that you can refer to it later. Here is the ABI for your
convenience:
261
Chapter 10 Project: Online Lottery
Click the Deploy button to deploy the contract. For reference, the address of my
deployed contract is 0xA0a4a98562587211CAbdC910721e0020A52AcE11.
262
Chapter 10 Project: Online Lottery
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" type="text/css" href="main.css">
<script src="./node_modules/web3/dist/web3.min.js">
</script>
</head>
<body>
<div class="container">
<h1>Ethereum Lottery</h1>
<center>
<label for="numberToWager"
class="col-lg-2 control-label">
Number to wager
</label>
<input id="numberToWager" type="text">
<label for="weiToWager"
class="col-lg-2 control-label">
Number of wei to wager
</label>
<input id="weiToWager" type="text">
<button id="btnBet">Bet</button>
<hr/>
<h2 id="result"></h2>
<h2 id="status"></h2>
</center>
</div>
263
Chapter 10 Project: Online Lottery
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js">
</script>
<script>
//-----Start of JavaScript functions-----
async function loadWeb3() {
//---if MetaMask is available on your web browser---
if (window.ethereum) {
web3 = new Web3(window.ethereum);
//---connect to account---
const account = await window.ethereum.request(
{method: 'eth_requestAccounts'});
console.log(account)
} else {
//---set the provider you want from Web3.providers---
web3 = new Web3(
new Web3.providers.HttpProvider(
"http://localhost:8545"));
var version = web3.version;
console.log("Version of web3: ", version);
}
}
264
Chapter 10 Project: Online Lottery
265
Chapter 10 Project: Online Lottery
266
Chapter 10 Project: Online Lottery
.on('transactionHash', function(hash){
$("#result").append("Bet has been submitted</br>");
console.log("Transaction hash: " + hash);
})
.on('receipt', function(receipt){
$("#result").append("Bet has been accepted</br>");
console.log("Receipt: " + receipt.toString());
})
.on('error', function(error, receipt) {
$("#result").append("An error has occurred. Betting was
not successful</br>");
console.log("Error: " + error + "," + receipt.
toString());
});
});
}
load();
//-----End of JavaScript functions-----
</script>
</body>
</html>
To test the web front end, type the following commands in terminal:
$ cd ~/webprojects
$ serve
Using three instances of the Chrome browser, load each browser with the following
URL: http://localhost:3000/OnlineBetting.html. You should see all display the
same statuses (see Figure 10-14).
267
Chapter 10 Project: Online Lottery
Figure 10-14. Three instances of Chrome displaying the same page and
game status
Using Account 1 (on the left browser), place a bet on the number 1 using 2 Ethers and
click the Bet button. Observe that MetaMask will pop up a window showing the amount
to be sent to the contract. Click CONFIRM. Once you click the CONFIRM button, the
first browser will show the message “Bet has been submitted.” After a while (when the
transaction has been confirmed), the app will also display two additional messages (may
not be displayed in any specific order; see Figure 10-15):
• Status of game: 1 of 3
268
Chapter 10 Project: Online Lottery
Figure 10-15. The messages you will see when you have submitted a bet and when
the transaction has been confirmed
269
Chapter 10 Project: Online Lottery
When the transaction has been confirmed, the winners will be announced (see
Figure 10-17).
270
Chapter 10 Project: Online Lottery
Figure 10-17. The app displaying the winner(s) and their winnings
Cashing Out
If there is any balance in the contract, you can cash out (to the owner) by clicking the
cashOut button in the Remix IDE (see Figure 10-18).
271
Chapter 10 Project: Online Lottery
Tip Make sure you set the account to Account 1 before you click the
cashOut button.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import "@openzeppelin/contracts/utils/Strings.sol";
contract Betting {
// record the owner of the contract
address owner = msg.sender;
//---define an event---
event Winner(
address winner,
uint amount
);
273
Chapter 10 Project: Online Lottery
// CHECK #2
// check to ensure caller has never betted
require(playerDetailsMapping[msg.sender].numberWagered == 0,
"You have already betted");
// CHECK #3
// check the range of numbers allowed for betting
require(number >=1 && number <= MAX_NUMBER_TO_BET,
string.concat("You need to bet between 1 and ",
Strings.toString(MAX_NUMBER_TO_BET)));
// CHECK #4
// ensure min. bet (note that msg.value is in wei)
require( msg.value >= MIN_WAGER,
string.concat("Minimum bet is ",
Strings.toString(MIN_WAGER), " wei"));
274
Chapter 10 Project: Online Lottery
numberOfPlayers++;
totalWager += msg.value;
require(msg.sender == owner,
"Only the owner can announce the winner");
winningBetNumber = winningNumber;
275
Chapter 10 Project: Online Lottery
276
Chapter 10 Project: Online Lottery
Summary
In this chapter, you learned how to build an online lottery game. Apart from using
the knowledge that you learned in the previous chapters, you also learned several
new things:
277
CHAPTER 11
279
© Wei-Meng Lee 2023
W.-M. Lee, Beginning Ethereum Smart Contracts Programming, https://doi.org/10.1007/978-1-4842-9271-6_11
Chapter 11 Creating Your Tokens
• The stalls need not deal with cash; this will prevent the workers at
the stalls (usually employees of the carnival owner) from pocketing
the cash.
• The owner of the carnival receives cash up front before you even play
the games, and unused tokens cannot be refunded.
• The owner of the carnival wants to sell you more tokens than you
need by giving you incentives to buy more up front.
In the cryptocurrency world, the same concepts apply to tokens. Instead of using fiat
currency to buy the tokens directly, you first buy Ethers and then use Ethers to buy the
tokens (see Figure 11-2).
280
Chapter 11 Creating Your Tokens
In the cryptocurrency world, two terms have been used interchangeably: coins and tokens.
So are they the same? First, the definition of a coin is that it is an asset that is native to its
own blockchain. Examples of coins are Bitcoin, Litecoin, and Ether. Each of these coins exists
on their own blockchain. Tokens, on the other hand, are created on existing blockchains. The
most common token platform is Ethereum, which allows developers to create their own tokens
using the ERC-20 standard (more on this in a later section of this chapter). Using Ether (which
is the coin native to the Ethereum blockchain), users can exchange them for specific tokens on
the Ethereum blockchain. Hence, strictly speaking, coins are not the same as tokens. In fact,
tokens are based on coins.
Figure 11-3. A token contract contains a map of account addresses and their
balances
281
Chapter 11 Creating Your Tokens
Figure 11-5. Minting new tokens by increasing the balance of one or more
accounts
Burning Tokens
The total supply of tokens can be decreased by burning tokens. That is, you reduce the
balance of an account, as shown in Figure 11-6.
282
Chapter 11 Creating Your Tokens
Figure 11-7. Burning tokens by sending tokens to a dead address. In this case, the
total supply does not change
283
Chapter 11 Creating Your Tokens
Figure 11-9. Token with three decimal precision, stored internally and viewed
from outside
284
Chapter 11 Creating Your Tokens
Internally, for a token with n-decimals of precision, the balance is represented using
this formula: token_internal = token_external * 10 n. For example, a user may own 4.497
GoldTokens, but internally, the token contract stores its balance as 4497.
contract ERC20Interface {
function totalSupply() public constant returns (uint);
function balanceOf(address tokenOwner) public constant
returns (uint balance);
function allowance(address tokenOwner, address
spender) public constant returns (uint remaining);
function transfer(address to, uint tokens) public
returns (bool success);
function approve(address spender, uint tokens) public
returns (bool success);
285
Chapter 11 Creating Your Tokens
Here are the uses for the various functions and events in the ERC20Interface:
• totalSupply: Returns the total token supply
286
Chapter 11 Creating Your Tokens
So if you are writing an ERC-20 token contract, you just need to import the base
implementation from OpenZeppelin and inherit from it.
For your example, create a new contract in the Remix IDE and name it as token.sol.
Populate it with the following statements:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/
contracts/token/ERC20/ERC20.sol";
In this contract, you are creating 1000 tokens (value of n), and each token can go up
to 18 decimal places of precision (since the decimals() function by default returns 18).
Internally within the contract, the number of tokens minted is dependent on n and the
precision. In this example, while the total supply is 1000 tokens, the total units of base
tokens minted is equal to 1000 x 1018, or 1000,000,000,000,000,000,000.
287
Chapter 11 Creating Your Tokens
Tip All operations involving tokens are based on the base token units. For
example, if I want to send 1 token to another account, I have to transfer
1,000,000,000,000,000,000 base token units of my tokens to the recipient
account.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/
contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
288
Chapter 11 Creating Your Tokens
In this example, the decimals() function returns 0, which means that your token
does not have any decimal places. If you return 1, the smallest denomination for your
token is 0.1.
For simplicity, your token contract will use the default 18 decimal places of precision.
Tip Symbols of tokens are not unique, but you should try to keep them to within
three to four characters.
Figure 11-10 shows an example name and symbol for a token. Click Deploy to deploy
the token contract.
Figure 11-10. Deploying the token contract with the name and symbol
Once the contract is deployed, you can expand on the contract name to review the
various functions (see Figure 11-11). These are the functions that you need to implement
in your ERC-20 token contract (which was implemented by the OpenZeppelin base
contract).
289
Chapter 11 Creating Your Tokens
Copy the address of the deployed token contract (see Figure 11-12).
Figure 11-12. The deployed token contract (copy its address to the clipboard)
290
Chapter 11 Creating Your Tokens
291
Chapter 11 Creating Your Tokens
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import "./token.sol";
contract EduCredentialsStore {
// store the owner of the contract
address owner = msg.sender;
//---define an event---
event Document(
address from,
bytes32 hash,
uint blockNumber
);
//==========================================
// return the token balance in the contract
//==========================================
function getBalance() public view returns (uint256) {
return token.balanceOf(address(this));
}
//--------------------------------------------------
// Store a proof of existence in the contract state
292
Chapter 11 Creating Your Tokens
//--------------------------------------------------
function storeProof(bytes32 proof) private {
// use the hash as the key
proofs[proof] = block.number;
//----------------------------------------------
// Calculate and store the proof for a document
//----------------------------------------------
function storeEduCredentials(string calldata
document) external {
require(msg.sender == owner,
"Only the owner of contract can store the
credentials");
// call storeProof() with the hash of the string
storeProof(proofFor(document));
}
//--------------------------------------------
// Helper function to get a document's sha256
//--------------------------------------------
// Takes in a string and returns the hash of the
// string
function proofFor(string calldata document) private
pure returns (bytes32) {
// converts the string into bytes array and then
// hash it
return sha256(bytes(document));
}
//-----------------------------------------------
// Check if a document has been saved previously
//-----------------------------------------------
function checkEduCredentials(string calldata
document) public payable returns (uint){
293
Chapter 11 Creating Your Tokens
// use the hash of the string and check the proofs
// mapping object
return proofs[proofFor(document)];
}
294
Chapter 11 Creating Your Tokens
With these additional statements in bold, you make the following changes to the
EduCredentialsStore contract:
• Import the token.sol token contract. This is the token contract you
deployed earlier in this chapter.
295
Chapter 11 Creating Your Tokens
Before you can call the checkEduCredentials() function (which now requires a
payment of 1000 base units of the tokens instead of 1000 Wei), the token owner (which
is you) needs to approve 1000 base units of the token to be paid to the smart contract. To
do that, you need to call the approve() function of the token contract with the following
values (see also Figure 11-15):
0x913286326233118493F2D5eA62dCA2E90133452B,1000
This value indicates that you want to approve 1000 base units of
the token to be spent on the smart contract whose address is specified
(0x913286326233118493F2D5eA62dCA2E90133452B).
Click the approve button and MetaMask will show the prompt shown in
Figure 11-16. Click Confirm to grant the permission for your tokens to be used on the
smart contract.
296
Chapter 11 Creating Your Tokens
297
Chapter 11 Creating Your Tokens
You can now call the checkEduCredentials() function (see Figure 11-17).
Once the transaction is confirmed, 1000 base units of the token will be transferred
to the smart contract. If you examine the transaction on Etherscan, you will observe that
there is a transfer of ERC-20 tokens (see Figure 11-18).
298
Chapter 11 Creating Your Tokens
Figure 11-18. Etherscan records the transfer of tokens from an account to the
smart contract
To verify that the contract did indeed receive the tokens, click the getBalance button
(see Figure 11-19). You should see a value of 1000.
299
Chapter 11 Creating Your Tokens
In MetaMask, you will also see that the account holding onto the tokens has its
balance reduced (see Figure 11-20). This is because 1000 base units of the tokens have
been transferred to the smart contract as payment.
300
Chapter 11 Creating Your Tokens
But how do you make money from the tokens? In the real world, you may want to sell
the tokens in exchange for Ethers. In fact, you can do this by programming it right onto
the token contract.
Here’s the improved version of your token contract that allows tokens to be
purchased with Ethers:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/
contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
// this function is called when someone sends ether to the token
contract
receive() external payable {
// msg.value (in Wei) is the ether sent to the token contract
// msg.sender is the account that sends the ether to the
// token contract
301
Chapter 11 Creating Your Tokens
• You first define the price of your token using the unitsOneEthCanBuy
variable (I will discuss this in more detail in the next section).
• You need to save the address of the owner of this token contract, so
you declare a variable called tokenOwner, which you will initialize in
the constructor of the token contract.
302
Chapter 11 Creating Your Tokens
Based on this formula, you can now very easily calculate how many tokens a user can
buy when they send Ethers to your token contract. Figure 11-22 shows how many tokens
a user will get when they send your token contract 2 Ethers.
From this explanation, you now know that the amount of tokens a user can
purchase is
303
Chapter 11 Creating Your Tokens
In order to sell the tokens to the user, you need to ensure that your contract has
enough tokens, so you use the require() function to check. The first argument in the
require() function is the condition that is evaluated. If the condition evaluates to false,
it will raise an exception with the second argument as the reason:
If the token contract has sufficient tokens, you use the _transfer() function to
transfer the tokens to the user:
When the transfer has been performed, emit the Transfer() event:
Finally, with the Ethers received, you need to transfer them to the token owner:
Caution This part is important! If you don’t do this, the Ethers will be stuck with
the token contract forever and there will be no way to get them back! They will be
lost forever! Kiss your Ethers goodbye!
304
Chapter 11 Creating Your Tokens
As usual, add the newly created tokens to MetaMask (see Figure 11-23).
Tip You can hide previously added tokens in MetaMask by selecting the token,
clicking the three vertical dots, and clicking Hide WML (see Figure 11-24).
305
Chapter 11 Creating Your Tokens
Switch to another account and send the token contract (using the address you got
in the previous step) and send it 0.001 Ether (see Figure 11-25). You will see a warning
about sending Ethers to a token contract. Click I understand and then click Next and in
the next screen click Confirm to pay the transaction fees.
306
Chapter 11 Creating Your Tokens
While waiting for the transaction to confirm, add the newly created token to the
current account in MetaMask. When the transaction is confirmed, you will see 0.01 WML
tokens in MetaMask (see Figure 11-26).
307
Chapter 11 Creating Your Tokens
308
Chapter 11 Creating Your Tokens
Click the transaction in MetaMask and view the transaction details on the Etherscan
blockchain explorer (see Figure 11-27).
309
Chapter 11 Creating Your Tokens
You will see that the contract transfers the received 0.001 Ether to the owner of the
contract (see Figure 11-28).
Summary
In this chapter, you learned how tokens work and you created one yourself using a token
contract. You also learned how to add tokens to your MetaMask account and use them to
pay for smart contract services. Finally, you learned how you can modify token contract
so that one can buy tokens by sending Ethers to it.
310
CHAPTER 12
Creating Non-Fungible
Tokens Using ERC-721
Non-fungible tokens (NFTs) seem to be the latest craze in the world of blockchain lately.
Chances are you have heard from friends or the media about some guys getting really
rich by selling selfies as NFTs. So, what exactly is an NFT and what do you as a developer
need to know about NFTs? This chapter explains what an NFT is and how to mint your
own NFTs.
What Is an NFT?
Before you learn what an NFT is and how to mint one, it is important that you
understand the meaning of fungible. The word fungible means “something whose part or
quantity may be replaced by another equal part or quantity in paying a debt or settling
an account” (source: www.merriam-webster.com/dictionary/fungible).
A good example of a fungible item is the US dollar bill (Figure 12-1).
311
© Wei-Meng Lee 2023
W.-M. Lee, Beginning Ethereum Smart Contracts Programming, https://doi.org/10.1007/978-1-4842-9271-6_12
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
Using the dollar bill as an example, it is fungible because you can use it to pay for any
good or service that is equivalent to a dollar. In addition, you can also use it to exchange
for 10 dimes. Two people, each holding a dollar note, would be happy to exchange the
dollar note with each other because after the exchange they would still have the same
buying power.
A baseball card (see Figure 12-2), on the other hand, is not fungible because each
card has unique qualities and has different values to different people. A baseball card
collector may value the card at $1 million, while a storeowner may not see much value in
the card and may value the card at $1.
312
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
Now that you know the meaning of fungible, let’s discuss what an NFT is. NFT stands
for non-fungible token. An NFT is basically a record on the blockchain (predominantly
on Ethereum but there are alternative blockchains that support NFTs) that records the
ownership of a digital art piece (or any item of value, but most NFTs today are digital
assets such as images, music, or videos). Buyers of NFTs typically get limited rights to
display the digital artwork they represent, but in many ways, they’re just buying bragging
rights and an asset they may be able to resell later.
Tip In short, an NFT is nothing more than a unique record on the blockchain with
a transactional record and a hyperlink to the digital asset.
• Rarible (https://rarible.com)
• Mintable (https://mintable.app)
313
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
For this chapter, you will focus on the ERC-721 standard for creating NFTs.
314
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/
contracts/access/Ownable.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/
contracts/token/ERC721/extensions/ERC721URIStorage.sol";
contract MyNFT is Ownable, ERC721URIStorage {
// name and symbol
constructor() ERC721("Learn2develop.net NFT", "DLS") {
}
315
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
• The mint() function takes three arguments: the address of the owner
of the NFT, the Token ID, and the TokenURI that points to the details
of the NFT.
316
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
Note For this example, you will be deploying the NFT contract onto the Goerli
testnet through MetaMask.
Once the NFT token contract has been deployed, you should be able to expand on
the contract address and see the list of functions, as shown in Figure 12-5.
Figure 12-5. Exploring the various functions in the deployed NFT contract
317
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
• Address of Account 2:
0x63eE1AEb74c52f09EaB6a2825bB1918B5e045050
• Address of Account 3:
0xC663D99b0B5D0F6eE163173E6889AA47F787c403
Figure 12-6. The owner function returns the account address that deployed the
NFT contract
318
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
First, you need to upload this digital asset somewhere. One option is to use IPFS. You
can use the following page to upload your image using an IPFS gateway: https://ipfs-
gateway.cloud (see Figure 12-8).
319
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
Figure 12-8. You can upload an image to IPFS using an IPFS gateway
Once the file is uploaded to IPFS, you obtain the hash of the image (known as the
Content ID, or CID) that has been uploaded to IPFS. You can use this hash to fetch the
file on IPFS.
You can use an IPFS gateway such as ipfs.io (another IPFS gateway) to specify
the location of the file using the following format: https://ipfs.io/ipfs/<hash_of_
image>. For your example, the image can be found using this URL: https://ipfs.io/
ipfs/QmbjYzobwnXvpHbSBjw8aHYuWYitdr33YyoZGeN7q5J4WC.
320
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
IPFS GATEWAY
IPFS gateways are how web users retrieve content on the IPFS network without running their
own IPFS node. IPFS gateways allow web users to fetch content that resides on the IPFS
network using the file’s hash (known as the Content ID, or CID).
The next step is to create the metadata for your NFT. AN NFT metadata file contains
details of the NFT. The minimum attributes needed in the metadata are
{
"name": "My NFT Artwork",
"description": "Mona Lisa",
"image": "https://ipfs.io/ipfs/QmbjYzobwnXvpHbSBjw8aHYuWYitdr33Yyo
ZGeN7q5J4WC"
}
Save the file and upload the JSON file to IPFS. Then, get the URL that points to the
metadata file on IPFS. For this example, the metadata file can be found at https://ipfs.
io/ipfs/QmfJahEinm6rYNfsDPynF3vm5x4xQiE9EnKW6TnadKhdky.
Tip Essentially, the TokenURI in your NFT token contract will point to this NFT
metadata file.
0xAF8b6CA21023A595F0C4919b8B4a9d1F0c1773e7,1,https://ipfs.io/ipfs/
QmfJahEinm6rYNfsDPynF3vm5x4xQiE9EnKW6TnadKhdky
321
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
Note The TokenID is a number that you can specify to uniquely identify the NFT
within the NFT token contract. The TokenURI is usually a URL that points to the
location of the NFT metadata. A common storage for the NFT is IPFS. Alternatively,
the TokenURI may point to an NFT marketplace such as Opensea.
Click the mint button and MetaMask will prompt you to confirm the transaction.
Tip Note that this mint operation can only be performed by the owner of the NFT
token contract, which is the account that deployed it (Account 1 in this case).
322
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
Figure 12-10. Getting the name and symbol of the NFT contract
In this example, you can see that Account 1 currently has one NFT in the NFT token
contract.
323
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
This output shows that the TokenURI of token ID 1 is currently set to https://ipfs.
io/ipfs/QmfJahEinm6rYNfsDPynF3vm5x4xQiE9EnKW6TnadKhdky, which is the path of the
NFT metadata file.
0xAF8b6CA21023A595F0C4919b8B4a9d1F0c1773e7,0x63eE1AEb74c52f09EaB6a2825b
B1918B5e045050,1
Tip Note that transferring of NFT tokens can only be performed by the owner of
the NFT token, which is Account 1 in this case.
324
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
When you click the transferFrom button, MetaMask will prompt you to confirm the
transaction (see Figure 12-15). Note that the image of the NFT is shown in MetaMask.
Figure 12-15. MetaMask displays the image of the NFT when you perform a
transfer of ownership of the NFT
Once the transaction is confirmed, typing the token ID of 1 and then clicking the
ownerOf button will confirm that the NFT now belongs to Account 2 (see Figure 12-16).
325
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
If you click the balanceOf button with the address of Account 1, you will see that
Account 1 no longer holds any NFTs (see Figure 12-17).
Tip Now that Account 2 is the new owner of the NFT token, if Account 2 wishes
to transfer this NFT token to another account (say, Account 3), you need to
switch to Account 2 in MetaMask before clicking the transferFrom button in the
Remix IDE.
326
Chapter 12 Creating Non-Fungible Tokens Using ERC-721
Tip Note that this operation can only be performed by the owner of the NFT
token contract, which is the account that deployed it (Account 1 in this case).
You can verify that the NFT token contract belongs to Account 3 by clicking the
owner button (see Figure 12-19).
Summary
In this chapter, you learned what an NFT is and how it works. Using the ERC-721
contract, you learned how to mint an NFT, transfer its ownership, and verify its
ownership. I hope this chapter has given you a clearer picture of what an NFT is and how
you can create one yourself.
327
CHAPTER 13
Introduction to
Decentralized Finance
The financial system that we are so familiar with today is known as traditional finance.
It is still largely centralized because it still relies on central authorities, predominantly
banks and governments. However, traditional finance has several flaws and I will discuss
them in this chapter.
In Chapter 2, you learned about the motivations behind blockchain and how it solves
the trust issue that people have with central authorities. Using blockchain, we can build
a new financial system known as decentralized finance (DeFi).
In this chapter, I will first compare the differences between traditional finance and
DeFi, and then introduce you to one key component of DeFi: stablecoins. You will also
learn how to build a decentralized exchange (DEX) to exchange tokens from one type to
another.
• Money transfers
• Loans
• Saving plans
• Insurance
• Stock markets
329
© Wei-Meng Lee 2023
W.-M. Lee, Beginning Ethereum Smart Contracts Programming, https://doi.org/10.1007/978-1-4842-9271-6_13
Chapter 13 Introduction to Decentralized Finance
• Some people are not able to create bank accounts and access
financial services. For example, due to sanctions, certain political
figures may not have access to financial services. A good example
is Carrie Lam (former leader of Hong Kong). She has no access to
banking services after the United States imposed sanctions on her in
response to a draconian security law China imposed on the city.
• Money transfers take a long time and cost too much. It typically takes
a few working days to remit money from one country to another and
banks typically charge a significant amount of transaction fees.
Decentralized Finance
With blockchains, we can now have an open and global financial system built for
the Internet age, known as decentralized finance (DeFi). DeFi is an alternative to the
traditional finance system that is opaque, tightly controlled, and built using decades-old
technologies and systems.
With DeFi, we can now
330
Chapter 13 Introduction to Decentralized Finance
Components in DeFi
To make DeFi possible, you need the following key components:
Stablecoins play a very important role in DeFi as they not only replace
fiat currencies; they are an increasingly important asset in the DeFi
lending market, where users can lock their stablecoins in platforms
such as Compound and Aave and earn lending interest rates from
0.15% to 12% APY.
Tip Both Compound and Aave are decentralized cryptocurrency protocols that
allow users to borrow and lend cryptos.
In the next section, you will learn more about stablecoins and the different types that
exist today.
Stablecoins
In Chapter 11, you learned about the ERC-20 specification that allows you to create
your own tokens. You also learned that tokens can be used as a form of investment and
used for paying smart contract services. In this section, you will learn about a specific
implementation of tokens in the real world, commonly known as stablecoins.
331
Chapter 13 Introduction to Decentralized Finance
So, what exactly are stablecoins? Stablecoins are cryptocurrencies where the price is
pegged to a reference asset, such as fiat currency, other cryptocurrencies, or gold. As the
name implies, stablecoins are built to withstand volatility that other cryptocurrencies
can’t tolerate.
To really understand the motivation behind stablecoin, you just need to look at Bitcoin. In 2011,
one BTC (bitcoin) was worth approximately US$1. However, at its peak in 2021, one BTC was
worth more than US$65,000. Imagine paying for two cups of Starbucks coffee with four BTC in
2011. The same four BTC could buy you a Ferrari in 2021!
Apparently, the aim of using Bitcoin as a fiat replacement is not feasible due to its wild
fluctuations in prices!
The following sections will discuss the first three types of stablecoins in more detail.
332
Chapter 13 Introduction to Decentralized Finance
Fiat-Backed Stablecoins
A fiat-backed stablecoin is a stablecoin backed by fiat-currency, such as the US Dollar,
Euro, or Pound. A good example of a fiat-backed stablecoin is the USD Coin (USDC).
To buy a USDC,
333
Chapter 13 Introduction to Decentralized Finance
So why do you want to buy USDC? Well, using USDC you can send money cheaply
and near-instantly anywhere in the world without a traditional bank account (a huge
improvement over wire transfers, which can be expensive and take days). You can
also earn rewards on USDCs held in a Coinbase account. In addition, you can earn
even higher yields by lending your USDC via a variety of decentralized finance (DeFi)
applications.
Besides USDC, some other examples of fiat-backed stablecoins include BUSD (Binance USD),
TUSD (True USD), and USDT (USD Tether).
Crypto-Backed Stablecoins
The next type of stablecoin is crypto-backed stablecoins. Instead of pegging a stablecoin
against a fiat currency, crypto-backed stablecoins are pegged against some cryptos.
A good example of a crypto-backed stablecoin is DAI. DAI is a crypto-backed
stablecoin running on Ethereum that attempts to maintain a value of US$1 per token.
Unlike fiat-backed stablecoins, DAI isn’t backed by US dollars in a bank account.
Instead, it’s backed by crypto collaterals on the Maker DAO platform. Maker DAO is an
organization developing technology for borrowing, saving, and a stable cryptocurrency
on the Ethereum blockchain.
334
Chapter 13 Introduction to Decentralized Finance
A typical organization has board members, and they hold board meetings to discuss
and plan strategic directions for the company. Major decisions made by the board must
be approved by the shareholders, who vote on decisions for the company. Once the votes
have been tallied, the CEO of the company executes the plan.
A DAO, on the other hand, does not have board members. Instead, a DAO is created
using a smart contract. Central to the DAO is the DAO’s token, which is used to manage
membership in the organization and structure within the DAO (Figure 13-3).
335
Chapter 13 Introduction to Decentralized Finance
Each member in the DAO can hold a different amount of tokens, which gives them
voting rights to propose and vote for projects.
336
Chapter 13 Introduction to Decentralized Finance
1. The user goes to the Maker DAO and borrows 10,000 DAI using
$15,000 worth of cryptos (such as Ether, BAT, etc.). These cryptos
will be used as the collateral for the 10,000 DAI borrowed.
3. Maker DAO sends the 10,000 DAI to the user and charges the user
a stability fee.
In this example, the collateral ratio for Ether is 150% (this varies for other cryptos).
This overcollaterialization accounts for the volatility of cryptocurrency.
The price of DAI is kept in check through a system of smart contracts automatically
executing themselves. If the price of DAI fluctuates too far from one dollar, Maker DAO
will adjust the interest fees to stabilize the price of DAI.
Tip You can also buy DAI from all major exchanges like Kraken and Coinbase.
You can use fiat currency to exchange for DAI or sell some of your crypto assets
for DAI.
337
Chapter 13 Introduction to Decentralized Finance
Maker DAO controls the DAI smart contracts, such as accepted collaterals, collateralization
ratios, and interest rates. When DAI dips below US$1, Maker DAO increase interest rates
on the loans. This incentivizes the customers to get rid of their DAI and close the loans. The
returned DAI are then destroyed and this limits the supply and this drives the price of DAI up;
the reverse happens when DAI becomes more expensive than US$1.
All changes to the smart contracts are visible to all blockchain participants, so this is fully
decentralized.
DAI can be used as payment for smart contracts as well as for passive income. You
can put your DAI into a DAI Saving Rate (DSR) program to earn interest.
Besides DAI, some other examples of crypto-backed stablecoins include WBTC (Wrapped
Bitcoin) and MIM (Magic Internet Money).
DAI Liquidation
Since DAI uses cryptos for collateral, and crypto prices fluctuate wildly, what happens
if the price of Ether drops? In this case, Maker DAO will perform a process known as
liquidation. Figure 13-5 shows the formula for liquidation.
338
Chapter 13 Introduction to Decentralized Finance
Let’s work out an example to understand how liquidation works. At the time of
writing, the liquidation ratio is 1.5, which means than if 1 Ether is worth $150 today, it
can be exchanged for 100 DAI. With this, liquidation price would now be (100 DAI * 1.5)
/ 1 Ether = $150.
Tip The liquidation price means that if one Ether falls below $150, the vault
would be closed and collaterals auctioned. The Maker Protocol generates new DAI
through smart contracts known as Maker Vaults.
If 1 Ether falls to, say, $140 (<$150), the vault would be liquidated! It is therefore
advisable not to withdraw all the generated DAI. Assuming that only 90 DAI is
withdrawn, then the liquidation price becomes (90 * 1.5) / 1 = $135. Hence even if Ether
falls to $140, liquidation will not occur.
To prevent liquidation, you can
• Add more collateral: Assuming you add more collateral (e.g., add an
addition 0.5 Ether), the liquidation price would now be (100 * 1.5) /
1.5 = $100. This significantly reduces the liquidation price.
• Repay DAI: Assuming that you now repay 20 DAI (from a loan of 100
DAIs), the liquidation price would now be (80 * 1.5) / 1 = $120. Again,
this significantly reduces the liquidation price.
339
Chapter 13 Introduction to Decentralized Finance
The whole liquidation process is very similar to how a pawn shop works in real life. It goes
like this:
• You bring something valuable (such as gold) to the pawnshop and use it as
collateral.
• The pawn shop loans you money against the collateral. If the gold is worth $15,000,
the pawn shop will give you something like $10,000. This overcollateralization
offers protection to the pawn shop in case the price of gold falls.
• When you repay the loan plus the interest, you get back your collateral.
• If you don’t pay back the loan, the pawn shop keeps the collateral.
Non-Collateralized Stablecoins
Non-collateralized stablecoins, also known as algorithmic stable coins, do not make use
of any reserve asset. Instead, they make use of smart contracts to regulate their prices.
For example, if a coin is trading at too high a value from its intended price, the supply is
increased through minting and then sold on the open market. This supply is increased
until the price returns to $1. Likewise, if the coin is traded too low, the smart contracts
will buy up more coins in the market to reduce the supply.
Crypto Exchanges
With all the discussions about cryptos (coins) and tokens, an important question
remains: how do you buy them? The answer is, through exchanges. A crypto exchange is
a platform on which you can buy and sell cryptocurrencies. There are two types of crypto
exchanges:
340
Chapter 13 Introduction to Decentralized Finance
For most crypto beginners, a CEX provides a user-friendly platform for getting into
the crypto world. Without knowing too much on how cryptos works, a user can buy
cryptos using their credit card for a fixed price (typically). However, CEX has several risks
involved that you should know. First, there is always the risk of credit default (think of
FTX, CoinBene, and Celsius).
Second, CEXs need to comply to KYC (Know Your Customer) and AML (Anti Money
Laundering) regulations, which defeats the idea of using cryptos in the first place
(anonymity). CEXs, as its name implies, are centralized as they are usually regulated by
the governments of countries that they operate it. Finally, CEXs are susceptible to cyber-
attacks and security breaches. As shown in Figure 13-6, when you buy cryptos from
CEXs, your cryptos are stored in wallets maintained by the CEXs. Unless you transfer the
cryptos to your own wallet (such as MetaMask or a hardware wallet), a security breach at
the CEX puts your cryptos at risk as hackers may illegally transfer your cryptos to other
wallets.
341
Chapter 13 Introduction to Decentralized Finance
Figure 13-6. A CEX stores your cryptos, which are subsceptible to cyber attacks
A DEX, on the other hand, does not have the limitations of a CEX. Using smart
contracts, DEXs allow you to exchange tokens easily and quickly from one type to
another. This allows users full flexibility and control over their own funds.
342
Chapter 13 Introduction to Decentralized Finance
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/
contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
This contract is the same basic ERC-20 token contract you saw in Chapter 11.
"WML token","WML"
343
Chapter 13 Introduction to Decentralized Finance
This creates the WML token. Next, use Account 2 and deploy the same token
contract, this time with the following constructor argument:
"LWM token","LWM"
This creates the LWM token. At this moment, the Remix IDE should have the token
contracts deployed as shown in Figure 13-8.
Add both tokens to Account 1 and Account 2 in MetaMask. At this moment, both
Account 1 and Account 2 should have the token balances shown in Figure 13-9.
344
Chapter 13 Introduction to Decentralized Finance
345
Chapter 13 Introduction to Decentralized Finance
3. The DEX contract needs to verify that the sender has authorized
the transfer of WML tokens to it. If this is true, the WML tokens are
transferred from the sender to the DEX contract.
Tip All transfers of tokens involve the token contract updating the amount of
tokens held by users and the DEX contract.
346
Chapter 13 Introduction to Decentralized Finance
In the Remix IDE, create a new contract named DEX.sol and populate it with the
following statements:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/
contracts/token/ERC20/ERC20.sol";
contract DEX {
ERC20 WML_token;
ERC20 LWM_token;
//=======================
// constructor of the DEX
//=======================
constructor() payable{
//---be sure to replace the following addresses with your own---
WML_token = ERC20(address(
0xfa8b8F0fd75ABf2aF088bf2D1115E6F97ED0cB3a));
LWM_token = ERC20(address(
0x234273bD4D1aa3D233135dD49A26675dA7eF6A9a));
}
//===================================
// find the tokens balance in the DEX
//===================================
function getBalance() public view returns (uint256,uint256) {
return (WML_token.balanceOf(address(this)),
LWM_token.balanceOf(address(this)));
}
//================
347
Chapter 13 Introduction to Decentralized Finance
//=========
// CHECK #1
//=========
// ensure the amount to convert is > 0
require(amount > 0, "You need to convert at least some tokens");
// obtain the allowance set by the sender to send to this DEX
uint256 approvedAmt;
approvedAmt = from.allowance(msg.sender, address(this));
//=========
// CHECK #2
//=========
// ensure the sender has enough tokens approved to convert
348
Chapter 13 Introduction to Decentralized Finance
//=========
// CHECK #3
//=========
// get the balance of tokens (that the sender wants to convert to)
in the pool
uint256 dexBalance = to.balanceOf(address(this));
// need to check that DEX has enough "to" token to send to sender
require(amount <= dexBalance,
"Sorry, not enough tokens in the DEX");
• You need to import the base definition of the ERC-20 token contract
written by OpenZeppelin.
• In the constructor, you create instances of the two tokens you are
swapping (both instances of ERC20 tokens). If you are trying this out,
remember to replace the contract addresses for the WML and LWM
tokens with your own.
349
Chapter 13 Introduction to Decentralized Finance
• There are a number of checks you need to perform before you can
exchange the tokens. First, ensure that the amount to swap is more
than 0. Second, ensure that the account performing the swap has
approved the tokens (that they are converting from) to be sent to the
DEX. The final check is to ensure that the DEX has sufficient tokens
for swapping.
• Once all the checks have passed, the DEX can transfer the tokens
from the caller to the DEX, followed by transferring the token (that
the caller wants to swap into) to the caller.
You can now deploy the DEX contract (using Account 1 or Account 2 does not
matter).
At this point, the Remix IDE should have two token contracts and one DEX contract
deployed, as shown in Figure 13-11.
Expand the DEX contract and click the getBalance button (see Figure 13-12). You
should see two 0s returned. This is because at this moment the DEX contract has 0 WML
and 0 LWM tokens.
350
Chapter 13 Introduction to Decentralized Finance
351
Chapter 13 Introduction to Decentralized Finance
Figure 13-13. Funding the DEX contract with 500 WML tokens
Next, use Account 2 and send the DEX contract 500 LWM tokens (see Figure 13-14).
352
Chapter 13 Introduction to Decentralized Finance
Figure 13-14. Funding the DEX contract with 500 LWM tokens
Once the transactions are confirmed, the DEX contract has 500 WML tokens and 500
LWM tokens. Table 13-1 shows the current token balances for Account 1, Account 2, and
the DEX contract.
Table 13-1. The Balances for the Two Accounts and the DEX Contract
Balance Account 1 Account 2 DEX
353
Chapter 13 Introduction to Decentralized Finance
The first step that Account 1 needs to do is to go to the first deployed token contract
(WML token) and fill in the following (see Figure 13-15) and click the approve button:
0xF4d9A3b468FBc0b256Da59B5B40CB20e5eD137c6,100000000000000000000
This statement grants the DEX contract permission to transfer up to 100 WML tokens
from Account 1 to the DEX contract.
MetaMask will display a popup asking you to confirm (see Figure 13-16). Click
Confirm.
354
Chapter 13 Introduction to Decentralized Finance
Figure 13-16. Confirming permission to let the DEX contract access the
WML tokens
Once the transaction is confirmed, Account 1 is ready to send 100 WML tokens to the
DEX contract. Fill in the following statement next to the exchange button for the DEX
contract (see Figure 13-17): "WML","LWM",100000000000000000000.
355
Chapter 13 Introduction to Decentralized Finance
356
Chapter 13 Introduction to Decentralized Finance
If you go to the DEX contract and click the getBalance button, you will that it has 600
WML tokens and 400 LWM tokens.
357
Chapter 13 Introduction to Decentralized Finance
Figure 13-19. The DEX contract now has 100 more WML tokens and 100 fewer
LWM tokens
Table 13-2 shows the updated current token balances for Account 1, Account 2, and
the DEX contract.
Table 13-2. The Balances for the Two Accounts and the DEX Contract
Balance Account 1 Account 2 DEX
Likewise, if Account 2 wants to exchange 100 LWM tokens for 100 WML, they need to
do the following:
358
Chapter 13 Introduction to Decentralized Finance
S
ummary
In this chapter, you explored a few important topics:
• You learned what stablecoins are, the different types, and how they
work to maintain price stability
• You learned how to buy cryptos through the different types of crypto
exchanges.
• More importantly, you learn how to implement a DEX using a smart
contract.
359
Index
A Bitcoin’s implementation, 37
32-bit instructions, 41
add_node() member, 69
Blockchain, 1
add_transaction() method, 59
block, 43
admin.addPeer() function, 99, 100
block header, 43
admin.peers property, 100, 102
blocks confirmations, 35
Algorithmic stable coins, 340
centralized databases and
announceWinners() function, 249, 250
institutions, 30
Anti Money Laundering (AML), 341
chaining, 31–34
append_block() method, 58
components, 52
Application binary interface (ABI), 148,
consensus protocols, 35–37
158–162, 175–177, 194,
decentralized database, 30
230, 260–262
distributed ledger, 29
Application-specific integrated
genesis block, 31
circuits (ASICs), 41
immutability, 34, 35
--authrpc.port option, 97
merkle root, 47, 48
Auto compile option, 152
merkle tree, 47
Avalanche effect, 4
PoS, 41–43
PoW, 37–41
B testing, 62–66
base64-encoded JSON string, 173, 177, transactions and timestamp, 30
179, 185, 188 types of nodes, 44–46
base64-encoded string, 173, 177, 179, Blockchain class, 59, 60, 68–71
185, 188 Blockchain network, 20, 44, 45, 67,
base64 encoding, 17, 168, 172, 178, 231 68, 76, 87
Baseball card, 312 blockchain.py, 56, 60, 62, 72
bet() function, 246, 247 Brew, 88, 89
Binance USD (BUSD), 334 Broadcasting, 37, 40
BIP 39 (Bitcoin Improvement Proposals) buildTransaction() function, 233
seed phrase, 117 Bytecode, 148, 158–162, 175
Bitcoin, 23, 40, 41, 332 bytes() function, 6
361
© Wei-Meng Lee 2023
W.-M. Lee, Beginning Ethereum Smart Contracts Programming, https://doi.org/10.1007/978-1-4842-9271-6
INDEX
C DEXs
address, 350
Caesar Cipher, 2
approve() function, 346
Calculator, 150, 151, 156
creation, 342
calculator.sol, 149
details, 349, 350
calldata keyword, 171, 172
DEX.sol, 347–349
call() function, 213, 214, 239
events, 345, 346
cashOut() function, 192
exchange tokens, 342
Centralized exchanges (CEX), 341
LWM tokens, 352, 353
checkEduCredentials button, 179,
Remix IDE, 350
185, 186
token balances, 350, 351, 353
checkEduCredentials() function, 171, 173,
WML tokens, 351, 352
182, 188, 190, 213, 214, 292
swapping
Chrome Web Store, 113
approve button, 354
Ciphertext, 2, 7, 9, 11, 12, 14
conversion, 355, 356
compareStrings() function, 349
exchange() function, 356
Conceptual blockchain
LWM tokens, 356–358
implementation, Python
permission, 354, 355
adding transactions, 59, 61, 62
token balances, 358
appending block, 58, 59
WML tokens, 356–358
exposing Blockchain class,
token contracts
REST API, 60
creation, 343
class declaration, 56, 57
deployment, 343–345
code, 78
types, 340, 341
finding nonce, 57, 58
Cryptographic algorithms, 1, 2, 19, 21
importing modules and libraries, 56
Cryptographic hash, 34, 47
installing flask, 55
Cryptography module, 8, 12
obtaining full blockchain, 60
Cryptography
obtaining nonce, 53, 54
asymmetric (see Asymmetric
performing mining, 60, 61
cryptography)
Consensus protocols, 35–37, 41
Caesar Cipher, 2
contract keyword, 151
ciphertext, 2
Crypto beginners, 341
definition, 1
Cryptocurrencies, 23, 24, 26, 34, 41, 45, 62,
hashing, 3–6
117, 279–281, 331–334, 337,
symmetric cryptography, 7–9
340, 341
types, 2
Crypto exchanges
Cryptography in blockchain
CEXs, 341, 342
cryptographic algorithms, 19
362
INDEX
363
INDEX
364
INDEX
365
INDEX
366
INDEX
367
INDEX
368
INDEX
369
INDEX
370