Getting the asset identifier behind a namespace with receipts

Get the resolution for a given alias and transaction using receipts.

Use case

Symbol accounts can link registered namespaces to other accounts or mosaics by announcing an MosaicAliasTransaction. This feature allows you to replace long and complex identifiers with short and familiar names for your accounts and mosaics.

Imagine a ticket vendor sending tickets to their customers on Symbol’s public chain. The company needs to send 1 0dc67fbe1cad29e3 to SCVG35-ZSPMYP-L2POZQ-JGSVEG-RYOJ3V-BNIU3U-N2E6. With aliases, the ticket vendor can define the same transaction as sending 1 ticketsales.event1.ticket to @alice instead.

../../_images/namespace-tickets.png

Recognizable mosaics and addresses

To ensure the transactions are being sent to the correct place with the correct mosaic, you can directly query the network about the current mosaic identifier behind a namespace by running the following snippet:

// replace with namespace name
const namespaceId = new NamespaceId('symbol.xym');

// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new RepositoryFactoryHttp(nodeUrl);
const namespaceHttp = repositoryFactory.createNamespaceRepository();

namespaceHttp.getLinkedMosaicId(namespaceId).subscribe(
  (mosaicId) => console.log(mosaicId!.toHex()),
  (err) => console.log(err),
);
// replace with namespace name
const namespaceId = new symbol_sdk_1.NamespaceId('symbol.xym');
// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new symbol_sdk_1.RepositoryFactoryHttp(nodeUrl);
const namespaceHttp = repositoryFactory.createNamespaceRepository();
namespaceHttp.getLinkedMosaicId(namespaceId).subscribe(
  (mosaicId) => console.log(mosaicId.toHex()),
  (err) => console.log(err),
);
        // replace with node endpoint
        try (final RepositoryFactory repositoryFactory = new RepositoryFactoryVertxImpl(
                "NODE_URL")) {
            final NamespaceRepository namespaceRepository = repositoryFactory.createNamespaceRepository();
            final NamespaceId namespaceId = NamespaceId.createFromName("symbol.xym");
            final MosaicId mosaicId = namespaceRepository.getLinkedMosaicId(namespaceId)
                    .toFuture().get();
            System.out.print(mosaicId.getIdAsHex());
        }

However, the same method cannot be used to verify transactions of the past. This is due to the facts that:

  • Transactions using aliased mosaics or accounts are stored on the blockchain using the namespace identifier, not the real address or mosaic id behind it.

  • Links are editable. The namespace creator can link its namespace to another asset.

  • Namespaces expire. The namespace link could be deleted.

At this point, you might be wondering: how then can we get the accurate relation between a namespace and its real identifier for a past transaction? The answer lies with receipts. For each block, Symbol nodes store receipts that contain every invisible state change that cannot be retrieved directly from the transaction or block header.

Prerequisites

Getting into some code

In this example, we are going to announce a TransferTransaction using symbol.xym instead of the native currency mosaic id. Once the network confirms the transaction, we will get the block height where the transaction has been recorded. With this information, we will then get the namespace-mosaic relation by looking into the block receipts’.

  1. Define the mosaic you want to send. Use a linked namespace identifier (e.g. symbol.xym) instead of the mosaic identifier.

const aliasedMosaic = new Mosaic(
  new NamespaceId('symbol.xym'),
  UInt64.fromUint(1000000),
);
const aliasedMosaic = new symbol_sdk_1.Mosaic(
  new symbol_sdk_1.NamespaceId('symbol.xym'),
  symbol_sdk_1.UInt64.fromUint(1000000),
);
  1. Attach the mosaic to a TransferTransaction.

// replace with network type
const networkType = NetworkType.TEST_NET;
const transferTransaction = TransferTransaction.create(
  Deadline.create(epochAdjustment),
  Address.createFromRawAddress('TCHBDE-NCLKEB-ILBPWP-3JPB2X-NY64OE-7PYHHE-32I'),
  [aliasedMosaic],
  PlainMessage.create('Test aliased mosaic'),
  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,
);
console.log(signedTransaction.hash);
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
const transferTransaction = symbol_sdk_1.TransferTransaction.create(
  symbol_sdk_1.Deadline.create(epochAdjustment),
  symbol_sdk_1.Address.createFromRawAddress(
    'TCHBDE-NCLKEB-ILBPWP-3JPB2X-NY64OE-7PYHHE-32I',
  ),
  [aliasedMosaic],
  symbol_sdk_1.PlainMessage.create('Test aliased mosaic'),
  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,
);
console.log(signedTransaction.hash);

3. Announce the TransferTransaction. Once the transaction is confirmed, retrieve the receipts attached to the block and find for the namespace resolution.

// replace with node endpoint
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(() => {
  transactionService
    .announce(signedTransaction, listener)
    .pipe(
      mergeMap((transaction) =>
        transactionService.resolveAliases([transaction.transactionInfo!.hash!]),
      ),
      map((transactions) => transactions[0] as TransferTransaction),
    )
    .subscribe(
      (transaction) => {
        console.log('Resolved MosaicId: ', transaction.mosaics[0].id.toHex());
        listener.close();
      },
      (err) => console.log(err),
    );
});
// replace with node endpoint
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(() => {
  transactionService
    .announce(signedTransaction, listener)
    .pipe(
      operators_1.mergeMap((transaction) =>
        transactionService.resolveAliases([transaction.transactionInfo.hash]),
      ),
      operators_1.map((transactions) => transactions[0]),
    )
    .subscribe(
      (transaction) => {
        console.log('Resolved MosaicId: ', transaction.mosaics[0].id.toHex());
        listener.close();
      },
      (err) => console.log(err),
    );
});

What is next?

Receipts do not only store resolutions for aliases, but also every invisible state change that is not directly retrievable from transactions or the block header. You can check under the receipts documentation the complete list of changes logged.