エスクローコントラクトの作成

エスクローコントラクトの作成でアグリゲートボンドトランザクションについて学習します。

ユースケース

エスクロー とは 第三者が主要取引当事者のために、金銭または書類を受け取り・支払い をする 契約上の取り決め です。この支払いは 取引当事者によって合意された条件 または 取引の完了または終了までの間、ブローカーの本人または他の人物に代わって 資産を保持するためにブローカーによって確立されたアカウント に依存します。より詳しい説明は Wikipedia を参照してください。

この例では2つの当事者が実質的なサービスに同意すると仮定して、エスクローが即時に実行できることを意味します:

  1. 買い手と売り手が規約に同意する
  2. 買い手がエスクローへ商品代金を送る
  3. 売り手が商品またはサービスを買い手へ届ける
  4. 買い手は商品またはサービスを受理する
  5. エスクローは売り手へ商品代を開放する
../../_images/aggregate-escrow-11.png

マルチアセットエスクロートランザクション

Symbol でのエスクローコントラクトの作り方

前述の説明を Symbol 関連の概念に正規化します:

  • 契約上の取り決め: AggregateTransaction と呼ばれる新しいタイプの取引。
  • 第三者がお金を受け取って支払いをする: 第三者は介在しない、私たちはブロックチェーン技術を使うつもりです。
  • 主要取引当事者: Symbol アカウントが参加者を表現します。
  • トランザクション当事者によって合意される条件: すべての参加者が AggregateTransaction に署名するとき。
  • 資産を保持するためにブローカーによって確立された口座: 中間口座はありません、交換は AggregateTransaction を使用してアトミックに行われます。
  • 取引の完了または終了まで: トランザクションはブロックに含まれるか期限切れになります。

前提条件

要求されているアカウントとモザイクをセットアップします

Alice とチケット販売者は以下のモザイクを交換したいと思っています。

保有者 総量 MosaicId 説明
Alice 100 symbol.xym ネイティブ通貨モザイク
チケット販売者 1 7cdf3b117a3c40cc ミュージアムチケットの表現

継続する前に symbol.xym を保有した アカウントを2つ作成 します。チケット販売者アカウントで モザイクを作成 します。この新しいモザイクはチケットを表現します。

エスクローコントラクトの作成

  1. 新しくファイルを開いて、2つの転送トランザクションを定義:
  1. Alice からチケット販売者へ 100 symbol.xym を送信します
  2. チケット販売者から Alice へ 1 7cdf3b117a3c40cc (museum ticket) を送信する TransferTransaction

注釈

博物館のチケットはネットワーク内の ID 7cdf3b117a3c40cc を持っていません。前の手順で作成したものにモザイク識別子を置き換えてください。

// replace with network type
const networkType = NetworkType.TEST_NET;
// replace with alice private key
const alicePrivateKey =
  '1111111111111111111111111111111111111111111111111111111111111111';
const aliceAccount = Account.createFromPrivateKey(alicePrivateKey, networkType);
// replace with ticket distributor public key
const ticketDistributorPublicKey =
  '20330294DC18D96BDEEF32FB02338A6462A0469CB451A081DE2F05B4302C0C0A';
const ticketDistributorPublicAccount = PublicAccount.createFromPublicKey(
  ticketDistributorPublicKey,
  networkType,
);
// replace with ticket mosaic id
const ticketMosaicId = new MosaicId('7cdf3b117a3c40cc');
// replace with ticket mosaic id divisibility
const ticketDivisibility = 0;
// replace with symbol.xym id
const networkCurrencyMosaicId = new MosaicId('5E62990DCAC5BE8A');
// replace with network currency divisibility
const networkCurrencyDivisibility = 6;

const aliceToTicketDistributorTx = TransferTransaction.create(
  Deadline.create(epochAdjustment),
  ticketDistributorPublicAccount.address,
  [
    new Mosaic(
      networkCurrencyMosaicId,
      UInt64.fromUint(100 * Math.pow(10, networkCurrencyDivisibility)),
    ),
  ],
  PlainMessage.create('send 100 symbol.xym to distributor'),
  networkType,
);

const ticketDistributorToAliceTx = TransferTransaction.create(
  Deadline.create(epochAdjustment),
  aliceAccount.address,
  [
    new Mosaic(
      ticketMosaicId,
      UInt64.fromUint(1 * Math.pow(10, ticketDivisibility)),
    ),
  ],
  PlainMessage.create('send 1 museum ticket to customer'),
  networkType,
);
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
// replace with alice private key
const alicePrivateKey =
  '1111111111111111111111111111111111111111111111111111111111111111';
const aliceAccount = symbol_sdk_1.Account.createFromPrivateKey(
  alicePrivateKey,
  networkType,
);
// replace with ticket distributor public key
const ticketDistributorPublicKey =
  '20330294DC18D96BDEEF32FB02338A6462A0469CB451A081DE2F05B4302C0C0A';
