LoginSignup
5
6

More than 5 years have passed since last update.

TruffleとRiotJSを使って流行りのDappsを作ってみた

Last updated at Posted at 2018-06-05

TokenをSPAから送金するサンプルを作ってみた

前提

レシピ

  • truffleをインストール
  • Contractを作る
  • RiotでUI作る
  • htmlからtruffleを通してContractを操作する

Truffleをインストール

WebインターフェースのDappsを作る為には欠かせない魔法のフレームワーク

先ずはこいつをインストールしないと話が始まらない

npm install -g truffle
cd [workspace]/
truffle init

Contractを作る

今回は極めてベーシックなERC223準拠のコインを作る

それ以外の機能は何もなし

contracts/Token.solファイルを作って以下ソースを貼り付け

ワンソースで済ませられるサンプルにするためにSafeMathとか入っていないけど本当は入れたほうが良いよ!

pragma solidity ^0.4.11;


contract ERC223ReceivingContract {
    function tokenFallback(address _from, uint _value, bytes _data);
}

contract ERC223 {

    string public name = "ERC223";
    string public symbol = "TKN";
    uint8 public decimals = 18;
    uint public totalSupply = 10000000;
    mapping(address => uint) balances;

    /* ERC20(Etherscan)用送金通知 */
    event Transfer(address indexed from, address indexed to, uint value);
    /* ERC223用送金通知 */
    event Transfer(address from, address to, uint value, bytes data);

    /* コンストラクタ */
    constructor(string _name, string _symbol, uint8 _decimals, uint256 _totalSupply, uint256 _initialCoin) public {
        if (keccak256(_name) != keccak256("")) {
            name = _name;
        }
        if (keccak256(_symbol) != keccak256("")) {
            symbol = _symbol;
        }
        if (_decimals > 0 && _decimals != 18) {
            decimals = _decimals;
        }
        uint __totalSupply = totalSupply;
        if (_totalSupply > 0 && _totalSupply != totalSupply) {
            __totalSupply = _totalSupply;
        }
        totalSupply = __totalSupply * 10 ** uint256(decimals);
        uint __initialCoin = totalSupply;
        if (_initialCoin > 0) {
            __initialCoin = _initialCoin;
        }
        balances[msg.sender] = __initialCoin;
    }

    /* 送金チェック */
    modifier checkTransferEnabled(address _to, uint256 _value) {
        // 送金先はOアドレスじゃない
        // 送金額が0では無い
        // 送金額は残高残高を超えていない
        require(
            _to != address(0)
            && _value > 0
            && balances[msg.sender] >= _value
        );
        _;
    }

    function _transfer(address _to, uint _value, bytes _data) internal checkTransferEnabled(_to, _value) {
        uint codeLength;
        assembly {
            codeLength := extcodesize(_to)
        }
        balances[msg.sender] = balances[msg.sender] - _value;
        balances[_to] = balances[_to] + _value;
        if(codeLength > 0) {
            ERC223ReceivingContract receiver = ERC223ReceivingContract(_to);
            receiver.tokenFallback(msg.sender, _value, _data);
        }
        Transfer(msg.sender, _to, _value);
        Transfer(msg.sender, _to, _value, _data);
    }

    function transfer(address _to, uint _value, bytes _data) {
        _transfer(_to, _value, _data);
    }

    function transfer(address _to, uint _value) {
        bytes memory empty;
        _transfer(_to, _value, empty);
    }

    function balanceOf(address _owner) constant returns (uint balance) {
        return balances[_owner];
    }
}

Remixで動くかテスト

https://remix.ethereum.org/

Rinkeby TestNetにデプロイして動くか確認してみる

※RemixからRinkebyへのデプロイはこの此方を参考にしましたm(_ _)m

「Ethereumブロックチェーンでめそ子の握手券を作ってみた」 => https://dev.classmethod.jp/etc/sell-handshake-voucher-in-ethereum-blockchain/

無事にデプロイさているか確認

https://rinkeby.etherscan.io/tx/0x4640afd04665d6c75bbd9eee87ab53d6c19d9e03ff52d57713b4a317503c5523

