Technical Reference

Smart Contracts

All of our smart contracts are available on GitHub:

If you're interested in using World ID and verifying proofs on-chain, see our On-Chain Verification guide.

Supported Chains

ChainTestnetRoleIdentity Availability
Ethereum logo

Ethereum

SepoliaCanonical~60 minutes after verification
Optimism logo

Optimism

Optimism SepoliaBridged~5 Minutes after Ethereum
Polygon logo

Polygon

N/ABridged~40 Minutes after Ethereum
Base logo

Base (Testnet Only)

Base SepoliaBridged~5 Minutes after Ethereum Sepolia
Find our smart contract address book here.

Architecture

This section offers a high-level overview of the various smart contracts that make up World ID. This structure (including state bridging) is replicated on testnets -- currently Sepolia, Optimism Sepolia, and Base Sepolia.

Identity Managers: WorldIdIdentityManager

Identity Managers are only deployed on Ethereum. The Identity Manager contracts are responsible for managing the Semaphore instance. Worldcoin's signup sequencers call the Identity Manager contracts to add or remove identities from the merkle tree.

State Bridges: OpStateBridge/PolygonStateBridge

One State Bridge contract is deployed on Ethereum for each bridged chain. It publishes the root of the merkle tree to its configured chain's World ID contract, allowing proofs to be verified on that chain.

Bridged World ID: OpWorldId/PolygonWorldId

One World ID contract is deployed on each bridged chain, with an associated State Bridge contract on Ethereum. It is responsible for receiving merkle roots from its State Bridge contract, and verifying World ID proofs against those roots.

You can deploy your own State Bridge contract on Ethereum and Bridged World ID contract to any chain to bridge World ID to that chain permissionlessly.

World ID Router: WorldIdRouter

This is the contract you should interact with.

The World ID Router will route your call to the correct Identity Manager contract (Ethereum) or World ID contract (L2 Chains) based on the groupId argument. This contract is proxied, so you will not need to update your code if the underlying contracts are upgraded.

Only Orb credentials are supported on-chain, so the groupId must be 1.

Usage

The verifyProof method of the World ID Router is used to verify proofs on-chain.

Arguments

  • Name
    root
    Type
    uint256
    Required
    REQUIRED
    Description

    The World ID root to verify against.

  • Name
    groupId
    Type
    uint256
    Required
    REQUIRED
    Description

    Determines which Credential Type to verify against. As only Orb credentials are supported on-chain, this must be 1.

    Orb-Only groupId

    uint256 internal immutable groupId = 1;
    
  • Name
    signalHash
    Type
    uint256
    Required
    REQUIRED
    Description

    The keccak256 hash of the signal to verify.

    signalHash

    abi.encodePacked(signal).hashToField();
    
  • Name
    nullifierHash
    Type
    uint256
    Required
    REQUIRED
    Description

    The root of the merkle tree to verify against. This is obtained from the IDKit widget as a hex string nullifier_hash, and must be converted to a uint256 before passing it to the verifyProof method.

  • Name
    externalNullifierHash
    Type
    uint256
    Required
    REQUIRED
    Description

    The keccak256 hash of the externalNullifier to verify. The externalNullifier is computed from the app_id and action.

    externalNullifierHash

    externalNullifier = abi.encodePacked(abi.encodePacked(appId).hashToField(), action)
    externalNullifierHash = externalNullifier.hashToField();
    
    Read more about the External Nullifier in Protocol Internals.
  • Name
    proof
    Type
    uint256[8]
    Required
    REQUIRED
    Description

    The zero-knowledge proof to verify. This is obtained from the IDKit widget as a hex string proof, and must be converted to a uint256[8] before passing it to the verifyProof method.

    Unpacking Proof

    import { decodeAbiParameters } from 'viem'
    
    const unpackedProof = decodeAbiParameters([{ type: 'uint256[8]' }], proof)[0]
    

Sybil resistance

While the World ID protocol makes it very easy to make your contracts sybil resistant, this takes a little more than just calling the verifyProof function. To make your contract sybil-resistant, you'll need to do the following:

  • Store the nullifierHash of each user that has successfully verified a proof.
  • When a user attempts to verify a proof, check that the nullifierHash is not already in the list of used nullifierHashes.

Here's an example function doing the above. You can also use the World ID starter kits to get started with sybil resistance.

/// @param root The root (returned by the IDKit widget).
/// @param groupId The group ID 
/// @param signal An arbitrary input from the user, usually the user's wallet address
/// @param nullifierHash The nullifier hash for this proof, preventing double signaling (returned by the IDKit widget).
/// @param proof The zero-knowledge proof that demonstrates the claimer is registered with World ID (returned by the IDKit widget).
function verifyAndExecute(
    address signal,
    uint256 root,
    uint256 nullifierHash,
    uint256[8] calldata proof
) public {
    // First make sure this person hasn't done this before
    if (nullifierHashes[nullifierHash]) revert InvalidNullifier();

    // Now verify the provided proof is valid and the user is verified by World ID
    worldId.verifyProof(
        root,
        groupId,
        abi.encodePacked(signal).hashToField(),
        nullifierHash,
		externalNullifierHash,
        proof
    );

    // Record the user has done this, so they can't do it again (proof of uniqueness)
    nullifierHashes[nullifierHash] = true;

    // Finally, execute your logic here, for example issue a token, NFT, etc...
}