Assigning metadata to a mosaic

Add custom data to a mosaic.

Background

Metadata is a Symbol feature that can be used to attach information about mosaics. 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 mosaic. 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 mosaic 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-mosaic.png

Prerequisites

  • Complete the getting started section.
  • Create a new account.
  • Load the account with enough symbol.xym to pay for transaction fees, create mosaics and register namespaces.

Creating the shares

1. Create a mosaic to represent the shares. The mosaic 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.

symbol-cli transaction mosaic --sync

Do you want an non-expiring mosaic? [y/n]: y
Enter mosaic divisibility: 2
Do you want mosaic to have supply mutable? [y/n]: y
Do you want mosaic to be transferable? [y/n]: y
Do you want mosaic to be restrictable? [y/n]: y
Enter max_fee (absolute amount): 0
Enter amount of tokens: 100
The new mosaic id is:  2C08D5EDB652AA79
Transaction confirmed.
  1. To make the mosaic easily identifiable in the network, create the namespace cc and the subnamespace cc.shares.
symbol-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.
symbol-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 mosaicId you have created in the first step.
symbol-cli transaction mosaicalias --sync

Enter namespace name: cc.shares
Enter alias action (1: Link, 0: Unlink): 1
Enter mosaic 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 MosaicMetatadaTransaction to add the ISIN and legal name to the mosaic:
  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 mosaic id
const mosaicId = new NamespaceId('cc.shares');

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

2. All metadata is attached only with the consent of the mosaic 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(),
    [isinMetadataTransaction.toAggregate(companyAccount.publicAccount),
        nameMetadataTransaction.toAggregate(companyAccount.publicAccount)],
    networkType,
    [],
    UInt64.fromUint(2000000));
const aggregateTransaction = symbol_sdk_1.AggregateTransaction.createComplete(symbol_sdk_1.Deadline.create(), [isinMetadataTransaction.toAggregate(companyAccount.publicAccount),
    nameMetadataTransaction.toAggregate(companyAccount.publicAccount)], networkType, [], symbol_sdk_1.UInt64.fromUint(2000000));

Note

In this example, the account signing the transaction is the creator of the mosaic. For that reason, the aggregate can be defined as complete. If a different account owned the mosaic, you would set the aggregate as bonded, and the mosaic 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 = 'http://api-01.us-east-1.096x.symboldev.network:3000';
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 = 'http://api-01.us-east-1.096x.symboldev.network:3000';
const repositoryFactory = new symbol_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 mosaic metadata entries.

Method #02: Using the CLI

symbol-cli transaction mosaicmetadata --target-address TCM6YD-BC3BW2-ZYXOXC-HHIRDV-MEZUIP-BRISYI-TPQ --mosaic-id 2C08D5EDB652AA79 --key BC2FC3ACFF58FF89 --value US00000000