Delegating token restrictions to a third party

Allow another account to add restrictions to your tokens.

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 tokens transfers), you know how to restrict accounts from transacting a given token by combining different key-values to match the global token restriction.

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

Use case diagram

In this guide, we will be restricting accounts to trade with cc.shares—token created by ComfyClothingCompany—if the KYC provider does not allow them.

Prerequisites

  • Complete restricting tokens transfers guide.

  • Create accounts for ComfyClothingCompany and KYC provider.

  • Load both accounts with enough bitxor to pay for the transactions fees and creation of tokens.

Method #01: Using the SDK

1. Start by creating a new restrictable token, We will refer to this token from now on as cc.shares.

bitxor-cli transaction token --amount 1000000 --transferable --supply-mutable --restrictable --divisibility 0 --non-expiring --profile cccompany --sync

The new token id is: 7cdf3b117a3c40cc
  1. The KYC provider registers a new token named kyc and adds the token global restriction { kyc, IsVerified, EQ, 1} to the token.

const networkType = NetworkType.TEST_NET;
// replace with kyc provider private key
const kycProviderPrivateKey =
  'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB';
const kycProviderAccount = Account.createFromPrivateKey(
  kycProviderPrivateKey,
  networkType,
);

// Define KYC Token Id
const tokenNonce = TokenNonce.createRandom();
const tokenDefinitionTransaction = TokenDefinitionTransaction.create(
  Deadline.create(epochAdjustment),
  tokenNonce,
  TokenId.createFromNonce(
    tokenNonce,
    kycProviderAccount.publicAccount.address,
  ),
  TokenFlags.create(true, true, true),
  0,
  UInt64.fromUint(0),
  networkType,
);
console.log('KYC TokenId:', tokenDefinitionTransaction.tokenId.toHex());

// Define Token global restriction Is_Verified = 1
const key = KeyGenerator.generateUInt64Key('IsVerified'.toLowerCase());
const tokenGlobalRestrictionTransaction = TokenGlobalRestrictionTransaction.create(
  Deadline.create(epochAdjustment),
  tokenDefinitionTransaction.tokenId, // tokenId
  key, // restictionKey
  UInt64.fromUint(0), // previousRestrictionValue
  TokenRestrictionType.NONE, // previousRestrictionType
  UInt64.fromUint(1), // newRestrictionValue
  TokenRestrictionType.EQ, // newRestrictionType
  networkType,
);

