Read this in other languages: English, 日本語
nem-kotlin は NEM のAPIを簡単に使うためのライブラリです.
このライブラリは、NIS(NEM Infrastructure Server) に対してのHTTPリクエストと、NISからのHTTPのレスポンスのラッパーとして機能します。
また、このライブラリではキーペアの作成や署名、署名検証といった暗号化関連のユーティリティも提供します。
サンプルのプロジェクトが samples ディレクトリにあります。ご参照ください。
最新の jar をダウンロードしてください。
gradle を使う場合: (gradle のバージョン 2.x を使う場合は、 'implmentaion' のかわりに 'compile' を指定してください)
implementation 'com.ryuta46:nem-kotlin:0.5.0'
maven を使う場合:
<dependency>
<groupId>com.ryuta46</groupId>
<artifactId>nem-kotlin</artifactId>
<version>0.5.0</version>
</dependency>
'AccountGenerator' で NEM のアカウントを作成できます。ネットワークバージョン(Main network か Test network か) を指定してください.
val account = AccountGenerator.fromRandomSeed(Version.Main)
秘密鍵がすでにあるのであれば、その情報からアカウントを生成することも出来ます。
val account = AccountGenerator.fromSeed(ConvertUtils.toByteArray("YOUR_PRIVATE_KEY"), Version.Main)
秘密鍵のバイト列をスワップして読み込む必要があるかもしれません。(e.g. NanoWalletが生成した秘密鍵を使う場合など。) このライブラリが要求する秘密鍵のバイト列表現のエンディアンが異なっている場合があるためです.
val account = AccountGenerator.fromSeed(ConvertUtils.swapByteArray(ConvertUtils.toByteArray("NANO_WALLET_PRIVATE_KEY")), Version.Main)
NEM の API クライアント作成時に、NIS の URL を指定します。
val client = NemApiClient("http://62.75.251.134:7890")
'NemApiClient' は同期クライアントです。そのため、このクライアントでは各APIの呼び出し時、NISからレスポンスが得られるまで呼び出し元スレッドをブロックします。
Reactive なクライアントを使いたい場合は、'RxNemApiClient' を使います。 'RxNemApiClient' は NemApiClient と同名のメソッドを持ちますが、戻り値の型が Observable になっています。
val rxClient = RxNemApiClient("http://62.75.251.134:7890")
下記のように処理を行うことで、API クライアントのセットアップに必要なスーパーノードの一覧を取得することができます。
var nodes: List<NodeInfo> = emptyList()
NisUtils.getSuperNodes().subscribe {
nodes = it
}
....
// Select a node and initialize a client with it.
val node = nodes.first()
val client = NemApiClient("http://${node.ip}:${node.nisPort}")
// Create clients for all nodes
val clients = nodes.map { NemApiClient("http://${it.ip}:${it.nisPort}") }
getSuperNodes()
は、ノードのリストをサーバ( デフォルトは "https://supernodes.nem.io/nodes/") から取得する関数ですので、非同期の処理となっています。.
getTestNodes()
を使うことで、テストネット用のノードの情報を取得する事もできます。
この関数は固定のノード情報を返すだけですので、同期的に処理を行います。
API クライアントは NEM の API に対応したメソッドを持っています。
アカウントの情報を取得する場合は下記のようにします。
val accountInfo = client.accountGet(account.address).account
rxClient.accountGet(account.address)
.subscribeOn(Schedulers.newThread())
.subscribe { response: AccountMetaDataPair ->
val accountInfo = response.account
}
トランザクションを作成する際は、NISからネットワーク時間を取得して、それをタイムスタンプとして使う必要があります。
最初に、NISからネットワーク時間を取得し、
val networkTime = client.networkTime()
val timeStamp = networkTime.receiveTimeStampBySeconds
次に、取得した時間を timeStamp
としてトランザクション作成時に設定します。
val transaction = TransactionHelper.createXemTransferTransaction(account, receiverAddress, amount, timeStamp = timeStamp)
timeStamp
パラメータが省略された場合はローカル時間が使われますが、その場合はトランザクションを送信した際に FAILURE_TIMESTAMP_TOO_FAR_IN_FUTURE
エラーが発生する場合があります。
送金など、署名が必要なトランザクションの生成は 'TransactionHelper' を使います。
XEM 送金をする場合
val transaction = TransactionHelper.createXemTransferTransaction(account, receiverAddress, amount, timeStamp = timeStamp)
val result = client.transactionAnnounce(transaction)
上記で指定している amount はマイクロ NEM 単位であることに注意してください。( 1 XEM = 1,000,000 マイクロ NEM)
モザイク送信をする場合
val transaction = TransactionHelper.createMosaicTransferTransaction(account, receiverAddress,
listOf(MosaicAttachment(mosaicNamespaceId, mosaicName, quantity, mosaicSupply, mosaicDivisibility), timeStamp = timeStamp)
)
val result = client.transactionAnnounce(transaction)
モザイクの供給量や可分性は、最低手数料を計算する際に用いられます。
もしそれらの値が不明な場合は、'namespaceMosaicDefinitionFromName' と 'mosaicSupply' を使って取得することが出来ます。
val response = client.namespaceMosaicDefinitionFromName(namespaceId, name)
if (response != null) {
divisibility = response.mosaic.divisibility!!
}
val response = client.mosaicSupply(mosaicId)
supply = response.supply
平文のメッセージを含めて XEM 送信を行う場合
val message = "message".toByteArray(Charsets.UTF_8)
val transaction = TransactionHelper.createXemTransferTransaction(account, receiverAddress, amount,
message = message,
messageType = MessageType.Plain,
timeStamp = timeStamp)
val result = client.transactionAnnounce(transaction)
暗号化メッセージを含めて XEM 送信を行う場合
val message = MessageEncryption.encrypt(account, receiverPublicKey, "message".toByteArray(Charsets.UTF_8))
val transaction = TransactionHelper.createXemTransferTransaction(account, receiverAddress, amount,
message = message,
messageType = MessageType.Encrypted,
timeStamp = timeStamp)
val result = client.transactionAnnounce(transaction)
受信したトランザクションからメッセージを読み取るには、下記のように実装します。
val message = transaction.asTransfer?.message ?: return
if (message.type == MessageType.Plain.rawValue) {
// for plain text message
val plainTextMessage = String(ConvertUtils.toByteArray(message.payload), Charsets.UTF_8)
} else {
// for encrypted text message
val decryptedBytes = MessageEncryption.decrypt(account, senderPublicKey, ConvertUtils.toByteArray(message.payload))
val encryptedTextMessage = String(decryptedBytes, Charsets.UTF_8)
}
マルチシグ関連のトランザクション(MultisigTransaction, MultisigSignatureTransaction, MultisigAggreageModificationTransaction) も TransactionHelper で作成します。
アカウントをマルチシグアカウントに変更する場合
val multisigRequest = TransactionHelper.createMultisigAggregateModificationTransaction(account,
modifications = listOf(MultisigCosignatoryModification(ModificationType.Add.rawValue, signerAccount.publicKeyString)),
minimumCosignatoriesModification = 1,
timeStamp = timeStamp)
val multisigResult = client.transactionAnnounce(multisigRequest)
マルチシグアカウントを通常アカウントに戻す方法はありませんのでご注意ください。
マルチシグアカウントからXEMを送金する場合
// Create inner transaction of which transfers XEM
val transferTransaction = TransactionHelper.createXemTransferTransactionObject(multisigAccountPublicKey, receiverAddress, amount, timeStamp = timeStamp)
// Create multisig transaction
val multisigRequest = TransactionHelper.createMultisigTransaction(signerAccount, transferTransaction, timeStamp = timeStamp)
val multisigResult = client.transactionAnnounce(multisigRequest)
さらに、そのトランザクションに署名を行う場合
val signatureRequest = TransactionHelper.createMultisigSignatureTransaction(anotherSignerAccount, innerTransactionHash, multisigAccountAddress, timeStamp = timeStamp)
val signatureResult = client.transactionAnnounce(signatureRequest)
innerTransactionHash は client.accountUnconfirmedTransactions(anotherSignerAddress)
にて取得できます。
もし使いたい API に対応するメソッドが定義されていない場合は、'get' と 'post' メソッドを使うことができます。
data class HarvestInfoArray(val data: List<HarvestInfo>)
...
val harvests: HarvestInfoArray = client.get("/account/harvests/", mapOf("address" to account.address")
'HttpClient' インターフェイスを実装することで、独自の HTTP クライアントを使うことが出来ます。
また、'Logger' インターフェイスを実装すことで、システム依存のログ機能(android.util.Log など) を使うこともできます。
これらは、クライアントのコンストラクタで指定します。
val client = NemApiClient("http://62.75.251.134:7890", yourHttpClient, yourLogger)
デフォルトの HTTP クライアントは 'HttpURLConnectionClient' で、'HttpURLConnection' を使っています。
デフォルトの Logger は 'NoOutputLogger' で、何も出力をしません。
標準出力にログを出力する 'StandardOutputLogger' を使うことも出来ます。
WebSocket クライアントを使うことができます。
val wsClient = RxNemWebSocketClient("http://62.75.251.134:7778")
WebSocket クライアントでは、各 API に対応した Observable を返します。
各 API に対応した情報が更新される度に、Observable を通して通知が行われます。
例) モザイクの保有量が変わる度に、モザイク保有量を表示
val subscription = wsClient.accountMosaicOwned(address)
.subscribeOn(Schedulers.newThread())
.subscribe { mosaic: Mosaic ->
print(Gson().toJson(mosaic))
}
通知が必要なくなったら必ず dispose を呼び出してください。
自動的に通知が完了するということはありません。
// Do not forget to dispose the subscription after you don't need to observe it.
subscription.dispose()
Twitter @ryuta461
寄付アドレス: NAEZYI6YPR4YIRN4EAWSP3GEYU6ATIXKTXSVBEU5