Smart Contract
Cairo smart contract for on-chain verification on Starknet.
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.
fn claim_attestation(
ref self: TContractState,
provider_id: felt252,
level: u8,
expires_at: u64,
nullifier_hash: felt252,
signature_r: felt252,
signature_s: felt252,
)| Parameter | Type | Description |
|---|---|---|
| provider_id | felt252 | Verification provider identifier (e.g. shortString.encodeShortString('worldcoin')). |
| level | u8 | Verification level: 1 (Device) or 2 (Orb). |
| expires_at | u64 | Unix timestamp when the attestation expires. |
| nullifier_hash | felt252 | Unique nullifier from World ID, preventing double-verification. |
| signature_r | felt252 | ECDSA signature R component from the HumanGate backend. |
| signature_s | felt252 | ECDSA 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
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.
fn is_human(
self: @TContractState,
address: ContractAddress,
min_level: u8,
) -> boolis_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.
fn is_eligible(
self: @TContractState,
address: ContractAddress,
scope_hash: felt252,
provider_id: felt252,
) -> boolhas_consumed
Checks whether an address has already consumed a specific scope.
fn has_consumed(
self: @TContractState,
address: ContractAddress,
scope_hash: felt252,
) -> boolget_attestation
Returns the full attestation record for an address and provider. Returns zero values if no attestation exists.
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().
fn get_signing_key(
self: @TContractState,
provider_id: felt252,
) -> felt252State-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.
fn consume_action(
ref self: TContractState,
address: ContractAddress,
scope_hash: felt252,
provider_id: felt252,
) -> boolAdmin 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.
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.
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().
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.
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.
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.
fn deactivate_provider(
ref self: TContractState,
provider_id: felt252,
)deactivate_scope
Deactivates a scope. Only callable by the scope creator or contract owner.
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:
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
| Event | Fields | Description |
|---|---|---|
| AttestationClaimed | address, provider_id, level, expires_at | Emitted when a user claims an attestation via claim_attestation(). |
| AttestationIssued | address, provider_id, level, expires_at | Emitted when an attestation is issued via issue_attestation() (admin). |
| AttestationRevoked | address, provider_id | Emitted when an attestation is revoked. |
| ActionConsumed | address, scope_hash, provider_id, timestamp | Emitted when a user consumes a scoped action. |
| ProviderRegistered | provider_id, registrar, name | Emitted when a new verification provider is registered. |
| ScopeCreated | scope_hash, creator, required_level | Emitted when a new scope is registered. |