Ethereum Tutorials

SMART CONTRACTS

"Duke" Web UI: Final Version

As our "Duke" contract have changed again, we need to reflect the changes in its Web UI. In the following HTML, a table with separate countries was added. Buttons to Create/Destroy a country were added, together with text fields for amounts and the Java Script logic for enabling/disabling/verifying controls.

Note that all this verification is for users' convenience only, as we MUST expect some malitious input from the users, and we have to handle it in the contract, not in Java Script.

A chartist.js library is used to create price chart, and this chart is now capable of displaying multiple countries.

Final code of "Duke of Ether" Web UI:

<!DOCTYPE html>
<html>
<head>
    <title>Duke of Ether!</title>

    <script src="https://cdn.rawgit.com/ethereum/web3.js/develop/dist/web3.js"></script>
    <script src="https://snowcron.com/jquery-2.1.1.min.js"></script>
    
    <script src="lib/moment.min.js"></script>
     <link rel="stylesheet" href="lib/chartist.min.css">
    <script src="lib/chartist.min.js"></script>
    
    <link rel="stylesheet" type="text/css" href="http://snowcron.com/styles_new.css">
    
<!-- replacer_ethereum_show_hide -->
    <script>
        function show_hide(btn_id, id, strLabel) 
        {
            var x = $(id);
            if(!x.is(':visible')) 
            {
                if(strLabel != "")
                    $(btn_id).html('Hide...');
                x.show();
            } 
            else 
            {
                if(strLabel != "")
                    $(btn_id).html(strLabel);
                x.hide();
            }
        }            
    </script>    
<!-- /replacer_ethereum_show_hide -->
</head>

<body style="background:white;margin-left:20px;margin-right:20px;" onload="init();">
    
<div id="holder">
    
<p><div class="footer">
    <div id="main">    
    
        <fieldset style="text-align:center;">
            <legend class="image_with_link">
                <a href="./index.html"><img src="images/home.png"></a>
                <a href="duke_of_ether.htm"><img src="images/duke_of_ether.png"></a>
                <a href="share_holder.htm"><img src="images/share_holder_grey.png"></a>
            </legend>
        
            <div id="duke_info" style="text-align:left;"></div>
            <div id="error_message">This isn't an ethereum browser</div> 
                
            <table border=0 width="100%">
                <td colspan="2">
