Interacting with Smart Contracts from Web Apps

Building web apps that work with Ethereum Smart Contracts

Feature Image

Hi there! Today we are covering how to build web applications capable of interacting with Ethereum Smart Contracts. This interaction is fascinating as it will open a new world of possibilities for web developers who want to build apps (dapps) around blockchain.

During this tutorial, we will build a tiny Smart Contract to store and retrieve data on the Ethereum blockchain, and we will create a web application that will allow us to access and change the data on the Smart Contract.

The content is available in two formats, a video explanation that covers the full article, you can watch it here:

And this article covers the same topics in a written format. The video is complete, meaning that you don’t need any special knowledge on the blockchain. However, I recommend checking out my previous article on building and deploying ERC20 smart contracts .


The Smart Contract

Let’s get started by presenting the smart contract which we will use for building our web applications. Since the article focused on connecting JavaScript to the blockchain, I kept the contract as simple as possible.

pragma solidity ^0.6.6;

contract CoolNumberContract {
    uint public coolNumber = 10;
    
    function setCoolNumber(uint _coolNumber) public {
        coolNumber = _coolNumber;
    }
}

Perhaps it is not the most impressive contract ever but will do for now. If you are not sure what this contract does, let’s explain it.

The contract CoolNumberContract stores a variable on the blockchain called coolNumber with an initial value of 10. This variable is public, meaning that we can access its value from the blockchain without building a getter function.

Additionally, the contract contains a public function called setCoolNumber, which, as you probably guessed, it will change the value of the variable on the blockchain. Something truly important to remember here is that any change in the blockchain data needs to be represented by a transaction. Meaning that calling the method setCoolNumber will require a transaction, and that transaction will have a gas fee associated with it.

Make sure you deploy the contract to a test network before moving forward.


Setting up the project and the dependencies

Here is where the fun begins. To interact with any Ethereum blockchain from JavaScript, you will need a library, and in our case, we will be using web3 . Web3 will allow us to interact with any Ethereum network through MetaMask or a web3 provider like Ganache.

Let’s start a new project; you can use any framework you want; I’ll go with vanilla JavaScript and HTML, but you can use any framework like React or Vue. All my code will go into one file, index.html and I’ll start with the following structure:

<!DOCTYPE html>
<html>

<head>
    <meta charset='utf-8'>
    <meta http-equiv='X-UA-Compatible' content='IE=edge'>
    <title>Web 3 Demo</title>
    <meta name='viewport' content='width=device-width, initial-scale=1'>

    <script src='node_modules/web3/dist/web3.min.js'></script>
</head>

<body>

    Web 3 Demo
    <br >
    <button onclick="printCoolNumber();">Print Cool Number</button>
    <button onclick="changeCoolNumber();">Change Cool Number</button>
    <br /><br />
    Status: <span id="status">Loading...</span>

    <script type="text/javascript">
        
    </script>
</body>

</html>

Let’s break the code above, and let’s get started with the body tag. It’s a simple UI with two buttons and a span representing a status. Both buttons call JavaScript functions, which for now are undefined.

On the head tag, the important tag is the script we are importing. That’s our dependency on web3. You can add this dependency to your code as I did or if you are using a framework, you can probably simply import the package with:

import Web3 from web3;

If you don’t have the library yet installed, you can do that through NPM:

npm install web3

Last but not least, before you continue, I strongly recommend that you install the MetaMask extension. If you prefer to use any other provider, you may have to change parts of the code accordingly, as the samples provided use the injected web3 provider by MetaMask.


Connecting your web application to the Ethereum blockchain

Now that we have our basic structure and dependencies ready, we can add the code that connects our application to the blockchain.

Inside the script tag on the body, we will add:

async function loadWeb3() {
    if (window.ethereum) {
        window.web3 = new Web3(window.ethereum);
        window.ethereum.enable();
    }
}

async function load() {
    await loadWeb3();
    updateStatus('Ready!');
}

function updateStatus(status) {
    const statusEl = document.getElementById('status');
    statusEl.innerHTML = status;
    console.log(status);
}

load();

All the code above is pretty straightforward except for the function loadWeb3, which we will explain further. This function is responsible for establishing the connection and authorizing us to interact with the blockchain.

To work with our Smart Contract, we will need a new instance of Web3. When creating this instance, we need to specify the provider we want to use. Since we are using MetaMask as a proxy, we use the window.ethereum provider injected by the MetaMask extension.

If you now go to your browser and load the page (by file or web browser), you will see the MetaMask authorization flow. It should look something like this:

Authorize your app to connect through MetaMask

Authorize your app to connect through MetaMask

Make sure to accept and connect the wallet to your application to continue.


Accessing the Smart Contract

So far, your code has access to interact with the blockchain; let’s now make sure your app can talk with the Smart Contract.

For that, we will create a new function to create a contract instance matching your contract interface.

async function loadContract() {
    return await new window.web3.eth.Contract(ABI, contractAddress);
}

To get an instance of any contract on the blockchain, all we need are two things. The ABI specification of the contract and the contract address, both of which you can extract from Remix.

To get the ABI specification of your contract, go to Remix on the Compile tab, make sure to compile, and then click on ABI.

Copy the ABI specification of your contract

Copy the ABI specification of your contract

This button will copy the ABI specification for your contract as a JSON array on your clipboard, which we can use directly as part of our first parameter.

The second parameter is the deployed contract address, which you can get from Remix at the moment of deployment or Etherscan.

Copy the contract address from Remix

Copy the contract address from Remix

Copy the contract address from Etherscan

Copy the contract address from Etherscan

Here is how the complete function code looks like for my contract:

async function loadContract() {
    return await new window.web3.eth.Contract([
        {
            "inputs": [],
            "name": "coolNumber",
            "outputs": [
                {
                    "internalType": "uint256",
                    "name": "",
                    "type": "uint256"
                }
            ],
            "stateMutability": "view",
            "type": "function"
        },
        {
            "inputs": [
                {
                    "internalType": "uint256",
                    "name": "_coolNumber",
                    "type": "uint256"
                }
            ],
            "name": "setCoolNumber",
            "outputs": [],
            "stateMutability": "nonpayable",
            "type": "function"
        }
    ], '0x5F4a8C71AFB0c01BA741106d418E78888607Ee63');
}

After we have that done, we can simply call loadContract from our loader function:

async function load() {
    await loadWeb3();
    window.contract = await loadContract();
    updateStatus('Ready!');
}

Reading values from the Smart Contract

We are ready to start calling the Smart Contract functions, and we will start by retrieving our coolNumber from the contract.

We can retrieve data from the contract very quickly thanks to web3; here is an example to get the value of the public variable coolNumber.

async function printCoolNumber() {
    updateStatus('fetching Cool Number...');
    const coolNumber = await window.contract.methods.coolNumber().call();
    updateStatus(`coolNumber: ${coolNumber}`);
}

Super easy! We use the contract instance from the previous section, we get the methods and call a function with the variable name (this is the getter I mentioned at the beginning), and finally, we use call to start the remote request.


Updating values to the Smart Contract

Lastly, we need to make sure we can also transact with the Smart Contract, and for that, we will show an example by accessing our setter function to change the coolNumber stored in the contract.

Our change function will simply assign a new random number and save it on the blockchain:

async function changeCoolNumber() {
    const value = Math.floor(Math.random() * 100);
    updateStatus(`Updating coolNumber with ${value}`);
    const account = await getCurrentAccount();
    const coolNumber = await window.contract.methods.setCoolNumber(value).send({ from: account });
    updateStatus('Updated.');
}

There are two things I want to highlight here. First, we refer to a getCurrentAccount() function, which is undefined for now; we will work on that a bit later. Second is how we call our setter. If you pay attention to the line where we call the setCoolNumber method from the contract, it looks slightly different from what we did for the caller.

const coolNumber = await window.contract.methods.setCoolNumber(value).send({ from: account });

Instead of using call we are using the send method. We need to specify the sender account. Why? It turns out that we need a transaction for changing values on the blockchain. With that said, a transaction requires a from and to account to be valid, from being who originated the transaction and the to, in this case, being the Smart Contract address.

Can we use any account as from value? No, it has to be an account you have access to (and in our case, registered on your MetaMask wallet) as you will need to authorize the transaction and confirm the gas fee.

Now that we cleared that out, let’s build the getCurrentAccount() method:

async function getCurrentAccount() {
    const accounts = await window.web3.eth.getAccounts();
    return accounts[0];
}

Web3 is excellent. We can interact with the blockchain and our wallet, so it is possible through Web3 to request information about the accounts registered on the wallet. In our sample, we simply get them all and use the first account to make the transactions.


Putting it all together

If you completed all the functions, your code should look something like mine. I’ve uploaded my code to a GitHub Gist so that you can compare it with yours or use mine to play around.

All my contact details are in the code, and you can use it as long as you connect your MetaMask to Ropsten and have some test ETH to pay for transaction fees.

When testing, you should see something like:

Application Flow

Application Flow


Conclusion

Blockchain is a topic that fascinates me, and building this tutorial was super fun; I learned a lot while doing this, and it’s the basis for more projects, articles, and videos.

If there’s a topic on smart contract development that you want me to cover, please let me know in the comments section.

Thanks for reading!

Join the Free Newsletter

A free, weekly e-mail with the best new articles, courses, and special bonuses.

We won't send you spam. Unsubscribe at any time.