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

今回はアグリゲートトランザクションなので、登場人物を３人用意しました。

```jsx
const alicePrivateKey = new symbolSdk.PrivateKey(ALICE_PRIVATE_KEY);
const aliceKeyPair = new facade.constructor.KeyPair(alicePrivateKey);
const aliceAddress = network.publicKeyToAddress(aliceKeyPair.publicKey);

const bobPrivateKey = new symbolSdk.PrivateKey(BOB_PRIVATE_KEY);
const bobKeyPair = new facade.constructor.KeyPair(bobPrivateKey);
const bobAddress = network.publicKeyToAddress(bobKeyPair.publicKey);

const carolPrivateKey = new symbolSdk.PrivateKey(CAROL_PRIVATE_KEY);
const carolKeyPair = new facade.constructor.KeyPair(carolPrivateKey);
const carolAddress = network.publicKeyToAddress(carolKeyPair.publicKey);
```

モザイクIDも定めておきます。

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

# 連署が要らないアグリゲートコンプリートトランザクション

Alice → Bob と Alice → Carol への送金のトランザクションを作ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172833/921a4241-7192-81b4-bc51-fd86bbfb4c13.png)


## 内部トランザクション

まずは、Alice → Bob。 `createEmbedded` を使うことに注意。

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

次いで、Alice → Carol。メッセージはありません。

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

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

これらをまとめてアグリゲートコンプリートトランザクションを作成します。

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

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

全体として必要な署名者は Alice のみで、アグリゲートトランザクションへの署名は Alice なので、追加の連署は必要ありません。

## 署名と送信

署名して、署名済みトランザクションを作ります。また、ハッシュも計算しておきます。

```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
const statusRes = await axios.get("<NODE_URL>/transactionStatus/" + hash).then((res) => res.data);
console.log(statusRes);
```

# アグリゲートボンデッドトランザクション

Alice → Bob、Bob → Carol への送金を行うトランザクションを作ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172833/ddfb55d1-9c02-7abd-aef1-093f10c81ef1.png)

アグリゲートトランザクションへの署名は Alice が行い、オンチェーン上で Bob が連署するというシナリオです。

## アグリゲートトランザクションを作成する

Alice → Bob へ送金します。 `createEmbedded` を使うことに注意。

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

Bob → Carol へ送金します。

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

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

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

const aggregateTransaction = facade.transactionFactory.create({
  type: 'aggregate_bonded_transaction_v2',
  signerPublicKey: aliceKeyPair.publicKey,
  fee: 1000000n,
  deadline,
  transactions: innerTransactions,
  transactionsHash,
});
```

署名をして、トランザクションハッシュを計算します。署名者は Alice です。

```jsx
const signatureAggregate = facade.signTransaction(aliceKeyPair, aggregateTransaction);
const jsonPayloadAggregate = facade.transactionFactory.constructor.attachSignature(aggregateTransaction, signatureAggregate);
const hashAggregate = facade.hashTransaction(aggregateTransaction);
```

## ハッシュロックトランザクションを作成する

アグリゲートボンデッドトランザクションなので、10 XEM を供託する必要があります。

そのためにはハッシュロックトランザクションを作成します。

まずは期間を定めます。これは10ブロックを指定してます。およそ3分で期限切れとなりますので、それまでにアグリゲートボンデッドトランザクションを送信しなければなりません。

```jsx
const duration = new symbolSdk.symbol.BlockDuration(10n);
```

ハッシュロックトランザクションを作ります。

```jsx
const hashLockTransaction = facade.transactionFactory.create({
  type: 'hash_lock_transaction_v1',
  signerPublicKey: aliceKeyPair.publicKey,
  fee: 1000000n,
  deadline,
  mosaic: { mosaicId: currencyMosaicId, amount: 10000000n },
  duration,
  hash: hashAggregate
});
```

署名。

```jsx
const signatureHashLock = facade.signTransaction(aliceKeyPair, hashLockTransaction);
const jsonPayloadHashLock = facade.transactionFactory.constructor.attachSignature(hashLockTransaction, signatureHashLock);
const hashHashLock = facade.hashTransaction(hashLockTransaction);
```

### 送信

 `axios` を使っています。

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

### 承認されるのを待つ

次の段階に進むためには、このハッシュロックトランザクションが承認される必要があります。

 `/transactionStatus` にアクセスして、 `group = "confirmed"` になるのを待ちます。

```jsx
for (let i = 0; i < 100; i++) {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  const hashLockStatus = await axios.get(`${NODE_URL}/transactionStatus/${hashHashLock}`).then((res) => res.data);
  if (hashLockStatus.group === "confirmed") {
    break;
  }
}
```

forループを使っていますが、無限ループ `while(true)` でもいいです。好みの問題です。

## アグリゲートボンデッドトランザクションを送信する

送信先は `/transactions/partial` となっており、他のトランザクションと異なります。

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

ステータスを確認します。

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

`group` が `partial` になっていれば成功です。

## 連署を作成する

Bob の連署を作成します。

連署は、アグリゲートボンデッドトランザクションのトランザクションハッシュに対して署名を行います。

```jsx
const bobCosignature = bobKeyPair.sign(hashAggregate.bytes);
```

エンドポイント用にデータを作成します。

```jsx
const bobCosignatureReq = {
  parentHash: hashAggregate.toString(),
  signature: bobCosignature.toString(),
  signerPublicKey: bobKeyPair.publicKey.toString(),
  version: "0",
}
```

送信します。送信先は、 `/transactions/cosignature` です。

```jsx
await axios.put(`${NODE_URL}/transactions/cosignature`, bobCosignatureReq)
  .then((res) => res.data)
  .then((sendRes) => {
    console.log(sendRes);
  })
  .catch((e) => {
    console.log(e.message, e.response.data)
  });
```

## ステータスを確認する

連署する前にも確認しましたが、ここで再度アグリゲートボンデッドトランザクションの状態を確認してみます。

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

`group` が `unconfirmed` もしくは `confirmed` になっていれば成功です。

# 連署を付けたアグリゲートコンプリートトランザクション

Alice → Bob、Bob → Carol への送金を行うトランザクションを作ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172833/ddfb55d1-9c02-7abd-aef1-093f10c81ef1.png)

ですが今回は、アグリゲートコンプリートトランザクションとして作成します。

## 内部トランザクション

Alice → Bob へ送金します。 `createEmbedded` を使うことに注意。

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

Bob → Carol へ送金します。

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

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

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

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

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

先に Alice の署名を行い、トランザクションハッシュを計算します。ここで、 `attachSignature` の戻り値は破棄します。

```jsx
const signature = facade.signTransaction(aliceKeyPair, transaction);
facade.transactionFactory.constructor.attachSignature(transaction, signature);
const hash = facade.hashTransaction(transaction).toString();
```

Bob の連署を作成し、トランザクションに取り込みます。

```jsx
const bobCosignature = facade.cosignTransaction(bobKeyPair, transaction);

transaction.cosignatures.push(bobCosignature);
```

ここで、もう一度 `attachSignature` を行い、トランザクションのペイロードを入手します。

```jsx
const jsonPayload = facade.transactionFactory.constructor.attachSignature(transaction, signature);
```

## 送信

あとはこのトランザクションを通常通り送信するだけです。そのあと、ステータスを確認します。

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

const statusRes = await axios.get(`${NODE_URL}/transactionStatus/${hash}`).then((res) => res.data);
console.log(statusRes);
```

# おわりに

今回は、アグリゲートトランザクションを作成していきました。

特にアグリゲートボンデッドトランザクションは、ハッシュロックトランザクションを送信しなければならないので、長くなりがちですが、便利なのでどんどん使っていきたいですね。

# シリーズ

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

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

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