NEMを使ってYoのパロディサイトを作ってみたのでだらだらと振り返る

NEMを使って何か作ってみたかったので「Yo.」のパロディサイトを作ってました。
~~https://ya-greeting.firebaseapp.com/~~
~~※テストネットです。~~
Firebase Hostingのプロジェクト作成数の限界が来たので止めました。

当初、本家Yoにならって4/1に公開しようかと思ったんですが、自分の期待よりもしょぼいものが出来上がってしまいました。
なので、とりあえずQiitaで振り返ろうと思います。

## NEMについて
知っている人も多いと思いますが、暗号通貨の一種です。日本で手に入りやすい＆プログラミングがし易いのが特徴だと思います。
2016年の記事なので今も同じ方針かわかりませんが、「スマートインテグレーション」を実現しているとのことです。
>スマート・コントラクトとは反対に、「スマート・インテグレーション」を実現しており、それによって、企業が各々のビジネス・ルールや契約を分けてコントロールできるようになっています。ふさわしい取引は、インテグレーション（統合）によて作られたルールにとって行われ、それと同時に、混和性台帳の環境において複数の台帳を共存させることができるのです。
https://blog.nem.io/jp-a-major-announcement/

確かに、スマコン部分が自分たちでコントロールできるというのはロックインという観点から考えると気楽に扱えそうでよいですね。途中までNEMで運用してトランザクション増えてきたらmijinへ移行するとかできたりすると個人的に理想です。


## 何が出来るサイトなのか？
１．Ya用ウォレットの作成
２．Yaトークンの購入
３．ニックネームの登録
４．他のユーザーを探す
５．Yaトークンを送る

