Getting Started
This guide shows you how to integrate World ID verification into your Starknet smart contracts.
New to World ID? Learn Why It Matters for your application. Need to set up your development environment first? Check our Prerequisites.
Step 1: Define the Interface
The first interface IGroth16VerifierBN254 defines how to interact with the World ID Bridge contract for proof verification. The second interface IMyApp defines your application's public function that will handle the verification process.
use starknet::ContractAddress;
#[starknet::interface]
pub trait IGroth16VerifierBN254<TContractState> {
fn verify_groth16_proof_bn254(
self: @TContractState,
full_proof_with_hints: Span<felt252>
) -> Option<Span<u256>>;
}
#[starknet::interface]
pub trait IMyApp<TContractState> {
fn verify_human(ref self: TContractState, full_proof_with_hints: Span<felt252>) -> bool;
}Step 2: Integrate in Your Contract
This contract stores the World ID Bridge address in its constructor and implements a verify_human function. The function calls the bridge to verify the proof and extracts public inputs including the nullifier hash and signal data for your application logic.
#[starknet::contract]
pub mod MyApp {
use starknet::ContractAddress;
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use super::{IGroth16VerifierBN254Dispatcher, IGroth16VerifierBN254DispatcherTrait, IMyApp};
#[storage]
struct Storage {
world_id_bridge: ContractAddress, // Address of the deployed World ID Bridge
}
#[constructor]
fn constructor(ref self: ContractState, world_id_bridge_address: ContractAddress) {
// Store the World ID Bridge contract address
self.world_id_bridge.write(world_id_bridge_address);
}
#[abi(embed_v0)]
impl MyAppImpl of IMyApp<ContractState> {
fn verify_human(
ref self: ContractState,
full_proof_with_hints: Span<felt252> // Garaga-formatted proof with hints
) -> bool {
// Create dispatcher to call the World ID Bridge
let world_id = IGroth16VerifierBN254Dispatcher {
contract_address: self.world_id_bridge.read()
};
// Verify the proof - bridge automatically validates root
match world_id.verify_groth16_proof_bn254(full_proof_with_hints) {
Option::Some(public_inputs) => {
// Proof valid - extract public inputs
let _root = *public_inputs.at(0); // Merkle tree root (auto-validated)
let _nullifier_hash = *public_inputs.at(1); // Prevents replay attacks
let _signal_hash = *public_inputs.at(2); // Your app signal
let _external_nullifier_hash = *public_inputs.at(3); // Your action identifier
// Add your application logic here
true
},
Option::None => false // Proof verification failed
}
}
}
}Step 3: Bridge Contract Addresses
Use these contract addresses to connect your application to the World ID Bridge. The testnet address is for development and testing, while mainnet will be available for production deployments.
Testnet
- Bridge Contract:
0x01167d6979330fcc6633111d72416322eb0e3b78ad147a9338abea3c04edfc8a
Mainnet
- Coming soon
Proof Formatting
World ID proofs must be formatted with Garaga hints before submission to the bridge. Garaga handles the conversion from standard Groth16 proofs to Starknet-compatible format.
For detailed proof formatting and generation instructions, see the Garaga Documentation.
Quick Setup:
- Install Garaga:
pip install garaga - Follow the Groth16 Verifier guide for formatting your World ID proofs
- Use the formatted proof with the bridge as shown in the examples above
For production applications, implement Nullifier Tracking to prevent replay attacks and add robust Error Handling.
Key Points
- The bridge automatically validates World ID roots
- Public inputs are returned in order:
[root, nullifierHash, signalHash, externalNullifierHash] - Proofs must be formatted with Garaga hints before submission
- Returns
Option::Some(public_inputs)if valid,Option::Noneif invalid