非同期なトランザクションのアナウンスを同期的に変える

トランザクションをアナウンスして、承認されるまで待つ便利なスニペット。

ユースケース

NIS1 ではトランザクションがアナウンスされると、ノードからのレスポンスを待たなければいけませんでした。 Symbol では異なる動作をします。トランザクションがアナウンスされると、REST API は常に OK レスポンスを返します。これにより開発者はサーバがレスポンスを返却するのを待つ必要がなく、よりレスポンシブなアプリケーションの開発を可能になりました。しかし、トランザクションの状態や承認されたことを確認するのは開発者の責務になります。

非同期的トランザクションのデメリットとして、小規模なプロジェクトを不必要に複雑にしてしまう場合があります。 Symbol SDK TransactionService がトランザクションの承認または却下を待機する機能を提供することでこの問題を解決します。

前提条件

同期的トランザクションの送信

Alice は Bob に 10 cat.currency を送るアプリケーションを開発しています。彼女は Bob にメールを送る前に、トランザクションがネットワークに到着したかどうかを知ろうとしています。

  1. 新しい .ts ファイルを作成して TransferTransaction を定義し署名します。
// replace with recipient address
const rawRecipientAddress = 'TB6Q5E-YACWBP-CXKGIL-I6XWCH-DRFLTB-KUK34I-YJQ';
const recipientAddress = Address.createFromRawAddress(rawRecipientAddress);
// replace with network type
const networkType = NetworkType.TEST_NET;
// replace with symbol.xym id
const networkCurrencyMosaicId = new MosaicId('5E62990DCAC5BE8A');
// replace with network currency divisibility
const networkCurrencyDivisibility = 6;

const transferTransaction = TransferTransaction.create(
  Deadline.create(epochAdjustment),
  recipientAddress,
  [
    new Mosaic(
      networkCurrencyMosaicId,
      UInt64.fromUint(10 * Math.pow(10, networkCurrencyDivisibility)),
    ),
  ],
  EmptyMessage,
  networkType,
  UInt64.fromUint(2000000),
);

// replace with sender private key
const privateKey =
  '1111111111111111111111111111111111111111111111111111111111111111';
const account = Account.createFromPrivateKey(privateKey, networkType);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
  '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = account.sign(
  transferTransaction,
  networkGenerationHash,
);
// replace with recipient address
const rawRecipientAddress = 'TB6Q5E-YACWBP-CXKGIL-I6XWCH-DRFLTB-KUK34I-YJQ';
const recipientAddress = symbol_sdk_1.Address.createFromRawAddress(
  rawRecipientAddress,
);
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
// replace with symbol.xym id
const networkCurrencyMosaicId = new symbol_sdk_1.MosaicId('5E62990DCAC5BE8A');
// replace with network currency divisibility
const networkCurrencyDivisibility = 6;
const transferTransaction = symbol_sdk_1.TransferTransaction.create(
  symbol_sdk_1.Deadline.create(epochAdjustment),
  recipientAddress,
  [
    new symbol_sdk_1.Mosaic(
      networkCurrencyMosaicId,
      symbol_sdk_1.UInt64.fromUint(
        10 * Math.pow(10, networkCurrencyDivisibility),
      ),
    ),
  ],
  symbol_sdk_1.EmptyMessage,
  networkType,
  symbol_sdk_1.UInt64.fromUint(2000000),
);
// replace with sender private key
const privateKey =
  '1111111111111111111111111111111111111111111111111111111111111111';
const account = symbol_sdk_1.Account.createFromPrivateKey(
  privateKey,
  networkType,
);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
  '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = account.sign(
  transferTransaction,
  networkGenerationHash,
);
  1. 署名したら、 TransactionHttp.announce の代わりに TransactionService.announce を使用して トランザクションをアナウンス します。
const nodeUrl = 'http://api-01.us-east-1.testnet.symboldev.network:3000';
const repositoryFactory = new RepositoryFactoryHttp(nodeUrl);
const receiptHttp = repositoryFactory.createReceiptRepository();
const transactionHttp = repositoryFactory.createTransactionRepository();
const listener = repositoryFactory.createListener();
const transactionService = new TransactionService(transactionHttp, receiptHttp);

listener.open().then(() => {
  merge(
    transactionService.announce(signedTransaction, listener),
    listener.status(account.address).pipe(
      filter((error) => error.hash === signedTransaction.hash),
      tap((error) => {
        throw new Error(error.code);
      }),
    ),
  ).subscribe(
    (transaction) => {
      console.log(transaction);
      // TODO: send email to recipient
      listener.close();
    },
    (err) => console.error(err),
  );
});
const nodeUrl = 'http://api-01.us-east-1.testnet.symboldev.network:3000';
const repositoryFactory = new symbol_sdk_1.RepositoryFactoryHttp(nodeUrl);
const receiptHttp = repositoryFactory.createReceiptRepository();
const transactionHttp = repositoryFactory.createTransactionRepository();
const listener = repositoryFactory.createListener();
const transactionService = new symbol_sdk_1.TransactionService(
  transactionHttp,
  receiptHttp,
);
listener.open().then(() => {
  rxjs_1
    .merge(
      transactionService.announce(signedTransaction, listener),
      listener.status(account.address).pipe(
        operators_1.filter((error) => error.hash === signedTransaction.hash),
        operators_1.tap((error) => {
          throw new Error(error.code);
        }),
      ),
    )
    .subscribe(
      (transaction) => {
        console.log(transaction);
        // TODO: send email to recipient
        listener.close();
      },
      (err) => console.error(err),
    );
});

注釈

この関数 TransactionService.announce() トランザクションがネットワークに到達し、バリデーションエラーが発生しなければ、成功レスポンスを返します。まだ次のアクションを起こす前に、 いくらかの承認を待つ 必要があるかもしれません。