Turning the asynchronous transaction announcement into synchronous

Handy snippet to announce a transaction and wait until this is confirmed.

Use case

When announcing a transaction in NIS1, you had to wait to get the response from the node. Symbol works differently: when a transaction is announced, the REST API server will always return an OK. As a result, the developer does not have to wait until the server returns a response, being able to make more responsive apps. However, now is the developer’s responsibility to check the status of the transaction and ensure it is confirmed.

The negative aspect of announcing transactions asynchronously is that it adds unnecessary complexity to small projects. The Symbol SDK TransactionService aims to solve this problem by providing a function that waits for the confirmation or rejection of the transaction.

Prerequisites

Sending a synchronous transaction

Alice is developing an app to send 10 cat.currency to Bob and wants to know if the transaction has reached the network before sending Bob an email.

  1. Create a new .ts file. Then, define and sign a 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. Once signed, announce the transaction using TransactionService.announce instead of TransactionHttp.announce.

const nodeUrl = 'NODE_URL';
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 = 'NODE_URL';
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),
    );
});

Note

The function TransactionService.announce() will respond successfully if the transaction reaches the network and does not have validation errors. You might still need to wait for several confirmations before executing additional actions.