const aggregateTransaction = AggregateTransaction.createComplete(
  Deadline.create(epochAdjustment),
  [
    tokenDefinitionTransaction.toAggregate(kycProviderAccount.publicAccount),
    tokenGlobalRestrictionTransaction.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 = bitxor_sdk_1.NetworkType.TEST_NET;
// replace with kyc provider private key
const kycProviderPrivateKey =
  'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB';
const kycProviderAccount = bitxor_sdk_1.Account.createFromPrivateKey(
  kycProviderPrivateKey,
  networkType,
);
// Define KYC Token Id
const tokenNonce = bitxor_sdk_1.TokenNonce.createRandom();
const tokenDefinitionTransaction = bitxor_sdk_1.TokenDefinitionTransaction.create(
  bitxor_sdk_1.Deadline.create(epochAdjustment),
  tokenNonce,
  bitxor_sdk_1.TokenId.createFromNonce(
    tokenNonce,
    kycProviderAccount.publicAccount.address,
  ),
  bitxor_sdk_1.TokenFlags.create(true, true, true),
  0,
  bitxor_sdk_1.UInt64.fromUint(0),
  networkType,
);
console.log('KYC TokenId:', tokenDefinitionTransaction.tokenId.toHex());
// Define Token global restriction Is_Verified = 1
const key = bitxor_sdk_1.KeyGenerator.generateUInt64Key(
  'IsVerified'.toLowerCase(),
);
const tokenGlobalRestrictionTransaction = bitxor_sdk_1.TokenGlobalRestrictionTransaction.create(
  bitxor_sdk_1.Deadline.create(epochAdjustment),
  tokenDefinitionTransaction.tokenId, // tokenId
  key, // restictionKey
  bitxor_sdk_1.UInt64.fromUint(0), // previousRestrictionValue
  bitxor_sdk_1.TokenRestrictionType.NONE, // previousRestrictionType
  bitxor_sdk_1.UInt64.fromUint(1), // newRestrictionValue
  bitxor_sdk_1.TokenRestrictionType.EQ, // newRestrictionType
  networkType,
);
const aggregateTransaction = bitxor_sdk_1.AggregateTransaction.createComplete(
  bitxor_sdk_1.Deadline.create(epochAdjustment),
  [
    tokenDefinitionTransaction.toAggregate(kycProviderAccount.publicAccount),
    tokenGlobalRestrictionTransaction.toAggregate(
      kycProviderAccount.publicAccount,
    ),
  ],
  networkType,
  [],
  bitxor_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 bitxor_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 token global restriction { kyc::IsVerified, EQ, 2} to the token ccf.shares. To implement the restriction from another token, use the field referenceId.

  1. Announce a TokenGlobalRestrictionTransaction, setting cc.shares as the targetTokenId, kyc as the referenceTokenId, and IsVerified as the key.

// replace with cc.shares token id
const sharesIdHex = '7cdf3b117a3c40cc';
const sharesId = new TokenId(sharesIdHex);
// replace with kyc token id
const kycIdHex = '183D0802BCDB97AF';
const kycId = new TokenId(kycIdHex);
// replace with network type
const networkType = NetworkType.TEST_NET;

const key = KeyGenerator.generateUInt64Key('IsVerified'.toLowerCase());

const transaction = TokenGlobalRestrictionTransaction.create(
  Deadline.create(epochAdjustment),
  sharesId, // tokenId
  key, // restictionKey
  UInt64.fromUint(0), // previousRestrictionValue
  TokenRestrictionType.NONE, // previousRestrictionType
  UInt64.fromUint(2), // newRestrictionValue
  TokenRestrictionType.EQ, // newRestrictionType
  networkType,
  kycId, // referenceTokenId
  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 token id
const sharesIdHex = '7cdf3b117a3c40cc';
const sharesId = new bitxor_sdk_1.TokenId(sharesIdHex);
// replace with kyc token id
const kycIdHex = '183D0802BCDB97AF';
const kycId = new bitxor_sdk_1.TokenId(kycIdHex);
// replace with network type
const networkType = bitxor_sdk_1.NetworkType.TEST_NET;
const key = bitxor_sdk_1.KeyGenerator.generateUInt64Key(
  'IsVerified'.toLowerCase(),
);
const transaction = bitxor_sdk_1.TokenGlobalRestrictionTransaction.create(
  bitxor_sdk_1.Deadline.create(epochAdjustment),
  sharesId, // tokenId
  key, // restictionKey
  bitxor_sdk_1.UInt64.fromUint(0), // previousRestrictionValue
  bitxor_sdk_1.TokenRestrictionType.NONE, // previousRestrictionType
  bitxor_sdk_1.UInt64.fromUint(2), // newRestrictionValue
  bitxor_sdk_1.TokenRestrictionType.EQ, // newRestrictionType
  networkType,
  kycId, // referenceTokenId
  bitxor_sdk_1.UInt64.fromUint(2000000),
);
const comfyClothingCompanyPrivateKey =
  'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
const comfyClothingCompanyAccount = bitxor_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 bitxor_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 token restriction {kyc, IsVerified, 1}.

  • Bob provides a valid passport and proof of address. The KYC provider awards Bob’s account with the token restriction {kyc, IsVerified, 2}.

  • Carol provides a valid passport and proof of address. The KYC provider awards Carol’s account with the token restriction {kyc, IsVerified, 2}.

The KYC provider has to tag the accounts accordingly sending token address restrictions.

// replace with kyc token id
const tokenIdHex = '183D0802BCDB97AF';
const tokenId = new TokenId(tokenIdHex);
// replace with alice address
const aliceRawAddress = 'BXRBDE-NCLKEB-ILBPWP-3JPB2X-NY64OE-7PYHHE-32I';
const aliceAddress = Address.createFromRawAddress(aliceRawAddress);
// replace with bob address
const bobRawAddress = 'BXRQ5E-YACWBP-CXKGIL-I6XWCH-DRFLTB-KUK34I-YJQ';
const bobAddress = Address.createFromRawAddress(bobRawAddress);
// replace with carol address
const carolRawAddress = 'BXR7MK-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 aliceTokenAddressRestrictionTransaction = TokenAddressRestrictionTransaction.create(
  Deadline.create(epochAdjustment),
  tokenId, // tokenId
  key, // restrictionKey
  aliceAddress, // address
  UInt64.fromUint(1), // newRestrictionValue
  networkType,
);

const bobTokenAddressRestrictionTransaction = TokenAddressRestrictionTransaction.create(
  Deadline.create(epochAdjustment),
  tokenId, // tokenId
  key, // restrictionKey
  bobAddress, // address
  UInt64.fromUint(2), // newRestrictionValue
  networkType,
);

const carolTokenAddressRestrictionTransaction = TokenAddressRestrictionTransaction.create(
  Deadline.create(epochAdjustment),
  tokenId, // tokenId
  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),
  [
    aliceTokenAddressRestrictionTransaction.toAggregate(
      kycProviderAccount.publicAccount,
    ),
    bobTokenAddressRestrictionTransaction.toAggregate(
      kycProviderAccount.publicAccount,
    ),
    carolTokenAddressRestrictionTransaction.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 token id
const tokenIdHex = '183D0802BCDB97AF';
const tokenId = new bitxor_sdk_1.TokenId(tokenIdHex);
// replace with alice address
const aliceRawAddress = 'BXRBDE-NCLKEB-ILBPWP-3JPB2X-NY64OE-7PYHHE-32I';
const aliceAddress = bitxor_sdk_1.Address.createFromRawAddress(aliceRawAddress);
// replace with bob address
const bobRawAddress = 'BXRQ5E-YACWBP-CXKGIL-I6XWCH-DRFLTB-KUK34I-YJQ';
const bobAddress = bitxor_sdk_1.Address.createFromRawAddress(bobRawAddress);
// replace with carol address
const carolRawAddress = 'BXR7MK-FL6QYF-UHWVRZ-6UUCLN-YBDWLQ-ZZC37A-2O6R';
const carolAddress = bitxor_sdk_1.Address.createFromRawAddress(carolRawAddress);
// replace with network type
const networkType = bitxor_sdk_1.NetworkType.TEST_NET;
const key = bitxor_sdk_1.KeyGenerator.generateUInt64Key(
    'IsVerified'.toLowerCase(),
);
const aliceTokenAddressRestrictionTransaction = bitxor_sdk_1.TokenAddressRestrictionTransaction.create(
    bitxor_sdk_1.Deadline.create(epochAdjustment),
    tokenId, // tokenId
    key, // restrictionKey
    aliceAddress, // address
    bitxor_sdk_1.UInt64.fromUint(1), // newRestrictionValue
    networkType,
);
const bobTokenAddressRestrictionTransaction = bitxor_sdk_1.TokenAddressRestrictionTransaction.create(
    bitxor_sdk_1.Deadline.create(epochAdjustment),
    tokenId, // tokenId
    key, // restrictionKey
    bobAddress, // address
    bitxor_sdk_1.UInt64.fromUint(2), // newRestrictionValue
    networkType,
);
const carolTokenAddressRestrictionTransaction = bitxor_sdk_1.TokenAddressRestrictionTransaction.create(
    bitxor_sdk_1.Deadline.create(epochAdjustment),
    tokenId, // tokenId
    key, // restrictionKey
    carolAddress, // address
    bitxor_sdk_1.UInt64.fromUint(2), // newRestrictionValue
    networkType,
);
// replace with kyc provider private key
const kycProviderPrivateKey =
    'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB';
const kycProviderAccount = bitxor_sdk_1.Account.createFromPrivateKey(
    kycProviderPrivateKey,
    networkType,
);
const aggregateTransaction = bitxor_sdk_1.AggregateTransaction.createComplete(
    bitxor_sdk_1.Deadline.create(epochAdjustment), [
        aliceTokenAddressRestrictionTransaction.toAggregate(
            kycProviderAccount.publicAccount,
        ),
        bobTokenAddressRestrictionTransaction.toAggregate(
            kycProviderAccount.publicAccount,
        ),
        carolTokenAddressRestrictionTransaction.toAggregate(
            kycProviderAccount.publicAccount,
        ),
    ],
    networkType, [],
    bitxor_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 bitxor_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.