Blockchain Symbol と NEM の転送トランザクションを発行するコード見比べてみる

## はじめに
何か違うのか両方のチェーンの転送トランザクションを投げるコードを書いて比べてみました。

## Faced生成
SymbolとNEMかの指定の違いだけで、殆ど同じですね。

**Symbol**
```typescript
// faced生成
const facade = new symbolSdk.facade.SymbolFacade("testnet");
```

**NEM**
```typescript
// facade生成
const facade = new symbolSdk.facade.NemFacade("testnet");
```

## アカウント生成
こちらも、SymbolとNEMかの指定の違いだけで、またまたほぼ同じです。

SymbolとNEMの秘密鍵長は同じなので、流用できます。やっていいのかどうかは分かりませんが…
なお、仕組みが違うので同じ秘密鍵から生成してもアドレス、公開鍵は異なります。

**Symbol**
```typescript
// 秘密鍵からアリスアカウント生成
const aliceKeyPair = new symbolSdk.symbol.KeyPair(
  new symbolSdk.PrivateKey(ALICE_PRIVATE_KEY)
);
// 公開鍵からボブアドレス生成
const bobPublicKey = new symbolSdk.symbol.PublicKey(
  Uint8Array.from(Buffer.from(BOB_PUBLIC_KEY, "hex"))
);
const bobAddress: Address = facade.network.publicKeyToAddress(bobPublicKey);
```

**NEM**
```typescript
// 秘密鍵からアリスアカウント生成
const aliceKeyPair = new symbolSdk.nem.KeyPair(
  new symbolSdk.PrivateKey(ALICE_PRIVATE_KEY)
);
// 公開鍵からボブアドレス生成
const bobPublicKey = new symbolSdk.nem.PublicKey(
  Uint8Array.from(Buffer.from(BOB_PUBLIC_KEY, "hex"))
);
const bobAddress: Address = facade.network.publicKeyToAddress(bobPublicKey);
```

## ネットワーク時間
ネットワーク時間は実時間よりズレているので、上限ギリギリの時間をセットする場合は、ノードからネットワーク時間を取得します。
ただ、NEMはトランザクション作るのに現在ネットワーク時刻が必須です。
Symbolはdeadlineの設定で必要としていますが、未来の時間を入力するので実時間で問題ないです（ギリギリだとアウトですけど…）。

今回は書き方を合わせるためにSymbolもネットワーク時間を取得しています。

**Symbol**
```typescript
// ネットワーク時間取得
const networkTimeRes = await fetch(new URL("/node/time", NODE), {
  method: "GET",
  headers: { "Content-Type": "application/json" },
});
const nodeTimeJson = await networkTimeRes.json();
const nodeTime = BigInt(nodeTimeJson.communicationTimestamps.receiveTimestamp);
const symbolNetworkTime = new symbolSdk.symbol.NetworkTimestamp(nodeTime);
```

**NEM**
```typescript
// ネットワーク時間取得
const networkTimeRes = await fetch(new URL("/time-sync/network-time", NODE), {
  method: "GET",
  headers: { "Content-Type": "application/json" },
});
const nodeTimeJson = await networkTimeRes.json();
const nodeTime = BigInt(nodeTimeJson.receiveTimeStamp);
const nemNetworkTime = new symbolSdk.nem.NetworkTimestamp(nodeTime);
```

## メッセージ作成
NEMがクラスやら定数があってすっきり書けて、Symbolはごちゃってますが、やってることは同じです。1024Byteの先頭1byteに制御用のコードが付与され、その後にメッセージ本文が続きます。

**Symbol**
```typescript
// 平文メッセージ作成
const messageData = new Uint8Array([
  0x00,
  ...new TextEncoder().encode("はじめてのSymbolトランザクション"),
]);
```

**NEM**
```typescript
// 平文メッセージ作成
const nemMessage = new symbolSdk.nem.Message();
nemMessage.messageType = symbolSdk.nem.MessageType.PLAIN;
nemMessage.message = new TextEncoder().encode("はじめてのNemトランザクション");
```

## トランザクション生成
だいたいパラメータは同じですが、NEMのみtimestampがあります。あと、NEMのtimestamp、deadlineはNumber型で、SymbolはBigInt型です。NEMはミリ秒ないと思っておけば大丈夫です。

手数料の計算は異なります。Symbolはトランザクションの容量から算出できます。NEMは計算式が分からなかったので、足りるだろうと思われる値を固定で入れてます。

