Boilerplateを使ったDapps(分散型アプリケーション)の作成(3)

続きです。

sendeetech.hatenablog.com

sendeetech.hatenablog.com

この連載の最後に、ContractやViewを簡単にカスタマイズして、独自のDappを作成してみたいと思います。
テストネットに接続してマイニング中、MeteorでBoilerplateを起動中、ということを前提とします。
この辺りの詳しい話は、第1回と第2回に記載しています。

成果物

今回最終的に目指すのは、下記3つの画面(と機能)を作成することです。

  1. 契約作成(金利スワップの契約)画面を作成
    ・ SmartContractの契約情報をブラウザから入力可能
    ・ MongoDBにも契約情報を保管
    ・ マイニングにより契約がブロックチェーン(テストネット)に埋め込まれる
    ・ マイニングが完了するとContractのアドレスが取得可能になる

  2. 契約一覧画面
    ・ 作成した契約のサマリが一覧表として取得できる
    ・ 3の契約詳細画面へ動的リンクが貼られる

  3. 契約詳細画面
    ・ 登録された商品のIDを動的なURLとして、契約商品の詳細画面を表示する

Ethereumだけでなく、Meteorを使ったアプリケーションの基本的な作成の流れも理解出来るようになると思います。

1.契約作成画面

(1)コントラクトの記載

client/lib/contracts 以下にコントラクトファイルを作成します。
この記事を参考にして、コントラクトを記述しました。

contract SwapTrade{
    address public fixed_side;
    address public floated_side;
    uint public price;
    uint public expired_date;
    uint public fixed_rate;
    uint public spread;
    function SwapTrade(
      address _fixed_side,
      address _floated_side,
      uint _price,
      uint _expired_date,
      uint _fixed_rate,
      uint _spread
      ){
      fixed_side = _fixed_side;
      floated_side = _floated_side;
      price = _price;
      expired_date = _expired_date;
      fixed_rate = _fixed_rate;
      spread = _spread;
    }
}

詳細はリンク先の記事の通りですが、Contract名と同名のfunctionはコンストラクタとして機能します。
なので、上記のコードは、コンストラクタのみですが、一通りの動作を確認するには十分かと思います。
それぞれの変数は、契約に最初に登録する数値を設定しています。

  • fixed_side...固定金利サイド
  • floated_side...変動金利サイド
  • price...想定元本
  • expired_date...満期日
  • fixed_rate...固定金利
  • spread...変動金利

(2)コントラクトの入力部分

client/templates/components 以下に、契約登録用のhtmlとJSファイルを作成します。
それぞれ、 swaptrade.html swaptrade.js とします。
htmlファイルに Template['components_swaptrade'] という記載をしておくことにより、そのコンポーネント{{components_swaptrade }} で呼び出せます。

(3)実際のコード

そして、 swaptrade.js は下記のように記述をしました。
conditionsクラスのsubmitをイベントとしたfunctionを作成しています。
MongoDBへの保存も行っています。

  "submit .conditions": function(event, template){ // Create Contract
    TemplateVar.set('state', {isMining: true});
    event.preventDefault();

    // Set coinbase as the default account
    web3.eth.defaultAccount = web3.eth.coinbase;

    // Get Abi definition
    var abi = SwapTrade.abi

    // assemble the tx object w/ default gas value
    var transactionObject = {
      data: SwapTrade.bytecode,
      gasPrice: web3.eth.gasPrice,
      gas: 5000000,
      from: web3.eth.accounts[0]
    };

    var position = event.target.position.value;
    if (position  == "fixed" ){
      var fixedSide = web3.eth.accounts[0];
      var floatedSide = web3.eth.accounts[1];

    } else {
      var fixedSide = web3.eth.accounts[1];
      var floatedSide = web3.eth.accounts[0];
    }

    var client = event.target.client.value;
    var price = event.target.price.value;
    var issuedYear = event.target.issuedYear.value;
    var issuedMonth = event.target.issuedMonth.value;
    var issuedDay = event.target.issuedDay.value;
    var expiredYear = event.target.expiredYear.value;
    var expiredMonth = event.target.expiredMonth.value;
    var expiredDay = event.target.expiredDay.value;
    var fixedRate = event.target.fixedRate.value;
    var spread = event.target.spread.value;
    var issuedDate = new Date(issuedDay + "/" + issuedMonth + "/" + issuedYear);
    var parseIssuedDate = Date.parse(issuedDate);
    var expiredDate = new Date(expiredDay + "/" + expiredMonth + "/" + expiredYear);
    var parseExpiredDate = Date.parse(expiredDate);

    // estimate gas cost then transact new SwapTrade
    web3.eth.estimateGas(transactionObject, function(err, estimateGas){
      if(!err)
      transactionObject.gas = estimateGas * 10;

      SwapTrade.new(fixedSide, floatedSide, price, parseExpiredDate, fixedRate, spread, transactionObject,
        function(err, contract){
          if(err)
          return TemplateVar.set(template, 'state', {isError: true, error: String(err)});

          if(contract.address) {
            TemplateVar.set(template, 'state', {isMined: true, address: contract.address, source: source});
            contractInstance = contract;
            var contract_address = contract.address;

            //Mongoにコントラクト情報を保存
            Meteor.call('insert_contracts', issuedDate, expiredDate, client, position, fixedSide, floatedSide, price,  fixedRate, spread, contract_address, abi);
          }
        });
      });
    },

