Ethereum Dapp Tutorial, Part 2 of 3: Web Front-End with Metamask Integration

in #tutorial6 years ago (edited)

Note: We highly recommend viewing this tutorial in Light Mode. Steemit does not support dark themes for inline and code blocks, making this page difficult to read in Night Mode.


In this tutorial series, we will be creating a dapp for Ethereum. It will be broken into three parts:

  1. The Smart Contract
  2. Web front-end with Metamask integration (this part)
  3. Ledger integration

We’ll be creating a simple dapp called ‘Message of the Moment,’ which will display a message that anyone is welcome to change.

Full source code for Part 2 is at the bottom of the post.


Part 2: Web Front-End with Metamask Integration


This tutorial will utilize the following references and resources. Don’t worry about collecting them all right now; we’ll refer to them inline as we use them.

Setting Up the Environment

We'll be using the NPM package manager, Node.js, and the Webpack dev server.

1. Install NPM and Node.js.

Metamask will only communicate with websites using https. We’ll be using Node.js as our web server so we can test https locally.


2. Create a folder for your dapp and a directory inside named src.

Directory structure


3. Create an empty file in the src directory named index.js.

This file, src/index.js, is required in order to start the Webpack dev server. We only need the most basic support from Webpack, so this file is going to remain empty for this tutorial.


4. Install Webpack.

Open a command prompt. Navigate to your dapp's directory and run the following:

npm install webpack webpack-cli webpack-dev-server --save-dev

npm

NPM is a package manager for Javascript. Its purpose is to make retrieving dependencies for your project, such as Webpack, easy.

install

The install command in NPM will download the specified component and save it in the current directory. This is something you would repeat per project.

webpack webpack-cli webpack-dev-server

Each space-delimited statement here refers to a component of Webpack, which we will be using as our local server for development and testing.

--save-dev

--save-dev tells NPM to make the packages available in development mode, but not in production. The dapp we are creating will have no server-side dependencies.


5. Launch the Webpack dev server.

npx webpack-dev-server --https

npx

npx is the command used to execute an NPM package, in this case, the webpack-dev-server.

--https

The --https option specified here will generate a certificate automatically, making testing easy.


6. Open https://localhost:8080 in your browser.

You can click past the scary warnings by selecting 'Advanced' followed by 'Proceed to localhost (unsafe)'. This warning message will only appear when testing locally. Once the app is deployed, users will not see this.

Create the Dapp’s Webpage

7. Create index.html in the dapp's directory.

Add the following code to create a basic website template with jQuery included:

<html>
    <head>
      <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
      <script>
      </script>
    </head>
    <body>
    </body>
</html>


8. Add a div to display the message.

<body>
  <div id="message"></div>
</body>


9. Add the smart contract’s address from Remix.

We will need the contract's address. Assign it as a string to a const variable named contract_address between the empty <script></script> tags in our template.

Note: If your dapp supports both testnet and mainnet, you will need a way of selecting the correct address for the specified network.

We will be using the Ropsten testnet for this; however, you may also consider using Ganache to test locally instead.

<script>
  const contract_address = "0x654b54c945d29981d597fc8756cdb3c6e372440c";
</script>


10. Copy the ABI from Remix.

The ABI, or Application Binary Interface, describes the API supported by the contract. We use this to call methods or read data types by name.

To get the abi, click the ‘Compile’ tab and select ‘Details’. Then click the copy icon next to ‘ABI’.

Remix

Paste this as a const variable named abi under the contract_address.

