Ethereum DApps: Building a Web3 UI for a DAO Contract


In part 6 of this tutorial series on building DApps with Ethereum, we took the DAO towards completion by adding voting, blacklisting/unblacklisting, and dividend distribution and withdrawal, while throwing in some additional helper functions for good measure. In this tutorial, we’ll build a web interface for interacting with our story, as we otherwise can’t count on any user engagement. So this is the final part of our story before we launch it into the wild.

Since this isn’t a web application tutorial, we’ll keep things extremely simple. The code below is not production-ready, and is meant to serve only as a proof of concept on how to connect JavaScript to the blockchain. But first, let’s add a new migration.

Automating Transfers

Right now as we deploy our token and DAO, they sit on the blockchain but don’t interact. To test what we’ve built, we need to manually transfer token ownership and balance to the DAO, which can be tedious during testing.

Let’s write a new migration which does this for us. Create the file 4_configure_relationship.js and put the following content in there:

var Migrations = artifacts.require("./Migrations.sol");
var StoryDao = artifacts.require("./StoryDao.sol");
var TNSToken = artifacts.require("./TNSToken.sol");

var storyInstance, tokenInstance;

module.exports = function (deployer, network, accounts) {

    deployer.then(function () {
            return TNSToken.deployed();
        }).then(function (tIns) {
            tokenInstance = tIns;
            return StoryDao.deployed();
        }).then(function (sIns) {
            storyInstance = sIns;
            return balance = tokenInstance.totalSupply();
        }).then(function (bal) {
            return tokenInstance.transfer(storyInstance.address, bal);
        })
        .then(function (something) {
            return tokenInstance.transferOwnership(storyInstance.address);
        });
}

Here’s what this code does. First, you’ll notice it’s promise-based. It’s full of then calls. This is because we depend on a function returning some data before we call the next one. All contract calls are promise-based, meaning they don’t return data immediately because Truffle needs to ask the node for information, so a promise to return data at a future time is made. We force the code to wait for this data by using then and providing all then calls with callback functions which get called with this result when it’s finally given.

So, in order:

  • first, ask the node for the address of the deployed token and return it
  • then, accepting this data, save it into a global variable and ask for the address of the deployed DAO and return it
  • then, accepting this data, save it into a global variable and ask for the balance the owner of the token contract will have in their account, which is technically the total supply, and return this data
  • then, once you get this balance, use it to call the transfer function of this token and send tokens to the DAO’s address and return the result
  • then, ignore the returned result — we just wanted to know when it’s done — and finally transfer ownership of the token to the DAO’s address, returning the data but not discarding it.

Running truffle migrate --reset should now produce an output like this:

Truffle migrate

The Front End

The front end is a regular, static HTML page with some JavaScript thrown in for communicating with the blockchain and some CSS to make things less ugly.

Let’s create a file index.html in the subfolder public and give it the following content:

<!DOCTYPE HTML>

<html lang="en">
<head>
    <title>The Neverending Story</title>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <meta name="description" content="The Neverending Story is an community curated and moderated Ethereum dapp-story">
    <link rel="stylesheet" href="assets/css/main.css"/>
