Unityで仮想通貨NEMの入門

ビットコイン等の高騰やPCでのマイニングすると儲かるらしいなどの話を聞いて、投機対象として仮想通貨に興味を持っているUnityエンジニアの方も増えているのではないでしょうか。

今回は投機対象としてではなく技術的な側面からとある仮想通貨（暗号通貨）に触れてみよう、という記事です。ﾕﾆﾃｨﾁｮｯﾄﾜｶﾙ人を対象としています。
前半はNEMの概要とUnityとの関連性について触れ、後半はUnityでNEMの処理を行います。

1. NEMとは
2. UnityWebRequestでアドレスの情報を確認する
3. CSharp2nemを使って送金してみる


<b>1. NEMとは</b>
仮想通貨（暗号通貨）のプロジェクトのことです。
その詳細については本記事の主旨ではありませんし、既に先人による素晴らしい記事があります。詳しくは下記を参照してください。NEMに何か動きがあった場合は都度更新されており大変オススメです。
http://www.cryptostream.jp/nem_xem/

上記の記事の内容で本記事において重要なのはNEMであれば簡単にSN（スーパーノード）を通して安定した送金処理等ができる、ということです。

そもそも、NEMとUnityの組み合わせは唐突と思う方もいるかもしれません。
一番有名なビットコインでもぼくは事例を見つけられていません（扱うためのプラグインは存在しているようですが）し、最近UnityはGameCreditsと[パートナーシップを結んだ](https://unity3d.com/jp/company/public-relations/news/gamecredits-signs-deal-unity-technologies-bring-blockchain-game)*1 ばかりなので、取り上げるべきはそちらでは？と思う方もいらっしゃるでしょう。

しかしぼくはNEMber*2 でNEMしかかじっていませんし、何より既にNEM x Unityは採用実績が少ないですが存在しています。GameCreditsのことは誰かに任せるとして、2つの事例を見てみましょう。

*1 しかもGameCreditsは去年の段階でアセットストアに[アセットを出していた](https://assetstore.unity.com/packages/tools/integration/gamecredits-plugin-63973)(!)ようです。
*2 NEMのことが好きで利用していたり普及させようとしている人たちのこと（だと思ってます）。NEMberという表記はNanoWalletというウォレットソフトでのログイン時等に表示されます。

<b>Send ‘em to Hell - http://xhaistudios.com/portfolio/send-em-to-hell/</b>
![Send 'em To Hell Game Trailer - YouTube 2017-11-13 01-27-35.png](https://qiita-image-store.s3.amazonaws.com/0/213908/2c335e8a-eb88-6e6b-955a-176f91ef2d14.png)


NEMのウォレットを搭載したモバイルゲームです。
[このページ](http://xhaistudios.com/nem-blockchain-and-xhai-studios-announce-partnership/)で触れられていますが、NEMを使用することで（ブロックチェインの恩恵を受けて取引内容の改ざん等をとても困難にするだけでなく）ストアの仲介を避けることで課金時の手数料を削減したり、NEMの通貨XEMを課金してゲーム内アイテムを購入したり、将来的にはゲームをプレイすることでポイントを貯め、逆にそのポイントをXEMに変換し、ゲームで収益を得るということを可能にするようです。

NEMは非常に送金が高速(Mainnet*3 の場合、現時点では1~2分程度)なことが強みの1つです。GameCreditsは送金したことがないのでわかりませんが、ビットコインは少なくとも数時間単位で時間がかかりました（これも設定した手数料等の要因に左右されると思いますが）。利用がとても容易ということも合わせて、ゲーム内課金でNEMを利用するのは現実的な選択肢と言えるのではないでしょうか。
送金の速さは利用者の数によるのでは？と思った方は鋭いですが、NEMには来年カタパルトというものが導入される予定で、それによってさらに大量の取引がこなせるようになるので大丈夫です。どのくらいかと言うと、クレジットカードのVISAに迫るくらいです。

ちなみにスマホアプリの主要なストアは下記のリンクの通り、規約でストアが提供する以外の課金方法を禁じているはずです。そのためNEMでの課金をアプリ内で行なっている場合、AppStoreでは審査に通らず Google Play ではあとからリジェクトされるはずです。上記アプリのiOS版は課金部分が動作しなかったためなんとも言えませんが、現実的にはNEMでの課金を使用できるのはAndroidの独自ストアを使用する場合や、PC、WebGL等に限られるでしょう（UnityのWebGLがスマホでも動作するようになればいいのにな・・）
https://developer.apple.com/app-store/review/guidelines/jp/
https://play.google.com/intl/ja/about/monetization-ads/

*3 本番環境のパブリックネットワークのこと。対してテスト環境はTestnetといいます。

<b>VERSES - http://verses.io/</b>
![verses.png](https://qiita-image-store.s3.amazonaws.com/0/213908/66e4a3a9-6de5-494b-39e5-1b1602a72981.png)

VR空間内で仮想通貨を使った売買等を完結させるソリューションです。下記リンクのデモ動画では送金処理後飛行機のモデルが表示され、その後その飛行機に乗って空を飛んでいる様子が見られます。
https://www.youtube.com/watch?v=i8WRcOzhJE4

Unity使ってますとは明言されていませんが、エディタが思いっきりUnityですね。

蛇足ですが、最近VERSESはCOMSAでのICOが発表されていました。
COMSAについては http://www.cryptostream.jp/comsa-6063/ を、ICOの概要については https://comsa.io/ja/54265.html を参照してください。


<b>2. UnityWebRequestでアドレスの情報を確認する</b>

ここからは実際にNEMに触れていきます。NEMにはAPIが用意されており、UnityWebRequest等で操作することができます。

今回はぼくの用意したウォレットのアドレスを使用しますが、本来はNanoWalletというウォレットアプリのインストールや入金を行う必要があります。
この後は送金処理も行いますので、そういったこともやってみたいと思う方は下記の記事を参考にNanoWalletをインストールしておきましょう。送金をしたい場合、Mainnet以外にTestnet用のウォレットも作成しておきましょう。
https://qiita.com/nem_takanobu/items/4636ce972c736bfc342f

さて実践です。

https://qiita.com/nem_takanobu/items/58d0586a0d53b6d0f48e
こちらの記事に掲載されているノードリストからランダムなSNを使用し、そちらに問い合わせを行うURLにアクセスして返ってきた値を Debug.Log() で表示するだけのコードを書きます。

```
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

/// <summary>
/// UnityWebRequestを使ってNEM APIでアカウント情報を調べるサンプル.
/// </summary>
public class NemGetAmountTest : MonoBehaviour {

    private string[] nodeList = new string[] {
        "http://62.75.171.41:7890",
        "http://san.nem.ninja:7890",
        "http://go.nem.ninja:7890",
        "http://hachi.nem.ninja:7890",
        "http://jusan.nem.ninja:7890",
        "http://nijuichi.nem.ninja:7890",
        "http://alice2.nem.ninja:7890",
        "http://alice3.nem.ninja:7890",
        "http://alice4.nem.ninja:7890",
        "http://alice5.nem.ninja:7890",
        "http://alice6.nem.ninja:7890",
        "http://alice7.nem.ninja:7890"
    };

    private const string Infix = "/account/get?address=";
    private const string MyNemAddress = "NAYMZYSY37TRDGDTMDZVW4LOS3ZX4QUMJR35VVXY";

    // Use this for initialization
    IEnumerator Start () {

        // ノード選択, URL決定, リクエスト送信.
        var node = nodeList [Random.Range (0, nodeList.Length)];
        var url = node + Infix + MyNemAddress;
        Debug.Log (url);
        UnityWebRequest request = UnityWebRequest.Get (url);

        // リクエストの結果待ち受け.
        yield return request.SendWebRequest ();

        // リクエストの処理.
        if (request.isNetworkError || request.isHttpError) {
            Debug.Log (request.error);
        } else {
            var json = request.downloadHandler.text;
            Debug.Log (json);
        }
    }
}
```

こちらのコードの場合以下のようなURLをブラウザで開いたのと同じような結果を得られます。
http://nijuichi.nem.ninja:7890/account/get?address=NAYMZYSY37TRDGDTMDZVW4LOS3ZX4QUMJR35VVXY

検証用に作っただけの財布であるため、初期値が並んでいます。

この要領で頑張れば自前で様々なことができると思いますが、なんとC#用のプラグインがあるため、次はそちらを使って送金もしてみましょう。

<b>3. CSharp2nemを使って送金してみる</b>

C#用プラグインは CSharp2nemと言います。
https://github.com/NemProject/csharp2nem/

Readmeにも書かれていますがドキュメントは下記ページにあります。
http://pickledrick.ddns.net/Help/Documentation.html

CompatibilityにUnity3dとあるように、Unity上でも動作します。
中身はHttpRequestを叩くものとなっているため環境を選ばないようです。

3.1 CSharp2nemのインストール

Visual Studioを使用できない環境を想定しています。
NuGetを使える場合素直にNuGetを使うのが良いですが、それが上手くいかない方も参考にしてください。

まずはパッケージをNuGetのサイトから手動でダウンロードします。
https://www.nuget.org/packages/CSharp2nem/

![cs2nem.png](https://qiita-image-store.s3.amazonaws.com/0/213908/2818164c-4fdc-d69c-9a92-4942f0b59595.png)

.nupkg は実際には .zip と同じものです。拡張子を .zip に変え解凍します。そうして出てきたフォルダ(csharp2nem.2.0.5)の lib の中身をUnityにインポートします。

3.2 Json.NETのインストール

次に、依存するパッケージのインポートです。 
今回の場合 Json.NET です。CSharp2nem.nuspecによると依存するバージョンは 8.0.1 のようなのでそちらを取ってきましょう。

下記リンクの下部、Json80r1.zipをクリックしダウンロードします。
https://github.com/JamesNK/Newtonsoft.Json/releases/tag/8.0.1

解凍したフォルダの Bin > Net35 > Newtonsoft.Json.dll をインポート…というのが順当な手順だと思いますが、この場合後々エラーが出ます。えー。
こちらに入っているdllは .Net3.5 に対応しており、所持している通貨の残高は確認できるのですが、その場合でも Unity 側で Scripting Runtime Version を 4.6 にしないと何故か送金のタイミングでエラーを吐き動作させることができません。
![scriptback.png](https://qiita-image-store.s3.amazonaws.com/0/213908/45ebf4dc-64fc-46d9-1f6b-83b2dd14b16e.png)


Player Settings > Other Settings > Configuration より変更します。

3.3 Testnet Faucetを使う

所持通貨の確認や送金処理を行うにはまずXEMを用意する必要があります。
NEMにはFaucetというものがあり、Mainnet, Testnetに関わらず一定時間ごとにXEMをもらうことができます。
今回はテスト環境用にこちらを利用させてもらいます。
http://namuyan.dip.jp/nem/testnet/

先ほど作成したNanoWalletを立ち上げログインし、送信 > 請求書を押すと支払先の欄に表示されます。
![wallet.png](https://qiita-image-store.s3.amazonaws.com/0/213908/52f5b0b4-8a2e-7ede-dcad-97d8fa213150.png)


Tから始まるものはテストネットのアドレスということを表しています。
こちらのアドレスを先ほどのページの NEM Address に貼り、reCAPTCHAを済ませて Click! を押せばウォレット宛にXEMが送金されます。

少し長かったですがこれで準備完了です！

3.3 モザイクの残高確認と送金

作成するサンプルは以下のGIFのようなものです。
Updateボタンを押すと自分の持っている通貨一覧を表示し、 Sendを押すとXEMを送ります。
![sendtest.gif](https://qiita-image-store.s3.amazonaws.com/0/213908/a86f95bf-3c5e-d574-d4e9-9ebeecffa05a.gif)


ﾕﾆﾃｨﾁｮｯﾄﾃﾞｷﾙ人を対象にしているのでUIの並べ方などの説明は省略し、駆け足気味でいきます。使用したスクリプトは以下の通りです。

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using CSharp2nem.Connectivity;
using CSharp2nem.RequestClients;
using CSharp2nem.Model.AccountSetup;
using CSharp2nem.Model.Transfer.Mosaics;
using CSharp2nem.Model.DataModels;

/// <summary>
/// CSharp2nemを使って所持モザイク一覧確認と送金を行うサンプル.
/// </summary>
public class SendTest : MonoBehaviour {

    private const string MyAddress = "TAJLIU5D255AOV7UDDXSUIO3IPCZ7JP2E7BVYG2S";
    private const string PrivateKey = “ここに秘密鍵を書く。実際の開発ではこのようなベタ書きはダメ絶対";

    [SerializeField]
    private Button updateButton, sendButton;

    [SerializeField]
    private Text resultText;

    private Connection connection = new Connection();

    void Awake()
    {
        // 接続先はテストネットに.
        connection.SetTestnet();    
    }

    // ----- 所持通貨確認 -----
    public void UpdateOwnedMosaicsList()
    {
        StartCoroutine (updateOwnedMosaicsListCor ());
    }

    IEnumerator updateOwnedMosaicsListCor()
    {
        setButtonsEnable (false);

        var mosaicClient = new NamespaceMosaicClient (connection);
        var result = mosaicClient.BeginGetMosaicsOwned (MyAddress);

        resultText.text = "Waiting...";
        while (!result.IsCompleted) {            
            yield return 0;
        }

        var response = mosaicClient.EndGetMosaicsOwned (result);
        resultText.text = response.ToLog ();

        setButtonsEnable (true);
    }

    // ----- 送金 -----
    public void Send()
    {
        StartCoroutine(sendCor());
    }

    IEnumerator sendCor()
    {
        setButtonsEnable (false);

        // 送金用クライアント設定.
        var accountFactory = new PrivateKeyAccountClientFactory (connection);
        var accClient = accountFactory.FromPrivateKey (PrivateKey);

        // トランザクションデータ作成.
        var mosaicList = new List<Mosaic> () {
            new Mosaic ("nem", "xem", 1000000)
        };

        var transData = new TransferTransactionData () {
            Amount = 1000000,
            Message = "test",
            RecipientAddress = "TCDAP7EJZCR3EEBWNI3JQGFB67Y6HZR7VSVTJN4E",
            ListOfMosaics = mosaicList,
        };

        // トランザクションデータ送信.
        var asyncResult = accClient.BeginSendTransaction(transData);

        resultText.text = "Waiting...";
        while(!asyncResult.IsCompleted)
        {
            yield return 0;            
        }

        var response = accClient.EndTransaction(asyncResult);
        resultText.text = response.ToLog ();

        setButtonsEnable (true);
    }

    // ボタンを押せない様にしたり戻したりする.
    void setButtonsEnable(bool enable)
    {
        updateButton.interactable = enable;
        sendButton.interactable = enable;
    }
}
```

以下、スクリプトのキーポイントや注意点等を列挙していきます。

・Connection クラスで接続先を設定
接続先を設定します。connection.SetTestnet()とすればTestnetの適当なサーバーに接続します。SetMainnetで同様にMainnetに接続することもできます。

・鍵を用いてクライアントの作成
所持通貨の確認等はアドレスや公開鍵を、送金など重要な処理は秘密鍵を用いてクライアントを作成してから行います。アドレスと公開鍵はNanoWalletから確認します。また、秘密鍵はウォレット作成時のものを使用します。

・送金トランザクション発行

まず、ここについてです。

```
        var mosaicList = new List<Mosaic> () {
            new Mosaic ("nem", "xem", 1000000)
        };
```

`new Mosaic ("ネームスペース", "モザイク名", モザイクが少数第何位まであるか)` という意味になります。

NEMは誰でも自分だけのネームスペースを作成し、独自の通貨を発行することができます。この通貨をモザイクといいます。主流のXEMもNEMというネームスペースに所属するモザイクです。
モザイクの送金時にはこのようにして送るモザイク一覧のデータを定義する必要があります。XEM単体を送る場合は他の書き方がありますが、今回はモザイクとして送ります。

次にこちらです。

```
        var transData = new TransferTransactionData () {
            Amount = 1000000,
            Message = "test",
            RecipientAddress = "TCDAP7EJZCR3EEBWNI3JQGFB67Y6HZR7VSVTJN4E",
            ListOfMosaics = mosaicList,
        };
```

Amountは1000000と凄い量が記載されています。こちらはマイクロNEM単位なので実際には1XEM送るという意味になります。RecipientAddress は送信先のアドレスですが、こちらは前もってもう1つ作っておいた別ウォレットのものを設定しています。

・処理の完了を待ちログテキストを表示
Begin系でリクエストを飛ばし、End系でその結果を受け取ります。非同期に行うこともできますが、今回はそうする意味がないのでコルーチンで完了を待ちました。
また、それぞれ処理結果を一括で表示するような仕組みがなく、処理結果の種類及び詳細が数字でしか返ってこないのが辛かったため拡張メソッドを用意して文字列を追加しています（WebAPIだとこれが普通なんですかね？）


さてこれでNEMの入門は終了です。
所感ですが、まず知らないものを触るのはとても楽しかったです。仮想通貨の知識はあまり持ち合わせていないのですが、それでも簡単に入門できたので初めての人でもNEMに触るのは有りだと思います。
また、英語ですらほぼ情報のないプラグインを触ったのは初めてで、Unityがめちゃくちゃ恵まれてるのを再認識するとともに、新雪の上を歩くような楽しさがありました。

仮想通貨気になるなあという方は是非NEMを触ってみましょう！