Allow another account to add restrictions to your mosaics.
Another company, ComfyClothingCompany wants to conduct an STO. In this case, they want to delegate the KYC process to a company specialized in KYC & AML.
If you have followed the previous guide (restricting mosaics transfers), you know how to restrict accounts from transacting a given mosaic by combining different key-values to match the global mosaic restriction.
In this guide, we will be restricting accounts to trade with cc.shares
—mosaic created by ComfyClothingCompany—if the KYC provider does not allow them.
Complete restricting mosaics transfers guide.
Create accounts for ComfyClothingCompany and KYC provider.
Load both accounts with enough symbol.xym
to pay for the transactions fees and creation of mosaics.
1. Start by creating a new restrictable
mosaic with the ComfyClothingCompany account account.
We will refer to this mosaic from now on as cc.shares
.
symbol-cli transaction mosaic --amount 1000000 --transferable --supply-mutable --restrictable --divisibility 0 --non-expiring --profile cccompany --sync
The new mosaic id is: 7cdf3b117a3c40cc
The KYC provider registers a new mosaic named kyc
and adds the mosaic global restriction { kyc, IsVerified, EQ, 1}
to the mosaic.
const networkType = NetworkType.TEST_NET;
// replace with kyc provider private key
const kycProviderPrivateKey =
'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB';
const kycProviderAccount = Account.createFromPrivateKey(
kycProviderPrivateKey,
networkType,
);
// Define KYC Mosaic Id
const mosaicNonce = MosaicNonce.createRandom();
const mosaicDefinitionTransaction = MosaicDefinitionTransaction.create(
Deadline.create(epochAdjustment),
mosaicNonce,
MosaicId.createFromNonce(
mosaicNonce,
kycProviderAccount.publicAccount.address,
),
MosaicFlags.create(true, true, true),
0,
UInt64.fromUint(0),
networkType,
);
console.log('KYC MosaicId:', mosaicDefinitionTransaction.mosaicId.toHex());
// Define Mosaic global restriction Is_Verified = 1
const key = KeyGenerator.generateUInt64Key('IsVerified'.toLowerCase());
const mosaicGlobalRestrictionTransaction = MosaicGlobalRestrictionTransaction.create(
Deadline.create(epochAdjustment),
mosaicDefinitionTransaction.mosaicId, // mosaicId
key, // restictionKey
UInt64.fromUint(0), // previousRestrictionValue
MosaicRestrictionType.NONE, // previousRestrictionType
UInt64.fromUint(1), // newRestrictionValue
MosaicRestrictionType.EQ, // newRestrictionType
networkType,
);
const aggregateTransaction = AggregateTransaction.createComplete(
Deadline.create(epochAdjustment),
[
mosaicDefinitionTransaction.toAggregate(kycProviderAccount.publicAccount),
mosaicGlobalRestrictionTransaction.toAggregate(
kycProviderAccount.publicAccount,
),
],
networkType,
[],
UInt64.fromUint(2000000),
);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
'1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = kycProviderAccount.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),
);
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
// replace with kyc provider private key
const kycProviderPrivateKey = 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB';
const kycProviderAccount = symbol_sdk_1.Account.createFromPrivateKey(kycProviderPrivateKey, networkType);
// Define KYC Mosaic Id
const mosaicNonce = symbol_sdk_1.MosaicNonce.createRandom();
const mosaicDefinitionTransaction = symbol_sdk_1.MosaicDefinitionTransaction.create(symbol_sdk_1.Deadline.create(epochAdjustment), mosaicNonce, symbol_sdk_1.MosaicId.createFromNonce(mosaicNonce, kycProviderAccount.publicAccount.address), symbol_sdk_1.MosaicFlags.create(true, true, true), 0, symbol_sdk_1.UInt64.fromUint(0), networkType);
console.log('KYC MosaicId:', mosaicDefinitionTransaction.mosaicId.toHex());
// Define Mosaic global restriction Is_Verified = 1
const key = symbol_sdk_1.KeyGenerator.generateUInt64Key('IsVerified'.toLowerCase());
const mosaicGlobalRestrictionTransaction = symbol_sdk_1.MosaicGlobalRestrictionTransaction.create(symbol_sdk_1.Deadline.create(epochAdjustment), mosaicDefinitionTransaction.mosaicId, // mosaicId
key, // restictionKey
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);
const aggregateTransaction = symbol_sdk_1.AggregateTransaction.createComplete(symbol_sdk_1.Deadline.create(epochAdjustment), [
mosaicDefinitionTransaction.toAggregate(kycProviderAccount.publicAccount),
mosaicGlobalRestrictionTransaction.toAggregate(kycProviderAccount.publicAccount),
], networkType, [], symbol_sdk_1.UInt64.fromUint(2000000));
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = kycProviderAccount.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));
The KYC provider defines the following permission tiers:
Key |
Operator |
Value |
Description |
---|---|---|---|
IsVerified |
EQ |
1 |
The client has issued a valid passport. |
IsVerified |
EQ |
2 |
The client has issued a valid proof of address and passport. |
ComfyClothingCompany decides that only accounts with the restriction {cc.shares, kyc::IsVerified, EQ = 2}
should be enabled to transfer shares.
For this reason, the company adds the mosaic global restriction { kyc::IsVerified, EQ, 2}
to the mosaic ccf.shares
.
To implement the restriction from another mosaic, use the field referenceId
.
Announce a MosaicGlobalRestrictionTransaction, setting cc.shares
as the targetMosaicId
, kyc
as the referenceMosaicId
, and IsVerified
as the key.
// replace with cc.shares mosaic id
const sharesIdHex = '7cdf3b117a3c40cc';
const sharesId = new MosaicId(sharesIdHex);
// replace with kyc mosaic id
const kycIdHex = '183D0802BCDB97AF';
const kycId = new MosaicId(kycIdHex);
// replace with network type
const networkType = NetworkType.TEST_NET;
const key = KeyGenerator.generateUInt64Key('IsVerified'.toLowerCase());
const transaction = MosaicGlobalRestrictionTransaction.create(
Deadline.create(epochAdjustment),
sharesId, // mosaicId
key, // restictionKey
UInt64.fromUint(0), // previousRestrictionValue
MosaicRestrictionType.NONE, // previousRestrictionType
UInt64.fromUint(2), // newRestrictionValue
MosaicRestrictionType.EQ, // newRestrictionType
networkType,
kycId, // referenceMosaicId
UInt64.fromUint(2000000),
);
const comfyClothingCompanyPrivateKey =
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
const comfyClothingCompanyAccount = Account.createFromPrivateKey(
comfyClothingCompanyPrivateKey,
networkType,
);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
'1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = comfyClothingCompanyAccount.sign(
transaction,
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 cc.shares mosaic id
const sharesIdHex = '7cdf3b117a3c40cc';
const sharesId = new symbol_sdk_1.MosaicId(sharesIdHex);
// replace with kyc mosaic id
const kycIdHex = '183D0802BCDB97AF';
const kycId = new symbol_sdk_1.MosaicId(kycIdHex);
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
const key = symbol_sdk_1.KeyGenerator.generateUInt64Key('IsVerified'.toLowerCase());
const transaction = symbol_sdk_1.MosaicGlobalRestrictionTransaction.create(symbol_sdk_1.Deadline.create(epochAdjustment), sharesId, // mosaicId
key, // restictionKey
symbol_sdk_1.UInt64.fromUint(0), // previousRestrictionValue
symbol_sdk_1.MosaicRestrictionType.NONE, // previousRestrictionType
symbol_sdk_1.UInt64.fromUint(2), // newRestrictionValue
symbol_sdk_1.MosaicRestrictionType.EQ, // newRestrictionType
networkType, kycId, // referenceMosaicId
symbol_sdk_1.UInt64.fromUint(2000000));
const comfyClothingCompanyPrivateKey = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
const comfyClothingCompanyAccount = symbol_sdk_1.Account.createFromPrivateKey(comfyClothingCompanyPrivateKey, networkType);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = comfyClothingCompanyAccount.sign(transaction, 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));
The KYC provider has encounters three potential investors:
Alice provides a valid passport but no proof of address. The KYC provider awards Alice’s account with the mosaic restriction {kyc, IsVerified, 1}
.
Bob provides a valid passport and proof of address. The KYC provider awards Bob’s account with the mosaic restriction {kyc, IsVerified, 2}
.
Carol provides a valid passport and proof of address. The KYC provider awards Carol’s account with the mosaic restriction {kyc, IsVerified, 2}
.
The KYC provider has to tag the accounts accordingly sending mosaic address restrictions.
// replace with kyc mosaic id
const mosaicIdHex = '183D0802BCDB97AF';
const mosaicId = new MosaicId(mosaicIdHex);
// replace with alice address
const aliceRawAddress = 'TCHBDE-NCLKEB-ILBPWP-3JPB2X-NY64OE-7PYHHE-32I';
const aliceAddress = Address.createFromRawAddress(aliceRawAddress);
// replace with bob address
const bobRawAddress = 'TB6Q5E-YACWBP-CXKGIL-I6XWCH-DRFLTB-KUK34I-YJQ';
const bobAddress = Address.createFromRawAddress(bobRawAddress);
// replace with carol address
const carolRawAddress = 'TCF7MK-FL6QYF-UHWVRZ-6UUCLN-YBDWLQ-ZZC37A-2O6R';
const carolAddress = Address.createFromRawAddress(carolRawAddress);
// replace with network type
const networkType = NetworkType.TEST_NET;
const key = KeyGenerator.generateUInt64Key('IsVerified'.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, // restrictionKey
bobAddress, // address
UInt64.fromUint(2), // newRestrictionValue
networkType,
);
const carolMosaicAddressRestrictionTransaction = MosaicAddressRestrictionTransaction.create(
Deadline.create(epochAdjustment),
mosaicId, // mosaicId
key, // restrictionKey
carolAddress, // address
UInt64.fromUint(2), // newRestrictionValue
networkType,
);
// replace with kyc provider private key
const kycProviderPrivateKey =
'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB';
const kycProviderAccount = Account.createFromPrivateKey(
kycProviderPrivateKey,
networkType,
);
const aggregateTransaction = AggregateTransaction.createComplete(
Deadline.create(epochAdjustment),
[
aliceMosaicAddressRestrictionTransaction.toAggregate(
kycProviderAccount.publicAccount,
),
bobMosaicAddressRestrictionTransaction.toAggregate(
kycProviderAccount.publicAccount,
),
carolMosaicAddressRestrictionTransaction.toAggregate(
kycProviderAccount.publicAccount,
),
],
networkType,
[],
UInt64.fromUint(2000000),
);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
'1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = kycProviderAccount.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 kyc mosaic id
const mosaicIdHex = '183D0802BCDB97AF';
const mosaicId = new symbol_sdk_1.MosaicId(mosaicIdHex);
// replace with alice address
const aliceRawAddress = 'TCHBDE-NCLKEB-ILBPWP-3JPB2X-NY64OE-7PYHHE-32I';
const aliceAddress = symbol_sdk_1.Address.createFromRawAddress(aliceRawAddress);
// replace with bob address
const bobRawAddress = 'TB6Q5E-YACWBP-CXKGIL-I6XWCH-DRFLTB-KUK34I-YJQ';
const bobAddress = symbol_sdk_1.Address.createFromRawAddress(bobRawAddress);
// replace with carol address
const carolRawAddress = 'TCF7MK-FL6QYF-UHWVRZ-6UUCLN-YBDWLQ-ZZC37A-2O6R';
const carolAddress = symbol_sdk_1.Address.createFromRawAddress(carolRawAddress);
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
const key = symbol_sdk_1.KeyGenerator.generateUInt64Key('IsVerified'.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, // restrictionKey
bobAddress, // address
symbol_sdk_1.UInt64.fromUint(2), // newRestrictionValue
networkType);
const carolMosaicAddressRestrictionTransaction = symbol_sdk_1.MosaicAddressRestrictionTransaction.create(symbol_sdk_1.Deadline.create(epochAdjustment), mosaicId, // mosaicId
key, // restrictionKey
carolAddress, // address
symbol_sdk_1.UInt64.fromUint(2), // newRestrictionValue
networkType);
// replace with kyc provider private key
const kycProviderPrivateKey = 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB';
const kycProviderAccount = symbol_sdk_1.Account.createFromPrivateKey(kycProviderPrivateKey, networkType);
const aggregateTransaction = symbol_sdk_1.AggregateTransaction.createComplete(symbol_sdk_1.Deadline.create(epochAdjustment), [
aliceMosaicAddressRestrictionTransaction.toAggregate(kycProviderAccount.publicAccount),
bobMosaicAddressRestrictionTransaction.toAggregate(kycProviderAccount.publicAccount),
carolMosaicAddressRestrictionTransaction.toAggregate(kycProviderAccount.publicAccount),
], networkType, [], symbol_sdk_1.UInt64.fromUint(2000000));
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = kycProviderAccount.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));
5. After the restrictions get confirmed, Bob and Carol will be able to buy and send the cc.shares
units to each other.
But Alice—who has not provided valid proof of address—will not be able to receive shares.