Getting the asset identifier behind a namespace with receipts

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

Use case

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

Imagine a ticket vendor sending tickets to their customers on Bitxor’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 tokens and addresses

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

// replace with namespace name
const namespaceId = new NamespaceId('bitxor');

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

namespaceHttp.getLinkedTokenId(namespaceId).subscribe(
  (tokenId) => console.log(tokenId!.toHex()),
  (err) => console.log(err),
);
// replace with namespace name
const namespaceId = new bitxor_sdk_1.NamespaceId('bitxor');
// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new bitxor_sdk_1.RepositoryFactoryHttp(nodeUrl);
const namespaceHttp = repositoryFactory.createNamespaceRepository();
namespaceHttp.getLinkedTokenId(namespaceId).subscribe(
    (tokenId) => console.log(tokenId.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("bitxor");
            final TokenId tokenId = namespaceRepository.getLinkedTokenId(namespaceId)
                    .toFuture().get();
            System.out.print(tokenId.getIdAsHex());
        }

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

  • Transactions using aliased tokens or accounts are stored on the blockchain using the namespace identifier, not the real address or token 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, Bitxor 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 bitxor instead of the native currency token 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-token relation by looking into the block receipts’.

  1. Define the token you want to send. Use a linked namespace identifier (e.g. bitxor) instead of the token identifier.

const aliasedToken = new Token(
  new NamespaceId('bitxor'),
  UInt64.fromUint(1000000),
);
const aliasedToken = new bitxor_sdk_1.Token(
    new bitxor_sdk_1.NamespaceId('bitxor'),
    bitxor_sdk_1.UInt64.fromUint(1000000),
);
  1. Attach the token to a TransferTransaction.

// replace with network type
const networkType = NetworkType.TEST_NET;
const transferTransaction = TransferTransaction.create(
  Deadline.create(epochAdjustment),
  Address.createFromRawAddress('BXRBDE-NCLKEB-ILBPWP-3JPB2X-NY64OE-7PYHHE-32I'),
  [aliasedToken],
  PlainMessage.create('Test aliased token'),
  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 = bitxor_sdk_1.NetworkType.TEST_NET;
const transferTransaction = bitxor_sdk_1.TransferTransaction.create(
    bitxor_sdk_1.Deadline.create(epochAdjustment),
    bitxor_sdk_1.Address.createFromRawAddress(
        'BXRBDE-NCLKEB-ILBPWP-3JPB2X-NY64OE-7PYHHE-32I',
    ), [aliasedToken],
    bitxor_sdk_1.PlainMessage.create('Test aliased token'),
    networkType,
    bitxor_sdk_1.UInt64.fromUint(2000000),
);
// replace with sender private key
const privateKey =
    '1111111111111111111111111111111111111111111111111111111111111111';
const account = bitxor_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 TokenId: ', transaction.tokens[0].id.toHex());
        listener.close();
      },
      (err) => console.log(err),
    );
});
// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new bitxor_sdk_1.RepositoryFactoryHttp(nodeUrl);
const receiptHttp = repositoryFactory.createReceiptRepository();
const transactionHttp = repositoryFactory.createTransactionRepository();
const listener = repositoryFactory.createListener();
const transactionService = new bitxor_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 TokenId: ', transaction.tokens[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.