const ticketDistributorPublicAccount = symbol_sdk_1.PublicAccount.createFromPublicKey(
  ticketDistributorPublicKey,
  networkType,
);
// replace with ticket mosaic id
const ticketMosaicId = new symbol_sdk_1.MosaicId('7cdf3b117a3c40cc');
// replace with ticket mosaic id divisibility
const ticketDivisibility = 0;
// replace with symbol.xym id
const networkCurrencyMosaicId = new symbol_sdk_1.MosaicId('5E62990DCAC5BE8A');
// replace with network currency divisibility
const networkCurrencyDivisibility = 6;
const aliceToTicketDistributorTx = symbol_sdk_1.TransferTransaction.create(
  symbol_sdk_1.Deadline.create(epochAdjustment),
  ticketDistributorPublicAccount.address,
  [
    new symbol_sdk_1.Mosaic(
      networkCurrencyMosaicId,
      symbol_sdk_1.UInt64.fromUint(
        100 * Math.pow(10, networkCurrencyDivisibility),
      ),
    ),
  ],
  symbol_sdk_1.PlainMessage.create('send 100 symbol.xym to distributor'),
  networkType,
);
const ticketDistributorToAliceTx = symbol_sdk_1.TransferTransaction.create(
  symbol_sdk_1.Deadline.create(epochAdjustment),
  aliceAccount.address,
  [
    new symbol_sdk_1.Mosaic(
      ticketMosaicId,
      symbol_sdk_1.UInt64.fromUint(1 * Math.pow(10, ticketDivisibility)),
    ),
  ],
  symbol_sdk_1.PlainMessage.create('send 1 museum ticket to customer'),
  networkType,
);
            NetworkType networkType = repositoryFactory.getNetworkType().toFuture().get();

            // replace with alice private key
            String alicePrivatekey = "";
            Account aliceAccount = Account.createFromPrivateKey(alicePrivatekey, networkType);

            // replace with bob public key
            String ticketDistributorPublicKey = "";
            PublicAccount ticketDistributorPublicAccount = PublicAccount
                .createFromPublicKey(ticketDistributorPublicKey, networkType);

            // replace with ticket mosaic id
            MosaicId ticketMosaicId = new MosaicId("7cdf3b117a3c40cc");
            int ticketDivisibility = 0;
            NetworkCurrency ticketCurrency = new NetworkCurrencyBuilder(ticketMosaicId, ticketDivisibility).build();
            // replace with ticket mosaic id divisibility
            NetworkCurrency networkCurrency = repositoryFactory.getNetworkCurrency().toFuture().get();

            TransferTransaction aliceToTicketDistributorTx = TransferTransactionFactory
                .create(networkType, ticketDistributorPublicAccount.getAddress(),
                    Collections.singletonList(networkCurrency.createRelative(BigInteger.valueOf(100))),
                    PlainMessage.create("send 100 symbol.xym to distributor")).build();

            TransferTransaction ticketDistributorToAliceTx = TransferTransactionFactory
                .create(networkType, aliceAccount.getAddress(),
                    Collections.singletonList(ticketCurrency.createRelative(BigInteger.ONE)),
                    PlainMessage.create("send 1 museum ticket to customer")).build();

2. Wrap the defined transactions in an AggregateTransaction and sign it with Alice’s account. An AggregateTransaction is complete if before announcing it to the network, all required cosigners have signed it. If valid, it will be included in a block. In case that signatures are required from other participants—the ticket distributor—it is considered bonded.

const aggregateTransaction = AggregateTransaction.createBonded(
  Deadline.create(epochAdjustment),
  [
    aliceToTicketDistributorTx.toAggregate(aliceAccount.publicAccount),
    ticketDistributorToAliceTx.toAggregate(ticketDistributorPublicAccount),
  ],
  networkType,
  [],
  UInt64.fromUint(2000000),
);

// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
  '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = aliceAccount.sign(
  aggregateTransaction,
  networkGenerationHash,
);
console.log('Aggregate Transaction Hash:', signedTransaction.hash);
const aggregateTransaction = symbol_sdk_1.AggregateTransaction.createBonded(
  symbol_sdk_1.Deadline.create(epochAdjustment),
  [
    aliceToTicketDistributorTx.toAggregate(aliceAccount.publicAccount),
    ticketDistributorToAliceTx.toAggregate(ticketDistributorPublicAccount),
  ],
  networkType,
  [],
  symbol_sdk_1.UInt64.fromUint(2000000),
);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
  '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = aliceAccount.sign(
  aggregateTransaction,
  networkGenerationHash,
);
console.log('Aggregate Transaction Hash:', signedTransaction.hash);
            AggregateTransaction aggregateTransaction = AggregateTransactionFactory.createBonded(networkType, Arrays
                .asList(aliceToTicketDistributorTx.toAggregate(aliceAccount.getPublicAccount()),
                    ticketDistributorToAliceTx.toAggregate(ticketDistributorPublicAccount)))
                .maxFee(BigInteger.valueOf(2000000)).build();

            String generationHash = repositoryFactory.getGenerationHash().toFuture().get();
            SignedTransaction signedTransaction = aliceAccount.sign(aggregateTransaction, generationHash);