</head>
<body>

    <div class="grid-container">
        <div class="header container">
            <h1>The Neverending Story</h1>
            <p>A story on the Ethereum blockchain, community curated and moderated through a Decentralized Autonomous Organization (DAO)</p>
        </div>
        <div class="content container">
            <div class="intro">
                <h3>Chapter 0</h3>
                <p class="intro">It's a rainy night in central London.</p>
            </div>
            <hr>
            <div class="content-submissions">
                <div class="submission">
                    <div class="submission-body">This is an example submission. A proposal for its deletion has been submitted.</div>
                    <div class="submission-submitter">0xbE2B28F870336B4eAA0aCc73cE02757fcC428dC9</div>
                    <div class="submission-actions">
                        <div class="deletionproposed" data-votes="3024" data-deadline="1531607200"></div>
                    </div>
                </div>
                <div class="submission">
                        <div class="submission-body">This is a long submission. It has over 244 characters, just we can see what it looks like when rendered in the UI. We need to make sure it doesn't break anything and the layout also needs to be maintained, not clashing with actions/buttons etc.</div>
                        <div class="submission-submitter">0xbE2B28F870336B4eAA0aCc73cE02757fcC428dC9</div>
                        <div class="submission-actions">
                            <div class="delete"></div>
                        </div>
                </div>
                <div class="submission">
                        <div class="submission-body">This is an example submission. A proposal for its deletion has been submitted but is looking like it'll be rejected.</div>
                        <div class="submission-submitter">0xbE2B28F870336B4eAA0aCc73cE02757fcC428dC9</div>
                        <div class="submission-actions">
                            <div class="deletionproposed" data-votes="-790024" data-deadline="1531607200"></div>
                        </div>
                </div>
            </div>    
        </div>
        <div class="events container">
            <h3>Latest Events</h3>
            <ul class="eventlist">

            </ul>
        </div>
        <div class="information container">
            <p>Logged in / out</p>
            <div class="avatar">
                <img src="http://placeholder.pics/svg/200/DEDEDE/555555/avatar" alt="avatar">
            </div>
            <dl>
                <dt>Contributions</dt>
                <dd>0</dd>
                <dt>Deletions</dt>
                <dd>0</dd>
                <dt>Tokens</dt>
                <dd>0</dd>
                <dt>Proposals submitted</dt>
                <dd>0</dd>
                <dt>Proposals voted on</dt>
                <dd>0</dd>
            </dl>
        </div>
    </div>

<script src="assets/js/web3.min.js"></script>
<script src="assets/js/app.js"></script>
<script src="assets/js/main.js"></script>

</body>
</html>

Note: this is a really really basic skeleton, just to demo integration. Please don’t rely on this being the final product!

It’s possible that you’re missing the dist folder in the web3 folder. The software is still beta, so minor slip-ups are still possible there. To get around this and install web3 with the dist folder, run npm install ethereum/web3.js --save.

For CSS, let’s put something rudimentary into public/assets/css/main.css:

@supports (grid-area: auto) {
    .grid-container{
      display: grid;
      grid-template-columns: 6fr 5fr 4fr;
      grid-template-rows: 10rem ;
      grid-column-gap: 0.5rem;
      grid-row-gap: 0.5rem;
      justify-items: stretch;
      align-items: stretch;
      grid-template-areas:
      "header header information"
      "content events information";
      height: 100vh;
    }
    .events {
      grid-area: events;
    }
    .content {
      grid-area: content;
    }
    .information {
      grid-area: information;
    }
    .header {
      grid-area: header;
      text-align: center;
    }

    .container {
        border: 1px solid black;
        padding: 15px;
        overflow-y: scroll;
    }

    p {
        margin: 0;
    }
  }

body {
    padding: 0;
    margin: 0;
    font-family: sans-serif;
}

Then as JS we’ll start with this in public/assets/js/app.js:

var Web3 = require('web3');

var web3 = new Web3(web3.currentProvider);
console.log(web3);

What’s going on here?

Since we agreed that we’ll assume all our users will have MetaMask installed, and MetaMask injects its own instance of Web3 into the DOM of any visited web page, we basically have access to the “wallet provider” from MetaMask right in our website. Indeed, if we log in to MetaMask while the page is open, we’ll see this in the console:

MetaMask provider is active

Notice how the MetamaskInpageProvider is active. In fact, if we type web3.eth.accounts into the console, all the accounts we have access to through MetaMask will be printed out:

Account is printed into the console

This particular account is, however, one that’s added to my own personal Metamask by default and as such will have a balance of 0 eth. It’s not part of our running Ganache or PoA chain:

An empty account

Notice how asking for the balance of our active (MetaMasked) account yields 0, while asking for balance of one of our private blockchain accounts yields 100 ether (in my case it’s Ganache, so all accounts are initialized with 100 ether).

The post Ethereum DApps: Building a Web3 UI for a DAO Contract appeared first on SitePoint.



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *