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

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

## 前提

- 執筆者はMacでやっている
- npmは入ってる
- `MetaMask`も入っている
	- [参考:「Ethereumブロックチェーンでめそ子の握手券を作ってみた」](https://dev.classmethod.jp/etc/sell-handshake-voucher-in-ethereum-blockchain/)


## レシピ

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


## Truffleをインストール

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

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

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

## Contractを作る

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

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

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

ワンソースで済ませられるサンプルにするために[SafeMath](http://blockchain.gunosy.io/entry/erc20-token-Vulnerability)とか入っていないけど本当は入れたほうが良いよ！

```solidity
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/](https://remix.ethereum.org/)

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

[※RemixからRinkebyへのデプロイはこの此方を参考にしましたm(_ _)m   
「Ethereumブロックチェーンでめそ子の握手券を作ってみた」 => https://dev.classmethod.jp/etc/sell-handshake-voucher-in-ethereum-blockchain/](https://dev.classmethod.jp/etc/sell-handshake-voucher-in-ethereum-blockchain/)

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

[https://rinkeby.etherscan.io/tx/0x4640afd04665d6c75bbd9eee87ab53d6c19d9e03ff52d57713b4a317503c5523](https://rinkeby.etherscan.io/tx/0x4640afd04665d6c75bbd9eee87ab53d6c19d9e03ff52d57713b4a317503c5523)

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

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

![](https://qiita-image-store.s3.amazonaws.com/0/151630/49185295-e2b4-25ef-9143-92ade608a8c4.png)

[https://rinkeby.etherscan.io/token/0x99b5df47024ee4f1266e6fc4d20666ed6fbed197](https://rinkeby.etherscan.io/token/0x99b5df47024ee4f1266e6fc4d20666ed6fbed197)

![](https://qiita-image-store.s3.amazonaws.com/0/151630/2f397e80-52af-831e-28f2-940b7d6bd578.png)

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


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


## RiotでUIを作る

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

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

※[Riot公式](https://riot.js.org/ja/)

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

```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サーバを立ち上げて確認してみる

```shell
python -m SimpleHTTPServer 8888
```

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

[http://localhost:8888/](http://localhost:8888/)

![](https://qiita-image-store.s3.amazonaws.com/0/151630/8b7042d1-226e-f8b7-b448-65fc2a99fbb5.png)

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


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

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

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

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

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

```schell
truffle devlop
truffle(develop)> migrate
```

すると・・・

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

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

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

`ERC223.json`の7980行目付近

```json
"networks": {},
```

ここを

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

こう変えます。

`"4"`の部分は固定

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

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

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

それぞれ入れる

あと、136行目からの

```json
    {
      "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の読み込みを加える

```html
			・・・
			<!-- 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`クラスを定義

```javascript
			・・・
			<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のロードで初期化するように書き換え

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

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

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

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

```javascript
			<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>
			・・・
```

**はい、完成！！**

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

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

![](https://qiita-image-store.s3.amazonaws.com/0/151630/91bb1a69-6070-5c67-f2ea-947ad94ca697.png)

無事に`MetaMask`起動！

SUBMITすると・・・

[https://rinkeby.etherscan.io/token/0x99b5df47024ee4f1266e6fc4d20666ed6fbed197](https://rinkeby.etherscan.io/token/0x99b5df47024ee4f1266e6fc4d20666ed6fbed197)

![](https://qiita-image-store.s3.amazonaws.com/0/151630/7e3f23b5-52aa-024b-2ec6-303f50a24616.png)

**ちゃんと送金されてHolderが増える！ (´v｀)v**


## まとめ

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

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

```json
  
  "networks": {
    "4": {
```
を

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

**是非是非お試しあれ！**

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

## おまけ

最終html

```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>
```