Restricting mosaic transfers

Limit how accounts can transact with Mosaic Restrictions.

Background

Let’s say a company, CharlieChocolateFactory, wants to go public by tokenizing their shares and conducting an STO. They create a mosaic ccf.shares and configure it to be restrictable. To comply with regulations, the company wants only the participants that have passed the KYC/AML process to buy and transact their stock.

In this guide, we are going to use Catapult’s Mosaic Restriction feature to define rules that determine which participants can transact with ccf.shares.

Prerequisites

Getting into some code

Creating a restrictable mosaic

Before starting to work with Mosaic Restrictions, we need to have created a restrictable mosaic. Only mosaics with the restrictable property set to true at the moment of their creation can accept mosaic restrictions.

  1. Start creating a new restrictable mosaic with NEM2-CLI using the CharlieChocolateFactory account.
nem2-cli transaction mosaic --profile ccfactory

Do you want an eternal mosaic? [y/n]: y
Introduce mosaic divisibility: 0
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
Introduce amount of tokens: 1000
Transaction announced correctly

Your mosaic id is: 634a8ac3fc2b65b3
  1. Then, copy and save the mosaic identifier. We will need it later to define restrictions.

Setting a Mosaic Global Restriction

The company wants to add a restriction to only permit accounts with elevated statuses to interact with the asset. To achieve this, the company will add a mosaic global restriction as {ccf.shares, KYC, EQ = 1}, which can be read as “only allow accounts to transact with the ccf.shares mosaic if their KYC restriction key for it has a value equal to 1”.

../../_images/mosaic-restriction-sto.png

Use case diagram

  1. Open a new TypeScript file. Then, place the mosaic identifier value you got while creating the mosaic in a variable named mosaicId. Also, you should represent the key KYC with a numeric value encoded as a UInt64.
const mosaicIdHexa = process.env.MOSAIC_ID as string;
const mosaicId = new MosaicId(mosaicIdHexa);
const key = 'KYC'.toLowerCase();
  1. Then, define a new MosaicGlobalRestrictionTransaction. Pass the mosaicId and keys you have defined in the previous step as arguments.

