Step 3: HOW

How do you gate it?

You can create your own hooks or modifiers to include a guard in your contracts. You simply read from Keyring the status of an address with respect to a policy. This is a flexible and easy way to customise the integration to your needs.

Contact us if you need any help setting it up. We have a library of examples you can leverage.

Integration Example

Let's assume we want to guard an ERC20 transfer function.

Firstly, define the Keyring interface for checking credentials.

/**
 * @title IKeyring
 * @dev Interface for the Keyring contract to check credentials.
 */
interface IKeyring {
    /**
     * @notice Checks the credential of an entity against a specific policy.
     * @param policyId The ID of the policy to check against.
     * @param entity The address of the entity to check.
     * @return A boolean value indicating whether the entity's credentials pass the policy check.
     */
    function checkCredential(uint256 policyId, address entity) external view returns (bool);
}

Secondly, define the Policy ID and the KeyringCredentialViewer address in the contract constructor. You may optionally define a Whitelist mapping with associated values.

While Keyring currently allows whitelisting via the Keyring infrastructure at this time, this capability will be deprecated in future versions. This does not mean whitelisting cannot be performed by Keyring on behalf of an integrating partner, but it will not be handled by default. Integrating partners should define their whitelist locally on smart contracts or seek assistance from Keyring to create a whitelist-capable integration.


/// @notice Address of the Keyring contract.
address public KeyringAddress;
/// @notice ID of the Keyring policy.
uint256 public KeyringPolicyId;
/// @notice Mapping to track whitelist status of addresses.
mapping(address => bool) public Whitelist;

/**
* @notice Constructor to initialize the KeyringToken contract.
* @param initialOwner The address of the initial owner.
* @param keyring The address of the Keyring contract.
* @param policyId The ID of the Keyring policy.
*/
constructor(address initialOwner, address keyring, uint256 policyId)
    ERC20("KeyringToken", "KTK")
    Ownable(initialOwner) {
        KeyringAddress = keyring;
        KeyringPolicyId = policyId;
}

Then, override the functions that need permissioning. In this example OpenZepplin 5.0.2 is used for the base ERC20 implementation and so we override the _update function in order to perform a Keyring check.

/**
* @dev Internal function to update the token transfer state.
* @param from The address sending the tokens.
* @param to The address receiving the tokens.
* @param amount The amount of tokens being transferred.
*/
function _update(
   address from,
   address to,
   uint256 amount
   ) internal virtual override {
        super._update(from, to, amount);
        if (to == address(0) || from == address(0) || Whitelist[to]) {
            // Do not check keyring for minting, burning, or whitelisted contracts
            return;
        }
        // Check the status of `to`
        address keyringAddress = KeyringAddress;
        if (keyringAddress != address(0)) {
            IKeyring k = IKeyring(keyringAddress);
            uint256 keyringPolicyId = KeyringPolicyId;
            bool ok = k.checkCredential(keyringPolicyId, to);
            if (!ok) {
                // Revert if credential check fails
                revert KeyringCredentialInvalid(to);
            }
        }
    }

When dealing with token transfers, make sure to properly address the mint function (where from is the null address 0x0000...) and the burn function (where the to is the zero address), as the address(0)is always regarded as unauthorised.

Last updated