無事されてます(´v`)

知らない人に送金してみる

https://rinkeby.etherscan.io/token/0x99b5df47024ee4f1266e6fc4d20666ed6fbed197

無事に送金されました(´v`)

※トランザクションID(0x4640afd04665d6c75bbd9eee87ab53d6c19d9e03ff52d57713b4a317503c5523)と、Contractアドレス(0x99b5df47024ee4f1266e6fc4d20666ed6fbed197)は後ほど使うのでちゃんとメモしておく!

RiotでUIを作る

Riotとはズバリ最強のコンポーネント指向JSフレームワークである

信じられないほど簡単にSPAっぽいhtmlが作れるのである

Riot公式

truffle initした時に出来たindex.htmlをひとまず以下の内容で上書き

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8"/>
        <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <meta name="description" content="Sample Riot Dapps"/>
        <meta name="robots" content="noindex"/>
        <title>Riot with Dapps</title>
        <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css"/>
        <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/css-spinning-spinners/1.1.1/load8.css"/>
        <link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"/>
        <style>
            html, body {
                height: 100%;
            }
            .footer {
              position: absolute;
                width: 100%;
              bottom: 0;
                padding: 5px;
                background-color: #ddd;
            }
            .orverlay {
                display: none;
                position: absolute;
                height: 100%;
                width: 100%;
                z-index: 9999;
                background-color: rgba(0, 0, 0, 0.4);
            }
            .popup-contents {
                width: 400px;
                height: 200px;
                margin: auto;
                margin-top: 200px;
                padding: 20px;
            }
            #loader {
                z-index: 99999;
            }
        </style>
    </head>

    <body>
        <div id="loader" class="orverlay loading"></div>
        <div id="popup" class="orverlay"></div>
        <app class="container">
            <navigationbar></navigationbar>
            <footer></footer>
            <router class="row">
                <route path="/">
                    <holders/>
                </route>
                <route path="/mypage">
                    <mypage/>
                </route>
            </router>
        </app>

        <!-- Riot tags -->
        <section id="riot-tags">

            <script type="riot/tag">
                <navigationbar>
                    <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
                        <div class="container">
                            <div class="navbar-header">
                                <!-- sp用 -->
                                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
                                    <span class="sr-only">Toggle navigation</span>
                                    <span class="icon-bar"></span>
                                    <span class="icon-bar"></span>
                                    <span class="icon-bar"></span>
                                </button>
                                <a class="navbar-brand" href="#">Riot Dapps</a>
                            </div>
                            <div class="collapse navbar-collapse">
                                <ul class="nav navbar-nav">
                                    <li><a href="#">一覧</a></li>
                                    <li><a href="#mypage">マイページ</a></li>
                                </ul>
                            </div>
                        </div>
                    </div>
                </navigationbar>
            </script>

            <script type="riot/tag">
                <footer class="footer text-right">
                    2018 &copy; <a href="https://www.digiq.co.jp/" target="_blnak">DigitalQuest,inc.</a>
                </footer>
            </script>

            <script type="riot/tag">
                <holders>
                    <div each="{value, index in list}" class="col-lg-4 col-sm-6 col-xs-12">
                        <holder index={index} data={value}></holder>
                    </div>
                    var testDatas = [];
                    testDatas[0] = { address: "0xf17f52151ebef6c7334fad080c5704d77216b732", balance: 10000 };
                    testDatas[1] = { address: "0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef", balance: 20000 };
                    this.on('mount', function() {
                        list = testDatas;
                        riot.update();
                    });
                </holders>
            </script>

            <script type="riot/tag">
                <mypage>
                    <holder mypage={true} index={index} data={value}/>
                    var testData = { address: "0x627306090abab3a6e1400e9345bc60c78a8bef57", balance: 100000 };
                    this.on('mount', function() {
                        index = 0;
                        value = testData;
                        riot.update();
                    });
                </mypage>
            </script>

            <script type="riot/tag">
                <holder>
                    <div class="bg-info" style="padding: 5px;">
                        <h4>{opts.data.address}</h4>
                        <h5>{opts.data.balance}</h5>
                        <button if={opts.mypage!=true} class="btn btn-success" onclick={onClickTransfer}>送金</button>
                    </div>
                    onClickTransfer = function () {
                        var div = document.createElement("div");
                        riot.mount(div, "popup-transfer", {"address" : opts.data.address });
                        $('#popup').html(div);
                        $('#popup').fadeIn();
                    };
                </holder>
            </script>

            <script type="riot/tag">
                <popup-transfer>
                    <div class="row bg-info popup-contents">
                        <p>TO: {opts.address}</p>
                        <div class="form-group">
                            <label for="example-email" class="col-md-12">送金数</label>
                            <div class="col-md-12">
                                <input type="number" class="form-control form-control-line" onchange="{onChangeForm}" name="coin" ref="coin" value="{coin}" placeholder="1000"/>
                                <br/>
                            </div>
                            <div class="col-sm-12">
                                <button class="btn btn-success">Transfer</button>
                            </div>
                        </div>
                    </div>
                    coin = 100;
                    onChangeForm = function (dom) {
                        coin = dom.target.value;
                    };
                    // ポップアップを閉じる
                    $('#popup').on('click touchend', function(dom) {
                        if (!$(dom.target).closest('.popup-contents').length) {
                            $('#popup').fadeOut().load(function () {
                                $('#popup').html('');
                            });
                        }
                    });
                </popup-transfer>
            </script>

        </section>

        <section id="scripts">
            <script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
            <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
            <!-- Riot -->
            <script src="//cdn.jsdelivr.net/npm/riot@3.9/riot+compiler.min.js"></script>
            <!-- Riot tag base rooting -->
            <script src="//cdn.jsdelivr.net/npm/riot-route@3.1.3/dist/route+tag.min.js"></script>
            <script>
                $(function() {
                    $("#loader").fadeIn();
                    $(window).load(function() {
                        riot.mount('navigationbar');
                        riot.mount('footer');
                        riot.mount('router');
                        $("#loader").fadeOut();
                    });
                });
            </script>
        </section>
    </body>
</html>

Webサーバを立ち上げて確認してみる

python -m SimpleHTTPServer 8888

おもむろにローカルホストにアクセス

http://localhost:8888/

こんな画面になってるかと思います(´v`)

htmlからtruffleを通してContractを操作する

魔法のフレームワーク truffleを使っているのでhtmlにちょっと細工すればもうContractにアクセス出来ちゃう!

truffleでContractをコンパイルした際に、web3js(※このあたりを参考https://tomokazu-kozuma.com/how-to-access-ethereums-block-chain-using-web3js/)経由でContractへのアクセスする為のjsonファイルを

なんと自動生成してくれる!

なので、ちょっいじって読み込めばもう使えてしまうのである

truffle devlop
truffle(develop)> migrate

すると・・・

build/contracts/ERC223.jsonと言うファイルが出来ていると思われる

このjsonファイルがhtmlからweb3js経由でContractを呼び出すキモになるファイル

しかし、このままだとローカルでしか動作しないので、先程のRinkeby TestNetにデプロイしたContractのネットワークとアドレス情報を書き加える

ERC223.jsonの7980行目付近

"networks": {},

ここを

  "networks": {
    "4": {
      "events": {},
      "links": {},
      "address": "0x99b5df47024ee4f1266e6fc4d20666ed6fbed197",
      "transactionHash": "0x4640afd04665d6c75bbd9eee87ab53d6c19d9e03ff52d57713b4a317503c5523"
    }
  },

こう変えます。

"4"の部分は固定

networks:4はRinkeby TestNetのネットワークIDになっている

addressには上でメモしたContractアドレスを

transactionHashには上でメモしたトランザクションIDを

それぞれ入れる

あと、136行目からの

    {
      "constant": false,
      "inputs": [
        {
          "name": "_to",
          "type": "address"
        },
        {
          "name": "_value",
          "type": "uint256"
        }
      ],
      "name": "transfer",
      "outputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "function"
    },

この部分はWebインターフェースからは不要なので削除

コレでContractのjsonの準備は終わり

ここまで来たらあとはhtmlを治すだけ!

htmlにweb3jsとtrufflejsの読み込みを加える

            ・・・
            <!-- Riot tag base rooting -->
            <script src="//cdn.jsdelivr.net/npm/riot-route@3.1.3/dist/route+tag.min.js"></script>

            <!-- web3jsとtrufflejsの読み込みを追加 -->
            <script src="//cdn.jsdelivr.net/gh/ethereum/web3.js/dist/web3.min.js"></script>
            <script src="//cdn.jsdelivr.net/npm/truffle-contract@3.0.5/dist/truffle-contract.min.js"></script>

            <script>
            ・・・

Cntractを操作するAppクラスを定義

            ・・・
            <script>
                App = {
                    web3Provider: null,
                    contracts: {},
                    dev: false,
                    contractJSONPath: '',
                    ganache: false,

                    init: function(_contractJSONPath, _ganache = false) {
                        contractJSONPath = _contractJSONPath;
                        ganache = _ganache;
                        return App.initWeb3();
                    },

                    initWeb3: function() {
                        if (typeof web3 !== 'undefined') {
                          App.web3Provider = web3.currentProvider;
                        }
                        else {
                          if (false === (navigator.userAgent.indexOf('Chrome') > 0 && document.location.href.indexOf('localhost:3000') > -1)) {
                            // web3ナシでスタート確定
                            App.dev = true;
                            riot.mount('navigationbar');
                            riot.mount('footer');
                            riot.mount('router');
                            $("#loader").fadeOut();
                            return;
                          }
                          if (ganache) {
                            // this is Ganache develop network
                            App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
                          }
                          else {
                            // this is truffle develop network
                            App.web3Provider = new Web3.providers.HttpProvider('http://localhost:9545');
                          }
                        }
                        web3 = new Web3(App.web3Provider);
                        return App.initContract();
                    },

                    initContract: function() {
                        $.getJSON(contractJSONPath, function(Artifact) {
                            App.contracts.contract = TruffleContract(Artifact);
                            App.contracts.contract.setProvider(App.web3Provider);
                            try {
                                riot.mount('navigationbar');
                                riot.mount('footer');
                                riot.mount('router');
                                $("#loader").fadeOut();
                            }
                            catch (err) {
                                App.error(err);
                            }
                            return;
                        }).error(function(jqXHR, textStatus, err) {
                            jqXHR.message = 'コントラクト見つかりません。';
                            App.error(jqXHR);
                        });
                        return App.bindEvents();
                    },

                    bindEvents: function() {
                        //$(document).on('click', '#registerBtn', App.handleRegister);
                    },

                    success: function(result, callback, errmessage = '取引エラーが発生した為、取引を完了出来ませんでした。') {
                        console.log(result);
                        if ('undefined' != typeof result.receipt && 'undefined' != typeof result.receipt.transactionHash && 0 == result.receipt.transactionHash.indexOf('0x')) {
                            // 正常
                            if ('undefined' != typeof callback) {
                                return callback(result);
                            }
                            alert('正常に取引が完了しました。');
                            return;
                        }
                        alert(errmessage);
                    },

                    error: function(err) {
                        if (-1 < err.message.indexOf('property') && -1 < err.message.indexOf('route') && -1 < err.message.indexOf('null')) {
                            // このエラーは一旦無視
                            return;
                        }
                        console.log(err);
                        message = err.message;
                        if (-1 < err.message.indexOf('Invalid') && -1 < err.message.indexOf('JSON')) {
                            message = 'コントラクトと接続出来ませんでした。';
                        }
                        else if (-1 < err.message.indexOf('wasn') && -1 < err.message.indexOf('t processed in')) {
                            message = 'この取引は処理に時間が掛かっています。\n暫く待ってから取引結果を確認して下さい。';
                            err = null;
                        }
                        else if (-1 < err.message.indexOf('User') && -1 < err.message.indexOf('denied') && -1 < err.message.indexOf('transaction')) {
                            message = 'コントラクトの実行を中止しました。';
                            err = null;
                        }
                        alert(message);
                        $("#loader").fadeOut();
                    },

                    transfer:  function(address, coin, callback) {
                        if (App.dev) {
                            // テストデータの返却
                            callback(true);
                            return;
                        }
                        web3.eth.getAccounts(function(error, accounts) {
                            if (error) {
                                console.log(error);
                            }
                            var account = accounts[0];
                            if ('undefined' == typeof account) {
                                App.error({message: 'コントラクトに参加中のアドレスが見つかりませんでした。'});
                                return;
                            }
                            if (account == address) {
                                App.error({message: '自分自身に対してこの操作は出来ません。'});
                                return;
                            }
                            coin = parseInt(coin + '000000000000000000');
                            App.contracts.contract.deployed().then(function(instance) {
                                return instance.transfer(address, coin, '0xad', {from: account});
                            }).then(function(result) {
                                App.success(result, callback);
                            }).catch(function(err) {
                                App.error(err);
                            });
                        });
                    },
                };
                $(function() {
                    $("#loader").fadeIn();
            ・・・

Appクラスをhtmlのロードで初期化するように書き換え

            ・・・
                $(function() {
                    $("#loader").fadeIn();
                    $(window).load(function() {
                        App.init('./build/contracts/ERC223.json');
                    });
                });
            </script>
            ・・・

初期化時に先程のERC223.jsonを渡してContractの操作をJSから行えるようにしている!

※このあたりの作りはフレームワークのもお作法による物のでは無いので、自分で使いやすいようにアレンジして行ったら良いです

最後に、UIボタンからApp.transferを実行する部分を追記して終わり!

            <script type="riot/tag">
                <popup-transfer>
            ・・・
                    // 送金アクション
                    transfer = function () {
                        $("#loader").fadeIn();
                        App.transfer(opts.address, coin, function(result) {
                            $("#loader").fadeOut();
                            alert(opts.address + '' + coin + ' 枚送金しました。');
                        });
                    };
                </popup-transfer>
            ・・・

はい、完成!!

(※最終的なソースはページの最後に書いてます)

実際に動かしてみる・・・

無事にMetaMask起動!

SUBMITすると・・・

https://rinkeby.etherscan.io/token/0x99b5df47024ee4f1266e6fc4d20666ed6fbed197

ちゃんと送金されてHolderが増える! (´v`)v

まとめ

以上、かなーーりハショリつつでしたが、Dappsの作り方のご紹介でした

ちなみ、本番のネットワークはID「1」なので


  "networks": {
    "4": {

  "networks": {
    "1": {

と変えて、Remixでデプロイ先をMainNetに向けてれば本番環境で動きます (´v`)d

是非是非お試しあれ!

m(_ _)mお粗末でした

おまけ

最終html

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8"/>
        <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <meta name="description" content="Sample Riot Dapps"/>
        <meta name="robots" content="noindex"/>
        <title>Riot with Dapps</title>
        <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css"/>
        <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/css-spinning-spinners/1.1.1/load8.css"/>
        <link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"/>
        <style>
            html, body {
                height: 100%;
            }
            .footer {
              position: absolute;
                width: 100%;
              bottom: 0;
                padding: 5px;
                background-color: #ddd;
            }
            .orverlay {
                display: none;
                position: absolute;
                height: 100%;
                width: 100%;
                z-index: 9999;
                background-color: rgba(0, 0, 0, 0.4);
            }
            .popup-contents {
                width: 400px;
                height: 200px;
                margin: auto;
                margin-top: 200px;
                padding: 20px;
            }
            #loader {
                z-index: 99999;
            }
        </style>
    </head>

    <body>
        <div id="loader" class="orverlay loading"></div>
        <div id="popup" class="orverlay"></div>
        <app class="container">
            <navigationbar></navigationbar>
            <footer></footer>
            <router class="row">
                <route path="/">
                    <holders/>
                </route>
                <route path="/mypage">
                    <mypage/>
                </route>
            </router>
        </app>

        <!-- Riot tags -->
        <section id="riot-tags">

            <script type="riot/tag">
                <navigationbar>
                    <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
                        <div class="container">
                            <div class="navbar-header">
                                <!-- sp用 -->
                                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
                                    <span class="sr-only">Toggle navigation</span>
                                    <span class="icon-bar"></span>
                                    <span class="icon-bar"></span>
                                    <span class="icon-bar"></span>
                                </button>
                                <a class="navbar-brand" href="#">Riot Dapps</a>
                            </div>
                            <div class="collapse navbar-collapse">
                                <ul class="nav navbar-nav">
                                    <li><a href="#">一覧</a></li>
                                    <li><a href="#mypage">マイページ</a></li>
                                </ul>
                            </div>
                        </div>
                    </div>
                </navigationbar>
            </script>

            <script type="riot/tag">
                <footer class="footer text-right">
                    2018 &copy; <a href="https://www.digiq.co.jp/" target="_blnak">DigitalQuest,inc.</a>
                </footer>
            </script>

            <script type="riot/tag">
                <holders>
                    <div each="{value, index in list}" class="col-lg-4 col-sm-6 col-xs-12">
                        <holder index={index} data={value}></holder>
                    </div>
                    var testDatas = [];
                    testDatas[0] = { address: "0xf17f52151ebef6c7334fad080c5704d77216b732", balance: 10000 };
                    testDatas[1] = { address: "0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef", balance: 20000 };
                    this.on('mount', function() {
                        list = testDatas;
                        riot.update();
                    });
                </holders>
            </script>

            <script type="riot/tag">
                <mypage>
                    <holder mypage={true} index={index} data={value}/>
                    var testData = { address: "0x627306090abab3a6e1400e9345bc60c78a8bef57", balance: 100000 };
                    this.on('mount', function() {
                        index = 0;
                        value = testData;
                        riot.update();
                    });
                </mypage>
            </script>

            <script type="riot/tag">
                <holder>
                    <div class="bg-info" style="padding: 5px;">
                        <h4>{opts.data.address}</h4>
                        <h5>{opts.data.balance}</h5>
                        <button if={opts.mypage!=true} class="btn btn-success" onclick={onClickTransfer}>送金</button>
                    </div>
                    onClickTransfer = function () {
                        var div = document.createElement("div");
                        riot.mount(div, "popup-transfer", {"address" : opts.data.address });
                        $('#popup').html(div);
                        $('#popup').fadeIn();
                    };
                </holder>
            </script>

            <script type="riot/tag">
                <popup-transfer>
                    <div class="row bg-info popup-contents">
                        <p>TO: {opts.address}</p>
                        <div class="form-group">
                            <label for="example-email" class="col-md-12">送金数</label>
                            <div class="col-md-12">
                                <input type="number" class="form-control form-control-line" onchange="{onChangeForm}" name="coin" ref="coin" value="{coin}" placeholder="1000"/>
                                <br/>
                            </div>
                            <div class="col-sm-12">
                                <button class="btn btn-success" onclick={transfer}>Transfer</button>
                            </div>
                        </div>
                    </div>
                    coin = 100;
                    onChangeForm = function (dom) {
                        coin = dom.target.value;
                    };
                    // ポップアップを閉じる
                    $('#popup').on('click touchend', function(dom) {
                        if (!$(dom.target).closest('.popup-contents').length) {
                            $('#popup').fadeOut().load(function () {
                                $('#popup').html('');
                            });
                        }
                    });
                    // 送金アクション
                    transfer = function () {
                        $("#loader").fadeIn();
                        App.transfer(opts.address, parseInt(coin + '000000000000000000'), function(result) {
                            $("#loader").fadeOut();
                            alert(opts.address + '' + coin + ' 枚送金しました。');
                        });
                    };
                </popup-transfer>
            </script>

        </section>

        <section id="scripts">
            <script src="//ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
            <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
            <!-- Riot -->
            <script src="//cdn.jsdelivr.net/npm/riot@3.9/riot+compiler.min.js"></script>
            <!-- Riot tag base rooting -->
            <script src="//cdn.jsdelivr.net/npm/riot-route@3.1.3/dist/route+tag.min.js"></script>

            <!-- Truffle -->
            <script src="//cdn.jsdelivr.net/gh/ethereum/web3.js/dist/web3.min.js"></script>
            <script src="//cdn.jsdelivr.net/npm/truffle-contract@3.0.5/dist/truffle-contract.min.js"></script>

            <script>
                App = {
                    web3Provider: null,
                    contracts: {},
                    dev: false,
                    contractJSONPath: '',
                    ganache: false,

                    init: function(_contractJSONPath, _ganache = false) {
                        contractJSONPath = _contractJSONPath;
                        ganache = _ganache;
                        return App.initWeb3();
                    },

                    initWeb3: function() {
                        if (typeof web3 !== 'undefined') {
                          App.web3Provider = web3.currentProvider;
                        }
                        else {
                          if (false === (navigator.userAgent.indexOf('Chrome') > 0 && document.location.href.indexOf('localhost:3000') > -1)) {
                            // web3ナシでスタート確定
                            App.dev = true;
                            riot.mount('navigationbar');
                            riot.mount('footer');
                            riot.mount('router');
                            $("#loader").fadeOut();
                            return;
                          }
                          if (ganache) {
                            // this is Ganache develop network
                            App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
                          }
                          else {
                            // this is truffle develop network
                            App.web3Provider = new Web3.providers.HttpProvider('http://localhost:9545');
                          }
                        }
                        web3 = new Web3(App.web3Provider);
                        return App.initContract();
                    },

                    initContract: function() {
                        $.getJSON(contractJSONPath, function(Artifact) {
                            App.contracts.contract = TruffleContract(Artifact);
                            App.contracts.contract.setProvider(App.web3Provider);
                            try {
                                riot.mount('navigationbar');
                                riot.mount('footer');
                                riot.mount('router');
                                $("#loader").fadeOut();
                            }
                            catch (err) {
                                App.error(err);
                            }
                            return;
                        }).error(function(jqXHR, textStatus, err) {
                            jqXHR.message = 'コントラクト見つかりません。';
                            App.error(jqXHR);
                        });
                        return App.bindEvents();
                    },

                    bindEvents: function() {
                        //$(document).on('click', '#registerBtn', App.handleRegister);
                    },

                    success: function(result, callback, errmessage = '取引エラーが発生した為、取引を完了出来ませんでした。') {
                        console.log(result);
                        if ('undefined' != typeof result.receipt && 'undefined' != typeof result.receipt.transactionHash && 0 == result.receipt.transactionHash.indexOf('0x')) {
                            // 正常
                            if ('undefined' != typeof callback) {
                                return callback(result);
                            }
                            alert('正常に取引が完了しました。');
                            return;
                        }
                        alert(errmessage);
                    },

                    error: function(err) {
                        if (-1 < err.message.indexOf('property') && -1 < err.message.indexOf('route') && -1 < err.message.indexOf('null')) {
                            // このエラーは一旦無視
                            return;
                        }
                        console.log(err);
                        message = err.message;
                        if (-1 < err.message.indexOf('Invalid') && -1 < err.message.indexOf('JSON')) {
                            message = 'コントラクトと接続出来ませんでした。';
                        }
                        else if (-1 < err.message.indexOf('wasn') && -1 < err.message.indexOf('t processed in')) {
                            message = 'この取引は処理に時間が掛かっています。\n暫く待ってから取引結果を確認して下さい。';
                            err = null;
                        }
                        else if (-1 < err.message.indexOf('User') && -1 < err.message.indexOf('denied') && -1 < err.message.indexOf('transaction')) {
                            message = 'コントラクトの実行を中止しました。';
                            err = null;
                        }
                        alert(message);
                        $("#loader").fadeOut();
                    },

                    transfer:  function(address, coin, callback) {
                        if (App.dev) {
                            // テストデータの返却
                            callback(true);
                            return;
                        }
                        web3.eth.getAccounts(function(error, accounts) {
                            if (error) {
                                console.log(error);
                            }
                            var account = accounts[0];
                            if ('undefined' == typeof account) {
                                App.error({message: 'コントラクトに参加中のアドレスが見つかりませんでした。'});
                                return;
                            }
                            if (account == address) {
                                App.error({message: '自分自身に対してこの操作は出来ません。'});
                                return;
                            }
                            coin = parseInt(coin + '000000000000000000');
                            App.contracts.contract.deployed().then(function(instance) {
                                return instance.transfer(address, coin, '0xad', {from: account});
                            }).then(function(result) {
                                App.success(result, callback);
                            }).catch(function(err) {
                                App.error(err);
                            });
                        });
                    },
                };
                $(function() {
                    $("#loader").fadeIn();
                    $(window).load(function() {
                        App.init('./build/contracts/ERC223.json');
                    });
                });
            </script>
        </section>
    </body>
</html>
5
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6