3. When an AggregateTransaction is bonded, Alice will need to lock 10 symbol.xym to prevent spamming the network. Once the ticket distributor signs the AggregateTransaction, the amount of locked symbol.xym becomes available again on Alice’s account, and the exchange will get through.

const hashLockTransaction = HashLockTransaction.create(
  Deadline.create(epochAdjustment),
  new Mosaic(
    networkCurrencyMosaicId,
    UInt64.fromUint(10 * Math.pow(10, networkCurrencyDivisibility)),
  ),
  UInt64.fromUint(480),
  signedTransaction,
  networkType,
  UInt64.fromUint(2000000),
);

const signedHashLockTransaction = aliceAccount.sign(
  hashLockTransaction,
  networkGenerationHash,
);

// replace with node endpoint
const nodeUrl = 'http://api-01.us-east-1.testnet.symboldev.network:3000';
const repositoryFactory = new RepositoryFactoryHttp(nodeUrl);
const listener = repositoryFactory.createListener();
const receiptHttp = repositoryFactory.createReceiptRepository();
const transactionHttp = repositoryFactory.createTransactionRepository();
const transactionService = new TransactionService(transactionHttp, receiptHttp);

listener.open().then(() => {
  transactionService
    .announceHashLockAggregateBonded(
      signedHashLockTransaction,
      signedTransaction,
      listener,
    )
    .subscribe(
      (x) => console.log(x),
      (err) => console.log(err),
      () => listener.close(),
    );
});
const hashLockTransaction = symbol_sdk_1.HashLockTransaction.create(
  symbol_sdk_1.Deadline.create(epochAdjustment),
  new symbol_sdk_1.Mosaic(
    networkCurrencyMosaicId,
    symbol_sdk_1.UInt64.fromUint(
      10 * Math.pow(10, networkCurrencyDivisibility),
    ),
  ),
  symbol_sdk_1.UInt64.fromUint(480),
  signedTransaction,
  networkType,
  symbol_sdk_1.UInt64.fromUint(2000000),
);
const signedHashLockTransaction = aliceAccount.sign(
  hashLockTransaction,
  networkGenerationHash,
);
// replace with node endpoint
const nodeUrl = 'http://api-01.us-east-1.testnet.symboldev.network:3000';
const repositoryFactory = new symbol_sdk_1.RepositoryFactoryHttp(nodeUrl);
const listener = repositoryFactory.createListener();
const receiptHttp = repositoryFactory.createReceiptRepository();
const transactionHttp = repositoryFactory.createTransactionRepository();
const transactionService = new symbol_sdk_1.TransactionService(
  transactionHttp,
  receiptHttp,
);
listener.open().then(() => {
  transactionService
    .announceHashLockAggregateBonded(
      signedHashLockTransaction,
      signedTransaction,
      listener,
    )
    .subscribe(
      (x) => console.log(x),
      (err) => console.log(err),
      () => listener.close(),
    );
});
            HashLockTransaction hashLockTransaction = HashLockTransactionFactory
                .create(networkType, networkCurrency.createRelative(BigDecimal.valueOf(10)), BigInteger.valueOf(480),
                    signedTransaction).build();

            SignedTransaction signedHashLockTransaction = aliceAccount.sign(hashLockTransaction, generationHash);
            try (Listener listener = repositoryFactory.createListener()) {
                listener.open().get();
                TransactionService transactionService = new TransactionServiceImpl(repositoryFactory);

                transactionService.announceHashLockAggregateBonded(listener, signedHashLockTransaction, signedTransaction).toFuture()
                    .get();
            }

販売者はまだ AggregateBondedTransaction に署名していないため、交換は完了していません。

  1. AggregateTransaction のハッシュ を (2) からコピーして 次のガイド で AggregateTransaction に署名をする方法を確認してください。

この取引をアグリゲートトランザクション無しに可能でしょうか?

これは安全ではありません なぜなら:

  • Alice はチケットを受け取った後に配給業者に支払いをしない可能性があります。
  • 配給業者は支払いを受領した後にチケットを送付しない可能性があります。

AggregateTransaction 機能を使用して、すべての参加者が合意したときに複数のトランザクションが同時に実行されるようにします。