はじめに
こんにちは。
symbol-sdkの3系、トランザクションシリーズ第四弾。
今回はアグリゲートトランザクションをやっていきます。
注意事項
書いてあるコードについて、正確性や完全性を保証するものではありません。あくまで参考程度として頂き、最新情報は公式ドキュメンテーションをご確認ください。
共通
まずは共通となる個所について。ネットワークやデッドライン、鍵ペアです。
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);
今回はアグリゲートトランザクションなので、登場人物を3人用意しました。
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も定めておきます。
const currencyMosaicId = 0x72C0212E67A08BCEn;
連署が要らないアグリゲートコンプリートトランザクション
Alice → Bob と Alice → Carol への送金のトランザクションを作ります。
内部トランザクション
まずは、Alice → Bob。 createEmbedded
を使うことに注意。
const innerTransaction1 = facade.transactionFactory.createEmbedded({
type: 'transfer_transaction_v1',
signerPublicKey: aliceKeyPair.publicKey,
recipientAddress: bobAddress,
mosaics: [
{ mosaicId: currencyMosaicId, amount: 1000000n },
],
});
次いで、Alice → Carol。メッセージはありません。
const innerTransaction2 = facade.transactionFactory.createEmbedded({
type: 'transfer_transaction_v1',
signerPublicKey: aliceKeyPair.publicKey,
recipientAddress: carolAddress,
mosaics: [
{ mosaicId: currencyMosaicId, amount: 2000000n },
],
});
アグリゲートトランザクション
これらをまとめてアグリゲートコンプリートトランザクションを作成します。
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 なので、追加の連署は必要ありません。
署名と送信
署名して、署名済みトランザクションを作ります。また、ハッシュも計算しておきます。
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
を使います。
const sendRes = await axios.put("<NODE_URL>/transactions", jsonPayload).then((res) => res.data);
console.log(sendRes);
送信後、1秒程度待ってから、ステータスを確認。
const statusRes = await axios.get("<NODE_URL>/transactionStatus/" + hash).then((res) => res.data);
console.log(statusRes);
アグリゲートボンデッドトランザクション
Alice → Bob、Bob → Carol への送金を行うトランザクションを作ります。
アグリゲートトランザクションへの署名は Alice が行い、オンチェーン上で Bob が連署するというシナリオです。
アグリゲートトランザクションを作成する
Alice → Bob へ送金します。 createEmbedded
を使うことに注意。
const innerTransaction1 = facade.transactionFactory.createEmbedded({
type: 'transfer_transaction_v1',
signerPublicKey: aliceKeyPair.publicKey,
recipientAddress: bobAddress,
mosaics: [
{ mosaicId: currencyMosaicId, amount: 1000000n },
],
});
Bob → Carol へ送金します。
const innerTransaction2 = facade.transactionFactory.createEmbedded({
type: 'transfer_transaction_v1',
signerPublicKey: bobKeyPair.publicKey,
recipientAddress: carolAddress,
mosaics: [
{ mosaicId: currencyMosaicId, amount: 2000000n },
],
});
これらをアグリゲートトランザクションにします。
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 です。
const signatureAggregate = facade.signTransaction(aliceKeyPair, aggregateTransaction);
const jsonPayloadAggregate = facade.transactionFactory.constructor.attachSignature(aggregateTransaction, signatureAggregate);
const hashAggregate = facade.hashTransaction(aggregateTransaction);
ハッシュロックトランザクションを作成する
アグリゲートボンデッドトランザクションなので、10 XEM を供託する必要があります。
そのためにはハッシュロックトランザクションを作成します。
まずは期間を定めます。これは10ブロックを指定してます。およそ3分で期限切れとなりますので、それまでにアグリゲートボンデッドトランザクションを送信しなければなりません。
const duration = new symbolSdk.symbol.BlockDuration(10n);
ハッシュロックトランザクションを作ります。
const hashLockTransaction = facade.transactionFactory.create({
type: 'hash_lock_transaction_v1',
signerPublicKey: aliceKeyPair.publicKey,
fee: 1000000n,
deadline,
mosaic: { mosaicId: currencyMosaicId, amount: 10000000n },
duration,
hash: hashAggregate
});
署名。
const signatureHashLock = facade.signTransaction(aliceKeyPair, hashLockTransaction);
const jsonPayloadHashLock = facade.transactionFactory.constructor.attachSignature(hashLockTransaction, signatureHashLock);
const hashHashLock = facade.hashTransaction(hashLockTransaction);
送信
axios
を使っています。
const sendResHashLock = await axios.put(`${NODE_URL}/transactions`, jsonPayloadHashLock).then((res) => res.data);
console.log(sendResHashLock);
承認されるのを待つ
次の段階に進むためには、このハッシュロックトランザクションが承認される必要があります。
/transactionStatus
にアクセスして、 group = "confirmed"
になるのを待ちます。
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
となっており、他のトランザクションと異なります。
const sendResAggregate = await axios.put(`${NODE_URL}/transactions/partial`, jsonPayloadAggregate).then((res) => res.data);
console.log(sendResAggregate);
ステータスを確認します。
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 の連署を作成します。
連署は、アグリゲートボンデッドトランザクションのトランザクションハッシュに対して署名を行います。
const bobCosignature = bobKeyPair.sign(hashAggregate.bytes);
エンドポイント用にデータを作成します。
const bobCosignatureReq = {
parentHash: hashAggregate.toString(),
signature: bobCosignature.toString(),
signerPublicKey: bobKeyPair.publicKey.toString(),
version: "0",
}
送信します。送信先は、 /transactions/cosignature
です。
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)
});
ステータスを確認する
連署する前にも確認しましたが、ここで再度アグリゲートボンデッドトランザクションの状態を確認してみます。
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 への送金を行うトランザクションを作ります。
ですが今回は、アグリゲートコンプリートトランザクションとして作成します。
内部トランザクション
Alice → Bob へ送金します。 createEmbedded
を使うことに注意。
const innerTransaction1 = facade.transactionFactory.createEmbedded({
type: 'transfer_transaction_v1',
signerPublicKey: aliceKeyPair.publicKey,
recipientAddress: bobAddress,
mosaics: [
{ mosaicId: currencyMosaicId, amount: 1000000n },
],
});
Bob → Carol へ送金します。
const innerTransaction2 = facade.transactionFactory.createEmbedded({
type: 'transfer_transaction_v1',
signerPublicKey: bobKeyPair.publicKey,
recipientAddress: carolAddress,
mosaics: [
{ mosaicId: currencyMosaicId, amount: 2000000n },
],
});
アグリゲートトランザクション
これらをアグリゲートトランザクションにします。
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
の戻り値は破棄します。
const signature = facade.signTransaction(aliceKeyPair, transaction);
facade.transactionFactory.constructor.attachSignature(transaction, signature);
const hash = facade.hashTransaction(transaction).toString();
Bob の連署を作成し、トランザクションに取り込みます。
const bobCosignature = facade.cosignTransaction(bobKeyPair, transaction);
transaction.cosignatures.push(bobCosignature);
ここで、もう一度 attachSignature
を行い、トランザクションのペイロードを入手します。
const jsonPayload = facade.transactionFactory.constructor.attachSignature(transaction, signature);
送信
あとはこのトランザクションを通常通り送信するだけです。そのあと、ステータスを確認します。
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);
おわりに
今回は、アグリゲートトランザクションを作成していきました。
特にアグリゲートボンデッドトランザクションは、ハッシュロックトランザクションを送信しなければならないので、長くなりがちですが、便利なのでどんどん使っていきたいですね。
シリーズ