2. On-chain Permissioning

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:

Whitelists are fully composable. For instance the modifier could allow action only to wallets that verify {whitelist A OR whitelist B AND NOT blacklist C}.

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.

Keyring does not natively allow whitelisting via the Keyring infrastructure. What this means is that you should create a whitelist for those addresses that are not able to have credentials created for them, such as AMM pools or other smart contracts that need to allow transfers in/out of your asset. 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