This policy template will expand upon the existing KYC gate policy by adding a mapped tracker and some additional rule conditions.
We will leverage the existing KYC approach and augment it by adding additional restrictions on how much of the token the wallet can hold based on the level of KYC completed. Let’s consider four distinct access levels, defined as follows:
| KYC Access Level | Explanation | Maximum Balance |
|---|
| 0 | No KYC | 0 tokens |
| 1 | email verified | 1,000 tokens |
| 2 | email + phone verified | 10,000 tokens |
| 3 | Gov’t ID + Liveness Check | 1,000,000 tokens |
Let’s create a mapped tracker that will store this info so it can be checked in rule conditions.
{
"Policy": "KYC gate + limited balance",
"Description": "Add balance limits based on KYC access level",
...
"MappedTrackers": [
{
"Name": "AccessLevelBalance",
"KeyType": "uint256",
"ValueType": "uint256",
"InitialKeys": [0,1,2,3],
"InitialValues": [0,1000,10000, 1000000]
}
]
...
}
To make these examples easier to read the amounts are using the whole number token value. In
actual practice you will need to include the the decimal count as well. For a typical 18 decimal
ERC-20 token you would need 18 zeros to indicate a single token.1 token == 1_000_000_000_000_000_000 w/o the underscores: 1000000000000000000
The next detail to add will be additional rule conditions to ensure the wallet’s balance does not exceed the permitted amount for the given KYC access level. The simplest way to do this is include the user’s balance as extra data when the transfer or transferFrom functions invoke the rules engine. First, indicate that the balance will be included as an EncodedValue as shown below:
{
...
"CallingFunctions": [
{
"Name": "transfer(address to, uint256 value)",
"FunctionSignature": "transfer(address to, uint256 value)",
"EncodedValues": "address to, uint256 value, uint256 userBalance"
},
{
"Name": "transferFrom(address from, address to, uint256 value)",
"FunctionSignature": "transferFrom(address from, address to, uint256 value)",
"EncodedValues": "address from, address to, uint256 value, uint256 userBalance"
}
],
...
}
Next, you’d need to update the modifer that is attached to the transfer and transferFrom functions in your token contract.
// update function signature to expect additional argument and then pass that encoding function
modifier checkRulesBeforetransfer(address to, uint256 value) {
modifier checkRulesBeforetransfer(address to, uint256 value, uint256 userBalance) {
bytes memory encoded = abi.encodeWithSelector(msg.sig, to, value);
bytes memory encoded = abi.encodeWithSelector(msg.sig, to, value, userBalance);
_invokeRulesEngine(encoded);
_;
}
You would also need to make the above change for the transferFrom function and
checkRulesBeforetransferFrom modifier.
Now you have everything required to adjust the rule conditions for this additional constraint.
{
...
"Rules": [
{
"Name": "KYC Enforcement for Transfer",
"Description": "Ensure balance limits based on KYC access level",
"Condition": "userBalance + value <= TR:AccessLevelLimit(FC:KYCAccessLevelTransfer)",
"PositiveEffects": [],
"NegativeEffects": ["revert(\"KYC access level insufficient for resulting balance\")"],
"CallingFunction": "transfer(address to, uint256 value)"
},
{
"Name": "KYC Enforcement for TransferFrom",
"Description": "Ensure balance limits based on KYC access level",
"Condition": "userBalance + value <= TR:AccessLevelLimit(FC:KYCAccessLevelTransferFrom)",
"PositiveEffects": [],
"NegativeEffects": ["revert(\"KYC access level insufficient for resulting balance\")"],
"CallingFunction": "transferFrom(address from, address to, uint256 value)"
}
]
}