Skip to content

Start typing to search the documentation.

Gating smart contracts

On this page

This guide gates Solidity functions on Self Agent ID, so only agents backed by a human with a fresh proof can call them. The agent sends the transaction from its own EVM key, so msg.sender is the agent.

1. Declare the registry interface

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface ISelfAgentRegistry {
    struct AgentCredentials {
        string issuingState;
        string[] name;
        string idNumber;
        string nationality;
        string dateOfBirth;
        string gender;
        string expiryDate;
        uint256 olderThan;
        bool[3] ofac;
    }

    function isVerifiedAgent(bytes32 agentKey) external view returns (bool);
    function getAgentId(bytes32 agentKey) external view returns (uint256);
    function isProofFresh(uint256 agentId) external view returns (bool);
    function proofExpiresAt(uint256 agentId) external view returns (uint256);
    function getProofProvider(uint256 agentId) external view returns (address);
    function selfProofProvider() external view returns (address);
    function getAgentCredentials(uint256 agentId) external view returns (AgentCredentials memory);
    function getHumanNullifier(uint256 agentId) external view returns (uint256);
    function getAgentCountForHuman(uint256 nullifier) external view returns (uint256);
    function sameHuman(uint256 agentIdA, uint256 agentIdB) external view returns (bool);
}

The full interfaces are in the repo: contracts/src/interfaces.

2. Add an onlyVerifiedAgent modifier

contract MyContract {
    ISelfAgentRegistry public immutable registry;

    constructor(address registry_) {
        registry = ISelfAgentRegistry(registry_);
    }

    modifier onlyVerifiedAgent() {
        bytes32 agentKey = bytes32(uint256(uint160(msg.sender)));
        require(registry.isVerifiedAgent(agentKey), "Agent not registered");
        require(registry.isProofFresh(registry.getAgentId(agentKey)), "Human proof expired");
        _;
    }

    function protectedAction() external onlyVerifiedAgent {
        // only verified agents get here
    }
}

The agent key is the address left-padded to 32 bytes: bytes32(uint256(uint160(addr))).

3. Require Self as the provider

uint256 agentId = registry.getAgentId(agentKey);
require(registry.getProofProvider(agentId) == registry.selfProofProvider(), "Not a Self proof");

4. Check credentials

Credentials hold what the human disclosed at registration. Undisclosed fields are empty.

function gatedByAge(uint256 minAge) external onlyVerifiedAgent {
    uint256 agentId = registry.getAgentId(bytes32(uint256(uint160(msg.sender))));
    ISelfAgentRegistry.AgentCredentials memory creds = registry.getAgentCredentials(agentId);
    require(creds.olderThan >= minAge, "Age requirement not met");
}

5. Add sybil checks

bool same = registry.sameHuman(agentIdA, agentIdB);

uint256 count = registry.getAgentCountForHuman(registry.getHumanNullifier(agentId));
require(count <= maxAllowed, "Too many agents for this human");

6. Deploy

Pass the SelfAgentRegistry address to the constructor: 0xaC3DF9ABf80d0F5c020C06B04Cced27763355944 on Celo Mainnet, or 0x043DaCac8b0771DD5b444bCC88f2f8BBDBEdd379 on Celo Sepolia. Other contract addresses are in Smart contracts.

7. Foundry setup

Hub V2 uses the PUSH0 opcode, so build and test with --evm-version cancun:

forge build --evm-version cancun
forge test --evm-version cancun

Or set it in foundry.toml:

[profile.default]
evm_version = "cancun"

Next steps

Was this page helpful?