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
Chain | Testnet | Role | Identity Availability |
---|---|---|---|
Ethereum | Goerli | Canonical | ~3 Minutes |
Optimism | Optimism Goerli | Bridged | ~5 Minutes |
Polygon | Mumbai | Bridged | ~30 Minutes |
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 Goerli, Optimism Goerli, and Polygon Mumbai.
World ID Router
This is the contract you should interact with. It will route your call to the correct Identity Manager contract (Ethereum) or State Bridge contract (L2 Chains) based on the groupId
argument. This contract is proxied, so you should not need to update your code if we upgrade the underlying contracts.
Identity Managers
Identity Managers are only deployed on Ethereum. One contract is deployed for each credential type accepted in World ID, currently two are deployed: Orb and Phone.
The Identity Manager contracts are responsible for managing the Semaphore instance. Worldcoin's signup sequencers call the Identity Manager contracts to add identities to the merkle tree, and anyone can call the verifyProof
function to verify a World ID proof (although it's suggested to use the World ID Router).
State Bridges
On Ethereum, one State Bridge contract is deployed for each Identity Manager. It publishes the root of the merkle tree to other chains, allowing proofs to be verified on multiple chains.
On other supported chains (currently Optimism and Polygon), there is also one State Bridge contract for each credential type. These contracts receive the root of the merkle tree from the Ethereum State Bridge, and expose the verifyProof
function to verify proofs on that chain (using the World ID Router is recommended).
Address Book
Here you can find the address and associated ENS name (if available) for all of the World ID contracts. For verifying proofs, the only contract you need is the WorldIdRouter
contract -- it will properly route the call to the correct contract based on the groupId
argument.
Contract | Ethereum Mainnet | Ethereum Goerli Testnet |
---|---|---|
World ID Router | id.worldcoin.eth | goerli.id.worldcoin.eth |
Orb State Bridge | 0x86D26Ed31556EA7694BD0cC4e674D7526f70511a | 0xcd60da00e20be3d23e2e135283423fd86867c98d |
Orb Identity Manager | 0xf7134CE138832c1456F2a91D64621eE90c2bddEa | 0x711965c7805635315201d0963148DF4445505182 |
Phone State Bridge | unknown | unknown |
Phone Identity Manager | 0x3310846ee4250603e6ac6e4904e7e1667a1b248a | 0xAcd2F54cA0304Dc1870500fEEEa3e5c6Cd70c1FC |
verifyProof
The verifyProof
function is meant to be called on the WorldIdRouter
contract.
The verifyProof
method takes the following arguments:
root
- The World ID root to verify against. This is obtained from the IDKit widget, and should just be passed as-is.groupId
- This must be1
for Orb-verified users, and0
for Phone-verified users. You may pass this dynamically based on a user's verification status, or you may set it during contract deployment it if you only want to allow one type of verification.signal
- The signal to verify.nullifierHash
- Anonymous user ID. This is obtained from the IDKit widget, and should just be passed as-is.action
- The action to verify.proof
- The proof to verify. This is obtained from the IDKit widget, and should be unpacked into auint256[8]
before being passed to the method.
root
The root of the merkle tree to verify against. This is obtained from the IDKit widget as a hex string merkle_root
, and should be passed as-is.
groupId
The groupId, indicating to the World ID Router whether to verify against the merkle tree of Orb- or Phone-verified users.
Orb-verified users: 1
Phone-verified users: 0
We recommend setting this to 1
in your contract's constructor, to only ever allow Orb-verified users to perform the specified action. Additionally, this saves on gas costs.
However, if you wish to allow Orb- and Phone-verified users, IDKit returns a credential_type
field, which is either phone
or orb
. You can use this to determine the groupId
to use in your call to verifyProof
.
groupId
uint256 internal immutable groupId = 1;
{/* ... */}
worldId.verifyProof(
root,
groupId,
abi.encodePacked(signal).hashToField(),
nullifierHash,
externalNullifier,
proof
);
{/* ... */}
signalHash
The keccak256 hash of the signal to verify. To get signalHash, you should pass the solidityEncoded signal to your smart contract, and then compute the signalHash within the contract. Ensure that you solidityEncode the signal before passing it to IDKit.
We provide a helper function hashToField
to properly calculate the keccak256 hash within your smart contract.
signalHash
function yourFunction(
uint256 root,
address signal, // here we use an address as the signal
uint256 nullifierHash,
uint256[8] calldata proof
) public {
{/* ... */}
worldId.verifyProof(
root,
groupId,
// using hashToField helper function
abi.encodePacked(signal).hashToField(),
nullifierHash,
externalNullifier,
proof
);
{/* ... */}
nullifierHash
The root of the merkle tree to verify against. This is obtained from the IDKit widget as a hex string merkle_root
, and should be passed as-is.
externalNullifierHash
The root of the merkle tree to verify against. This is obtained from the IDKit widget as a hex string merkle_root
, and should be passed as-is.
proof
The proof
argument is returned from IDKit as a string, but depending how you're calling your smart contract (when using wagmi
or ethers.js
, for example), you might be required to unpack it into a uint256[8]
before passing it to the verifyProof
method. To unpack it, use the following code:
unpackedProof
import { decodeAbiParameters } from 'viem'
const unpackedProof = decodeAbiParameters([{ type: 'uint256[8]' }], proof)[0]