3. Permission smart contracts

This step is optional. Client can use Keyring's Pro and Connect onboardings without the on-chain credentials component.

How to permission contracts?

Any smart contract function can be permissioned with a modifier, allowing only whitelisted wallets to perform the underlying action. Examples of functions you can gate:

How to implement Keyring Permissioning?

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.

Here is an example implementation:

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 Keyring address in the contract constructor. You may optionally define a Whitelist mapping with associated values.


/// @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);
            }
        }
    }

Last updated