Limit how accounts can transact with Mosaic Restrictions.
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.
This guide shows you how to use Symbol’s Mosaic Restriction feature to define rules that determine which participants can transact with ccf.shares
.
Complete creating a mosaic guide.
Create accounts for CharlieChocolateFactory, Alice, And Bob.
Load CharlieChocolateFactory’s account with enough symbol.xym
to pay for the transactions fees and creation of mosaics.
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.
Start creating the new restrictable mosaic ccf.shares
with Symbol CLI using the CharlieChocolateFactory account.
symbol-cli transaction mosaic --profile ccfactory --sync
Do you want a non-expiring mosaic? [y/n]: y
Enter 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
Enter amount of tokens: 1000
Transaction confirmed
The new mosaic id is: 634a8ac3fc2b65b3
Then, copy and save the mosaic identifier. We will need it later to define restrictions.
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”.
Open a new file and 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.
// replace with mosaic id
const mosaicIdHex = '634a8ac3fc2b65b3';
const mosaicId = new MosaicId(mosaicIdHex);
const key = KeyGenerator.generateUInt64Key('KYC'.toLowerCase());
// replace with mosaic id
const mosaicIdHex = '634a8ac3fc2b65b3';
const mosaicId = new symbol_sdk_1.MosaicId(mosaicIdHex);
const key = symbol_sdk_1.KeyGenerator.generateUInt64Key('KYC'.toLowerCase());
Then, define a new MosaicGlobalRestrictionTransaction. Pass the mosaic id 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
.
// replace with network type
const networkType = NetworkType.TEST_NET;
const transaction = MosaicGlobalRestrictionTransaction.create(
Deadline.create(epochAdjustment),
mosaicId, // mosaicId
key, // restrictionKey
UInt64.fromUint(0), // previousRestrictionValue
MosaicRestrictionType.NONE, // previousRestrictionType
UInt64.fromUint(1), // newRestrictionValue
MosaicRestrictionType.EQ, // newRestrictionType
networkType,
undefined,
UInt64.fromUint(2000000),
);
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
const transaction = symbol_sdk_1.MosaicGlobalRestrictionTransaction.create(symbol_sdk_1.Deadline.create(epochAdjustment), mosaicId, // mosaicId
key, // restrictionKey
symbol_sdk_1.UInt64.fromUint(0), // previousRestrictionValue
symbol_sdk_1.MosaicRestrictionType.NONE, // previousRestrictionType
symbol_sdk_1.UInt64.fromUint(1), // newRestrictionValue
symbol_sdk_1.MosaicRestrictionType.EQ, // newRestrictionType
networkType, undefined, symbol_sdk_1.UInt64.fromUint(2000000));
After defining the global restriction, sign the transaction with the mosaic creator’s account—CharlieChocolateFactory—and announce it to the network.
// replace with company private key
const privateKey =
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
const account = Account.createFromPrivateKey(privateKey, networkType);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
'1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = account.sign(transaction, networkGenerationHash);
// 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 company private key
const privateKey = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
const account = symbol_sdk_1.Account.createFromPrivateKey(privateKey, networkType);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = account.sign(transaction, networkGenerationHash);
// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new symbol_sdk_1.RepositoryFactoryHttp(nodeUrl);
const transactionHttp = repositoryFactory.createTransactionRepository();
transactionHttp.announce(signedTransaction).subscribe((x) => console.log(x), (err) => console.error(err));
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.
Define both MosaicAddressRestrictionTransaction for Alice and Bob accounts as follows:
Alice: {ccf.shares, Alice, KYC, 1}
Bob: {ccf.shares, Bob, KYC, 0}
// replace with network type
const networkType = NetworkType.TEST_NET;
// replace with mosaic id
const mosaicIdHex = '634a8ac3fc2b65b3';
const mosaicId = new MosaicId(mosaicIdHex);
// replace with address
const aliceRawAddress = 'TCHBDE-NCLKEB-ILBPWP-3JPB2X-NY64OE-7PYHHE-32I';
const aliceAddress = Address.createFromRawAddress(aliceRawAddress);
// replace with address
const bobRawAddress = 'TB6Q5E-YACWBP-CXKGIL-I6XWCH-DRFLTB-KUK34I-YJQ';
const bobAddress = Address.createFromRawAddress(bobRawAddress);
const key = KeyGenerator.generateUInt64Key('KYC'.toLowerCase());
const aliceMosaicAddressRestrictionTransaction = MosaicAddressRestrictionTransaction.create(
Deadline.create(epochAdjustment),
mosaicId, // mosaicId
key, // restrictionKey
aliceAddress, // address
UInt64.fromUint(1), // newRestrictionValue
networkType,
);
const bobMosaicAddressRestrictionTransaction = MosaicAddressRestrictionTransaction.create(
Deadline.create(epochAdjustment),
mosaicId, // mosaicId
key, // restictionKey
bobAddress, // address
UInt64.fromUint(0), // newRestrictionValue
networkType,
);
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
// replace with mosaic id
const mosaicIdHex = '634a8ac3fc2b65b3';
const mosaicId = new symbol_sdk_1.MosaicId(mosaicIdHex);
// replace with address
const aliceRawAddress = 'TCHBDE-NCLKEB-ILBPWP-3JPB2X-NY64OE-7PYHHE-32I';
const aliceAddress = symbol_sdk_1.Address.createFromRawAddress(aliceRawAddress);
// replace with address
const bobRawAddress = 'TB6Q5E-YACWBP-CXKGIL-I6XWCH-DRFLTB-KUK34I-YJQ';
const bobAddress = symbol_sdk_1.Address.createFromRawAddress(bobRawAddress);
const key = symbol_sdk_1.KeyGenerator.generateUInt64Key('KYC'.toLowerCase());
const aliceMosaicAddressRestrictionTransaction = symbol_sdk_1.MosaicAddressRestrictionTransaction.create(symbol_sdk_1.Deadline.create(epochAdjustment), mosaicId, // mosaicId
key, // restrictionKey
aliceAddress, // address
symbol_sdk_1.UInt64.fromUint(1), // newRestrictionValue
networkType);
const bobMosaicAddressRestrictionTransaction = symbol_sdk_1.MosaicAddressRestrictionTransaction.create(symbol_sdk_1.Deadline.create(epochAdjustment), mosaicId, // mosaicId
key, // restictionKey
bobAddress, // address
symbol_sdk_1.UInt64.fromUint(0), // newRestrictionValue
networkType);
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 creator account.
// replace with company private key
const privateKey =
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
const account = Account.createFromPrivateKey(privateKey, networkType);
const aggregateTransaction = AggregateTransaction.createComplete(
Deadline.create(epochAdjustment),
[
aliceMosaicAddressRestrictionTransaction.toAggregate(account.publicAccount),
bobMosaicAddressRestrictionTransaction.toAggregate(account.publicAccount),
],
networkType,
[],
UInt64.fromUint(2000000),
);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
'1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = account.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 company private key
const privateKey = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
const account = symbol_sdk_1.Account.createFromPrivateKey(privateKey, networkType);
const aggregateTransaction = symbol_sdk_1.AggregateTransaction.createComplete(symbol_sdk_1.Deadline.create(epochAdjustment), [
aliceMosaicAddressRestrictionTransaction.toAggregate(account.publicAccount),
bobMosaicAddressRestrictionTransaction.toAggregate(account.publicAccount),
], networkType, [], symbol_sdk_1.UInt64.fromUint(2000000));
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = account.sign(aggregateTransaction, networkGenerationHash);
console.log(signedTransaction.hash);
// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new symbol_sdk_1.RepositoryFactoryHttp(nodeUrl);
const transactionHttp = repositoryFactory.createTransactionRepository();
transactionHttp.announce(signedTransaction).subscribe((x) => console.log(x), (err) => console.error(err));
Once the transaction gets confirmed, try to send mosaics to Alice’s and Bob’s accounts.
Now, 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}
.
symbol-cli transaction transfer --recipient-address TCHBDE-NCLKEB-ILBPWP-3JPB2X-NY64OE-7PYHHE-32I --mosaics 634a8ac3fc2b65b3::1 --sync
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 because he is not allowed to transact with ccf.shares
.