Assigning metadata to an account

Add custom data to an account.

Prerequisites

Background

Bob works as a digital notary that stamp accounts on the NEM blockchain. When a customer comes to Bob to notarize a document, he checks the authentication of the customer’s documents then tags the customer’s account with the digitized document as metadata.

In this tutorial, you are going to implement a program to allow Bob tag accounts issuing metadata transactions .

Getting into some code

Alice is a recent graduate who wants her educational certificate accredited to her NEM account to avoid the hassle of repeatedly providing verification of her degree. So she goes to Bob and provides him with proof of her degree. Once Alice pays a fee, Bob verifies the authenticity and stamps Alice’s account with metadata that signifies her degree.

../../_images/metadata-certificate1.png
  1. Create an account for Alice, using the nem2-cli.
nem2-cli account generate --save

Introduce network type (MIJIN_TEST, MIJIN, MAIN_NET, TEST_NET): MIJIN_TEST
Do you want to save it? [y/n]: y
Introduce NEM 2 Node URL. (Example: http://localhost:3000): http://localhost:3000
Insert profile name (blank means default and it could overwrite the previous profile): alice
  1. Bob has to pick a key to store Alice’s certificate. Imagine that CERT is a common key to store university degrees. Define this key as a new variable.
const key = KeyGenerator.generateUInt64Key('CERT');
  1. Alice’s degree brings the identifier 123456 for her certificate. Help Bob to assign this value to the key defined in the previous step. To achieve so, define an AccountMetadataTransaction linking Alice account, the key (CERT), and the associated value (123456).
const alicePublicKey = process.env.ALICE_PUBLIC_KEY as string;
const alicePublicAccount = PublicAccount.createFromPublicKey(alicePublicKey, NetworkType.MIJIN_TEST);

const value = '123456';
const accountMetadataTransaction = AccountMetadataTransaction.create(
    Deadline.create(),
    alicePublicAccount.publicKey,
    key,
    value.length,
    value,
    NetworkType.MIJIN_TEST,
);
  1. To avoid spamming the account with invalid metadata keys, all metadata is attached only with the consent of the account owner through Aggregate Transactions. Thus, Alice will have to opt-in if she wants the metadata entry assigned to its account. Wrap the AccountMetadataTransaction inside an AggregateBondedTransaction and sign the transaction using Bob’s account.
const bobPrivateKey = process.env.BOB_PRIVATE_KEY as string;
const bobAccount = Account.createFromPrivateKey(bobPrivateKey, NetworkType.MIJIN_TEST);

const aggregateTransaction = AggregateTransaction.createBonded(
    Deadline.create(),
    [accountMetadataTransaction.toAggregate(bobAccount.publicAccount)],
    NetworkType.MIJIN_TEST);
const networkGenerationHash = process.env.NETWORK_GENERATION_HASH as string;
const signedTransaction = bobAccount.sign(aggregateTransaction, networkGenerationHash);
console.log(signedTransaction.hash);
  1. Before sending an aggregate transaction to the network, Bob has to lock 10 cat.currency. Define a new HashLockTransaction and sign it with Bob’s account, locking the amount of cat.currency required to announce the aggregate transaction.
const hashLockTransaction = HashLockTransaction.create(
    Deadline.create(),
    NetworkCurrencyMosaic.createRelative(10),
    UInt64.fromUint(480),
    signedTransaction,
    NetworkType.MIJIN_TEST);
const signedHashLockTransaction = bobAccount.sign(hashLockTransaction, networkGenerationHash);

Note

Bob will receive the locked funds back if Alice cosigns the aggregate during the next 480 blocks.

  1. Announce the HashLockTransaction. Monitor the network until the transaction gets confirmed, and then announce the AggregateTransaction containing the AccountMetadataTransaction.
const nodeUrl = 'http://localhost:3000';
const transactionHttp = new TransactionHttp(nodeUrl);
const listener = new Listener(nodeUrl);

const announceHashLockTransaction = (signedHashLockTransaction: SignedTransaction) => {
    return transactionHttp.announce(signedHashLockTransaction);
};

const announceAggregateTransaction = (listener: Listener,
                                      signedHashLockTransaction: SignedTransaction,
                                      signedAggregateTransaction: SignedTransaction,
                                      senderAddress: Address) => {
    return listener
        .confirmed(senderAddress)
        .pipe(
            filter((transaction) => transaction.transactionInfo !== undefined
                && transaction.transactionInfo.hash === signedHashLockTransaction.hash),
            mergeMap(ignored => {
                listener.terminate();
                return transactionHttp.announceAggregateBonded(signedAggregateTransaction)
            })
        );
};

listener.open().then(() => {
    merge(announceHashLockTransaction(signedHashLockTransaction),
        announceAggregateTransaction(listener, signedHashLockTransaction, signedTransaction, bobAccount.address))
        .subscribe(x => console.log('Transaction confirmed:', x.message),
            err=> console.log(err));
});
  1. Once the transaction gets confirmed, cosign the hash obtained in the fourth step using Alice’s profile.
nem2-cli transaction cosign --hash <transaction-hash> --profile alice
  1. If everything goes well, now Alice has assigned the metadata value {bobPublicKey, CERT, 123456}, which can be read as “Alice account has the certificate number 123456 and it was verified by Bob”.