NEM2カタパルトで双方向ペイメントチャネルするには その２ トランザクションは作れるのか

# はじめに

前回の記事

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

いろいろ書いてあるが、図に載っているトランザクションは本当に作れるのか。

図中にあるトランザクションは、２種類である。両者とも、Aggregate Bondedとなっており、Cosignが必要である。特に、Commitment Transactionは、Secret Lockが含まれるレアな使い方をしている。

今回は、図のようなトランザクションが作れて、送信できて、連署して、承認されることを目標とする。

# 作ってみる

## Opening Transaction

![image.png](https://qiita-image-store.s3.amazonaws.com/0/172833/a18f2fb6-4427-65d0-3536-e25e9072b787.png)

これは比較的簡単である。アカウントからアカウントへの送金トランザクションを２つ作り、アグリゲートトランザクションにしたものである。

いちおう、Lock Fundsは第三者が署名することにしている。この人をGuarantorとする。

```javascript
function createOpeningTransaction(multisigPublicAccount, account1, account2, accountG) {
    return new Promise((resolve, reject) => {
        const account1Tx = TransferTransaction.create(
            Deadline.create(),
            multisigPublicAccount.address,
            [XEM.createRelative(5)],
            PlainMessage.create('opening tx 5XEM'),
            NetworkType.MIJIN_TEST,
        );
        const publicAccount2Tx = TransferTransaction.create(
            Deadline.create(),
            multisigPublicAccount.address,
            [XEM.createRelative(5)],
            PlainMessage.create('opening tx 5XEM'),
            NetworkType.MIJIN_TEST,
        );
        const opningTransaction = AggregateTransaction.createBonded(
            Deadline.create(),
            [
                account1Tx.toAggregate(account1.publicAccount),
                publicAccount2Tx.toAggregate(account2.publicAccount),
            ],
            NetworkType.MIJIN_TEST
        );
        const signedTransaction = account1.sign(opningTransaction);
        console.log(signedTransaction.hash);
        const lockFundsTransaction = LockFundsTransaction.create(
            Deadline.create(),
            XEM.createRelative(10),
            UInt64.fromUint(480),
            signedTransaction,
            NetworkType.MIJIN_TEST
        );
        const signedLockFundsTransaction = accountG.sign(lockFundsTransaction);
        console.log(signedLockFundsTransaction.hash);
        const cosignAggregateBondedTransaction = function(transaction, account) {
            const cosignatureTransaction = CosignatureTransaction.create(transaction);
            const signedTransaction = account.signCosignatureTransaction(cosignatureTransaction);
            return signedTransaction;
        };
        const listener = new Listener(ENDPOINT);
        const transactionHttp = new TransactionHttp(ENDPOINT);
        listener.open().then(() => {
            transactionHttp.announce(signedLockFundsTransaction).subscribe(
                x => console.log(x),
                err => console.error(err)
            );
            listener.confirmed(accountG.address).pipe(
                filter((transaction) => transaction.transactionInfo !== undefined
                && transaction.transactionInfo.hash === signedLockFundsTransaction.hash),
                flatMap(ignored => transactionHttp.announceAggregateBonded(signedTransaction))
            ).subscribe(
                x => console.log(x),
                err => console.error(err)
            );
            listener.aggregateBondedAdded(account2.address).pipe(
                filter((_) => !_.signedByAccount(account2.publicAccount)),
                map(transaction => cosignAggregateBondedTransaction(transaction, account2)),
                flatMap(cosignatureSignedTransaction => transactionHttp.announceAggregateBondedCosignature(cosignatureSignedTransaction))
            ).subscribe(
                x => console.log(x),
                err => console.error(err)
            );
            listener.confirmed(account1.address).pipe(
                filter((transaction) => transaction.transactionInfo !== undefined
                && transaction.transactionInfo.hash === signedTransaction.hash)
            ).subscribe(
                ignore => {
                    listener.close();
                    resolve();
                },
                err => console.error(err)
            );
        }).catch((error) => {
            console.error(error);
        });
    });
}
```

### Commitment Transaction

![image.png](https://qiita-image-store.s3.amazonaws.com/0/172833/1e486400-d701-3cf9-6a12-02c3213b48a9.png)

こちらは少しややこしい。

マルチシグアカウントからの送金が２つ。そしてシークレットロックトランザクションが１つ入っている。

```javascript
function createCommitmentTransaction(multisigPublicAccount, account1, account2, accountG, amount1, amount2, secret) {
    return new Promise((resolve, reject) => {
        const account1Tx = TransferTransaction.create(
            Deadline.create(),
            account1.address,
            [XEM.createRelative(amount1)],
            PlainMessage.create(`commitment tx ${amount1}XEM`),
            NetworkType.MIJIN_TEST,
        );
        const account2Tx = TransferTransaction.create(
            Deadline.create(),
            account2.address,
            [XEM.createRelative(amount2)],
            PlainMessage.create(`commitment tx ${amount2}XEM`),
            NetworkType.MIJIN_TEST,
        );
        const secretLockTx = SecretLockTransaction.create(
            Deadline.create(),
            XEM.createRelative(amount2),
            UInt64.fromUint(1000 * 10),
            HashType.SHA3_512,
            secret,
            account1.address,
            NetworkType.MIJIN_TEST
        );
        const commitmentTransaction = AggregateTransaction.createBonded(
            Deadline.create(),
            [
                account1Tx.toAggregate(multisigPublicAccount),
                account2Tx.toAggregate(multisigPublicAccount),
                secretLockTx.toAggregate(account2.publicAccount),
            ],
            NetworkType.MIJIN_TEST
        );
        const signedTransaction = account1.sign(commitmentTransaction);
        console.log(signedTransaction.hash);
        const lockFundsTransaction = LockFundsTransaction.create(
            Deadline.create(),
            XEM.createRelative(10),
            UInt64.fromUint(480),
            signedTransaction,
            NetworkType.MIJIN_TEST
        );
        const signedLockFundsTransaction = accountG.sign(lockFundsTransaction);
        console.log(signedLockFundsTransaction.hash);
        const cosignAggregateBondedTransaction = function(transaction, account) {
            const cosignatureTransaction = CosignatureTransaction.create(transaction);
            const signedTransaction = account.signCosignatureTransaction(cosignatureTransaction);
            return signedTransaction;
        };
        const listener = new Listener(ENDPOINT);
        const transactionHttp = new TransactionHttp(ENDPOINT);
        listener.open().then(() => {
            transactionHttp.announce(signedLockFundsTransaction).subscribe(
                x => console.log(x),
                err => console.error(err)
            );
            listener.confirmed(accountG.address).pipe(
                filter((transaction) => transaction.transactionInfo !== undefined
                && transaction.transactionInfo.hash === signedLockFundsTransaction.hash),
                flatMap(ignored => transactionHttp.announceAggregateBonded(signedTransaction))
            ).subscribe(
                x => console.log(x),
                err => console.error(err)
            );
            listener.aggregateBondedAdded(account2.address).pipe(
                filter((_) => !_.signedByAccount(account2.publicAccount)),
                map(transaction => cosignAggregateBondedTransaction(transaction, account2)),
                flatMap(cosignatureSignedTransaction => transactionHttp.announceAggregateBondedCosignature(cosignatureSignedTransaction))
            ).subscribe(
                x => console.log(x),
                err => console.error(err)
            );
            listener.confirmed(account1.address).pipe(
                filter((transaction) => transaction.transactionInfo !== undefined
                && transaction.transactionInfo.hash === signedTransaction.hash)
            ).subscribe(
                ignore => {
                    listener.close();
                    resolve();
                },
                err => console.error(err)
            );
        }).catch((error) => {
            console.error(error);
        });
    });
}
```

# 送ってみる

送信者が誰とか、設定値がいくらとかは気にせずに、純粋にconfirmされるかどうかを確認する。

カタパルトは、Alpacaバージョンのものを使う。

ソースはこちら

https://github.com/planethouki/bidirectional-nem2-payment-channel-sample/blob/master/paymentch9a.js

Alice、Bob、マルチシグアカウントの秘密鍵は、実行の度に新しく作成している。そのため、マルチシグアカウントへの変換とXEMの送金を追加している。

## Opening Transaction

http://catapult48gh23s.xyz:3000/transaction/2CC60B778542BCB58579EA49EA3D5324DC3E7F375694E202D1B6F431628F3907

Gistに保存したもの

https://gist.github.com/planethouki/949d0280cd61870431f75470ea6798ac#file-paymentch9a-openingtx-json

## Commitment Transaction

http://catapult48gh23s.xyz:3000/transaction/B24F6D9078EC6849F0994B03779C3EDD31BEE886285D1C09F9E886C328A1AC8A

Gistに保存したもの

https://gist.github.com/planethouki/949d0280cd61870431f75470ea6798ac#file-paymentch9a-commitmenttx-json

# まとめ

作ることができる


# 関連

NEM2カタパルトで双方向ペイメントチャネルするには その１ 概要
https://qiita.com/planethouki/items/c22a21836d913418de82

NEM2カタパルトで双方向ペイメントチャネルするには その２ トランザクションは作れるのか
https://qiita.com/planethouki/items/53415a14b34bebd450fc

NEM2カタパルトで双方向ペイメントチャネルするには その３ ペイロードの送信と罰則
https://qiita.com/planethouki/items/945239fe04d2af1fb203