NEMの転送トランザクションv1はXEMのみ扱えるので、モザイクの指定はありません。

**Symbol**
```typescript
// トランザクション生成
const transferTx = facade.transactionFactory.create({
  type: "transfer_transaction_v1",
  signerPublicKey: aliceKeyPair.publicKey,
  deadline: symbolNetworkTime.addHours(2).timestamp,
  recipientAddress: bobAddress.toString(),
  mosaics: [{ mosaicId: 0x72c0212e67a08bcen, amount: 100000n }],
  message: messageData,
}) as TransferTransactionV1;
transferTx.fee = new symbolSdk.symbol.Amount(BigInt(transferTx.size * 100));
```

**NEM**
```typescript
// トランザクション生成
const transferTx = facade.transactionFactory.create({
  type: "transfer_transaction_v1",
  signerPublicKey: aliceKeyPair.publicKey,
  timestamp: Number(nemNetworkTime.timestamp / 1000n),
  deadline: Number(nemNetworkTime.addHours(2).timestamp / 1000n),
  recipientAddress: bobAddress,
  amount: 100000n,
  message: nemMessage,
  fee: 500000n,
}) as TransferTransactionV1;
```

## 署名
SymbolとNEMかの指定の違いだけで、同じですね。

**Symbol**
```typescript
// 署名
const transferTxSignature = facade.signTransaction(aliceKeyPair, transferTx);
const transferTxPayloadJson =
  symbolSdk.symbol.SymbolTransactionFactory.attachSignature(
    transferTx,
    transferTxSignature
  );
const transferTxHash = facade.hashTransaction(transferTx);
console.log(`transferTxHash: ${transferTxHash}`);
```

**NEM**
```typescript
// 署名
const transferTxSignature = facade.signTransaction(aliceKeyPair, transferTx);
const transferTxPayloadJson =
  symbolSdk.nem.NemTransactionFactory.attachSignature(
    transferTx,
    transferTxSignature
  );
const transferTxHash = facade.hashTransaction(transferTx);
console.log(`transferTxHash: ${transferTxHash}`);
```

## アナウンス
RESTにペイロードJsonを投げるだけですね。
ただ、メソッドがSymbolがPUT、NEMがPOSTとなっています。

**Symbol**
```typescript
// RESTにアナウンス
const announceResponse = await fetch(new URL("/transactions", NODE), {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  body: transferTxPayloadJson,
});
const announceResponseJson = await announceResponse.json();
console.log(announceResponseJson);
```

**NEM**
```typescript
// RESTにアナウンス
const announceResponse = await fetch(new URL("/transaction/announce", NODE), {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: transferTxPayloadJson,
});
const announceResponseJson = await announceResponse.json();
console.log(announceResponseJson);
```

## 全体のコード
**Symbol**
```typescript
import symbolSdk from "symbol-sdk";
import {
  Address,
  TransferTransactionV1,
} from "symbol-sdk/ts/src/symbol/models.js";

/** APIノードURL */
const NODE = "http://sym-test-01.opening-line.jp:3000";

/** アリス秘密鍵 */
const ALICE_PRIVATE_KEY =
  "****************************************************************";

/** ボブ公開鍵 */
const BOB_PUBLIC_KEY =
  "94EC711522B4B32A1B6A6ED61D86D1E3EE11AFB9B912A17F8983EED3808819FD";

// faced生成
const facade = new symbolSdk.facade.SymbolFacade("testnet");

// 秘密鍵からアリスアカウント生成
const aliceKeyPair = new symbolSdk.symbol.KeyPair(
  new symbolSdk.PrivateKey(ALICE_PRIVATE_KEY)
);

// 公開鍵からボブアドレス生成
const bobPublicKey = new symbolSdk.symbol.PublicKey(
  Uint8Array.from(Buffer.from(BOB_PUBLIC_KEY, "hex"))
);
const bobAddress: Address = facade.network.publicKeyToAddress(bobPublicKey);

// ネットワーク時間取得
const networkTimeRes = await fetch(new URL("/node/time", NODE), {
  method: "GET",
  headers: { "Content-Type": "application/json" },
});
const nodeTimeJson = await networkTimeRes.json();
const nodeTime = BigInt(nodeTimeJson.communicationTimestamps.receiveTimestamp);
const symbolNetworkTime = new symbolSdk.symbol.NetworkTimestamp(nodeTime);

// 平文メッセージ作成
const messageData = new Uint8Array([
  0x00,
  ...new TextEncoder().encode("はじめてのSymbolトランザクション"),
]);

// トランザクション生成
const transferTx = facade.transactionFactory.create({
  type: "transfer_transaction_v1",
  signerPublicKey: aliceKeyPair.publicKey,
  deadline: symbolNetworkTime.addHours(2).timestamp,
  recipientAddress: bobAddress.toString(),
  mosaics: [{ mosaicId: 0x72c0212e67a08bcen, amount: 100000n }],
  message: messageData,
}) as TransferTransactionV1;
transferTx.fee = new symbolSdk.symbol.Amount(BigInt(transferTx.size * 100));

// 署名
const transferTxSignature = facade.signTransaction(aliceKeyPair, transferTx);
const transferTxPayloadJson =
  symbolSdk.symbol.SymbolTransactionFactory.attachSignature(
    transferTx,
    transferTxSignature
  );
const transferTxHash = facade.hashTransaction(transferTx);
console.log(`transferTxHash: ${transferTxHash}`);

// RESTにアナウンス
const announceResponse = await fetch(new URL("/transactions", NODE), {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  body: transferTxPayloadJson,
});
const announceResponseJson = await announceResponse.json();
console.log(announceResponseJson);
```