![output.gif](https://qiita-image-store.s3.amazonaws.com/0/165335/17015c7d-130c-a18f-7313-eff84f542fb8.gif)
※モザイクトークンを送ってる様子。トランザクションが確定すると送った分のYaトークンが減ります。

本当はPush通知とか、トランザクション履歴が見れたりとかしても良かったかもしれないんですけどね。

## ソースコード
決して見やすい状態ではないと保険をかけておく。
・Ya本体 
https://github.com/scrpgil/Ya
※Ionicで作ってあります。PWA対応のWebサイト作る時にIonic使うと楽ですよ。

・着金監視
https://github.com/scrpgil/Ya-trigger
※もう一つニックネーム登録用のプログラムがありますが、Firebase APIをべた書きしているので上げるのはアップロードしていません。

## 各処理の解説
おおざっぱですが、各処理の解説を・・・

### Yaトークンの購入シーケンス
登場人物は以下の通り
・NanoWallet
・Ya本体
・NIS
・着金監視プログラム
※Ya本体、着金監視プログラムはNISとWebsocketsで通信しています。

処理の流れ
１．NanoWalletからYaトークン販売用アドレスに1xem送金
２．入金が完了したら着金監視プログラムから100Yaトークン送付
３．Ya本体に入金が反映される

シーケンス
<img width="570" alt="Screen Shot 2018-03-25 at 0.11.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/165335/7ce62677-6af0-5d3e-47f6-7e668cdcc687.png">

Ya本体と着金監視プログラムがNISを介して通信しているので不思議な感じですね。各ウォレットの残高は分散台帳のNISが管理しているから、NISに対して入金や残高情報の取得を行います。

### ニックネーム登録のシーケンス
登場人物は以下の通り
・Ya本体
・NIS
・ニックネーム変更監視プログラム
・Firebase RealtimeDatabase
※Ya本体、着金監視プログラムはNISとWebsocketsで通信しています。

処理の流れ
１．Yaからニックネーム変更用アドレスへニックネームをメッセージに乗せて書き込み
２．入金が確認されたらニックネーム監視プログラムがFirebase RealtimeDBに書き込み
３．ニックネーム検索はFirebase RealtimeDBに対して行う

シーケンス
<img width="710" alt="Screen Shot 2018-03-25 at 0.29.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/165335/fb5a501e-efa8-d52b-5a65-d66ec6b2f806.png">

当初はDBなしのNISだけで完結を目指したんですが無理でした。登録されたニックネームに対して検索する方法がわからなかったので...。
Firebase RealtimeDBを使うなら別にニックネームをNISに書き込む必要はありません。手数料かかるし。唯一の利点は、もしFirebase RealtimeDBが吹っ飛んでもNISに書き込まれたトランザクションで復旧ができることでしょう。


# ソースコードの振り返り
## ウォレットまわり
### ウォレットの作成
NEM-sdkを使えば関数一発でできました。

https://github.com/scrpgil/Ya/blob/2dfb252e1c88a92eb467d16cbdf7eed98a053513/src/providers/nem/nem.ts#L113

````typescript
    createWallet(walletName, password){
        let wallet = nem.model.wallet.createPRNG(walletName, password, nem.model.network.data.testnet.id);
        return wallet;
    }
````

上記関数をたたくと以下のようなJSONが返ってきます。秘密鍵は暗号化されています。ウォレット作成時に入力したパスワードを用いることで複合できます。

````json
{
  "name": "Ya-wallet-1521906460517",
  "accounts": {
    "0": {
      "brain": true,
      "algo": "pass:bip32",
      "encrypted": "8e7d9be8365205fc239e57651b438fc92a4782876f72193004e043bddad328d6217f342ef1781e512b9a5e52216871a6",
      "iv": "42f2901d7cb39a6540358bf05e3a332f",
      "address": "TBGDUCQI2CUGDMWMQ7W3HH7W5JSZTAL6CA3YYEE5",
      "label": "Primary",
      "network": -104
    }
  }
}
````

・ウォレット作成時のパスワード入力画面
<img width="395" alt="Screen Shot 2018-03-25 at 0.59.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/165335/b6b7ec74-77ef-5944-d4fc-56f0d0870fc1.png">

#### ウォレット名について(2018年4月1日追記)

Ya!ではウォレット名を自動生成しています。「Ya-wallet-Timestampe」の形で。
これは、以下の理由です。
・ユーザーのウォレット名入力という手間を省きたかった
・TimeStampがついているのはNanoWalletで読み込む際に同一ウォレット名だと読み込みができないため。

サービスによっては、「アカウント＝ウォレット」という認識でもよいかと思います。

### ウォレットのエクスポート
NanoWalletでアカウント作成をする時にもでてくるウォレットのエクスポートです。
先程のウォレットのJSONをbase64でエンコーディングしてエクスポートします。

https://github.com/scrpgil/Ya/blob/2dfb252e1c88a92eb467d16cbdf7eed98a053513/src/pages/signup/signup.ts#L68

````
    goToBackup(){
        let date = new Date();
        let a = date.getTime() ;
        this.wallet = this.nem.createWallet("Ya-wallet-"+a, this.password);
        this.base64Wallet = btoa(JSON.stringify(this.wallet));//ここでbase64エンコーディング
        this.pageState = this.BACKUP;
        this.store.dispatch(new AccountAction.RegisterWallet(this.wallet));
    }
   exportWallet(){
        var blob = new Blob([this.base64Wallet]);
        var url = window.URL || (window as any).webkitURL;
        var blobURL = url.createObjectURL(blob);

        var a = document.createElement('a');
        a.download = "Ya-wallet.wlt";//download属性はiOSだと動かない
        a.href = blobURL;
        a.click();  
    }
````

ちなみにbase64エンコーディングしたものは以下です。

````
eyJuYW1lIjoiWWEtd2FsbGV0LTE1MjE5MDY0NjA1MTciLCJhY2NvdW50cyI6eyIwIjp7ImJyYWluIjp0cnVlLCJhbGdvIjoicGFzczpiaXAzMiIsImVuY3J5cHRlZCI6IjhlN2Q5YmU4MzY1MjA1ZmMyMzllNTc2NTFiNDM4ZmM5MmE0NzgyODc2ZjcyMTkzMDA0ZTA0M2JkZGFkMzI4ZDYyMTdmMzQyZWYxNzgxZTUxMmI5YTVlNTIyMTY4NzFhNiIsIml2IjoiNDJmMjkwMWQ3Y2IzOWE2NTQwMzU4YmYwNWUzYTMzMmYiLCJhZGRyZXNzIjoiVEJHRFVDUUkyQ1VHRE1XTVE3VzNISDdXNUpTWlRBTDZDQTNZWUVFNSIsImxhYmVsIjoiUHJpbWFyeSIsIm5ldHdvcmsiOi0xMDR9fX0
````

エクスポートしたウォレットはNanoWallet等にインポートすることが可能です。（なんか、バージョンが古いってエラー出るけどパスワードを入力したら自動的にアップデートされました。）

・ウォレットエクスポート画面
<img width="523" alt="Screen Shot 2018-03-25 at 0.58.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/165335/384894e0-aed2-a73d-454a-fc7de93a3077.png">


### 秘密鍵のデコード＆ログイン

ウォレット作成時のパスワードを使えば秘密鍵を取り出せます。秘密鍵はYaトークンの送付や、ニックネームの登録時に必要になります。
とりえあずログイン時にパスワードを入力してもらい秘密鍵が取り出せるかどうか確認しています。

・ログイン画面（ウォレット作成後）
<img width="397" alt="Screen Shot 2018-03-25 at 0.59.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/165335/ff88be4b-70d7-4450-e970-9a731755443d.png">


https://github.com/scrpgil/Ya/blob/2dfb252e1c88a92eb467d16cbdf7eed98a053513/src/providers/nem/nem.ts#L232

````
    login(password){
        if(this.wallet){
            this.common = nem.model.objects.create("common")(password, "");
            let result = nem.crypto.helpers.passwordToPrivatekey(this.common, this.wallet.accounts[0], this.wallet.accounts[0].algo);
            console.log(result);
            if(64 <= this.common.privateKey.length && this.common.privateKey.length <= 66){
                this.store.dispatch(new AccountAction.RegisterPassword(password));
                return true;
            }else{
                return false;
            }
        }else{
            return false;
        }
    }
````


ちなみに、YaではYaトークン送金時に毎回パスワードを入力するのも面倒かな？と思ったので、パスワードはログイン時に入力したらStoreで保存し以降は入力の必要がないようにしています。
ブラウザの更新をするとまた、入力が必要になります。この仕様がいいか悪いかは扱う額によって変わってきそうですね。今回は、謎トークンを送付するぐらいなのでよいかな。

## 送金まわり
### モザイク（Yaトークン）の送金
NEM-sdkを使ったモザイク送金の処理はざっくり下の作業が必要になる様子。
１．モザイク送金用トランザクションのオブジェクトを生成
２．送金

本家NanoWallletにモザイク送金の細かい処理は乗っているのでそれをパクって作ったコードが以下。

https://github.com/scrpgil/Ya-trigger/blob/3365d0a01b0da0e2ab960db29b96c44247c66ce9/src/util/util.ts#L97

````
    prepareTransaction(amount, recipient:string = "", message:string = "" , mosaicsAmount:number = 0) {
        // Create a new object to not affect the view
        let cleanTransferTransaction = nem.model.objects.get("transferTransaction");

        cleanTransferTransaction.recipient = recipient;

        cleanTransferTransaction.message = message;
        cleanTransferTransaction.messageType = 1;

        let entity:any;
        if(mosaicsAmount > 0){
            cleanTransferTransaction.amount = 1;
            let xemMosaic = nem.model.objects.create("mosaicAttachment")("nem", "xem", amount);
            let yaMosaic = nem.model.objects.create("mosaicAttachment")("greeting", "ya", mosaicsAmount); 
            let metaData = this.getMosaicMetaData();
            let m = this.cleanMosaicAmounts([xemMosaic, yaMosaic],metaData);
            cleanTransferTransaction.mosaics = m;
            console.log("metaData:");
            console.log(metaData);
            console.log("m:");
            console.log(m);
            entity = nem.model.transactions.prepare("mosaicTransferTransaction")(this.common, cleanTransferTransaction, metaData, this.network_id);
        }else{
            cleanTransferTransaction.amount = amount;
            cleanTransferTransaction.mosaics = null;
            entity = nem.model.transactions.prepare("transferTransaction")(this.common, cleanTransferTransaction, this.network_id);
        }

        return entity;
    }
````

面倒くさい手数料計算もNEM-sdkがやってくれます。素晴らしいですね。
https://github.com/QuantumMechanics/NEM-sdk/blob/20884d9ea93a03f65016eca00d1be44f853e613f/src/model/fees.js#L124

### モザイク手数料計算でハマった所
NEM-sdkを使ったモザイク手数料計算の処理で最初、手数料が「NaN」ってなってしまってハマってました。
どうやら手数料計算に必要な発行総量の取得方法に癖があるようです。そのあたりのやり取りは以下のissueを見てもらえれば...。
https://github.com/QuantumMechanics/NEM-sdk/issues/36

ざっくり説明すると
１．nem.com.requests.namespace.mosaicDefinitions()でinitialSupply（初期発行総量）を取得できるがこれは、発行総量を変更しても変わらない
２．nem.com.requests.mosaic.supply()で取得すると発行総量の変更にも対応できる。
３．nem.model.transactions.prepare("mosaicTransferTransaction")には、nem.com.requests.namespace.mosaicDefinitions()で取得した構造体にnem.com.requests.mosaic.supply()で取得したsupplyの値をくっつけたオレオレ構造体を突っ込むと良い。
とのことでした。
※ nem.com.requests.mosaic.supply()はREADMEに乗っていないけど存在する関数です。裏関数かもしれません。そして、/mosaic/supplyというAPIはNISのAPIドキュメントに載っていない気がします。こちらも裏APIなのかもしれませんね。

本当かよ！と思いましたが、NanoWalletでもそのような処理をしているっぽいのでまあ、そうなんでしょう。
https://github.com/NemProject/NanoWallet/blob/be6f84ef88bcd9863654797a832d8d99a96c1320/src/app/modules/explorer/namespaces-mosaics/namespaces-mosaics.controller.js#L181
このあたり、もう少し検証していつか別記事で書いてもよさそうですね。

ちなみに、NEM-libraryだとinitialSupplyのみで手数料計算しているので、後々発行総量を変更したら「FAILURE_INSUFFICIENT_FEE」のエラーになるかもしれませんね。
https://github.com/aleixmorgadas/nem-library-ts/blob/d8df40c5a1f8b2cc858e416d7c0f9801d21e6d2d/src/models/transaction/TransferTransaction.ts#L209

私はNEM-libraryの方は使ってないですが、もし使ってる方がいたらコントリビュートするチャンスなのでだれかぜひ。


## NISとの通信まわり
### 自分で立てるか？誰かのNISを使わせてもらうか？
お金に余裕があるなら自分でNISを立てた方がよいかと思います。NEMは300万XEMないとNISを立てれない雰囲気を感じますが、それはスーパーノードの要件というだけで普通のノードなら誰でも立てることができます。
今あるNISはSSL通信対応していないものが多いので、使えるNISを頑張って探すより自分で立てるほうが速いと思っています。
邪推ですけどやっぱりスーパーノードは300万xem保有しているので独自ドメイン前提のSSL通信対応はやりたくないのかも知れませんね。ドメインから簡単にIPアドレスが割れちゃいますものね。

### TestnetのNISの建て方
ここに素晴らしい記事がありました。これを見てNISを立てました。実作業10分くらいでNISは立てれます。
https://blog.44uk.net/2018/02/12/up-nem-testnet-node/

NISはGMOクラウドVPS 1GBプラン（780円）で運用していますが今のところ問題なく動いています。

### https通信
今回、Webサイトのホスティング先がFirebase Hostingです。なんと、Firebase HostingはSSL通信以外の通信を一切許さない仕様です。ハードコアな仕様ですが、時代の流れなので、仕方がありません。
https://firebase.google.com/docs/hosting/deploying?hl=ja

なので、TestnetのNISをSSL通信に対応させています。https通信への対応は以下の記事を参考にしています。30分もかからずにSSL通信に対応できると思います。
https://blog.nem.io/https-nis-node/


### Mainnet版のNISについて
Yaでは使っていませんが、Mainnet版のNISも簡単に立てれます。しかし、GMOクラウドVPS 1GBプランではメモリが枯渇するようです。
現在、GCPのカスタムインスタンスでvCPU x 1、メモリ 2.75 GBで動かしていますが、まだ足りなさそうです。やはりみなりんさんのブログに書いてあるように4GBないと厳しいのかもしれません。
http://mizunashi-rin.hatenablog.jp/entry/2017/02/12/114147

<img width="813" alt="Screen Shot 2018-03-25 at 2.02.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/165335/0a991874-1351-a8ab-35d5-b038638129c6.png">
※グラフを見るにあんまりCPUは使っていない様子。

### NISとの中継プログラム
昔作ったので一応紹介。Websocketsに対応していません。
https://github.com/scrpgil/transfer-nem-api

# まとめ
## 作ってみてどうか？
当初はDBを使わずNISだけで全部実装できるか模索していましたが、検索ができないので断念しました。やはり、分散台帳という肩書なのでDBの代替というのは向いていないのでしょう。

## 仕様として難しいと思ったところ
作ろうと思った当初は「面白いかな！」と思ったんですが、作ってみたらあまり面白くありませんでした。恐らくブロックチェーンでやる意味が皆無だったからでしょう。ここらへん結構難しいですね。

あと、Yaトークンの送金にXemが手数料としているところはサービスを考える上で地味に面倒な仕様だと思いました。せっかくおれおれトークンのYaを発行したんだからそれだけで済むと理想的なんですが、手数料としてXEMが必要になるとは...。これは、NEMに限ったわけではなく他の暗号通貨上でおれおれトークンを発行してもやはりプラットフォーム通貨が必要になるようです。
ここらへんはCatapultのアグリゲートトランザクションという仕組みで誰かが手数料を肩代わりできるそうなのでそれに期待したいです。あとプライベートブロックチェーンのmijinにも期待したいところ。

## 最後に
これからもNEM関係のプログラムをいろいろ作る予定ですが、これからはもっとトランザクションそのものに価値をもたせるようなものを考えたいです。後、おれおれトークンを発行するのではなくもっと暗号通貨を使いやすくする方向で考えたいですね。
おわり。