web3というgethのJavaScript APIを用いているため、 web3 でgethを操作することが出来ます。
詳細は公式のWikiを読んでもらえればと思いますが、例えば、 web3.eth.coinbase で、接続しているテストネットのCoinbase(メインとなるアカウント = 自分で設定出来ます)が取り出せたりします。

また、SwapTradeというオブジェクトは、上記で記載した .sol ファイルから作成されています。 Boilerplateでは、自動的にcontractsファイルで記載した .sol ファイルをオブジェクトとして利用出来るようになります。
これが非常に便利です。

「abi」(application binary interface)というのは、ざっくり捉えると「バイナリ化されたAPI」ぐらいに考えていただければ大丈夫だと思います。

transactionObject に格納しているのはコントラクトを送る際のオプションです。
gasとはマイニングした人に送られる報酬であり、gasが少なすぎるといつまでたってもマイニングがされず、トランザクションが実行されない、ということになってしまいます。
ここでは、5000000に設定しています。

event.target で取得している変数群は、フォームから入力された値を取得してきています。

そして、 SwapTrade.new(....) の部分がコントラクトをトランザクションする記述です。
オブジェクトにnewするだけで、コンストラクタを含めたコントラクトを簡単にブロックチェーンに送ることが出来ます。

同時に Meteor.call(....) の部分でMongoDBにデータを格納しています。
今回は、サーバー側で定義した insert_contracts メソッドを呼び出していて、これは(5)で説明します。
Meteor.call することで、定義したメソッドを呼び出すことができます。

(4) コレクションの定義

Mongoで扱うデータはコレクションとしてオブジェクト化することで使えるようになります。
lib/collections.js というファイルを作成して、そこで下記のように書いておきます。
そうすることにより Contracts というオブジェクトをインスタンス化し、メソッドでDBにアクセス出来ます。

Contracts = new Mongo.Collection('contracts');

(5) サーバー側の変数定義

クライアントから呼び出せるような insert_contracts メソッドをサーバーで定義します。
コードは下記の通りです。

if (Meteor.isServer) {
  Meteor.startup(function () {
    Meteor.methods({
      'insert_contracts': function(issuedDate, expiredDate, client, position, fixedSide, floatedSide, price, fixedRate, spread, contract_address, abi){
        Contracts.insert({
          issuedDate: issuedDate,
          expiredDate: expiredDate,
          client: client,
          position: position,
          fixedSide: fixedSide,
          floatedSide: floatedSide,
          price: price,
          fixedRate: fixedRate,
          spread: spread,
          contract_address: contract_address,
          abi: abi,
        });
      }
    });
  });
};

わざわざ複数ファイルに分けるほど、多くサーバー側にコードを書かないため、 server/main.js にまとめて記載します。

  • if (Meteor.isServer) の記述によりサーバー側のコードと明示します
  • Meteor.startup で、常に使えるfunctionを定義します
  • あとは、 insert_contract で引数と、 (4)で作成したContractsオブジェクトにより新規データをMongoに格納しています。

(6)最終形

最終的には、下記のような画面を作成しました。
Screen Shot 2016-05-24 at 22.08.55.png

作成するボタンを押すことで、マイニング待ちの状態になります。
同時にMongoDBにもデータを格納しています。
Screen Shot 2016-05-24 at 22.10.49.png

マイニングが完了することで、アドレスが発行されました
Screen Shot 2016-05-24 at 22.11.31.png

これで契約作成画面は完成です。

次回でいよいよ最終回です。
最後に、格納したデータを一覧としてみる、そのデータから動的に作成されたURLで詳細ページにアクセスする、という機能を実装していきます。