Beta version

Smart Contract

Cairo smart contract for on-chain verification on Starknet.

Starknet Mainnet

0x07f66804a1556e31993afb4eddfb2c90b78f67785c92a5860a076edc38b17d26

View on Starkscan

Overview

The HumanGateRegistry is a Cairo smart contract deployed on Starknet Mainnet that serves as the on-chain source of truth for human verification. It stores attestations, tracks which actions have been consumed, and provides view functions that other contracts can call to gate access.

The primary way attestations are registered is through claim_attestation() — the user calls this function directly with their wallet, submitting signed data from the HumanGate backend. The contract verifies the ECDSA signature against the registered signing key before storing the attestation. This means users pay their own gas (~$0.01), and the protocol has zero gas costs.

User Functions

These functions are called by end users with their own wallets.

claim_attestation

The primary function for registering a human verification on-chain. The user submits signed attestation data received from the HumanGate backend. The contract verifies the ECDSA signature, checks the nullifier hasn't been used, and stores the attestation.

IHumanGate
fn claim_attestation(
    ref self: TContractState,
    provider_id: felt252,
    level: u8,
    expires_at: u64,
    nullifier_hash: felt252,
    signature_r: felt252,
    signature_s: felt252,
)
ParameterTypeDescription
provider_idfelt252Verification provider identifier (e.g. shortString.encodeShortString('worldcoin')).
levelu8Verification level: 1 (Device) or 2 (Orb).
expires_atu64Unix timestamp when the attestation expires.
nullifier_hashfelt252Unique nullifier from World ID, preventing double-verification.
signature_rfelt252ECDSA signature R component from the HumanGate backend.
signature_sfelt252ECDSA signature S component from the HumanGate backend.

Signature verification: The contract computes a chained Pedersen hash of (caller_address, provider_id, level, expires_at, nullifier_hash, chain_id) and verifies the ECDSA signature against the public key registered for the provider via set_signing_key().

JavaScript Example

claim.ts
import { CallData } from "starknet";

// signedAttestation received from POST /api/verify
const result = await account.execute({
  contractAddress: REGISTRY_ADDRESS,
  entrypoint: "claim_attestation",
  calldata: CallData.compile({
    provider_id: signedAttestation.providerId,
    level: signedAttestation.level,
    expires_at: signedAttestation.expiresAt,
    nullifier_hash: signedAttestation.nullifierHash,
    signature_r: signedAttestation.signatureR,
    signature_s: signedAttestation.signatureS,
  }),
});

View Functions

View functions are read-only and free to call (no gas). Use them to check verification status from your dApp or contract.

is_human

The simplest check — returns true if the address has any valid, non-expired, non-revoked attestation from any active provider at or above the minimum level. Iterates all registered providers.

IHumanGate
fn is_human(
    self: @TContractState,
    address: ContractAddress,
    min_level: u8,
) -> bool

is_eligible

The primary function for checking whether an address can perform a scoped action. Returns true only if the address has a valid, non-expired, non-revoked attestation and has not already consumed the given scope.

IHumanGate
fn is_eligible(
    self: @TContractState,
    address: ContractAddress,
    scope_hash: felt252,
    provider_id: felt252,
) -> bool

has_consumed

Checks whether an address has already consumed a specific scope.

IHumanGate
fn has_consumed(
    self: @TContractState,
    address: ContractAddress,
    scope_hash: felt252,
) -> bool

get_attestation

Returns the full attestation record for an address and provider. Returns zero values if no attestation exists.

IHumanGate
fn get_attestation(
    self: @TContractState,
    address: ContractAddress,
    provider_id: felt252,
) -> AttestationView
// AttestationView { provider_id, level, issued_at, expires_at, is_revoked }

get_signing_key

Returns the public signing key registered for a provider. Used by the contract internally to verify ECDSA signatures in claim_attestation().

IHumanGate
fn get_signing_key(
    self: @TContractState,
    provider_id: felt252,
) -> felt252

State-Changing Functions

consume_action

Marks an action scope as consumed for an address. Returns true if successful. Only the address owner can call this (caller == address). Typically called through your dApp contract that gates operations behind human verification.