<!-- replacer_ethereum_word_of_caution -->
                    <div align="left">
                        <h3 style="color:#CD2626">A word of caution:</h3>
                        <p>This is not exactly a commercial site: it teaches rather 
                            than asking for money (please, take a look at our "donate" link :) 
                            So let's discuss a security
                            issue that most DAO web sites would rather avoid mentioning.
                            
                        <p><button class="btn-normal" type="button" id="show_hide" 
                            onClick="show_hide($(this), '#hidable_security', 'See more...');" 
                            style="min-width:200px;">See more...</button>
                            
                        <div id="hidable_security" style="display:none;">    
                            <p>Note: this is not the issue of this particular site, but of all 
                                sites that provide Web interface to Ethereum payments, 
                                so it is a very good idea to familiarize yourself with the subject.
                            <p>Below, you have two payment choices. First of all, you can 
                                start your favourite wallet (Mist, MetaMask, geth - whatever) 
                                and click the "Become Duke!" button below.
                                You will be prompted for a password, because wallets require you to reconfirm the 
                                password in order to send coins. 
                            <p>Is it safe? The answer is NO. It is hard to hack the ethereum network, so you can be
                                reasonably confident paying with your wallet, but a web site is a different thing.
                                It can be hacked.
                            <p>Now, imagine someone hacking the "Duke of Ether" web site and placing there some malicious
                                code. It can: a) send your coins elsewhere and b) send more than you expected.
                            <p>This is an important issue to keep in mind: for example 
                                (source: http://fortune.com/2017/07/18/ethereum-coindash-ico-hack/):
                                <font color="#8B8682">CoinDash, an Israeli startup, planned to raise capital by 
                                selling its own digital tokens in exchange for the cryptocurrency Ethereum... But just 13 
                                minutes into the token sale... an "unknown perpetrator" hacked CoinDash's website and 
                                changed the address for sending investments to a fake one, the company later announced 
                                on its website. That diverted millions of dollars in contributions to the attacker...</font>
                            <p>To avoid the problem, you have few choices. 
                            <p>First, if you are a web guru, you can examine the web site... 
                                which is long, boring and there still is a chance you'll miss something.
                            <p>Second, do not pay from a wallet that has a lot of money. 
                                If you have $1 in your wallet, and a hacker drains it all, he'll get $1, 
                                which isn't that much after all. 
                            <p>Third, keep as much control over your payment as you can: do not pay 
                                through a web site, pay directly. Below, in the "If you don't trust us" 
                                section a cold payment instructions are displayed.
                                Use your wallet to send money (look in the suggested amount in the left column).
                                If the site is hacked, you will still loose your money, but at least you will loose 
                                only the amount you sent, not all coins a malicious script finds in your wallet.
                            <p>Finally, to avoid that last danger, you can use 
                                <a href="https://etherscan.org">etherscan.org</a>
                                to find the Duke contract by its address (see below in the 
                                "If you don't trust us" section) and study it. First, if you can not find it, 
                                then the site was probably hacked; second, if you know Solidity, you can analyze 
                                the code, and finally, look at the date a contract was published. 
                            <p>As an additional precaution, you can save the web site on your disk and run 
                                the local copy. It will not help if the site ALREADY contains malicious code, 
                                but it can protect you from the future attacks. There are two disadvantages: 
                                first, not all sites support independent work (ours does), and second, if the site 
                                changes, providing more functional, you will miss it.
                            <p>How realistic is the danger? Well, there are thousands sites online that 
                                have "please donate" Bitcoin and Ethereum addresses... and they seem to work. 
                                But - see the CoinDash story above - it is possible.                            
                        </div>        
                    </div>        
<!-- /replacer_ethereum_word_of_caution -->    
                </td>
                <tr>
                <td width="50%" style="background-color:#FFDAB9;"><h3>If you trust us:</h3></td>
                <td width="50%" style="background-color:#A4D3EE;"><h3>If you don't trust us:</h3></td>
                <tr>
                <td style="background-color:#FAEBD7;text-align:left;">
                    <table class="table_align_baseline" border=0 style="vertical-align:baseline;">
                        <td width=50%><h3 style="display:inline;">Enter wallet password: </h3></td>
                        <td><input type="text" id="edt_wallet_password" value="password"/></td>
                    <tr>
                        <td><h3 style="display:inline;">Enter Country: </h3></td>
                        <td>  
                            <select id="select_countries">
                                <!-- option>Country 1</option -->
                            </select>
                        </td>
                    <tr>
                        <td><h3 style="display:inline;">Enter nick name: </h3></td>
                        <td><input type="text" id="edt_duke_nick_name" value="Anonimus"/></td>
                </table>    
                
                </td>
                <td width="50%" align="left" style="background-color:#D1EEEE;padding:5px;">
                    <p>Thatsfer ether (min. amount is <span id="min_next_bet"></span> ether) 
                    <p>to the function "BecomeDuke" or (<span id="create_restore_fee"></span> ether) 
                        "addRemoveCountry" of the following contract: 
                    <p><span id="contract_address"/>
                </td>
                <tr>
                <td style="background-color:#FAEBD7;text-align:left;" colspan=2>
                    <table class="table_align_baseline" border=0>
                        <td><h3 style="display:inline;">Enter amount: </h3></td>
                        <td><input type="text" id="edt_pay_to_become_duke" 
                            onInput="adjustNumericInput('#edt_pay_to_become_duke');" /></td>
                        <td><input type="text" id="edt_destroy_country" style="width:190px;" 
                            onInput="adjustNumericInput('#edt_destroy_country');" /></td>
                        <td><input type="text" id="edt_restore_country" style="width:190px;" 
                            onInput="adjustNumericInput('#edt_restore_country');" /></td>
                        <tr>
                        <td width=25%>
                            <button class="btn-large" type="button" value="Become Duke!" id="btn_become_duke"
                                onClick="becomeDuke(0);">Become Duke!</button>
                        </td>
                        <td width=25%>
                            <input type="text" id="edt_country_name" onInput="adjustCreateDestroyPrice();"/>    
                        </td>
                        <td>
                            <button class="btn-large" type="button" value="Destroy Country!" id="btn_destroy_country"
                                onClick="destroyCountry();">Destroy Country!</button>
                        </td>
                        <td>
                            <button class="btn-large" type="button" value="Create Country!" id="btn_restore_country"
                                onClick="restoreCountry();">Create Country!</button>
                                
                            <img src="https://snowcron.com/images/question_orange.png" style="vertical-align:middle;"
                                onClick="show_hide($(this), '#hidable_rules', '');" >
                        </td>
                        <tr>
                        <td colspan=4 id="hidable_rules" style="display:none;padding:5px;">
                            <h2 class="header">Duke of Ether: Rules and Regulations</h2>
                            <p><h3 class="header_ex">What you need</h3>
                                <p>You have to have Ethereum enabled browser. It can be Geth or Mist running alongside to 
                                    Browser, or MetaMask plugin. Anything that gives a Browser an access to blockchain.
                                    If you do not know how to do it, read our 
                                        <a href="https://duke-of-ether.com/getting_started.htm">
                                    Very Brief Instructions</a>.     
                            <p><h3 class="header_ex">Becoming a Duke</h3>
                                <p>Select a Country you feel like ruling. Select payment: anything larger than a 
                                    suggested min. price.
                                <p>Read instructions above and pay the "I trust you" or "I don't trust you" way. If you are 
                                    reasonably paranoid, as any Duke should be, use contract address to fing it on 
                                    <a href="https://etherscan.io">etherscan.io</a> and read the contract.
                                <p>You do this - you become a Duke. To comfort your predecessor, to throw him a bone, so to speak, 
                                    money you paid will go to him. Keep in mind that the next Duke has to pay MORE, so if 
                                    some impostor decides to overthrow you, he'll pay dearly, and you will end up with 50% more than
                                    you spent... Well, there is always a chance that they will be too scared, so no one will come.
                                <p>After two month of tyrany (it is your tyrany, so it is a good thing), the price ot throne will
                                    begin to go down, 5% a day. It is like you are asking "Ok, are you still afraid to 
                                    challenge me?!"
                                <p>And even if an usurper comes and kicks... sorry, asks you away, your name will stay in the 
                                    blockchain for as long as Ethereum itself exists.
                                <p>Long live the Duke!
                            <p><h3 class="header_ex">Destroying the Country</h3>    
                                <p>Now, THAT costs more. Also, if overthrown, you will get back exactly what you paid (minus a small
                                    comission). No profit. One have to be deep on the Dark Side to bring devastation on an entire
                                    country, so this is going to be his reward. 
                                <p>Ok, in human language: to destroy the country, you pay more, become the Duke of a 
                                    DESTROYED country
                                    (it will clearly say so in a block chain) and when (if) the next Duke overthrows you, you will
                                    get back what you paid (minus small fee) - no extra bonus, as it would be in case of 
                                    taking the throne of an intact country.
                            <p><h3 class="header_ex">Restoring the Country</h3>    
                                <p>To overthrow the Dark Duke, you have to take a throne. As with Destroying, 
                                    Restoring rewards you (when/if you are overthrown) with exactly (minus fee) 
                                    same money you paid. No extra bonus. 
                                    Being on a Brigth Side, you should forget about greed. Correct? 
                        </td>
                    </table>    
                </td>
            </table>    
            
            <p><p><p>
                <table border=0 id="history">
                    <td colspan=5><h2 class="header">The Dukes that ruled the Ether before</h2></td>
                    <tr>
                    <td width=20%>Nick Name</td>
                    <td width=50%>Address</td>
                    <td width=15%>Price (ether)</td>
                    <td width=15%>Date</td>
                </table>
            <p><table border=0>
                <td width=85%>
                    <div id="myChart" style="height:600px;"></div>
                </td>
                <td id="chart_legends">
                    
                </td>    
        </fieldset>    
    </div>    
</footer>
    <script>

    var Web3 = require('web3');

    var strContractAddress = "0x87D32Bdd815Eb09d4EcE8424dDEc41b9CBd926be";
    var abi = ... Put ABI of your contract here ...;
    var contract = null;    
        
    var arrThronePrice = [];
    //var x = 0;
    
    //var history = [];

    var strCurrentCountry = "USA"; //$('#select_countries option:selected').text();
    var bRedrawChart = false;
    
    function init() 
    {
<!-- replacer_ethereum_init -->    
        document.getElementById("error_message").style.display = "none";
    
        // Checks Web3 support
        if(typeof web3 !== 'undefined' && typeof Web3 !== 'undefined') 
        {
            // If there's a web3 library loaded, then make your own web3
            web3 = new Web3(web3.currentProvider);
        } 
        else if (typeof Web3 !== 'undefined') 
        {
            // If there isn't then set a provider
            web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
        } 
        else if(typeof web3 == 'undefined') 
        {
            // This isn't an ethereum browser
            document.getElementById("share_holder_info").style.display = "none";
            document.getElementById("error_message").style.display = "block";
            return;    
        }
<!-- /replacer_ethereum_init -->

        var duke = web3.eth.contract(abi);
        contract = duke.at(strContractAddress);    
        //var currentDukeAddress = contract.getCurrentDuke.call();
        $('#contract_address').html(strContractAddress);
        
        setDukeAndDestroyAndRestoreStatus();
        
        $("#select_countries").change(
            function (event, ui) 
            { 
                strCurrentCountry = $('#select_countries option:selected').text();
                setDukeAndDestroyAndRestoreStatus();
            } 
        );
        
        //--- {_from: web3.eth.coinbase} {_sender: currentDukeAddress}
        
        var prevDukeTransactions = contract.updateDukeHistory(
            {_from: web3.eth.coinbase}, {fromBlock: contract.getInitBlock(), toBlock: 'latest'});
        prevDukeTransactions.watch(function(err, result) 
        {
            if(err) 
            {
                document.getElementById("error_message").style.display = "block";
                document.getElementById("error_message").text = err;
                return;
            }

            document.getElementById("error_message").style.display = "none";
            
            add_duke_info_to_history(result);
            
            if(bRedrawChart)
                chart();
        });
        
        // ---
        
        window.setTimeout(function() 
        {
            //arrThronePrice.push({x:new Date(contract.getDukeDate() * 1000), y:nNextMinBet});

            chart();
            bRedrawChart = true;
        }, 3000);
    }    
    
    // ------
    
    function adjustNumericInput(controlId)
    {
        var strValue = $(controlId).val();
        strValue = strValue.replace(/[^\d\.]/g,'');
        $(controlId).val(strValue);
    }
    
    // ------
    
    function adjustCreateDestroyPrice()
    {
        strCurrentCountry = $('#edt_country_name').val();
        strCurrentCountry = strCurrentCountry.replace(/[^A-Za-z ]/g,'');
        $('#edt_country_name').val(strCurrentCountry);    
        
        setDukeAndDestroyAndRestoreStatus();
    }
    
    // ------
    
    function setDukeAndDestroyAndRestoreStatus()
    {
        var nNextMinBet = contract.getMinNextBet.call(strCurrentCountry).shift(-18).toNumber();
        var nPayToDestroyCountry = Math.ceil(100000 * 
            contract.getPaymentToAddRemoveCountry.call(
                strCurrentCountry, true).shift(-18).toNumber()) / 100000;
        var nPayToRestoreCountry = Math.ceil(100000 * 
            contract.getPaymentToAddRemoveCountry.call(
                strCurrentCountry, false).shift(-18).toNumber()) / 100000;
    
        var bIsDestroyed = contract.isDestroyed.call(strCurrentCountry);
        
        if(bIsDestroyed)
        {
            $('#edt_pay_to_become_duke').val("DESTROYED");
            
            $('#edt_destroy_country').val("DESTROYED");
            $('#edt_restore_country').val(nPayToRestoreCountry);

            $('#edt_pay_to_become_duke').prop('disabled',true).css('opacity',0.5);
            $('#edt_destroy_country').prop('disabled',true).css('opacity',0.5);
            $('#edt_restore_country').prop('disabled',false).css('opacity',1);
            
            $('#btn_restore_country').html("Restore Country!");
            
            $('#min_next_bet').html(nPayToRestoreCountry);
            $('#create_restore_fee').html(nPayToRestoreCountry);
            
            $('#btn_become_duke').prop('disabled',true).css('opacity',0.5);
            $('#btn_destroy_country').prop('disabled',true).css('opacity',0.5);
            $('#btn_restore_country').prop('disabled',false).css('opacity', 1);
        }    
        else if(nPayToRestoreCountry == 0)    // Country Exists and not destroyed
        {
            $('#edt_pay_to_become_duke').val(nNextMinBet);
            $('#edt_destroy_country').val(nPayToDestroyCountry);
            $('#edt_restore_country').val("EXISTS");

            $('#edt_pay_to_become_duke').prop('disabled',false).css('opacity',1);
            $('#edt_destroy_country').prop('disabled',false).css('opacity',1);
            $('#edt_restore_country').prop('disabled',true).css('opacity',0.5);

            $('#btn_restore_country').html("Create Country!");
            
            $('#min_next_bet').html(nNextMinBet);
            $('#create_restore_fee').html(nPayToDestroyCountry);
            
            $('#btn_become_duke').prop('disabled',false).css('opacity',1);
            $('#btn_destroy_country').prop('disabled',false).css('opacity',1);
            $('#btn_restore_country').prop('disabled',true).css('opacity', 0.5);
        }    
        else // Country never existed
        {
            $('#edt_pay_to_become_duke').val("NO SUCH COUNTRY");
            $('#edt_destroy_country').val("NO SUCH COUNTRY");
            $('#edt_restore_country').val(nPayToRestoreCountry);

            $('#edt_pay_to_become_duke').prop('disabled',true).css('opacity',0.5);
            $('#edt_destroy_country').prop('disabled',true).css('opacity',0.5);
            $('#edt_restore_country').prop('disabled',false).css('opacity',1);

            $('#btn_restore_country').html("Create Country!");
            
            $('#min_next_bet').html(nNextMinBet);
            $('#create_restore_fee').html(nPayToRestoreCountry);
            
            $('#btn_become_duke').prop('disabled',true).css('opacity',0.5);
            $('#btn_destroy_country').prop('disabled',true).css('opacity',0.5);
            $('#btn_restore_country').prop('disabled',false).css('opacity', 1);
        }    

        $('#edt_country_name').val(strCurrentCountry);
    }
    
    // ------
    // string strCountry, boolean bIsDestroyed, string strNickName, 
    // address indexed addrCurrentDuke, uint nCurrentDukePaid, uint date
    function add_duke_info_to_history(result)
    {
        var strCountry = result.args.strCountry;
        var strCountryId = ("#" + strCountry).replace(/\s/g, "");
        var strCountryCompressed = strCountry.replace(/\s/g, "");
    
        var bIsDestroyed = result.args.bIsDestroyed;
        var strNickName = result.args.strNickName;
        var addrCurrentDuke = result.args.addrCurrentDuke;
        var nCurrentDukePaid = Math.round(100000 * result.args.nCurrentDukePaid.shift(-18).toNumber()) / 100000;
        
        var rawDate = new Date(result.args.date * 1000);
        var strRawDate = rawDate.toISOString();
        var strDate = strRawDate.slice(0, 10) + " " + strRawDate.slice(11, 19);    
    
        if(arrThronePrice[strCountry] == undefined)
        {
            arrThronePrice[strCountry] = new Array();
            //arrThronePrice[strCountry].push({x:rawDate - 24*3600*1000,y:0});
            
            $('#select_countries').append($("<option></option>").attr("value", strCountry).text(strCountry));
        }    
        arrThronePrice[strCountry].push({x:rawDate,y:nCurrentDukePaid});
    
        var strRecord = 
            "<tr><td width=20%>" + (bIsDestroyed?"<font color='red'>Destroyed by: ":"")
                + strNickName + (bIsDestroyed?"</font>":"") + 
                "</td><td width=50%>" + addrCurrentDuke + "</td><td width=15%>" 
                + nCurrentDukePaid + 
                "</td><td width=15%>" + strDate + "</td>";
        
        strHtml = $('#history ' + strCountryId).html();
        if(typeof strHtml == 'undefined' || strHtml == '')
        {
            strHtml = "<tr><td colspan=5><table id='" + strCountryCompressed + 
                "' border=0 style='background-color:#FFDAB9;text-align:left;' 
                class='zebra'><td colspan=4 bgcolor='#7FFFD4'><h3 class='header_ex'>" 
                + strCountry + "</h3></td></table></td>";
            $('#history').append(strHtml);
        }    
        
        $('#history ' + strCountryId).append(strRecord);

        // ---
        if(bRedrawChart)
            setDukeAndDestroyAndRestoreStatus();
    }

    // ------
    
    function chart()
    {
        var arrColors = ['#d70206', '#f05b4f', '#f4c63d', '#d17905', '#453d3f', '#59922b', '#0544d3', '#6b0392', '#f05b4f', '#dda458', '#eacf7d', '#86797d'];
    
        // We insert today's date and prev. price into each array, so that all lines end up the same date
        var dateLatest = new Date();
        var arrSeries = [];
        var i = 0;
        $('#chart_legends').html("");
        for(var country in arrThronePrice)
        {
            var arrData = arrThronePrice[country];
            arrSeries.push({ name:country, data:arrData.concat([{x:dateLatest, y:arrData[arrData.length - 1].y}]) });
            $('#chart_legends').append("<div style='text-align:right;color:" + arrColors[i] + "'>" + country + "</div>"); 
            i++;
        }    
        
        var chart = new Chartist.Line('#myChart', 
        {
            series: arrSeries,
            //[
            //    {
            //        name: 'series-1',
            //        data: arrThronePrice
            //    }
            //],
        }, 
        {
            axisX: 
            {
                type: Chartist.FixedScaleAxis,
                divisor: 5,
                labelInterpolationFnc: function(value) 
                {
                    return moment(value).format('MMM D YY');
                }
            },
            axisY:
            {
                offset: 80,
            },
            showArea: true
        });
    }
    
    // ------

    function destroyCountry()
    {
        becomeDuke(2);
    }
    
    function restoreCountry()
    {
        becomeDuke(1);
    }
    
    // ------
    // My first account (at index 0) has password "password"
    // In order to send transactions to it, I have to unlock it.
    // TBD: move it to a separate function and don't keep a password in HTML 
    // (ask user to unlock it, and if connection is not encrypted (http, not https), ask to do it in geth/Mist...)
    // TBD: allow the user to select a wallet, instead of using the first one.
    // TBD: replace it all with "here is an address, send ether"
    
    // 0 - duke, 1 - create, 2 - destroy
    function becomeDuke(nAction)
    {
        document.getElementById("error_message").style.display = "block";

        if(strCurrentCountry.length > 30)
        {
            document.getElementById("error_message").innerHTML = "Country name is too long, should be < 30 char.";
            return;
        }    
        
        var strNickName = $('#edt_duke_nick_name').val();
        if(strNickName.length > 30)
        {
            document.getElementById("error_message").innerHTML = "Duke name is too long, should be < 30 char.";
            return;
        }    
                
        var strPassword = $('#edt_wallet_password').val();
        
        var nPayToBecomeDuke;
        switch(nAction)
        {
            case 1:    
                nPayToBecomeDuke = $('#edt_restore_country').val();
                break;
            case 2:    
                nPayToBecomeDuke = $('#edt_destroy_country').val();
                break;
            case 0:
            default:
                nPayToBecomeDuke = $('#edt_pay_to_become_duke').val();
                break;
        }
            
        var nMinNextBet;
        switch(nAction)
        {
            case 1:    
                nMinNextBet = contract.getPaymentToAddRemoveCountry.call(strCurrentCountry, false).shift(-18).toNumber();
                break;
            case 2:    
                nMinNextBet = contract.getPaymentToAddRemoveCountry.call(strCurrentCountry, true).shift(-18).toNumber();
                break;
            case 0:
            default:
                nMinNextBet = contract.getMinNextBet.call(strCurrentCountry).shift(-18).toNumber();
                break;
        }
        
        if(nMinNextBet == 0)
        {
            var strMessage = "Error";
            if(nAction == 1)
                strMessage = "Sorry, but this country already exists: Destroy it first.";
            else if(nAction == 2)    
                strMessage = "Sorry, but someone else have already destroyed this country: Consider creating it first.";
                
            document.getElementById("error_message").innerHTML = strMessage;    
            return;    
        }
        
        if(nPayToBecomeDuke < nMinNextBet)
        {
            document.getElementById("error_message").innerHTML = "Amount is too small, min amount is " + nMinNextBet + " ether";
            return;
        }    
        
        var account = web3.eth.accounts[0];
        var acctBal = web3.fromWei(web3.eth.getBalance(account), "ether").toNumber();
        if(acctBal < nPayToBecomeDuke)
        {
            document.getElementById("error_message").innerHTML = 
                "Insufficient funds, required amount is " + nPayToBecomeDuke + " ether";
            return;
        }
                
        web3.personal.unlockAccount(account, strPassword, 3000);
        
        var nPay = web3.toWei(nPayToBecomeDuke, "ether");
        
                switch(nAction)
        {
            case 1:    
                contract.addRemoveCountry.sendTransaction(strCurrentCountry, strNickName, 
                    false, {from: web3.eth.coinbase, value: nPay, gas: 1000000 },
                    function(err, transactionHash) 
                    {
                        if (err)
                            document.getElementById("error_message").innerHTML = err;
                    });

                break;
            case 2:    
                contract.addRemoveCountry.sendTransaction(strCurrentCountry, strNickName, 
                    true, {from: web3.eth.coinbase, value: nPay, gas: 1000000 },
                    function(err, transactionHash) 
                    {
                        if (err)
                            document.getElementById("error_message").innerHTML = err;
                    });

                break;
            case 0:
            default:
                contract.becomeDuke.sendTransaction(strCurrentCountry, strNickName, 
                    {from: web3.eth.coinbase, value: nPay, gas: 1000000 },
                    function(err, transactionHash) 
                    {
                        if (err)
                            document.getElementById("error_message").innerHTML = err;
                    });

                break;
        }
        
        var date = new Date().toISOString().slice(0, 10) + " " + new Date().toISOString().slice(11, 19);
        document.getElementById("error_message").innerHTML  = "Date: " + date + "; Transaction sent.";
    }
    
    </script>
    
</body>    
</html>

The Web UI code is a bit too complex for a tutorial, but it shouldn't pose any problems if you keep in mind that
a) We do a lot of switching of enabled/disabled state just to keep input fields filled with proper info and in proper state. It has nothing to do with smart contracts, it is just Java Scripting.
b) We keep our data in a dynamic array, updated as new contract's events arrive, and we use this array to build the table and to draw a chart. All you need is the logic of catching events, the rest is, again, Java Script decorations.
c) Charts are created using chartist.js library, if you want to understand it better, there is a tutorial on its site.







(C) snowcron.com, all rights reserved

Please read the disclaimer