const abi = [
    {
        "anonymous": false,
        "inputs": [
    ...
    }
];

The ABI may be quite long. You might want to save this in a separate file for readability.

11. Create a contract object.

Metamask automatically adds an object named web3 to the page before the load event completes. On load, we leverage the web3 object to create a contract object using the abi and contract_address defined above.

If the web3 object is not found, it is because the user does not have Metamask installed. We will address this scenario in Part 3.

let contract;
window.addEventListener('load', () => {
      if(typeof(web3) === 'undefined') {
        return console.log("Metamask is not installed.");
      }
      contract = web3.eth.contract(abi).at(contract_address);
});


12. Read the current message.

After the contract object has been created, we can use it to read data, call methods, and post transactions through Metamask. Here we are reading message, which is a string in the contract.

window.addEventListener('load', () => {
    if(typeof(web3) === 'undefined') {
        return console.log("Metamask is not installed.");
    }
    contract = web3.eth.contract(abi).at(contract_address);

    contract.message.call((error, result) => {
        if(error) {
            return console.log(error);
        }
        $('#message').text(result);
    });
});

call

call is a web3.js function which will read a data type or result from a read-only method. This will not create a transaction, does not cost gas, and returns very quickly.


13. Add a form allowing users to change the message.

New Message: <input type="text" id="new_message" />
<button onclick="setMessage()">Set Message</button>


14. Create the setMessage function for the form to call.

function setMessage() {
    let message = $('#new_message').val();
    contract.setMessage.sendTransaction(
        message, 
        {gasPrice: web3.toWei(4.1, 'Gwei')}, 
        (error, result) => {
            if(error) {
                return console.log(error);
            }
            console.log("txhash: " + result); 
        }
    );
}

$('#new_message').val()

Returns the value in the textbox using jQuery.

sendTransaction(params, options, callback)

sendTransaction is a web3.js function which will cause Metamask to prompt the user for confirmation. We are passing one parameter in, the new message. The optionsparameter is, well, optional; here, we use it to get a nice default on the gas price of 4.1 Gwei, which must be converted to Wei for the API call.

The callback will return the transaction’s hash code and/or any error messages. You can use the transaction’s hash to monitor its status and automatically refresh the page as appropriate. This is out of scope for our tutorial, but as a next step consider Web3.js's getTransactionReceipt function.

Test and Deploy


15. Using Chrome's Developer Tools

Chrome offers some useful tools when debugging. Open the Developer Tools by right-clicking on the page and selecting 'Inspect.' You should see a navigation bar at the top of the new dialog window that looks like this:

Chrome Dev Tools

The 'Console' tab will display diagnostic information as your code runs, including any error messages. You can also use the 'Sources' tab to add a breakpoint by clicking a line number in your code. This will pause the site on that line and allow you to mouse over data for more information and to step through the logic.


16. Deploy to GitHub

Obviously, there are tons of hosting options. I like GitHub as a free and easy way to host a website, and it has the https support required for our dapp.

Create a repository on GitHub and upload the entire project directory (or just your index.html file and any other dependencies that you may have added, such as images). If you're unfamiliar with Github, check out how to create a repository using the GUI or from the command line.

Open the repository 'Settings' page.

Github GUI

Find the 'GitHub Pages' dialog. Under 'Source,' select 'master branch' and hit 'Save'.

Github GUI

The URL for your site will be generated. It will take a few minutes before your content is available.



That’s it! Hope this was helpful. In Part 3 we will be adding support for the Ledger Nano S hardware wallet and addressing some of the corner cases, including message display for users who do not have either Metamask or a Ledger.



Source Code - Part 2

<html>
<head>
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
  <script>
    const contract_address = "0x654b54c945d29981d597fc8756cdb3c6e372440c";
    const abi = [
    {
        "anonymous": false,
        "inputs": [
            {
                "indexed": true,
                "name": "previousOwner",
                "type": "address"
            }
        ],
        "name": "OwnershipRenounced",
        "type": "event"
    },
    {
        "anonymous": false,
        "inputs": [
            {
                "indexed": true,
                "name": "previousOwner",
                "type": "address"
            },
            {
                "indexed": true,
                "name": "newOwner",
                "type": "address"
            }
        ],
        "name": "OwnershipTransferred",
        "type": "event"
    },
    {
        "constant": false,
        "inputs": [],
        "name": "renounceOwnership",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_maxLength",
                "type": "uint256"
            }
        ],
        "name": "setMaxLength",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_message",
                "type": "string"
            }
        ],
        "name": "setMessage",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_newOwner",
                "type": "address"
            }
        ],
        "name": "transferOwnership",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "inputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "constructor"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "maxLength",
        "outputs": [
            {
                "name": "",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "message",
        "outputs": [
            {
                "name": "",
                "type": "string"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "owner",
        "outputs": [
            {
                "name": "",
                "type": "address"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    }
];
    

    let contract;
    window.addEventListener('load', () => {
      if(typeof(web3) === 'undefined') {
          return console.log("Metamask is not installed");
      }
      contract = web3.eth.contract(abi).at(contract_address);
      contract.message.call((error, result) => {
          if(error) {
              return console.log(error);
          }
          $('#message').text(result);
      });
    });

    function setMessage() {
      let message = $('#new_message').val();
      contract.setMessage.sendTransaction(
        message, 
        {gasPrice: web3.toWei(4.1, 'Gwei')}, 
        (error, result) => {
            if(error) {
                return console.log(error);
            }
            console.log("txhash: " + result); 
        }
      );
    }
  </script>
</head>
<body>
  <div id="message"></div>

  <br>
  New Message: <input type="text" id="new_message" />
  <button onclick="setMessage()">Set Message</button>
</body>
</html>

Coin Marketplace

STEEM 0.19
TRX 0.16
JST 0.033
BTC 64192.94
ETH 2764.54
USDT 1.00
SBD 2.65