Skip to content

Start typing to search the documentation.

Gating Smart Contracts

This guide shows how to use the SelfAgentRegistry from Solidity to gate smart contract functions by proof-of-human agent identity.

1. Import the Registry Interface

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

interface ISelfAgentRegistry {
    // Agent verification
    function isVerifiedAgent(bytes32 agentKey) external view returns (bool);
    function getAgentId(bytes32 agentKey) external view returns (uint256);
    function getAgentCredentials(uint256 agentId) external view returns (AgentCredentials memory);

    // Proof-of-human (IERC8004ProofOfHuman)
    function hasHumanProof(uint256 agentId) external view returns (bool);
    function isProofFresh(uint256 agentId) external view returns (bool);
    function proofExpiresAt(uint256 agentId) external view returns (uint256);
    function getProofProvider(uint256 agentId) external view returns (address);

    // Sybil detection
    function sameHuman(uint256 agentIdA, uint256 agentIdB) external view returns (bool);
    function getAgentCountForHuman(uint256 nullifier) external view returns (uint256);
    function getHumanNullifier(uint256 agentId) external view returns (uint256);

    // ERC-8004 metadata
    function setAgentURI(uint256 agentId, string calldata newURI) external;
    function getMetadata(uint256 agentId, string memory key) external view returns (bytes memory);
    function setMetadata(uint256 agentId, string calldata key, bytes calldata value) external;

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

Or import from the contracts package:

import {ISelfAgentRegistryReader} from "self-agent-id/contracts/src/interfaces/ISelfAgentRegistryReader.sol";

2. onlyVerifiedAgent Modifier

The most common pattern — gate a function to verified agents only:

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 human-verified");
        _;
    }

    function protectedAction() external onlyVerifiedAgent {
        // Only callable by verified agents
    }
}

The agent key derivation: bytes32(uint256(uint160(address))) — pads the 20-byte address to 32 bytes.

3. Query Credentials On-Chain

function gatedByAge(uint256 minAge) external onlyVerifiedAgent {
    bytes32 agentKey = bytes32(uint256(uint160(msg.sender)));
    uint256 agentId = registry.getAgentId(agentKey);
    ISelfAgentRegistry.AgentCredentials memory creds = registry.getAgentCredentials(agentId);

    require(creds.olderThan >= minAge, "Age requirement not met");
    // creds.nationality, creds.ofac, creds.dateOfBirth, etc.
}

4. Check Proof Freshness

Proofs expire. Always check freshness for security-critical operations:

function requireFreshProof(bytes32 agentKey) internal view {
    require(registry.isVerifiedAgent(agentKey), "Agent not verified");
    uint256 agentId = registry.getAgentId(agentKey);
    require(registry.isProofFresh(agentId), "Human proof expired — re-authentication required");
}

5. Sybil Checks

Detect or prevent multiple agents from the same human:

// Check if two agents are the same person
bool same = registry.sameHuman(agentIdA, agentIdB);

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

6. EIP-712 Meta-Transaction Pattern

For gasless agent verification, use a relayer that submits EIP-712 typed data on behalf of the agent:

// Agent signs off-chain:
//   domain: { name, version, chainId, verifyingContract }
//   types:  { Verify: [agentKey, nonce, deadline] }
//   values: { agentKey, nonce, deadline }

function metaVerifyAgent(
    bytes32 agentKey,
    uint256 nonce,
    uint256 deadline,
    bytes calldata signature
) external returns (uint256 agentId) {
    require(block.timestamp <= deadline, "Expired");
    require(nonces[agentKey] == nonce, "Invalid nonce");

    // Recover signer from EIP-712 signature
    bytes32 structHash = keccak256(abi.encode(VERIFY_TYPEHASH, agentKey, nonce, deadline));
    bytes32 digest = _hashTypedDataV4(structHash);
    address signer = ECDSA.recover(digest, signature);

    // Verify signer matches agent key
    require(bytes32(uint256(uint160(signer))) == agentKey, "Signer mismatch");
    require(registry.isVerifiedAgent(agentKey), "Not verified");

    nonces[agentKey]++;
    agentId = registry.getAgentId(agentKey);
}

See AgentDemoVerifier.sol in the repo for a complete implementation.

7. Reference Contracts

The repo includes two demo contracts you can use as templates:

AgentDemoVerifier

EIP-712 meta-transaction verifier. Demonstrates gasless verification where a relayer submits on behalf of agents.

AgentGate

Access gate requiring age-verified agents. Demonstrates direct msg.sender verification with credential checks.

8. Contract Addresses

Celo Mainnet (Chain ID: 42220)

ContractAddress
SelfAgentRegistry (proxy)0xaC3DF9ABf80d0F5c020C06B04Cced27763355944
SelfHumanProofProvider0x4b036aFD959B457A208F676cf44Ea3ef73Ea3E3d
AgentDemoVerifier0xD8ec054FD869A762bC977AC328385142303c7def
AgentGate0x26e05bF632fb5bACB665ab014240EAC1413dAE35
Hub V20xe57F4773bd9c9d8b6Cd70431117d353298B9f5BF

Celo Sepolia Testnet (Chain ID: 11142220)

ContractAddress
SelfAgentRegistry (proxy)0x043DaCac8b0771DD5b444bCC88f2f8BBDBEdd379
SelfHumanProofProvider0x5E61c3051Bf4115F90AacEAE6212bc419f8aBB6c
AgentDemoVerifier0xc31BAe8f2d7FCd19f737876892f05d9bDB294241
AgentGate0x86Af07e30Aa42367cbcA7f2B1764Be346598bbc2
Hub V20x16ECBA51e18a4a7e61fdC417f0d47AFEeDfbed74

9. Foundry Setup

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

Or add to foundry.toml:

[profile.default]
evm_version = "cancun"

Next Steps