この記事は nem Advent Calendar 2018 の18日目の記事です。
担当のりゅーたです。
NEMを使ったアプリ(RaccoonWalletのiOS版、PWA版 とか)、nem-kotlin、nem-swiftなどのライブラリの開発を行ってます。
Advent Calendar 、せっかくなので何か作ってみようぜと思いたち、スマホアプリを一つ作ってみました。
以前チラ見せしたやつです。
NEM のAdvent Calendar 向けのネタアプリ。
— りゅーた@田舎フリーランス (@ryuta461) 2018年11月24日
Cloud Schedular と Cloud Functions で NEM の承認済みブロックを定期的に取得して、指定アドレス(または指定モザイク)のトランザクションがあれば端末にプッシュ通知を送る。(Flutter 製)
コア部分は作った。 pic.twitter.com/FHj0rKxDDU
Firebase、Flutter、NEM っていう最近自分が興味あるものを組み合わせたアプリです。
まぁこんなことできるよっていうデモアプリみたいなものだと思っていただければ。
デモアプリと言いつつせっかく作ったのでリリースしました。Android/iOS 両対応で作ったんですが(Flutter という最近 1.0 になったクロスプラットフォーム環境を使ってます)、残念ながら iOS の方は審査が間に合わずリリースできてません。
このアプリは何?
- 指定したアドレスに対してXEM、モザイクが送信されてきたらスマホ端末にプッシュ通知を送る
- 指定したモザイクの送信を確認したらスマホ端末にプッシュ通知を送る
メインの機能としてはほぼそれだけです。
使い方
アプリ右下の + をタップすると監視対象のアドレスかモザイクを追加できます。
監視対象に追加したアドレスに関係するトランザクションが承認されたらプッシュ通知が届きます。
アドレスにはラベルが設定できます。通知には指定したラベルが表示されます。
モザイクを監視対象に指定すると、そのモザイクが移動した時に通知が来るようになります。
使いみち
アドレス監視の方は、寄付アドレスへの入金とか投げ銭があったことのお知らせとして。
モザイク監視の方は、モザイクと紐付いたサービスを作っている方が、自分の発行したモザイクがブロックに刻まれていくのを見てモチベーションを上げるためとか。
実装面
アプリ全体の構成です。shohei さんがとても良い図を書いてくれた ので、それに倣って構成図を書いてみました。
バックエンドの処理の主体は Cloud Functions for Firebase (以下 Functions) が行っています。
Functions 内で NEM のブロックチェーンの情報を取得し、ユーザが監視対象にしているアドレス、モザイクの情報があれば Firebase Cloud Messaging (以下 FCM) 経由でユーザ端末にプッシュ通知を送るという構成です。
Functions は node.js でバックエンドの処理を書くことができ、とても便利な機構なのですが、Functions 自体には定期的に処理を実行するという機能がありません。
そこで、先日 Beta版がリリースされた Cloud Sheduler を使います。
Cloud Sheduler は定期的にユーザが指定した動作を実行する機構です。これを使って、同じく GCPファミリーの Cloud Pub/Sub に1分おきにメッセージを配信し、それをトリガにして Functions が実行される、という流れになっています。
フロントエンドは Flutter で書いていて、コードの殆どは Android と iOS で共通のものを使っています。アプリの主な役割は、ユーザごとに監視対象を管理している Firestore へのデータの書き込みと、トランザクションの詳細情報を NEM のブロックチェーンから探してきて表示する、という部分になります。
バックエンドもフロントエンドもコードはGitHub においてますので興味があればどぞ。
バックエンド実装詳細
前述のとおり、バックエンドは GCP を使ったサーバレスな環境セットを使っていて、メインの処理は Functions になります。
このあたりが Functions のコードになります。
Functions は node.js が使用可能です。TypeScript も使えます。というわけで、ここは NEM Library の出番ですね。
Functions 内でNEM Libraryを使って行っていることは主に2つです。
- 現在のブロック高の取得 (chain/height API)
- 前回チェック時から現在のブロック高までのブロックの取得 (block/at/public API)
指定アドレスのトランザクションを調べる場合、アカウント関連の API (account/transfers/all など) を叩くのが手っ取り早い気がしますが、Functions 上で全ユーザが指定したアドレスに対していちいち API を叩くのは負荷がかかりすぎると判断して、ブロック情報の方を取得する方法を取りました。(このアプリがそんなにたくさんのユーザから使われたらそれはそれで嬉しいけど)
結局はブロックチェーン、承認されたブロックに情報がつまってるはずなので、必要な情報は block の API からとってしまおうという話です。これなら 1ブロックにつき 1回の API コールで済みます。
NEM のブロック承認の間隔は 1分に 1回程度なので、1回の Functions の呼び出しで処理するのは数ブロックに収まり、API の呼び出しも同様に少なく抑えることができます。
ブロックの情報が取得できたらあとは監視対象のアドレス、モザイクに合致した場合に FCM 経由でプッシュ通知を送るという流れになります。
フロントエンド
フロントエンドは Flutter を使っています。
Flutter は最近 1.0 がリリースされて話題にもなった、Google 製のクロスプラットフォームのアプリ開発環境です。
Dart という言語で記述することで Android/iOS 両対応のアプリを簡単につくることができます。UIもマテリアルデザインのパーツが最初から用意されていて、レイアウトも作りやすいと感じています。
1.0 リリースになる以前からちょこちょこ触っていたのですが、今回は試しにアプリ使ってみようと思い、採用してみました。
Flutter から NEM のブロックチェーンにアクセスする
Flutter で使用する言語は Dart です。
再び、shohei さんの記事 にもあるように、今は NEM の API を使う時は言語ごとに用意されているライブラリを使うことが多いです。
しかし、Dart 向けのNEM ライブラリは現在はなさそうな感じでした。
とはいえ、NEMのAPIは REST の API で、HTTP通信とJSONのデシリアライズができれば情報を取得するところはできるので、アプリの方で Dart でHTTPアクセス、データモデルを取得する部分を作りました。ライブラリ化してメンテする自信がなかったのでアプリのコードにそのまま入れてます。
実装はこの辺です。
このアプリで NIS へのアクセスは、トランザクションの詳細な情報を調べることに限定されているので、今回実装した Dart の処理も部分的なものになります。もし使えそうな部分があればお好きに持っていってください。
Dart だけだと難しそうな箇所の対処
殆どは NIS から取得した数値や文字列をそのままモデル化するだけで済むのですが、一点そのまま扱うと不便な箇所がありました。
トランザクションの署名者(送信者)の情報です。NIS から取得できるのは公開鍵の情報ですが、ユーザ的にはアドレスの方がわかりやすいので、アドレスに変換して表示する必要があります。
KotlinやらSwift やらで公開鍵 -> アドレス変換は実装しているのでおおよそ変換方法はわかるのですが、RIPEMD-160 などのハッシュ関数をいくつか使わなければなりません。
Dart にもそれらのハッシュ関数の実装はありそうでしたが、現在 Flutter で使える Dart2 ではこれらのライブラリを取り込むとエラーになるというような情報もありました。
そこで、公開鍵 -> アドレス変換に関しては Flutter の機能である platform channels を使うことにしました。
この機能を使うと、Flutter の Dart コードから Android、iOS のそれぞれのプラットフォームごとの処理を呼び出すことができます。Android なら Kotlin、iOS なら Swift のコードを呼ぶことができます。
これを使って、Android環境では nem-kotlin、iOS環境では nem-swift のそれぞれの公開鍵 -> アドレス変換の処理を呼び出して Dart コードの方に返す実装にしました。
コードはこの辺
Dartで必要なライブラリが無いって時でもその部分だけ Android/iOS それぞれ向けにコード書けばなんとかなるっていうのは本当ありがたいです。
同じ方法を使えば、Ed25519 の署名の処理だけ Kotlin、Swift の方で行うといったこともできそうでした。(今回のアプリは署名しないのでそういったコードは入れてません)
ちなみにアドレス取得したいだけなら account/get/from-public-key API でもできます。実装時はこのことをすっかり忘れてました。
NEM 関連以外の部分
NEM関連以外の部分だと、Firestore へのアクセスや FCM の通知受信部分になるのですが、さすが Google、そこはバッチリ Flutter 向けのライブラリを作ってくれていて、これらを使って簡単に実装することができます。
- cloud_firestore Cloud Firestore でのデータのやり取り
- firebse_messaging FCM のプッシュ通知の受信
Firebase も Flutter も Google からリリースされているので、この辺の連携はかなり強い感じです。
Firebase のチュートリアルにもFlutter でのセットアップ方法が書かれてたりします。
さいごに
予告どおり、何か作ってみました。
RaccoonWallet でもプッシュ通知を入れる検討を行っていますので、今回のアプリの内容が参考になるかもしれないし、全然違った方法での実装になるかもしれません。