Delegating mosaic restrictions to a third party

Allow another account to add restrictions to your mosaics.

Use case

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.

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

Use case diagram

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.

Prerequisites

  • 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.

Method #01: Using the SDK

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
  1. 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.

  1. 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),
);
  1. 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.