symbol-sdk@3.0.7で作る転送トランザクション

# はじめに

こんにちは。
symbol-sdkの3.0.7がついに出ました。学習のために、様々なトランザクションを作ってみようと思います。

https://www.npmjs.com/package/symbol-sdk/v/3.0.7

全部いっぺんに書くと長くなってしまうので、少しずつ分けて書いていく予定です。

今回は、転送トランザクションについて書いてみたいと思います。

## 注意事項

書いてあるコードについて、正確性や完全性を保証するものではありません。あくまで参考程度として頂き、最新情報は公式ドキュメンテーションをご確認ください。

# Transfer Transaction

まずはデッドラインと鍵ペアです。

```jsx
import symbolSdk from 'symbol-sdk';

const network = symbolSdk.symbol.Network.TESTNET;
const deadline = network.fromDatetime(new Date(Date.now() + 7200000)).timestamp;

const facade = new symbolSdk.facade.SymbolFacade(network.name);

const privateKey = new symbolSdk.PrivateKey(PRIVATE_KEY);
const keyPair = new facade.constructor.KeyPair(privateKey);
```

## シンプル

最もシンプルな形です。モザイクもメッセージもありません。

```jsx
const transaction = facade.transactionFactory.create({
  type: 'transfer_transaction_v1',
  signerPublicKey: keyPair.publicKey.toString(),
  fee: 1000000n,
  deadline,
  recipientAddress: 'TARDV42KTAIZEF64EQT4NXT7K55DHWBEFIXVJQY',
});
```

## モザイク

モザイクを送信してみます。

このように、モザイクIDをBigIntで指定して、

```jsx
const currencyMosaicId = 0x72C0212E67A08BCEn;
```

 `mosaics` として渡します。

```jsx
const transaction = facade.transactionFactory.create({
  type: 'transfer_transaction_v1',
  signerPublicKey: keyPair.publicKey.toString(),
  fee: 1000000n,
  deadline,
  recipientAddress: 'TARDV42KTAIZEF64EQT4NXT7K55DHWBEFIXVJQY',
  mosaics: [
    { mosaicId: currencyMosaicId, amount: 1000000n },
  ],
});
```

### ネームスペース

ネームスペースで送る場合は、以下のようなユーティリティが提供されていました。

```jsx
const xymNamespaceId = symbolSdk.symbol.generateMosaicAliasId('symbol.xym');
```

これを使って、以下のようにします。

```jsx
  mosaics: [
    { mosaicId: currencyMosaicId, amount: 1000000n },
		{ mosaicId: xymNamespaceId, amount: 1000000n },
  ],
```

## メッセージ

文字列をutf-8にするために、Node.jsのパッケージをインポートします。

```jsx
import { TextEncoder } from 'util';

const textEncoder = new TextEncoder();
```

### 平文

先頭に0x00をつけてエンコードしたデータを用います。

```jsx
const message = new Uint8Array([0x00, ...textEncoder.encode('あいうえお')]);
```

トランザクション作成は以下のようになります。

```jsx
const transaction = facade.transactionFactory.create({
  type: 'transfer_transaction_v1',
  signerPublicKey: keyPair.publicKey.toString(),
  fee: 1000000n,
  deadline,
  recipientAddress: 'TARDV42KTAIZEF64EQT4NXT7K55DHWBEFIXVJQY',
  mosaics: [
    { mosaicId: currencyMosaicId, amount: 1000000n },
  ],
  message,
});
```

### 暗号化メッセージ

メッセージを暗号化するクラスを使います。このとき、暗号化メッセージを送る際には、受信者の公開鍵が必要になります。

```jsx
const messageEncoder = new symbolSdk.symbol.MessageEncoder(keyPair);
const message = messageEncoder.encode(
  new symbolSdk.PublicKey(PUBLIC_KEY_RECEIVER),
  textEncoder.encode('message')
)
```

メッセージを復号するには、以下のようにします。

```jsx
const [result, decodedMessage] = messageEncoder.tryDecode(
  new symbolSdk.PublicKey(PUBLIC_KEY_RECEIVER),
  message
)
```

