Assigning metadata to a token

Add custom data to a token.

Use case

Metadata is a Bitxor feature that can be used to attach information about tokens. For example, small pieces of data such as legal name, ticker, or ISIN, can be assigned as on-chain metadata, while sizable documents, like the prospectus or investor agreement, can be kept off-chain.

In this tutorial, you are going to implement a program to add relevant data to a token. Imagine that the company ComfyClothingCompany has applied for an ISIN code to conduct an STO. After receiving the code US0000000000, the company decided to represent the company shares creating a token named cc.shares. Before distributing the shares between the investors, ComfyClothingCompany wants to attach its ISIN code and legal name to the shares definition.

../../_images/metadata-token.png

Prerequisites

  • Complete the getting started section.

  • Create a new account.

  • Load the account with enough bitxor to pay for transaction fees, create tokens and register namespaces.

Creating the shares

1. Create a token to represent the shares. The token we are creating will have the properties supplyMutable, transferable, restrictable, non-expiring, and we will be able to operate with up to 2 decimal places.

bitxor-cli transaction token --sync

Do you want an non-expiring token? [y/n]: y
Enter token divisibility: 2
Do you want token to have supply mutable? [y/n]: y
Do you want token to be transferable? [y/n]: y
Do you want token to be restrictable? [y/n]: y
Enter max_fee (absolute amount): 0
Enter amount of tokens: 100
The new token id is:  2C08D5EDB652AA79
Transaction confirmed.
  1. To make the token easily identifiable in the network, create the namespace cc and the subnamespace cc.shares.

bitxor-cli transaction namespace --sync

Enter namespace name: cc
Do you want to create a root namespace? [y/n]: y
Enter the namespace rental duration: 172800
Enter max_fee (absolute amount): 0
Transaction confirmed.
bitxor-cli transaction namespace --sync

Enter namespace name: shares
Do you want to create a root namespace? [y/n]: n
Enter the parent namespace name: cc
Enter max_fee (absolute amount): 0
Transaction confirmed.
  1. Link the subnamespace cc.shares with the tokenId you have created in the first step.

bitxor-cli transaction tokenalias --sync

Enter namespace name: cc.shares
Enter alias action (1: Link, 0: Unlink): 1
Enter token in hexadecimal format: 2C08D5EDB652AA79
Enter max_fee (absolute amount): 0
Transaction confirmed.

Method #01: Using the SDK

  1. Now that you have created cc.shares, define two TokenMetatadaTransaction to add the ISIN and legal name to the token:

  1. Key: ISIN, Value: US00000000.

// replace with network type
const networkType = NetworkType.TEST_NET;
// replace with company private key
const companyPrivateKey =
  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
const companyAccount = Account.createFromPrivateKey(
  companyPrivateKey,
  networkType,
);
// replace with token id
const tokenId = new NamespaceId('cc.shares');

const isin = 'US00000000';
const isinMetadataTransaction = TokenMetadataTransaction.create(
  Deadline.create(epochAdjustment),
  companyAccount.address,
  KeyGenerator.generateUInt64Key('ISIN'),
  tokenId,
  isin.length,
  isin,
  networkType,
);
// replace with network type
const networkType = bitxor_sdk_1.NetworkType.TEST_NET;
// replace with company private key
const companyPrivateKey =
  'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
const companyAccount = bitxor_sdk_1.Account.createFromPrivateKey(
  companyPrivateKey,
  networkType,
);
// replace with token id
const tokenId = new bitxor_sdk_1.NamespaceId('cc.shares');
const isin = 'US00000000';
const isinMetadataTransaction = bitxor_sdk_1.TokenMetadataTransaction.create(
  bitxor_sdk_1.Deadline.create(epochAdjustment),
  companyAccount.address,
  bitxor_sdk_1.KeyGenerator.generateUInt64Key('ISIN'),
  tokenId,
  isin.length,
  isin,
  networkType,
);
  1. Key: NAME, Value: ComfyClothingCompany.

const name = 'ComfyClothingCompany';
const nameMetadataTransaction = TokenMetadataTransaction.create(
  Deadline.create(epochAdjustment),
  companyAccount.address,
  KeyGenerator.generateUInt64Key('NAME'),
  tokenId,
  name.length,
  name,
  networkType,
);
const name = 'ComfyClothingCompany';
const nameMetadataTransaction = bitxor_sdk_1.TokenMetadataTransaction.create(
  bitxor_sdk_1.Deadline.create(epochAdjustment),
  companyAccount.address,
  bitxor_sdk_1.KeyGenerator.generateUInt64Key('NAME'),
  tokenId,
  name.length,
  name,
  networkType,
);

2. All metadata is attached only with the consent of the token creator through Aggregate Transactions. Wrap the metadata transactions inside an AggregateCompleteTransaction and sign the aggregate with the company’s account.

const aggregateTransaction = AggregateTransaction.createComplete(
  Deadline.create(epochAdjustment),
  [
    isinMetadataTransaction.toAggregate(companyAccount.publicAccount),
    nameMetadataTransaction.toAggregate(companyAccount.publicAccount),
  ],
  networkType,
  [],
  UInt64.fromUint(2000000),
);
const aggregateTransaction = bitxor_sdk_1.AggregateTransaction.createComplete(
  bitxor_sdk_1.Deadline.create(epochAdjustment),
  [
    isinMetadataTransaction.toAggregate(companyAccount.publicAccount),
    nameMetadataTransaction.toAggregate(companyAccount.publicAccount),
  ],
  networkType,
  [],
  bitxor_sdk_1.UInt64.fromUint(2000000),
);

Note

In this example, the account signing the transaction is the creator of the token. For that reason, the aggregate can be defined as complete. If a different account owned the token, you would set the aggregate as bonded, and the token creator would opt-in the metadata request by cosigning the transaction.

  1. Sign and announce the AggregateTransaction to the network.

// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
  '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = companyAccount.sign(
  aggregateTransaction,
  networkGenerationHash,
);
console.log(signedTransaction.hash);

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

transactionHttp.announce(signedTransaction).subscribe(
  (x) => console.log(x),
  (err) => console.error(err),
);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
  '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = companyAccount.sign(
  aggregateTransaction,
  networkGenerationHash,
);
console.log(signedTransaction.hash);
// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new bitxor_sdk_1.RepositoryFactoryHttp(nodeUrl);
const transactionHttp = repositoryFactory.createTransactionRepository();
transactionHttp.announce(signedTransaction).subscribe(
  (x) => console.log(x),
  (err) => console.error(err),
);
  1. When the transaction gets confirmed, fetch the token metadata entries.

Method #02: Using the CLI

Open a terminal window and run the following command.

Replace BXR6YD-BC3BW2-ZYXOXC-HHIRDV-MEZUIP-BRISYI-TPQ with the token owner address, 2C08D5EDB652AA79 with the target token id. Then, change BC2FC3ACFF58FF89 with a key, and US00000000 with the value you want to attach as metadata.

Note

You can use the command bitxor-cli converter stringtokey to transform an string (e.g. ISIN) into a valid UInt64 key (2C08D5EDB652AA79).

bitxor-cli transaction tokenmetadata --target-address BXR6YD-BC3BW2-ZYXOXC-HHIRDV-MEZUIP-BRISYI-TPQ --token-id 2C08D5EDB652AA79 --key BC2FC3ACFF58FF89 --value US00000000