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

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

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


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

https://docs.symbolplatform.com/ja/getting-started/setup-workstation.html#creating-a-project

ここで

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

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

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


### 基本プログラム

```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

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

```java
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では最初に宣言しておきます。

https://qiita.com/nem_takanobu/items/f3c02caa17ad385b6155


### リポジトリ生成

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

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

### アカウント生成

```java
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はランダムに生成しました。

### トークン定義

```java
BigInteger amount = BigInteger.valueOf(1);

Mosaic mosaic = new Mosaic(
    new MosaicId("091F837E059AE13C"),
    amount.multiply(
        BigInteger.valueOf(10).pow(6)
    )
);
```
送金するトークンを定義します。
テストネットのXYMはID:091F837E059AE13C 可分性:6で定義されています。
公式ドキュメントは古いテストネットのIDで解説されているのでご注意ください。

### トランザクション生成

```java
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の引数ではなく、メソッドチェーンで追加していきます。

https://docs.symbolplatform.com/ja/getting-started/first-application.html#sending-the-ticket

### 署名とアナウンス

```java
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で署名してネットワークにアナウンスします。

### 着金検知

```java
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さんの記事を参考にさせていただきました。

https://nemlog.nem.social/blog/63692

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

https://nemlog.nem.social/blog/67760


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

```java
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();
	}
}


```