JavaでもSymbolブロックチェーン~送金編~


Symbol from NEMではエンタープライズ分野で人気のあるJava言語でも使用することができます。

今回はJavaでSymbol送金を体験してみましょう。

まず、公式の案内通りにGradleの準備をします(Gradle 7.0 以上)

ここで

dependencies {
    compile "io.nem:symbol-sdk-vertx-client"
}

とありますが、これでは動きません。
implementation 'io.nem:symbol-sdk-vertx-client:1.0.0'
と読み替えてください。

gradle run が実行できるようになったらApp.javaを書き換えていきます。

基本プログラム

package symbol_test;

public class App  {

    public static void main(String[] args) throws Exception {
        run();
    }

    public String getGreeting() {
        return "Hello Symbol!";
    }

    private static void run() throws Exception {
        Thread.sleep(1000);
        run();
    }
}

これが基本フォーマットになります。
public static void mainからプログラムが開始され runで強制終了されるまでループします。
送金プログラムだけの場合はループは必要ありませんが、着金を検知するためにループで待機しています。

import

今回使用するライブラリです。

import java.time.Duration;
import java.math.BigInteger;
import java.util.Collections;

import io.nem.symbol.sdk.api.Listener;
import io.nem.symbol.sdk.api.RepositoryFactory;
import io.nem.symbol.sdk.api.TransactionRepository;
import io.nem.symbol.sdk.model.account.Address;
import io.nem.symbol.sdk.model.account.Account;
import io.nem.symbol.sdk.model.mosaic.Mosaic;
import io.nem.symbol.sdk.model.mosaic.MosaicId;
import io.nem.symbol.sdk.model.message.PlainMessage;
import io.nem.symbol.sdk.model.network.NetworkType;
import io.nem.symbol.sdk.model.transaction.Deadline;
import io.nem.symbol.sdk.model.transaction.Transaction;
import io.nem.symbol.sdk.model.transaction.SignedTransaction;
import io.nem.symbol.sdk.model.transaction.TransferTransaction;
import io.nem.symbol.sdk.model.transaction.TransferTransactionFactory;
import io.nem.symbol.sdk.infrastructure.vertx.RepositoryFactoryVertxImpl;

python版では対話式に都度ライブラリを追加していきましたが、
Javaでは最初に宣言しておきます。

リポジトリ生成

RepositoryFactory repo = new RepositoryFactoryVertxImpl(
    "http://ngl-dual-101.testnet.symboldev.network:3000"
);

接続するノードを指定してレポジトリを生成します。

アカウント生成

Account alice = Account.createFromPrivateKey(
    "896E43895B908AF5847ECCB2645543751D94BD87E71058B003417FED512****", 
    NetworkType.TEST_NET
);
System.out.printf("Alice address is: %s \n",alice.getAddress().plain());

Account bob = Account.generateNewAccount(NetworkType.TEST_NET);
System.out.printf("Bob address is: %s \n",bob.getAddress().plain());

送金者Aliceと受信者Bobを生成します。送信者は残高を持つ秘密鍵から生成。Bobはランダムに生成しました。

トークン定義

BigInteger amount = BigInteger.valueOf(1);

Mosaic mosaic = new Mosaic(
    new MosaicId("091F837E059AE13C"),
    amount.multiply(
        BigInteger.valueOf(10).pow(6)
    )
);

送金するトークンを定義します。
テストネットのXYMはID:091F837E059AE13C 可分性:6で定義されています。
公式ドキュメントは古いテストネットのIDで解説されているのでご注意ください。

トランザクション生成

TransferTransaction tx = TransferTransactionFactory.create(
        NetworkType.TEST_NET,
        Deadline.create(repo.getEpochAdjustment().toFuture().get()),
        bob.getAddress(),
        Collections.singletonList(mosaic)
).message(
    PlainMessage.create("This is a test message")
).maxFee(BigInteger.valueOf(2000000)
).build();

送金トランザクションを生成します。
このあたりは公式ドキュメントの情報が古いので、少し変更が必要です。
- Deadlineを指定してください(引数にはネットワークのエポックタイムの指定が必要です)。
- メッセージはcreateの引数ではなく、メソッドチェーンで追加していきます。

