Skip to main content
There are various reasons you may want to restrict some onchain action to users that have completed KYC checks first. The example below ensures an ERC-20 token can only be held by wallets associated with a KYC’d user.

How does it work?

By hooking the transfer and transferFrom functions of the ERC-20 contract into the Rules Engine with a policy that verifies the receiving wallet has completed the appropriate checks.

Let’s break that down further.

KYC is an off-chain process where the user verifies their identity by submitting government issued identification that matches a liveness selfcheck via webcam or phone. This process can also include email, phone, or address verification. Once completed, the wallet address that the user initiated the request with is stored onchain. When the token contract is called to transfer tokens it first checks this onchain KYC access level contract to make sure the receiver has completed sufficient KYC. If they haven’t, then the transaction will fail, preventing the non-KYC’d wallet from receiving the token.

Implementation

Forte offers a fully integrated KYC and compliance solution that handles this entire flow for you. Learn more at https://docs.fortepayments.io.
KYC data for the user is not actually stored onchain. Rather, the KYC all happens off-chain in a secure system. That process then completes by pushing the related wallet address onchain with a specific access level that you can use to build your rules around.
The Rules Engine is flexible enough to work with other KYC providers, but you will need to integrate backend logic that pushes the user’s wallet address onchain so that it can be checked during rule evaluation. mmc-demo-kyc

Martian Mining Demo

You can see this implementation in action on this demo site

Policy JSON

{
  "Policy": "KYC Gate",
  "Description": "Only allow KYC'd wallets to receive the token",
  "PolicyType": "open",
  "CallingFunctions": [
    {
      "Name": "transfer(address to, uint256 value)",
      "FunctionSignature": "transfer(address to, uint256 value)",
      "EncodedValues": "address to, uint256 value"
    },
    {
      "Name": "transferFrom(address from, address to, uint256 value)",
      "FunctionSignature": "transferFrom(address from, address to, uint256 value)",
      "EncodedValues": "address from, address to, uint256 value"
    }
  ],
  "ForeignCalls": [
    {
      "Name": "KYCDeniedForTransfer",
      "Address": "0x_kyc_access_level_contract",
      "Function": "getAccessLevel(address)",
      "ReturnType": "uint256",
      "ValuesToPass": "to",
      "MappedTrackerKeyValues": "",
      "CallingFunction": "transfer(address to, uint256 value)"
    },
    {
      "Name": "KYCDeniedForTransferFrom",
      "Address": "0x_kyc_access_level_contract",
      "Function": "getAccessLevel(address)",
      "ReturnType": "uint256",
      "ValuesToPass": "to",
      "MappedTrackerKeyValues": "",
      "CallingFunction": "transferFrom(address from, address to, uint256 value)"
    }
  ],
  "MappedTrackers": [],
  "Trackers": [],
  "Rules": [
    {
      "Name": "KYC Enforcement for Transfer",
      "Description": "This rule ensures wallet has completed KYC for the transfer function",
      "Condition": "FC:KYCDeniedForTransfer > 0",
      "PositiveEffects": [],
      "NegativeEffects": ["revert(\"Insufficient KYC Access Level\")"],
      "CallingFunction": "transfer(address to, uint256 value)"
    },
    {
      "Name": "KYC Enforcement for TransferFrom",
      "Description": "This rule ensures wallet has completed KYC for the transferFrom function",
      "Condition": "FC:KYCDeniedForTransferFrom > 0",
      "PositiveEffects": [],
      "NegativeEffects": ["revert(\"Insufficient KYC Access Level\")"],
      "CallingFunction": "transferFrom(address from, address to, uint256 value)"
    }
  ]
}
It’s critical that you add the rule to both the transfer and transferFrom functions to ensure the tokens cannot be received by non-KYC’d wallets.
I