Allow another account to add restrictions to your tokens.
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.
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.
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.
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
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
.
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),
);
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.