署名とアナウンス

String generationHash = repo.getGenerationHash().toFuture().get();
SignedTransaction signedTx = alice.sign(tx, generationHash);

System.out.printf("SignedTransaction is: %s ",signedTx.getHash());

TransactionRepository txRepo = repo.createTransactionRepository();
txRepo.announce(signedTx).toFuture().get();

作成したトランザクションをAliceで署名してネットワークにアナウンスします。

着金検知

Listener listener = repo.createListener();
listener.open().get();

listener.confirmed(bob.getAddress())
.subscribe(
    (notice) -> { System.out.println(notice.getType().toString());},
    (e) -> { System.out.println(e);}
);

リスナークラスを利用してBobへの着金を検知します。
Listener.confirmedで承認されたトランザクションが検知できます。

この書き方はVistiel Archさんの記事を参考にさせていただきました。

また、nabe3さんがJava版マイクラでXYMを送金するプラグインを実装した記事を公開されています。

最後に全ソースコードを載せておきます。
ぜひみなさん、お試しください!

package symbol_test;

import java.time.Duration;
import java.math.BigInteger;
import java.util.Collections;

import io.nem.symbol.sdk.api.Listener;
import io.nem.symbol.sdk.api.RepositoryFactory;
import io.nem.symbol.sdk.api.TransactionRepository;
import io.nem.symbol.sdk.model.account.Address;
import io.nem.symbol.sdk.model.account.Account;
import io.nem.symbol.sdk.model.mosaic.Mosaic;
import io.nem.symbol.sdk.model.mosaic.MosaicId;
import io.nem.symbol.sdk.model.message.PlainMessage;
import io.nem.symbol.sdk.model.network.NetworkType;
import io.nem.symbol.sdk.model.transaction.Deadline;
import io.nem.symbol.sdk.model.transaction.Transaction;
import io.nem.symbol.sdk.model.transaction.SignedTransaction;
import io.nem.symbol.sdk.model.transaction.TransferTransaction;
import io.nem.symbol.sdk.model.transaction.TransferTransactionFactory;
import io.nem.symbol.sdk.infrastructure.vertx.RepositoryFactoryVertxImpl;


public class App  {

    public static void main(String[] args) throws Exception {

        RepositoryFactory repo = new RepositoryFactoryVertxImpl(
            "http://ngl-dual-101.testnet.symboldev.network:3000"
        );

        Account alice = Account.createFromPrivateKey(
            "896E43895B908AF5847ECCB2645543751D94BD87E71058B003417FED512314AE", 
            NetworkType.TEST_NET
        );

        System.out.printf("Alice address is: %s \n",alice.getAddress().plain());

        Account bob = Account.generateNewAccount(NetworkType.TEST_NET);
        System.out.printf("Bob address is: %s \n",bob.getAddress().plain());

        BigInteger amount = BigInteger.valueOf(1);

        Mosaic mosaic = new Mosaic(
            new MosaicId("091F837E059AE13C"),
            amount.multiply(
                BigInteger.valueOf(10).pow(6)
            )
        );

        TransferTransaction tx = TransferTransactionFactory.create(
                NetworkType.TEST_NET,
                Deadline.create(repo.getEpochAdjustment().toFuture().get()),
                bob.getAddress(),
                Collections.singletonList(mosaic)
            ).message(
                PlainMessage.create("This is a test message")
            ).maxFee(BigInteger.valueOf(2000000)
            ).build();

        String generationHash = repo.getGenerationHash().toFuture().get();
        SignedTransaction signedTx = alice.sign(tx, generationHash);

        System.out.printf("SignedTransaction is: %s ",signedTx.getHash());

        TransactionRepository txRepo = repo.createTransactionRepository();
        txRepo.announce(signedTx).toFuture().get();

        Listener listener = repo.createListener();
        listener.open().get();

        listener.confirmed(bob.getAddress())
        .subscribe(
            (notice) -> { System.out.println(notice.getType().toString());},
            (e) -> { System.out.println(e);}
        );

        run();
    }

    public String getGreeting() {
        return "Hello Symbol!";
    }

    private static void run() throws Exception {
        Thread.sleep(1000);
        run();
    }
}