**NEM**
```typescript
import symbolSdk from "symbol-sdk";
import {
  Address,
  TransferTransactionV1,
} from "symbol-sdk/ts/src/nem/models.js";

/** APIノードURL */
const NODE = "http://hugetestalice.nem.ninja:7890";

/** アリス秘密鍵 */
const ALICE_PRIVATE_KEY =
  "****************************************************************";

/** ボブ公開鍵 */
const BOB_PUBLIC_KEY =
  "6048C62CDCEB5D80E8B4C637B9F149F6FA71FB4A227E1C16AF273549B09DCB1E";

// facade生成
const facade = new symbolSdk.facade.NemFacade("testnet");

// 秘密鍵からアリスアカウント生成
const aliceKeyPair = new symbolSdk.nem.KeyPair(
  new symbolSdk.PrivateKey(ALICE_PRIVATE_KEY)
);

// 公開鍵からボブアドレス生成
const bobPublicKey = new symbolSdk.nem.PublicKey(
  Uint8Array.from(Buffer.from(BOB_PUBLIC_KEY, "hex"))
);
const bobAddress: Address = facade.network.publicKeyToAddress(bobPublicKey);

// ネットワーク時間取得
const networkTimeRes = await fetch(new URL("/time-sync/network-time", NODE), {
  method: "GET",
  headers: { "Content-Type": "application/json" },
});
const nodeTimeJson = await networkTimeRes.json();
const nodeTime = BigInt(nodeTimeJson.receiveTimeStamp);
const nemNetworkTime = new symbolSdk.nem.NetworkTimestamp(nodeTime);

// 平文メッセージ作成
const nemMessage = new symbolSdk.nem.Message();
nemMessage.messageType = symbolSdk.nem.MessageType.PLAIN;
nemMessage.message = new TextEncoder().encode("はじめてのNemトランザクション");

// トランザクション生成
const transferTx = facade.transactionFactory.create({
  type: "transfer_transaction_v1",
  signerPublicKey: aliceKeyPair.publicKey,
  timestamp: Number(nemNetworkTime.timestamp / 1000n),
  deadline: Number(nemNetworkTime.addHours(2).timestamp / 1000n),
  recipientAddress: bobAddress,
  amount: 100000n,
  message: nemMessage,
  fee: 500000n,
}) as TransferTransactionV1;

// 署名
const transferTxSignature = facade.signTransaction(aliceKeyPair, transferTx);
const transferTxPayloadJson =
  symbolSdk.nem.NemTransactionFactory.attachSignature(
    transferTx,
    transferTxSignature
  );
const transferTxHash = facade.hashTransaction(transferTx);
console.log(`transferTxHash: ${transferTxHash}`);

// RESTにアナウンス
const announceResponse = await fetch(new URL("/transaction/announce", NODE), {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: transferTxPayloadJson,
});
const announceResponseJson = await announceResponse.json();
console.log(announceResponseJson);
```

## さいごに
ほぼ、同じ書き方でトランザクション投げれる。***すごい！！***