ただ、これを使ってトランザクションを送ってみたものの、Symbolデスクトップウォレットではうまく復号できませんでした。原因はまだ追い切れてません。何か知ってる方おられましたら教えてください。

## 受取人

### アドレス

受取人をアドレスで指定する場合は、文字列で指定すればいいようです。

```jsx
const transaction = facade.transactionFactory.create({
  type: 'transfer_transaction_v1',
  signerPublicKey: keyPair.publicKey.toString(),
  fee: 1000000n,
  deadline,
  recipientAddress: 'TARDV42KTAIZEF64EQT4NXT7K55DHWBEFIXVJQY',
});
```

### ネームスペース

受取人をネームスペースで指定する場合は、ちょっとうまいやり方がわかりませんでした。

とりあえず、 `recipientAddress` に `UnresolvedAddress` 型が使われているのはわかりました。なので、データを作ってすごく強引にトランザクションに入れ込んだら、とりあえずは動きました。

```jsx
const addressAliasId = symbolSdk.symbol.generateMosaicAliasId('address.alias');
const hexByteArray = addressAliasId.toString(16).toUpperCase().match(/../g);
hexByteArray.reverse();
const addressAliasIdLE = hexByteArray.join('');
const addressAlias = `99${addressAliasIdLE}`.padEnd(48, '0');
const unresolvedAddress = new symbolSdk.symbol.UnresolvedAddress(symbolSdk.utils.hexToUint8(addressAlias));

const transaction = facade.transactionFactory.create({
  type: 'transfer_transaction_v1',
  signerPublicKey: keyPair.publicKey.toString(),
  fee: 1000000n,
  deadline,
});

transaction.recipientAddress = unresolvedAddress;
```

## アグリゲートトランザクション

転送トランザクションをアグリゲートトランザクションにしてみます。

詳細は別にアグリゲートトランザクションの回でやってみたいと思いますので、今回はシンプルに。

まずは `createEmbedded` で内部トランザクションを作成。

```jsx
const innerTransaction1 = facade.transactionFactory.createEmbedded({
  type: 'transfer_transaction_v1',
  signerPublicKey: keyPair.publicKey.toString(),
  recipientAddress: ADDRESS_RECEIVER_1,
  mosaics: [
    { mosaicId: currencyMosaicId, amount: 1000000n },
  ],
  message,
});

const innerTransaction2 = facade.transactionFactory.createEmbedded({
  type: 'transfer_transaction_v1',
  signerPublicKey: keyPair.publicKey.toString(),
  recipientAddress: ADDRESS_RECEIVER_2,
  mosaics: [
    { mosaicId: currencyMosaicId, amount: 2000000n },
  ],
  message,
});
```

ここで、内部トランザクションのマークルルートを計算します。

```jsx
const innerTransactions = [ innerTransaction1, innerTransaction2 ];
const transactionsHash = symbolSdk.facade.SymbolFacade.hashEmbeddedTransactions(innerTransactions)
```

あとはこれらをアグリゲートトランザクションに渡します。

```jsx
const transaction = facade.transactionFactory.create({
  type: 'aggregate_complete_transaction_v2',
  signerPublicKey: keyPair.publicKey.toString(),
  fee: 1000000n,
  deadline: BigInt(Date.now()) - epochAdjustment + 7200000n,
  transactions: innerTransactions,
  transactionsHash,
});
```

そして、これに署名をして完了です。

## 署名と送信

```javascript
const signature = facade.signTransaction(keyPair, transaction);
const jsonPayload = facade.transactionFactory.constructor.attachSignature(transaction, signature);
const hash = facade.hashTransaction(transaction).toString();

console.log(jsonPayload);
console.log(hash);

const sendRes = await axios.put(`${NODE_URL}/transactions`, jsonPayload).then((res) => res.data);
console.log(sendRes);

await axios
  .get(`${NODE_URL}/transactionStatus/${hash}`).then((res) => res.data)
  .then((statusRes) => {
    console.log(statusRes)
  })
  .catch((e) => {
    console.log(e.message, e.response.data)
  });
```

# おわりに

今回は転送トランザクションについて書いてみました。

これだけでもまぁまぁな量がありました。まだうまく行ってない個所もありますが、また進捗ありましたら更新したいと思います。