【個人開発】ブロックチェーン初心者がNFTのガチャゲームを行うDAppsをリリースしてみた話

### 1. はじめに

スマートコントラクトに興味を持ったので、ブロックチェーン初心者がIOSTでNFT（Non-Fungible Token：非代替性トークン）のDApps（decentralized applications：分散型アプリケーション）を作ってリリースしてみた際の話になります。
スマートコントラクトはソースコードがブロックチェーン上で公開されおり、実行時に不正を行うのも困難なため、運営サイド不正操作が話題となるガチャゲームに適用してみました。

### 2. 何を作ったの？

IOSTでNFTを発行する[COTOVA](https://cotova.net/)（言葉）というサービスです。
https://cotova.net/

![twHeader.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195748/880bf3a1-3606-49fb-06c5-a7da17ef76b2.jpeg)

NFTのコンテンツは漢字ガチャで特定漢字の組み合わせになります。仕様概要は以下の通りで、思いもよらない漢字の組み合わせに出会えるかもしれません。

* JIS第一水準漢字、JIS第二水準漢字の6,355個の漢字の組み合わせになります。
* 1文字、2文字、3文字の種類があり、1億3千3百以上のパターンになります。
* さらに7つのカラーバリエーションがあります。
* トークンの入手にはIOSTが必要ですが、**NFTのガチャを試すのは無料です。**
* 入手にはIOSTの[Webウォレット（Chromeプラグイン）であるIWALLET](https://chrome.google.com/webstore/detail/iwallet/kncchdigobghenbbaddojjnnaogfppfj)が必要です。

IRC721（EthereumでいうERC721）のNFTと、IRC20（EthereumでいうERC20）の独自トークンの２種類を同時に発行します。ERC20のトークン（筆トークン）は使い道がありませんが、複数トークンを同時に扱うDeFi（decentralized finance：分散型金融）の勉強のために組み込んでいます。

* IRC20のコントラクト
    * [Contract98ZrKX3JuVAv5ehf16JRCAnonmNhVQD47j7kC3v9kgAC](https://www.iostabc.com/contract/Contract98ZrKX3JuVAv5ehf16JRCAnonmNhVQD47j7kC3v9kgAC?page=1&size=50&order=asc) の「CONTRACT CODE」タブ
* IRC721のコントラクト
    * [ContractFwEBEEgJnCKnq52uEpcmQrk1A686MrR9NWYR6qu7naEy](https://www.iostabc.com/contract/ContractFwEBEEgJnCKnq52uEpcmQrk1A686MrR9NWYR6qu7naEy?page=1&size=50&order=asc) の「CONTRACT CODE」タブ

* 現在の発行状況(NFT Cotova)

https://api.iost.io/getTokenInfo/cotova/true

```json:Cotovaの発行状況
{
"symbol":"cotova",
"full_name":"cotova_nft_token",
"issuer":"ContractFwEBEEgJnCKnq52uEpcmQrk1A686MrR9NWYR6qu7naEy",
"total_supply":"4294967296",
"current_supply":"3",
"decimal":0,
"can_transfer":true,
"only_issuer_can_transfer":false,
"total_supply_float":4294967296,
"current_supply_float":3
}
```

* 現在の発行状況(Fude token)

https://api.iost.io/getTokenInfo/fude/true

```json:Fudeの発行状況
{
"symbol":"fude",
"full_name":"fude",
"issuer":"Contract98ZrKX3JuVAv5ehf16JRCAnonmNhVQD47j7kC3v9kgAC",
"total_supply":"1000000000000000000",
"current_supply":"6000000000",
"decimal":8,
"can_transfer":true,
"only_issuer_can_transfer":false,
"total_supply_float":10000000000,
"current_supply_float":60
}
```

### 3. アーキテクチャの概要

今回作成したサービスのアーキテクチャ概要です。大きく３の機能で構成されます。

![qiita_archi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195748/b894b457-bd55-8384-6b03-b378e8ce86aa.png)

#### 3.1. スマートコントラクトの機能



#### 3.2. IWALLETと連携してスマートコントラクトを実行する機能

Web（インターネット）の世界からブロックチェーンの世界のスマートコントラクトを直接呼び出すことはできません。IOSTではブロックチェーンのノードがGRPC、HTTPS（REST API）のエンドポイントを用意しているため、これを経由してアクセスすることになります。IWALLETの場合、エンドポイントの指定はできず、https://api.iost.io となります。動作確認ではこの仕様がちょっと面倒になります。詳細は「5. 作ってみた分かったこと、注意点」を参照ください。

#### 3.3. NFTを表示する機能

NFTのコンテンツを表示する機能です。今回はコンテンツの種類が非常に多く（組み合わせ爆発）、事前にすべてを用意するのか現実的でないため、オンデマンドで生成することにしました。内容がとてもシンプルなので、こちらも分散システムにしてしまおう、ということでクライアントサイドレンダリングにしました。

### 4. 必要なもの

新規サービスの公開で準備したものです。スマートコントラクトで必要なものは少なく、Webサイトの公開で必要となる一般的なものが多いです。

#### 4.1. 暗号資産IOST

「IOSTメインネットのアカウント発行代」や「RAMの購入代」です。日本国内では[Coincheck](https://coincheck.com/ja)で購入できます。下記のメインネットアカウントに送信する必要がありますが、数秒で届きます。IOST速いです。

#### 4.2. IOSTメインネットのアカウント

電話番号の登録が必要（SMSで認証コードを受領するため）ですが[IOST Sonata](https://sonata.asia/)でメインネットのアカウントを無料で作成することができます。アカウントを追加作成する場合は、ここで作成したアカウントを利用してコマンドで作成します。（10 IOSTが必要）

#### 4.3 Webサーバ

ランディングページとNFTコンテンツの公開用のWebサーバです。IOSTの開発環境はDockerで公開されているので、これを動作させる環境としても利用しました。
今回はネットワークデータ量に関わらず定額でクレジットカード登録が不要な[ConohaVPS](https://www.conoha.jp/vps/)を利用しました。2GBでDockerの起動が難しければ4GBにスケールアップしようと思ってましたが、動いているので2GBプランのままです。（様子を見てさくらのVPSに移動を計画中）

#### 4.4. 独自ドメイン

[さくらのドメイン](https://domain.sakura.ad.jp/)でドメインを取得しました。WHOIS情報の個人情報サポートと下記のメールボックス対応があったのでさくらインターネットにまとめています。

#### 4.5. メールボックス

独自ドメインのメールアカウントを用意するのが面倒だったので、手軽にメールサービスを使える[さくらのメールボックス](https://www.sakura.ne.jp/mail/)を利用しました。
DNSの設定でちょっと戸惑いました。`@`のエントリを変更する場合、`@`の「新規登録」はエラーで、新規内容を「変更」で登録しないといけないというワナにはまりました、、、

#### 4.6. HTTPSの証明書

無料でサーバ証明書が取得できる定番の[Let's Encrypt](https://letsencrypt.org/)で取得しました。ノウハウもWebにたくさんあるので問題なく導入できました。

#### 4.7. Googleアナリティクス

Webサイトなのでアクセス情報を収集・分析するため[Google Analytics](https://www.google.com/analytics/web/?hl=ja)を準備し、トラッキングコードをページに設定しました。後から設定するのではなく、公開前に準備して最初からトレースできるようにしましょう。

#### 4.8. サイト用のTwitterアカウント

サイト告知や連絡用に専用のTwitterアカウントを用意しました。[@CotovaNft](https://twitter.com/CotovaNft)です。

### 5. 作ってみた分かったこと、注意点
#### 5.1. スマートコントラクトをJavaScriptで書けるけど制限がある

IOSTではJavaScriptでスマートコントラクトを書けることをアピールポイントにしていますが、本当に素のJavaScriptで書けるだけです。ブロックチェーン上のノードで実行されることから、外部ライブラリは利用できず、またJavaScriptの標準の機能にも利用制限が掛けられています。

> Disabled Javascript Methods
> Some functions of Javascript is forbidden for security reasons.
> [This document gives a list of allowed and forbidden APIs.](https://developers.iost.io/docs/en/6-reference/GasChargeTable.html)

結論として、スマートコントラクトでは複雑なことはせず、シンプルかつ必要最低限とする必要があります。

#### 5.2. コンパイルはABIを生成するだけでABIも手修正が必要

IOSTでは以下のコマンド例のようにしてJavaScriptをコンパイルしましょうという説明があります。

```console:helloworld.jsをコンパイルするコマンド
iwallet compile helloworld.js
```

実はこのコマンドが行うのはABI（Application Binary Interface）ファイルを生成するだけです。ABIファイルはソースコードのJavaScriptからパブリックのメソッドを抽出し、そのインターフェース（メソッド名、引数名）を定義したものです。ソースコードにバグ（例えば変数の定義エラー）があっても分かりません。
また、出力されたABIファイルは未完成で、トークンの移動が発生する場合、ABIファイルにその旨を追加する必要があります。`amountLimit`に許可する制限を記載しないとトランザクション実行時にエラーとなります。

```json:出力されたABIの一部
        {
            "name": "transfer",
            "args": [
                "string",
                "string",
                "string",
                "string",
                "string"
            ],
            "amountLimit": [],
            "description": ""
        },
```

```json:修正したABIの一部（すべてのトークンを、上限無制限に許可する記載）
        {
            "name": "transfer",
            "args": [
                "string",
                "string",
                "string",
                "string",
                "string"
            ],
            "amountLimit": [{"token":"*","val":"unlimited"}],
            "description": ""
        },
```

#### 5.3. 残念ながら公式ドキュメントはいまいち

[IOSTの公式ドキュメント（3.4.0）](https://developers.iost.io/docs/en/1-getting-started/Overview.html)は残念ながら内容はいまいちです。何がいまいちかというと記載の分類がちゃんぽんで以下の内容が混ざっています、、、

* ①スマートコントラクトのJavaScript
* ②WEB REST API　：　ノードマシンに対してブロックチェーンのデータにアクセスする方法
* ③スマートコントラクトを呼び出すクライアント側のJavaScript

②はサンプル例がCurlなので他と異なるのですぐに分かりますが、①と③はコンテキストを理解しないとちょっと迷子になります。詳細のリンクが①、②、③の別の分類を指していたりするので注意が必要です。

#### 5.3. IWALLETからのスマートコントラクトの呼び出し方のノウハウが非常に少ない

前述の公式ドキュメントにはIOSTのWebウォレットであるIWALLETとの連携方法が書かれていません。③の方法は秘密鍵をクライアントのJavaScriptが直接触るもののため、秘密鍵の扱いを検討しなければならず、実システムでは使いづらいです。
スマホアプリのローカルでプライベートな領域に格納するなら可能性はありますが、手軽なWebシステムではWebウォレットとの連携が最重要かと思います。

[③のサンプルアプリ https://github.com/iost-official/gobang](https://github.com/iost-official/gobang) では秘密鍵を画面で生成、入力しています。コレジャナイ感しかありません。
[JIOSTA 日本IOST協会:Japan IOST Association](https://jiosta.com/about/)の方が最近[IOSTの送金デモ](https://codepen.io/aiyaanet/pen/WNprNLQ)を公開されたので、これを参考に何とか連携できるようになりました。

#### 5.4. IWALLETの接続はほぼメインネット（本番環境）のみ

IWALLETからスマートコントラクトを呼び出せるようになりましたが、なんとIWALLETで接続できるのは①メインネット、②テストネットのみです。なぜなら[そのようにハードコーディング](https://github.com/iost-official/iost-extension/blob/master/public/app/iostProxy.js)されているからです。

```javascript:該当の箇所（iostProxy.js）
const IOST_NODE_URL = 'https://api.iost.io' //当前节点
const IOST_TEST_NODE_URL = 'https://test.api.iost.io' //当前节点
const IWalletJS = {
  newIOST: (IOST) => {
      IWalletJS.pack = IOST
      IWalletJS.iost = new IOST.IOST(DEFAULT_IOST_CONFIG);
      const IOST_PROVIDER = new IOST.HTTPProvider(IWalletJS.network == 'MAINNET'?IOST_NODE_URL: IOST_TEST_NODE_URL)
      IWalletJS.rpc = new IOST.RPC(IOST_PROVIDER)
      IWalletJS.iost.signAndSend = signAndSend
      IWalletJS.iost.signMessage = signMessage
      IWalletJS.iost.setRPC(IWalletJS.rpc)
      IWalletJS.iost.setAccount(IWalletJS.iost.account)
      IWalletJS.iost.account = new IOST.Account(IWalletJS.account.name)
      IWalletJS.iost.rpc = IWalletJS.rpc
      return IWalletJS.iost
  },
```

ネットワークが`MAINNET`なら https://api.iost.io そうでなければ https://test.api.iost.io というロジックになっています。そしてこのネットワークはIWALLETにインポートしたアカウント（秘密鍵）の情報から判断されます。テストネットに接続したいならテストネットのカウント、メインネットに接続したいならメインネットのアカウントを有効にするということです。

***残念なお知らせとなりますが、テストネットのアカウント作成は現在正常に動作しません、、、***
🌀くるくるマークのままタイムアウトとなります。
http://testnet.explorer.iost.io/applyIOST

シングルノード（開発環境）でそれなりに動作確認が取れたら、以降のIWALLETとの連携はメインネットでお金を掛けた戦いになります。

#### 5.6. リリース時はGASとRAMに注意

スマートコントラクトをリリースする際は必要となるGASとRAMをある程度見積もっておくことをお勧めします。トランザクションを投げた際にGASが不足すると、今回のトランザクション分のGAS代を引かれた挙句、トランザクションは成功しないという悲しいことになります。

他にもGASのデフォルト上限値1000000を超えるGASが必要な場合、`--gas_limit`オプションで上限を変更することも忘れないようにしましょう。

GASやRAMの見積はどうするの？ですが、シングルノード（開発環境）にリリースした際のトランザクション情報（`gasUsage`と`ramUsage`の値）が参考になります。

```console:トランザクションの実行例
Transaction has been sent.
The transaction hash is: 98ZrKX3JuVAv5ehf16JRCAnonmNhVQD47j7kC3v9kgAC
Checking transaction receipt...
Transaction receipt:
{
    "txHash": "98ZrKX3JuVAv5ehf16JRCAnonmNhVQD47j7kC3v9kgAC",
    "gasUsage": 524355,
    "ramUsage": {
        "cotova": "5929"
    },
    "statusCode": "SUCCESS",
    "message": "",
    "returns": [
        "[\"Contract98ZrKX3JuVAv5ehf16JRCAnonmNhVQD47j7kC3v9kgAC\"]"
    ],
    "receipts": [
        {
            "funcName": "token.iost/create",
            "content": "[\"fude\",\"Contract98ZrKX3JuVAv5ehf16JRCAnonmNhVQD47j7kC3v9kgAC\",10000000000,\"eyJERUNJTUFMIjo4LCJGVUxMX05BTVdFIjoidG9rZW4gZm9yIGNvdG92YSIsImNhblRyYW5zZmVyIjp0cnVlLCJvbmx5SXNzdWVyQ2FuVHJhbnNmZXIiOmZhbHNlfQ==\"]"
        }
    ]
}
Transaction has been packed! Waiting for being irreversible...........................
SUCCESS! Transaction has been irreversible
The contract id is: Contract98ZrKX3JuVAv5ehf16JRCAnonmNhVQD47j7kC3v9kgAC
```


### 6. さいごに

ブロックチェーンのネットワークが生き残っている限りスマートコントラクトはいつでも実行可能な状態で存在し続けるので、スマートコントラクトはある意味可用性最高の分散システムと言えると思います。そして、トランザクションの実行費用は利用者がGAS代として払うので、ランニングコストも非常に小さいです。そのGAS代もIOSTの場合はIOSTのトークンを保持している限り、ゆっくりですが自動で補填されるので、利用者にとってもローコストかと。

暗号資産の購入が流行っていますが、ブロックチェーンだけのトークンとスマートコントラクトの機能のあるトークンでは、ビジネスによる広がりを考えると、スマートコントラクトの効果でエコシステムが広がっていく気がします。（ブロックチェーンだけのトークンは決済以外の用途がないので）

今回はコストが安いIOSTを利用しましたが、次回は第一世代スマートコントラクトであるEthereumでも作ってみたいです。Ethereumの方が圧倒的にドキュメントやノウハウが充実しており、WebウォレットはMETAMASKがほぼデフォルトスタンダードになっているので作り易いです。そして一番のポイントがそもそも利用者層が圧倒的に多いです。

* [Solidity](https://docs.soliditylang.org/en/v0.8.4/)
* [OpenZeppelin](https://openzeppelin.com/)
* [Web3.js](https://web3js.readthedocs.io/en/v1.3.4/)
* [METAMASK](https://metamask.io/)

なお、スマートコントラクトの学習方法としては、Ethereumから入った方がよいと思います。自身も[cryptozombies](https://cryptozombies.io/)をAdvancedの最後までやってからIOSTに着手しました。ERC20、ERC721、アドレス、スマートコントラクト連携等々の考え方を理解できるので、IOSTの場合はどうすればと実装方法だけ調査すればいいレベルになっていると思います。

今回、技術検証が目的でサービスを作ってみましたが、漢字ガチャというしょぼいコンテンツではなく、NFTとしてちゃんと需要が出そうなものを作ってみたいです。[CryptKitties](https://www.cryptokitties.co/)がカワイイ？、珍しい絵柄だけでNFTとして成り立っているので、カワイイ絵や相応の世界観を持っているクリエイターさんと組めれば面白いことができるかもしれないなと思案中です。

あとIOSTは2021/5末時点で1桁円なので、数年後には上がっていると嬉しいです。