The SDK will also request the previous mosaic restriction value and type for this key and mosaic. As it is the first global restriction we are announcing, set the `previousRestrictionValue to 0 and the mosaicRestrictionType to None.

const transaction = MosaicGlobalRestrictionTransaction
    .create(
        Deadline.create(),
        mosaicId, // mosaicId
        new UInt64(NamespaceMosaicIdGenerator.namespaceId(key)), // restrictionKey
        UInt64.fromUint(0), // previousRestrictionValue
        MosaicRestrictionType.NONE, // previousRestrictionType
        UInt64.fromUint(1), // newRestrictionValue
        MosaicRestrictionType.EQ, // newRestrictionType
        NetworkType.MIJIN_TEST);
  1. After defining the global restriction, sign the transaction with the mosaic owner’s account—CharlieChocolateFactory—and announce it to the network.
const privateKey = process.env.PRIVATE_KEY as string;
const account = Account.createFromPrivateKey(privateKey, NetworkType.MIJIN_TEST);
const networkGenerationHash = process.env.NETWORK_GENERATION_HASH as string;
const signedTransaction = account.sign(transaction, networkGenerationHash);
console.log(signedTransaction.hash);

const transactionHttp = new TransactionHttp('http://localhost:3000');
transactionHttp
    .announce(signedTransaction)
    .subscribe(x => console.log(x), err => console.error(err));

Assigning Mosaic Address Restrictions

When investors complete the KYC/AML process, the CharlieChocolateFactory alters their accounts with a MosaicAddressRestrictionTransaction with parameters ccf.shares, KYC, 1, allowing certified investors to participate in the STO. Others who have not provided the necessary information will not be able to receive or trade the asset.

Alice, a potential investor, passes the KYC process. Once Alice has been verified, the company tags Alice’s account with the mosaic address restriction {ccf.shares, Alice, KYC, 1}. On the other hand, Bob, another interested investor, did not pass the KYC process. Bob’s account is not eligible to receive ccf.shares as it does not meet the mosaic global restriction requirements. Nevertheless, CharlieCholocalteFatory decides to tag the account with the mosaic address restriction {ccf.shares, Bob, KYC, 0}. Doing so, they know that Bob has attempted and failed the KYC process.

  1. Define both MosaicAddressRestrictionTransaction for Alice and Bob accounts as follows:
  • Alice: {ccf.shares, Alice, KYC, 1}
  • Bob: {ccf.shares, Bob, KYC, 0}
const mosaicIdHexa = process.env.MOSAIC_ID as string;
const mosaicId = new MosaicId(mosaicIdHexa);

const aliceRawAddress = 'SDDOLW-ESKH33-YYW5XF-42F3ZJ-ZL6JIA-DP4TFT-H6RH';
const aliceAddress = Address.createFromRawAddress(aliceRawAddress);

const bobRawAddress = 'SDI4YV-LEDOHE-NVRPRX-7P3Q3P-RXNJQW-S2YPGA-SA2Q';
const bobAddress = Address.createFromRawAddress(bobRawAddress);

const key = 'KYC'.toLowerCase();
const aliceMosaicAddressRestrictionTransaction = MosaicAddressRestrictionTransaction
    .create(
        Deadline.create(),
        mosaicId, // mosaicId
        new UInt64(NamespaceMosaicIdGenerator.namespaceId(key)), // restrictionKey
        aliceAddress, // address
        UInt64.fromUint(1), // newRestrictionValue
        NetworkType.MIJIN_TEST);

const bobMosaicAddressRestrictionTransaction = MosaicAddressRestrictionTransaction
    .create(
        Deadline.create(),
        mosaicId, // mosaicId
        new UInt64(NamespaceMosaicIdGenerator.namespaceId(key)), // restictionKey
        bobAddress, // address
        UInt64.fromUint(0), // newRestrictionValue
        NetworkType.MIJIN_TEST);
  1. Now, you can announce the transactions to the network. To do so, try to announce both transactions together using an aggregate transaction. Remember that you will have to announce the transactions from the mosaic’s owner account.
const privateKey = process.env.PRIVATE_KEY as string;
const account = Account.createFromPrivateKey(privateKey, NetworkType.MIJIN_TEST);
const networkGenerationHash = process.env.NETWORK_GENERATION_HASH as string;

const aggregateTransaction = AggregateTransaction.createComplete(
    Deadline.create(),
    [
        aliceMosaicAddressRestrictionTransaction.toAggregate(account.publicAccount),
        bobMosaicAddressRestrictionTransaction.toAggregate(account.publicAccount)],
    NetworkType.MIJIN_TEST,
    []
);

const signedTransaction = account.sign(aggregateTransaction, networkGenerationHash);
console.log(signedTransaction.hash);

const transactionHttp = new TransactionHttp('http://localhost:3000');
transactionHttp
    .announce(signedTransaction)
    .subscribe(x => console.log(x), err => console.error(err));
  1. Once the transaction gets confirmed, try to send mosaics to Alice’s and Bob’s accounts.

You should be able to send ccf.shares to Alice without any problems. Additionally, Alice will be able to transfer mosaics with other accounts with restrictions set to {ccf.shares, KYC, 1}.

nem2-cli transaction transfer --recipient SD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54 --mosaics 634a8ac3fc2b65b3::1

However, when you send the same mosaic to Bob’s account, you should get the error Failure_RestrictionMosaic_Account_Unauthorized through the status error channel as he is not allowed to transact with ccf.shares.

nem2-cli transaction transfer --recipient SD5DT3-CH4BLA-BL5HIM-EKP2TA-PUKF4N-Y3L5HR-IR54 --mosaics 634a8ac3fc2b65b3::1