symbol-sdk@3.0.7で作るメタデータトランザクション

# はじめに

こんにちは。

symbol-sdkの3系、トランザクションシリーズ第六弾です。

今回はメタデータをやっていきます。

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

## 注意事項

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

# デッドラインなど

まずは各トランザクションで共通となる個所について。デッドラインと鍵ペアです。

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

`TextEncoder` も必要になりますので用意しておきます。

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

const textEncoder = new TextEncoder();
```

## メタデータのキー

メタデータはキーとバリューが必要になります。

キーの方はBigIntで4バイト。任意の値が設定できます。

```jsx
const scopedMetadataKey = 0x0000000012345678n;
```

## メタデータのバリュー

バリューのほうは、 `metadataUpdateValue` という関数があるのでそれを使っていきます。

仕様的には古い値と新しい値でXORをとるみたいです。新しくメタデータを追加するなど、古い値がない場合はXORは考えずに値そのままで大丈夫です。

```jsx
const value = symbolSdk.symbol.metadataUpdateValue(
	textEncoder.encode("oldValue"),
	textEncoder.encode("newValue")
);
```

ほかにも、Uint8Arrayをそのまま使うことができます。

```jsx
const value = textEncoder.encode("fuga");
```

文字列でもよいです。

```jsx
const value = "hoge";
```

## valueSizeDelta

メタデータの値のバイトサイズに関するパラメータです。（英数字だと基本的に1文字1バイトです。）

古いメタデータの値と新しいメタデータの値で、サイズが異なる場合、それをトランザクションに記述する必要があります。

（確か）古い値が4バイトで、新しい値が5バイトだった場合、 `valueSizeDelta = 1` となるはずです。

もし新しくメタデータを追加するなど、古い値がない場合は、新しいメタデータの値のサイズを指定します。

```jsx
const valueSizeDelta = 4;
```

# アカウントメタデータトランザクション

`targetAddress` にメタデータを付けたいアドレスを指定します。

```jsx
const accountMetadataTransaction = facade.transactionFactory.createEmbedded({
  type: 'account_metadata_transaction_v1',
  signerPublicKey: keyPair.publicKey.toString(),
  targetAddress: network.publicKeyToAddress(keyPair.publicKey),
  scopedMetadataKey,
  valueSizeDelta,
  value
});
```

# モザイクメタデータトランザクション

モザイクのメタデータですが、 `targetAddress` が必要になります。

`targetMosaicId` にモザイクIDを指定します。これはBigIntで指定しています。

```jsx
const mosaicMetadataTransaction = facade.transactionFactory.createEmbedded({
  type: 'mosaic_metadata_transaction_v1',
  signerPublicKey: keyPair.publicKey.toString(),
  targetAddress: network.publicKeyToAddress(keyPair.publicKey),
  scopedMetadataKey: 0x0000000012345678n,
  targetMosaicId: 0x500952FEB9E8A682n,
  valueSizeDelta: 4,
  value: symbolSdk.symbol.metadataUpdateValue(textEncoder.encode(""), textEncoder.encode("fuga")),
});
```

`targetMosaicId` は `UnresolvedMosaicId` でも大丈夫です。

```jsx
targetMosaicId: new symbolSdk.symbol.UnresolvedMosaicId(0x500952FEB9E8A682n),
```

# ネームスペースメタデータトランザクション

ネームスペースのメタデータですが、 `targetAddress` が必要になります。

`targetNamespaceId` にネームスペースIDを指定します。これはBigIntです。

```jsx
const namespaceMetadataTransaction = facade.transactionFactory.createEmbedded({
  type: 'namespace_metadata_transaction_v1',
  signerPublicKey: keyPair.publicKey.toString(),
  targetAddress: network.publicKeyToAddress(keyPair.publicKey),
  scopedMetadataKey: 0x0000000012345678n,
  targetNamespaceId: 0xFBE54EC2AAA8EFDFn,
  valueSizeDelta: 4,
  value: symbolSdk.symbol.metadataUpdateValue(textEncoder.encode(""), textEncoder.encode("fuga")),
});
```

`targetNamespaceId` は `NamespaceId` でも大丈夫です。

```jsx
targetNamespaceId: new symbolSdk.symbol.NamespaceId(0xFBE54EC2AAA8EFDFn),
```

# 共通

ここまでそれぞれメタデータのトランザクションを作りました。ここからは共通です。

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

送信するにあたっては、アグリゲートトランザクションでラップする必要があるみたいです。

（ラップしなかったら送信しても受理されなかった）

今回は、メタデータを付ける対象（`targetAddress`）がすべてトランザクションの署名者と一致しているので、アグリゲート **コンプリート** トランザクションで行います。

もしそうでない場合は、アグリゲート **ボンデッド** トランザクションにする必要があるかと思います。


```jsx
// 必要に応じてどれか選択
const innerTransactions = [ accountMetadataTransaction ];
const innerTransactions = [ mosaicMetadataTransaction ];
const innerTransactions = [ namespaceMetadataTransaction ];
```

今回はアグリゲートコンプリートトランザクションにします。

```jsx
const transactionsHash = symbolSdk.facade.SymbolFacade.hashEmbeddedTransactions(innerTransactions)

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

## 署名とハッシュ

署名をして、トランザクションにアタッチします。

ハッシュを計算します。

```jsx
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);
```

## 送信

`axios` を使います。

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

## ステータス取得

トランザクションが正しくネットワークに受理されたかを確認します。

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

# おわりに

メタデータのトランザクションを作りました。 `valueSizeDelta` を自分で計算するのは少し大変ですね。

# シリーズ

https://qiita.com/planethouki/items/860d9f1095c087ccd98f

https://qiita.com/planethouki/items/25f104ef4930b019ddfb

https://qiita.com/planethouki/items/525b5a582f78be57217b

https://qiita.com/planethouki/items/c0336b479f6c11918183

https://qiita.com/planethouki/items/348bbfdc550ab5528c31