IHumanGate
fn consume_action(
    ref self: TContractState,
    address: ContractAddress,
    scope_hash: felt252,
    provider_id: felt252,
) -> bool

Admin Functions

Only callable by the contract owner.

set_signing_key

Registers or updates the public signing key for a provider. The backend signs attestation data with the corresponding private key, and the contract verifies signatures against this public key.

IHumanGate
fn set_signing_key(
    ref self: TContractState,
    provider_id: felt252,
    public_key: felt252,
)

register_provider

Registers a new verification provider with a name, registrar address, and level bounds. Fails if the provider is already active.

IHumanGate
fn register_provider(
    ref self: TContractState,
    provider_id: felt252,
    name: felt252,
    registrar: ContractAddress,
    min_level: u8,
    max_level: u8,
)

issue_attestation

Directly issues an attestation without signature verification. Only callable by the registered registrar address. Used for admin/batch operations. For normal user flow, use claim_attestation().

IHumanGate
fn issue_attestation(
    ref self: TContractState,
    address: ContractAddress,
    provider_id: felt252,
    level: u8,
    expires_at: u64,
    nullifier_hash: felt252,
)

revoke_attestation

Revokes an existing attestation. Only callable by the registrar.

IHumanGate
fn revoke_attestation(
    ref self: TContractState,
    address: ContractAddress,
    provider_id: felt252,
)

register_scope

Registers a new scope for scoped actions (e.g. an airdrop or vote). Anyone can register a scope. The required_level sets the minimum verification level needed to use this scope.

IHumanGate
fn register_scope(
    ref self: TContractState,
    scope_hash: felt252,
    required_level: u8,
)

deactivate_provider

Deactivates a verification provider. Only callable by the contract owner. Attestations from deactivated providers are no longer valid for eligibility checks.

IHumanGate
fn deactivate_provider(
    ref self: TContractState,
    provider_id: felt252,
)

deactivate_scope

Deactivates a scope. Only callable by the scope creator or contract owner.

IHumanGate
fn deactivate_scope(
    ref self: TContractState,
    scope_hash: felt252,
)

Integration Example

To gate an action in your own Cairo contract, use the dispatcher pattern to call the HumanGateRegistry:

airdrop.cairo
use starknet::ContractAddress;
use starknet::get_caller_address;

#[starknet::interface]
trait IHumanGate<TContractState> {
    fn is_eligible(
        self: @TContractState,
        address: ContractAddress,
        scope_hash: felt252,
        provider_id: felt252,
    ) -> bool;
    fn consume_action(
        ref self: TContractState,
        address: ContractAddress,
        scope_hash: felt252,
        provider_id: felt252,
    ) -> bool;
}

#[starknet::contract]
mod Airdrop {
    use super::{ContractAddress, get_caller_address};
    use super::{IHumanGateDispatcher, IHumanGateDispatcherTrait};

    #[storage]
    struct Storage {
        registry: ContractAddress,
        scope_hash: felt252,
        provider_id: felt252,
    }

    #[external(v0)]
    fn claim(ref self: ContractState) {
        let caller = get_caller_address();
        let registry = IHumanGateDispatcher {
            contract_address: self.registry.read()
        };

        // Check eligibility (free read)
        let eligible = registry.is_eligible(
            caller,
            self.scope_hash.read(),
            self.provider_id.read(),
        );
        assert(eligible, 'Not eligible');

        // Consume the action to prevent double-claiming
        let consumed = registry.consume_action(
            caller,
            self.scope_hash.read(),
            self.provider_id.read(),
        );
        assert(consumed, 'Failed to consume');

        // Safe to proceed — distribute tokens to caller
    }
}

Events

EventFieldsDescription
AttestationClaimedaddress, provider_id, level, expires_atEmitted when a user claims an attestation via claim_attestation().
AttestationIssuedaddress, provider_id, level, expires_atEmitted when an attestation is issued via issue_attestation() (admin).
AttestationRevokedaddress, provider_idEmitted when an attestation is revoked.
ActionConsumedaddress, scope_hash, provider_id, timestampEmitted when a user consumes a scoped action.
ProviderRegisteredprovider_id, registrar, nameEmitted when a new verification provider is registered.
ScopeCreatedscope_hash, creator, required_levelEmitted when a new scope is registered.