NEM Libraryを使ってみる(3) ウォレットの管理
NEM Libraryにはウォレットを管理するクラスも用意されています。
Walletの生成
秘密鍵からアドレスを生成する場合はWallet
クラスを継承したSimpleWallet
クラスを利用します。
let password: Password = new Password("passwordstr"); let privateKey: string = "privatekeyxxxxxxxxxxxxxxxxxxxxxxxx"; let simpleWallet: SimpleWallet = SimpleWallet.createWithPrivateKey("nextem test", password, privateKey);
生成されたSimpleWallet
は以下のようなオブジェクトになり、秘密鍵は暗号化されています。
{ "name":"nextem test", "network":104," address": { "value":"ND6VJMWYX7CZRPLYXX566DP2Z6O7ZKYG745EV2AX", "networkType":104 }, "creationDate":"2017-08-26T11:29:37.729", "schema":1, "encryptedPrivateKey": { "encryptedKey":"encryptedxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "iv":"ivxxxxxxxxxxxxxxxxxxxxxxxxx" } }
ちなみに秘密鍵をランダム生成するには、SimpleWallet
クラスのcreateメソッドを利用します。
let simpleWallet: SimpleWallet = SimpleWallet.create("random wallet", password);
Walletの利用
公開鍵や秘密鍵を利用するには、open
メソッドでAccount
クラスを作成して利用します。
秘密鍵はprivateになっているので、秘密鍵を直接操作するのではなく、戻ってくるAccount
クラスの各メソッドを利用してトランザクションへの署名や、メッセージの暗号化、復号化を実施します。
let account: Account = simpleWallet.open(password); account.signTransaction(transaction)
バックアップ
WalletのバックアップはwriteWLTFile
メソッドを利用します。
string
が返ってくるので適当に保存します。
let mimetype = 'application/octet-stream'; let url = window.URL.createObjectURL(new Blob([simpleWallet.writeWLTFile()], { 'type': mimetype })); let a = document.createElement('a'); a.target = '_blank'; a.download = simpleWallet.name + ".wlt"; a.href = url; a.click();
バックアップからの復元
wltファイルからWalletを復元するには、wltファイルの文字列を抽出した上でreadFromWLT
メソッドを利用します。
なお、NanoWalletから出力したwltファイルは形式が異なるようで読み込めませんでした。
let simpleWallet = SimpleWallet.readFromWLT("wltstrxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
このあたり自前で実装するのも面倒なので、WEBサイトなどに導入する際には地味に便利なのではないでしょうか。
※筆者のモチベーション向上のため、以下NEMアドレスへxemなりシットトークンなりの寄付を受け付けています。
NDY4RH-UZ3CZO-Z53O5H-NEXTEM-7UF5X3-MMDGH4-IMAD
NEM Libraryを使ってみる(2) トランスファートランザクション
次にxemや各種モザイクの送信方法についてです。
xemの送信
xemを送信するには、TransactionHttp
クラスのannounceTransaction
メソッドを利用します。
送信する署名済トランザクションを作成するには、TransferTransaction
クラスのcreate
メソッドでトランザクションを作成し、Account
クラスのsignTransaction
メソッドで署名します。
let account = Account.createWithPrivateKey("privatexxxxxxxxxxxxx"); let tx = TransferTransaction.create( TimeWindow.createWithDeadline(), new Address("NCS5BI-MFLIOP-5TMLKM-INCN5C-4PQ5VK-YXK7BB-VGYX"), new XEM(0.1), EmptyMessage ); let signedTransaction: SignedTransaction = account.signTransaction(tx); let transactionHttp = new TransactionHttp(); transactionHttp.announceTransaction(signedTransaction) .subscribe( x => console.log(x));
SUCCESSが返ってきました。
{ "type":1, "code":1, "message":"SUCCESS", "transactionHash":{ "data":"28b15df7e2a28e215385ce50ba2e7efb9efe0943db8f585576e071638bd8aa28" }, "innerTransactionHash":{} }
モザイクの送信
モザイクを送信するには、MosaicHttp
クラスのgetMosaicTransferableWithAmount
でMosaicTransferable
オブジェクトを作成し、配列にまとめてからTransferTransaction
クラスのcreateWithMosaics
メソッドでトランザクションを作成します。
getMosaicTransferableWithAmount
にxemを喰わせるとエラーになったので、個別に配列に追加しました。
ちなみにquantity
を1にした場合、divisibility
がいくつであってもしっかりと1送られます。何気にハマるポイントなので気にしなくていいのはありがたいです。
Observable.from([ {mosaic: new MosaicId("nextem", "nex"), quantity: 1}, {mosaic: new MosaicId("nextem.ex", "photon"), quantity: 1}, {mosaic: new MosaicId("nextem.ex", "higgs"), quantity: 1} ]).flatMap(mosaicWithAmount => mosaicHttp.getMosaicTransferableWithAmount( mosaicWithAmount.mosaic, mosaicWithAmount.quantity )) .toArray() .map(mosaics => { mosaics.unshift(new XEM(1)); return mosaics; }) .map(mosaics => TransferTransaction.createWithMosaics( TimeWindow.createWithDeadline(), new Address("NCS5BI-MFLIOP-5TMLKM-INCN5C-4PQ5VK-YXK7BB-VGYX"), mosaics, EmptyMessage ) ) .map(transaction => account.signTransaction(transaction)) .flatMap(signedTransaction => transactionHttp.announceTransaction(signedTransaction)) .subscribe(nemAnnounceResult => { console.log(nemAnnounceResult); });
残高の確認と送受信。これでもうほとんどのことは出来てしまいます。しかも容易に。
feeも安くなってネームスペースを初めて取ったなんて人も少しずつ出てきているようですし、さらに一歩進めて何か作ってみるというのはいかがでしょうか。
※筆者のモチベーション向上のため、以下NEMアドレスへxemなりシットトークンなりの寄付を受け付けています。
NDY4RH-UZ3CZO-Z53O5H-NEXTEM-7UF5X3-MMDGH4-IMAD
NEM Libraryを使ってみる(1) 初期設定と各種アカウント情報
以前試してみたNEM-sdkよりも、明らかに使いやすそうなNEM LibraryをAngularで動かしてみます。
TypeScriptできれいに書かれていて、RxJSが使われているのでAngularとも相性が良く、モダンJavaScriptプログラミングのいい教材になると思います。
また、NISからは配列で返ってきて操作しにくいモザイクのpropertiesがちゃんとプロパティになっていたり、ややこしいことで有名なdivisibility周辺も勝手にうまいことやってくれるので、NISの変な癖みたいな部分をきれいに吸収してくれています。
さらにはMultisigAggregateModificationTransactionなどにも対応しているので、機能的にも十分な機能を備えています。
初期設定
npmでパッケージをインストールします。
$ npm install nem-library --save
NEM Libraryはメインネットでもテストネットでも使えるので、app.module.ts
あたりでどちらか指定しておきます。
app.module.ts
import { NEMLibrary, NetworkTypes } from "nem-library"; NEMLibrary.bootstrap(NetworkTypes.MAIN_NET);
ちなみに自分の環境(@angular/cli: 1.3.1
)では以下のエラーが出ました。
In ambi ent enum declarations member initializer must be constant expression.
これはpackage.json
にあるdevDependencies
部分のtypescript
のバージョンを最新にしてnpm install
し直したところ解決しました。
Angular CLIを使って普通にプロジェクト生成した場合のデフォルトのバージョンが2.3.3でしたが、2.4以降が必要なんだと思います。
NISへの接続
NEM LibraryでNISへアクセスする場合、用途に応じてAccountHttp
、TransactionHttp
、MosaicHttp
などのHttpEndpoint
を継承したクラスを利用します。
HttpEndpoint
を継承したクラスはデフォルトでConnection Poolを使用しているので、引数無しで呼べば接続に成功するまで自動的にNISノードのリストに次々接続していきます。
(NanoWalletもこのような仕組みにすれば、開いたら残高がゼロなんです!!みたいな質問もなくなっていいんじゃないかと思うんですがどうなんでしょう。)
Account情報
AccountHttp
を利用するとアカウントの様々な情報を参照出来ます。
xemの残高や公開鍵などの情報を参照するには、getFromAddress
メソッドを利用します。
ちなみに一度もトランザクションを発行していないアドレスだと、Not a valid public key
のエラーが出ます。
let accountHttp: AccountHttp = new AccountHttp(); let address: Address = new Address("NDLHY5-KMQTAT-AR7IBR-BF32MA-QWDK73-33VNI2-MD5W"); accountHttp.getFromAddress(address) .subscribe(accountInfoWithMetaData => { console.log("getFromAddress", accountInfoWithMetaData); });
以下のようなAccountInfoWithMetaDataオブジェクトが返ります。
{ "balance":{ "balance":32799996, "vestedBalance":32796162, "unvestedBalance":3834 }, "importance":0, "publicAccount":{ "address":{ "value":"NDLHY5KMQTATAR7IBRBF32MAQWDK7333VNI2MD5W", "networkType":104 }, "publicKey":"099132a49ed0c15936a464cf6ef43120f01fa88835803593571882feea6161db" }, "harvestedBlocks":0, "status":"LOCKED", "remoteStatus":"INACTIVE", "cosignatoryOf":[], "cosignatories":[] }
パブリックキーからAccountInfoWithMetaData
を参照するには、getFromPublicKey
メソッドを利用します、
let publicKey: string = "099132a49ed0c15936a464cf6ef43120f01fa88835803593571882feea6161db"; accountHttp.getFromPublicKey(publicKey) .subscribe(accountInfoWithMetaData => { console.log("getFromPublicKey", accountInfoWithMetaData); });
モザイク情報
モザイクの情報を取得するにはMosaicHttp
を利用します。
AccountHttp
のgetMosaicOwnedByAddress
で所有モザイクの一覧を取得し、MosaicHttp
のgetMosaicDefinition
メソッドでそれぞれのモザイクの定義情報を取得します。
xemはgetMosaicDefinition
でエラーになったのでnemネームスペースでフィルターしました。
※中の人のアドバイスを受けて、RxJSを全力で活用したいい感じのコードに修正しました。
accountHttp.getMosaicOwnedByAddress(address) .flatMap(_ => _) .filter(mosaic => mosaic.mosaicId.namespaceId !== "nem") .flatMap(mosaic => { return mosaicHttp.getMosaicDefinition(mosaic.mosaicId) .map(mosaicDefinition => <any>{ mosaicOwnedByTheUser: mosaic, mosaicDefinition: mosaicDefinition }) }) .subscribe(mosaicInformation => { console.log(mosaicInformation.mosaicDefinition.id, mosaicInformation.mosaicDefinition); console.log(mosaicInformation.mosaicDefinition.id, mosaicInformation.mosaicOwnedByTheUser.quantity / (10 ^ mosaicInformation.mosaicDefinition.properties.divisibility)); });
MosaicDefinition
は以下のようなオブジェクトが返ります。
{ "creator":{ "address":{"value":"NDY4RHUZ3CZOZ53O5HNEXTEM7UF5X3MMDGH4IMAD","networkType":104}, "publicKey":"506e25d76aba0bc36dcf28127626a61f1d49ed45352f64fafaa72243c3e9e0ba" }, "id":{"namespaceId":"nextem.ex","name":"photon"}, "description":"Let there be light.", "properties":{ "initialSupply":1000000, "supplyMutable":true, "transferable":false, "divisibility":0 }, "metaId":75 }
アドレスが所有するモザイクの定義と残高を一度に取得するにはAccountOwnedMosaicsService
を利用します。
let accountOwnedMosaics = new AccountOwnedMosaicsService(new AccountHttp(), new MosaicHttp()); accountOwnedMosaics.fromAddress(address) .subscribe(mosaics => { console.log("accountOwnedMosaics", mosaics); });
以下のようなMosaicTransferableオブジェクト配列が返ります。
[ { "mosaicId":{"namespaceId":"nem","name":"xem"}, "properties":{ "initialSupply":8999999999, "supplyMutable":false, "transferable":true, "divisibility":6 }, "amount":32.799996 }, { "mosaicId":{"namespaceId":"nextem.ex","name":"higgs"}, "properties":{ "initialSupply":1000000, "supplyMutable":true, "transferable":true, "divisibility":6 }, "levy":{ "type":2, "recipient":{ "value":"NDY4RHUZ3CZOZ53O5HNEXTEM7UF5X3MMDGH4IMAD", "networkType":104 }, "mosaicId":{"namespaceId":"nem","name":"xem"}, "fee":10000 }, "amount":95.999999 } ]
トランザクション
トランザクションを参照するには、allTransactions
、incomingTransactions
、unconfirmedTransactions
などのメソッドを利用します。
pageSize
を指定しないとデフォルトでは10になります。最小5、最大100です。
accountHttp.allTransactions(address, undefined, undefined, 100) .subscribe(transaction => { console.log("allTransactions", transaction); });
以下のようなTransactionオブジェクト配列が返ります。
[ { "type":257, "version":1744830466, "timeWindow":{ "deadline":"2017-08-26T23:49:09", "timeStamp":"2017-08-26T21:49:09" }, "signature":"f5d861c62a2abd11a1517911a74123a4bf5a9ec749500bd0f14dcd8059ff3ec5814025969ddf3d79fbd0cbbec81d2f9519bf5c96fcccf76155c67450c86ee20b", "signer":{ "address":{ "value":"NDLHY5KMQTATAR7IBRBF32MAQWDK7333VNI2MD5W", "networkType":104 }, "publicKey":"099132a49ed0c15936a464cf6ef43120f01fa88835803593571882feea6161db" }, "transactionInfo":{ "height":1257790, "id":1006473, "hash":{ "data":"39d46a2fcbea6fed97518e0e77fae424c6dc5200bfa1169a76110ba0c84269ce" } }, "fee":200000, "recipient":{ "value":"NCS5BIMFLIOP5TMLKMINCN5C4PQ5VKYXK7BBVGYX", "networkType":104 }, "amount":1000000, "message":{ "payload":"" }, "mosaics":[ {"mosaicId":{"namespaceId":"nem","name":"xem"},"quantity":1000000}, {"mosaicId":{"namespaceId":"nextem.ex","name":"higgs"},"quantity":1000000}, {"mosaicId":{"namespaceId":"nextem.ex","name":"photon"},"quantity":1}, {"mosaicId":{"namespaceId":"nextem","name":"nex"},"quantity":1} ] } ]
もうこれ完全に暗号通貨の知識いらないですね。
※筆者のモチベーション向上のため、以下NEMアドレスへxemなりシットトークンなりの寄付を受け付けています。
NDY4RH-UZ3CZO-Z53O5H-NEXTEM-7UF5X3-MMDGH4-IMAD
NEMのマルチシグを使ったトラストレスな取引
そもそもの仕様を理解していなかったようで、コメントで頂いた指摘を受け修正しました。 (2of2のマルチシグ ⇒ 2of3のマルチシグ)
やっぱりしっかり検証してないとダメですね。(まだしてない
今回は、前々から色々考えて実験したりしていたことと、夜中に突然目覚めて関連することで思いついたことがあったので併せて残しておきます。
真夜中に目覚めて初めに考えたことがマルチシグの活用だなんて、我ながら消耗していますね。
ちなみに、結論から言っておくとあんまり価値のない話なんですが、考えていると結構面白かったので。
まず、Catapultのホワイトペーパーに以下のようなことが実現出来ると書いてありました。
これがあればDEXも簡単に実装出来そうですし、待ち遠しい限りです。
取引所があるだけで、今までただ投げ合っていた純粋に無価値なトークンが、ほぼ無価値なトークンに格上げされます。 まぁほぼ無価値なことに変わりはありませんが、ゼロかゼロでないかは大きな差ですね。
ただ、実際にCatapultがNEMにまでフィードバックされるのはいつでしょう?夏なのか、秋なのか、冬なのか、または。。 まぁいつになるのか分かりませんので、それまでのつなぎのサービスがあっても良いと思います。
ということで、Mosaic交換の肝になる資金のエスクローについて、現状の標準機能でどこまで実装できるのかについて考えてみました。
資金のエスクロー
NEMの標準機能であるマルチシグアドレスを使って実現します。
以下をそれぞれ交換するとします。
- アドレスA nem:xem
- アドレスB nextem:nex
資金の移動用に以下アドレスを利用します。
- アドレスC
- アドレスD
- アドレスE ダミーアドレス
リファンド用トランザクションの作成
以下のトランザクションをtimestampを1時間後など未来の時間にして作成します。
- 「アドレスCからアドレスAへnem:xemを送金」をラップしたマルチシグトランザクションにアドレスBの秘密鍵を使って署名。
- 「アドレスCからアドレスBへnextem:nexを送金」をラップしたマルチシグトランザクションにアドレスAの秘密鍵を使って署名。
それぞれの署名済みトランザクションをブロードキャストせずにアドレスAとアドレスBで交換します。 交換が終わったら、それぞれ署名に問題ないか事前に検証しておきます。
エスクロー用アドレスの作成
アドレスCを2of2マルチシグアドレス(連署人がアドレスAとアドレスB)に変換します。
以下の指摘の通り、連署人を勝手に外せる2of2ではダメみたいです。
id:mizunashi_rin 2017-06-26 13:41:50 NEMのMultisignature aggregate modification transactionの仕様で、N-of-Nのmultisig構成を組んでいる場合は、N-1の署名(つまりIsserのみ)で連署名者を削除できます。 この様な仕様を盛り込んだ形で、再度事例を具現化した記事を読みたいです♪ feeに関しては、1ヶ月後には1/20になっていると思われます。
誰も秘密鍵を知らないことが既知のダミーアドレスEを連署人に加えた2of3にすることで、連署人の追加削除を禁止します。
アドレスCを2of3マルチシグアドレス(連署人がアドレスAとアドレスBとダミーアドレスE)に変換します。
アドレスCへそれぞれ交換したいMosaicと必要なfeeを入金します。
2of3なのでもう資金はどちらかが勝手に動かすことは出来ません。
また、片方が入金しなかったような場合も、交換した署名済みのトランザクションがあるので、1時間後には自分の資金をリファンドすることが出来ます。
交換処理用アドレスの作成
BTCのようにひとつのトランザクションで同時に複数のアドレス宛に送金出来れば簡単なのですが、NEMではそれぞれへの送金を一つのトランザクションにまとめることが出来ないので、もうワンクッション必要になります。
複数アドレス宛には送れませんが、複数Mosaicを一つのアドレスに送ることは可能なので、それを利用します。
以下のトランザクションを作成します。
- 「アドレスDからアドレスAへnextem:nexを送金」をラップしたマルチシグトランザクションにアドレスBの秘密鍵を使って署名。
- 「アドレスDからアドレスBへnem:xemを送金」をラップしたマルチシグトランザクションにアドレスAの秘密鍵を使って署名。
それぞれの署名済みトランザクションをブロードキャストせずにアドレスAとアドレスBで交換します。 交換が終わったら、それぞれ署名に問題ないか事前に検証しておきます。
アドレスDを2of3マルチシグアドレス(連署人がアドレスAとアドレスBとダミーアドレスE)に変換します。
アドレスDへアドレスCからそれぞれのMosaicを同時に送金します。
ブロードキャストせずに交換した署名済みのトランザクションを使って、交換を完了させます。
トラストレス!
問題点と結論
NEMの標準機能だけを使ってDEXを作ろうと思うと、上記署名の交換などをmessageでやり取りすることになると思うので、正直現実的ではないレベルでfeeが必要になります。
messageをシグナリングサーバー代わりにして、WebRTCとかで接続すればP2Pでやり取り出来るのかもしれませんが、なんだか複雑になりそうです。
また、マルチシグアドレスを使うので、これまたfeeが高くなります。2of2マルチシグアドレスを二つ作成するだけで56xemかかり、なんとこれ今千円以上なんですね。。
やはりこれも現実的ではないです。
ということで結論ですが、下手にDecentralizedとか言わずに、素直にWEBサービスを立ち上げればいいんじゃないか、とw
今なんとなく想定しているのは、資金のロックすらせず、取引が成立したら送金トランザクションの署名を集めて、揃ったら残高だけ確認して問題なければブロードキャストするというような、ゆるーいサービスです。
需要あるかな?
※筆者のモチベーション向上のため、以下NEMアドレスへxemなりシットトークンなりの寄付を受け付けています。
NDY4RH-UZ3CZO-Z53O5H-NEXTEM-7UF5X3-MMDGH4-IMAD
NEM-sdkを使ってみる WebSocket編
少しバタバタしていたので間が空いてしまいましたが、引き続き以下のNEM-sdkを使ってみます。
今回はsdkを利用してWebSocketのAPIと接続してみます。
コネクターオブジェクトの作成
まずendpoint
とaddress
を渡してconnector
オブジェクトを作成します。
let endpoint = nem.model.objects.create("endpoint") (nem.model.nodes.defaultMainnet, nem.model.nodes.websocketPort); let address = "NDLHY5KMQTATAR7IBRBF32MAQWDK7333VNI2MD5W"; let connector = nem.com.websockets.connector.create(endpoint, address);
NISへの接続
次にconnector
オブジェクトのconnect
を呼んでNISへ接続し、返されたPromise
のコールバック関数でチャネルの購読処理や、リクエストを行います。
subscribe
チャネルの購読処理にはnem.com.websockets.subscribe
を利用します。
errors
文字通りなんだとは思いますが、今のところ何か流れてきたことがありません。
chain.height
現在のブロック高を返します。
chain.blocks
最新のブロックの情報と含まれるトランザクションの配列を返します。
account.data
アカウントの残高やパブリックキーなどの情報を返します。
account.transactions.recent
該当アドレスの最新25個の承認済トランザクション情報を返します。
account.transactions.unconfirmed
該当アドレスに関する未承認トランザクションを返します。
account.transactions.confirmed
未承認トランザクションが承認されたタイミングで、そのトランザクションを返します。
account.mosaics.definitions
該当アドレスが所有するモザイクの定義を返します。xemも含まれます。配列ではなくモザイクごとに別々にオブジェクトを返します。
account.mosaics.owned
該当アドレスのモザイク残高を返します。xemも含まれます。配列ではなくモザイクごとに別々にオブジェクトを返します。
account.namespaces.owned
該当アドレスがオーナーのnamespaceを返します。
requests
こちらからデータを要求する場合は、nem.com.websockets.requests
を利用します。
account.data
account.transactions.recent
account.mosaics.definitions
account.mosaics.owned
account.namespaces.owned
それぞれ該当のチャネルを購読していれば、そこにデータが流れてきます。
なお、account.transactions.recent
をリクエストすると、承認済トランザクションと共に未承認トランザクションも返ってきます。逆に、初回起動時にaccount.transactions.recent
をリクエストしていない場合、account.transactions.unconfirmed
を購読していても、すでにブロードキャスト済で未承認なトランザクションを把握出来ません。
コード
そのまま書くと関数名がいちいち長くて見づらいですが、コードは以下のようになります。
connector.connect().then(() => { console.log("Connected"); nem.com.websockets.subscribe.errors(connector, res => console.log("errors", res)); nem.com.websockets.subscribe.chain.blocks(connector, res => console.log("blocks", res)); nem.com.websockets.subscribe.chain.height(connector, res => console.log("height", res)); nem.com.websockets.subscribe.account.data(connector, res => console.log("data", res)); nem.com.websockets.subscribe.account.transactions.recent(connector, res => console.log("recent", res)); nem.com.websockets.subscribe.account.transactions.unconfirmed(connector, res => console.log("unconfirmed", res)); nem.com.websockets.subscribe.account.transactions.confirmed(connector, res => console.log("confirmed", res)); nem.com.websockets.subscribe.account.mosaics.definitions(connector, res => console.log("definitions", res)); nem.com.websockets.subscribe.account.mosaics.owned(connector, res => console.log("owned", res)); nem.com.websockets.subscribe.account.namespaces.owned(connector, res => console.log("namespaces", res)); nem.com.websockets.requests.account.data(connector); nem.com.websockets.requests.account.transactions.recent(connector); nem.com.websockets.requests.account.mosaics.definitions(connector); nem.com.websockets.requests.account.mosaics.owned(connector); nem.com.websockets.requests.account.namespaces.owned(connector); }, err => console.log("errorMessage", err));
実行時の流れ
まず接続に成功するとConnected
が出力され、リクエストしたdata
、recent
、definitions
、owned
、存在すればnamespaces
が返り、ブロックが進むごとにheight
とblocks
が返ります。
ちなみに接続に失敗すると10回繰り返してその後エラーが出力されます。
送金や着金があった場合、まずunconfirmed
が返ります。
この時点ではnem.com.websockets.requests.account.data
で最新データを要求してもまだ残高は変わっていないので、未承認トランザクションを含めるとどうなるかはこのunconfirmed
の結果を反映してあげる必要があるようです。
ブロックが進むとheight
とblocks
が返り、未承認のトランザクションが承認に変わるのでconfirmed
が返ります。
承認されると自分の残高が変わるので最新のdata
が返ります。ゼロになった場合は残高ゼロで値が返ります。
モザイク
xem以外のモザイクに動きがあった場合はdefinitions
とowned
が所有モザイクの数だけそれぞれ返りますが、ゼロになった場合はゼロが返るのではなく、該当モザイクのオブジェクトは何もかえってきません。
手持ちのモザイクの一つを送信してゼロになった場合、モザイクに動きがあったので残された他のモザイクについてdefinitions
とowned
は返りますが、該当のモザイクについては何も返らないという事になります。
返ってきたオブジェクトを次々と配列に入れていく作りにした場合、ゼロになったタイミングを把握出来ないのでいまいち使いにくいです。NanoWalletの実装もそうなっていて、送金後ゼロになった場合も、残高は更新されません。(少なくとも1.2.12ではそうでした)
何故配列で返す仕様にしなかったんだろう??
とりあえず、承認されたトランザクションを元に自力で計算するか、confirmed
のところで一旦配列をリセットし、definitions
とowned
を強制的にリクエストして再度配列を作り直すなど工夫が必要そうです。
これで残高、未承認トランザクションなど、リアルタイムでデータを更新可能なサービスが作れるようになりました。
今度こそチャーハンを作る準備は整った。かな?
※筆者のモチベーション向上のため、以下NEMアドレスへxemなりシットトークンなりの寄付を受け付けています。
NDY4RH-UZ3CZO-Z53O5H-NEXTEM-7UF5X3-MMDGH4-IMAD
NEM-sdkを使ってみる
NEMで何らかのサービスを作るにあたり、Node側からもAngular側からも同じように使えるライブラリを作ろうと思っていたのですが、NanoWalletとかにガンガンコミットしている方が作ったNEM-sdkというのが既にあるらしいので使ってみます。
インストール
普通にnpmモジュールになっているので、npm install
でインストールします。
Angular側から呼んだ際にajv
でエラーが出たので、ajv
を最新バージョンでインストールしなおしておきます。
(Nodeから直接実行するときは特にエラー出なかったので、Angular側の問題かもしれません。)
$ npm install nem-sdk --save $ npm install ajv --save
オブジェクト
オブジェクトの種類は主に以下で、必要に応じて組み合わせて使います。
common
パスワード、秘密鍵endpoint
SNのホスト名、ポート番号mosaicAttachment
送金するモザイクmosaicDefinitionMetaDataPair
モザイクの基本情報invoice
請求書用?transferTransaction
送金トランザクションsignatureTransaction
マルチシグの署名トランザクション
空のオブジェクトを作成するには、nem.model.objects.get
を利用します。
let transferTx = nem.model.objects.get("transferTransaction");
以下のようなオブジェクトが生成されます。
{ "amount":0, "recipient":"", "recipientPublicKey":"", "isMultisig":false, "multisigAccount":"", "message":"", "isEncrypted":false, "mosaics":[] }
パラメータのセットされたオブジェクトを生成するには、nem.model.objects.create
を利用します。
この関数は関数を返すのでその関数に引数を渡します。
let transferTx = nem.model.objects.create("transferTransaction")("NDY4RHUZ3CZOZ53O5HNEXTEM7UF5X3MMDGH4IMAD", 1, "soon");
以下のようなオブジェクトが生成されます。このままではまだ使えません。
{ "amount":1, "recipient":"NDY4RHUZ3CZOZ53O5HNEXTEM7UF5X3MMDGH4IMAD", "recipientPublicKey":"", "isMultisig":false, "multisigAccount":"", "message":"soon", "isEncrypted":false, "mosaics":[] }
トランザクション作成
先ほどのトランザクションにトランザクションタイプやタイムスタンプなどを付加するには、nem.model.transactions.prepare
を利用します。
送信サイドの情報が必要なので、先にcommon
オブジェクトを作成します。(privateKey
部分は生の秘密鍵文字列。)
let common = nem.model.objects.create("common")("", "privateKey"); let transactionEntity = nem.model.transactions.prepare("transferTransaction")(common, transferTx, nem.model.network.data.mainnet.id);
必要情報が付加されました。
{ "type":257, "version":-1744830463, "signer":"099132a49ed0c15936a464cf6ef43120f01fa88835803593571882feea6161db", "timeStamp":68485327, "deadline":68488927, "recipient":"NDY4RHUZ3CZOZ53O5HNEXTEM7UF5X3MMDGH4IMAD", "amount":1000000, "fee":2000000, "message":{"type":1,"payload":"736f6f6e"}, "mosaics":null }
送信
トランザクションの送信には、nem.model.transactions.send
を利用します。実際にネットワークへ送信するので、endpoint
オブジェクトを作成しておきます。
実行するとnem.model.transactions.prepare
で準備したトランザクションをシリアライズして署名し、ネットワークへ送信します。
let endpoint = nem.model.objects.create("endpoint")(nem.model.nodes.defaultMainnet, nem.model.nodes.defaultPort); nem.model.transactions.send(common, transactionEntity, endpoint) .then(res => console.log(JSON.stringify(res)));
以下の通りSUCCESSが返りました。
{ "innerTransactionHash":{}, "code":1, "type":1, "message":"SUCCESS", "transactionHash":{ "data":"2b1b0b95ec2f6181991e1f71c4501b56b51b9f2841f4840602590e867b480693" } }
ということで、このライブラリを使うと本当に手軽にXEMやモザイクの送信が可能です。当然、Node側からもHTML側からも同じように呼べますし、モザイクを使ったWEBサービスやゲームなども簡単に実装出来そうです。
また、今後このライブラリがNanoWalletに統合されるようなことも書いてあるので、どうやらもう車輪から自作する必要はなく、すぐに作りたいものを作るフェーズから開始すれば良さそうです。
よし、何か作ろう。(soon)
※筆者のモチベーション向上のため、以下NEMアドレスへxemなりシットトークンなりの寄付を受け付けています。
NDY4RH-UZ3CZO-Z53O5H-NEXTEM-7UF5X3-MMDGH4-IMAD
NEM NanoWalletのServicesをnpmモジュール化する
前回に続いて、NEM NanoWalletの中身をnpmモジュール化していきます。
前回はUtilsをnpmモジュール化したので、今回はそのモジュールを利用した各種Servicesの移植に進みます。
Services
まずはServicesの中身を整理します。
- Alert
alert.service.js
エラーメッセージに関するサービス。
- Connector
connector.service.js
SockJSを利用してWebSocket APIに接続するサービス。
- DataBridge
dataBridge.service.js
NetworkRequestsやConnectorを利用してデータを取得、保持するサービス。
- NetworkRequests
networkRequests.service.js
各種APIへ接続するサービス。
- Transactions
transactions.service.js
各種トランザクションを作成するサービス。
- Wallet
wallet.service.js
カレントアドレスと接続ノードなどを設定するサービス。
- WalletBuilder
walletBuilder.service.js
アドレスを生成するサービス。
WebSocketでリアルタイムにデータを取得したいのであれば、Connector
とDataBridge
が必要になりますが、トランザクションを生成してブロードキャストするだけなら、NetworkRequests
とTransactions
があれば事足りるので、今回はこの二つのファイルを対象とします。
対象のファイル2ファイルは、上記のDataBridge
、Wallet
を参照しています。
DataBridge
については、NISとの時刻の同期を行っている部分を参照しているだけなので、今回は時刻同期無しでDataBridge
は無視します。
Wallet
については、今回は定数としてしか使わないので、定数を保持するだけのクラスとして実装します。本来はWallet
のインスタンスをコンストラクタで受ける形になるかと思います。
また、以下のconfig
フォルダにあるAppConstants
も参照しています。
AppConstantsはデフォルトのポート番号や、有効な言語の一覧などが入っている設定ファイルなので、スタティックな変数だけ持つクラスとして実装します。
Typescriptで実装する
せっかくなのでTypescriptで書き換えていきます。(と言っても片っ端からanyで型指定していくことに何の意味があるのかは知りませんがw)
まずは、TypeScriptのファイルと、生成されるJavaScriptのファイルをそれぞれ管理する必要があるので、少し環境を整えます。
src
がソース置き場、lib
が実体置き場ということにしてディレクトリを分けます。
npmには実体だけアップしたいので、.npmignore
に除外するsrc
フォルダを指定します。
.npmignore
src/
また、生成されたJavaScriptのファイルをgitで管理したくないので、.gitignore
に除外するlib
フォルダを指定します。
.gitignore
lib/
src
ディレクトリに移動してtsc
の--init
オプションでtsconfig.json
を生成し、outDir
で出力先を設定します。
tsconfig.json
{ "compilerOptions": { "module": "commonjs", "target": "es5", "noImplicitAny": false, "sourceMap": false, "outDir": "../lib" } }
最終的なディレクトリ構成は以下のようになります。
nem-services │ .gitignore │ .npmignore │ LICENSE │ package.json │ README.md │ ├─lib │ appConstants.js │ index.js │ networkRequests.js │ transactions.js │ wallet.js │ └─src appConstants.ts index.ts networkRequests.ts transactions.ts tsconfig.json wallet.ts
それぞれのサービスの実装
あとはそれぞれのファイルの、元のコードのままでは動かない部分について修正しつつ、狂ったように型指定していきます。
基本的にはそのままですが、AngularJSのhttp
部分をisomorphic-fetch
で書き換えました。
fetchした時に帰ってくるPromise
をObservable
にしていじくりまわしたのでrxjs
も必要になりました。
なぜここでRxJSかというと、それってモダンじゃね?ぐらいの感覚で良く分かってませんw
package.json
"dependencies": { "rxjs": "^5.1.0", "isomorphic-fetch": "^2.2.1", "nem-utils": "0.0.3" }
NetworkRequests
httpアクセス部分は以下のような感じです。
正直、Observable
の使い方、使いどころが正しいのか良く分かりませんが、動いたので正しいということにしておきます。
import * as fetch from "isomorphic-fetch"; import { Observable } from 'rxjs'; import 'rxjs/add/operator/map'; getHeight(host: string): Observable<number> { let url: string = "http://" + host + ":" + this.getPort() + "/chain/height"; return Observable.fromPromise(fetch(url)) .flatMap(response => response.json()) .map(json => json.height); }
Transactions
DataBridge
が担っていた部分は、以下のように書き換えました。
// let d = new Date(); // let timeStamp = Math.floor(this._DataBridge.networkTime) + Math.floor(d.getSeconds() / 10); let timeStamp: number = helpers.createNEMTimeStamp();
その他、jQueryを使っている部分をNativeなコードに直したり、型に関しては結構ゆるい作り(数値型っぽいところに突然エラーメッセージ配列を放り込む)になっていたので、その辺を微調整しました。
実行
以下のようにしてインポートして利用します。 プライベートキーは伏せてありますが、実際は正しいものを利用します。
import { Transactions } from "nem-services"; let common = { privateKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", password: "" } let dummyTransaction = { recipient: "NDLHY5KMQTATAR7IBRBF32MAQWDK7333VNI2MD5W", recipientPubKey: "099132a49ed0c15936a464cf6ef43120f01fa88835803593571882feea6161db", amount: 0, message: "sooooooooooooooooooooooooooooon", mosaics: null, fee: 12000000, innerFee: 0, due: 60, isMultisig: false, multisigAccount: "" } let tx = new Transactions(); let entity = tx.prepareTransfer(common, dummyTransaction, null); tx.serializeAndAnnounceTransaction(entity, common) .subscribe(s => console.log(s));
以下のレスポンスが返ってきました。
{ innerTransactionHash: Object, code: 1, type: 1, message: "SUCCESS", transactionHash: Object }
そして無事トランザクションが取り込まれました。
http://chain.nem.ninja/#/transfer/aa530b1189fa5401101df71232fa0cf0f883a5d25364e552ea95d986a2c215c4
※筆者のモチベーション向上のため、以下NEMアドレスへxemなりシットトークンなりの寄付を受け付けています。
NDY4RH-UZ3CZO-Z53O5H-NEXTEM-7UF5X3-MMDGH4-IMAD