# Ecosystem Source: https://docs.world.org/agents/agent-kit/ecosystem Projects and services that integrate AgentKit. Find places to use AgentKit at [agentbook.world](https://agentbook.world/). To add your project, open a PR to the [AgentBook registry](https://github.com/andy-t-wang/agentbook). If you build an agent that calls x402 APIs, use [`createAgentkitClient`](/agents/agent-kit/sdk-reference#createagentkitclientoptions) and call `agentkit.fetch` so the agent tries AgentKit verification before paying. If you cannot change the agent's HTTP client, add the `agentkit-x402` skill: ```bash theme={"system"} npx skills add worldcoin/agentkit agentkit-x402 ``` Register your agent with World ID to enable human-backed agent benefits. Use `agentkit.fetch` before payment fallback. # Integrate AgentKit Source: https://docs.world.org/agents/agent-kit/integrate AgentKit Beta extends x402, allowing websites to distinguish human-backed agents from bots and scripts. AgentKit Beta extends x402 allowing websites to distinguish human-backed agents from bots and scripts. Enable agentic traffic to access api endpoints while blocking malicious actors, scalpers and spam. This quickstart follows a default implementation path: * Agent-side x402 calls use `agentkit.fetch` from `createAgentkitClient` * Accepts payments on both World Chain and Base * Agent registration on World Chain * AgentBook lookup always resolves on World Chain (caller side is chain-agnostic) * `free-trial` mode with 3 uses * Hono plus `@x402/hono` as the reference server example # Step 1: Install AgentKit ```bash theme={"system"} npm install @worldcoin/agentkit ``` # Step 2: Register the agent in AgentBook Register the wallet address your agent will sign with: ```bash theme={"system"} npx @worldcoin/agentkit-cli register ``` Check whether a wallet is already registered: ```bash theme={"system"} npx @worldcoin/agentkit-cli status ``` By default, the CLI registers on World Chain and submits through the hosted relay. AgentBook lookup always resolves against the canonical World Chain deployment. During registration, the CLI: 1. Looks up the next nonce for the agent address 2. Prompts the World App verification flow 3. Submits the registration transaction Once the wallet is registered, AgentKit can resolve it to an anonymous human identifier at request time.

*Example of the registration flow*

# Step 3: Wrap x402 calls in the agent Use this in agents that call paid x402 APIs. Without this client, agents may go straight to payment instead of using their AgentKit registration. ```typescript theme={"system"} import { createAgentkitClient } from '@worldcoin/agentkit' const agentkit = createAgentkitClient({ signer: { address: agentWallet.address, chainId: 'eip155:8453', type: 'eip191', signMessage: message => agentWallet.signMessage(message), }, }) const response = await agentkit.fetch('https://api.example.com/data') ``` Use `agentkit.fetch` anywhere the agent would otherwise call `fetch` for x402-protected APIs. It tries AgentKit first, then leaves the normal x402 payment fallback in place. If you cannot change the agent's HTTP client, add the fallback skill: ```bash theme={"system"} npx skills add worldcoin/agentkit agentkit-x402 ``` # Step 4: Wire the hooks-based server flow The example below shows the maintained Hono wrapper path. AgentKit itself is not Hono-only: Express and Next.js route handlers can use the same hooks and low-level helpers from the [SDK Reference](/agents/agent-kit/sdk-reference). ```typescript theme={"system"} import { Hono } from 'hono' import { serve } from '@hono/node-server' import { HTTPFacilitatorClient } from '@x402/core/http' import { ExactEvmScheme } from '@x402/evm/exact/server' import { paymentMiddlewareFromHTTPServer, x402HTTPResourceServer, x402ResourceServer, } from '@x402/hono' import { agentkitResourceServerExtension, createAgentBookVerifier, createAgentkitHooks, declareAgentkitExtension, InMemoryAgentKitStorage, } from '@worldcoin/agentkit' const WORLD_CHAIN = 'eip155:480' const BASE = "eip155:8453"; const WORLD_USDC = '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1' const payTo = '0xYourAddress' const facilitatorClient = new HTTPFacilitatorClient({ url: 'https://x402-worldchain.vercel.app/facilitator', }) const evmScheme = new ExactEvmScheme() // Register a money parser to accept USDC payments on WorldChain. .registerMoneyParser(async (amount, network) => { if (network !== WORLD_CHAIN) return null return { amount: String(Math.round(amount * 1e6)), asset: WORLD_USDC, extra: { name: 'USD Coin', version: '2' }, } }) const agentBook = createAgentBookVerifier() const storage = new InMemoryAgentKitStorage() const hooks = createAgentkitHooks({ agentBook, storage, mode: { type: 'free-trial', uses: 3 }, }) const resourceServer = new x402ResourceServer(facilitatorClient) .register(WORLD_CHAIN, evmScheme) .register(BASE, new ExactEvmScheme()) .registerExtension(agentkitResourceServerExtension) const routes = { 'GET /data': { // Accept payments on both World Chain and Base accepts: [ { scheme: 'exact', price: '$0.01', network: WORLD_CHAIN, payTo, }, { scheme: 'exact', price: '$0.01', network: BASE, payTo, }, ], extensions: declareAgentkitExtension({ statement: 'Verify your agent is backed by a real human', mode: { type: 'free-trial', uses: 3 }, }), }, } const httpServer = new x402HTTPResourceServer(resourceServer, routes) .onProtectedRequest(hooks.requestHook) const app = new Hono() app.use(paymentMiddlewareFromHTTPServer(httpServer)) app.get('/data', c => { return c.json({ message: 'Protected content' }) }) serve({ fetch: app.fetch, port: 4021 }) ``` This example accepts payments on both World Chain and Base. AgentBook lookup automatically resolves against the canonical World Chain deployment. # Step 5: Configure the default mode and storage This guide uses `free-trial` mode so registered human-backed agents get 3 free requests before the normal x402 payment flow resumes. `InMemoryAgentKitStorage` is fine for local testing, but production should persist both usage counters and nonces. ```typescript theme={"system"} import type { AgentKitStorage } from '@worldcoin/agentkit' class DatabaseAgentKitStorage implements AgentKitStorage { async tryIncrementUsage(endpoint: string, humanId: string, limit: number) { return db.tryIncrementUsage(endpoint, humanId, limit) } async hasUsedNonce(nonce: string) { return db.hasUsedNonce(nonce) } async recordNonce(nonce: string) { await db.recordNonce(nonce) } } const hooks = createAgentkitHooks({ agentBook, storage: new DatabaseAgentKitStorage(), mode: { type: 'free-trial', uses: 3 }, }) ``` Need `discount` mode, custom AgentBook deployments, or the low-level validation helpers? Continue to the [SDK Reference](/agents/agent-kit/sdk-reference). # SDK Reference Source: https://docs.world.org/agents/agent-kit/sdk-reference Reference for AgentKit modes, APIs, EVM signatures, and low-level helpers. Use this page when you need the full AgentKit surface area. For the shortest path to a working setup, start with [Integrate AgentKit](/agents/agent-kit/integrate). ## Access modes Usage counters are tracked per human per endpoint. Two agents backed by the same human share the same counter. | Mode | Fields | Behavior | | ------------ | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | | `free` | `{ type: "free" }` | Registered human-backed agents always bypass payment. | | `free-trial` | `{ type: "free-trial"; uses?: number }` | Registered human-backed agents bypass payment the first `N` times. Default `uses` is `1`. | | `discount` | `{ type: "discount"; percent: number; uses?: number }` | Registered human-backed agents can underpay by the configured percentage for the first `N` times. | `discount` mode requires `verifyFailureHook` to be registered on the facilitator. Without it, discounted underpayments fail settlement verification. ## Agent client APIs ### `createAgentkitClient(options)` Use this client in agents that call paid x402 APIs. `agentkit.fetch` inspects `402 Payment Required` responses and retries once with a signed `agentkit` header when the response advertises `extensions.agentkit`. ```typescript theme={"system"} import { createAgentkitClient } from '@worldcoin/agentkit' const agentkit = createAgentkitClient({ signer: { address: agentWallet.address, chainId: 'eip155:8453', type: 'eip191', signMessage: message => agentWallet.signMessage(message), }, }) ``` | Option | Type | Description | | --------- | ------------------------------------- | ------------------------------------------------------------------- | | `signer` | `AgentkitSigner` | Agent wallet identity and SIWE signing function. Required. | | `fetch` | `typeof fetch` | Optional base fetch implementation. Defaults to `globalThis.fetch`. | | `onEvent` | `(event: AgentkitFetchEvent) => void` | Optional callback for logging and debugging. | The client does not create x402 payments. It returns the original response unchanged when AgentKit is unavailable or cannot be used, so your existing x402 payment client can handle fallback. ### `AgentkitSigner` ```ts theme={"system"} type AgentkitSigner = { address: string chainId: string type: 'eip191' | 'eip1271' signMessage(message: string): Promise } ``` Use `eip191` for EOAs and `eip1271` for smart contract wallets. The returned client exposes: | Field | Type | Description | | -------------- | --------------------------------------------------- | --------------------------------------------------------------------------- | | `fetch` | `typeof fetch` | Fetch-compatible function that retries AgentKit-enabled 402 responses once. | | `createHeader` | `(extension: AgentkitExtension) => Promise` | Creates the base64 `agentkit` HTTP header for custom HTTP clients. | ## Core server APIs ### `declareAgentkitExtension(options?)` Use this helper in your x402 route config to declare the `agentkit` extension that should be returned in a `402 Payment Required` response. | Parameter | Type | Description | | ------------------- | -------------------- | ---------------------------------------------------------------------------------------------------- | | `domain` | `string` | Server hostname. Usually auto-derived from the request URL. | | `resourceUri` | `string` | Full protected resource URI. Usually auto-derived from the request URL. | | `network` | `string \| string[]` | CAIP-2 network or list of networks. If omitted, the extension derives them from `accepts[].network`. | | `statement` | `string` | Human-readable signing purpose. | | `version` | `string` | CAIP-122 version. Defaults to `"1"`. | | `expirationSeconds` | `number` | Challenge lifetime in seconds. | | `mode` | `AgentkitMode` | Access mode clients should expect after verification. | Returns a record keyed by `agentkit` that can be attached directly to an x402 route declaration. ### `agentkitResourceServerExtension` Register this extension once on your x402 resource server. It turns the declaration returned by `declareAgentkitExtension(...)` into a full 402 challenge by: * generating the nonce and timestamps * inferring `domain` and `resourceUri` from the incoming request when you omit them * expanding each supported EVM network into `eip191` and `eip1271` signature types ### `createAgentkitHooks(options)` Creates the request-time verification hooks used by the golden path integration. | Option | Type | Description | | ----------- | ------------------------------------ | -------------------------------------------------------------------------- | | `agentBook` | `AgentBookVerifier` | Verifier used to resolve the agent wallet to a human identifier. Required. | | `mode` | `AgentkitMode` | Access mode. Defaults to `{ type: "free" }`. | | `storage` | `AgentKitStorage` | Required for `free-trial` and `discount`. Optional for `free`. | | `rpcUrl` | `string` | Custom EVM RPC used during signature verification. | | `onEvent` | `(event: AgentkitHookEvent) => void` | Optional logging/debug callback. | Returns: | Field | Type | Description | | ------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------- | | `requestHook` | `function` | Runs before payment settlement and can grant access for `free` or `free-trial`. | | `verifyFailureHook` | `function \| undefined` | Present only for `discount` mode. Register it on the facilitator so discounted underpayments can be recovered. | `requestHook` expects a context shaped like: ```ts theme={"system"} { adapter: { getHeader(name: string): string | undefined getUrl(): string } path: string } ``` That is why Express and Next.js are compatible even though the docs use Hono for the concrete example: you can adapt any server framework to this minimal contract. ### `AgentkitHookEvent` `onEvent` receives one of these event shapes: | Event type | Fields | | -------------------- | -------------------------------- | | `agent_verified` | `resource`, `address`, `humanId` | | `agent_not_verified` | `resource`, `address` | | `validation_failed` | `resource`, `error?` | | `discount_applied` | `resource`, `address`, `humanId` | | `discount_exhausted` | `resource`, `address`, `humanId` | ## AgentBook lookup ### `createAgentBookVerifier(options?)` Creates the verifier used to resolve a wallet address to an anonymous human identifier. Lookup **always** resolves against the canonical AgentBook deployment on World Chain (`eip155:480`). Canonical deployment: * World Chain mainnet: `0xA23aB2712eA7BBa896930544C7d6636a96b944dA` | Option | Type | Description | | ----------------- | ------------------- | ----------------------------------------------------------------------------------------------------------- | | `rpcUrl` | `string` | Custom World Chain RPC URL. Defaults to the chain's default public RPC. Ignored if `client` is provided. | | `contractAddress` | `` `0x${string}` `` | Custom AgentBook contract address on World Chain. Defaults to the canonical deployment. | | `client` | `PublicClient` | Advanced override. Inject a fully custom viem public client (useful for tests or non-standard deployments). | In the common case call it with no arguments: ```typescript theme={"system"} const agentBook = createAgentBookVerifier() ``` The returned object exposes: ```ts theme={"system"} lookupHuman(address: string): Promise ``` ## Storage and replay protection ### `AgentKitStorage` `AgentKitStorage` is the persistence interface used for free-trial counters, discount counters, and optional nonce replay protection. | Method | Description | | --------------------------------------------- | --------------------------------------------------------------------------------------- | | `tryIncrementUsage(endpoint, humanId, limit)` | Atomically increment usage if below the limit. Returns `true` when the use is recorded. | | `hasUsedNonce?(nonce)` | Optional replay check. Return `true` if the nonce has already been seen. | | `recordNonce?(nonce)` | Optional replay recorder. Persist the nonce after validation succeeds. | ### `InMemoryAgentKitStorage` `InMemoryAgentKitStorage` is the reference implementation exported by the package. * Good for local development, demos, and tests * Not appropriate for production because usage counters and nonce history disappear on restart ## Validation and verification helpers ### `parseAgentkitHeader(header)` Parses the base64-encoded `agentkit` header into a structured payload. It throws if the header is not valid base64, is not valid JSON, or does not match the expected schema. ### `validateAgentkitMessage(payload, resourceUri, options?)` Validates message binding, freshness, and optional replay checks. | Option | Type | Description | | ------------ | ------------------------------------------------ | ------------------------------------------------------------------ | | `maxAge` | `number` | Maximum age for `issuedAt` in milliseconds. Defaults to 5 minutes. | | `checkNonce` | `(nonce: string) => boolean \| Promise` | Optional replay validation hook. | Returns: ```ts theme={"system"} { valid: boolean; error?: string } ``` ### `verifyAgentkitSignature(payload, rpcUrl?)` Verifies the cryptographic signature and returns the recovered address on success. | Option | Type | Description | | -------- | -------- | -------------------------------------------------- | | `rpcUrl` | `string` | Optional custom RPC endpoint for EVM verification. | Behavior: * `eip155:*` payloads are reconstructed into a SIWE message and verified with viem * unsupported chain namespaces return `{ valid: false, error: ... }` Returns: ```ts theme={"system"} { valid: boolean; address?: string; error?: string } ``` ### `buildAgentkitSchema()` Returns the JSON schema used in 402 challenge payloads. ## Chain utilities ### EVM | Export | Description | | -------------------- | ------------------------------------------------------------------- | | `formatSIWEMessage` | Reconstruct the SIWE message used for EVM signing and verification. | | `verifyEVMSignature` | Verify an EVM signature for the reconstructed SIWE message. | | `extractEVMChainId` | Convert a CAIP-2 `eip155:*` chain ID to its numeric chain ID. | EVM verification uses viem's `verifyMessage`, which covers EOAs and ERC-1271 smart wallets. Counterfactual wallets can still represent their signature scheme with `signatureScheme: "eip6492"` in the payload schema. ## Supported chains and signature behavior * Chain namespace: `eip155:*` * Payload `type`: `eip191` or `eip1271` * Optional `signatureScheme`: `eip191`, `eip1271`, or `eip6492` * Message format: SIWE ## Manual usage example Use the low-level helpers directly when you are not using the x402 Hono wrapper or when you want full control over request handling: ```typescript theme={"system"} import { AGENTKIT, createAgentBookVerifier, declareAgentkitExtension, parseAgentkitHeader, validateAgentkitMessage, verifyAgentkitSignature, } from '@worldcoin/agentkit' const extensions = declareAgentkitExtension({ domain: 'api.example.com', resourceUri: 'https://api.example.com/data', network: 'eip155:480', statement: 'Verify your agent is backed by a real human', }) const agentBook = createAgentBookVerifier() async function handleRequest(request: Request) { const header = request.headers.get(AGENTKIT) if (!header) return const payload = parseAgentkitHeader(header) const validation = await validateAgentkitMessage(payload, 'https://api.example.com/data') if (!validation.valid) { return { error: validation.error } } const verification = await verifyAgentkitSignature(payload) if (!verification.valid || !verification.address) { return { error: verification.error } } const humanId = await agentBook.lookupHuman(verification.address) if (!humanId) { return { error: 'Agent is not registered in the AgentBook' } } return { humanId } } ``` ## Production notes * Treat `InMemoryAgentKitStorage` as a demo-only implementation. * If you need limited free uses, persistent storage is part of the integration, not an optional enhancement. * If you need `discount` mode, wire `verifyFailureHook` into the facilitator before you ship. * Use Hono as a reference example, not as a framework restriction. The package surface is generic enough to be adapted to Express or Next.js handlers. # [Sold Out] Claim Your Free Hat Source: https://docs.world.org/agents/hats/index Prove you're human-backed with AgentKit and claim an exclusive free hat. All hats have been claimed. Thanks for the incredible response — stay tuned for future drops. The [Human Required](https://humanrequired.shop/) store is a Shopify store demo that only sells to agents verified as human-backed through [AgentKit](/agents/agent-kit/integrate). Agents registered in [AgentBook](https://agentbook.world/) can unlock a 100% discount and claim the hat for free. Discount codes are unique per human — each person can generate one. Human in the Loop Hat Install the AgentKit Shopify plugin and let your agent handle the rest: ```bash Claude Code theme={"system"} /plugin marketplace add worldcoin/agentkit-shopify-demo /plugin install agentkit-shopify@worldcoin-agentkit /reload-plugins # Then ask Claude: # "Help me purchase this product: https://humanrequired.shop/products/human-in-the-loop-hat" ``` ```bash Manual theme={"system"} curl -LsSf https://astral.sh/uv/install.sh | sh git clone https://github.com/worldcoin/agentkit-shopify-demo.git cd agentkit-shopify-demo # Generate an agent key uv run --with eth-account python3 -c \ "from eth_account import Account; a = Account.create(); print(a.key.hex())" > .agent-key # Register in AgentBook (opens QR for World ID verification) npx --registry https://registry.npmjs.org @worldcoin/agentkit-cli register # Get the discount code PRIVATE_KEY=$(cat .agent-key) ./skills/shopify-agent-discount/get-coupon.py \ https://humanrequired.shop/products/human-in-the-loop-hat ``` # Integrate Source: https://docs.world.org/agents/human-in-the-loop/integrate Add human approval workflows to AI agents using World ID.

*AI agent pauses for World ID approval before booking a flight*

Human-in-the-loop lets an AI agent **pause mid-execution** and wait for a real, verified human to approve an action before continuing. Every approval is cryptographically bound to the action via [World ID](/world-id/overview) — no bots, no spoofing, no replay. Built on the [Workflow SDK](https://useworkflow.dev) and the [Vercel AI SDK](https://ai-sdk.dev). ## Install ```bash theme={"system"} # Server — human-in-the-loop + peer dependencies npm install @worldcoin/human-in-the-loop ai workflow # Client — React bindings + peer dependencies npm install @worldcoin/human-in-the-loop-react @worldcoin/idkit ai react ``` ## Environment variables ```bash theme={"system"} # Server — used by @worldcoin/human-in-the-loop WORLD_RP_ID=your_rp_id WORLD_SIGNING_KEY=your_signing_key # Client — used by the component (optional if passing appId prop) NEXT_PUBLIC_WORLD_APP_ID=app_... ``` Get these from the [World developer portal](https://developer.world.org) by creating an app. ## Step 1: Define the workflow ```ts theme={"system"} // src/workflows/chat/index.ts import { DurableAgent } from 'workflow/ai' import { getWritable } from 'workflow' import { openai } from '@workflow/ai/openai' import { tools } from './steps/tools' export async function chatWorkflow(messages: ModelMessage[]) { // Durable workflow — can pause for hours/days and resume where it left off 'use workflow' const writable = getWritable() const agent = new DurableAgent({ model: openai('gpt-5.4'), tools, system: 'You are a helpful assistant. Before performing any sensitive action, use the approveAction tool.', }) await agent.stream({ messages, writable }) } ``` ## Step 2: Register the approval tool ```ts theme={"system"} // src/workflows/chat/steps/tools.ts import { requestHumanAuthorization } from '@worldcoin/human-in-the-loop/workflows' import { z } from 'zod' export const tools = { approveAction: { description: 'Request human approval via World ID before a sensitive action.', inputSchema: z.object({ summary: z.string() }), // Pauses the workflow, streams approval context to the client, // waits for World ID proof, verifies it, then resumes. // Action defaults to toolCallId; pass a function to bind to input fields: // action: ({ input }) => `booking:${input.flightNumber}` execute: requestHumanAuthorization(), }, // ...your other tools } ``` ## Step 3: Render the approval on the client This example uses the `` component, if you want to customize the UI you can use the `useHumanApproval` hook instead. ```tsx HumanApproval component theme={"system"} import { HumanApproval } from '@worldcoin/human-in-the-loop-react' // Match on the tool name from Step 2. renders the World ID // widget and POSTs the proof back to the server automatically. {message.parts.map(part => { if (part.type === 'tool-approveAction' && 'toolCallId' in part) { return ( ) } // ...your other part renderers })} ``` ```tsx useHumanApproval hook theme={"system"} import { useHumanApproval } from '@worldcoin/human-in-the-loop-react' import { IDKitRequestWidget, orbLegacy } from '@worldcoin/idkit' import { useState } from 'react' function MyApproval({ message, part }) { const [open, setOpen] = useState(false) // ready: true once the server streams the approval context // verify: POSTs the World ID proof to the server webhook const { ready, action, rpContext, state, verify } = useHumanApproval(message, part) if (state.status === 'verified') return

Approved.

return ( <> {state.status === 'error' &&

{state.error.message}

} {ready && ( {}} handleVerify={verify} app_id={process.env.NEXT_PUBLIC_WORLD_APP_ID as `app_${string}`} action={action!} rp_context={rpContext!} preset={orbLegacy()} allow_legacy_proofs={false} /> )} ) } ```
### Flight booking example Check out [Flight booking example](https://github.com/worldcoin/human-in-the-loop/tree/main/examples/flight-booking) for a complete implementation of a human-in-the-loop workflow with World ID approval. # SDK Reference Source: https://docs.world.org/agents/human-in-the-loop/sdk-reference API reference for @worldcoin/human-in-the-loop and @worldcoin/human-in-the-loop-react. ## Server — `@worldcoin/human-in-the-loop` ### `requestHumanAuthorization(options?)` Import from `@worldcoin/human-in-the-loop/workflows`. Returns a tool `execute` function that pauses the workflow until a World ID proof is received. Returns `Promise`. | Option | Type | Default | Description | | ------------ | ----------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------- | | `action` | `string \| ((ctx) => string)` | `toolCallId` | Unique action string bound to this verification. When a function, receives `{ toolCallId, input }`. | | `signingKey` | `string` | `WORLD_SIGNING_KEY` env | Hex-encoded RP signing key. | | `rpId` | `string` | `WORLD_RP_ID` env | Relying-party ID for verification. | *** ## Client — `@worldcoin/human-in-the-loop-react` ### `` Drop-in component that renders the World ID widget and posts the proof back automatically. Ships unstyled. | Prop | Type | Default | Description | | ------------------- | --------------------- | ------------------------------ | ----------------------------------------------- | | `message` | `UIMessage` | required | The message containing the tool part. | | `part` | `UIMessagePart` | required | The tool-call part (must include `toolCallId`). | | `appId` | `` `app_${string}` `` | `NEXT_PUBLIC_WORLD_APP_ID` env | World app ID. | | `preset` | `Preset` | `orbLegacy()` | IDKit verification preset. | | `allowLegacyProofs` | `boolean` | `false` | Accept v3 World ID proofs. | | `triggerLabel` | `ReactNode` | `'Verify with World ID'` | Button label. | | `successContent` | `ReactNode` | `'Approved with World ID.'` | Shown after verification. | | `className` | `string` | — | CSS class for the outer wrapper. | ### `useHumanApproval(message, part)` Headless hook for custom approval UI. | Field | Type | Description | | ------------ | --------------------------------------- | -------------------------------------------------------------- | | `ready` | `boolean` | `true` once the approval context has streamed from the server. | | `action` | `string \| undefined` | Action string. Pass to `IDKitRequestWidget`. | | `rpContext` | `RpContext \| undefined` | Pass to `IDKitRequestWidget`'s `rp_context`. | | `webhookUrl` | `string \| undefined` | Webhook URL. Handled by `verify` automatically. | | `state` | `HumanApprovalState` | `'idle' \| 'verifying' \| 'verified' \| 'error'` | | `verify` | `(proof: IDKitResult) => Promise` | Wire to `IDKitRequestWidget.handleVerify`. | ```ts theme={"system"} type HumanApprovalState = | { status: 'idle' } | { status: 'verifying' } | { status: 'verified' } | { status: 'error'; error: Error } ``` # Get Prices Source: https://docs.world.org/api-reference/developer-portal/get-prices /openapi/developer-portal.json get /public/v1/miniapps/prices Query latest prices of tokens in various fiat currencies. # Get Transaction Source: https://docs.world.org/api-reference/developer-portal/get-transaction /openapi/developer-portal.json get /api/v2/minikit/transaction/{transaction_id} Query transactions for their current status. # Get Transaction Debug URL Source: https://docs.world.org/api-reference/developer-portal/get-transaction-debug-url /openapi/developer-portal.json get /api/v2/minikit/transaction/debug Debug transactions that failed during the prepare stage. Returns Tenderly URLs when applicable. # Get User Grant Cycle Source: https://docs.world.org/api-reference/developer-portal/get-user-grant-cycle /openapi/developer-portal.json get /api/v2/minikit/user-grant-cycle Retrieve the next grant claim date for a user of your mini app. Returns the user's humanity (orb-verified) grant cycle date if available, otherwise falls back to their document (passport-verified) grant cycle date. # Get User Operation Source: https://docs.world.org/api-reference/developer-portal/get-user-operation /openapi/developer-portal.json get /api/v2/minikit/userop/{user_op_hash} Query a MiniKit user operation by `userOpHash` and resolve the final on-chain transaction hash when it becomes available. # Send Notification Source: https://docs.world.org/api-reference/developer-portal/send-notification /openapi/developer-portal.json post /api/v2/minikit/send-notification Send notifications to users of your mini app. # Verify Source: https://docs.world.org/api-reference/developer-portal/verify /openapi/developer-portal.json post /api/v4/verify/{rp_id} Verifies World ID 4.0 proofs and legacy 3.0 proofs. Forward the complete IDKit result without remapping response identifiers or constructing a legacy `verification_level`. Use `rp_id` (`rp_...`) when possible; `app_id` (`app_...`) is still accepted for backward compatibility. # Verify (Legacy) Source: https://docs.world.org/api-reference/developer-portal/verify-legacy /openapi/developer-portal.json post /api/v2/verify/{app_id} Verify a World ID proof for a Cloud action. # Get is valid root Source: https://docs.world.org/api-reference/gateway/get-is-valid-root https://gateway.id-infra.worldcoin.dev/openapi.json get /is-valid-root # Get status Source: https://docs.world.org/api-reference/gateway/get-status https://gateway.id-infra.worldcoin.dev/openapi.json get /status/{id} # Post create account Source: https://docs.world.org/api-reference/gateway/post-create-account https://gateway.id-infra.worldcoin.dev/openapi.json post /create-account # Post insert authenticator Source: https://docs.world.org/api-reference/gateway/post-insert-authenticator https://gateway.id-infra.worldcoin.dev/openapi.json post /insert-authenticator # Post recover account Source: https://docs.world.org/api-reference/gateway/post-recover-account https://gateway.id-infra.worldcoin.dev/openapi.json post /recover-account # Post remove authenticator Source: https://docs.world.org/api-reference/gateway/post-remove-authenticator https://gateway.id-infra.worldcoin.dev/openapi.json post /remove-authenticator # Post update authenticator Source: https://docs.world.org/api-reference/gateway/post-update-authenticator https://gateway.id-infra.worldcoin.dev/openapi.json post /update-authenticator # Get Authenticator Pubkeys Source: https://docs.world.org/api-reference/indexer/get-authenticator-pubkeys https://indexer.us.id-infra.worldcoin.dev/openapi.json post /authenticator-pubkeys Returns the compressed authenticator public keys for a given World ID by leaf index. Removed authenticator slots are returned as `null` to preserve `pubkey_id` positions. # Get Inclusion Proof Source: https://docs.world.org/api-reference/indexer/get-inclusion-proof https://indexer.us.id-infra.worldcoin.dev/openapi.json post /inclusion-proof Returns a Merkle inclusion proof for the given leaf index to the current `WorldIDRegistry` tree. In addition, it also includes the entire authenticator slot list for the World ID account. Removed authenticators are represented as `null` entries to preserve `pubkey_id` positions. # Get Packed Account Data Source: https://docs.world.org/api-reference/indexer/get-packed-account-data https://indexer.us.id-infra.worldcoin.dev/openapi.json post /packed-account Returns the packed account data for a given authenticator address from the `WorldIDRegistry` contract. # Get Signature Nonce Source: https://docs.world.org/api-reference/indexer/get-signature-nonce https://indexer.us.id-infra.worldcoin.dev/openapi.json post /signature-nonce Returns the current signature nonce for a given World ID based on its leaf index. The nonce is used to perform on-chain operations for the World ID. # World Developer Docs Source: https://docs.world.org/index Build Mini Apps, integrate World ID, and deploy on World Chain with official developer documentation.

World Developer Docs

Integrate World ID and deploy on World Chain with official developer documentation.
World Tech Updates
World Chain launches full block access lists

World Chain now streams full EIP-7928 block access lists in every flashblock, enabling parallel block verification, faster validation, and a path toward gigagas-per-second throughput without increasing validator hardware requirements.

Remainder: World's GKR prover for ML and more

Today we announce the open-sourcing of Remainder (Reasonable Machine Learning Doubly-Efficient Prover), Tools for Humanity's in-house GKR + Hyrax proof system. Remainder enables World's users to run ML models locally over private data and prove that they executed them correctly.

Introducing World ID 4.0 - Request for Comments

The new World ID 4.0 upgrade introduces account abstraction with multi-key support, transforming a World ID from a single secret into an abstract record in a public registry. This increases protocol resilience by allowing the introduction of multiple key support, portability, recovery and improved privacy.

Go to World Tech
# Attestation Source: https://docs.world.org/mini-apps/commands/attestation Request an attestation token using the unified MiniKit API. Use `MiniKit.attestation()` to request an app attestation token for a request hash. ## Basic Usage ```tsx title="Example" theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import type { CommandResultByVia, MiniAppAttestationSuccessPayload, MiniKitAttestationOptions, } from "@worldcoin/minikit-js/commands"; export async function requestAttestation() { const input = { requestHash: "0x1234", } satisfies MiniKitAttestationOptions; const result: CommandResultByVia< MiniAppAttestationSuccessPayload, MiniAppAttestationSuccessPayload, "minikit" > = await MiniKit.attestation(input); console.log(result.data.token); } ``` ```ts title="Type" theme={"system"} type MiniKitAttestationOptions = { requestHash: string; fallback?: () => unknown; }; ``` ## Result ```ts title="Type" theme={"system"} type AttestationResponse = | { executedWith: "minikit"; data: { status: "success"; version: number; token: string; }; } | { executedWith: "fallback"; data: unknown; }; ``` ```json title="Example" theme={"system"} { "executedWith": "minikit", "data": { "status": "success", "version": 1, "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.mock.payload" } } ``` ## Fallback Behavior Define a custom fallback in the command payload for support outside mini apps. ## Error Codes | Code | Meaning | | --------------------- | ------------------------------------ | | `unauthorized` | The request is not authorized | | `attestation_failed` | Attestation generation failed | | `integrity_failed` | Integrity checks failed | | `invalid_input` | The request hash is invalid | | `unsupported_version` | The command version is not supported | # World Chat Source: https://docs.world.org/mini-apps/commands/chat Share a message through World Chat using the unified MiniKit API. Use `MiniKit.chat()` to open World Chat with a prefilled message. ## Basic Usage ```tsx title="Example" theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import type { CommandResultByVia, MiniAppChatSuccessPayload, MiniKitChatOptions, } from "@worldcoin/minikit-js/commands"; export async function shareToChat() { const input = { message: "Check out this mini app", to: ["andy"], } satisfies MiniKitChatOptions; const result: CommandResultByVia< MiniAppChatSuccessPayload, MiniAppChatSuccessPayload, "minikit" > = await MiniKit.chat(input); console.log(result.data.count); } ``` ```ts title="Type" theme={"system"} type MiniKitChatOptions = { message: string; to?: string[]; fallback?: () => unknown; }; ``` ## Result ```ts title="Type" theme={"system"} type ChatResponse = | { executedWith: "minikit"; data: { status: "success"; version: number; count: number; timestamp: string; }; } | { executedWith: "fallback"; data: unknown; }; ``` ```json title="Example" theme={"system"} { "executedWith": "minikit", "data": { "status": "success", "version": 1, "count": 2, "timestamp": "2026-03-28T18:24:00.000Z" } } ``` ## Fallback Behavior Define a custom fallback in the command payload for support outside mini apps. ## Error Codes | Code | Meaning | | --------------- | ----------------------------- | | `user_rejected` | The user rejected the request | | `send_failed` | Sending the message failed | | `generic_error` | Unexpected failure | ## Preview
# Close Mini App Source: https://docs.world.org/mini-apps/commands/close-miniapp Programmatically close the mini app using the unified MiniKit API. Use `MiniKit.closeMiniApp()` to programmatically close the mini app. ## Basic Usage ```tsx title="Example" theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import type { CommandResultByVia, CloseMiniAppResult, MiniKitCloseMiniAppOptions, } from "@worldcoin/minikit-js/commands"; export async function closeMiniApp() { const input = {} satisfies MiniKitCloseMiniAppOptions; const result: CommandResultByVia< CloseMiniAppResult, CloseMiniAppResult, "minikit" > = await MiniKit.closeMiniApp(input); return result.data.status; } ``` ```ts title="Type" theme={"system"} type MiniKitCloseMiniAppOptions = { fallback?: () => unknown; }; ``` ## Result ```ts title="Type" theme={"system"} type CloseMiniAppResponse = | { executedWith: "minikit"; data: { status: "success"; version: number; }; } | { executedWith: "fallback"; data: unknown; }; ``` ```json title="Example" theme={"system"} { "executedWith": "minikit", "data": { "status": "success", "version": 1 } } ``` ## Fallback Behavior Define a custom fallback in the command payload for support outside mini apps. # Get Permissions Source: https://docs.world.org/mini-apps/commands/get-permissions Read current mini app permission settings using the unified MiniKit API. Use `MiniKit.getPermissions()` to read the current permission state for the mini app. ## Basic Usage ```tsx title="Example" theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import type { CommandResultByVia, MiniAppGetPermissionsSuccessPayload, MiniKitGetPermissionsOptions, } from "@worldcoin/minikit-js/commands"; export async function getPermissions() { const input = {} satisfies MiniKitGetPermissionsOptions; const result: CommandResultByVia< MiniAppGetPermissionsSuccessPayload, MiniAppGetPermissionsSuccessPayload, "minikit" > = await MiniKit.getPermissions(input); console.log(result.data.permissions); } ``` ```ts title="Type" theme={"system"} type MiniKitGetPermissionsOptions = { fallback?: () => unknown; }; ``` ## Result ```ts title="Type" theme={"system"} type GetPermissionsResponse = | { executedWith: "minikit"; data: { status: "success"; version: number; permissions: { notifications?: any; contacts?: any; microphone?: any; }; timestamp: string; }; } | { executedWith: "fallback"; data: unknown; }; ``` ```json title="Example" theme={"system"} { "executedWith": "minikit", "data": { "status": "success", "version": 1, "permissions": { "notifications": true, "contacts": false, "microphone": true }, "timestamp": "2026-03-28T18:24:00.000Z" } } ``` ## Fallback Behavior Define a custom fallback in the command payload for support outside mini apps. ## Error Codes | Code | Meaning | | --------------- | ------------------ | | `generic_error` | Unexpected failure | # Send Notifications Source: https://docs.world.org/mini-apps/commands/how-to-send-notifications Please take a minute to read the [Features & Guidelines](/mini-apps/guidelines/features-and-guidelines). It's important to follow these, otherwise we may disable your ability to send notifications. To send notifications to users you need to: * Request permission in the Developer Portal Advanced settings, for your mini app, * Request permission to send notifications from the user, via MiniKit (see [Request Permission](/mini-apps/commands/request-permission)), * Actually send the notification using our API or the Developer Portal. ## Calling the send-notification endpoint The API endpoint provides capabilities for sending notifications programmatically. These notifications can be localized, which ensures you reach users in their preferred language. This results in a dramatically higher engagement rate. ### Localization made simple ```javascript theme={"system"} const response = await fetch( "https://developer.worldcoin.org/api/v2/minikit/send-notification", { method: "POST", headers: { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ app_id: "your_app_id", wallet_addresses: ["0x123...", "0x456..."], localisations: [ { language: "en", title: "🎉 Rewards Available", message: "Hey ${username}, your daily rewards are ready!", }, { language: "es", title: "🎉 Recompensas Disponibles", message: "Hola ${username}, tus recompensas diarias están listas!", }, { language: "fr", title: "🎉 Récompenses Disponibles", message: "Salut ${username}, vos récompenses quotidiennes sont prêtes!", }, ], mini_app_path: "worldapp://mini-app?app_id=your_app_id&path=/rewards", }), } ); ``` Each user automatically receives the notification in their preferred language. If their language isn't included in your localizations, you'll receive a specific `reason` in the response. For complete API documentation including all supported languages and response formats, see the [API Reference](/api-reference/developer-portal/send-notification). ## Manually sending notifications from Developer Portal This form doesn't support localized notifications yet. To take advantage of localizations, use the API. You can send notifications to multiple wallet addresses (up to 1000) directly from the Developer Portal.
Notification Interface in the Developer Portal
Use the form to input addresses, and content details. Once you click the `Send` button and get a success response, your notifications will be queued for delivery. ## Testing We currently have a limit of 40 notifications per 4 hours for unverified apps. This is to help you test your notification implementations. Currently you will need to create a new app if your app is verified as it will default to the verified app's metadata. In addition, you need to enable notifications for your mini app inside of World App to receive them. ## Useful links * [Features & Guidelines](/mini-apps/guidelines/features-and-guidelines) * [How To Request Notification Permissions](/mini-apps/commands/request-permission) * [How To Get Notification Permissions](/mini-apps/commands/get-permissions) * [Send Notification API Reference](/api-reference/developer-portal/send-notification) # Pay Source: https://docs.world.org/mini-apps/commands/pay Request a payment from the user This command is an abstraction for a simple transfer. This shouldn't be used outside of World App. Pay supports WLD and all local stablecoins. ## Basic Usage ```tsx title="Example" theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import { Tokens, tokenToDecimals, type CommandResultByVia, type MiniKitPayOptions, type PayResult, } from "@worldcoin/minikit-js/commands"; export async function sendPayment() { // Create a nonce in the backend to use as a reference for this payment. const response = await fetch("/api/generate-nonce", { method: "POST" }); const { id } = await response.json(); const input = { reference: id, to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", tokens: [ { symbol: Tokens.WLD, token_amount: tokenToDecimals(1, Tokens.WLD).toString(), }, ], description: "Example payment", fallback: () => { alert("Please complete the payment in World App to proceed."); }, }; const result: CommandResultByVia = await MiniKit.pay(input); await fetch("/api/confirm-payment", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(result.data), }); } ``` ```ts title="Type" theme={"system"} type MiniKitPayOptions = { reference: string; to: string; tokens: { symbol: Tokens; token_amount: string; }[]; description: string; fallback?: () => unknown; }; ``` ## Result ```ts title="Type" theme={"system"} type PayResponse = | { executedWith: "minikit"; data: { transactionId: string; reference: string; from: string; chain: "worldchain"; timestamp: string; }; } | { executedWith: "fallback"; data: unknown; }; ``` ```json title="Example" theme={"system"} { "executedWith": "minikit", "data": { "transactionId": "tx_1234567890abcdef", "reference": "order-123", "from": "0x1234567890123456789012345678901234567890", "chain": "worldchain", "timestamp": "2026-03-28T18:24:00.000Z" } } ``` ## Backend Verification Always verify the payment on your backend before treating it as final. ```ts theme={"system"} import { NextRequest, NextResponse } from "next/server"; import type { PayResult } from "@worldcoin/minikit-js/commands"; type RequestBody = { payload: PayResult; }; export async function POST(req: NextRequest) { const { payload } = (await req.json()) as RequestBody; const response = await fetch( `https://developer.worldcoin.org/api/v2/minikit/transaction/${payload.transactionId}?app_id=${process.env.APP_ID}&type=payment`, { method: "GET", headers: { Authorization: `Bearer ${process.env.DEV_PORTAL_API_KEY}`, }, }, ); const transaction = await response.json(); return NextResponse.json(transaction); } ``` ## Error Codes | Code | Meaning | | ---------------------- | ----------------------------------------------- | | `input_error` | The payment payload is invalid | | `user_rejected` | The user rejected the request | | `payment_rejected` | The user cancelled the payment | | `invalid_receiver` | The recipient address is invalid or not allowed | | `insufficient_balance` | The user does not have enough balance | | `transaction_failed` | The payment failed on-chain | | `generic_error` | Unexpected failure | | `user_blocked` | Payments are not available in the user's region | ## Fallback Behavior Define a custom fallback in the command payload for support outside mini apps. ## Preview
Pay command demo
# Request Permission Source: https://docs.world.org/mini-apps/commands/request-permission Request a mini app permission using the unified MiniKit API. Use `MiniKit.requestPermission()` to ask the user for a permission such as notifications or microphone access. ## Basic Usage ```tsx title="Example" theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import { Permission, type CommandResultByVia, type MiniAppRequestPermissionSuccessPayload, type MiniKitRequestPermissionOptions, } from "@worldcoin/minikit-js/commands"; export async function requestNotifications() { const input = { permission: Permission.Notifications, } satisfies MiniKitRequestPermissionOptions; const result: CommandResultByVia< MiniAppRequestPermissionSuccessPayload, MiniAppRequestPermissionSuccessPayload, "minikit" > = await MiniKit.requestPermission(input); console.log(result.data.permission); } ``` ```ts title="Type" theme={"system"} type MiniKitRequestPermissionOptions = { permission: Permission; fallback?: () => unknown; }; ``` ## Result ```ts title="Type" theme={"system"} type RequestPermissionResponse = | { executedWith: "minikit"; data: { status: "success"; version: number; permission: "notifications" | "contacts" | "microphone"; timestamp: string; }; } | { executedWith: "fallback"; data: unknown; }; ``` ```json title="Example" theme={"system"} { "executedWith": "minikit", "data": { "status": "success", "version": 1, "permission": "notifications", "timestamp": "2026-03-28T18:24:00.000Z" } } ``` ## Fallback Behavior Define a custom fallback in the command payload for support outside mini apps. ## Error Codes | Code | Meaning | | ------------------------ | ---------------------------------------------------- | | `user_rejected` | The user rejected the request | | `generic_error` | Unexpected failure | | `already_requested` | The permission prompt was already shown and rejected | | `permission_disabled` | The permission is disabled | | `already_granted` | The permission is already granted | | `unsupported_permission` | The permission is not supported | ## Preview
# Send Haptic Feedback Source: https://docs.world.org/mini-apps/commands/send-haptic-feedback Trigger haptic feedback using the unified MiniKit API. Use `MiniKit.sendHapticFeedback()` to trigger native haptic feedback. ## Haptic Types * **`"notification"`** — style: `"success"`, `"warning"`, or `"error"` * **`"impact"`** — style: `"light"`, `"medium"`, or `"heavy"` * **`"selection-changed"`** — no style needed ## Basic Usage ```tsx title="Impact" theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import type { MiniKitSendHapticFeedbackOptions } from "@worldcoin/minikit-js/commands"; export async function sendImpactHaptic() { await MiniKit.sendHapticFeedback({ hapticsType: "impact", style: "medium", } satisfies MiniKitSendHapticFeedbackOptions); } ``` ```tsx title="Selection Changed" theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import type { MiniKitSendHapticFeedbackOptions } from "@worldcoin/minikit-js/commands"; export async function sendSelectionHaptic() { await MiniKit.sendHapticFeedback({ hapticsType: "selection-changed", } satisfies MiniKitSendHapticFeedbackOptions); } ``` ```ts title="Type" theme={"system"} type MiniKitSendHapticFeedbackOptions = | { hapticsType: "notification"; style: "error" | "success" | "warning"; fallback?: () => unknown; } | { hapticsType: "impact"; style: "light" | "medium" | "heavy"; fallback?: () => unknown; } | { hapticsType: "selection-changed"; fallback?: () => unknown; }; ``` ## Result ```ts title="Type" theme={"system"} type SendHapticFeedbackResponse = | { executedWith: "minikit"; data: { status: "success"; version: number; timestamp: string; }; } | { executedWith: "fallback"; data: unknown; }; ``` ```json title="Example" theme={"system"} { "executedWith": "minikit", "data": { "status": "success", "version": 1, "timestamp": "2026-03-28T18:24:00.000Z" } } ``` ## Fallback Behavior Define a custom fallback in the command payload for support outside mini apps. ## Error Codes | Code | Meaning | | --------------- | ------------------------ | | `generic_error` | Unexpected failure | | `user_rejected` | The request was rejected | # Send Transaction Source: https://docs.world.org/mini-apps/commands/send-transaction Send one or more World Chain transactions using the unified MiniKit API. **Breaking Changes in MiniKit v2**: * SignatureTransfer is no longer supported. Please use Allowance Transfers documented below. * Standard ERC-20 `approve()` calls now work in mini apps. Approval will be automatically revoked after the transaction. ## Permit2 Allowance Transfers (Recommended) [Allowance transfers](https://docs.uniswap.org/contracts/permit2/reference/allowance-transfer#approve) are the recommended method for moving tokens in mini apps. World App automatically approves tokens to Permit2, so you can bundle the Permit2 approval and your contract call in a single `sendTransaction` for a better UX. Expiration should always be set to 0 as the approval will be consumed in the same transaction. Standard ERC-20 `approve()` also works if you prefer. ```tsx title="Frontend" theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import { encodeFunctionData, parseEther } from "viem"; const PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; async function approveAndSwap( token: `0x${string}`, spender: `0x${string}`, amount: bigint, ) { const result = await MiniKit.sendTransaction({ chainId: 480, transactions: [ // 1. Approve spender via Permit2 { to: PERMIT2, data: encodeFunctionData({ abi: [ { name: "approve", // You must use this method of Allowance Transfers type: "function", inputs: [ { name: "token", type: "address" }, { name: "spender", type: "address" }, { name: "amount", type: "uint160" }, { name: "expiration", type: "uint48" }, ], outputs: [], stateMutability: "nonpayable", }, ], functionName: "approve", args: [ token, spender, amount, // Always set deadline to 0 as it will be consumed in the same transaction 0, ], }), }, // 2. Call your contract (which pulls tokens via permit2.transferFrom) { to: spender, data: encodeFunctionData({ abi: [ { name: "swap", type: "function", inputs: [{ name: "amount", type: "uint256" }], outputs: [], stateMutability: "nonpayable", }, ], functionName: "swap", args: [amount], }), }, ], }); console.log(result.data.userOpHash); } ``` ```solidity title="Contract" theme={"system"} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; contract Swap { IERC20 public tokenA; IERC20 public tokenB; IAllowanceTransfer public immutable permit2; constructor(address _tokenA, address _tokenB, address _permit2) { tokenA = IERC20(_tokenA); tokenB = IERC20(_tokenB); permit2 = IAllowanceTransfer(_permit2); } function swap(uint256 amount) external { // Pull tokenA from caller via Permit2 AllowanceTransfer permit2.transferFrom( msg.sender, address(this), uint160(amount), address(tokenA) ); // Send tokenB to caller tokenB.transfer(msg.sender, amount); } } ``` World App automatically approves new ERC-20 tokens to the Permit2 contract. Your contract only needs to call `permit2.transferFrom` — the token-level approval is already in place. ## Result ```ts title="Type" theme={"system"} type SendTransactionResponse = | { executedWith: "minikit" | "wagmi"; data: { userOpHash: string; status: "success"; version: number; from: string; timestamp: string; }; } | { executedWith: "fallback"; data: unknown; }; ``` ```json title="Example" theme={"system"} { "executedWith": "minikit", "data": { "userOpHash": "0x8004b63530b968a2a2c9ff414e01fc06a3ec5e4068d36d923df6aa4334744369", "status": "success", "version": 2, "from": "0x1234567890123456789012345678901234567890", "timestamp": "2026-03-28T18:24:00.000Z" } } ``` If you are integrating through Wagmi, viem, or ethers with the World App EIP-1193 provider, the standard `eth_sendTransaction` call resolves to the MiniKit `userOpHash`, not a canonical transaction hash. Do not treat the returned value as a final transaction hash or pass it directly to `waitForTransactionReceipt`. If you need the final on-chain transaction hash, resolve the user operation status first. ## Confirming the User Operation The command resolves as soon as the user operation is submitted, so the first identifier you receive is `userOpHash`. You can either use the `@worldcoin/minikit-react` hook or poll the Developer Portal to check when the user operation is mined and get the final `transaction_hash`. ```tsx title="React" theme={"system"} import { useUserOperationReceipt } from "@worldcoin/minikit-react"; import { createPublicClient, http } from "viem"; import { worldchain } from "viem/chains"; const client = createPublicClient({ chain: worldchain, transport: http("https://worldchain-mainnet.g.alchemy.com/public"), }); const { poll, isLoading, reset } = useUserOperationReceipt({ client }); const onClick = async () => { const result = await MiniKit.sendTransaction({...}); const { receipt } = await poll(result.data.userOpHash); // receipt contains the final transaction receipt }; ``` ```ts title="API" theme={"system"} type UserOperationStatusSuccess = { status: "success"; userOpHash: string; sender: string; transaction_hash: string; nonce: string; }; const response = await fetch( `https://developer.world.org/api/v2/minikit/userop/${userOpHash}`, ); const status = await response.json(); if (status.status === "success") { const success = status as UserOperationStatusSuccess; console.log(success.transaction_hash); } ``` See [GET /api/v2/minikit/userop/](/api-reference/developer-portal/get-user-operation) for the full endpoint response shape. ## Error Codes | Code | Meaning | | ----------------------------------- | ---------------------------------------- | | `invalid_operation` | The requested operation is not allowed | | `user_rejected` | The user rejected the request | | `input_error` | The payload is invalid | | `simulation_failed` | Simulation failed before execution | | `transaction_failed` | The transaction failed after submission | | `generic_error` | Unexpected failure | | `disallowed_operation` | The requested operation is disallowed | | `validation_error` | Validation failed before execution | | `invalid_contract` | The contract is not allowed for your app | | `malicious_operation` | The operation was flagged as malicious | | `daily_tx_limit_reached` | Daily transaction limit reached | | `permitted_amount_exceeds_slippage` | Permit2 amount exceeds allowed slippage | | `permitted_amount_not_found` | Permit2 amount could not be resolved | ## Fallback Behavior By default we intend for mini apps to be useable outside of World App. Fallbacks generally will not be needed for this command and you should instead follow the [migration path outlined](/mini-apps/migration/minikit-v2). ## Allowlisting Contracts and Tokens Before your mini app can send transactions, you must allowlist the contracts and tokens it interacts with. Navigate to **Developer Portal > Mini App > Permissions** and add: * **Permit2 Tokens** — every ERC-20 token your app transfers via Permit2 * **Contract Entrypoints** — every contract your app calls directly
Developer Portal showing Permit2 token and contract entrypoint whitelisting

Developer Portal Whitelist

Transactions that touch non-whitelisted contracts or tokens will be blocked by the backend with an `invalid_contract` error. ## Preview
# Share Source: https://docs.world.org/mini-apps/commands/share Open the native share sheet using the unified MiniKit API. Use `MiniKit.share()` to open the native share sheet with text, links, or files. ## Basic Usage ```tsx title="Example" theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import type { CommandResultByVia, MiniAppShareSuccessPayload, MiniKitShareOptions, } from "@worldcoin/minikit-js/commands"; export async function shareLink() { const input = { title: "Invite Link", text: "Use this invite code to join my mini app", url: "https://world.org", } satisfies MiniKitShareOptions; const result: CommandResultByVia< MiniAppShareSuccessPayload, MiniAppShareSuccessPayload, "minikit" > = await MiniKit.share(input); console.log(result.data.shared_files_count); } ``` ```ts title="Type" theme={"system"} type MiniKitShareOptions = { files?: File[]; title?: string; text?: string; url?: string; fallback?: () => unknown; }; ``` ## Result ```ts title="Type" theme={"system"} type ShareResponse = | { executedWith: "minikit"; data: { status: "success"; version: number; shared_files_count: number; timestamp: string; }; } | { executedWith: "fallback"; data: unknown; }; ``` ```json title="Example" theme={"system"} { "executedWith": "minikit", "data": { "status": "success", "version": 1, "shared_files_count": 2, "timestamp": "2026-03-28T18:24:00.000Z" } } ``` ## Fallback Behavior Define a custom fallback in the command payload for support outside mini apps. ## Error Codes | Code | Meaning | | ------------------- | ---------------------------------- | | `user_rejected` | The user rejected the share flow | | `generic_error` | Unexpected failure | | `invalid_file_name` | One or more file names are invalid | ## Preview
# Share Contacts Source: https://docs.world.org/mini-apps/commands/share-contacts Open the native contact picker using the unified MiniKit API. Use `MiniKit.shareContacts()` to open the World App contact picker. ## Basic Usage ```tsx title="Example" theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import type { CommandResultByVia, MiniKitShareContactsOptions, ShareContactsResult, } from "@worldcoin/minikit-js/commands"; export async function pickContacts() { const input = { isMultiSelectEnabled: true, inviteMessage: "Join me in this mini app", } satisfies MiniKitShareContactsOptions; const result: CommandResultByVia< ShareContactsResult, ShareContactsResult, "minikit" > = await MiniKit.shareContacts(input); console.log(result.data.contacts); } ``` ```ts title="Type" theme={"system"} type MiniKitShareContactsOptions = { isMultiSelectEnabled?: boolean; inviteMessage?: string; fallback?: () => unknown; }; ``` ## Result ```ts title="Type" theme={"system"} type ShareContactsResponse = | { executedWith: "minikit"; data: { contacts: Array<{ username: string; walletAddress: string; profilePictureUrl: string | null; }>; timestamp: string; }; } | { executedWith: "fallback"; data: unknown; }; ``` ```json title="Example" theme={"system"} { "executedWith": "minikit", "data": { "contacts": [ { "username": "alex", "walletAddress": "0x1234567890123456789012345678901234567890", "profilePictureUrl": "https://cdn.example.com/profile/alex.png" } ], "timestamp": "2026-03-28T18:24:00.000Z" } } ``` ## Error Codes | Code | Meaning | | --------------- | ----------------------------- | | `user_rejected` | The user rejected the request | | `generic_error` | Unexpected failure | ## Fallback Behavior Define a custom fallback in the command payload for support outside mini apps. ## Preview
# Sign Message Source: https://docs.world.org/mini-apps/commands/sign-message Sign an EIP-191 message using the unified MiniKit API. Use `MiniKit.signMessage()` to request a personal signature from the user's wallet. ## Basic Usage ```tsx title="Example" theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import type { CommandResultByVia, MiniAppSignMessageSuccessPayload, MiniKitSignMessageOptions, } from "@worldcoin/minikit-js/commands"; export async function signMessage() { const input = { message: "Hello world", } satisfies MiniKitSignMessageOptions; const result: CommandResultByVia = await MiniKit.signMessage(input); console.log(result.data.signature); } ``` ```ts title="Type" theme={"system"} type MiniKitSignMessageOptions = { message: string; fallback?: () => unknown; }; ``` ## Result ```ts title="Type" theme={"system"} type SignMessageResponse = | { executedWith: "minikit" | "wagmi"; data: { status: "success"; version: number; signature: string; address: string; }; } | { executedWith: "fallback"; data: unknown; }; ``` ```json title="Example" theme={"system"} { "executedWith": "minikit", "data": { "status": "success", "version": 1, "signature": "0xabcdef1234567890", "address": "0x1234567890123456789012345678901234567890" } } ``` ## Fallback Behavior Define a custom fallback in the command payload for support outside mini apps. ## Notes Verify signatures in a trusted environment before using them for sensitive application logic. ## Error Codes | Code | Meaning | | ----------------- | ------------------------------ | | `invalid_message` | The message payload is invalid | | `user_rejected` | The user rejected the request | | `generic_error` | Unexpected failure | # Sign Typed Data Source: https://docs.world.org/mini-apps/commands/sign-typed-data Sign EIP-712 typed data using the unified MiniKit API. Use `MiniKit.signTypedData()` to request an EIP-712 signature from the user's wallet. ## Basic Usage ```tsx title="Example" theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import type { CommandResultByVia, MiniAppSignTypedDataSuccessPayload, MiniKitSignTypedDataOptions, } from "@worldcoin/minikit-js/commands"; export async function signTypedData() { const input = { primaryType: "Mail", domain: { name: "Example", version: "1", chainId: 480, }, types: { EIP712Domain: [ { name: "name", type: "string" }, { name: "version", type: "string" }, { name: "chainId", type: "uint256" }, ], Mail: [ { name: "contents", type: "string" }, ], }, message: { contents: "Hello world", }, } satisfies MiniKitSignTypedDataOptions; const result: CommandResultByVia = await MiniKit.signTypedData(input); console.log(result.data.signature); } ``` ```ts title="Type" theme={"system"} type MiniKitSignTypedDataOptions = { types: TypedData; primaryType: string; message: Record; domain?: TypedDataDomain; chainId?: number; fallback?: () => unknown; }; ``` ## Result ```ts title="Type" theme={"system"} type SignTypedDataResponse = | { executedWith: "minikit" | "wagmi"; data: { status: "success"; version: number; signature: string; address: string; }; } | { executedWith: "fallback"; data: unknown; }; ``` ```json title="Example" theme={"system"} { "executedWith": "minikit", "data": { "status": "success", "version": 1, "signature": "0xabcdef1234567890", "address": "0x1234567890123456789012345678901234567890" } } ``` ## Fallback Behavior Define a custom fallback in the command payload for support outside mini apps. ## Error Codes | Code | Meaning | | ---------------------- | ----------------------------------------- | | `invalid_operation` | The request contains an invalid operation | | `user_rejected` | The user rejected the request | | `input_error` | The payload is invalid | | `simulation_failed` | Simulation failed before signing | | `generic_error` | Unexpected failure | | `disallowed_operation` | The operation is not allowed | | `invalid_contract` | The contract or domain is invalid | | `malicious_operation` | The request was flagged as malicious | # World ID Source: https://docs.world.org/mini-apps/commands/verify World ID verification has moved to IDKit. World ID has been unified into [IDKit](/world-id/idkit/mini-apps), so the same integration works in both mini apps and desktop. If you are migrating from MiniKit 1.x, replace `MiniKit.verify(...)` or `MiniKit.commandsAsync.verify(...)` with IDKit. See [IDKit for Mini Apps](/world-id/idkit/mini-apps) for the Mini App integration shape, including Selfie Check. # Wallet Authentication Source: https://docs.world.org/mini-apps/commands/wallet-auth Authenticate a user with Sign-In with Ethereum using the unified MiniKit API. Use `MiniKit.walletAuth()` to authenticate a user with Sign-In with Ethereum inside World App. This is the recommended authentication flow for mini apps. ## Basic Usage Generate the nonce on your backend. The nonce must be alphanumeric and at least 8 characters. ```tsx title="Example" theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import type { CommandResultByVia, MiniKitWalletAuthOptions, WalletAuthResult, } from "@worldcoin/minikit-js/commands"; export async function signInWithWallet() { const response = await fetch("/api/nonce"); const { nonce } = await response.json(); const input = { nonce, statement: "Sign in to Example Mini App", expirationTime: new Date(Date.now() + 1000 * 60 * 60), // requestId: "optional-tracking-id", // notBefore: new Date(), } satisfies MiniKitWalletAuthOptions; const result: CommandResultByVia = await MiniKit.walletAuth(input); if (result.executedWith === "fallback") { return; } await fetch("/api/complete-siwe", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ payload: result.data, nonce, }), }); } ``` ```ts title="Type" theme={"system"} type MiniKitWalletAuthOptions = { nonce: string; statement?: string; expirationTime?: Date; notBefore?: Date; requestId?: string; fallback?: () => unknown; }; ``` ## Result ```ts title="Type" theme={"system"} type WalletAuthResponse = | { executedWith: "minikit" | "wagmi"; data: { address: string; message: string; signature: string; }; } | { executedWith: "fallback"; data: unknown; }; ``` ```json title="Example" theme={"system"} { "executedWith": "minikit", "data": { "address": "0x1234567890123456789012345678901234567890", "message": "example.com wants you to sign in with your Ethereum account", "signature": "0xabcdef1234567890" } } ``` ## Backend Verification Always verify the returned SIWE payload on your backend. ```ts theme={"system"} import { cookies } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; import type { MiniAppWalletAuthSuccessPayload } from "@worldcoin/minikit-js/commands"; import { verifySiweMessage } from "@worldcoin/minikit-js/siwe"; type RequestBody = { payload: MiniAppWalletAuthSuccessPayload; nonce: string; }; export async function POST(req: NextRequest) { const { payload, nonce } = (await req.json()) as RequestBody; if (nonce !== cookies().get("siwe")?.value) { return NextResponse.json( { isValid: false, error: "Invalid nonce" }, { status: 400 }, ); } try { // Optional: pass statement and requestId for additional server-side validation const verification = await verifySiweMessage( payload, nonce, // statement, — validates the statement matches what you sent // requestId, — validates the request ID matches what you sent // viemClient, — custom viem Client; defaults to a public Worldchain client ); return NextResponse.json({ isValid: verification.isValid, address: verification.siweMessageData.address, }); } catch (error) { return NextResponse.json( { isValid: false, error: error instanceof Error ? error.message : "Unknown error", }, { status: 400 }, ); } } ``` ## Options | Field | Type | Required | Description | | ---------------- | ------------------ | -------- | -------------------------------------------------------- | | `nonce` | `string` | Yes | Alphanumeric, at least 8 characters | | `statement` | `string` | No | Human-readable statement included in the SIWE message | | `expirationTime` | `Date` | No | When the SIWE message expires | | `notBefore` | `Date` | No | SIWE message is not valid before this time | | `requestId` | `string` | No | Arbitrary ID for correlating the request on your backend | | `fallback` | `() => Promise` | No | Custom fallback for non-World-App environments | ## Notes * Use `MiniKit.user.walletAddress` after successful auth if you need cached user state * Use `MiniKit.getUserByAddress()` or `MiniKit.getUserByUsername()` to resolve username and profile metadata * Do not use World ID verification as a login substitute ## Error Codes | Code | Meaning | | ------------------- | --------------------------------------- | | `malformed_request` | The SIWE request payload is invalid | | `user_rejected` | The user rejected the signature request | | `generic_error` | Unexpected failure | ## Fallback Behavior Define a custom fallback in the command payload for support outside mini apps. ## Preview
# Ecosystem Tools Source: https://docs.world.org/mini-apps/ecosystem/ecosystem Ecosystem tools and integrations for builders on World Chain and Mini Apps. Early builders get \~10,000 real users sent to your Mini App. Free analytics and smart push notifications. By [Human Labs](https://x.com/WorldHumanLabs). Generate Uniswap API keys to add swap and LP functionality directly to your app in minutes. For agentic workflows, use the Uniswap Skill to integrate via AI agents. # Data & Analytics Source: https://docs.world.org/mini-apps/growth/analytics Track only what answers **critical product questions**—nothing more.\ This page shows you **what to measure, why, and how to start in one sprint**. ## 1 · Mental Model *Q → M → E* > **Question → Metric → Event** 1. **Question** you want answered 2. **Metric** that quantifies it 3. **Event** (or two) that feed that metric | Product Question | Metric (M) | Minimal Events (E) | | ---------------------------- | ----------------------------- | -------------------------------- | | Do users see value on day 0? | **Conversion to First Value** | `signup`, `first_value` | | Do they come back? | **D1 / D7 Retention** | `app_open` | | Will growth be organic? | **Invite Acceptance Rate** | `invite_sent`, `invite_accepted` | | Can we re‑engage them? | **Push Open Rate** | `notification_open` | If an event doesn't roll up to a key question, skip it. ## 2 · Core Event Set (6 Lines of Code) ```ts theme={"system"} track('app_open') // every launch track('signup', { method: 'id' }) track('first_value', { action: '🎉' }) track('invite_sent') track('invite_accepted') track('notification_open') ``` That is enough to build funnels, retention, and viral metrics. ## 3 · Action Plan **Focus on these 4 metrics weekly until they're all green:** * **Signup → First Value %** (target: ≥40%) - Are users getting value immediately? * **D1 Retention** (target: ≥25%) - Are they coming back the next day? * **Invite Acceptance %** (target: ≥15%) - Is word-of-mouth working? * **Push Open %** (target: ≥15%) - Can we re-engage users? **Next steps:** Pick the lowest metric and spend 1-2 sprints fixing it. Don't touch anything else until all four are hitting targets. ## 4 · Common Pitfalls to Dodge * **Event sprawl** – >10 events = analysis paralysis. * **Vanity charts** – page views ≠ value. * **No cohorts** – averages hide churn. * **Premature segmentation** – wait for 1k users. * **Ignoring retention** – acquisition is useless without it. Analytics should light the path, not become the journey. Keep it lean, learn fast, and build what moves the needle. # Gamification Source: https://docs.world.org/mini-apps/growth/gamification Gamification isn't about turning your app into a game—it's about using proven psychological principles to make routine actions more engaging and rewarding. ## Why It Works Game mechanics tap into fundamental human psychology: our need for achievement, progress, and social comparison. Duolingo saw retention jump from 12% D1 to 55% after implementing streak mechanics. The key is choosing mechanics that align with your core user behavior, not just adding badges everywhere. ## Core Mechanics by Impact & Effort | Mechanic | Effort | Why It Works | Quick Implementation | | ------------------- | ------ | ------------------------------------------------------- | ------------------------------------------------------------ | | **Daily Streaks** | ★★☆☆☆ | Creates habit formation; users hate breaking chains | Track daily actions, show streak count, celebrate milestones | | **Progress Bars** | ★☆☆☆☆ | Visual momentum; users hate seeing 80% incomplete | CSS progress indicators for any goal completion | | **Badges & Levels** | ★★☆☆☆ | Collectibles trigger completion bias | Emoji icons + achievement unlocks | | **Leaderboards** | ★★☆☆☆ | Social comparison drives engagement | Simple top 10 list, refresh weekly | | **Mystery Rewards** | ★★☆☆☆ | Variable rewards release dopamine (slot machine effect) | Daily random reward from small pool | ## Strategic Implementation ### 1. Daily Streaks **Why this first**: Highest retention impact with moderate effort. Creates powerful daily habit loops. **Core concept**: Track consecutive days of user activity, celebrate milestones, make breaking streaks feel costly. **Implementation strategy**: * Store `currentStreak`, `longestStreak`, `lastActiveDate` per user * Check daily: consecutive day = +1, gap = reset to 1 * Celebrate at days 3, 7, 14, 30 with visual rewards * Show streak prominently in app header ```typescript theme={"system"} // Simple streak logic const updateStreak = (lastActive: string) => { const daysSinceLastActive = getDaysDifference(lastActive, today) return daysSinceLastActive === 1 ? streak + 1 : 1 } ``` ### 2. Progress Bars & Visual Momentum Users hate leaving things 80% complete. Visual progress creates urgency to finish. **Where to use**: * **Profile completion:** 2/5 steps remaining * **Daily/weekly goals:** 7/10 tasks done * **Social milestones:** 3/5 friends invited * **Skill progression**: Level 4: 80% to Level 5 **Key principle**: Always show progress toward the next achievable milestone, not distant end goals. ### 3. Achievement Badges **Strategy**: Create collectible moments that trigger completion bias and provide social proof. **Badge categories that work**: * **Onboarding**: "First Steps" (welcome badge), "Explorer" (tried 3 features) * **Social**: "Social Butterfly" (first share), "Connector" (5 friends invited) * **Engagement**: "Week Warrior" (7-day streak), "Power User" (daily active for 30 days) * **Milestones**: "High Achiever" (reached level 10), "Completionist" (100% profile) **Design tips**: * Make early badges easy to get within first session * Create clear progression: Common → Rare → Epic * Show badge collection in user profile for social proof ### 4. Social Leaderboards **When to use**: Best for apps with clear scoring metrics (points, levels, achievements completed). **Types that work**: * **Weekly leaderboards**: Reset regularly so everyone has a chance * **Friend leaderboards**: Compare with people you know (higher engagement) * **Category leaderboards**: "Top Streaks", "Most Social", "Fastest Completion" **Key strategies**: * Keep it simple: Top 10 list with current user highlighted * Refresh weekly to prevent permanent dominance * Only show verified users to prevent gaming **World App advantage**: World ID ensures fair competition with one-person-one-account guarantee. ### 5. Variable Reward Systems **Psychology**: Variable rewards trigger dopamine more than predictable ones (slot machine effect). **Implementation ideas**: * **Daily mystery box**: Random reward from a small pool (coins, badges, features) * **Streak bonuses**: Random multiplier for milestone completions * **Surprise rewards**: Occasional "lucky day" bonuses for regular actions * **Loot boxes**: Earned through achievements, contain random useful items **Reward pool strategy**: * 70% common rewards (small coin amounts, basic items) * 25% rare rewards (larger bonuses, temporary premium features) * 5% epic rewards (exclusive badges, significant bonuses) **Key principle**: Make the anticipation of opening more exciting than the reward itself. ## Design Principles ### 1. One Core Loop Pick one primary habit loop and nail it before adding more. **Example flow**: Daily check-in → earn streak → unlock reward → share achievement → invite friends ### 2. Early Wins Users should earn their first badge/reward within 30 seconds of first use. This creates immediate positive reinforcement and sets expectations for future rewards. ### 3. Surface Progress Everywhere * Show current streak in app header/navigation * Display progress bars for any incomplete goals * Badge count in user profile for social proof * Preview next achievable reward/milestone ### 4. Measure & Optimize Track key events: `streak_extended`, `badge_earned`, `leaderboard_viewed`, `reward_claimed` **Golden rule**: If a mechanic doesn't improve D7 retention after 2 weeks, remove it. ## Common Pitfalls 1. **Over-gamification**: Don't add badges for every tiny action—dilutes achievement value 2. **Participation trophies**: Make early badges easy but later ones meaningful 3. **Pay-to-win mechanics**: Keep purchases separate from core progression 4. **Feature creep**: Start with one mechanic, prove it works, then expand ## Your Next Steps 1. **Start simple**: Implement daily streaks first—highest impact for effort invested 2. **Add visual progress**: One progress bar or completion indicator 3. **Create 3-5 early badges**: Tied directly to your core user actions 4. **Measure ruthlessly**: Track D7 retention before/after each mechanic 5. **Expand gradually**: Only add new mechanics after current ones prove effective The goal isn't to build a game —it's to make your core experience more engaging and habit-forming. # Overview Source: https://docs.world.org/mini-apps/growth/index A focused, step-by-step guide to growing your mini app within the World ecosystem. Everything is scoped to what an indie hacker or small team can realistically ship in a few weeks. ## Overview & Philosophy Building a successful mini app requires more than great features—you need users to discover, engage with, and stick around. This growth playbook focuses on four key areas that drive sustainable growth: 1. **[Invites & Viral Loops](/mini-apps/growth/invites-viral)** - Turn your users into advocates 2. **[Gamification](/mini-apps/growth/gamification)** - Build engagement through game mechanics 3. **[Retention via Notifications](/mini-apps/growth/notifications)** - Keep users coming back 4. **[Data & Analytics](/mini-apps/growth/analytics)** - Measure what matters ## Real-World Results The strategies in this playbook are based on proven case studies: * **PayPal's referral program**: 7-10% daily growth * **Dropbox's two-sided rewards**: 3900% growth in 15 months * **Duolingo's streak mechanics**: Retention jumped from 12% to 55% # Invites & Viral Loops Source: https://docs.world.org/mini-apps/growth/invites-viral ## Why It Works Referral programs are proven growth drivers because they leverage trust. People are 4x more likely to try something recommended by a friend versus discovering it through ads. PayPal's $20/$20 referral program produced 7-10% daily growth, while Dropbox's free storage rewards drove 3900% growth in 15 months. ## Step-by-Step Implementation ### 1. Set Up Universal Links Create server-side invite pages that work across all platforms: ```typescript theme={"system"} // pages/invite.tsx export default function InvitePage({ code }: { code: string }) { useEffect(() => { // Redirect to mini app window.location.href = `https://world.org/mini-app?app_id=${YOUR_APP_ID}&path=/invite?code=${code}` }, [code]) return
Redirecting to mini app...
} ``` Universal-link format `https://world.org/mini-app?app_id={app_id}&path={path}` Deep-link (opens World App directly if installed) `worldapp://mini-app?app_id={app_id}&path={path}` To force opening in the device browser instead of the native webview, append `open_out_of_window=true` to the URL (works for both universal and deep links). Note: path should be URL encoded. ### 2. Generate Share Links Create a shareable link that includes the referral information: ```typescript theme={"system"} function generateInviteLink(userId: string): string { const baseUrl = "https://world.org/mini-app"; const appId = "your_app_id"; const path = encodeURIComponent(`/invite?code=${userId}`); return `${baseUrl}?app_id=${appId}&path=${path}`; } ``` ### 3. Implement Share Functionality Add share buttons at key moments in your user journey: ```typescript theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; async function shareInvite() { const inviteLink = generateInviteLink(currentUser.id); try { await MiniKit.share({ title: "Join me on [Your App Name]!", text: `I'm using this amazing mini app. Join me and we both get rewards! 🎁`, url: inviteLink, }); // Track the share event trackEvent("invite_link_created", { user_id: currentUser.id, share_method: "native", }); } catch (error) { console.error("Share failed:", error); } } ``` ### 4. Handle Incoming Referrals Process referral codes when new users sign up: ```typescript theme={"system"} // On app initialization function handleReferral() { const urlParams = new URLSearchParams(window.location.search); const refCode = urlParams.get("ref"); if (refCode && !currentUser.referredBy) { // Credit the referrer fetch("/api/process-referral", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ newUserId: currentUser.id, referrerCode: refCode, }), }); } } ``` ### 5. Reward System Implement two-sided rewards that benefit both parties: ```typescript theme={"system"} // api/process-referral.ts export async function processReferral(data: { newUserId: string; referrerCode: string; }) { const referrer = await getUserByCode(data.referrerCode); if (!referrer) { return { success: false, reason: "invalid_referrer" }; } // Credit both users await Promise.all([ creditUser(referrer.id, { type: "referral_bonus", amount: 100, reason: "Friend joined via your invite", }), creditUser(data.newUserId, { type: "signup_bonus", amount: 50, reason: "Welcome bonus for joining via invite", }), ]); return { success: true }; } ``` ### 6. Support Deferred Deep Links (Android) ``` https://play.google.com/store/apps/details?id=com.worldcoin&referrer=app_id%3D{app_id}%26path%3D{path} ``` World App prompts the install and then automatically forwards the user to your specified path. ### Optimising Social-Share Previews World app universal links will forward the og:image from your integration url. Eg. `world.org/mini-app?app_id=1234&path=/invite?code=abcd` will forward the og:image from `[your-miniapp-url]/invite?code=abcd`. ### Inside World App Discovery * **Quick Actions**: Mini apps can hand off context to one another. Split distinct flows into focused apps that trigger each other with Quick Actions. * **Widget**: Your mini app can live on the phone's home screen. Prompt the user to add the mini app as a widget in the home screen.
Widget
Widget on iOS ## Design Best Practices ### Placement Strategy * **After first value delivery**: When user completes their first meaningful action * **Post-achievement**: Right after earning a badge, completing a level, or winning * **Onboarding finale**: As the last step of user setup ### Copy That Converts * **Personal benefit first**: "Get 100 coins for each friend who joins" * **Mutual benefit**: "You both get rewards when they sign up" * **Social proof**: "Join 10,000+ users already earning rewards" ### Visual Design * Use prominent, action-oriented buttons ("Invite Friends", "Share & Earn") * Show potential rewards clearly with icons or progress bars * Include preview of what the shared content looks like ## Metrics to Track Monitor these key events to measure your viral loop performance: ```typescript theme={"system"} // Essential tracking events const events = { invite_link_created: { user_id, share_method }, invite_link_clicked: { ref_code, source }, signup_source_invite: { ref_code, new_user_id }, referral_reward_granted: { referrer_id, new_user_id, reward_amount }, }; ``` ### Key Metrics Dashboard * **Invite Conversion Rate**: (Signups from invites) / (Total invite links clicked) * **K-Factor**: (New users from invites) / (Total active users) * **Viral Cycle Time**: Average time from invite sent to new user activated * **Reward Cost per Acquisition**: Total rewards paid / New users acquired ## Quick A/B Test Ideas Test these variables to optimize your viral loop: 1. **Reward Amount**: Test 50 vs 100 vs 200 coin rewards 2. **Timing**: Share prompt after first win vs after onboarding 3. **Copy**: Personal benefit vs mutual benefit messaging 4. **Incentive Type**: Coins vs premium features vs exclusive content Start with small reward amounts and scale up based on unit economics. Cap total lifetime rewards per user to control costs. ## Next Steps 1. Implement universal links for your invite flow 2. Add share buttons after key user achievements 3. Set up two-sided rewards with World ID verification 4. Track invite metrics and run small A/B tests 5. Scale successful invite mechanics across more touchpoints # Retention via Notifications Source: https://docs.world.org/mini-apps/growth/notifications Thoughtful, behavior‑based notifications keep users engaged long after they close your Mini App. Done right, they lift retention and even earn you a coveted **home‑screen badge** in World App. ### 1 · Why They Matter | Benefit | Details | | -------------------- | -------------------------------------------------------------------- | | **Retention boost** | Targeted pushes can 2–3× day‑7 retention. | | **Free visibility** | ≥ 15 % open rate unlocks a persistent badge on your app icon. | | **Strict standards** | \< 10 % open = delivery paused for 7 days—quality is non‑negotiable. | ### 2 · Quality Thresholds | Open‑Rate (7‑day) | Platform Action | Your Next Step | | ----------------- | ----------------- | --------------------------------- | | **\< 10%** | Paused for 1 week | Audit triggers & copy immediately | | **10%+** | Badge displayed | Maintain & iterate | | **25%+** | "Excellent" tier | Scale what works, test new ideas | ### 3 · Core Principles 1. **Trigger‑based > Broadcasts** – React to *user actions* (wins, risks) instead of fixed schedules. 2. **Personalize** – Use `${username}` placeholder to personalize notifications with usernames. 3. **Copy rules** – ≤ 30‑char title, 1–2 emojis, clear value + curiosity gap. ### 4 · Trigger Library | Trigger | Why It Works | Sample Title | | ---------------- | --------------------- | --------------------------------- | | **Achievement** | Users love quick wins | `🎉 Level 2 unlocked!` | | **Streak Risk** | Loss aversion | `🔥 Keep your 5‑day streak alive` | | **Social** | FOMO / competition | `👀 Maya just beat your score` | | **Limited‑time** | Urgency | `⏰ Double rewards end in 2 h` | | **Re‑engage** | Gentle nudge | `🚀 Welcome back bonus ready` | ### 5 · Frequency & Control * **Start** at **≤ 1 push/day**; add more only if retention rises. * Present a clear **opt‑out** path; trust drives long‑term engagement. ### 6 · Copy Cheatsheet * **Lead with benefit**: "Earn 50 coins" beats "Check the app". * **Curiosity**: "Something new awaits …". * **Concrete numbers**: "30 s left" > "Hurry up". * **Avoid** vague, generic, or feature‑only blasts. ### 7 · Test & Improve | Variable | What to Try | | -------------- | --------------------- | | Emoji | 🔥 vs ⚡ | | Timing | Instant vs +2 h | | CTA | "Claim" vs "Open now" | | Content length | Short vs rich preview | ### 8 · Integrations * **Gamification** – Push on badge earned, streak day, leaderboard change. * **Virality** – Notify referrer when invitee signs up or hits milestones. * **Core value** – Release drops, new content, feature unlocks. ### 9 · Home‑Screen Badge
Notification badges on home screen
Maintain ≥ 15 % opens to display this permanent badge—top‑of‑mind visibility every time users open World App. ### 10 · Implementation Checklist * [ ] Ask permission **after** first value delivered * [ ] Use `${username}` in every push * [ ] Include `mini_app_path` deep link * [ ] Limit to ≤ 1 notification/day at launch * [ ] Monitor 7‑day open rate weekly * [ ] A/B test copy monthly * [ ] Iterate triggers; retire low performers # App Guidelines Source: https://docs.world.org/mini-apps/guidelines/app-guidelines ## Mobile First Mini apps are inherently accessed via mobile, so your application UI should look and feel like a mobile app. ### Key considerations for a mobile-first experience: * Use tab navigation to simplify movement within the app. * Implement snap-to text boxes for easy user input. * Avoid footers, sidebars, and excessive scrolling. * Provide clear and direct navigation without hamburger menus. * Ensure smooth transitions between different screens or sections. * Use consistent background colors for a cohesive visual experience. * Provide clear navigation cues to help users understand where they are and how to proceed. * Ensure all UI elements are responsive and adapt well to different screen sizes. * Use fonts that are optimized for readability on mobile devices. * Include a splash page for sign-in if needed.
❌ Bad Example
Footer and long scrolling
✅ Good Example
Bottom tab navigation and anchored buttons
## Scroll Bounce on IOS. We recommend you avoid scroll bounce error on iOS devices. Try disabling autoscroll & maybe fixed position elements or using 100dvh instead of 100vh. If you are not using a bottom navigation bar, you can use the following CSS to disable the scroll bounce error: ```css theme={"system"} html, body { width: 100vw; height: 100vh; overscroll-behavior: none; overflow: hidden; } ``` Alternatively, you can try this approach which allows scrolling while still preventing the bounce effect: ```css theme={"system"} html, body { -webkit-overflow-scrolling: touch; overscroll-behavior: none; overflow: scroll; } ``` ## Design Patterns Here are some design patterns that we recommend you follow: 1. When a user is authenticated through their wallet, always show their username instead of the wallet address 2. Use the "Verify" command to confirm important actions or identity verification. 3. When dealing with wallet addresses, use an address book to link them to recognizable usernames or other identifiers ## App Icon Your app icon should be a **square** image with a non white background. ## Content Card Follow these guidelines when preparing the content card image: * Content card size is 345x240 px. * Avoid adding text inside content card images as much as possible. * Keep the bottom 94 px free of important details; this area is overlaid in app. * The text and icon on the card are rendered from your app description and app icon automatically—no need to add them to the image. * Export the banner without metadata, and include the blurred foreground element covering the bottom 94 px. * Export without border radius, as PNG at 3x scale.
Good content card example ✅ Good Example

## Load times For mini apps, 2-3 seconds max for initial load and under 1 second for subsequent actions should be your target. However, always test for real-world scenarios and provide visual feedback during loading to maintain user trust. ## Branding & Identity Do not use the term "official" in your app name, description, or interface. Mini apps are third-party applications and should not create the impression that they are officially endorsed by or affiliated with World. Mini apps should maintain their own distinct brand identity while integrating with the World ecosystem Additionally, do not use the **World logo** or any modified version of it in your app. ## Chance based We recommend developers to avoid building chance based games, as these games have a very low likelihood of being approved. **Chance based** = prize awarded based on chance, not skill. This means you are using a RNG to determine a winner. You can still have prizes but they need to be awarded based on skill. Not randomness. So winning a game where I get a prize is skill based. ## Memberships & Yield Selling memberships or subscriptions that grant users access to increased yield, higher returns, or enhanced earning rates is strictly prohibited. Apps must not offer paid access to features or tiers that boost financial returns. ## No Token Pre-sales Token pre-sales are strictly prohibited. Mini Apps must not offer, promote, or facilitate token pre-sales in any form. ## NFTs on iOS Mini Apps may allow users to view their own NFTs, provided that NFT ownership does not unlock features or functionality within the app. Mini Apps may allow users to browse NFT collections owned by others, provided that the mini apps may not include buttons, external links, or other calls to action that direct customers to purchasing mechanisms other than in-app purchase. ## Localization Many of our users are located around the world. Apps that are localised for each region will perform significantly better. You can recognize the user's locale by using the [Accept-Language](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language) header For next.js apps, you can use [Next.js Internationalization](https://nextjs.org/docs/app/building-your-application/routing/internationalization) to localize your app. These languages are particularly important given our users: 1. English 2. Spanish 3. Thai 4. Japanese 5. Korean 6. Portuguese ## Usernames You should avoid displaying the user's wallet address, use their username instead. ## Using the Address Book World ID inherently allows anonymity between applications. We generally encourage developers to use their own Verify Command and verify the proof. However, we also offer a World ID address book. This contract stores a mapping `addressVerifiedUntil` you can query to see if a World App address is World ID Orb verified. ## UI Kit The Mini Apps UI Kit is a React-based design system for accelerating the development of mini apps. It provides pre-built, reusable UI components that align with World's design guidelines for consistency and high-quality UX. ### Get Started Install the UI Kit via NPM: ```bash theme={"system"} npm install @worldcoin/mini-apps-ui-kit-react ``` Learn more in the package README and Storybook: * [Package README](https://www.npmjs.com/package/@worldcoin/mini-apps-ui-kit-react?activeTab=readme) href="[https://www.npmjs.com/package/@worldcoin/mini-apps-ui-kit-react?activeTab=readme](https://www.npmjs.com/package/@worldcoin/mini-apps-ui-kit-react?activeTab=readme)" target="\_blank" > Package README * [UI Kit Storybook](https://mini-apps-ui-kit.world.org) # Design Guidelines Source: https://docs.world.org/mini-apps/guidelines/design-guidelines This page gives you quick, visual rules to meet a high design standard for mini apps. Each image shows the intended pattern and a short usage note so you can apply it fast. ## Grid and spacing **Usage** Padding is set to a default of 24px, providing ample breathing room around elements while maintaining a clean and structured appearance. Base unit ## Paddings **Usage** Padding is set to a default of 24px, providing ample breathing room around elements while maintaining a clean and structured appearance. Paddings ## Navigation bar **Usage** Except for the control components of Mini Apps in the upper-right corner, all the other contents can be configured for custom designs. If interactive elements need to be set near the control components of Mini Apps, the developer shall note that whether the interaction area will conflict with the control components, and whether the operation is easy. Navigation bar ## Main pages
One clear task per page

The default space between a header and its associated content is 16px, and the same 16px spacing applies between elements within a section. This consistent use of spacing helps create a clean visual structure and predictable rhythm across the UI.

Keep CTAs visible

The default space between sections is set to 32px, helping to create clear separation between distinct areas of content. The space between the search bar and the content below is 24px, while the space between a sub-headline and its associated content is 16px.

Minimize unnecessary scrolling

The default space between header and a section that starts with a sub headline is set to 24px.

Prioritize primary actions

The default space between last item inside the scrollable area if a bottom bar is presented is 32px.

## Secondary pages **Usage** The default space between a header and its associated content is 24px, while the space between a header and a secondary title is 32px. The space between secondary title and description should be 12px.
Secondary 1 Secondary 2
## Tab bar (Android vs iOS) **Usage** Navigation bar is positioned with a 12px space from the iOS & Android bottom bar, ensuring optimal accessibility and a comfortable tap area.
Tab bar Android
Tab bar iOS
## Drawers / sheets (Android vs iOS) **Usage** Drawer is positioned with a 12px space from the iOS & Android bottom bar, ensuring optimal accessibility and a comfortable tap area.
Drawers Android
Drawers iOS
## Toasts (Android vs iOS) **Usage** The toast message should be horizontally centered and positioned directly below the header to ensure visibility without disrupting user interaction.
Toast Android
Toast iOS
## Keyboard handling (Android vs iOS) **Usage** Buttons are placed 24px above the active keyboard. This spacing ensures that buttons remain easily accessible and visually distinct, even when the keyboard is active, preventing accidental taps or overlap.
Keyboard Android
Keyboard iOS
## Bottom safe area space (Android vs iOS) **Usage** Buttons are positioned with a 24px space from the iOS bottom bar, ensuring optimal accessibility and a comfortable tap area.
Bottom space Android
Bottom space iOS
## States **Usage** Middle alignment ensures consistency across various screen sizes and component states, making the interface feel polished and intentional. This alignment strategy is especially effective for empty states, loading indicators, or other transient states, providing a seamless and cohesive user experience. Common states # Notification Guidelines Source: https://docs.world.org/mini-apps/guidelines/features-and-guidelines ## Guidelines To maintain high-quality notifications, please adhere to the following guidelines: * Notifications should be purely functional, not marketing-related. * Notifications must be directly related to the mini app. * Notifications must be relevant to the user. ## Username Substitution It's extremely easy to personalize your notifications, by using usernames. Use the special string `${username}` in your notification message, and it will substitute in the recipient's username. ```bash cURL theme={"system"} curl -X POST "https://developer.worldcoin.org/api/v2/minikit/send-notification" \ -H "Authorization: Bearer {api_key}" \ -H "Content-Type: application/json" \ -d '{ "app_id": "app_id", "wallet_addresses": ["0x123", "0x456"], "localisations": [ { "language": "en", "title": "title", "message": "🧑‍🍳 We're cooking something special for you ${username}" } ], "mini_app_path": "worldapp://mini-app?app_id=[app_id]&path=[path]" }' ``` when sent to users, the message becomes:\ `🧑‍🍳 We're cooking something special for you mistico`\ `🧑‍🍳 We're cooking something special for you tute`\ `🧑‍🍳 We're cooking something special for you struck`\ and so on.
## Notification Badges If a user has pending notifications for your miniapp, and you maintain a 7 day rolling average open rate of 15% or higher, we'll show a badge on the user's home screen, next to your miniapp's icon. It's cleared once the miniapp is opened. Notification badges increase your miniapp's visibility on the home screen, leading to higher user engagement and traffic.
Notification Interface in the Developer Portal
## Open Rate Goal An excellent goal to aim for is a 25% open rate. This % means your notifications drive strong traffic to the miniapp. You can see this stat in your dashboard in Developer Portal. ## Use Emoji & Curiosity in Titles Increases visual salience and emotion.\ Examples:\ `🔥 You're missing out on rewards`\ `🕹️ A new level just unlocked`\ `🤔 What's waiting inside the vault today?` ## Useful links * [How To Send Notifications](/mini-apps/commands/how-to-send-notifications) * [How To Request Notification Permissions](/mini-apps/commands/request-permission) * [How To Get Notification Permissions](/mini-apps/commands/get-permissions) * [Send Notification API Reference](/api-reference/developer-portal/send-notification) # App Review Guidelines Source: https://docs.world.org/mini-apps/guidelines/policy World App seeks to foster a diverse and flourishing ecosystem of applications while at the same time ensuring users stay safe, and privacy is maximized. These applications are displayed to users within World App, but users interact with these applications either within a web browser or as a platform native application. World App has sole discretion of determining how apps are presented to users within its platform. These guidelines dictate the rules for apps which seek to be displayed to users from within World App. ## MiniKit Integration Integrate MiniKit to supercharge your web app with exclusive features like World ID and Wallet access, making your mini app more engaging and valuable to users. To get your mini app approved, it's essential to use the MiniKit SDK commands effectively to enhance the user experience. We're looking for meaningful integrations, whether through *World ID* and *Wallet access* , or other creative uses that add real value. ## Safety The following content is not permitted for apps: * **Objectionable Content:** * Defamatory, discriminatory, or mean-spirited content, including references or commentary about religion, race, sexual orientation, gender, national/ethnic origin, or other targeted groups * Realistic portrayals of people or animals being killed, maimed, tortured, or abused, or content that encourages violence. * Depictions that encourage illegal or reckless use of weapons and dangerous objects, or facilitate the purchase of firearms or ammunition. * Overtly sexual or pornographic material, defined as "explicit descriptions or displays of sexual organs or activities intended to stimulate erotic rather than aesthetic or emotional feelings." * NSFW (Not Safe For Work) content of any kind, including bots or AI generators that can produce, distribute, or facilitate access to NSFW content. * Inflammatory religious commentary or inaccurate or misleading quotations of religious texts. * False information and features, including inaccurate device data or trick/joke functionality, such as fake location trackers. * Harmful concepts which capitalize or seek to profit on recent or current events, such as violent conflicts, terrorist attacks, and epidemics. * Impersonating TFH or Worldcoin. * **Physical Harm** * If your app behaves in a way that risks physical harm, we may reject it. * Apps that encourage consumption of tobacco and vape products, illegal drugs, or excessive amounts of alcohol are not permitted. ## Naming Apps must comply with the below naming and description guidelines. * **App naming:** * Keep names short and memorable for optimal recall and display. * Don't use "World" in the name. * Don't use descriptions as Mini App names. * Don't use generic functional terms like "Earn", "Swap", etc. as your Mini App name. * Don't try to mimic the name of a popular app, as this can lead to confusion. Apps may be rejected or penalized if their names mimic popular brands by simply swapping numbers or case. * Do not include trademarked words, names of other apps, or explicit content. * Exclude the use of special characters, or emojis. * Ensure the name works well in your primary languages, and isn't offensive in other major markets. * **App description:** * Use plain, approachable language that clearly explains what the Mini App does. * Describe the benefit or purpose in a straightforward way. * Avoid spammy or misleading descriptions. * Keep the tone aligned with World's voice: simple, human, and trustworthy. Avoid exaggerated, tech-heavy, or overly promotional phrasing. * Write descriptions that are respectful, globally understandable, and free from language that might be offensive in other regions. * Don't list functionality like "Earn", "Swap", or "Send" as standalone descriptions. Instead, explain how the app helps the user in their everyday life. * Aim for one or two short sentences (under 25 words total) that communicate the app's value quickly. Users should understand the purpose at a glance e.g. Join real local meetups. No accounts, no spam, just one human per invite. ## Legal Apps must comply with all legal requirements to be listed within World App. This includes the following: * **Privacy:** * **Consent**: All apps that collect and store data from the user must request access and gain approval from the user before doing so * **Data Minimization:** Apps should only request access to data that is relevant to how the app functions * **Regulatory Compliance:** The application itself is responsible for maintaining regulatory compliance in all jurisdictions where it selects to be shown to users. Although the burden of compliance rests on the submitted application, if TFH determines that an application is non-compliant with jurisdiction(s) it has a right to take down the application and ask for it to be re-submitted in jurisdictions where it is compliant ## App Submission Applications must be submitted for review on the developer platform. Before submission, please ensure the following: * Test your app for bugs * Ensure that your app contains a live integration with a Worldcoin SDK, either IDkit or MiniKit * Ensure that all app information in your dev portal submission is complete and accurate * Ensure your contact information is updated so the review team can contact you * Ensure your app can be accessed by the review team for testing * Ensure your app complies with laws and regulations in the jurisdictions where it is shown to users. * Check whether your app follows the [app guidelines](/mini-apps/guidelines/app-guidelines) * Check whether your app follows the [smart contract development guidelines](/mini-apps/guidelines/smart-contract-development-guidelines) (if applicable) * Check whether your app follows the [notification guidelines](/mini-apps/guidelines/features-and-guidelines) (if applicable) Ensure your app does not violate any of these guidelines, otherwise your app will be rejected. ## App Review The review team has sole discretion of approving applications for Mini Apps. Apps will be reviewed as quickly as possible, though if the app is complex or difficult to test it may take some time. You will receive an email if the review status of your app changes, and you can view updates in the developer portal as well. Tools for Humanity will approve all submitted apps if it deems the following are true: * The data submitted in the form is complete and accurate * The app is complete and contains all necessary copy and functionality to fulfil it’s purpose outlined in the submission * The app is a final version, and is not a demo, trial or beta version * The app contains a live integration with IDkit or MiniKit that functioned properly when tested * The app abides by the safety and legal guidelines The review team will provide a rationale for any rejections, to which the developer can re-submit after remediating any concerns. If the application continuously fails review for the same reason, it may take longer for subsequent reviews to occur. Additionally, World App provides users the ability to report apps. If an app is in violation of the safety and legal guidelines or the review team deems the app should be removed for other reasons, the team has the sole discretion to remove an app from the platform. If an app is removed from the platform or an investigation is in progress, the development team will be alerted. ## Technical Requirements * On Android and iOS, the World App Mini App should support operation under poor internet connections and handle temporary disconnections properly. * The World App Mini App must be reliable, with no infinite loading during non-standard user actions. * The World App Mini App must comply with the rules of both the Android and iOS app stores. * The World App Mini App must not contain features that are unavailable on certain platforms. * User progress must synchronize seamlessly between different platform versions. ## User Support * Developers must provide a valid email address for user support and ensure accessible means of communication for resolving any issues that arise. # Smart Contract Development Guidelines Source: https://docs.world.org/mini-apps/guidelines/smart-contract-development-guidelines These guidelines define the minimum requirements for developing smart contracts for deployment & use within the World App Mini App Store. They are designed to protect users from malicious or negligent behaviors, especially in contracts that custody user funds. ## Applicability These requirements apply to **all** smart contracts submitted to the Mini App Store that **custody, lock, or manage user-owned assets** (fungible tokens, NFTs, or other on-chain value). Failure to comply may result in rejection or removal from the platform. ## Custody & Upgradeability Rules ### Non-Upgradeable Custody Contracts * **Requirement:** Custody contracts must be **immutable** after deployment. ### Controlled Upgradeability (If Needed) * If upgradeability is essential (e.g., for bug fixes), it must: * Use a **multi-signature upgrade authorization** where **Tools For Humanity (TFH)** holds **1 of 2 keys**. * Require **TFH review and written approval** before any upgrade is executed. * Be subject to a **public notice period** (recommended: 48–72 hours) before upgrade execution. ## Restricted Owner Privileges ### Prohibition of User Asset Withdrawal by Owner * **No direct owner/admin functions** may exist that allow the developer (or any third party) to: * Transfer, withdraw, or seize assets deposited by users. * Change accounting logic in a way that reassigns user balances to the developer. ### Exceptions * **Permitted:** Functions to withdraw **protocol fees** or **platform earnings** that are: * Clearly documented. * Separately accounted for from user funds. * Agreed upon during code review. ## Code Quality & Review Requirements ### Testing * Contracts must include **comprehensive automated tests**, ideally in a **Foundry** or similar high-quality test framework. * Tests must cover: * Normal operation flows. * Edge cases and failure modes. * Security-critical logic (access control, withdrawals, deposits, upgrades). * Code coverage target: **≥90%** for critical custody logic. ### Repository Access * Source code must be stored in a **version-controlled repository**. * The repository must be **shared with the TFH review team** prior to deployment. ### Documentation * Contracts must be **fully documented** with: * **NatSpec comments** for all public/external functions. * Clear explanation of any access control roles and permissions. * Rationale for any upgradeability or special privilege mechanisms. ## Security Best Practices ### Recommended Patterns * Inherit [**OpenZeppelin contracts**](https://github.com/OpenZeppelin/openzeppelin-contracts) for all token/contract standards * Follow [**Checks-Effects-Interactions**](https://fravoll.github.io/solidity-patterns/checks_effects_interactions.html) pattern ubiquitously ### Audits * High-value custody contracts are **strongly recommended** to undergo an **independent security audit**. * Audit reports should be shared with TFH prior to mainnet deployment. ### Common Pitfalls to Avoid * No unbounded loops over user-controlled data. * No hardcoded privileged addresses. * Avoid arbitrary external call execution (`call`, `delegatecall`) in external facing functions unless strictly necessary and reviewed. ## Compliance & Enforcement * Contracts failing to comply with these guidelines may be: * Rejected during review. * Removed from the Mini App Store. * TFH reserves the right to request code changes or audits before approval. # What are Mini Apps? Source: https://docs.world.org/mini-apps/index Learn what Mini Apps are and how to build native-like apps in World App with MiniKit, distribution and monetization. Mini apps enable third-party developers to create native-like applications within World App. Best for small and medium sized developers who want access to a distribution channel of millions of users. Build with flexible smart contracts and leverage World ID and the World Wallet for seamless user experiences. ## How it Works Mini apps are web applications running inside World App via webview. Using the MiniKit SDK, these applications can become native-like and interact with the World ecosystem. ## Quick links Statistics about the Mini Apps ecosystem. Check the current status of World App. # MiniKit 2.0 Migration Source: https://docs.world.org/mini-apps/migration/minikit-v2 MiniKit 2.x consolidates command handling around async `MiniKit` methods and removes World ID verification from MiniKit. ## Breaking Changes * World ID verification moved out of MiniKit and into [`@worldcoin/idkit`](/world-id/idkit/mini-apps) * Commands moved to top-level async methods on `MiniKit` instead of `commandsAsync` and `commands` * Response interface changed to `{ executedWith, data }` * Types and helpers moved to `@worldcoin/minikit-js/commands`, `@worldcoin/minikit-js/siwe`, and `@worldcoin/minikit-js/address-book` * `walletAuth` nonce validation is stricter and expects an alphanumeric nonce without hyphens * `signTypedData` deprecated * `sendTransaction` now takes encoded calldata `transactions` and returns `userOpHash` * Permit2 switched from SignatureTransfer to AllowanceTransfer * Standard ERC-20 `approve()` calls are now allowed, and approval will be revoked after the transaction ## World ID Changes Any old `MiniKit.verify` or `MiniKit.commandsAsync.verify` flow should be replaced with IDKit. MiniKit 2.x only owns mini app commands. See [IDKit for Mini Apps](/world-id/idkit/mini-apps) for the current integration shape. ## Old To New ```ts theme={"system"} // 1.x MiniKit.commands.signMessage({ message: "hello" }); await MiniKit.commandsAsync.walletAuth({ nonce }); await MiniKit.commandsAsync.sendTransaction({ transaction: [tx] }); // 2.x await MiniKit.signMessage({ message: "hello" }); await MiniKit.walletAuth({ nonce }); await MiniKit.sendTransaction({ chainId: 480, transactions: [{ to, data, value }], }); ``` ## Import Path Changes ```ts theme={"system"} import type { MiniKitSendHapticFeedbackOptions } from "@worldcoin/minikit-js/commands"; import { getIsUserVerified } from "@worldcoin/minikit-js/address-book"; import { verifySiweMessage } from "@worldcoin/minikit-js/siwe"; ``` ## `walletAuth` Changes * Validation now requires you to strip hyphens from UUID-based nonces ```ts theme={"system"} const nonce = crypto.randomUUID().replace(/-/g, ""); ``` ## `sendTransaction` Migration * No longer uses abi encoding and instead expects pre-encoded calldata in the `transactions` array * Poll transaction progress with `userOpHash` ```ts theme={"system"} const result = await MiniKit.sendTransaction({ chainId: 480, transactions: [ { to: "0x...", data: "0x...", value: "0x0", }, ], }); console.log(result.data.userOpHash); ``` # Upgrade to a Standalone Web App Source: https://docs.world.org/mini-apps/migration/standalone-dapp Convert your mini app into a standalone web app that works both inside World App and in any browser. MiniKit commands auto-detect the environment. Outside World App, they fall back to Wagmi. | Feature | Effort | Details | | -------------- | --------------------------- | --------------------------------- | | World ID | None | IDKit works out of the box | | Auth | Add Wagmi + providers | Wagmi handles SIWE on web | | Transactions | Add Wagmi + branch receipts | Hash type differs per environment | | Other commands | Add `fallback` function | Custom logic per command | ## Ask an agent Add this skill and ask your agent to convert your web app to a mini app using the steps outlined in this guide. ``` npx skills add worldcoin/minikit-js miniapp-to-web ``` ## 1. Install Dependencies ```bash theme={"system"} pnpm add wagmi @tanstack/react-query siwe ``` ## 2. Wagmi Config ```ts title="config.ts" theme={"system"} import { worldApp } from "@worldcoin/minikit-js/wagmi"; import { createConfig, http } from "wagmi"; import { worldchain } from "wagmi/chains"; import { injected } from "wagmi/connectors"; export const config = createConfig({ chains: [worldchain], connectors: [worldApp(), injected()], transports: { [worldchain.id]: http(), }, }); ``` ## 3. Providers ```tsx title="providers.tsx" theme={"system"} "use client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { MiniKitProvider } from "@worldcoin/minikit-js/minikit-provider"; import { WagmiProvider } from "wagmi"; import { config } from "./config"; const queryClient = new QueryClient(); export default function Providers({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` The `worldApp()` connector automatically registers the wagmi fallback. If your app uses wagmi **without** the `worldApp()` connector (e.g. a pure-web setup that still wants MiniKit's fallback), you need to explicitly register it: ```ts theme={"system"} import "@worldcoin/minikit-js/wagmi-fallback"; ``` Or call `registerWagmiFallback(config)` from the same subpath. ## World ID No changes. [IDKit](https://docs.worldcoin.org/world-id/quick-start) is independent of the wallet layer. ## Auth `MiniKit.walletAuth()` works automatically. No code changes needed. ```tsx theme={"system"} const result = await MiniKit.walletAuth({ nonce, statement: "Sign in" }); // result.executedWith === "minikit" (World App) or "wagmi" (web) ``` ### Backend Verification `verifySiweMessage` handles both Smart Accounts (EIP-1271) and EOAs (ECDSA) automatically: ```ts title="api/verify.ts" theme={"system"} import { verifySiweMessage } from "@worldcoin/minikit-js/siwe"; const { isValid, siweMessageData } = await verifySiweMessage(payload, nonce); ``` ## Transactions `MiniKit.sendTransaction()` works automatically. World Chain only (chainId 480). * **World App**: native bridge, atomic batching, returns `userOpHash` * **Web (single tx)**: sent directly via Wagmi * **Web (multiple txs)**: executed sequentially — each requires wallet confirmation and is not atomic ```tsx theme={"system"} const result = await MiniKit.sendTransaction({ chainId: 480, transactions: [ { to: "0x...", data: encodeFunctionData({ abi, functionName: "mint", args: [] }) }, ], }); ``` ### Receipts Branch on `result.executedWith` — World App returns a UserOperation hash, web returns a standard tx hash: ```tsx theme={"system"} import { useUserOperationReceipt } from "@worldcoin/minikit-react"; // For World App: poll for the UserOperation receipt const { poll } = useUserOperationReceipt({ client }); if (result.executedWith === "minikit") { await poll(result.data.userOpHash); } else { // Web: standard tx hash, use wagmi or viem directly await publicClient.waitForTransactionReceipt({ hash: result.data.userOpHash as `0x${string}`, }); } ``` ## Other Commands Commands without Wagmi fallbacks (`pay`, `shareContacts`, etc.) need a `fallback`: ```tsx theme={"system"} const result = await MiniKit.pay({ amount: "1.00", token: "USDC", reference: "order-123", description: "Coffee", fallback: async () => myCustomPaymentFlow(), }); ``` Without a fallback, these throw `CommandUnavailableError` on web. # Migrate a Web App to Mini App Source: https://docs.world.org/mini-apps/migration/web-to-miniapp Convert an existing Next.js web app to work as a World App mini app. Convert an existing Next.js web app that uses viem to work as a World App mini app. ## Ask an agent Add this skill and ask your agent to convert your web app to a mini app using the steps outlined in this guide. ``` npx skills add worldcoin/minikit-js web-to-miniapp ``` ## 1. Install MiniKit ```bash theme={"system"} pnpm add @worldcoin/minikit-js @worldcoin/minikit-react ``` ## 2. Disable SSR MiniKit depends on `window.WorldApp`. SSR causes hydration mismatches that silently break event handlers. ```tsx title="src/app/page.tsx" theme={"system"} "use client"; import dynamic from "next/dynamic"; const App = dynamic(() => import("../components/App"), { ssr: false }); export default function Page() { return ; } ``` ## 3. Add MiniKitProvider ```tsx title="src/app/providers.tsx" theme={"system"} "use client"; import { MiniKitProvider } from "@worldcoin/minikit-js/minikit-provider"; export default function Providers({ children }: { children: React.ReactNode }) { return {children}; } ``` Wrap children in your layout: ```tsx title="src/app/layout.tsx" theme={"system"} import Providers from "./providers"; {children} ``` ## 4. Wallet Connection Use `getWorldAppProvider()` inside World App, fall back to `window.ethereum` for browsers. ```tsx theme={"system"} import { MiniKit, getWorldAppProvider } from "@worldcoin/minikit-js"; import { createWalletClient, custom } from "viem"; import { worldchain } from "viem/chains"; const provider = MiniKit.isInWorldApp() ? getWorldAppProvider() : window.ethereum; const walletClient = createWalletClient({ chain: worldchain, transport: custom(provider), }); ``` Under the hood, `getWorldAppProvider()` maps: * `eth_requestAccounts` → `MiniKit.walletAuth()` * `eth_sendTransaction` → `MiniKit.sendTransaction()` * `eth_chainId` → `0x1e0` (World Chain 480) Existing `writeContract` / `readContract` calls work unchanged. ## 5. Bundle Approve + Contract Calls World App resets approvals to 0 after each transaction. A separate `approve()` tx followed by `transferFrom()` in the next tx will fail. Bundle them in one call: ```tsx theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import { encodeFunctionData } from "viem"; await MiniKit.sendTransaction({ chainId: 480, transactions: [ { to: TOKEN, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [CONTRACT, amount], }), }, { to: CONTRACT, data: encodeFunctionData({ abi: contractAbi, functionName: "swap", args: [amount], }), }, ], }); ``` World App executes these atomically. On web, they execute sequentially — each requires a separate wallet confirmation and is not atomic. ## 6. Handle userOpHash Receipts MiniKit returns a `userOpHash`, not a standard tx hash. Use `useUserOperationReceipt` from `@worldcoin/minikit-react` to poll for the receipt: ```tsx theme={"system"} import { useUserOperationReceipt } from "@worldcoin/minikit-react"; import { createPublicClient, http } from "viem"; import { worldchain } from "viem/chains"; const client = createPublicClient({ chain: worldchain, transport: http(), }); const { poll, isLoading } = useUserOperationReceipt({ client }); // After sendTransaction: const result = await MiniKit.sendTransaction({ ... }); await poll(result.data.userOpHash); ``` ## 7. Whitelist Contracts and Tokens In **Developer Portal > Mini App > Permissions**, add: * **Permit2 Tokens** — every ERC-20 your app transfers * **Contract Entrypoints** — every contract your app calls Non-whitelisted contracts are blocked with `invalid_contract`. ## Common Gotchas | Issue | Symptom | Fix | | -------------------------- | ------------------------------------- | ------------------------------------------------------------- | | SSR hydration mismatch | Clicks do nothing | `dynamic(..., { ssr: false })` | | Approval reset after tx | `transferFrom` reverts | Bundle approve + call in one `sendTransaction` | | `userOpHash` not a tx hash | `waitForTransactionReceipt` times out | Use `useUserOperationReceipt` from `@worldcoin/minikit-react` | | Missing contract whitelist | `invalid_contract` error | Add to Developer Portal | # Community Perks Source: https://docs.world.org/mini-apps/more/community-tools-perks Special perks and integrations from trusted providers supporting the World Mini App ecosystem. Pilot program rewarding qualifying Mini App developers based on verified human usage. \$300K USD equivalent in WLD over three months. Foundation Grants supporting the World Network and novel mini apps (50M WLD dedicated). [Learn more](https://world.org/grants) 4 months free for Enterprise, 3 months free for PAYG, $500–$2000 in bonus incentives, plus co-marketing and onsite opportunities Apply for Cloudflare for Startups program. Use 'World Mini App' as your partner when applying. 15% discount on any Lazo product, plus 6 months free of Lazo One (Data Room * AI for fundraising). 30 day free trial for all builders on World Chain. Use code: TW-WORLDCHAIN # FAQ Source: https://docs.world.org/mini-apps/more/faq ## Who covers gas fees for transactions? World App sponsors gas fees for most transactions on Worldchain, subject to transaction minimums and restrictions. When someone verifies their identity with World ID and uses the World App, their transactions on Worldchain are automatically covered by the network. This means users don't need to hold or spend ETH to interact with mini apps, send tokens, or perform on-chain actions. The sponsorship is handled behind the scenes with a paymaster contract that pays the gas fees whenever a verified user initiates a transaction. ## Do I need approval to launch a Mini App? Yes. After development, submit your Mini App for review through the [Developer Portal](https://developer.worldcoin.org/). Approval is required before it's publicly listed. ## How does Send transaction command work? The [Send Transaction command](/mini-apps/commands/send-transaction) is designed to execute on-chain transactions from the Mini App. When developers invoke this command, they supply a `chainId` and one or more `transactions` containing the target contract address, optional encoded function call (`data`), and optional value. For security, World App requires that any smart contract or token interactions are pre-approved (whitelisted) in the Developer Portal to prevent unauthorized transfers. For ERC-20 token transfers, standard `approve()` calls work, but Permit2 is recommended. World App automatically approves tokens to the Permit2 contract, so you can bundle the Permit2 approval and your contract call in a single transaction for a better UX. ## How does Sign Message command work? The [Sign Message command](/mini-apps/commands/sign-message) prompts the user to cryptographically sign an arbitrary message using their wallet's private key. When you call the command (for example, `await MiniKit.signMessage({ message: "Hello" })`), World App generates a standardized payload that may include a nonce, timestamp, and other context to prevent replay attacks. The user's wallet then signs this message using ECDSA, producing a signature that proves the user controls the wallet, all without exposing the private key. This signed message can be sent back to your backend for verification, ensuring the integrity of user actions and serving as secure off-chain authorization for various operations. ## Can I use the simulator to test transactions on mini apps? No, mini app needs to be developed on mainnet (we don't support testnet). Gas is covered in the World App, so there's only deployment costs for you to develop on mainnet. Deploy "test" contracts to mainnet, and then redeploy "prod" mainnet contracts. ## How do I debug send transaction failed simulation? Use the [Get Transaction Debug URL endpoint](/api-reference/developer-portal/get-transaction-debug-url) to get the debug URL for the transaction. ## How do I check how commands work and it's implementation? Check the [Prod QA App](https://world.org/ecosystem/app_dfbe55706a640c82dce839bb0ecae74d) to check commands and the [minikit-js](https://github.com/worldcoin/minikit-js) repository. ## How do I test my mini app on mobile? Remember that minikit ONLY works inside World App, so in order to test your mini app commands you must open it in World App To test your Mini App directly on your phone, expose your app publicly using ngrok or any other tunneling service. After the URL is generated, go to the [Developer Portal](https://developer.worldcoin.org/) to configure it. Example: ```bash theme={"system"} ngrok http http://localhost:3000 ``` ## Why does my command fail when triggered immediately on page load? This happens due to a race condition where MiniKit hasn't finished installing when you try to call a command. Since MiniKit uses a client-side component to install the provider onto the Window object, triggering a command in a separate useEffect hook right after page load can fail. Solution: Include any commands you want to trigger on initialization inside the same useEffect hook where you install MiniKit: ```tsx theme={"system"} "use client"; import { useEffect, ReactNode } from "react"; import { MiniKit } from "@worldcoin/minikit-js"; export const MiniKitProvider = ({ children }: { children: ReactNode }) => { useEffect(() => { const { success } = MiniKit.install(); if (!success) return; // Add any commands you wish to trigger on start here to prevent race conditions void MiniKit.getPermissions(); }, []); return <>{children}; }; ``` ## What security considerations should I keep in mind when building Mini Apps? MiniKit is purely a communication channel between the client and the app. Your application should never trust any payloads it receives on the client side by default. World ID verification via IDKit, Pay, and Wallet Auth operations should be verified on your backend. Never rely solely on client-side validation for sensitive operations, as client-side data can be manipulated. Always implement server-side verification to ensure the integrity and authenticity of user actions. # Webview Specifications Source: https://docs.world.org/mini-apps/more/webview-spec The widget is opened within the World App via a WebView. This means providers can tailor their solutions by considering the specific features and restrictions of these platforms. ### **Capabilities:** * **WebView Engine:** * **Android:** Uses Android's native WebView implementation. * **iOS:** Uses **WKWebView**, the recommended web rendering engine on iOS, offering enhanced security and performance. * **File System and Camera Access:** * Access to the camera and file system (e.g., for file uploads) is possible if the user grants permission. * **Cookies and DOM Storage:** * Supported on both platforms with explicit activation for Android and default behavior for iOS. * **Location:** * Location is supported on both platforms. ```javascript theme={"system"} navigator.geolocation.getCurrentPosition((position) => { console.log(position); }); ``` ### **Restrictions:** * **New Windows:** * Opening new browser windows is prohibited. All navigation remains within the current WebView instance. * **Zooming:** * **Android:** Not restricted by default. * **iOS:** Disabled. * **Alert Dialogs:** * Alert Dialogs are not supported on iOS # Mini App Store Source: https://docs.world.org/mini-apps/quick-start/app-store Submit your Mini App for review and distribution in World App; follow guidelines and resolve rejections. To ensure a consistent user experience, apps will be rejected unless they follow our [guidelines](/mini-apps/guidelines/app-guidelines). Once your mini app is ready to be published, you can submit it for review inside of the Developer Portal. Once approved, your mini app will be available to all World App users to discover in the Mini Apps. Submit an App If your app was rejected please reach out to @MateoSauton on Telegram for more information. # Commands Source: https://docs.world.org/mini-apps/quick-start/commands Overview of current MiniKit commands for mini apps. Commands are async methods on `MiniKit`. Use `await MiniKit.()` and handle the returned `{ executedWith, data }` result. World ID verification is no longer a MiniKit command. Use [`@worldcoin/idkit`](/world-id/idkit/mini-apps) for new verification flows. Try our preview mini app to get a sense of how commands work. Scan the QR code below with your phone (you must have World App installed).
Command Description
Pay Request a payment inside World App
Wallet Auth Authenticate with Sign-In with Ethereum
Send Transaction Submit one or more World Chain transactions
Sign Message Sign an EIP-191 personal message
Sign Typed Data Sign an EIP-712 typed payload
Share Contacts Open the World App contact picker
Request Permission Request notifications or microphone access
Get Permissions Read current mini app permission state
Send Haptic Feedback Trigger native haptic feedback
Share Open the native share sheet
World Chat Open World Chat with a prefilled message
Attestation Request an app attestation token
Close Mini App Programmatically close the mini app
Push notifications are documented separately in [Send Notifications](/mini-apps/commands/how-to-send-notifications), since they combine permission handling with Developer Portal setup. # Initialization Source: https://docs.world.org/mini-apps/quick-start/init When your mini app initializes inside World App, MiniKit stores device context, launch context, and basic user metadata on the client. If you are using React, `MiniKitProvider` will perform the initialization for you. Otherwise, manually initialize MiniKit at the start of your app: ```tsx theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; const { success } = MiniKit.install(); ``` ## MiniKit State After install, these are the public MiniKit state accessors you can rely on: ```tsx theme={"system"} // MiniKit state { user: { walletAddress?: string; username?: string; profilePictureUrl?: string; permissions?: { notifications: boolean; contacts: boolean; }; optedIntoOptionalAnalytics?: boolean; verificationStatus?: { isOrbVerified: boolean; isDocumentVerified: boolean; isSecureDocumentVerified: boolean; }; preferredCurrency?: string; pendingNotifications?: number; }; deviceProperties: { safeAreaInsets?: { top: number; right: number; bottom: number; left: number; }; deviceOS?: string; worldAppVersion?: number; }; location: "chat" | "home" | "app-store" | "deep-link" | "wallet-tab" | null; } ``` Notes: * `walletAddress`, `verificationStatus`, `preferredCurrency`, `pendingNotifications`, and `optedIntoOptionalAnalytics` are all available at initialization. * `username` and `profilePictureUrl` are populated after `walletAuth()`. * `MiniKit.location` is the mapped launch location. Use this instead of reading older launch-origin fields directly. ## Permissions `MiniKitProvider` installs MiniKit, but it does not automatically fetch permission state. If you need the current permission settings, call `MiniKit.getPermissions()` explicitly: ```tsx theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import type { MiniAppGetPermissionsSuccessPayload } from "@worldcoin/minikit-js/commands"; const result = await MiniKit.getPermissions(); const permissions: MiniAppGetPermissionsSuccessPayload["permissions"] = result.data.permissions; ``` Notes: * `MiniKit.user.permissions` is cached MiniKit state and should be treated as incomplete until you fetch permissions. * `getPermissions()` can return `notifications`, `contacts`, and `microphone`. * The cached `MiniKit.user.permissions` shape only exposes `notifications` and `contacts`. ## Launch Location MiniKit normalizes the raw World App launch origin into: ```tsx theme={"system"} type MiniAppLaunchLocation = | "chat" | "home" | "app-store" | "deep-link" | "wallet-tab" | null; ``` ```tsx theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; if (MiniKit.location === "chat") { console.log("Opened from chat"); } ``` ## Raw World App Object If you need the untransformed World App payload, read `window.WorldApp` directly. ```tsx theme={"system"} // window.WorldApp { world_app_version: number; device_os: "ios" | "android"; is_optional_analytics: boolean; wallet_address: string; verification_status: { is_orb_verified: boolean; is_document_verified: boolean; is_secure_document_verified: boolean; }; preferred_currency: string; pending_notifications: number; supported_commands: Array<{ name: | "verify" | "attestation" | "pay" | "wallet-auth" | "send-transaction" | "sign-message" | "sign-typed-data" | "share-contacts" | "request-permission" | "get-permissions" | "send-haptic-feedback" | "share" | "chat" | "close-miniapp" | "microphone-stream-started" | "microphone-stream-ended"; supported_versions: number[]; }>; safe_area_insets: { top: number; right: number; bottom: number; left: number; }; location: { open_origin: string; } | null | undefined; } ``` ```json theme={"system"} { "world_app_version": 4001000, "device_os": "ios", "is_optional_analytics": true, "wallet_address": "0x377da9cab87c04a1d6f19d8b4be9aef8df26fcdd", "verification_status": { "is_orb_verified": true, "is_document_verified": true, "is_secure_document_verified": false }, "preferred_currency": "USD", "pending_notifications": 0, "supported_commands": [ { "name": "verify", "supported_versions": [1] }, { "name": "attestation", "supported_versions": [1] }, { "name": "pay", "supported_versions": [1] }, { "name": "wallet-auth", "supported_versions": [1, 2] }, { "name": "send-transaction", "supported_versions": [1, 2] }, { "name": "sign-message", "supported_versions": [1] }, { "name": "sign-typed-data", "supported_versions": [1] }, { "name": "share-contacts", "supported_versions": [1] }, { "name": "request-permission", "supported_versions": [1] }, { "name": "get-permissions", "supported_versions": [1] }, { "name": "send-haptic-feedback", "supported_versions": [1] }, { "name": "share", "supported_versions": [1] }, { "name": "chat", "supported_versions": [1] }, { "name": "close-miniapp", "supported_versions": [1] }, { "name": "microphone-stream-started", "supported_versions": [1] }, { "name": "microphone-stream-ended", "supported_versions": [1] } ], "safe_area_insets": { "top": 0, "right": 0, "bottom": 0, "left": 0 }, "location": { "open_origin": "deeplink" } } ``` Use `window.WorldApp` only when you need the raw payload. In application code, prefer `MiniKit.user`, `MiniKit.deviceProperties`, and `MiniKit.location`. # Getting Started Source: https://docs.world.org/mini-apps/quick-start/installing Create a Mini App with the official template or install MiniKit-JS manually. [MiniKit-JS](https://github.com/worldcoin/minikit-js) is our official SDK for creating mini apps that work with World App. ## Quick Start The fastest way to get started is by using our template next-15 repository. Run the following command and follow the instructions to create a new mini app. For cleanliness we recommend using `pnpm` as your package manager. ```bash theme={"system"} npx @worldcoin/create-mini-app@latest my-first-mini-app ```
Template

Correctly running template should look like this

## Manual Installation [MiniKit-JS](https://www.npmjs.com/package/@worldcoin/minikit-js) is the core library and is framework agnostic. Install it with `pnpm`, or use a CDN like [jsdelivr](https://www.jsdelivr.com/package/npm/@worldcoin/minikit-js) for inline HTML. Replace `[version]` with the release you want to load. ```bash title="pnpm" theme={"system"} pnpm install @worldcoin/minikit-js ``` ```html title="jsDelivr CDN" theme={"system"} ``` ## Usage 1. Wrap your app with `MiniKitProvider` in a client component. This initializes MiniKit and makes it available throughout your app. ```tsx src/app/providers.tsx theme={"system"} "use client"; import { MiniKitProvider } from "@worldcoin/minikit-js/minikit-provider"; export default function Providers({ children, }: { children: React.ReactNode; }) { return {children}; } ``` ```tsx src/app/layout.tsx theme={"system"} import Providers from "./providers"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` 2. Check if MiniKit is installed. `MiniKit.isInstalled()` will only return true if the mini app is opened and initialized inside the World App. This is useful to distinguish between a user opening your app in the browser or in the World App. ```tsx theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; console.log(MiniKit.isInstalled()); ``` ## Build with AI The [World Docs MCP](/model-context-protocol/world-docs) lets any coding assistant search the World documentation to help you build your mini app. ```bash theme={"system"} claude mcp add --transport http world https://docs.world.org/mcp ``` ```bash theme={"system"} codex mcp add --transport http world https://docs.world.org/mcp ``` Add to `.cursor/mcp.json`: ```json theme={"system"} { "mcpServers": { "world": { "url": "https://docs.world.org/mcp" } } } ``` Add to `.vscode/mcp.json`: ```json theme={"system"} { "servers": { "world": { "type": "http", "url": "https://docs.world.org/mcp" } } } ``` ## Template Repositories The following template repositories are also available: * [Vanilla JS (using a CDN) template (featuring a simple backend for verifications)](https://github.com/new?template_name=minikit-js-template\&template_owner=worldcoin), * [Community example - Wallet Auth using JWT](https://github.com/wlding-blocks/wld-mini-apps-101). * [Community example - Wallet Auth using NextAuth](https://github.com/supercorp-ai/minikit-wallet-auth-next-auth). Otherwise, continue below with the installation instructions. Watch a video tutorial [here](https://www.youtube.com/watch?v=QJ0htHP6lb0). # Responses Source: https://docs.world.org/mini-apps/quick-start/responses Handle MiniKit command results with `{ executedWith, data }` and standard error handling. Modern MiniKit commands are async methods on `MiniKit`. They resolve to a result object shaped like `{ executedWith, data }` and throw if the command fails. ## Async Command Results Use `executedWith` to understand whether the command ran through World App, Wagmi, or a custom fallback. ```tsx theme={"system"} import { MiniKit } from "@worldcoin/minikit-js"; import type { CommandResultByVia, MiniKitWalletAuthOptions, WalletAuthResult, } from "@worldcoin/minikit-js/commands"; async function signInWithWallet() { const input = { nonce: "random-nonce-123", } satisfies MiniKitWalletAuthOptions; try { const result: CommandResultByVia = await MiniKit.walletAuth(input); console.log(result.executedWith); // "minikit" | "wagmi" | "fallback" console.log(result.data.address); console.log(result.data.signature); } catch (error) { console.error("Command failed", error); } } ``` ## What To Expect * `executedWith` tells you which runtime handled the command * `data` contains the success payload for that command * command-specific failures are thrown, so use `try/catch` * some commands still require backend verification even after a successful response, such as `walletAuth` and `pay` ## Event Subscriptions For new 2.x command integrations, prefer `await MiniKit.()`. # Testing your mini app Source: https://docs.world.org/mini-apps/quick-start/testing Enter your App ID and scan the generated QR code to test your mini app. # Testing ### Testing your mini app Enter your app id in the text box below and scan the QR code generated with your phone's camera. Your app id is in the developer portal in the format `app_xxxxxxxxxx`. ### Tips 1. You can use [Ngrok](https://ngrok.com/), [zrok](https://zrok.io/) or [tunnelmole](https://tunnelmole.com/) for local testing. 2. [Eruda](https://github.com/liriliri/eruda) is helpful for showing logs on mobile. 3. You can use the [L2 Faucet](https://www.l2faucet.com/world) to get testnet WLD on Sepolia. # Address Book Source: https://docs.world.org/mini-apps/reference/address-book The Address Book is a contract that stores verified World ID addresses. You can check if a user's address and associated ENS name (if available) is Orb verified using the `getIsUserVerified` helper function. ## Considerations * Default RPC is `https://worldchain-mainnet.g.alchemy.com/public` * Contract Address is [0x57b930D551e677CC36e2fA036Ae2fe8FdaE0330D](https://worldscan.org/address/0x57b930D551e677CC36e2fA036Ae2fe8FdaE0330D#readContract). ## Implementation The helper function connects to the World Chain and checks if a given wallet address is verified by querying the Address Book contract. ## Example Usage ```typescript theme={"system"} import { getIsUserVerified } from "@worldcoin/minikit-js/address-book" const userWalletAddress = "0x000000000000000000000000000000000000dEaD" const isUserVerified = await getIsUserVerified(userWalletAddress) // optionally you can provide your rpc url as a second argument to the function ``` * Returns `true` if the address is verified * Returns `false` if the address is not verified * Throws an error if the verification check fails ## React Bindings For React applications, we provide a hook `useIsUserVerified` that handles the verification check and loading state. # Credit Scoring API Source: https://docs.world.org/mini-apps/reference/credit-api Real-time creditworthiness for World accounts. Look up by wallet address or World username to get borrower state and a credit score. World proves there's a real human behind an account. The Credit API tells you whether you can trust them. It provides real-time creditworthiness data for [over half a million](https://credit.cash/analytics) World accounts that have borrowed on [Credit](https://world.org/ecosystem/app_ebdd8475db3238254fca5b25ccba266a). This lets you focus your app and incentives on trustworthy users who are less likely to game or farm your system. This API derives a user’s credit score solely from their on-chain behavior on Credit, using data sourced directly from smart contract events. ## How to use it Send a `GET` request to: ```bash theme={"system"} https://credit.cash/api/borrower/[identifier] ``` where `[identifier]` is either the user's wallet address or [World username](/mini-apps/reference/usernames). The response is a JSON object with two keys: * `state`: the user's status on Credit * `score`: the user's credit score (integer) ### User state `state` is one of: * `INACTIVE`: Borrower has no outstanding loan. They could be a new user or never borrowed. * `ACTIVE`: Borrower has an outstanding loan that has not yet passed its overdue deadline. * `DEFAULTED`: Borrower has an outstanding loan that is past due and not fully repaid. ### Credit score The `score` is an integer that: * Starts at 0 for new users * Increases as users successfully repay loans and build a repayment track record Use these bands to categorize borrowers: | **Score range** | **Band** | **Description** | | --------------- | -------- | -------------------------------------------- | | 0 | New | No loan history or no loans timely repaid | | 1-99 | Bronze | Early borrower with minimal history | | 100-299 | Silver | Developing credit with some successful loans | | 300-599 | Gold | Established borrower with solid history | | 600-999 | Platinum | Excellent track record | | 1000+ | Diamond | Elite borrower with extensive history | ## Example ### Query Get data for user with wallet address `0x764C890E7D96481cBEa64c64C0F9cFF34bFF2Ce7`: ```bash theme={"system"} curl -s "https://credit.cash/api/borrower/0x764C890E7D96481cBEa64c64C0F9cFF34bFF2Ce7" ``` ### Response ```json theme={"system"} { "state": "INACTIVE", "score": 1947 } ``` ## Tips * Use score thresholds to shape your rewards and incentives. For example, offer higher rewards to users with high credit scores, who are more likely to be high-quality, and lower rewards to users with low credit scores, who are more likely to engage in fraud. * Treat `DEFAULTED` as a strong risk signal. These accounts are much more likely to have sold their World account or be banned for fraud, so exclude them from sensitive actions like valuable rewards, referrals, and high-impact features. * Add extra scrutiny or manual review for low-score users, such as additional verification or smaller caps, instead of blocking them outright. This lets you onboard new but unproven accounts safely. ## Error codes | **Status** | **Error** | **Description** | | ---------- | --------------------------- | -------------------------------------- | | 400 | Missing identifier | No wallet address or username provided | | 404 | Username not found | World username could not be resolved | | 500 | Failed to get borrower info | Internal server error | ## Support For support, questions, or suggestions, contact via Telegram: * [Diego](http://t.me/antidiego) * [Monchi](http://t.me/cairoeth) # Microphone Source: https://docs.world.org/mini-apps/reference/microphone Microphone is only available from World App 2.8.85 and MiniKit 1.9.6. Microphone uses the standard web api [Navigator.mediaDevices](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/mediaDevices). These two conditions must be met in order to use the microphone: * The user must grant permission to your mini app to use the microphone. * The user must grant permission to World App to use the microphone. ## Using the microphone Request permission from the user to enable microphone for your mini app with the [request permission command](/mini-apps/commands/request-permission). * If you receive a `world_app_permission_not_enabled` or `permission_disabled` error code, you must prompt the user to enable microphone for World App first. This can be done by simply trying to start recording. * The microphone will automatically be turned off if the user closes your mini app or World App. ```tsx theme={"system"} const [isMicOn, setIsMicOn] = useState(false); const [stream, setStream] = useState(null); // ... const toggleMicrophone = useCallback(async () => { if (isMicOn) { // Stop microphone access if (stream) { stream.getTracks().forEach((track) => track.stop()); setStream(null); } setIsMicOn(false); } else { // Start microphone access try { const newStream = await navigator.mediaDevices.getUserMedia({ audio: true, }); setStream(newStream); setIsMicOn(true); } catch (error) { console.error('Error accessing microphone:', error); } } }, [isMicOn, stream]); ``` ## Debugging * If you receive a `DomException Error`, it's most likely because the user hasn't granted permission to your mini app to use the microphone. * If the microphone turns off after a few seconds it's because the user has not granted permission to your mini app to use the microphone. # Payment Methods Source: https://docs.world.org/mini-apps/reference/payment-methods Integrate Apple Pay and Google Pay into your mini-app Both Apple Pay and Google Pay run directly in the WebView environment of your mini-app, using their respective JavaScript APIs. ## Environment Setup First, ensure your mini-app has a secure HTTPS environment, as both Apple Pay and Google Pay require it. For Apple Pay, you must: - Register for an Apple Developer account - Configure your merchant ID - Register your domain with Apple - Set up merchant validation on your server [Complete setup guide on Apple's website](https://applepaydemo.apple.com/) For Google Pay, you must: - Set up a Google Pay merchant account - Configure your Google Pay API in the Google Pay Business Console - Obtain your merchant ID [Complete setup guide on Google's website](https://developers.google.com/pay/api/web/guides/resources/demos) ### Testing Test your integration thoroughly using test cards and sandbox environments. **Development Environment:** - Use Apple Pay's sandbox environment - Test with Apple's test cards **Test Cards:** - You can use any valid card number in the sandbox - No actual charges will be processed **Testing Checklist:** * Verify merchant validation works - Test on both iOS devices and Safari on macOS - Check error handling - Verify payment completion flows Apple Pay integration demo **Development Environment:** - Use Google Pay's TEST environment - Test with Google's test cards **Test Cards:** - Use test PAN: 4111 1111 1111 1111 - Expiration: Any future date - CVV: Any 3 digits **Testing Checklist:** - Verify button displays correctly - Test on Android devices - Check error handling - Verify payment completion flows Google Pay integration example Google Pay integration example ## Limitations and Considerations * **Platform Restrictions**: Apple Pay only works on iOS/macOS devices with Safari, while Google Pay is primarily for Android * **Device Support**: Users must have devices that support these payment methods * **WebView Context**: Some features might behave differently in the mini-app WebView compared to a standard browser For Apple Pay merchant validation, your server needs to be accessible via HTTPS and properly configured with Apple's developer portal. ## Additional Resources * [Apple Pay Demo and Documentation](https://applepaydemo.apple.com/) * [Google Pay API Documentation](https://developers.google.com/pay/api/web/guides/resources/demos) * [Apple Pay Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/apple-pay/overview/) * [Google Pay Web Integration Guide](https://developers.google.com/pay/api/web/guides/tutorial) # Status Page Source: https://docs.world.org/mini-apps/reference/status-page You can check the current status of World services at [status.world.org](https://status.world.org). The Networks section is not up to date yet. For transactions, status is determined by: * **Disruption**: Transactions taking longer than 45 seconds * **Outage**: Transactions taking longer than 5 minutes ## Get Status [https://status.worldcoin.org/api/services](https://status.worldcoin.org/api/services) This endpoint returns the current status of all World services. ### Query Params Include `logs=true` to get historical status logs for each service. ```bash cURL theme={"system"} curl -X GET "https://status.worldcoin.org/api/services?logs=true" ``` ```javascript theme={"system"} fetch('https://status.worldcoin.org/api/services?logs=true') ``` ### Response Array of service status objects, each containing: Service name (e.g., "Mini Apps", "World ID Verifications") Service identifier Description of the service Category the service belongs to (e.g., "mini-apps", "world-id", "finance") Current service status: "ok", "warning", or "error" Historical status changes, containing: * `datetime`: Unix timestamp * `status`: Status at that time * `name`: Event name * `description`: Event description * `downtime`: Duration in seconds (if applicable) Uptime percentages for different time periods: * `1`: Last 24 hours * `7`: Last 7 days * `30`: Last 30 days * `90`: Last 90 days ```json theme={"system"} { "services": [ { "name": "Crypto Transactions", "id": "crypto-transactions", "description": "", "categoryId": "mini-apps", "status": "ok", "logs": [ { "datetime": 1739546233, "status": "ok", "name": "Running again", "description": "Service outage" } ], "uptimeRatio": { "1": 99.7, "7": 100, "30": 100, "90": 100 } } ], "categories": [ { "id": "mini-apps", "name": "Mini Apps", "status": "ok" } ], "uptimeRatio": { "1": 98, "7": 98.7, "30": 99.6, "90": 99.9 }, "status": "ok" } ``` # Usernames Source: https://docs.world.org/mini-apps/reference/usernames ## Introduction Usernames are ENS-compatible identifiers for every World App user, ensuring consistency and easy recognition. For example, when displaying transaction history, show the username instead of the wallet address to make it more user-friendly and private. The usernames service is public, docs can be found [here](https://usernames.worldcoin.org/docs). This will be mostly useful for more advanced use cases. ## How to get it To get the user's username you can either complete Wallet Auth and access the username/profile picture url from MiniKit directly: ```tsx theme={"system"} const username = MiniKit.user.username ``` Or you can request it manually, using the `getUserByAddress` method on MiniKit: ```tsx theme={"system"} const worldIdUser = await MiniKit.getUserByAddress(userAddress) ``` Other ways involve querying the [usernames service](https://usernames.worldcoin.org/docs). # Add Money Source: https://docs.world.org/mini-apps/sharing/add-money-qa Add money to your World Wallet directly from exchanges like Binance and Coinbase. Deposit, withdraw, and swap tokens across multiple exchanges and chains effortlessly.

Parameters

Unique ID for the Add Money mini app. URL-encoded path to the bridge interface (typically `%2Fbridge`). Address of the recipient World Wallet user. Token contract address to deposit (supports USDC or WLD). USD amount to deposit. If omitted, the user selects the amount in-app. App ID of the source app for quick navigation back. Name of the source app displayed in the "Go Back" button. Path within the source app to deeplink back to (URL-encoded).
Add Money Screen
Url follows the schema below. Navigate there to use this Quick Action. ``` https://worldcoin.org/mini-app?app_id=app_e7d27c5ce2234e00558776f227f791ef &path={%2Fbridge} &toAddress={0xRecipientAddressHere} &toToken={0xUSDCOrWLDAddress} &amountUsd={100} &sourceAppId={app_source1234567890abcdef} &sourceAppName={My%20App} &sourceDeeplinkPath={%2Fdashboard} ``` # DNA Send & Swap Source: https://docs.world.org/mini-apps/sharing/dna-qa Generate deep links to the DNA app for quick actions like Swap and Send. [DNA](https://worldcoin.org/ecosystem/app_8e407cfbae7ae51c19b07faff837aeeb) now supports a **Quick Action** to deeplink directly into the wallet interface, allowing users to perform specific actions like sending tokens or swapping assets with predefined parameters.

Parameters

Supports deep linking to the `swap` and `send` tabs. The contract address of the token being sent (`fromToken`). The contract address of the token being received (`toToken`). This is used in **swap** actions. The recipient's wallet address or username for sending tokens. The amount of the `fromToken` to be sent, specified in its **base unit** . The application ID of the source app initiating the deeplink. A deeplink path from the source application, which will be **URL-encoded**.
Send Screen Swap Screen
## Helper Function ```tsx theme={"system"} const DNA_APP_ID = "app_8e407cfbae7ae51c19b07faff837aeeb"; function getDNADeeplinkUrl({ tab, fromToken, toToken, recipientAddress, amount, sourceAppId, sourceDeeplinkPath, }: { tab: "swap" | "send"; fromToken?: string; toToken?: string; recipientAddress?: string; amount?: string; sourceAppId?: string; sourceDeeplinkPath?: string; }) { let path = `/wallet?tab=${tab}`; if (fromToken) { path += `&fromToken=${fromToken}`; if (amount) { path += `&amount=${amount}`; } } if (toToken) { path += `&toToken=${toToken}`; } if (recipientAddress) { path += `&recipientAddress=${recipientAddress}`; } if (sourceAppId) { path += `&sourceAppId=${sourceAppId}`; } if (sourceDeeplinkPath) { path += `&sourceDeeplinkPath=${encodeURIComponent(sourceDeeplinkPath)}`; } const encodedPath = encodeURIComponent(path); return `https://worldcoin.org/mini-app?app_id=${DNA_APP_ID}&path=${encodedPath}`; } ``` ## **Returns** A string representing the complete deeplink URL to the DNA application with the specified parameters. ## **Example Usage** ```typescript theme={"system"} const deeplinkUrl = getDNADeeplinkUrl({ fromToken: "0x79A02482A880bCE3F13e09Da970dC34db4CD24d1", toToken: "0x4200000000000000000000000000000000000006", recipientAddress: "0xRecipientAddressHere", amount: "1235", sourceAppId: "app_a4f7f3e62c1de0b9490a5260cb390b56", sourceDeeplinkPath: "/path", }); console.log(deeplinkUrl); ``` ## **Generated Deeplink URL:** ```bash theme={"system"} https://worldcoin.org/mini-app?app_id=app_8e407cfbae7ae51c19b07faff837aeeb&path=%2Fwallet%3Ftab%3Dsend%26fromToken%3D0x79A02482A880bCE3F13e09Da970dC34db4CD24d1%26amount%3D1234500%26toToken%3D0x4200000000000000000000000000000000000006%26recipientAddress%3D0xRecipientAddressHere%26sourceAppId%3Dapp_a4f7f3e62c1de0b9490a5260cb390b56%26sourceDeeplinkPath%3D%252Fsome%252Fpath ``` ## **Note** * Ensure that the **amount** is specified in the unit of the fromToken (e.g., wei for Ethereum-based tokens). * The **sourceDeeplinkPath** is URL-encoded to ensure it is correctly interpreted when the deeplink is accessed. * The **DNA\_APP\_ID** should be defined in your environment to match the application ID assigned to your DNA instance. * If the tab is **Send**, it is necessary/recommended to provide **fromToken**, **amount**, and **the recipient's address or username** *(toToken is not required)*. * If the tab is **Swap**, it is necessary/recommended to provide **fromToken**, **toToken**, and **amount** *(in base unit)*. This function facilitates the creation of deeplink URLs that can be used to direct users seamlessly into specific actions within the DNA application, enhancing the user experience by pre-filling transaction details. # Earn WLD Pool Source: https://docs.world.org/mini-apps/sharing/earn-wld-qa Earn high yields with your WLD token. Put your WLD to work by adding liquidity to the markets and earning large rewards from other traders. Earn WLD now supports a Quick Action to deeplink directly to their staking screen.

Parameters

Unique ID for the Earn WLD mini app.
Liquidity Pool Screen
Url follows the schema below. Navigate there to use this Quick Action. ``` http://worldcoin.org/mini-app?app_id=app_b0d01dd8f2bdfbff06c9e123de487eb8 ``` # Link to your Mini App Source: https://docs.world.org/mini-apps/sharing/quick-actions ### **What Are Quick Actions?** A Quick Action is a universal deeplink defined by a schema that navigates to a specific path inside your app. It's meant to be used between mini apps, enabling one app (App A) to use a feature or action of another app (App B) seamlessly. In short, you should create a quick action to link to another mini app. If a user clicks on your link and has World App installed, it will automatically open the mini app inside of World App to the specified path. Otherwise, it will direct them to the app store and prompt them to install World App. ### **Benefits of Quick Actions** 1. **Leverage Expertise**: Use other mini apps already working specific features. 2. **Resource & Time Optimization**: Focus on your apps core functionalities while outsourcing supplementary features. 3. **Community Growth**: Integrating another apps functionality opens opportunities for co-marketing. ### **How to Make a Quick Action** 1. **Create a Universal Link Schema**: Define a schema with a custom path that aligns with the functionality you want to provide. 2. **Publish it in our docs for all devs to use**: Fill this [form](https://forms.gle/UBcKMrnxtyxqX4dq6) for our team to test and publish your quick action on this page. Urls will follow the schema below. ``` https://world.org/mini-app?app_id={app_id}&path={path} ``` ### Parameters The `app_id` corresponding to your mini app. Should be the url encoded path where you want to link to inside of your mini app # Sage Support Source: https://docs.world.org/mini-apps/sharing/sage-qa [Sage](https://worldcoin.org/ecosystem/app_5dee2f19cd6eef599eb6ab275a0a7523) is an AI chatbot that lets users ask questions and get answers. Sage Support enables developers to integrate Sage chats seamlessly into their World Mini Apps. Developers get a white label version of Sage that acts as a support assistant for their Mini App using the context they give it. ## Key Features * **Seamless Integration**: Easily embed Sage chats within any World Mini App. * **Custom Context Parameters**: Tailor Sage's responses based on Mini App specific data. * **Logs and Analytics**: Track and understand what types of questions your users are asking.
Sage Developer Dashboard Analytics Sage Developer Dashboard Settings
*** ## Getting Started ### 1. Create a Sage Developer project Go to the [Sage Developer dashboard](https://dev-dashboard-gamma.vercel.app) and sign in. Create a new project. ### 2. Customizing Sage to fit your app With a project created, head over to the project's settings, this page is accessible by going into: **Apps > Your App > Settings**. (Navigation is available through the menu bar on the top right or the top left breadcrumbs) In this page you can find parameters to fine tune Sage to best interact with your users in the context of your app, a comprehensive description of these parameters is present [below](#app-settings). ### 3. Integrating Sage Support in your app The final step to integrating Sage Support into your React application is to install and make use of the `SageSupport` component. Add the npm package with the command: ```bash theme={"system"} npm install sage-support ``` Once the dependency installed, the simplest way to integrate Sage Support is: ```tsx theme={"system"} import { SageSupport } from "sage-support"; ; ``` Where `YOUR_APP_ID` is the App ID (number) you can find in the [Sage Developer Dashboard](https://dev-dashboard-gamma.vercel.app) (top right). More configuration options for the component are available [below](#sagesupport-component). ### 4. Sage Support Link Generate your app's Support Link using the [Support Link wizard](#support-link-generator) and use it as the Support URL under **Worldcoin Developer > Mini App > Configuration > App Store > Support > Link**. ### Analytics Once you've started using Sage Support in your Mini App, analytics are also available in the [Sage Developer Dashboard](https://dev-dashboard-gamma.vercel.app), allowing you to identify the usage of Sage Support in your app and understand how users are using it. You can find more information about analytics [below](#app-analytics). This is all you need to get Sage Support working. If you wish to customize it further, or better understand the various parameters continue reading below. *** ## App Settings Located at **Apps > Your App > Settings**, the Settings page allows you to fine tune the Sage Support assistant to meet the needs and context of your Mini App. ### `App Name` This name only lives in the [Sage Developer dashboard](https://dev-dashboard-gamma.vercel.app) universe and serves the purpose of differentiating between multiple Sage Support integrations, it's recommended that this field is set accordingly. ### `App ID` Uniquely identifies your Sage Developer App and is used to link your `SageSupport` component to the [Sage Developer dashboard](https://dev-dashboard-gamma.vercel.app). This field is automatically generated and cannot be changed. ### `Introduction Message` Defines the first message to be sent by the Sage assistant once a new chat is created/open by a user. This field is also capable of encompassing a custom variable (`%USER%`) which Sage will replace by the user's username. ### `App Description` Provide a solid description about your app and what it focuses on, this field is processed by Sage as an additional context parameter, and is useful to keep the assistant behavior and subject within the landscape of your Mini App. *** ## App Analytics Located at **Apps > Your App > Analytics**, the Analytics page allows you to view statistics and analytic data about the usage of Sage Support within your Mini App. ### `Messages` See how many messages were sent to Sage Support chats from your Mini App in each of the last 7 days. ### `Users` See how many users interacted with Sage Support chats from your Mini App in each of the last 7 days. ### `Keywords` Currently not available. ### `Next Step` Currently not available. ### `Logs` See how users chat with Sage Support inside your app, this data is shown anonymously and provides the developer with a more in-depth tool to explore the interactions between users and Sage Support. *** ## `SageSupport` Component ### Installation To use the Sage Support React component you must first install it using npm (or your package manager of choice). ```bash theme={"system"} npm install sage-support ``` ### Basic Usage From the get go you can use Sage Support by using the basic implementation, the only mandatory parameter to be passed to the component is the `projectId`. This ID (App ID) is retrieve from the Settings page in the [Sage Developer dashboard](https://dev-dashboard-gamma.vercel.app). ```tsx theme={"system"} import { SageSupport } from "sage-support"; ; ``` ### Props & Personalisation Other props allow you to customize both the aesthetic and behavior of the Sage Support Chat. Links your component to your Sage Developer project, used for analytics and behavior. Allows you to make use of the return button on the top left of the Sage Support chat window. The value to be passed to this prop is a [quick action URL](https://docs.world.org/mini-apps/sharing/quick-actions). Allows you to pass custom CSS classes to affect the fallback default button. Allows you to pass a custom React element to replace the default button. *** ## Support Link You can also integrate Sage Support directly into your World Mini App by using a Support Link. This allows your users to access Sage's AI-powered support without requiring you to install any additional packages or components. The Support Link is a quick action URL that opens the Sage Support chat interface directly within the World App. When users click on this link, they'll be able to ask questions and get support related to your Mini App. This link is particularly useful for developers to use Sage as their Mini App's native Support link, available under **Worldcoin Developer > Mini App > Configuration > App Store > Support > Link** ### Base URL & Parameters The base URL to access a support chat is: `https://worldcoin.org/mini-app?app_id=app_5dee2f19cd6eef599eb6ab275a0a7523&path=/support-chat` This support link, akin to the [`SageSupport` component](#sagesupport-component), accepts and requires some parameters to be passed, in this case URL Query Parameters, for the Support Chat to work like expected. Links your Support Link to your Sage Developer project, used for analytics and behavior. Allows you to make use of the return button on the top left of the Sage Support chat window by passing a Mini App quick action URI/URL. Due to some encoding functionalities and implementation caveats, the value to be passed to this parameter has to be formatted accordingly beforehand: 1. The URL must be submitted to a [UTF-8 encoding](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). 2. All ampersand characters (such as the one in `&path`) have to be replaced with the sequence `(amps)`. To make this process easier, you can make use of the generator below or inside the [Sage Developer Dashboard](https://dev-dashboard-gamma.vercel.app). Any URIs outside of the `worldcoin.org/mini-app` space are not allowed. ### Support Link Generator The Support Link Generator component would be inserted here in the actual implementation. # Swap Source: https://docs.world.org/mini-apps/sharing/swap-qa Use this Quick Action to deep link into the Swap interface with prefilled parameters.

Parameters

Target app id for the Swap quick action: app\_6c5c5717c77abe83be8814c032c3a6f9. Path inside the target app. Use '/' for Swap. Token address for the "from" token. Token address for the "to" token. Amount of the from token in base token units. app\_id of the app that uses this quick action. Name of the app that uses this quick action.
Swap Screen
### Helper function ```ts theme={"system"} export interface ActionLinkProps { targetAppId: string; // app_6c5c5717c77abe83be8814c032c3a6f9 for Swap quick action path: string; // '/' for Swap quick action params: Record; } const MAIN_ACTION_URL = "https://worldcoin.org/mini-app"; export function getActionLink({ targetAppId, path, params }: ActionLinkProps) { let fullPath = path; if (Object.keys(params).length > 0) { const paramParts = Object.entries(params).map(([key, value]) => { return `${key}=${value}`; }); fullPath += `?${paramParts.join("&")}`; } const queryString = `${MAIN_ACTION_URL}?app_id=${targetAppId}&path=${encodeURIComponent(fullPath)}`; return queryString; } // Example usage const link = getActionLink({ targetAppId: "app_6c5c5717c77abe83be8814c032c3a6f9", path: "/", params: { fromToken: "0x2cFc85d8E48F8EAB294be644d9E25C3030863003", toToken: "0x4200000000000000000000000000000000000006", amount: "1234500", sourceAppId: "app_source1234567890abcdef", sourceAppName: "My%20App", }, }); console.log(link); ``` ### Example link ``` https://worldcoin.org/mini-app?app_id=app_6c5c5717c77abe83be8814c032c3a6f9&path=%2F%3FfromToken%3D0x2cFc85d8E48F8EAB294be644d9E25C3030863003 ``` # World Chat Source: https://docs.world.org/mini-apps/sharing/world-chat-qa World Chat is a messaging platform in the World App ecosystem. Quick Actions let you link to specific chat features. Url follows the schema below. ``` https://worldcoin.org/mini-app?app_id=app_e293fcd0565f45ca296aa317212d8741 ```

Parameters

The username of the recipient you want to chat with. You can resolve a username using the user's [wallet address](/mini-apps/reference/address-book). Predefined message text to include in the draft chat. When included, opens a draft chat with the send payment option pre-selected. You can optionally specify an amount by using `pay=amount` where amount is the numeric value in USDC. When included, opens a draft chat with the payment request option pre-selected. You can optionally specify an amount by using `request=amount` where amount is the numeric value in USDC.
World Chat Screen
## Helper function ```tsx theme={"system"} const WORLD_CHAT_APP_ID = "app_e293fcd0565f45ca296aa317212d8741"; function getWorldChatDeeplinkUrl({ username, message, pay, request, }: { username: string; message?: string; pay?: string | number; request?: string | number; }) { let path = `/${username}/draft`; if (message) { path += `?message=${message}`; } else if (pay !== undefined) { if (pay === "true" || pay === true) { path += `?pay`; } else { path += `?pay=${pay}`; // Pay with amount } } else if (request !== undefined) { if (request === "true" || request === true) { path += `?request`; } else { path += `?request=${request}`; // Request with amount } } const encodedPath = encodeURIComponent(path); return `https://worldcoin.org/mini-app?app_id=${WORLD_CHAT_APP_ID}&path=${encodedPath}`; } // Create a chat with predefined message console.log( getWorldChatDeeplinkUrl({ username: "johndoe", message: "Hello from my mini app!", }) ); // Create a chat with send payment option console.log( getWorldChatDeeplinkUrl({ username: "johndoe", pay: "true", }) ); // Create a chat with send payment option and amount console.log( getWorldChatDeeplinkUrl({ username: "johndoe", pay: 5.25, }) ); // Create a chat with payment request option console.log( getWorldChatDeeplinkUrl({ username: "johndoe", request: "true", }) ); // Create a chat with payment request option and amount console.log( getWorldChatDeeplinkUrl({ username: "johndoe", request: 10, }) ); ``` ### Example output links ``` // Message draft https://worldcoin.org/mini-app?app_id=app_e293fcd0565f45ca296aa317212d8741&path=%2Fjohndoe%2Fdraft%3Fmessage%3DHello // Payment draft with amount https://worldcoin.org/mini-app?app_id=app_e293fcd0565f45ca296aa317212d8741&path=%2Fjohndoe%2Fdraft%3Fpay%3D5.25 // Request draft with amount https://worldcoin.org/mini-app?app_id=app_e293fcd0565f45ca296aa317212d8741&path=%2Fjohndoe%2Fdraft%3Frequest%3D10 ``` ### Appendix **Caveats/Warnings** * The username must be a valid World ID username. * Only one quick action type (message, pay, or request) can be used at a time. * If the recipient does not exist, the app will show an appropriate error message. * Only USDC & WLD are supported for payments. * Currency amounts should be specified as decimal numbers (e.g., 5.25 for 5.25 USDC). # Developer Portal MCP Source: https://docs.world.org/model-context-protocol/developer-portal Manage World developer portal apps from your AI coding assistant. The Developer Portal MCP lets AI assistants create apps, configure World ID, manage Mini App metadata, upload app store assets, and submit apps for review. It authenticates with a developer portal team API key. Treat that key like a secret: anyone with access to it can make changes to apps in that team. ## Endpoint ```text theme={"system"} https://developer.world.org/api/mcp ``` ## Create an API key 1. Open the [Developer Portal](https://developer.world.org). 2. Select your team. 3. Go to **API keys**. 4. Create or reset an API key. 5. Copy the generated key immediately. It is shown once. Developer portal API keys start with `api_`. ## Connect your client Replace `api_...` with the API key you copied from the Developer Portal. ```bash theme={"system"} claude mcp add world-developer-portal \ https://developer.world.org/api/mcp \ --transport http \ --scope project \ --header "Authorization: Bearer api_..." ``` ```bash theme={"system"} codex mcp add world-developer-portal \ --env WORLD_DEVELOPER_API_KEY=api_... \ -- npx -y mcp-remote https://developer.world.org/api/mcp \ --transport http-only \ --header 'Authorization:Bearer ${WORLD_DEVELOPER_API_KEY}' ``` Add this to `.cursor/mcp.json`: ```json theme={"system"} { "mcpServers": { "world-developer-portal": { "url": "https://developer.world.org/api/mcp", "headers": { "Authorization": "Bearer api_..." } } } } ``` Add this to `.vscode/mcp.json`: ```json theme={"system"} { "servers": { "world-developer-portal": { "type": "http", "url": "https://developer.world.org/api/mcp", "headers": { "Authorization": "Bearer api_..." } } } } ``` ## Available tools | Tool | Purpose | | ---------------------------------- | ------------------------------------------------------------------------------------- | | `get_team_context` | List your team's apps and status. | | `get_app_config` | Fetch app, World ID, Mini App, and app store configuration. | | `create_app` | Create an external World ID app or Mini App. | | `configure_world_id` | Create a managed World ID 4.0 relying party for an app. | | `get_world_id_signing_key` | Fetch the current signer address. Private keys are not returned. | | `rotate_world_id_signing_key` | Generate or set a new World ID signing key. The private key is returned once. | | `get_world_id_registration_status` | Fetch and sync World ID registration status from the registry contracts. | | `create_world_id_action` | Create or update a World ID action for an app. | | `configure_mini_app` | Update Mini App portal settings, app store metadata, and permissions. | | `upload_app_image` | Upload logo, hero, content card, meta tag, or showcase images and patch app metadata. | | `submit_app_for_review` | Submit an app for review after explicit confirmation. | ## Recommended flow Start every session by asking the assistant to inspect the team: ```text theme={"system"} Use the Developer Portal MCP to get my team context. If there is no app for this project, create one. ``` For an external World ID app: ```text theme={"system"} Create a production external World ID app named Example, configure World ID, create an action called verify-account, and tell me which environment variables to add. ``` For a Mini App: ```text theme={"system"} Create a Mini App named Example, fill the non-image app store metadata, upload the logo, content card, and showcase images I provide, and submit it for review after confirming the final values with me. ``` ## Images Use `upload_app_image` for app store images. It uploads the image and stores the correct filename in the matching metadata field. The tool accepts either: * `source_url`: a public HTTPS URL to a PNG or JPEG. * `image_base64`: base64-encoded PNG or JPEG bytes for local files. Use `configure_mini_app` for Mini App text, links, categories, permissions, countries, and languages. Use `upload_app_image` for logo, content card, meta tag, hero, and showcase assets. Review submission still requires image metadata. Upload the required images before calling `submit_app_for_review`: ```text theme={"system"} upload_app_image { app_id, image_type: "logo", image_base64 | source_url } upload_app_image { app_id, image_type: "content_card", image_base64 | source_url } upload_app_image { app_id, image_type: "showcase_1", image_base64 | source_url } ``` ## Security notes * Store generated World ID private keys immediately. They are returned once and are not recoverable from the portal. * Use a separate API key per local agent or project when possible. * Delete or rotate API keys that are no longer needed. * Confirm destructive actions before asking the assistant to rotate a signer key or submit an app for review. # MCP Source: https://docs.world.org/model-context-protocol/index Connect AI assistants to World documentation and the World developer portal with Model Context Protocol servers. Model Context Protocol (MCP) servers let AI assistants use World tools directly from your local coding environment. World provides two MCP servers: Search World documentation from Claude, Codex, Cursor, VS Code, and other MCP clients. No API key required. Create apps, configure World ID, upload app store assets, and submit apps for review with a developer portal API key. ## Choose the right MCP | MCP | Use it when | Authentication | | ---------------------------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------- | | [World Docs MCP](/model-context-protocol/world-docs) | You want your AI assistant to search World docs while editing code. | None | | [Developer Portal MCP](/model-context-protocol/developer-portal) | You want your AI assistant to manage apps in the World developer portal. | Developer portal team API key | ## Client support Both MCP servers use streamable HTTP. Most modern MCP clients can connect directly to an HTTP MCP server. Clients that only support stdio can connect through [`mcp-remote`](https://www.npmjs.com/package/mcp-remote). Keep developer portal API keys scoped to trusted local MCP clients. The Developer Portal MCP can mutate apps in your team. # World Docs MCP Source: https://docs.world.org/model-context-protocol/world-docs Search World documentation from your AI coding assistant. The World Docs MCP gives your AI assistant direct access to World documentation through the [Model Context Protocol](https://modelcontextprotocol.io/). Use it when you want Claude, Codex, Cursor, VS Code, or another MCP client to search current World docs while you build. ## Endpoint ```text theme={"system"} https://docs.world.org/mcp ``` The docs MCP does not require authentication. ## Connect your client ```bash theme={"system"} claude mcp add --transport http --scope project world-docs https://docs.world.org/mcp ``` ```bash theme={"system"} codex mcp add world-docs -- npx -y mcp-remote https://docs.world.org/mcp --transport http-only ``` Add this to `.cursor/mcp.json`: ```json theme={"system"} { "mcpServers": { "world-docs": { "url": "https://docs.world.org/mcp" } } } ``` Add this to `.vscode/mcp.json`: ```json theme={"system"} { "servers": { "world-docs": { "type": "http", "url": "https://docs.world.org/mcp" } } } ``` ## Available tools | Tool | Purpose | | ---------------------------- | ---------------------------------------------------------------------- | | `search_world_documentation` | Search and retrieve relevant World documentation for the current task. | ## Suggested prompts ```text theme={"system"} Use the World Docs MCP to find the current MiniKit install steps and add them to this project. ``` ```text theme={"system"} Search the World docs for World ID verification and explain which endpoint this app should call. ``` ## When to use it Use the docs MCP for read-only documentation lookup. To create or configure apps in the developer portal, use the [Developer Portal MCP](/model-context-protocol/developer-portal). # Deploy Smart Contracts Source: https://docs.world.org/world-chain/developers/deploy Deploy a HelloWorldChain smart contract to World Chain Sepolia using Foundry: setup, build, test, fund, and create. In this tutorial, we will use the [Solidity programming language](https://docs.soliditylang.org/en/v0.8.28/) to write the `HelloWorldChain` smart contract for World Chain. Solidity is a programming language that can compile to EVM (Ethereum Virtual Machine) bytecode which can be executed on the World Chain EVM. We will also be using the [Foundry CLI](https://book.getfoundry.sh/) toolkit, which has a lot of tools to help build, test and interact with Solidity programs. ## Download development tools First, we need to install the Foundry CLI toolkit and the Solidity compiler. Solidity comes with a compiler called `solc` which we will use to compile the `HelloWorldChain` contract. The Foundry CLI will automatically download the right version of the Solidity compiler for you during the compilation process using `forge build`. ```bash Install Foundry theme={"system"} curl -L https://foundry.paradigm.xyz | bash ``` ## Create a Foundry project Open your terminal of choice, navigate to a directory where you want to create your project, and run the following command to create a new Foundry project: ```bash Create a new Foundry project theme={"system"} forge init hello-world-chain && cd hello-world-chain ``` Now that you have created a new Foundry project, you can start writing your smart contract. All smart contracts in Foundry projects are stored in the `src` directory. If you are using VSCode, it should look something like this: Foundry 1 ## Write the HelloWorldChain contract First, delete the template `src/Counter.sol` file: ```bash Delete Template theme={"system"} rm src/Counter.sol ``` Next, create a new `src/HelloWorldChain.sol` file and add the following code to it: ```solidity HelloWorldChain.sol theme={"system"} // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; contract HelloWorldChain { string private word; // Constructor that sets the initial word to "Hello World Chain!" constructor() { word = "Hello World Chain!"; } // Setter function to update the word function setWord(string memory newWord) public { word = newWord; } // Getter function to return the current word function getWord() public view returns (string memory) { return word; } } ``` This contract has a `word` variable that stores a string and two functions: `setWord` to update the word and `getWord` to return the current word. ## Update Scripts and Tests Since we deleted `Counter.sol`, we need to update or remove the scripts and tests that reference it to prevent compilation errors. ### Delete the `script/` directory The script directory contains scripts that import `Counter.sol`. Since we no longer have `Counter.sol`, we can delete the entire script directory to avoid any compilation issues: ```bash Delete Script Directory theme={"system"} rm -rf script ``` ### Replace `Counter.t.sol` with `HelloWorldChain.t.sol` In the test directory, delete the existing `Counter.t.sol` and create a new test file called `HelloWorldChain.t.sol` and add the following simple tests: ```solidity HelloWorldChain.t.sol theme={"system"} // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; import { Test } from "forge-std/Test.sol"; import { HelloWorldChain } from "../src/HelloWorldChain.sol"; contract HelloWorldChainTest is Test { HelloWorldChain helloWorldChain; function setUp() public { helloWorldChain = new HelloWorldChain(); } function testInitialWord() public view { string memory expected = "Hello World Chain!"; string memory actual = helloWorldChain.getWord(); assertEq(actual, expected); } function testSetWord() public { string memory newWord = "Hello Foundry!"; helloWorldChain.setWord(newWord); string memory actual = helloWorldChain.getWord(); assertEq(actual, newWord); } } ``` Now you can run tests: ```bash Test the contract theme={"system"} forge test ``` ## Compile the contract To compile the `HelloWorldChain` contract, run the following command: ```bash Compile the contract theme={"system"} forge build ``` The `forge build` command will compile the contract using the Solidity compiler and generate the necessary artifacts in the `out` directory. ## Generate a wallet To deploy the `HelloWorldChain` contract to World Chain Sepolia, you will need a wallet with some World Chain Sepolia ETH. An easy way to generate a wallet using the Foundry CLI is to run the following command: ```bash Generate a wallet theme={"system"} cast wallet new ``` `cast` is a versatile set of utility functions and commands for Solidity development. In this case, we are using one of its many built-in features to generate a wallet with one account. Never share your private key with anyone and always make sure that you don't upload them to code versioning tools like Git and hosting platforms like GitHub. Research best practices for private key management in order to avoid loss of funds. The output of the command will look something like this: ```bash Wallet output theme={"system"} Successfully created new keypair. Address: 0xB815A0c4bC23930119324d4359dB65e27A846A2d Private key: 0xcc1b30a6af68ea9a9917f1dda20c927704c5cdb2bbe0076901a8a0e40bf997c5 ``` ## Fund your wallet Now that you have a wallet, you need to fund it with some World Chain Sepolia ETH. You can get some World Chain Sepolia ETH from the [World Chain Sepolia faucet](https://www.alchemy.com/faucets/world-chain-sepolia) operated by Alchemy. In the form on the faucet page, enter the address of your wallet which you generated above and click the "Send me ETH" button. If you have any issues please send us a message in the developer group chat on [Telegram](https://t.me/worlddevelopersupport) or [Discord](https://world.org/discord). ## Deploy the contract Now that you have a wallet and you funded it with World Chain Sepolia ETH, you can deploy the `HelloWorldChain` contract to World Chain Sepolia using the following `forge create` command: ```bash Deploy the contract theme={"system"} forge create src/HelloWorldChain.sol:HelloWorldChain \ --rpc-url https://worldchain-sepolia.g.alchemy.com/public \ --private-key 0xcc1b30a6af68ea9a9917f1dda20c927704c5cdb2bbe0076901a8a0e40bf997c5 ``` Here, we are using the `:` format to specify the contract. This tells Foundry where to find the contract file (`src/HelloWorldChain.sol`) and which contract within the file (HelloWorldChain) to deploy. We also use the `--rpc-url` flag to specify the RPC URL of the World Chain Sepolia network and the `--private-key` flag to specify the private key of the wallet we generated earlier. On top of this we can also provide other flags like `-vvvvv` to get more verbose output from the deployment process, `--verify` to verify the contract on [Worldscan](https://worldscan.org) or [Blockscout](https://worldchain-sepolia.explorer.alchemy.com/) (alongside with an `--etherscan-api-key` flag) and several other flags to toggle different features that you can find more about in the [Foundry documentation](https://book.getfoundry.sh/). And that's it! You have successfully deployed a smart contract to World Chain Sepolia. You can interact with the contract using `forge script` scripts, using a block explorer or any other EVM library like [ethers.js](https://docs.ethers.io/v5/), [alloy-rs](https://github.com/alloy-rs/alloy/), and many others. # EVM Equivalence Source: https://docs.world.org/world-chain/developers/evm-equivalence How World Chain achieves EVM equivalence via OP Stack, plus key parameter differences vs OP Mainnet and Ethereum. World Chain is EVM-equivalent because it utilizes the [OP Stack](https://docs.optimism.io/stack/getting-started), a modular framework developed by Optimism, which ensures compatibility with the [Ethereum Virtual Machine (EVM)](https://ethereum.org/en/developers/docs/evm/). By leveraging the OP Stack, World Chain can execute smart contracts and interact with decentralized applications designed for Ethereum without any modifications. This equivalency enables seamless interoperability with Ethereum's ecosystem of applications and protocols. The OP Stack's modularity also allows World Chain to implement scaling solutions and other customizations while maintaining the fundamental EVM equivalence, ensuring a flexible yet consistent environment for developers and users alike. ## Differences between World Chain, OP Mainnet and Ethereum Though the EVM remains unchanged across OP Stack (Superchain) chains compared to the Ethereum layer 1, there are some configurations of the execution clients that do differ.
Parameter World Chain OP Mainnet Ethereum
Block time in seconds 2 2 12
Block gas limit 30,000,000 30,000,000 30,000,000
Block gas target 10,000,000 5,000,000 15,000,000
EIP-1559 elasticity multiplier 6 6 2
EIP-1559 denominator 250 250 8
Maximum base fee increase (per block) 0.8% 2% 12.5%
Maximum base fee decrease (per block) 0.4% 0.4% 12.5%
For more information check out the [OP Stack docs](https://docs.optimism.io/stack/getting-started). # Transaction Fees Source: https://docs.world.org/world-chain/developers/fees Understand World Chain fees: L2 execution vs L1 security costs, variability, and ways to optimize gas. Every World Chain transaction consists of two costs: an L2 (execution) fee and an L1 (security) fee. The L2 fee is the cost to execute your transaction on the L2, while the L1 fee covers the estimated cost of publishing the transaction on the L1. Typically, the L1 security fee is higher than the L2 execution fee. The L1 fee fluctuates depending on the volume of transactions on the L1. If your transaction timing is flexible, you can save on costs by submitting during periods of lower gas fees on the L1 (for example, weekends) or using products like [GasHawk](https://gashawk.io/) which schedule transactions during periods of low demand. Similarly, the L2 fee can vary based on the number of transactions submitted to the L2. This dynamic adjustment works similarly to the L1; you can learn more about it [here](https://blog.thirdweb.com/eip-1559-ethereum-gas-fees/). For additional details about fee calculation on World Chain, please refer to the relevant [op-stack developer documentation](https://docs.optimism.io/stack/transactions/fees). # Grants Program Source: https://docs.world.org/world-chain/developers/grants Apply for Worldcoin Foundation grants: areas of interest, continuous grants program, and how to submit. The [Worldcoin Foundation](https://worldcoin.foundation) runs the [Human Collective Grants](https://world.org/community-grants) program which gives grants to builders that help accelerate the [Worldcoin Tech Tree](https://world.org/tech-tree) and [related RFPs](https://world.org/rfp). The grants program switched from a quarterly waves format to a continuous format where builders can apply for a grant at any point in time. For more information you can read the [Continuous Grants program announcement blogpost](https://world.org/blog/announcements/worldcoin-foundation-introduces-new-continuous-grants-program-spur-innovation). ## Areas of interest for applications * Rustification of the [OP Stack](https://docs.optimism.io/stack/getting-started) * Furthering [the gigagas roadmap](https://www.google.com/url?q=https://www.paradigm.xyz/2024/04/reth-perf\&sa=D\&source=editors\&ust=1726507097184499\&usg=AOvVaw34fUyrLcoigheJlrfbIDM2) * ZK-ifying the OP Stack (efforts like [OP Succinct](https://blog.succinct.xyz/op-succinct/), [Zeth from Risc0](https://risczero.com/blog/zeth-release) and [Mina's ZK fault proof RFP]()) * Scalability research and engineering (can be Ethereum core as well) * Embedding priority blockspace for humans into the OP Stack derivation pipeline + fault proof program (open R\&D) * Separate eip1559 fee market for humans on OP Stack * L2 Execution client development (especially [reth](https://github.com/paradigmxyz/reth)) and benchmarking * Improving UX and interoperability * Chain-level experiments with digital identity and the OP Stack (things like [human priority blockspace](/world-chain/quick-start/features#priority-blockspace-for-humans) and [free gas allowances](/world-chain/quick-start/features#gas-allowance-for-humans) for unique humans) * Interesting applications on World Chain (past examples include proof aggregators like [Nebra](https://nebra.one/), storage proofs like [Herodotus](https://herodotus.dev/) and [Axiom](https://www.axiom.xyz/), Passkeys module for Safe, and others) * and more... ### If you are interested [apply here](https://airtable.com/appftNMpv819abvTc/pag0uKCtjQAPJgaEB/form) or send us an email to [grants@worldcoin.org](mailto:grants@worldcoin.org)! # Deploy a World ID Template App Source: https://docs.world.org/world-chain/developers/template Deploy the World ID on-chain template to a World Chain Sepolia fork with Foundry; configure env, contracts, and local web. In this tutorial we are going to deploy a [World ID template app](https://github.com/worldcoin/world-id-onchain-template) on a World Chain Sepolia local fork using anvil. This app will be a simple web application that allows users to create a World ID proof of unique human and verify it. The app will be deployed on World Chain Sepolia and will interact with the `WorldIDRouter` smart contract to verify the ZK proofs of unique human. ## Prerequisites Before we start, make sure you have the following tools installed: * [Git](https://git-scm.com/) (usually pre-installed on most systems) * [Node.js](https://nodejs.org/en/) * [pnpm](https://yarnpkg.com/) (or equivalent like npm, yarn or bun) * [Foundry CLI](https://book.getfoundry.sh/) ## Clone template First, clone the World ID template app repository from GitHub: ```bash theme={"system"} git clone https://github.com/worldcoin/world-id-onchain-template.git && cd world-id-onchain-template ``` ## Install dependencies Next, install the dependencies for the World ID template app: ```bash theme={"system"} pnpm install ``` ## Build the smart contracts Next we are going to compile the smart contracts for the World ID template app: ```bash theme={"system"} cd contracts && forge build ``` ## Understanding World ID Before we deploy the World ID template app, let's take a look at the smart contracts that are part of the app: ```solidity contracts/src/Contract.sol theme={"system"} // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import { ByteHasher } from "./helpers/ByteHasher.sol"; import { IWorldID } from "./interfaces/IWorldID.sol"; contract Contract { using ByteHasher for bytes; /////////////////////////////////////////////////////////////////////////////// /// ERRORS /// ////////////////////////////////////////////////////////////////////////////// /// @notice Thrown when attempting to reuse a nullifier error DuplicateNullifier(uint256 nullifierHash); /// @dev The World ID instance that will be used for verifying proofs IWorldID internal immutable worldId; /// @dev The contract's external nullifier hash uint256 internal immutable externalNullifier; /// @dev The World ID group ID (always 1) uint256 internal immutable groupId = 1; /// @dev Whether a nullifier hash has been used already. Used to guarantee an action is only performed once by a single person mapping(uint256 => bool) internal nullifierHashes; /// @param nullifierHash The nullifier hash for the verified proof /// @dev A placeholder event that is emitted when a user successfully verifies with World ID event Verified(uint256 nullifierHash); /// @param _worldId The WorldID router that will verify the proofs /// @param _appId The World ID app ID /// @param _actionId The World ID action ID constructor(IWorldID _worldId, string memory _appId, string memory _actionId) { worldId = _worldId; externalNullifier = abi.encodePacked(abi.encodePacked(_appId).hashToField(), _actionId).hashToField(); } /// @param signal An arbitrary input from the user, usually the user's wallet address (check README for further details) /// @param root The root of the Merkle tree (returned by the JS widget). /// @param nullifierHash The nullifier hash for this proof, preventing double signaling (returned by the JS widget). /// @param proof The zero-knowledge proof that demonstrates the claimer is registered with World ID (returned by the JS widget). /// @dev Feel free to rename this method however you want! We've used `claim`, `verify` or `execute` in the past. function verifyAndExecute(address signal, uint256 root, uint256 nullifierHash, uint256[8] calldata proof) public { // First, we make sure this person hasn't done this before if (nullifierHashes[nullifierHash]) revert DuplicateNullifier(nullifierHash); // We now verify the provided proof is valid and the user is verified by World ID worldId.verifyProof( root, groupId, abi.encodePacked(signal).hashToField(), nullifierHash, externalNullifier, proof ); // We now record that 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... // Make sure to emit some kind of event afterwards! emit Verified(nullifierHash); } } ``` This contract has all the necessary pieces that any app that wants to integrate World ID proofs of personhood will require. The World ID docs have a [detailed explanation](https://docs.worldcoin.org/world-id) of how the World ID system works and how to integrate it into your app. But we will go over the main parts of the contract here: 1. The `Contract` contract is the main contract that will be deployed to World Chain Sepolia. It has a constructor that takes the `IWorldID` interface, the app ID and the action ID as parameters. The `IWorldID` interface is the World ID router that will verify the proofs, the app ID is the ID of the app can be created by the developer using the [World ID Developer Portal](https://developer.worldcoin.org/login) and that the action ID is the ID of the action that the user is performing which will be generated automatically by the [IDKit SDK](https://docs.worldcoin.org/reference/idkit) and derived from `action` string defined in the Developer Portal. 2. The [`nullifierHashes` mapping](https://docs.worldcoin.org/reference/contracts#sybil-resistance) is used to keep track of the nullifier hashes that have been used already. This is used to guarantee that an action is only performed once by a single person in order to achieve sybil resistance. 3. The `verifyAndExecute` function is the main function that will be called by the user to verify their proof of unique human. It takes the user's wallet address, the root of the Merkle tree, the nullifier hash, and the proof as parameters. 4. The function first checks if the nullifier hash has been used already and reverts if it has. 5. It then verifies the proof using the `worldId.verifyProof` function which is part of the `IWorldID` interface. 6. If the proof is valid, the function records the nullifier hash and executes the logic of the app. In this case, it emits the `Verified` event. If you want an example of a production application which uses the World ID protocol, you can check out the [Worldcoin grants contracts](https://github.com/worldcoin/worldcoin-grants-contracts). Specifically, the [`RecurringGrantDrop.sol`](https://github.com/worldcoin/worldcoin-grants-contracts/blob/main/src/RecurringGrantDrop.sol) contract which uses the World ID protocol to verify that the user is a unique human before they can claim a grant. ## Deploy template app First, you have to go to a node provider that supports World Chain Sepolia. You can use [Alchemy](https://www.alchemy.com/) or any of the other providers listed in the [World Chain documentation](/world-chain/providers/nodes). Once you have a node provider account, you need to get an RPC URL for the World Chain Sepolia network. For a simple deployment, the public RPC URL is sufficient. However, for doing a fork deployment, you will need to run a local fork of World Chain Sepolia using anvil which requires higher requirements on the RPC provider for forking the network. First, we will fork the World Chain Sepolia network using anvil: ```bash theme={"system"} # Substitute the RPC_URL with the RPC URL of the World Chain Sepolia network anvil -f $RPC_URL ``` We also need to set three important environment variables that are required for the deployment of the World ID template app: * `WORLD_ID_ROUTER`: The address of the World ID router contract that will verify the proofs (can be found in the [World ID documentation](https://docs.worldcoin.org/world-chain/reference/useful-contracts)) * `NEXT_PUBLIC_APP_ID`: The app ID that was generated in the [Developer Portal](https://developer.worldcoin.org/login) * `NEXT_PUBLIC_ACTION`: The action ID as configured in the [Developer Portal](https://developer.worldcoin.org/login) Once the fork is running, you can deploy the World ID template app to the World Chain Sepolia network using the Foundry CLI: ```bash theme={"system"} # cd into the contracts directory cd contracts forge create --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 src/Contract.sol:Contract --constructor-args $WORLD_ID_ROUTER $NEXT_PUBLIC_APP_ID $NEXT_PUBLIC_ACTION ``` This command will deploy the `Contract` contract to the World Chain Sepolia network using the provided RPC URL and private key. ## Local Web Setup Set up your environment variables in the `.env` file. You will need to set the following variables: * `NEXT_PUBLIC_APP_ID`: The app ID as configured in the [Worldcoin Developer Portal](https://developer.worldcoin.org). * `NEXT_PUBLIC_ACTION`: The action ID as configured in the Worldcoin Developer Portal. * `NEXT_PUBLIC_WALLETCONNECT_ID`: Your WalletConnect ID. * `NEXT_PUBLIC_CONTRACT_ADDRESS`: The address of the contract deployed in the previous step. Back in the root directory of the World ID template app, you can start the local web server: ```bash theme={"system"} pnpm dev ``` The Contract ABI will be automatically re-generated and saved to `src/abi/ContractAbi.json` on each run of `pnpm dev`. ## Iterating After making changes to the contract, you should: * re-run the `forge create` command from above * replace the `NEXT_PUBLIC_CONTRACT_ADDRESS` environment variable with the new contract address * if your contract ABI has changed, restart the local web server ## Testing You'll need to import the private keys on the local testnet into your wallet used for local development. The default development seed phrase is `test test test test test test test test test test test junk`. This is only for local development. Do not use this seed phrase on mainnet or any public testnet. When connecting your wallet to the local development environment, you will be prompted to add the network to your wallet. Use the [Worldcoin Simulator](https://simulator.worldcoin.org) in place of World App to scan the IDKit QR codes and generate the zero-knowledge proofs. ## Further resources If you want to learn more about the World ID protocol, you can check out the [World ID documentation](/world-id). If you want to build an application that uses World ID and targets existing World App users, check out [miniapps](/mini-apps)! # World Chain Contracts Source: https://docs.world.org/world-chain/developers/world-chain-contracts ## World Chain Mainnet
Name Address
L2ToL1MessagePasser [`0x4200000000000000000000000000000000000016`](https://worldscan.org/address/0x4200000000000000000000000000000000000016)
L2CrossDomainMessenger [`0x4200000000000000000000000000000000000007`](https://worldscan.org/address/0x4200000000000000000000000000000000000007)
L2StandardBridge [`0x4200000000000000000000000000000000000010`](https://worldscan.org/address/0x4200000000000000000000000000000000000010)
L2ERC721Bridge [`0x4200000000000000000000000000000000000014`](https://worldscan.org/address/0x4200000000000000000000000000000000000014)
SequencerFeeVault [`0x4200000000000000000000000000000000000011`](https://worldscan.org/address/0x4200000000000000000000000000000000000011)
OptimismMintableERC20Factory [`0x4200000000000000000000000000000000000012`](https://worldscan.org/address/0x4200000000000000000000000000000000000012)
OptimismMintableERC721Factory [`0x4200000000000000000000000000000000000017`](https://worldscan.org/address/0x4200000000000000000000000000000000000017)
L1Block [`0x4200000000000000000000000000000000000015`](https://worldscan.org/address/0x4200000000000000000000000000000000000015)
GasPriceOracle [`0x420000000000000000000000000000000000000F`](https://worldscan.org/address/0x420000000000000000000000000000000000000F)
ProxyAdmin [`0x4200000000000000000000000000000000000018`](https://worldscan.org/address/0x4200000000000000000000000000000000000018)
BaseFeeVault [`0x4200000000000000000000000000000000000019`](https://worldscan.org/address/0x4200000000000000000000000000000000000019)
L1FeeVault [`0x420000000000000000000000000000000000001A`](https://worldscan.org/address/0x420000000000000000000000000000000000001A)
GovernanceToken [`0x4200000000000000000000000000000000000042`](https://worldscan.org/address/0x4200000000000000000000000000000000000042)
SchemaRegistry [`0x4200000000000000000000000000000000000020`](https://worldscan.org/address/0x4200000000000000000000000000000000000020)
EAS [`0x4200000000000000000000000000000000000021`](https://worldscan.org/address/0x4200000000000000000000000000000000000021)
## Ethereum Mainnet
Name Address
AnchorStateRegistryProxy [`0x90BB48fe3310499Db36437dCAE642F721e32d094`](https://etherscan.io/address/0x90BB48fe3310499Db36437dCAE642F721e32d094)
Batch Submitter [`0xdBBE3D8c2d2b22A2611c5A94A9a12C2fCD49Eb29`](https://etherscan.io/address/0xdBBE3D8c2d2b22A2611c5A94A9a12C2fCD49Eb29)
Challenger [`0xA4fB12D15Eb85dc9284a7df0AdBC8B696EdbbF1d`](https://etherscan.io/address/0xA4fB12D15Eb85dc9284a7df0AdBC8B696EdbbF1d)
DelayedWETHProxy [`0x19f4eF9dDE39a50a2EF948B7d55AA19F60d99498`](https://etherscan.io/address/0x19f4eF9dDE39a50a2EF948B7d55AA19F60d99498)
DisputeGameFactoryProxy [`0x069c4c579671f8c120b1327a73217D01Ea2EC5ea`](https://etherscan.io/address/0x069c4c579671f8c120b1327a73217D01Ea2EC5ea)
L1CrossDomainMessengerProxy [`0xf931a81D18B1766d15695ffc7c1920a62b7e710a`](https://etherscan.io/address/0xf931a81D18B1766d15695ffc7c1920a62b7e710a)
L1ERC721BridgeProxy [`0x1Df436AfDb2fBB40F1fE8bEd4Fc89A0D0990a8E9`](https://etherscan.io/address/0x1Df436AfDb2fBB40F1fE8bEd4Fc89A0D0990a8E9)
L1StandardBridgeProxy [`0x470458C91978D2d929704489Ad730DC3E3001113`](https://etherscan.io/address/0x470458C91978D2d929704489Ad730DC3E3001113)
L2OutputOracleProxy (legacy) [`0x19A6d1E9034596196295CF148509796978343c5D`](https://etherscan.io/address/0x19A6d1E9034596196295CF148509796978343c5D)
MIPS [`0xaCc005DCd857B401e4732E6F7837135A22825cfA`](https://etherscan.io/address/0xaCc005DCd857B401e4732E6F7837135A22825cfA)
OptimismMintableERC20FactoryProxy [`0x82Cb528466cF22412d89bdBE9bCF04856790dD0e`](https://etherscan.io/address/0x82Cb528466cF22412d89bdBE9bCF04856790dD0e)
OptimismPortalProxy [`0xd5ec14a83B7d95BE1E2Ac12523e2dEE12Cbeea6C`](https://etherscan.io/address/0xd5ec14a83B7d95BE1E2Ac12523e2dEE12Cbeea6C)
PermissionedDisputeGame [`0xe1dFFCBE4e22B813F26d2106D943C102e7cAb87e`](https://etherscan.io/address/0xe1dFFCBE4e22B813F26d2106D943C102e7cAb87e)
PreimageOracle [`0x1E1d73536A081Ef2F355d29794547a9770Aeb1E0`](https://etherscan.io/address/0x1E1d73536A081Ef2F355d29794547a9770Aeb1E0)
ProtocolVersionsProxy [`0x8eeF04eef96fef1050702453f75F0Fc4f8F70973`](https://etherscan.io/address/0x8eeF04eef96fef1050702453f75F0Fc4f8F70973)
ProxyAdmin [`0xd7405BE7f3e63b094Af6C7C23D5eE33Fd82F872D`](https://etherscan.io/address/0xd7405BE7f3e63b094Af6C7C23D5eE33Fd82F872D)
SafeProxyFactory [`0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2`](https://etherscan.io/address/0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2)
SuperchainConfigProxy [`0x95703e0982140D16f8ebA6d158FccEde42f04a4C`](https://etherscan.io/address/0x95703e0982140D16f8ebA6d158FccEde42f04a4C)
SystemConfigProxy [`0x6ab0777fD0e609CE58F939a7F70Fe41F5Aa6300A`](https://etherscan.io/address/0x6ab0777fD0e609CE58F939a7F70Fe41F5Aa6300A)
OpUSDCBridgeAdapter [`0x153A69e4bb6fEDBbAaF463CB982416316c84B2dB`](https://etherscan.io/address/0x153A69e4bb6fEDBbAaF463CB982416316c84B2dB)
## World Chain Sepolia Testnet
Name Address
L2ToL1MessagePasser [`0x4200000000000000000000000000000000000016`](https://worldchain-sepolia.explorer.alchemy.com/address/0x4200000000000000000000000000000000000016)
L2CrossDomainMessenger [`0x4200000000000000000000000000000000000007`](https://worldchain-sepolia.explorer.alchemy.com/address/0x4200000000000000000000000000000000000007)
L2StandardBridge [`0x4200000000000000000000000000000000000010`](https://worldchain-sepolia.explorer.alchemy.com/address/0x4200000000000000000000000000000000000010)
L2ERC721Bridge [`0x4200000000000000000000000000000000000014`](https://worldchain-sepolia.explorer.alchemy.com/address/0x4200000000000000000000000000000000000014)
SequencerFeeVault [`0x4200000000000000000000000000000000000011`](https://worldchain-sepolia.explorer.alchemy.com/address/0x4200000000000000000000000000000000000011)
OptimismMintableERC20Factory [`0x4200000000000000000000000000000000000012`](https://worldchain-sepolia.explorer.alchemy.com/address/0x4200000000000000000000000000000000000012)
OptimismMintableERC721Factory [`0x4200000000000000000000000000000000000017`](https://worldchain-sepolia.explorer.alchemy.com/address/0x4200000000000000000000000000000000000017)
L1Block [`0x4200000000000000000000000000000000000015`](https://worldchain-sepolia.explorer.alchemy.com/address/0x4200000000000000000000000000000000000015)
GasPriceOracle [`0x420000000000000000000000000000000000000F`](https://worldchain-sepolia.explorer.alchemy.com/address/0x420000000000000000000000000000000000000F)
ProxyAdmin [`0x4200000000000000000000000000000000000018`](https://worldchain-sepolia.explorer.alchemy.com/address/0x4200000000000000000000000000000000000018)
BaseFeeVault [`0x4200000000000000000000000000000000000019`](https://worldchain-sepolia.explorer.alchemy.com/address/0x4200000000000000000000000000000000000019)
L1FeeVault [`0x420000000000000000000000000000000000001A`](https://worldchain-sepolia.explorer.alchemy.com/address/0x420000000000000000000000000000000000001A)
GovernanceToken [`0x4200000000000000000000000000000000000042`](https://worldchain-sepolia.explorer.alchemy.com/address/0x4200000000000000000000000000000000000042)
SchemaRegistry [`0x4200000000000000000000000000000000000020`](https://worldchain-sepolia.explorer.alchemy.com/address/0x4200000000000000000000000000000000000020)
EAS [`0x4200000000000000000000000000000000000021`](https://worldchain-sepolia.explorer.alchemy.com/address/0x4200000000000000000000000000000000000021)
## Ethereum Sepolia Testnet
Name Address
AnchorStateRegistryProxy [`0xcC59030b952CE2c888f7Fc15f99E34c72cC6ca21`](https://sepolia.etherscan.io/address/0xcC59030b952CE2c888f7Fc15f99E34c72cC6ca21)
Batch Submitter [`0x0f3ff4731D7a10B89ED79AD1Fd97844d7F66B96d`](https://sepolia.etherscan.io/address/0x0f3ff4731D7a10B89ED79AD1Fd97844d7F66B96d)
Challenger [`0x945185C01fb641bA3E63a9bdF66575e35a407837`](https://sepolia.etherscan.io/address/0x945185C01fb641bA3E63a9bdF66575e35a407837)
DelayedWETHProxy [`0x5706FB7D51a5c75cafaED0506d5a8e8ade1D83f7`](https://sepolia.etherscan.io/address/0x5706FB7D51a5c75cafaED0506d5a8e8ade1D83f7)
DisputeGameFactoryProxy [`0x8Ec1111f67Dad6b6A93B3F42DfBC92D81c98449A`](https://sepolia.etherscan.io/address/0x8Ec1111f67Dad6b6A93B3F42DfBC92D81c98449A)
L1CrossDomainMessengerProxy [`0x7768c821200554d8F359A8902905Ba9eDe5659a9`](https://sepolia.etherscan.io/address/0x7768c821200554d8F359A8902905Ba9eDe5659a9)
L1ERC721BridgeProxy [`0x3580505c56f8560E3777E92Fb27f70fD20c5B493`](https://sepolia.etherscan.io/address/0x3580505c56f8560E3777E92Fb27f70fD20c5B493)
L1StandardBridgeProxy [`0xd7DF54b3989855eb66497301a4aAEc33Dbb3F8DE`](https://sepolia.etherscan.io/address/0xd7DF54b3989855eb66497301a4aAEc33Dbb3F8DE)
L2OutputOracleProxy (legacy) [`0xc8886f8BAb6Eaeb215aDB5f1c686BF699248300e`](https://sepolia.etherscan.io/address/0xc8886f8BAb6Eaeb215aDB5f1c686BF699248300e)
MIPS [`0xaCc005DCd857B401e4732E6F7837135A22825cfA`](https://sepolia.etherscan.io/address/0xaCc005DCd857B401e4732E6F7837135A22825cfA)
OptimismMintableERC20FactoryProxy [`0x2D272eF54Ee8EF5c2Ff3523559186580b158cd57`](https://sepolia.etherscan.io/address/0x2D272eF54Ee8EF5c2Ff3523559186580b158cd57)
OptimismPortalProxy [`0xFf6EBa109271fe6d4237EeeD4bAb1dD9A77dD1A4`](https://sepolia.etherscan.io/address/0xFf6EBa109271fe6d4237EeeD4bAb1dD9A77dD1A4)
PermissionedDisputeGame [`0xe1dFFCBE4e22B813F26d2106D943C102e7cAb87e`](https://sepolia.etherscan.io/address/0xe1dFFCBE4e22B813F26d2106D943C102e7cAb87e)
PreimageOracle [`0x1E1d73536A081Ef2F355d29794547a9770Aeb1E0`](https://sepolia.etherscan.io/address/0x1E1d73536A081Ef2F355d29794547a9770Aeb1E0)
ProtocolVersionsProxy [`0x01DBC9aBe8e59f021d47Cf79143DE830820CbA29`](https://sepolia.etherscan.io/address/0x01DBC9aBe8e59f021d47Cf79143DE830820CbA29)
ProxyAdmin [`0x3a987FE1cb587B0A1808cf9bB7Cbe0E341838319`](https://sepolia.etherscan.io/address/0x3a987FE1cb587B0A1808cf9bB7Cbe0E341838319)
SafeProxyFactory [`0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2`](https://sepolia.etherscan.io/address/0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2)
SuperchainConfigProxy [`0xC2Be75506d5724086DEB7245bd260Cc9753911Be`](https://sepolia.etherscan.io/address/0xC2Be75506d5724086DEB7245bd260Cc9753911Be)
SystemConfigProxy [`0x166F9406e79A656f12F05247fb8F5DfA6155bCBF`](https://sepolia.etherscan.io/address/0x166F9406e79A656f12F05247fb8F5DfA6155bCBF)
# World Chain Overview Source: https://docs.world.org/world-chain/index Overview of World Chain: a human-centric L2 with a free gas allowance for verified users, Mini Apps distribution and sybil resistance. World Chain is a blockchain for humans. World chain offers several unique primitives: * A free gas allowance for verified humans * Native mobile distribution to all World App users through [mini-apps](/mini-apps) * Simplified crypto transactions [through mini apps](/mini-apps/commands/verify) * Sybil resistance for developers via [World ID](/world-id) * Airdrop of WLD tokens to all verified humans These primitives enable World Chain builders to build never before possible applications and reach a global audience. World Chain is built on the [Superchain](https://docs.optimism.io/superchain/superchain-explainer). To learn more about what is World Chain, watch our presentation from the [A New World](https://www.youtube.com/watch?v=_RWvsCZ17x8\&ab_channel=World) event:
[A New World: World Chain and Priority Blockspace for Humans ft. Liam Horne](https://www.youtube.com/watch?v=NSxyKsSwjsc\&ab_channel=World) on our [YouTube channel](https://www.youtube.com/@worldnetworkofficial). Is anything missing in the documentation? Please reach out on our [Telegram for World Chain developers](https://t.me/worlddevelopersupport) # Bridges Source: https://docs.world.org/world-chain/providers/bridges ## Superchain Bridges The Superchain bridge is the native bridge for World Chain as it comes with the OP Stack smart contracts which power the network. There are several interface providers for this bridge and it allows users to bridge assets from Ethereum mainnet and onto World Chain and vice versa. ### Superbridge Core [Superbridge.app](https://superbridge.app/world-chain) is a blockchain bridging platform that enables users to transfer Ethereum (ETH) and ERC20 tokens between different blockchain networks, primarily focusing on OP Stack Layer 2 rollups chains including the World Chain network. ### Alchemy Bridge The [native bridge interface for World Chain mainnet](https://worldchain-mainnet.bridge.alchemy.com/) provided by [Alchemy](https://alchemy.com/) is the rollup as a service (RaaS) provider for World Chain. As part of this service it also provides a World Chain bridge interface for users to deposit to and withdraw assets from the network. There is also a [testnet bridge](https://worldchain-sepolia.bridge.alchemy.com/) available for developers and users to bridge assets to the World Chain Sepolia testnet. ## Third-party bridges Across is the recommended provider for bridging WLD between World Chain and other networks. ### Across [Across](https://app.across.to/bridge?) is an intent-based cross-chain bridging protocol that allows users to transfer tokens between different blockchain networks, particularly focusing on Layer 2 solutions and Ethereum-compatible chains. ### Brid.gg [Brid.gg](https://brid.gg/) facilitates cross-chain transactions and aims to improve interoperability between different blockchain networks. It primarily connects Ethereum Mainnet to OP Chains including World Chain, allowing for transfers of digital assets across various blockchains. ### Superbridge fast [Superbridge Fast](https://superbridge.app/fast) is a service offered by Superbridge that allows users to deposit and withdraw assets to and from World Chain quickly using third party bridges like [Synapse](/world-chain/providers/bridges#synapse), [Across](/world-chain/providers/bridges#across), and [Hyperlane](/world-chain/providers/bridges#hyperlane) which are directly integrated with the Superchain bridge interface. This is the fastest way to bridge assets to and from World Chain. The next best method is to use the native Superchain bridge for which both Alchemy and Superbridge Core provide interfaces. ### Synapse [Synapse](https://synapseprotocol.com/) is a cross-chain communication protocol that enables seamless asset transfers and messaging across different blockchain networks. It provides a secure and efficient infrastructure for interoperability, allowing users to move tokens and data between various chains without the need for centralized intermediaries. ### Hyperlane [Hyperlane](https://hyperlane.xyz/) is an innovative interoperability protocol designed to facilitate seamless cross-chain communication and enable the development of interchain applications. Hyperlane provides permissionless infrastructure for sending arbitrary data between blockchains, allowing developers to create applications that can be accessed from any connected chain. It supports general asset transfers and custom cross-chain messaging, enabling users to interact with assets and applications across different networks including World Chain. ### LayerZero [LayerZero](https://layerzero.network/) is an omnichain interoperability protocol that enables seamless communication between different blockchains. ### Chainlink CCIP [Chainlink CCIP](https://chain.link/cross-chain) is a blockchain interoperability protocol that enables developers to build secure applications that can transfer tokens, messages (data), or both tokens and messages across chains. You can see World Chain-specific documentation for CCIP [here](https://docs.chain.link/ccip/directory/mainnet/chain/ethereum-mainnet-worldchain-1). ### Thirdweb Universal Bridge Thirdweb's [Universal Bridge](https://portal.thirdweb.com/connect/pay/overview) is a comprehensive Web3 payment solution that allows your users to onramp, bridge, and swap on any EVM chain — with any EVM token or fiat — thanks to its automatic cross-chain routing. ## Liquidity Layers ### Cortex Protocol [Cortex Protocol](https://cortexprotocol.com/) is a decentralized, non-custodial liquidity protocol built on Ethereum that enables users to lend and borrow crypto assets. The protocol is designed to provide a secure and efficient platform for decentralized finance (DeFi) activities. # Data Indexing and Analytics Source: https://docs.world.org/world-chain/providers/data Data indexing solutions and other APIs streamline access to blockchain data, enabling efficient querying and real-time analysis of on-chain events. This is crucial for decentralized applications built on World Chain, as it reduces the cost of processing, and presenting data. APIs also facilitate third-party integration, enhancing developer experiences and expanding the ecosystem with more accessible, decentralized apps (dApps) and services. ## Allium [Allium](https://allium.so/) is an enterprise blockchain data platform designed to provide accurate, fast, and simple access to blockchain data across over 70 blockchains and more than 1,000 enriched schemas. It offers tools for developers and analysts to build real-time applications and perform cross-chain data exploration with low-latency APIs and customizable workflows. Allium features: * Low-Latency APIs * Data Freshness * Real-Time Data Streams * Decoded logs and traces ### Supported networks * World Chain ## Dune [Dune Analytics](https://dune.com/) is a blockchain data platform that enables users to query, visualize, and share insights from on-chain data. It provides a powerful interface for querying blockchain information using SQL-like queries, allowing users to create custom dashboards and visualizations. Dune's enhanced data accessibility and insights will give developers and non-developers on World Chain the ability to: * Explore metrics related to real humans interacting on the chain * Track the performance of DeFi protocols and DEXes * Explore onchain data of any public blockchain project With Dune's comprehensive dataset and web-based app, anyone (with a little SQL knowledge) may quickly query World Chain data and create insightful dashboards. For more data on [World](https://world.org/) and [World Chain](https://world.org/world-chain), visit the World Data Dashboards on Dune: * [World Chain](https://dune.com/blockchains/worldchain) * [World](https://dune.com/world/world) ### Supported networks * World Chain ## Zerion API [The Zerion API](https://zerion.io/api) can be used to build feature-rich web3 apps, wallets, and protocols with ease. Across all major blockchains, you can access wallets, assets, and chain data for web3 portfolios. ### Supported networks * World Chain ## GoldSky Subgraphs GoldSky Subgraphs is a data indexing service designed to simplify querying blockchain data. It provides developers with scalable, customizable subgraphs for efficiently indexing and retrieving on-chain data from various blockchain networks. GoldSky streamlines data access for decentralized applications (dApps), offering a user-friendly interface and advanced APIs that help developers query blockchain data faster and more accurately. ### Supported networks * World Chain ## Alchemy Subgraphs [Alchemy Subgraphs](https://docs.alchemy.com/reference/subgraphs-quickstart) provide fast, reliable blockchain indexing and community APIs. ### Supported networks * World Chain * World Chain Sepolia ## QuickNode Build real-time data processing pipelines with QuickNode Streams. Get instant access to World Chain data feeds with custom webhooks, filtering, and automatic retries. Backfill historical blockchain data in minutes with our ETL tools. Perfect for indexers, analytics platforms, and data-intensive applications. ### Supported Resources * [Streams](https://www.quicknode.com/streams) * [Backfills](https://www.quicknode.com/streams/backfills) ### Supported networks * World Chain * World Chain Sepolia ## Noves [Noves](https://www.noves.fi/) provides easy-to-use APIs for a variety of data on World Chain. You can find documentation for Noves APIs [here](https://docs.noves.fi/reference/api-overview). ### Supported networks * World Chain * World Chain Sepolia # Developer Tooling Source: https://docs.world.org/world-chain/providers/developer-tooling ## Alchemy Alchemy provides a suite of data tools to make it easy to build on World Chain: * [APIs](https://docs.alchemy.com/reference/token-api-quickstart) provide out-of-the-box solutions to retrieve fungible token balances, metadata, and historical transaction activity. * [Webhooks](https://docs.alchemy.com/reference/notify-api-quickstart) allow you to configure real-time push notifications for on-chain activity. * [Subgraphs](https://docs.alchemy.com/reference/subgraphs-quickstart) provide fast, reliable blockchain indexing and community APIs. * [Account Kit](https://accountkit.alchemy.com/) provides smart wallets to grow your app. Securely onboard and activate users with no seed phrase or gas fees with easy-to-use, enterprise-grade wallets. ### Supported networks * World Chain * World Chain Sepolia ## Blocknative [Blocknative's Gas Price API](https://docs.blocknative.com/gas-prediction/gas-platform) predicts next-block gas prices. ### Supported networks * World Chain ## QuickNode Access comprehensive developer tools built for World Chain: * [RPC API](https://www.quicknode.com/core-api) * [Streams](https://www.quicknode.com/streams) * [Functions](https://www.quicknode.com/functions) * [Dedicated Clusters](https://www.quicknode.com/clusters) * [Rollup Deployer](https://www.quicknode.com/rollup) Each tool is designed for production-grade applications with enterprise-level support and documentation. ### Supported networks * World Chain * World Chain Sepolia ## Tenderly [Tenderly](https://tenderly.co/) is a blockchain development platform that provides tools for building, monitoring, and managing smart contracts on Ethereum and other EVM-compatible chains. It offers features like real-time transaction monitoring, debugging, and advanced analytics to help developers optimize and maintain their decentralized applications (dApps). ### Supported networks * World Chain * World Chain Sepolia ## Thirdweb [Thirdweb](https://thirdweb.com/) is a comprehensive web3 development platform that provides a full-stack, open-source toolkit for building decentralized applications on EVM-compatible chains. It offers frontend SDKs for connecting users to web3, backend APIs for scalable smart contract interactions, and a suite of pre-built, audited smart contracts. Thirdweb's platform simplifies the development process by providing tools for wallet integration, NFT minting, payment processing, and user onboarding, allowing developers to create sophisticated web3 applications with ease across various verticals including gaming, creator platforms, and enterprise solutions. ### Supported networks * World Chain * World Chain Sepolia ## Worldscan (Etherscan) [Worldscan](https://worldscan.org/) (provided by [Etherscan](https://etherscan.io/)) provides several valuable features for developers working with the Ethereum blockchain. Here are some key developer-focused features offered by Etherscan: * API access to the World Chain Blockchain explorer * Smart contract verification * Smart contract analytics * Gas Tracking ### Supported networks * [World Chain](https://worldscan.org/) * [World Chain Sepolia](https://sepolia.worldscan.org/) # Block Explorers Source: https://docs.world.org/world-chain/providers/explorers ## Blockscout A [Blockscout](https://blockscout.com/) explorer is available for [World Chain](https://worldchain-mainnet.explorer.alchemy.com/) provided by Alchemy. Blockscout is a comprehensive, open-source blockchain explorer designed for inspecting and analyzing EVM (Ethereum Virtual Machine) based blockchains. A testnet explorer is also available for [World Chain Sepolia](https://worldchain-sepolia.explorer.alchemy.com/). ## Dora [Dora](https://www.ondora.xyz/network/worldchain/interactions) is an advanced multi-chain block explorer and unified search engine designed for the evolving blockchain ecosystem. It offers users the ability to search and interact with data across more than 10 different networks, including World Chain. It is an innovative blockchain explorer and search engine designed for the multichain and multi-VM world. ## Worldscan (Etherscan) [Worldscan](https://worldscan.org) (provided by [Etherscan](https://etherscan.io)) is a comprehensive blockchain explorer and analytics platform specifically designed for the World Chain network. It allows users to search, verify, and analyze transactions, addresses, smart contracts, and tokens on the World Chain blockchain. [World Chain Sepolia](https://sepolia.worldscan.org/) is also supported. Worldscan provides tools to help you view transaction data and debug smart contracts: * Search by address, transaction hash, batch, or token * View, verify, and interact with smart contract source code * View detailed transaction information * View L1-to-L2 and L2-to-L1 transactions # Nodes Source: https://docs.world.org/world-chain/providers/nodes ## Alchemy [Alchemy](https://alchemy.com/) is a leading blockchain development platform that provides robust node provisioning services for Web3 applications on World Chain. Alchemy's node infrastructure services simplify the process of building, deploying, and scaling blockchain applications by providing developers with access to a network of nodes on an on-demand basis. For access to a World Chain node, check out the [World Chain Alchemy page](https://www.alchemy.com/world-chain). ### Supported networks * World Chain * World Chain Sepolia ## Blast API Blast API offers standardized blockchain API services for Web3 development, allowing users to generate dedicated endpoints (RPC/WSS/REST) for supported blockchain networks. The platform employs geographically distributed third-party nodes to ensure reliability, low latency, and decentralization. ### Supported networks * World Chain ## QuickNode Enterprise-grade infrastructure for World Chain development with global edge delivery. Build with high-performance RPC APIs, real-time data streaming/ETL capabilities, decentralized storage via IPFS, and extensive developer tools through our marketplace. Ideal for teams building production-grade applications on World Chain. QuickNode offers several benefits for developers building on World Chain, read more [here](https://quicknode.notion.site/QuickNode-Benefits-for-WorldChain-Developers-14b15a82e84c807ba912cc1a6a8a5c4a)! ### Supported networks * [World Chain](https://www.quicknode.com/chains/worldchain) * [World Chain Sepolia](https://www.quicknode.com/chains/worldchain) * [Documentation](https://www.quicknode.com/chains/worldchain) ## Tenderly RPC [Tenderly](https://tenderly.co/) is a blockchain development platform that provides tools for building, monitoring, and managing smart contracts on Ethereum and other EVM-compatible chains. They also provide node infrastructure services for World Chain. ### Supported networks * World Chain * World Chain Sepolia # Onramps Source: https://docs.world.org/world-chain/providers/onramps ## Ramp Network [Ramp Network](https://ramp.network/) is a fintech company that provides a seamless, non-custodial fiat-to-crypto and crypto-to-fiat onramp solution for decentralized applications, wallets, and platforms. It enables users to buy, sell, and trade cryptocurrencies directly through integrated services without leaving the app they are using. Ramp focuses on simplifying the process for users by handling complex regulatory compliance, identity verification (KYC), and liquidity provisioning, making it easier for businesses to integrate crypto transactions into their services. Their API is widely used by developers to onboard users into the crypto ecosystem with minimal friction. ## Alfred pay [Alfred Pay](https://www.alfredpay.io/) is a fintech company focused on bridging the gap between traditional financial systems and digital assets, specifically across Latin America. It offers a fiat-to-crypto and crypto-to-fiat gateway service, enabling users and businesses to easily move between digital currencies and local fiat currencies. ## Moonpay [MoonPay](https://www.moonpay.com/) is a global fintech platform that simplifies the process of buying and selling cryptocurrencies. It provides a seamless fiat-to-crypto and crypto-to-fiat service, allowing users to purchase digital assets like Bitcoin and Ethereum using traditional payment methods such as credit cards, bank transfers, and mobile payment options. MoonPay also offers APIs and SDKs for developers to integrate crypto transactions into their applications. # Oracles Source: https://docs.world.org/world-chain/providers/oracles Blockchain oracles are essential middleware that allow smart contracts to access external data and systems that exist outside the blockchain. They act as a secure bridge between blockchains and external sources, enabling smart contracts to use data that primarily exists off-chain. ## Api3 [Api3](https://api3.org/) provides 200+ push-based data feeds on World Chain. You can enable any of Api3's data feeds on World Chain using the [Api3 Market](https://market.api3.org/world). Find the Api3 Market documentation [here](https://docs.api3.org/dapps/integration/). ### Supported networks * World Chain ## Chainlink Data Streams [Chainlink Data Streams](https://chain.link/) delivers low-latency market data offchain, which you can verify onchain. You can find documentation for Chainlink Data Streams Direct [here](https://docs.chain.link/data-streams/streams-direct). ### Supported networks * World Chain ## Pyth [Pyth](https://www.pyth.network/) provides 1300+ pull-based price feeds on World Chain. You can find documentation for Pyth Price Feeds [here](https://docs.pyth.network/price-feeds). ### Supported networks * World Chain * World Chain Sepolia ## RedStone [RedStone](https://www.redstone.finance/)'s Pull Model provides price data for 200+ assets that can be verified on World Chain. You can find documentation for RedStone's Pull Model [here](https://docs.redstone.finance/docs/dapps/redstone-pull/). ### Supported networks * World Chain ## WitNet [WitNet](https://witnet.io/) provides 6 price feeds on World Chain with Wit/Price Feeds, in addition to secure random data with Wit/Randomness and securely retrieving arbitrary data via HTTP from within smart contracts using Wit/Oracle. Documentation for these products can be found below: * [Wit/Price Feeds Documentation](https://docs.witnet.io/intro/tutorials/data-feeds-tutorial) * [Wit/Randomness Documentation](https://docs.witnet.io/intro/tutorials/randomness) * [Wit/Oracle Documentation](https://docs.witnet.io/intro/tutorials/apis-and-http-get-post) ### Supported networks * World Chain * World Chain Sepolia # Paymasters Source: https://docs.world.org/world-chain/providers/paymasters ## Alchemy [Alchemy Paymasters](https://www.alchemy.com/overviews/what-is-a-paymaster) are smart contracts that enable decentralized applications (dApps) to implement flexible gas policies, including: * Sponsoring gas fees for users * Accepting gas payments in ERC-20 tokens instead of native blockchain currency ### Supported Networks * World Chain * World Chain Sepolia ## Pimlico [Pimlico](https://pimlico.io/) provides account abstraction infrastructure including [paymasters](https://docs.pimlico.io/infra/paymaster) and [bundlers](https://docs.pimlico.io/infra/bundler). They offer two types of paymasters to abstract away gas fees for users in the ERC-4337 ecosystem. A verifying paymaster allows developers to sponsor on-chain gas fees for users, it utilizes an off-chain Pimlico balance loaded through a dashboard and an ERC-20 paymaster which is a permissionless on-chain smart contract that enables users to pay gas fees using their ERC-20 tokens and operates without requiring developer intervention. Pimlico's paymasters can be seamlessly integrated with [permissionless.js](https://docs.pimlico.io/permissionless), a TypeScript library built on [viem](https://github.com/wevm/viem/) for ERC-4337 development . ### Supported Networks * World Chain * World Chain Sepolia ## Thirdweb Not only does [Thirdweb](https://thirdweb.com/) provide developer tools, but they also have [ERC-4337 compliant smart contract accounts](https://portal.thirdweb.com/contracts/build/base-contracts/erc-4337) with role-based permission control. They offer two main types: Simple and Managed smart accounts. ### Supported Networks * World Chain * World Chain Sepolia # World Chain Data Dashboards Source: https://docs.world.org/world-chain/quick-start/data Worldcoin has partnered with several [data providers](/world-chain/providers/data) which have indexed World Chain data and serve it to developers and data analysts through their APIs. As part of our ongoing efforts for transparency and accountability through open-sourcing our technologies like the orb hardware, orb firmware, biometrics pipeline, World ID protocol and most other components of the Worldcoin ecosystem, we believe that having open-source data and dashboards that showcase the progress of the Worldcoin project is essential. You can find all of the main dashboards that track World Chain, World App and Worldcoin metrics in the [World Dune dashboard](https://dune.com/world/world). If you want to see Dune dashboards that are specific to World Chain, check out [this Dune page](https://dune.com/blockchains/worldchain). Another very important dashboard is the [L2BEAT World Chain dashboard](https://l2beat.com/scaling/projects/world) which shows all the metrics related to the OP Stack which includes a security, decentralization and scalability assessment, risk analysis table and a TVL dashboard. # Unique Features Source: https://docs.world.org/world-chain/quick-start/features World Chain is built on the OP Stack and is part of the Superchain, it uses the EVM for execution and Ethereum for data availability and finality. These are standard properties of all Superchain networks, however, there are several features that make World Chain unique. World Chain is a network built for unique humans and its features reflect that. ## Priority Blockspace for Humans While it's open for everyone, World Chain was designed to prioritize anonymously verified human interactions over bots and AI through direct protocol integrations with [World ID](https://world.org/world-id) for proof-of-human verification. One of the initial protocol integrations being worked on is Priority Blockspace for Humans (PBH). PBH enables verified users to execute transactions guaranteeing top of block inclusion, enabling a more frictionless user experience. PBH ensures that ordinary users aren't unfairly disadvantaged by automated systems, greatly mitigates the impact of [MEV](https://ethereum.org/en/developers/docs/mev/) attacks and exploits, and reduces the need to pay significant gas fees to be included in a block. PBH also enables future flexibility, allowing for a separate EIP-1559-style market for human transactions. If you want to learn the details of how Priority Blockspace for Humans will work, please read the [World Blog PBH article](https://world.org/blog/engineering/introducing-pbh-priority-blockspace-for-humans). If you are interested in PBH, how it works or if you would like to apply for a grant to help contribute to the roadmap, check out the [Human Collective Grants section](/world-chain/developers/grants) or send us a message to [grants@worldcoin.org](mailto:grants@worldcoin.org). ## Gas Allowance for Humans Not only do humans get priority inclusion on World Chain through PBH, but they also will have a gas stipend to transact on World Chain for free. Initially the stipend to fund this gas allowance will be provided by the [World Foundation](https://worldcoin.foundation) with the goal of progressive decentralization allowing World governance to set it. There are two main approaches to implement the gas allowance, one option is on the app/wallet level through the use of account abstraction and the other one is to set it at the OP Stack level through sequencer reimbursements or separate fee markets for unique humans where unique humans are not charged or their fees are paid for by non-human transactions in the mempool which are executed by the sequencer. The simplest one is to implement a World ID gatekept [EIP4337](https://www.erc4337.io/) [paymaster](https://www.alchemy.com/overviews/what-is-a-paymaster) where each `userOp` or group of `userOps` per user requires a World ID proof. More details on gas allowance for humans coming soon. # Funding a Wallet Source: https://docs.world.org/world-chain/quick-start/fund-wallet Learn how to fund your wallet on World Chain Make sure that whatever assets you are bridging to and from World Chain are always supported by your wallet and the exchange you are using. Depositing an unsupported asset on an unsupported chain to an unsupported wallet or exchange will result in loss of funds. In order to use World Chain you will need an [EVM-compatible wallet](https://ethereum.org/en/wallets/) such as MetaMask, Rabby, or any other wallet where you can add custom EVM networks. If you are a user of World App then you can use the built-in wallet to interact with World Chain as well as the existing onramps and offramps which are integrated into the app. The [Ethereum.org website](https://ethereum.org/en/) has [a great explainer on wallets](https://ethereum.org/en/wallets/) which goes into more detail on how to choose a wallet that is right for you. ## Bridging from Ethereum If you are coming from Ethereum, you can bridge your assets to World Chain using the bridge interface provided by [Alchemy](https://worldchain-mainnet.bridge.alchemy.com/) which is an interface to the native OP Stack bridge contract that allows you to move assets between Ethereum and World Chain natively. Since World Chain is an optimistic rollup, built on the [OP Stack](https://docs.optimism.io/stack/getting-started) and part of the [Superchain](https://docs.optimism.io/superchain/superchain-explainer) it takes about 7 days to withdraw from World Chain back to Ethereum through the native bridge as the OP Stack needs to wait for [the fault proof period](https://docs.optimism.io/stack/fault-proofs/explainer) to expire before the L2 finalizes and the assets can be withdrawn back to Ethereum. ## Bridging from another network There are several other bridges live between different L2s that are provided by multiple third parties that leverage different bridging mechanisms. The World Chain documentation has a [bridge section](/world-chain/providers/bridges) that lists several bridges that support the network. ## Bridging from an exchange or onramp provider If you are coming from an exchange or [onramp provider](/world-chain/providers/onramps) that supports World Chain and you already have a wallet that supports the network then you can deposit your assets directly to your wallet. # Getting Started Source: https://docs.world.org/world-chain/quick-start/index Add World Chain to your wallet and start using mainnet or Sepolia, with manual network details and helpful links. In order to start using World Chain, you need to add the World Chain network to your wallet of choice. Either by clicking the button below: Or by manually adding the information available in the [World Chain network section below](/world-chain/quick-start/info). # Network Information Source: https://docs.world.org/world-chain/quick-start/info World Chain network configuration and technical details ## World Chain Mainnet World Chain Mainnet's per-block gas limit and target are regularly increased to accommodate the growing number of users and transactions. You can always find the current gas limit [here](https://etherscan.io/address/0x6ab0777fD0e609CE58F939a7F70Fe41F5Aa6300A#readProxyContract#F18).
Property Value
Framework OP Stack
Chain ID ID480 (0x1e0)
EIP-3770 Short Name wc
Settlement layer Ethereum
Data availability Ethereum
Gas Limit 80M
Gas Target 40M
Block Time 2s
Important Links URLs
Bridge worldchain-mainnet.bridge.alchemy.com
Block Explorer worldscan.org
Status Page worldchain-mainnet-status.alchemy.com
RPC worldchain-mainnet.g.alchemy.com/public
Developer Telegram @worlddevelopersupport
Hardfork Timestamp
Fjord 1721826000 (Wed 24 Jul 2024 13:00:00 UTC)
Granite 1727780400 (Tue 1 Oct 2024 11:00:00 UTC)
Holocene 1738238400 (Thu 30 Jan 2025 12:00:00 UTC)
## World Chain Sepolia Testnet
Property Value
Framework OP Stack
Chain ID ID4801 (0x12C1)
EIP-3770 Short Name wcsep
Settlement layer Ethereum Sepolia
Data availability Ethereum Sepolia
Gas Limit 30M
Gas Target 15M
Block Time 2s
Important Links URLs
Bridge worldchain-sepolia.bridge.alchemy.com
Block Explorer worldchain-sepolia.explorer.alchemy.com
Status Page worldchain-sepolia-status.alchemy.com
Faucet alchemy.com/faucets/world-chain-sepolia
RPC worldchain-sepolia.g.alchemy.com/public
Hardfork Timestamp
Fjord 1721739600 (Tue 23 Jul 2024 13:00:00 UTC)
Granite 1726570800 (Tue 17 Sep 2024 11:00:00 UTC)
Holocene 1737633600 (Thu 23 Jan 2025 12:00:00 UTC)
Pectra Blob Fix 1742486400 (Thu 20 Mar 2025 16:00:00 UTC)
# Why World Chain? Source: https://docs.world.org/world-chain/quick-start/why The [World App](https://world.org/world-app) has undergone several transitions and it has lived on multiple chains over time. First it was [Hubble](https://github.com/worldcoin/hubble-commander), an open-source optimistic rollup with [BLS signature aggregation](https://hackmd.io/@benjaminion/bls12-381) of ERC20 transfers, then it was [Polygon PoS](https://polygon.technology/polygon-pos), recently [OP mainnet](https://www.optimism.io/) and now [World Chain](https://world.org/world-chain). There were several motivating factors for all of these changes. Onchain Evolution of World App ## Onchain evolution Originally, the main target use case of World App was payments and WLD grants (pre-launch beta version), but as we required other applications such as DeFi and identity, World App was migrated to Polygon PoS as it provided full EVM programmability and access to tools, infrastructure and applications like [Safe smart contract wallets](https://world.org/blog/announcements/introducing-world-chain) DEXes and liquidity like [Uniswap](https://app.uniswap.org/), and plenty of others that directly provided utility to World App, [World ID](https://world.org/world-id), their users and the wider World ecosystem of applications and integrations. Another big motivating factor is cost, security, decentralization and ecosystem network effects which were the main motivators behind the switch from Polygon PoS to OP mainnet. As the rollup-centric roadmap became the foundation of Ethereum scalability, solutions became adopted enough and reached a certain point of maturity, it made sense to migrate World App over as it benefits a lot from all of the other integrations, applications and infrastructure that were and are available on OP mainnet. We migrated over ahead of our launch on July 24th 2023 and over the upcoming year World App reached a peak of [60% of OP mainnet blockspace consumption](https://dune.com/queries/491942/932394) (over a 30 day period). As World App starts demanding more and more blockspace in order to support the goal of the largest digital identity and financial network with over a billion unique humans, it will require further scaling the network it operates under. Which is why World Chain exists, it has pristine blockspace for the World App and the World ID ecosystem of applications. For more details on World Chain you can read our [Introducing World Chain blogpost](https://world.org/blog/announcements/introducing-world-chain). World Chain will allow World App, World ID and their ecosystems to scale throughput, increase gas limits, data availability and its overall scalability as the OP Stack improves with better execution clients, higher Ethereum blob counts and sizes (more DA) and plenty of other technological improvements. ## What is different about World Chain? The main differentiating properties of World Chain compared to other L2s or other Superchain members are that World Chain is a blockchain made for humans, where the transactions of unique humans have priority and where they also have a free allowance, just for being a World ID verified unique human using proof of unique human. More on these features in the [Priority Blockspace for Humans and Free Gas Allowance sections](/world-chain/quick-start/features) Compared to other L2s one other very big differentiator is the number of unique users that World App brings with it, and the possibilities for use cases and applications used by millions of unique humans will bring. ## Further resources If you want to learn more about World Chain, OP Stack and the Superchain, you can take a look at the resources below: * [A New World: World Chain and Priority Blockspace for Humans ft. Liam Horne](https://www.youtube.com/watch?v=NSxyKsSwjsc\&ab_channel=World) * [Encode x World Educate Series: World Chain and the OP Stack](https://www.youtube.com/watch?v=7pt8c5fy-xg\&ab_channel=EncodeClub) * [OP Mainnet docs](https://docs.optimism.io/app-developers/building-apps) * [OP Stack docs](https://docs.optimism.io/stack/getting-started) * [Superchain docs](https://docs.optimism.io/superchain/superchain-explainer) # How to Set Up a World Chain Node Source: https://docs.world.org/world-chain/reference/node-setup Follow this guide to set up your own World Chain node. Anyone running a World Chain node is encouraged to join this Telegram channel for notifications of required software updates or other relevant information: [World Chain Node Updates Telegram Channel](https://t.me/world_chain_updates) ## Overview World Chain mainnet and testnet run on the OP Stack as part of the Superchain. We provide a simple Docker Compose configuration for running World Chain nodes, [simple-worldchain-node](https://github.com/worldcoin-foundation/simple-worldchain-node). If you're interested in building a node from source, see the [documentation from Optimism](https://docs.optimism.io/operators/node-operators/tutorials/node-from-source). ## Using `simple-worldchain-node` `simple-worldchain-node` supports World Chain Mainnet and Sepolia, full nodes and archive nodes, and two execution clients: [op-geth](https://github.com/ethereum-optimism/op-geth) and [op-reth](https://github.com/paradigmxyz/reth). World Chain archive node snapshots for `op-geth` are available from Bware Labs [here](https://bwarelabs.com/snapshots/worldchain). ### Installation First, download [`simple-worldchain-node`](https://github.com/worldcoin-foundation/simple-worldchain-node) and create your `.env` file. ```bash Download simple-worldchain-node theme={"system"} git clone https://github.com/worldcoin-foundation/simple-worldchain-node.git cd simple-worldchain-node cp .env.example .env ``` Ensure you have installed Docker and Docker Compose by following [this guide](https://docs.docker.com/compose/install/#scenario-three-install-the-docker-compose-standalone). ### Configuration Open your `.env` file in an editor of your choice. The following values must be configured before starting your node. Used to select which network the node connects to, either `worldchain-mainnet` or `worldchain-sepolia`. Used to select your execution client, either `geth` (default) or `reth` (recommended for archive nodes). When using `op-geth`, determines which type of node to run. Either `full` (less storage, but only recent history) or `archive` (more storage, all history). An L1 (Ethereum) RPC endpoint. We recommend using [Alchemy](https://www.alchemy.com/) or [QuickNode](https://www.quicknode.com), but any Ethereum RPC provider or archive node will work. An L1 Beacon Archive RPC endpoint. Note that this is not the same as a standard RPC endpoint, as this is used to retrieve Blobs from the Ethereum Beacon Chain. We recommend using [QuickNode](https://www.quicknode.com/). Selects which RPC provider is set in `OP_NODE__RPC_ENDPOINT`. This allows for more efficient syncing given different RPC capabilities. Choose from `alchemy`, `quicknode`, `erigon`, or `basic` for other RPC providers. Selects whether `op-geth` will use snap sync or full sync. Defaults to `snap` for non-archival nodes and `full` for archive nodes. Selects whether `op-geth` uses hash-based or path-based storage. As of `op-geth` v1.101602.0, path-based storage is supported for archive nodes and is enabled by default, leading to lower disk usage. The `eth_getProof` RPC method is not supported when using path-based storage. For details on additional settings, see the `simple-worldchain-node` [README](https://github.com/worldcoin-foundation/simple-worldchain-node?tab=readme-ov-file#optional-configurations). ### Running your node To start your node in the background, run the following command from the `simple-worldchain-node` folder: ```bash theme={"system"} docker compose up -d --build ``` To view logs for your node, run the following command: ```bash theme={"system"} docker compose logs -f --tail 10 ``` To shut down your node: ```bash theme={"system"} docker compose down ``` ### Monitoring your node A Grafana dashboard is included to monitor your node. Access it by visiting [http://localhost:3000](http://localhost:3000) and logging in with these credentials: * Username: `admin` * Password: `worldchain` ### Upgrading your Node When new versions of `op-geth`, `op-reth`, or `op-node` are released, we will update the `simple-worldchain-node` repository to use these new versions. You can then update your node to use these versions with the following commands: ```bash theme={"system"} git pull docker compose pull docker compose up -d --build ``` # Useful Contract Deployments Source: https://docs.world.org/world-chain/reference/useful-contracts ## Tokens
Contract World Chain Mainnet Address
WLD [`0x2cfc85d8e48f8eab294be644d9e25c3030863003`](https://worldscan.org/address/0x2cFc85d8E48F8EAB294be644d9E25C3030863003)
WBTC [`0x03c7054bcb39f7b2e5b2c7acb37583e32d70cfa3`](https://worldscan.org/address/0x03c7054bcb39f7b2e5b2c7acb37583e32d70cfa3)
SDAI [`0x859dbe24b90c9f2f7742083d3cf59ca41f55be5d`](https://worldscan.org/address/0x859dbe24b90c9f2f7742083d3cf59ca41f55be5d)
WETH [`0x4200000000000000000000000000000000000006`](https://worldscan.org/address/0x4200000000000000000000000000000000000006)
USDC [`0x79A02482A880bCE3F13e09Da970dC34db4CD24d1`](https://worldscan.org/address/0x79A02482A880bCE3F13e09Da970dC34db4CD24d1)
L1 OpUSDCBridgeAdapter (Ethereum mainnet) [`0x153A69e4bb6fEDBbAaF463CB982416316c84B2dB`](https://etherscan.io/address/0x153A69e4bb6fEDBbAaF463CB982416316c84B2dB)
## World ID You can find the World ID Address Book for all chains [here](https://docs.world.org/world-id/reference/address-book).
Contract World Chain Mainnet Address
WorldIDAddressBook [`0x57b930D551e677CC36e2fA036Ae2fe8FdaE0330D`](https://worldscan.org/address/0x57b930D551e677CC36e2fA036Ae2fe8FdaE0330D)
WorldIDRouter [`0x17B354dD2595411ff79041f930e491A4Df39A278`](https://worldscan.org/address/0x17B354dD2595411ff79041f930e491A4Df39A278)
## Gnosis Safe 1.3.0
Contract World Chain Mainnet Address
HelperBatch Contract [`0x8d98006269238CAEd033b2d94661B29312AD09b7`](https://worldscan.org/address/0x8d98006269238CAEd033b2d94661B29312AD09b7)
SafeL2Singleton [`0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552`](https://worldscan.org/address/0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552)
SafeProxyFactory [`0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2`](https://worldscan.org/address/0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2)
## Gnosis Safe 1.4.1
Contract World Chain Mainnet Address
HelperBatch Contract [`0x866087c23a7eE1fD5498ef84D59aF742f3d4b322`](https://worldscan.org/address/0x866087c23a7eE1fD5498ef84D59aF742f3d4b322)
SafeL2Singleton [`0x29fcB43b46531BcA003ddC8FCB67FFE91900C762`](https://worldscan.org/address/0x29fcB43b46531BcA003ddC8FCB67FFE91900C762)
SafeProxyFactory [`0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67`](https://worldscan.org/address/0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67)
## Gnosis Modules
Contract World Chain Mainnet Address
TimeBasedAllowance Module [`0xa9bcF56d9FCc0178414EF27a3d893C9469e437B7`](https://worldscan.org/address/0xa9bcF56d9FCc0178414EF27a3d893C9469e437B7)
4337 Module [`0x75cf11467937ce3F2f357CE24ffc3DBF8fD5c226`](https://worldscan.org/address/0x75cf11467937ce3F2f357CE24ffc3DBF8fD5c226)
AddModules Helper [`0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67`](https://worldscan.org/address/0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67)
## Uniswap
Contract World Chain Mainnet Address
ApprovalSwap [`0xf4305dd6256dc2b0d07caaf2953688defbc86e9d`](https://worldscan.org/address/0xf4305dd6256dc2b0d07caaf2953688defbc86e9d)
v3CoreFactoryAddress [`0x7a5028BDa40e7B173C278C5342087826455ea25a`](https://worldscan.org/address/0x7a5028BDa40e7B173C278C5342087826455ea25a)
AddModules Helper [`0x091AD9e2e6e5eD44c1c66dB50e49A601F9f36cF6`](https://worldscan.org/address/0x091AD9e2e6e5eD44c1c66dB50e49A601F9f36cF6)
Multicall2Address [`0x0a22c04215c97E3F532F4eF30e0aD9458792dAB9`](https://worldscan.org/address/0x0a22c04215c97E3F532F4eF30e0aD9458792dAB9)
ProxyAdminAddress [`0x8B52DaCB7B5d9A959CDcD5419061c0eDD1296c29`](https://worldscan.org/address/0x8B52DaCB7B5d9A959CDcD5419061c0eDD1296c29)
TickLensAddress [`0xE61df0CaC9d85876aCE5E3037005D80943570623`](https://worldscan.org/address/0xE61df0CaC9d85876aCE5E3037005D80943570623)
NftDescriptorLibraryAddressV1\_3\_0 [`0x38c68A1D60C47973EcE5bc1725B65D8Bec438192`](https://worldscan.org/address/0x38c68A1D60C47973EcE5bc1725B65D8Bec438192)
NonfungibleTokenPositionDescriptorAddressV1\_3\_0 [`0x70410a302c4a5c52C659b780941c947Abd437FeB`](https://worldscan.org/address/0x70410a302c4a5c52C659b780941c947Abd437FeB)
DescriptorProxyAddress [`0xe6FcB4952b2d3Fab6DA4BC165831f5575e093feC`](https://worldscan.org/address/0xe6FcB4952b2d3Fab6DA4BC165831f5575e093feC)
NonfungibleTokenPositionManagerAddress [`0xec12a9F9a09f50550686363766Cc153D03c27b5e`](https://worldscan.org/address/0xec12a9F9a09f50550686363766Cc153D03c27b5e)
V3MigratorAddress [`0x9EBDdCBa71C9027E1eB45135672a30bcFEec9de3`](https://worldscan.org/address/0x9EBDdCBa71C9027E1eB45135672a30bcFEec9de3)
V3StakerAddress [`0x053956ab1618EcaCc135Ebc18Fd7564979aD4295`](https://worldscan.org/address/0x053956ab1618EcaCc135Ebc18Fd7564979aD4295)
QuoterV2Address [`0x10158D43e6cc414deE1Bd1eB0EfC6a5cBCfF244c`](https://worldscan.org/address/0x10158D43e6cc414deE1Bd1eB0EfC6a5cBCfF244c)
## Grants
Contract World Chain Mainnet Address
WLDGrant [`0x7DD5B6B5A574EFd452AC0cfE3e1B69384a03C6f9`](https://worldscan.org/address/0x7DD5B6B5A574EFd452AC0cfE3e1B69384a03C6f9?tab=contract)
RecurringGrantDrop [`0x2c1Ca1FBbD5f28e5492Cc6bF8C4e8c57354eb162`](https://worldscan.org/address/0x2c1Ca1FBbD5f28e5492Cc6bF8C4e8c57354eb162?tab=contract)
WLDGrantReservations [`0x3a00fe3254c94c4689cb5163c91ee501d942e710`](https://worldscan.org/address/0x3a00fE3254c94C4689CB5163c91Ee501D942E710?tab=contract)
RecurringGrantDropReservations [`0xc2D270651cEF0AA3734c9A7fEaCd3b3B39e36e18`](https://worldscan.org/address/0xc2D270651cEF0AA3734c9A7fEaCd3b3B39e36e18?tab=contract)
Grants4FirstBatch [`0xae3f204c75e46c27f66c843bc9f3bbd04a6374c5`](https://worldscan.org/address/0xaE3f204c75E46C27f66C843bC9F3Bbd04a6374c5?tab=contract)
WLDVault [`0x14a028cC500108307947dca4a1Aa35029FB66CE0`](https://worldscan.org/address/0x14a028cC500108307947dca4a1Aa35029FB66CE0?tab=contract)
## MiniKit
Contract World Chain Mainnet Address
MiniKitTransfer [`0x9CC547e0Ca60dC249Eea2d91Ba12F00C4ca12787`](https://worldscan.org/address/0x9CC547e0Ca60dC249Eea2d91Ba12F00C4ca12787?tab=contract)
## Account Abstraction
Contract World Chain Mainnet Address
Entrypoint v0.7 [`0x0000000071727De22E5E9d8BAf0edAc6f37da032`](https://worldscan.org/address/0x0000000071727De22E5E9d8BAf0edAc6f37da032#code)
## Oracles
Contract World Chain Mainnet Address
WLD/USD [`0x8Bb2943AB030E3eE05a58d9832525B4f60A97FA0`](https://worldscan.org/address/0x8Bb2943AB030E3eE05a58d9832525B4f60A97FA0)
ETH/USD [`0xe1d72a719171DceAB9499757EB9d5AEb9e8D64A6`](https://worldscan.org/address/0xe1d72a719171DceAB9499757EB9d5AEb9e8D64A6)
BTC/USD [`0xdD91675235C37a47597c053807d61Da27Ae1AE6C`](https://worldscan.org/address/0xdD91675235C37a47597c053807d61Da27Ae1AE6C)
USDC/USD [`0xF4301686AfF4eE36d70c718a9e62309b53862BE8`](https://worldscan.org/address/0xF4301686AfF4eE36d70c718a9e62309b53862BE8)
ezETH/USD [`0x78ce3664d1582e14270092ea9817f111ac53524b`](https://worldscan.org/address/0x78ce3664d1582e14270092ea9817f111ac53524b#code)
# Resources Source: https://docs.world.org/world-chain/resources ## Status Links * [World Chain Mainnet Status](https://worldchain-mainnet-status.alchemy.com/) * [World Chain Sepolia Status](https://worldchain-sepolia-status.alchemy.com/) ## Documentation * [Brand Kit](https://world.org/press) * [World Whitepaper](https://whitepaper.world.org/) ## Legal * [Terms of Service](https://vault.pactsafe.io/s/8a18d792-fd76-44db-9b92-b0bb7981c248/legal.html#contract-byutjvtyt) * [Privacy Policy](https://vault.pactsafe.io/s/8a18d792-fd76-44db-9b92-b0bb7981c248/legal.html#contract-s1ytru6kk) # Token Bridging Guide Source: https://docs.world.org/world-chain/tokens/bridging ## 1. Deploy your token on World Chain Choose your preferred bridging framework and use it to deploy an ERC-20 for your token on World Chain. We recommend using the framework provided by World Chain's [standard bridge contracts](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/bridges.md), and deploying your token with the [OptimismMintableERC20Factory](/world-chain/developers/world-chain-contracts). This deployment method offers guarantees that will streamline the approval process. If you opt for a different bridging framework, it must be compatible with the standard bridge interface, or we may have difficulty supporting it. ## 2. Submit details for your token Follow the instructions in the [GitHub repository](https://github.com/ethereum-optimism/ethereum-optimism.github.io) and submit a PR with the required details for your token. You must specify a section for `worldchain-sepolia` and/or `worldchain` in your token's data.json file. The submission is straightforward if your token is already listed on the Superchain token list. ## 3. Await final approval The World team regularly reviews submissions, and you should receive a response within 24-72 hours, depending on whether the PR is submitted on a weekday, weekend, or holiday. # Superchain Token Bridging (Coming Soon) Source: https://docs.world.org/world-chain/tokens/superchain-token The SuperchainERC20 standard will become the default standard for issuing tokens on the Superchain once the OP Stack Interop features are live on mainnet. Currently the [Superchain token bridging standard](https://github.com/ethereum-optimism/specs/blob/main/specs/interop/token-bridging.md) documentation is a work in progress, so please stay tuned. The [Superchain token bridging standard](https://github.com/ethereum-optimism/specs/blob/main/specs/interop/token-bridging.md) (`SuperchainERC20`) is a set of properties and an interface designed to enable ERC20 tokens to be fungible across the Superchain using the official `SuperchainERC20Bridge`. This standard builds upon the existing [ERC20 token standard](https://docs.openzeppelin.com/contracts/4.x/erc20) and implements the `ICrosschainERC20` interface, which includes two key properties: only allowing the `SuperchainERC20Bridge` to call `crosschainMint` and `crosschainBurn` functions, and ensuring the token is deployed at the same address on every chain in the Superchain. The `SuperchainERC20Bridge` is a predeploy that works as an abstraction on top of the `L2ToL2CrossDomainMessenger` for token bridging. It includes two main functions: `sendERC20`, which initiates a cross-chain transfer by burning tokens locally and sending a message to the target chain, and `relayERC20`, which processes incoming messages and mints the corresponding amount of tokens on the destination chain. This bridge utilizes the `L2ToL2CrossDomainMessenger` for replay protection, domain binding, and access to additional message information. By implementing the `SuperchainERC20` standard, tokens can achieve fungibility across the Superchain while maintaining a trust-minimized bridging solution. The standard's design ensures liquidity availability, which is fundamental to achieving fungibility, and removes the need for cross-chain access control lists. Additionally, the standard allows for potential future enhancements, such as cross-chain transferFrom functionality and concatenated actions for more complex cross-chain operations. # USDC Quick Start Source: https://docs.world.org/world-chain/tokens/usdc USDC is a digital dollar issued by Circle, also known as a stablecoin, designed to represent US dollars on the internet and operate seamlessly across many of the world's leading blockchains, including World Chain. Backed 100% by highly liquid cash and cash-equivalent assets, USDC is always redeemable 1:1 for USD. Circle provides monthly attestation reports for USDC reserve holdings on its [Transparency page](https://www.circle.com/en/transparency). On World Chain, USDC can be transferred using its token contract, enabling fast, secure, and programmable digital dollar transactions. This guide will walk you through building a simple script to perform your first USDC transaction on World Chain. ## Prerequisites * Node.js (v16 or higher) * npm or yarn ## Part 1: Setup up your project ### Step 1: Create a new project directory: ```bash theme={"system"} mkdir usdc-world-transfer cd usdc-world-transfer npm init -y ``` ### Step 2: Enable ES module support: In your `package.json`, add the following line: ```json theme={"system"} { "type": "module" } ``` This allows you to use the modern ES module syntax (`import/export`) in your script. **Tip:** Alternatively, you can use the following command to set `"type": "module"` directly: ### Step 3: Install required dependencies: ```bash theme={"system"} npm install viem @inquirer/prompts ``` ### Step 4: Create your script file: Create a new file called `index.js`: ```bash theme={"system"} touch index.js ``` ## Part 2: Build the script Open `index.js` and complete the following sections: ### Step 1: Import Dependencies ```javascript theme={"system"} import { createWalletClient, http, formatEther, createPublicClient } from 'viem'; import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts'; import { worldchainSepolia } from 'viem/chains'; import { input } from '@inquirer/prompts'; import fs from 'fs'; import path from 'path'; ``` These imports include: * `viem` for blockchain interactions * `@inquirer/prompts` for interactive CLI prompts * `fs` and `path` for saving private key backups ### Step 2: Define Constants ```javascript theme={"system"} // USDC contract address on World Chain Sepolia const USDC_CONTRACT = '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88'; const USDC_DECIMALS = 6; ``` * `USDC_CONTRACT` is the address of the USDC token on World Chain Sepolia * `USDC_DECIMALS` is the number of decimals used in USDC calculations ```javascript theme={"system"} // USDC ABI (minimal for transfer) const USDC_ABI = [ { name: 'transfer', type: 'function', stateMutability: 'nonpayable', inputs: [ { name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' } ], outputs: [{ name: '', type: 'bool' }] }, { name: 'balanceOf', type: 'function', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ name: '', type: 'uint256' }] }, { name: 'decimals', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'uint8' }] } ]; ``` The above ABI defines the minimum required functions for: * Transferring USDC * Checking USDC balance * Getting USDC decimals ### Step 3: Add Helper Functions ```javascript theme={"system"} async function generateWallet() { const privateKey = generatePrivateKey(); const account = privateKeyToAccount(privateKey); return { privateKey, address: account.address }; } ``` ```javascript theme={"system"} async function savePrivateKeyBackup(sourceWallet, destinationWallet) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupDir = path.join(process.cwd(), 'backups'); if (!fs.existsSync(backupDir)) { fs.mkdirSync(backupDir); } const backupFile = path.join(backupDir, `wallet-backup-${timestamp}.txt`); const content = `Wallet Backup (${timestamp}) Source Wallet: Address: ${sourceWallet.address} Private Key: ${sourceWallet.privateKey} Destination Wallet: Address: ${destinationWallet.address} Private Key: ${destinationWallet.privateKey} ⚠️ IMPORTANT: Keep this file secure and delete it after use. `; fs.writeFileSync(backupFile, content); return backupFile; } ``` These functions: * Generate new wallets * Save them as JSON files for backup ### Step 4: Add Main Function ```javascript theme={"system"} async function main() { try { console.log('\n1. Creating source wallet...'); await input({ message: 'Press Enter to generate source wallet...' }); const sourceWallet = await generateWallet(); console.log('Source Address:', sourceWallet.address); await input({ message: 'Press Enter to continue...' }); console.log('\n2. Creating destination wallet...'); await input({ message: 'Press Enter to generate destination wallet...' }); const destinationWallet = await generateWallet(); console.log('Destination Address:', destinationWallet.address); await input({ message: 'Press Enter to continue...' }); console.log('\n3. Saving wallet information...'); const backupFile = await savePrivateKeyBackup(sourceWallet, destinationWallet); console.log('Backup saved to:', backupFile); console.log('\n4. Fund your source wallet:'); console.log('Get testnet ETH from: https://www.alchemy.com/faucets/world-chain-sepolia'); console.log('Get testnet USDC from: https://faucet.circle.com'); console.log('Source Wallet Address:', sourceWallet.address); await input({ message: 'Press Enter after funding your wallet...' }); console.log('\n5. Checking wallet balances...'); const publicClient = createPublicClient({ chain: worldchainSepolia, transport: http() }); let ethBalance, usdcBalance; let isFunded = false; while (!isFunded) { ethBalance = await publicClient.getBalance({ address: sourceWallet.address }); usdcBalance = await publicClient.readContract({ address: USDC_CONTRACT, abi: USDC_ABI, functionName: 'balanceOf', args: [sourceWallet.address] }); console.log('ETH Balance:', formatEther(ethBalance), 'ETH'); console.log('USDC Balance:', Number(usdcBalance) / 10 ** USDC_DECIMALS, 'USDC'); if (ethBalance === 0n || usdcBalance === 0n) { console.log('\nPlease fund your wallet with testnet ETH and USDC before proceeding.'); await input({ message: 'Press Enter after funding your wallet...' }); } else { isFunded = true; } } console.log('\n6. Ready to transfer USDC to Destination Address:', destinationWallet.address); const amount = await input({ message: 'Enter amount of USDC to transfer:', validate: (value) => { const num = Number(value); if (isNaN(num) || num <= 0) return 'Please enter a valid positive number'; if (num > Number(usdcBalance) / 10 ** USDC_DECIMALS) return 'Insufficient USDC balance'; return true; } }); const amountInDecimals = BigInt(Math.floor(Number(amount) * 10 ** USDC_DECIMALS)); const walletClient = createWalletClient({ account: privateKeyToAccount(sourceWallet.privateKey), chain: worldchainSepolia, transport: http() }); console.log('\nExecuting transfer...'); const hash = await walletClient.writeContract({ address: USDC_CONTRACT, abi: USDC_ABI, functionName: 'transfer', args: [destinationWallet.address, amountInDecimals] }); console.log('\nTransfer successful!'); console.log('Transaction Hash:', hash); console.log('View on Explorer:', `https://sepolia.worldscan.org/tx/${hash}`); } catch (error) { console.error('\nError:', error.message); } } main(); ``` This function drives the full flow to: * Create wallets * Back up credentials * Prompt you to fund the source wallet * Send USDC to the destination * Print the transaction hash ## Part 3: Run the Script Enter the following command: ```bash theme={"system"} node index.js ``` Follow the prompts in your terminal. ## What This Script Does 1. Creates source and destination wallets 2. Saves wallet information securely 3. Prompts you to fund the source wallet 4. Transfers USDC between wallets 5. Displays the transaction hash and link to the explorer ## Important Notes * Keep your private keys secure * Delete backup files after use * Always test with small amounts first * This script uses World Chain Sepolia testnet, not mainnet # World ID 4.0 Source: https://docs.world.org/world-id/4-0-migration Migration guide for moving your app to World ID 4.0 ## Start Here World ID 4.0 is available for new and existing integrations. New apps should start with IDKit 4.x; existing apps should choose the migration path that matches how they use World ID. * Register or upgrade your app in the [Developer Portal](https://developer.worldcoin.org). * Upgrade to IDKit 4.x (see below). * Choose a migration path below based on your app's behavior. ### Uniqueness vs Session Proofs (3.0 to 4.0) World ID 4.0 has two proof types: * **Uniqueness proofs** for one-time checks. * **Session proofs** for returning-user continuity (new in 4.0). In 3.0, many RPs treated nullifiers as persistent user identifiers. In 4.0, nullifiers are one-time-use, and `session_id` is the stable link across requests. | Proof type | World ID 3.0 | World ID 4.0 | What your backend should store | | -------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------ | | Uniqueness proof | Nullifier prevented proof reuse. | Nullifier still prevents proof reuse and is one-time-use. | Used nullifiers. | | Session proof (new in 4.0) | N/A. Nullifier were persistent. | `session_id` links the same user across requests. `session_nullifier` is per-proof replay protection. | `session_id` | Rule of thumb: use `nullifier` for one-time uniqueness and `session_id` for continuity. ## Upgrading to IDKit 4.x Adopting World ID 4.0 requires upgrading to IDKit 4.x, which introduces major breaking changes to support the new protocol. What changed: 1. **RP context is required**: Requests now require `rp_context` (`rp_id`, `nonce`, `created_at`, `expires_at`, `signature`). 2. **IDKit response changed**: No longer reshape the payload or compute `signal_hash` for the verify endpoint. 3. **[Backend verification endpoint](/api-reference/developer-portal/verify) changed**: Use `POST /api/v4/verify/{rp_id}`. 4. `@worldcoin/idkit-standalone` is discontinued. Use `@worldcoin/idkit-core` for vanilla JS/browser. For details and example code, see the [IDKit 4.0 integration guide](/world-id/idkit/integrate). See [Configure Credentials](/world-id/idkit/credentials) to choose a World ID 4.0 credential, or [Other legacy presets](/world-id/idkit/credentials#other-legacy-presets) to maintain an existing World ID 3.0 integration. ## Migration Path Choose a migration path based on how you previously used World ID in your application. ### One time actions These apps have a single long-running action. **Examples:** A stamp for every verified human in the world. A token given to every human in the world once. **Important:** `genesis_issued_at` is when the user originally got their credential (for example, went to an Orb), not when they upgraded their authenticator to v4. A user who was Orb-verified in 2023 and upgrades to v4 in 2025 still has `genesis_issued_at` from 2023. #### Migration Flow Diagram This diagram shows an app-controlled migration: preparation, a compatibility period, and the point when your app stops accepting World ID 3.0 proofs. ```mermaid theme={"system"} sequenceDiagram participant RP as Relying Party participant Portal as Developer Portal participant User as User/Authenticator Note over RP,User: Phase 1: Preparation (Backwards Compatible) RP->>RP: Upgrade SDKs, contracts, API calls RP->>Portal: Register for v4 Protocol RP->>Portal: Create v4 actions Note over RP,User: Phase 2: Compatibility period (TD = migration start date) rect rgb(240, 248, 255) Note right of RP: Temporary compatibility mode (allow_legacy_proofs: true) RP->>User: Request proof (genesis_issued_at=TD, allow_legacy_proofs: true) alt User has v4 credential issued AFTER TD User->>RP: v4 proof + nullifier else User migrated to v4, but credential issued BEFORE TD User->>RP: v3 proof + nullifier else User has v3 only (not migrated) User->>RP: v3 proof + nullifier end RP->>RP: Store nullifier end Note over RP,User: Phase 3: App cutover to v4 RP->>User: Request proof (allow_legacy_proofs: false, v4 only) alt User has v4 credential issued AFTER TD User->>RP: v4 proof + nullifier else User credential issued BEFORE TD or still on v3 User--xRP: Rejected end ``` **Summary:** During the compatibility period, the app accepts both v3 and v4 proofs. At the app's cutover, it begins accepting only v4 proofs. Before switching your app to v4-only, confirm that the users you support can produce the required v4 credentials. Choose the transition and cutover dates for your own rollout rather than relying on a fixed global schedule. #### Step-by-step Migration Details 1. **Update SDKs and Contracts:** Upgrade SDKs, contracts, and API calls to enable baseline support for the upgraded protocol. This is backwards compatible. 2. **Register in Developer Portal:** Generate your new RP registration and relevant actions for the v4 protocol in the Developer Portal. 3. **For long-running actions:** * Decide a transition date (`TD`) to start accepting v4 proofs. Specify a minimum `genesis_issued_at = TD` timestamp in the IDKit request with `allow_legacy_proofs: true` as a temporary compatibility mode. Only users who get their Orb credential (or document credentials) from this point forward can generate v4 proofs. Users who have not upgraded their World ID can still issue v3 proofs during this window. Track both nullifiers. * At a future cut-off date (`CD > TD`), switch the IDKit request to `allow_legacy_proofs: false` to stop accepting v3 proofs and accept only v4 proofs. 4. **For limited-time actions** (for example, recurring grant drops): Make the transition at the action level. Short-running actions have a simpler migration path. #### Example Code **Old Contract - Disable minting here:** ```jsx Mint.sol theme={"system"} mapping(uint256 => bool) internal oldNullifierHashes; mapping(address => bool) public oldHasMinted; function mint(){ // Existing logic for checking World ID uniqueness if (hasMinted[msg.sender]) revert AlreadyMinted(); if (nullifierHashes[nullifierHash]) revert DuplicateNullifier(nullifierHash); } ``` **New Contract - Check both old and new nullifiers:** ```ts Mintv4.sol theme={"system"} mapping(uint256 => bool) internal nullifierHashes; mapping(address => bool) public hasMinted; // This function is used to verify a 4.0 proof function mint({..., nullifier}){ // Check old contracts and new mapping if (OldContract.hasMinted[msg.sender] || hasMinted[msg.sender]) revert AlreadyMinted(); if (OldContract.oldNullifierHashes[nullifier] || nullifierHashes[nullifier]) revert DuplicateNullifier(nullifier); // Verify 4.0 Proof Verifier.verify(...) } // Needed to support v3 proofs during migration function mintLegacy({..., nullifierHash}){ // Check old contracts and new ones if (OldContract.hasMinted[msg.sender] || hasMinted[msg.sender]) revert AlreadyMinted(); if (OldContract.oldNullifierHashes[nullifierHash] || nullifierHashes[nullifierHash]) revert DuplicateNullifier(nullifierHash); // Verify Legacy Proof WorldIDRouter.verify(...) } ``` ### Short Term Recurring Actions These apps create multiple one-time actions. These actions are short lived. **Example:** A daily voting app where each vote requires a fresh proof of unique human. **Migration approach:** Migrate your SDK and Developer Portal account. Pick a new future action to start accepting only v4 proofs. #### Migration Flow Diagram This diagram shows a simpler two-step migration with an app-controlled cutover. ```mermaid theme={"system"} sequenceDiagram participant RP as Relying Party participant Portal as Developer Portal participant User as User/Authenticator Note over RP,User: Phase 1: Preparation (Backwards Compatible) RP->>RP: Upgrade SDKs, contracts, API calls RP->>Portal: Register for v4 Protocol RP->>Portal: Create v4 actions Note over RP,User: Phase 2: App cutover (TD) rect rgb(255, 240, 245) Note right of RP: Hard Migration (allow_legacy_proofs: false) RP->>User: Request proof (genesis_issued_at=TD, allow_legacy_proofs: false) alt User has v4 credential issued AFTER TD User->>RP: v4 proof + nullifier else User migrated but credential issued BEFORE TD User--xRP: Rejected (credential too old) else User has v3 only User--xRP: Rejected (must upgrade) end end ``` **Summary:** At the app's cutover, new actions accept only v4 proofs. ### Recurring Verifications and New Credential Checks For apps that rely on unlimited verifications of the same action. **Examples:** Partners that check for users who've added new credentials. Apps that allow users to verify before each claim using the same action (note this is an anti-pattern of World ID). **Migration approach:** Migrate to [Session Proofs](https://github.com/worldcoin/world-id-protocol/blob/main/docs/world-id-4-specs/README.md#session-proofs), which let you verify credentials over a period of time while ensuring it's the same user. The session ID returned in the proof becomes the long-lived stable identifier instead. #### Migration Flow Diagram This diagram shows how Session Proofs provide a stable identifier across multiple verifications. ```mermaid theme={"system"} sequenceDiagram participant RP as Relying Party participant Portal as Developer Portal participant User as User/Authenticator Note over RP,User: Phase 1: Setup RP->>Portal: Register app for v4 Protocol RP->>RP: Update to IDKit.Session Note over RP,User: Phase 2: Initial Session Enrollment rect rgb(240, 248, 255) RP->>User: IDKit.Session request User->>RP: proof + sessionId RP->>RP: Store sessionId (replaces nullifier as stable identifier) end Note over RP,User: Phase 3: Subsequent Interactions rect rgb(240, 255, 240) RP->>User: Session proof request (sessionId) User->>RP: proof + sessionNullifier + sessionId RP->>RP: Verify sessionId matches stored value end Note over RP,User: Example: Credential Upgrade rect rgb(255, 250, 240) RP->>User: Session proof request (sessionId, credential=Document) User->>RP: proof + sessionNullifier + sessionId RP->>RP: If the sessionID doesn't match the proof will fail end ``` **Summary:** Session IDs provide continuity across verifications, replacing nullifiers as the stable identifier. ```jsx Creating a session theme={"system"} export async function createSession() { const rpContext = await fetch("/api/worldid/rp-context").then((r) => r.json()); const request = await IDKit.createSession({ app_id: APP_ID, rp_context: rpContext, }).constraints(any(CredentialRequest("proof_of_human"))); // Web only: render this QR URL const qrUrl = request.connectorURI; const completion = await request.pollUntilCompletion({ timeout: 120000 }); if (!completion.success) throw new Error(completion.error); const verify = await fetch("/api/worldid/verify", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(completion.result), }).then((r) => r.json()); if (!verify.success) throw new Error("Verification failed"); // IMPORTANT: Save this in order to prove future sessions for the same user return completion.result.session_id; } ``` ```tsx Proving a session theme={"system"} // sessionId should have been saved when you created the session export async function proveSession(sessionId) { const rpContext = await fetch("/api/worldid/rp-context").then((r) => r.json()); const request = await IDKit.proveSession(sessionId, { app_id: APP_ID, rp_context: rpContext, }).constraints(any(CredentialRequest("proof_of_human"))); const completion = await request.pollUntilCompletion({ timeout: 120000 }); if (!completion.success) throw new Error(completion.error); const verify = await fetch("/api/worldid/verify", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(completion.result), }).then((r) => r.json()); if (!verify.success) throw new Error("Verification failed"); return verify; } ``` ### New Apps New apps should start with IDKit 4.x and do not need a protocol migration path. ## Further Migration Details * Recovery applies to users in the v4 protocol. Users with pre-v4 credentials may receive a new credential based on issuer policy, while `genesis_issued_at` still reflects original issuance date. # SKILL Source: https://docs.world.org/world-id/SKILL Use this skill when adding, upgrading, debugging, or testing World ID verification with IDKit in a new or existing web or mobile app. Covers Proof of Human, passport/document, Selfie Check (Beta), and session/sign-in flows; Developer Portal app, RP, and action setup; server-side signing and proof verification; environment matching; nullifier replay protection; and launch testing. Trigger when the user asks to add World ID, verify humans, stop bots or multi-accounting, add Sybil resistance, or mentions IDKit, Orb, World ID proof flows, World App proof flows, @worldcoin/idkit, signing keys, rp_id, or app_id. # Add World ID to your app > **For developers:** give this file to your coding agent with a one-line ask like "help me add World ID to my app." The agent will figure out the rest. **What is World ID?** A privacy-preserving way to prove a user is a unique real human. Use it to gate signups, kill bots, prevent multi-accounting, run one-person-one-vote, or distribute scarce rewards fairly. Zero PII shared, zero-knowledge proofs on the wire. The full integration guide with code for every language lives at [https://docs.world.org/world-id/idkit/integrate](https://docs.world.org/world-id/idkit/integrate). **This file is the meta-guide that gets you to the right code without missteps.** *** # Agent: read these instructions before doing anything Take the user from "I want World ID" to a verified working flow. Inspect first, ask only for missing information, and resume from the user's actual state rather than restarting setup. ## Phase 0 — Establish readiness Before changing code or creating Portal resources: 1. Read the project and inspect connected tools. Do not ask for information you can infer. 2. Establish the current state: * integration goal and credential preset * new or existing project; client, backend, package manager, and persistence layer * Developer Portal account/team, `app_id`, `rp_id`, and action * target environment and test path: staging simulator, production World ID, or both * Developer Portal MCP connection * whether an RP signing key already exists in a server-side secret store * requested credential policy, feature access (especially Selfie Check (Beta)), and whether legacy proof fallback is needed 3. Report a short readiness summary and ask only for unresolved blockers. **Never ask the user to paste a signing key, Portal API key, or other secret into chat.** Ask only whether it exists and where the application expects it. 4. Build a TODO from the missing steps. Preserve working configuration and existing Portal resources unless the user explicitly wants replacements. Do not generate or rotate an RP signing key until a server-only secret destination is ready. Rotation invalidates the old signer: explain the impact and get explicit confirmation first. ## Phase 1 — Use the Developer Portal MCP when available The **World ID Developer Portal MCP** turns the app-creation lifecycle into MCP tools. It replaces dashboard setup with a few tool calls and makes the one-time `signing_key` response explicit so the agent can persist it immediately. * Source & full tool reference: [https://github.com/worldcoin/developer-portal/tree/main/web/api/mcp](https://github.com/worldcoin/developer-portal/tree/main/web/api/mcp) * Endpoint: `https://developer.world.org/api/mcp` (transport: streamable-http) * Auth: `Authorization: Bearer api_` (Developer Portal team API key) Prefer the MCP over the dashboard when connected. Start with `get_team_context`, then `get_app_config` for the selected app before creating anything. Clients may namespace MCP tools differently; match these final tool names: | Dashboard action | MCP tool | | ------------------------------------------------------- | ---------------------------------- | | List my team's apps | `get_team_context` | | App details / config snapshot | `get_app_config` | | Create a new app | `create_app` | | Configure World ID (mint RP, get signing key) | `configure_world_id` | | Create or update an action | `create_world_id_action` | | Check on-chain registration status | `get_world_id_registration_status` | | Read the current signer address (never the private key) | `get_world_id_signing_key` | | Rotate signing key (returns the new private key once) | `rotate_world_id_signing_key` | If the MCP is not connected, explain that it can inspect and configure the user's Portal resources directly. Offer the client-specific setup at [https://docs.world.org/model-context-protocol/developer-portal](https://docs.world.org/model-context-protocol/developer-portal), or let the user continue through the dashboard. **Do not silently choose a path or block an otherwise valid integration on MCP setup.** ## Phase 2 — Understand the project before writing anything Two paths land here. Identify which: * **Path 1 — Building from scratch** (demo, hackathon, new product). You control the full stack. Default to **Next.js (App Router) + TypeScript** as the golden path — every official sample uses it and it's the fastest way to a working flow. * **Path 2 — Integrating into an existing stack.** Read the codebase first. Confirm the **frontend** (web / mobile) and the **backend** (where secrets live), then pick the right SDK pair below. Supported clients and backends — all interoperable: | Client | SDK | | ---------------------- | ------------------------------------------------------------------- | | React / Next.js | `@worldcoin/idkit` (pre-built widget) | | Other JS / vanilla web | `@worldcoin/idkit-core` | | iOS | [`worldcoin/idkit-swift`](https://github.com/worldcoin/idkit-swift) | | Android | `com.worldcoin:idkit` (Gradle) | | Backend | RP signing | Proof verify | | -------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------ | | Node / Next.js | `@worldcoin/idkit-core/signing` | `fetch` to `developer.world.org/api/v4/verify/{rp_id}` | | Go | server SDK in `/world-id/idkit/go` | same HTTPS POST | | Anything else | re-implement signing ([signatures spec](https://docs.world.org/world-id/idkit/signatures)) | same HTTPS POST | **DO NOT pin SDKs to `^2.x` or `^3.x`** — those examples litter the public internet and **will not work** with v4. The API was redesigned. Pin **`^4.x`** and verify with `npm view @worldcoin/idkit version` if uncertain. ## Phase 3 — Pick the credential and confirm access The credential decides what the user proves. Nail this down before scaffolding — switching later means a new action. | Preset | What it proves | Use it for | | ---------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | **`proofOfHuman`** — Proof of Human (flagship) | The user is a unique person, biometrically verified at an Orb | Sybil resistance, airdrops, one-vote-per-human, gated signups. **The default if the user said "proof of human" or "verify a real human."** | | **`passport`** — Passport | The user holds a valid government passport (NFC-verified) | Higher-assurance flows where you need document-grade identity (regulated apps, age-gating, KYC-adjacent). | | **`selfieCheckLegacy`** — Selfie Check (Beta) | A liveness selfie signal backed by a World ID 3.0 proof | Lower-assurance "is a human in front of the camera" — friction/bot deterrence without the full Orb requirement. | **DO NOT default to `proofOfHuman` if the user said "passport" or "verify their ID"** — that's `passport`. **DO NOT default to `proofOfHuman` if the user said "selfie" or "liveness"** — that's `selfieCheckLegacy`. When in doubt, ask one question. Other legacy presets exist (`documentLegacy`, `deviceLegacy`); reach for them only when the user asks specifically. For sign-in / session reuse across visits, use the v4 **session** flow instead of a uniqueness preset (see the integrate doc). ### Selfie Check (Beta) access Before implementing or testing Selfie Check, confirm that its feature flag is enabled for the target app. If it is not enabled, stop and tell the user to request access through their World contact or the documented support path. A valid app or action does not imply Selfie Check access. ## Phase 4 — Implement the 6 integration steps and explain the WHY The full code for each step is at [https://docs.world.org/world-id/idkit/integrate](https://docs.world.org/world-id/idkit/integrate). Don't reproduce it; link to it and adapt to the user's framework. The agent owns making sure each step is done **and understood**. **Copy this checklist into your TODO and update it as you go.** Don't move on with an unchecked step. * [ ] Step 1 — Install IDKit * [ ] Step 2 — Create or reuse app + RP + action (store any newly generated signing key immediately) * [ ] Step 3 — Sign RP request in backend * [ ] Step 4 — Open IDKit widget on client * [ ] Step 5 — Verify proof in backend * [ ] Step 6 — Store nullifier with UNIQUE constraint 1. **Install IDKit** — `^4.x`, the right package for the platform (table in Phase 2). 2. **Create or reuse the Portal resources.** Use the MCP when available. Reuse an existing app, RP, and action when they match the requested integration. For a new RP, capture `app_id`, `rp_id`, and `signing_key.private_key` from `configure_world_id`, create the action in the intended environment, and write the signing key to the prepared server-only secret store in the same step. The portal returns it exactly once. **Do not print, log, or return the private key to chat.** If the key is lost, explain that `get_world_id_signing_key` cannot recover it; rotation creates a new key and invalidates the old signer. 3. **Generate the RP signature in your backend.** *Why backend?* The signing key authenticates your app to the protocol. Leaking it lets anyone impersonate your app and forge proof requests. **CRITICAL: never sign on the client. Never expose `RP_SIGNING_KEY` as a `NEXT_PUBLIC_*` var. Never log it.** 4. **Open the IDKit widget on the client** with the signature your backend returned. The widget hands off to World ID, which produces a zero-knowledge proof. 5. **Verify the proof in your backend** by POSTing it **as-is** to `https://developer.world.org/api/v4/verify/{rp_id}`. *Why backend?* A client can return any JSON it wants. Only the World verifier — called from a trusted server — confirms the proof is real and tied to a unique credential. Verifying client-side defeats the entire point. **DO NOT mutate, re-encode, or trim the proof JSON before forwarding** — pass exactly what IDKit returned. For Selfie Check, IDKit returns `responses[].identifier: "selfie"`. Do not turn it into a hand-built `verification_level`; `face` is only a backward-compatible alias for legacy integrations. 6. **Store the nullifier.** Every successful proof returns a `nullifier` — an RP-scoped, action-scoped, non-reversible identifier for that user. *Why store it?* Without uniqueness storage, a user can verify the same proof twice and double-claim a reward, vote, etc. Persist `(action, nullifier)` with a `UNIQUE` constraint and reject duplicates on insert. Column type: **`NUMERIC(78, 0)`** (256-bit field elements). The nullifier reveals nothing about the user — safe to store, but it's the *only* anti-replay mechanism, so it's required. ## Phase 5 — Match environments end-to-end * The **production** World ID app only signs **production** proofs. * A **staging** action only verifies against the World ID **Simulator** ([https://simulator.worldcoin.org](https://simulator.worldcoin.org)). * The IDKit `environment` prop, the action's `environment`, and the simulator-vs-real-app choice **must all match.** **CRITICAL: if real users will scan with their phones, the action environment must be `production`.** A staging action with the production World ID app will silently produce zero proofs and look like a frontend bug. If the user needs both simulator testing and real-device QA, create separate staging and production actions. ## Phase 6 — Test the integration end-to-end Do not declare the integration complete from compilation or Portal configuration alone. Test the selected path and record evidence: * [ ] RP-signing endpoint succeeds without exposing or logging secrets. * [ ] Widget/request opens in the selected environment. * [ ] The selected credential completes with the staging simulator or production World ID as intended. * [ ] Backend verification succeeds and the exact IDKit result reaches `/api/v4/verify/{rp_id}`. * [ ] The verified nullifier is persisted. * [ ] Replaying the same nullifier is rejected by the database uniqueness constraint. * [ ] Relevant failures—unavailable Selfie Check, invalid action/signature, or environment mismatch—produce an actionable user-facing error instead of an indefinite loading state. * [ ] JS/React failures retain the `debugReport` and `request_id` needed for diagnosis without logging secrets. Run automated tests for the routes and persistence behavior. Clearly identify simulator, phone, or production checks that still require the user; never imply a manual proof flow ran when it did not. If a check fails, use the concrete error, response payload, or execution trace to fix the root cause, then rerun the failed check and any downstream checks. Do not weaken a security or access gate just to make validation pass. ## Phase 7 — Gotchas and recovery Surface these proactively when you see the matching symptom — don't make the user search for the cause. | Symptom | Cause | Recovery | | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | World ID shows "action not found" or QR scan does nothing | Action wasn't created in the environment IDKit is pointing at | Create the missing action with `create_world_id_action` (`environment: "production"` for real devices, `"staging"` for simulator). Confirm `NEXT_PUBLIC_WLD_ENVIRONMENT` matches. | | `/api/v4/verify/{rp_id}` returns `invalid_proof` or `verification_failed` | Often staging/production env mismatch, or proof was mutated before forward | Re-check Phase 5. Forward the proof JSON byte-for-byte without re-encoding fields. | | Verification fails in JS/React and the error code alone isn't enough | Need transport/payload diagnostics | Read `getDebugReport()` (or the `onError` `debugReport` arg) — it carries `transport`, `request_id`, request/response payloads, and World App `mini_app` channel info. JS SDKs only. | | `/api/v4/verify/{rp_id}` returns `not_registered` or 4xx with `rp` errors | On-chain registration is still `pending` | Poll `get_world_id_registration_status` until the intended environment is `registered`. Production must be registered before launch. | | Signing key is gone (lost `.env`, never persisted) | `signing_key.private_key` is returned exactly once at create/rotate time | Prepare the replacement secret destination, explain the invalidation impact, get confirmation, then call `rotate_world_id_signing_key`, persist the new key immediately, and redeploy. | | Duplicate-verification (user submits the same proof twice) | Expected — that's what nullifiers prevent | Reject on the unique-constraint violation. **Do not** "helpfully" upsert. | | TypeScript: `Property 'allow_legacy_proofs' is missing` | v4 requires this prop on `IDKitRequestWidget` | Add `allow_legacy_proofs={true}` for `proofOfHuman`, `orbLegacy`, and other legacy/fallback presets. | | TypeScript: import errors for `IRpContext` / `ISuccessResult` | v3 type names — removed in v4 | Use `RpContext` / `IDKitResult`. No `I` prefix. | | Widget never opens | Treating `IDKitRequestWidget` as a render-prop / function-as-child component | v4 widget is **controlled** — pass `open` and `onOpenChange` props. There is no child function. | | `npm install` fails: `No matching version found for @worldcoin/idkit@^2.x` (or 3.x) | Stale code sample | Pin `^4.x`. Run `npm view @worldcoin/idkit version` to confirm the latest. | | Existing app rejects World ID config | App was created as `mini-app` (or wrong `engine`) | `app_mode` is fixed at create time. Create a **new** app with the right mode (`external` for IDKit, `mini-app` for MiniKit). | ## Phase 8 — Hand off with evidence Before declaring done, report: * the readiness state you found and which path you followed * files and routes changed * `app_id`, `rp_id`, action, and environments configured—never secret values * commands and tests run, with results * simulator, phone, or production checks still outstanding * blockers and exact recovery steps For launch readiness, also confirm: * [ ] `RP_SIGNING_KEY` is server-only, in a real secret store, never logged. * [ ] Action exists in `production` (not just `staging`). * [ ] Nullifier persistence is real — DB-backed, `NUMERIC(78, 0)`, `UNIQUE (action, nullifier)`. The in-memory `Set` in samples is illustrative only. * [ ] On-chain registration polled to `registered` (`get_world_id_registration_status`) before launch. * [ ] The user knows that a leaked or lost signing key requires confirmed rotation and redeployment. ## Reference * Full integration guide (code for every language): [https://docs.world.org/world-id/idkit/integrate](https://docs.world.org/world-id/idkit/integrate) * Core concepts: [https://docs.world.org/world-id/concepts](https://docs.world.org/world-id/concepts) * Credentials reference: [https://docs.world.org/world-id/credentials](https://docs.world.org/world-id/credentials) * RP signature spec (for non-Node backends): [https://docs.world.org/world-id/idkit/signatures](https://docs.world.org/world-id/idkit/signatures) * Error codes: [https://docs.world.org/world-id/idkit/error-codes](https://docs.world.org/world-id/idkit/error-codes) * Developer Portal MCP: [https://github.com/worldcoin/developer-portal/tree/main/web/api/mcp](https://github.com/worldcoin/developer-portal/tree/main/web/api/mcp) * Developer Portal: [https://developer.world.org](https://developer.world.org) * World ID Simulator (staging only): [https://simulator.worldcoin.org](https://simulator.worldcoin.org) *** # TL;DR **Developer:** give this file to a coding agent with "Help me add World ID." **Agent:** establish readiness → inspect or configure Portal resources → understand the stack → pick a credential and confirm access → implement → match environments → test end-to-end → hand off with evidence. # Core Concepts Source: https://docs.world.org/world-id/concepts Minimal concepts and vocabulary for integrating World ID. World ID is designed for straightforward integration. This page covers only the core concepts most teams need before shipping. ## World ID A user's self-custodial identity which lives in their Authenticator. When referring to the protocol, it's usually written as the *World ID Protocol*. ## Relying Party (RP) A third-party application that wants to verify a user's credential. This is probably you. ## Credential A signed attestation about a subject used to generate proofs. It includes issuer, subject, validity window, and claim commitments as defined in the [Credential's World ID 4.0 specs](https://docs.rs/world-id-primitives/latest/world_id_primitives/credential/struct.Credential.html) ## Issuer An entity that issues credentials to users. In World ID, issuers are responsible for verifying a user's identity and signing their credential. Issuers must be registered in the `CredentialSchemaIssuerRegistry` to be recognized by the protocol. ## Proof Concepts * **Action**: A developer-facing primitive that lets you put any app operation behind a unique-human gate. An app can have one or more actions depending on your use case. * **Zero-Knowledge Proof (ZKP)**: A cryptographic method to prove that a statement is true without revealing any information about the statement itself. World ID uses ZKPs to prove that a user is verified without revealing the user's identity. * **Nullifier**: A component of the World ID ZKP; a unique identifier for a combination of a user, `app_id`, and `action`. * **Signal**: A component of the World ID ZKP; data attached to the proof that cannot be tampered with. An example may be a user's choice for an election. # Legacy Concepts (World ID 3.0) * **Identity Commitment**: A hash of a user's secret identity nullifier and trapdoor, which is inserted into the Merkle Tree. This is no longer used in World ID 4.0, but you may see it referenced in older documentation. * **App ID**: The ID of your app that is assigned in our [Developer Portal](https://developer.worldcoin.org/). In World ID 4.0, this is now referred to as **RP ID**. * **Merkle Root**: A component of the World ID ZKP; The root of the [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree) that identity commitments are inserted to. While 4.0 still uses Merkle Trees, the root is now encoded within the received ZKP, so you don't need to worry about handling it. # Proof of Human (PoH) Source: https://docs.world.org/world-id/credentials/1 High assurance biometric credential captured by the Orb and used for uniqueness. ## Introduction The Proof of Human (PoH) Credential is the highest-assurance credential issued under World ID. Users obtain it by verifying at an Orb, a device that captures iris biometrics by taking images of the iris. The credential is an anonymous proof that the user is a unique, live human. ## Use Cases Use the PoH Credential when you need a strong guarantee that you are interacting with a unique, live human. It is the right choice for: * **Sybil resistance**: enforcing one account per human for airdrops, voting, rewards or rate limiting. * **High-assurance authentication**: gating sensitive actions where you need biometric confidence that you're interacting with the correct, unique human. * **Proof of personhood**: distinguishing humans from bots, agents and synthetic identities. ## Credential Structure This credential implements the following attributes beyond the defaults in the [Credential](https://docs.rs/world-id-primitives/latest/world_id_primitives/credential/struct.Credential.html).
Attribute Description
genesis\_issued\_at The Unix timestamp of the user's first PoH credential. Remains constant across credential renewals.
expires\_at Unix timestamp when the credential expires. This is currently three years after the most recent issuance.
associated\_data\_hash The PoH credential has no associated data, so this field is always FieldElement::ZERO.
In addition, the credential implements the following claim: ### Claim 0 - Orb Credential Commitment A commitment that binds the PoH credential to the Orb credential the user presented at issuance. This allows the PoH Issuer to attest that the credential was minted from a valid Orb verification without revealing the underlying Orb credential to relying parties. | Source | Value | | -------------- | ---------------------------------------------------------------- | | Orb credential | `H(hashes.json)` — the hash signed by the Orb during enrollment. | ## Credential Renewal Users can renew their PoH credential to extend its validity as long as the Orb capture is deemed fresh by the issuer. Each renewal updates the credential with `expires_at = now + 3 years` while `genesis_issued_at` remains constant. ## Technical Reference For issuer endpoints and implementation-specific details, see the [PoH Issuer reference](/world-id/reference/poh-issuer). # Selfie Check (Beta) Source: https://docs.world.org/world-id/credentials/11 A medium-assurance biometric credential using the device camera for liveness and facial similarity. ## Introduction Selfie Check (Beta) uses the user's mobile device camera for liveness and facial similarity checks. It adds friction against automated and repeated account creation without requiring a Proof of Human. Unlike high-assurance Orb verification, Selfie Check does not provide a strict one-person-one-account guarantee and is considered a medium-assurance verification method. It returns a proof of the completed check, not a numeric Sybil or uniqueness score. Use Selfie Check (Beta) for: * **Liveness detection:** Confirm the user is a real person, not a spoof or injection attack. * **Abuse resistance:** Add friction to automated and repeated account creation. * **Continuity:** Confirm a returning user is the same person who originally enrolled. Selfie Check has a 90-day inactivity window. After 90 days without use, the user completes the camera flow again before returning another proof. Selfie Check (Beta) is access-gated. To use it, [request access](mailto:developers@toolsforhumanity.com) so the feature flag can be enabled for your app. Once enabled, anyone with World ID App can use Selfie Check. No Orb, passport or other prerequisite credential is required. ## How it works Use IDKit to integrate Selfie Check into your application. * **On mobile (iOS/Android):** Use IDKit to generate a deep link and attach it to a "Verify" CTA. When the user taps it, they are redirected to World ID App to complete Selfie Check. * **On desktop:** Use IDKit to generate a QR code and display it to the user. When the user scans it with their mobile device camera, World ID App launches and guides them through Selfie Check. ## User Experience Flow 1. **Challenge:** The user initiates the flow on your app (Relying Party). 2. **Hand-off:** The user is redirected to World ID App. If they don't have World ID App installed, they are guided to download it and go straight into the Selfie Check experience. 3. **Enrollment/Auth:** * **New User:** Enrolls with a selfie and liveness check. * **Returning User:** Completes a short camera check to verify continuity. 4. **Success:** The user returns to your application with a verified credential. ## Next steps See [Integrate IDKit](/world-id/idkit/integrate) for the complete integration flow. Testing your integration? See [Testing Selfie Check in Sandbox](/world-id/sandbox/testing-selfie-check) for coverage, critical user journeys, and known limitations. # NFC Credential Source: https://docs.world.org/world-id/credentials/9303 A unique government-issued document, such as a passport or eID. ## Introduction The NFC Credential represents a unique government-issued document. It supports passports and eIDs. Availability varies by country and continues to expand over time. An NFC Credential is **guaranteed to be issued to a single World ID per unique document**. In addition to ICAO-9303 compliant documents (such as passports or eIDs), the Japanese [My Number Card](https://en.wikipedia.org/wiki/My_Number_Card) (MNC) is also supported. The MNC flow uses different enrollment handling internally, but it issues the same credential. ## Use Cases Use the NFC Credential when you need proof of a unique government document. This is useful for applications that need document-level Sybil resistance without requiring a proof of a unique human. ## Credential Structure This credential implements the following attributes beyond the defaults in the [Credential](https://docs.rs/world-id-primitives/latest/world_id_primitives/credential/struct.Credential.html).
Attribute Description
genesis\_issued\_at The timestamp of when the unique document was first verified.
expires\_at The expiration of the document with a maximum of 10 years.
associated\_data\_commitment A commitment to the user's Associated Data (see NFC Issuer implementation notes ).
In addition, the credential implements the following claims: ### Claim 0 - Authentication Claim Identifies the type of authentication performed when enrolling a document. This helps determine the state of the document at enrollment time. For example, documents that only undergo Passive Authentication have no guarantee that the data isn't cloned from an original document. Please note that not all authentications are supported for all documents, and it usually varies per country. The strongest authentication available is always selected. | Value | Claim | Description | | ----- | --------------------- | -------------------------------------------------------------------------------------------------- | | `1` | None | Passive authentication only (document signature verification). | | `2` | Chip Authentication | Document passed Chip Authentication (CA) per ICAO 9303. Proves the chip is genuine and not cloned. | | `3` | Active Authentication | Document passed Active Authentication (AA) per ICAO 9303. Proves the chip holds a private key. | | `4` | MNC Authentication | Document was verified via the MNC (My Number Card) SD-JWT flow. | ### Claim 1 - SOD Signature Contains a hash of the document's signature from the issuing authority. For passports and other ICAO-9303 compliant documents, the signature is retrieved from `SignedData.SignerInfos[0].Signature` in the `EF.SOD` (Security Object Document) (see [Section 4.6.2.1 from ICAO-9303 Part 10](https://www.icao.int/sites/default/files/publications/DocSeries/9303_p10_cons_en.pdf)). The raw signature bytes are then hashed with blake3 and converted to a field element with modulo reduction. Please note that this claim is not set for credentials from My Number Cards. ## Credential Renewal Renewal is not supported for this credential. A document can only be enrolled once. From a user standpoint, they will generally obtain a new document from their issuing authority (e.g. a new passport) and register it as a new credential. ## Technical Reference For issuer endpoints, migration payloads, and implementation-specific details, see the [NFC Issuer reference](/world-id/reference/nfc-issuer). # IDKit Standalone Source: https://docs.world.org/world-id/from-idkit-standalone Migrate from @worldcoin/idkit-standalone to @worldcoin/idkit-core. The `@worldcoin/idkit-standalone` package is discontinued. Migrate any vanilla JavaScript or custom QR flow to `@worldcoin/idkit-core`. ## Migration Checklist 1. Enable World ID 4.0 in the [Developer Portal](https://developer.world.org) and keep your `app_id`, `rp_id`, and server-only `signing_key`. 2. Install `@worldcoin/idkit-core`. 3. Add a backend endpoint that generates an RP signature. See [RP Signatures](/world-id/idkit/signatures). 4. Pick a legacy preset that matches your previous `verification_level` configuration. 5. Replace `verifyCloudProof(...)` with a backend POST to `https://developer.world.org/api/v4/verify/{rp_id}`. 6. Store the verified `nullifier` for replay protection. For the broader protocol migration, including proof types and timelines, see [World ID 4.0](/world-id/4-0-migration). ## Install ```bash npm theme={"system"} npm i @worldcoin/idkit-core ``` ```bash pnpm theme={"system"} pnpm add @worldcoin/idkit-core ``` ```bash yarn theme={"system"} yarn add @worldcoin/idkit-core ``` ## Request a Proof Standalone mounted UI through browser globals: ```ts title="Before" theme={"system"} import "@worldcoin/idkit-standalone"; IDKit.init({ app_id: "app_xxxxx", action: "my-action", signal: "user-123", verification_level: "orb", }); await IDKit.open(); ``` With `idkit-core`, fetch an RP signature from your backend, build the request, render the `connectorURI`, and poll until World ID returns a proof. ```ts title="After" theme={"system"} import { IDKit, orbLegacy } from "@worldcoin/idkit-core"; const action = "my-action"; const signal = "user-123"; const rpContext = await fetch("/api/rp-signature", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action }), }).then((r) => r.json()); const request = await IDKit.request({ app_id: "app_xxxxx", action, rp_context: { rp_id: "rp_xxxxx", nonce: rpContext.nonce, created_at: rpContext.created_at, expires_at: rpContext.expires_at, signature: rpContext.sig, }, allow_legacy_proofs: true, environment: "production", }).preset(orbLegacy({ signal })); renderQrCode(request.connectorURI); const completion = await request.pollUntilCompletion({ pollInterval: 2_000, timeout: 120_000, }); if (!completion.success) { throw new Error(`World ID verification failed: ${completion.error}`); } await fetch("/api/verify-proof", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ idkitResponse: completion.result }), }); ``` ## Credential Mapping Standalone used `verification_level` to choose what the user proves. In `idkit-core`, use a legacy preset instead. These presets are drop-in replacements for the old verification levels. Legacy presets return the maximum credential a user has. For example, if a user has an Orb credential but you request `documentLegacy`, they verify with their Orb credential. | Standalone | `idkit-core` preset | | --------------------------------------- | ---------------------------------- | | `verification_level: "orb"` | `orbLegacy({ signal })` | | `verification_level: "secure_document"` | `secureDocumentLegacy({ signal })` | | `verification_level: "document"` | `documentLegacy({ signal })` | | `verification_level: "device"` | `deviceLegacy({ signal })` | ```ts theme={"system"} import { IDKit, deviceLegacy, documentLegacy, orbLegacy, secureDocumentLegacy, } from "@worldcoin/idkit-core"; const request = await IDKit.request(config).preset( orbLegacy({ signal: "user-123" }), ); ``` ## RP Signatures IDKit 4.x requests require `rp_context`, signed by your backend with the Developer Portal `signing_key`. Never generate signatures in client code. For implementation details, see the [RP Signatures](/world-id/idkit/signatures) reference. It includes the JavaScript helper, the signing algorithm, and test vectors for non-JavaScript backends. ## Verify the Proof Standalone integrations typically verified with `verifyCloudProof(...)`: ```ts title="Before" theme={"system"} import { verifyCloudProof } from "@worldcoin/idkit"; const response = await verifyCloudProof(proof, app_id, action, signal); ``` In IDKit 4.x, send the IDKit result to your backend and forward it directly to the [v4 verify endpoint](/api-reference/developer-portal/verify). ```ts title="After" theme={"system"} import type { IDKitResult } from "@worldcoin/idkit-core"; export async function POST(request: Request): Promise { const { idkitResponse } = (await request.json()) as { idkitResponse: IDKitResult; }; const response = await fetch( `https://developer.world.org/api/v4/verify/${process.env.WORLD_ID_RP_ID}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(idkitResponse), }, ); return new Response(await response.text(), { status: response.status, headers: { "content-type": "application/json" }, }); } ``` ## Store the Nullifier After `/api/v4/verify/{rp_id}` succeeds, store the verified `nullifier` for the action and reject duplicates. During migration, keep checking any old `nullifier_hash` records if the same user could have verified before the upgrade. # Build with LLMs Source: https://docs.world.org/world-id/idkit/build-with-llms ## Before you start Create an app in the [Developer Portal](https://developer.world.org). You will need the `app_id`, `rp_id` and the signing key for the integration. Prefer to integrate manually? Follow the [IDKit integration guide](/world-id/idkit/integrate) for the complete client, signing, verification, and testing flow. If your coding assistant accepts a documentation index, give it the complete [World documentation index](https://docs.world.org/llms.txt). Copy this prompt and paste it into Codex, Claude, Cursor, or your preferred AI coding assistant: ```text title="Copy this prompt" theme={"system"} Read world.id/SKILL.md and add World ID to my app ``` ## World MCP You can also connect your AI assistant to World with MCP servers: * [World Docs MCP](/model-context-protocol/world-docs): search documentation while editing code. * [Developer Portal MCP](/model-context-protocol/developer-portal): create apps, configure World ID, and manage Mini App setup from your MCP client. # Configure Credentials Source: https://docs.world.org/world-id/idkit/credentials A [Credential](https://docs.rs/world-id-primitives/latest/world_id_primitives/credential/struct.Credential.html) is a statement an issuer makes about a World ID holder. In IDKit, the credential you request determines what the user proves to your app. # What you can request with IDKit | Offering | What it proves | Common SDK path | | ------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | Proof of Human | The user is a unique human, backed by anonymous biometric verification by the Orb. | `proofOfHuman` | | Passport | The user holds a verified NFC passport credential. | `passport` | | Selfie Check (Beta) | A medium-assurance biometric credential using the device camera for liveness and facial similarity. | `selfieCheckLegacy` | | Identity Check | An attestation that a document-backed property about the user matches your requested attributes. | `identityCheck` with attributes like `minimum_age`, `nationality`, or `document_type`. | You can also add a liveness check to any preset with `require_user_presence`. If you have an existing legacy integration, see the [World ID 4.0 migration guide](/world-id/4-0-migration). # SDK presets Preset helpers cover common credential requests: `proofOfHuman`, `passport`, `selfieCheckLegacy`, `identityCheck`, and the legacy presets. The snippets below assume `rp_context` was already generated and signed by your backend. See [Integrate IDKit](/world-id/idkit/integrate) for the full RP-signing flow. ## Proof of Human Use `proofOfHuman` for the current World ID 4.0 Proof of Human credential. The preset also includes legacy Orb fallback for users without a World ID 4.0 proof. ```typescript title="JavaScript" theme={"system"} import { IDKit, proofOfHuman } from "@worldcoin/idkit-core"; const preset = proofOfHuman({ signal: "user-123" }); const request = await IDKit.request({ app_id: "app_xxxxx", action: "my-action", rp_context, allow_legacy_proofs: true, }).preset(preset); ``` ```tsx title="React" theme={"system"} import { IDKitRequestWidget, proofOfHuman } from "@worldcoin/idkit"; const preset = proofOfHuman({ signal: "user-123" }); { /* ... */ }} />; ``` ## Passport Use `passport` for the current World ID 4.0 Passport credential. The preset also includes legacy Document fallback for users without a World ID 4.0 proof. ```typescript title="JavaScript" theme={"system"} import { IDKit, passport } from "@worldcoin/idkit-core"; const preset = passport({ signal: "user-123" }); const request = await IDKit.request({ app_id: "app_xxxxx", action: "my-action", rp_context, allow_legacy_proofs: true, }).preset(preset); ``` ```tsx title="React" theme={"system"} import { IDKitRequestWidget, passport } from "@worldcoin/idkit"; const preset = passport({ signal: "user-123" }); { /* ... */ }} />; ``` ## Selfie Check (Beta) [Request access](mailto:developers@toolsforhumanity.com) to enable Selfie Check (Beta) for your app. Once enabled, use `selfieCheckLegacy()` to request Selfie Check. The preset currently uses World ID 3.0; World ID 4.0 support is not yet available. Anyone with World ID App can complete the flow—no Orb or document credential is required. Forward the complete IDKit result to the [verification endpoint](/api-reference/developer-portal/verify). ```typescript title="JavaScript" theme={"system"} import { IDKit, selfieCheckLegacy } from "@worldcoin/idkit-core"; const preset = selfieCheckLegacy({ signal: "user-123" }); const request = await IDKit.request({ app_id: "app_xxxxx", action: "my-action", rp_context, }).preset(preset); ``` ```tsx title="React" theme={"system"} import { IDKitRequestWidget, selfieCheckLegacy } from "@worldcoin/idkit"; const preset = selfieCheckLegacy({ signal: "user-123" }); { /* ... */ }} />; ``` To test your integration end-to-end, see [Testing Selfie Check in Sandbox](/world-id/sandbox/testing-selfie-check). ## Identity Check (Preview) Identity Check is currently in preview. To use it or learn more, contact us. Identity Check lets your app ask the user to attest that document-backed attributes match your policy, without handling the underlying document data. Use it for eligibility checks such as minimum age, document type, issuing country, or nationality. Request attributes such as: | Attribute | Value type | | ----------------- | -------------------------------- | | `document_type` | `"passport"`, `"eid"` or `"mnc"` | | `document_number` | `string` | | `issuing_country` | ISO 3166-1 alpha-3 country code | | `full_name` | `string` | | `minimum_age` | `number` | | `nationality` | ISO 3166-1 alpha-3 country code | ```typescript title="JavaScript" theme={"system"} import { IDKit, identityCheck } from "@worldcoin/idkit-core"; const preset = identityCheck({ attributes: [ { type: "document_type", value: "passport" }, { type: "minimum_age", value: 18 }, ], }); const request = await IDKit.request({ app_id: "app_xxxxx", action: "my-action", rp_context, allow_legacy_proofs: false, }).preset(preset); ``` ```tsx title="React" theme={"system"} import { IDKitRequestWidget, identityCheck } from "@worldcoin/idkit"; const preset = identityCheck({ attributes: [ { type: "document_type", value: "passport" }, { type: "minimum_age", value: 18 }, ], }); { /* ... */ }} />; ``` Successful Identity Check responses include `identity_attested` so your backend can tell whether the requested attributes matched. ## User presence and liveness To check for user presence and liveness, add the `require_user_presence` flag to your request. This is a request-level flag, not a credential; it asks World ID for a fresh liveness check before returning the proof and fails with `user_presence_failed` if the check does not complete. Depending on the credential requested, World ID matches the user's live selfie to the credential image, such as the passport photo or the image captured during Orb verification. ```typescript title="JavaScript" theme={"system"} import { IDKit, proofOfHuman } from "@worldcoin/idkit-core"; const preset = proofOfHuman({ signal: "user-123" }); const request = await IDKit.request({ app_id: "app_xxxxx", action: "my-action", rp_context, allow_legacy_proofs: true, require_user_presence: true, }).preset(preset); ``` ```tsx title="React" theme={"system"} import { IDKitRequestWidget, proofOfHuman } from "@worldcoin/idkit"; const preset = proofOfHuman({ signal: "user-123" }); { /* ... */ }} />; ``` ## Other legacy presets These presets only return World ID 3.0 proofs. Use them for existing integrations or when you need the older verification level.
Preset What it requests
orbLegacy Orb verification.
secureDocumentLegacy At least a Secure Document verification. Returns the user's highest legacy credential: Secure Document or Orb.
documentLegacy At least a Document verification. Returns the user's highest legacy credential: Document, Secure Document, or Orb.
deviceLegacy Deprecated. Keep deviceLegacy only for existing Device integrations. For new integrations, use Selfie Check (Beta).
## Common parameters
Parameter Where it is used Description
signal Presets Binds app context into the proof, such as a user ID or wallet address. Your backend should enforce the same value.
allow\_legacy\_proofs Request config and widgets Required for request flows. Set to true while accepting World ID 3.0 fallback proofs; set to false for World ID 4.0-only requests.
require\_user\_presence Request config and widgets Optional liveness step. Defaults to false.
# Design Guidelines Source: https://docs.world.org/world-id/idkit/design-guidelines ## Figma Library
### IDKit Figma Library

Design components and templates for World ID integration

## **Core structure:** * **World ID logo** * **Partner logo** (Optional) * **Title:** "Connect your World ID" * **Description:** "Scan the QR code to connect your World ID" * **QR code container** (central and prominent) * **Footer:** "Terms & Privacy" link * **Dismiss (X)** icon for closing the modal or window **Note:** The QR code is dynamically generated and time-limited. Keep it up to date. ## Customization Guidelines **✅ Partners may:** * Adjust **typography** to match brand styles, keeping clear hierarchy (title > body) * Modify **corners or shadows** to match their brand * Modify **colors**, as long as contrast and accessibility are preserved * Customize **container shape** (e.g., rounded corners) and **shadow** * Add a **light/dark mode** toggle or theme-matching behavior * Localize the **copy** if needed (preferably keeping semantic structure) * Add their **brand logo** above the title **❌ Partners must not:** * Remove or replace the **World ID logo** * Alter or obscure the **QR code** * Change the **copy** in a way that misrepresents World ID * Make the QR code **too small** ## Size & Spacing
Element Recommendation
QR Code Minimum 160 x 160px
Padding Min. 24px all sides
Logo size 32-48px width
Font (title) Minimum 16px
Font (description) Minimum 14px
### **Accessibility checklist:** * Meet minimum QR code sizing for easy scanning * Meet WCAG AA contrast for all text * Make the modal **keyboard-navigable** and screen-reader friendly * Make "Terms & Privacy" a focusable link ### Behavior checklist * Open the modal centered and dim the background * Auto-refresh or invalidate the QR code after a set time (e.g., 5 mins) * Optional callback/event on QR scan success * Close the modal gracefully with the X button ## States **Default** Waiting for user to scan the QR code Default state - Waiting for user to scan the QR code **Success** - Successful connection Success - Successful connection **Request canceled** - User manually canceled request in World ID Request canceled - User manually canceled request in World ID **Connection lost** - Lost connection to server or user offline Connection lost - Lost connection to server or user offline **Error** - Generic technical error or proof failure Error - Generic technical error or proof failure ### Partnership example Partnership example ## Verified Human Badge The **Human Badge** signals that an account or user is verified as a unique human via **World ID**. It builds trust, improves authenticity, and embeds into products across industries (social, gaming, commerce, identity, etc.).
Human badge
### Human Badge Figma Assets

Download the human badge asset.

Open in Figma
*** ## Badge design * Symbol: World icon * Label: human in lowercase, optionally styled in pill or badge form ### **Variants:** * **Icon-only:** for dense UI like leaderboards or tooltips * **Icon + label:** preferred for public profiles, social posts, and player cards ### Do: * Keep badge **small, clear, unobtrusive** * Always pair with or near a user identity (e.g. username, avatar) * Use consistent shape and padding ### Don't: * Overstyle or animate * Replace or remix copy ("real human", "verified user", etc.) * Add misleading hierarchy (e.g. badge > blue check) *** ## Placement guidelines
Content Placement Format
Social profiles Below or beside username / bio Icon + label
Gaming profiles Under character name or stats Icon + label
Leaderboards Right of username Icon + label
Messaging/chat Right of username Icon + label
Account dashboards Near user ID, next to login method, etc. Icon + label
## Theming & Branding **✅ Partners may:** * Adapt badge color to suit **light/dark mode** **❌ Partners must not:** * Remove or replace the word human * Make it look like a World ID badge gives higher permissions than intended * Change the logo shape ## Rules checklist ### **Layout and Visual** * Correct badge asset used * Logo is not distorted, recolored or modified * Minimum height is respected ### Placement * Placed directly next to or below user, display name, or avatar * Badge is not visually grouped with unrelated badges * Badge does not imply special privileges unless defined # Error Codes Source: https://docs.world.org/world-id/idkit/error-codes Reference for IDKit request error codes and handling guidance across JS, React, Kotlin, and Swift. This page documents the IDKit SDK and bridge error codes returned during request flows. ## Canonical codes
Code Meaning Typical action
user\_rejected User cancelled in World ID. Treat as user cancellation, allow retry.
verification\_rejected Legacy rejection code (older bridge/app behavior). Handle same as user\_rejected.
credential\_unavailable Requested credential type is not available for that user. Offer fallback credential policy or explain requirement.
world\_id\_4\_not\_available World ID 4.0 credential is not available for that user. Use a compatible fallback request or explain the World ID 4.0 requirement.
world\_id\_3\_not\_available World ID 3.0 credential is not available for that user. Use a compatible fallback request or explain the World ID 3.0 requirement.
malformed\_request Payload or configuration is invalid. Check app\_id, rp\_context, and request shape.
invalid\_network Environment mismatch between app config and World App context. Align staging/production settings.
inclusion\_proof\_pending Credential inclusion data is not ready yet. Retry later.
inclusion\_proof\_failed Inclusion proof retrieval failed. Retry; if repeated, treat as operational incident.
unexpected\_response Malformed or unsupported bridge/app response. Log diagnostics and retry once.
connection\_failed Could not establish/maintain bridge communication. Check connectivity and bridge reachability.
max\_verifications\_reached Action already verified the maximum allowed number of times. Treat as terminal business-rule outcome.
failed\_by\_host\_app World App returned a proof, but the host app's handleVerify callback threw or rejected it. Inspect the callback and backend verification response. Do not use this code alone to decide whether the issue is in the integration or credential availability.
invalid\_rp\_signature RP signature could not be verified. Check the RP signing key, nonce, timestamps, action, and signed message.
nullifier\_replayed Nullifier was already used for this action. Treat as an already-verified outcome; do not retry the same action as a new verification.
duplicate\_nonce RP reused a signature nonce. Generate a fresh nonce and signed RP context for each request.
unknown\_rp RP is not known to the registry. Check the registered RP ID and app configuration.
inactive\_rp RP is registered but inactive. Reactivate or reconfigure the RP before retrying.
timestamp\_too\_old RP request timestamp is too old. Generate a new signed RP context with a current timestamp.
timestamp\_too\_far\_in\_future RP request timestamp is too far in the future. Fix server clock skew and generate a new signed RP context.
invalid\_timestamp RP request timestamp is invalid. Check timestamp format and regenerate the signed RP context.
rp\_signature\_expired RP signature has expired. Request a fresh RP signature before starting verification.
user\_presence\_failed Required user-presence check was not completed. Let the user retry the request.
identity\_attributes\_not\_matched User identity attributes did not match the requested constraints. Show an eligibility fallback or adjust the requested attribute constraints.
generic\_error Catch-all unknown failure. Log details and retry with backoff.
invalid\_rp\_id\_format RP ID is malformed. Use the registered rp\_... ID from your app configuration.
timeout Client-side polling timeout. Extend timeout or let the user retry.
cancelled Client-side cancellation (abort/task cancel/user close). Treat as neutral cancellation path.
## Handling errors Widgets expose an `onError` callback. Hooks expose `isError` and `errorCode` on the result object. In JS and React, failed requests expose an `IDKitDebugReport` for triage. The widget `onError` callback receives it as a second `debugReport` argument for flow/bridge errors (host-app verify failures such as `failed_by_host_app` omit it); hooks expose `getDebugReport(): IDKitDebugReport | undefined`. Treat version availability errors such as `world_id_4_not_available` and `world_id_3_not_available` as terminal for the current user and request. Retrying the same request usually returns the same result; change the requested credential policy or show a user-facing fallback instead. In JS and React, match these with `IDKitErrorCodes`. Kotlin and Swift expose the same raw values through their `IDKitErrorCode` enums. ```tsx theme={"system"} { console.error("IDKit error", errorCode, debugReport); }} /> ``` ```tsx theme={"system"} const flow = useIDKitRequest({ /* ... */ }); if (flow.isError) { console.error(flow.errorCode, flow.getDebugReport()); } ``` # Go Source: https://docs.world.org/world-id/idkit/go Lightweight Go module for generating RP signatures. The Go module is a **backend signing utility only**. It is used to generate `rp_context` signatures for IDKit requests, and does not run client-side IDKit request flows. ## Install ```bash theme={"system"} go get github.com/worldcoin/idkit/go/idkit@latest ``` ## Generate RP signature ### One-shot signing Use `SignRequest` with functional options for the simplest integration: ```go theme={"system"} import "github.com/worldcoin/idkit/go/idkit" // For uniqueness proofs: include the action sig, err := idkit.SignRequest( os.Getenv("RP_SIGNING_KEY"), idkit.WithAction("my-action"), ) if err != nil { // handle error } rpContext := map[string]any{ "rp_id": "rp_xxxxx", "nonce": sig.Nonce, "created_at": sig.CreatedAt, "expires_at": sig.ExpiresAt, "signature": sig.Sig, } ``` ### Reusable signer For high-throughput backends, create a `Signer` once and reuse it. This parses the key upfront and avoids repeated allocations. ```go theme={"system"} signer, err := idkit.NewSigner(os.Getenv("RP_SIGNING_KEY")) if err != nil { log.Fatal(err) } // Use in your request handler sig, err := signer.SignRequest( idkit.WithAction("my-action"), idkit.WithTTL(600), // optional, default 300s ) ``` ## API ### Functions | Function | Description | | ---------------------------------------- | ------------------------------------------ | | `SignRequest(signingKeyHex, opts...)` | One-shot signing with options | | `SignRequestWithTTL(signingKeyHex, ttl)` | Convenience wrapper with custom TTL | | `NewSigner(signingKeyHex)` | Creates a reusable `Signer` from a hex key | ### Options | Option | Description | | -------------------- | ------------------------------------------------------------------------------------ | | `WithAction(action)` | Hashes and appends the action to the signed payload (required for uniqueness proofs) | | `WithTTL(ttl)` | Overrides the default 300-second TTL | ### `RpSignature` ```go theme={"system"} type RpSignature struct { Sig string `json:"sig"` // 0x-prefixed, 65-byte hex Nonce string `json:"nonce"` // 0x-prefixed, 32-byte field element CreatedAt uint64 `json:"created_at"` // Unix seconds ExpiresAt uint64 `json:"expires_at"` // Unix seconds } ``` ## Related pages * [RP Signatures](/world-id/idkit/signatures) — algorithm details, pseudocode, and test vectors * [Integrate IDKit](/world-id/idkit/integrate) # IDKit Source: https://docs.world.org/world-id/idkit/integrate Integrate World ID into your app IDKit is the SDK for integrating World ID into your app. It handles proof requests, verification flows and communication with the World ID App, so your backend receives cryptographic proof of human, not personal data. Use the React widget for drop-in integration, or the JS, Swift and Kotlin SDKs for custom flows. For the core concepts of World ID, see [this page](/world-id/concepts). > Tip: To integrate faster, give your coding agent the [Build with LLMs prompt](/world-id/idkit/build-with-llms) — copy it once, then paste into Claude, Cursor or any AI coding assistant. ## How it works Send a **proof request** through IDKit, challenging the user to prove something about themselves — such as uniqueness, document possession or liveness — based on their [credentials](/world-id/overview#credentials). The user's World ID App generates a zero-knowledge proof without revealing personal data. Your backend then verifies the proof and stores a **nullifier** (a per-app, per-action identifier) to prevent the same person from verifying twice.
IDKit integration flow: your app sends a proof request to World App, receives a ZK proof, forwards it to your backend, and the backend verifies it with the World ID Developer Portal
Four components are involved: your **client** (where IDKit runs), your **backend** (which signs requests and verifies proofs), the **World ID App** (on the user's device) and the **Developer Portal** (which validates proofs on-chain). The steps below cover each one. # Step 1: Install IDKit Use the latest `4.x` version. ```bash title="JavaScript" theme={"system"} npm i @worldcoin/idkit-core ``` ```bash title="React" theme={"system"} npm i @worldcoin/idkit ``` ```swift title="Swift (SPM)" theme={"system"} .package(url: "https://github.com/worldcoin/idkit-swift.git", from: "") ``` ```kotlin title="Kotlin (Gradle)" theme={"system"} dependencies { implementation("com.worldcoin:idkit:") } ``` # Step 2: Create an app in the Developer Portal Create your app in the [Developer Portal](https://developer.world.org). If you're migrating from an old app, complete RP registration by clicking the Enable World ID 4.0 banner. Keep these values: * `app_id` * `rp_id` * `signing_key` - this should be stored as a secret. # Step 3: Generate an RP signature in your backend Signatures verify that proof requests come from your app, preventing impersonation attacks. ```typescript title="JavaScript" theme={"system"} import { NextResponse } from "next/server"; import { signRequest } from "@worldcoin/idkit-core/signing"; export async function POST(request: Request): Promise { const { action } = await request.json(); const { sig, nonce, createdAt, expiresAt } = signRequest({ signingKeyHex: process.env.RP_SIGNING_KEY!, action, }); return NextResponse.json({ sig, nonce, created_at: createdAt, expires_at: expiresAt, }); } ``` ```go title="Go" theme={"system"} package main import ( "encoding/json" "net/http" "os" "github.com/worldcoin/idkit/go/idkit" ) func handleRPSignature(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } var body struct { Action string `json:"action"` } _ = json.NewDecoder(r.Body).Decode(&body) sig, err := idkit.SignRequest( os.Getenv("RP_SIGNING_KEY"), idkit.WithAction(body.Action), ) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("content-type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{ "sig": sig.Sig, "nonce": sig.Nonce, "created_at": sig.CreatedAt, "expires_at": sig.ExpiresAt, }) } ``` Never generate RP signatures on the client and never expose your RP signing key. If the key leaks, attackers can forge requests from your app. The steps below cover the standard request flow. Depending on whether the user already has World ID and the credential you're requesting, they may see a different experience. See [Verification flows](/world-id/idkit/verification-flows) for details. # Step 4: Generate the connect URL and collect proof To test during development, use the [simulator](https://simulator.worldcoin.org/) and set `environment` to `"staging"`. ```typescript title="JavaScript" theme={"system"} import { IDKit, orbLegacy } from "@worldcoin/idkit-core"; const rpSig = await fetch("/api/rp-signature", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action: "my-action" }), }).then((r) => r.json()); const request = await IDKit.request({ // App ID: `app_id` from the Developer Portal app_id: "app_xxxxx", // Action: Context that scopes what the user is proving uniqueness for // e.g., "verify-account-2026" or "claim-airdrop-2026". action: "my-action", rp_context: { rp_id: "rp_xxxxx", // Your app's `rp_id` from the Developer Portal nonce: rpSig.nonce, created_at: rpSig.created_at, expires_at: rpSig.expires_at, signature: rpSig.sig, }, allow_legacy_proofs: true, environment: "production", // Only set this to staging for testing with the simulator return_to: "myapp://verify-done", // Optional: mobile deep-link callback URL // Signal (optional): Bind specific context into the requested proof. // Examples: user ID, wallet address. Your backend should enforce the same value. }).preset(orbLegacy({ signal: "local-election-1" })); const connectUrl = request.connectorURI; const response = await request.pollUntilCompletion(); ``` ```tsx title="React" theme={"system"} import { IDKitRequestWidget, orbLegacy, type RpContext, } from "@worldcoin/idkit"; const rpSig = await fetch("/api/rp-signature", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ action: "my-action" }), }).then((r) => r.json()); const rp_context: RpContext = { rp_id: "rp_xxxxx", // Your app's `rp_id` from the Developer Portal nonce: rpSig.nonce, created_at: rpSig.created_at, expires_at: rpSig.expires_at, signature: rpSig.sig, }; // ... { const response = await fetch("/api/verify-proof", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ rp_id: rp_context.rp_id, idkitResponse: result, }), }); if (!response.ok) { throw new Error("Backend verification failed"); } }} onSuccess={(result) => { // Runs after `handleVerify` succeeds. Update app state/UI here. }} />; ``` ```swift title="Swift" theme={"system"} import IDKit // Fetch the RP signature from your backend let rpSig = try await yourBackend.fetchRpSignature(action: "my-action") let rpContext = try RpContext( rpId: "rp_xxxxx", // Your app's `rp_id` from the Developer Portal nonce: rpSig.nonce, createdAt: rpSig.createdAt, expiresAt: rpSig.expiresAt, signature: rpSig.sig ) let config = IDKitRequestConfig( // App ID: `app_id` from the Developer Portal appId: "app_xxxxx", // Action: Context that scopes what the user is proving uniqueness for // e.g., "verify-account-2026" or "claim-airdrop-2026". action: "my-action", rpContext: rpContext, actionDescription: "Verify user", bridgeUrl: nil, allowLegacyProofs: true, overrideConnectBaseUrl: nil, environment: .production ) // Signal (optional): Bind specific context into the requested proof. // Examples: user ID, wallet address. Your backend should enforce the same value. let request = try IDKit.request(config: config).preset(orbLegacy(signal: "local-election-1")) let connectUrl = request.connectorURL let completion = await request.pollUntilCompletion() ``` ```kotlin title="Kotlin" theme={"system"} import com.worldcoin.idkit.IDKit import com.worldcoin.idkit.IDKitRequestConfig import com.worldcoin.idkit.RpContext import com.worldcoin.idkit.Environment import com.worldcoin.idkit.orbLegacy // Fetch the RP signature from your backend (see Step 2) val rpSig = yourBackend.fetchRpSignature(action = "my-action") val rpContext = RpContext( rpId = "rp_xxxxx", // Your app's `rp_id` from the Developer Portal nonce = rpSig.nonce, createdAt = rpSig.createdAt.toULong(), expiresAt = rpSig.expiresAt.toULong(), signature = rpSig.sig, ) val config = IDKitRequestConfig( // App ID: `app_id` from the Developer Portal appId = "app_xxxxx", // Action: Context that scopes what the user is proving uniqueness for // e.g., "verify-account-2026" or "claim-airdrop-2026". action = "my-action", rpContext = rpContext, allowLegacyProofs = true, ) // Signal (optional): Bind specific context into the requested proof. // Examples: user ID, wallet address. Your backend should enforce the same value. val request = IDKit.request(config).preset(orbLegacy(signal = "local-election-1")) val connectorURI = request.connectorURI val completion = request.pollUntilCompletion() ``` ### IDKit response After the user completes the verification flow, IDKit returns one of the following response shapes, depending on the protocol version and proof type. ```json title="World ID 3.0 (Legacy)" theme={"system"} { "protocol_version": "3.0", "nonce": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "action": "my-action", "environment": "production", "responses": [ { "identifier": "orb", "signal_hash": "0x00c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4", "proof": "0x1a2b3c...encoded_proof", "merkle_root": "0x0abc123...root_hash", "nullifier": "0x04e5f6...nullifier_hash" } ], "user_presence_completed": false } ``` ```json title="World ID 4.0 Uniqueness" theme={"system"} { "protocol_version": "4.0", "nonce": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "action": "my-action", "environment": "production", "responses": [ { "identifier": "proof_of_human", "signal_hash": "0x0", "proof": ["0x1a2b...", "0x3c4d...", "0x5e6f...", "0x7a8b...", "0x9c0d..."], "nullifier": "0x04e5f6...rp_scoped_nullifier", "issuer_schema_id": 1, "expires_at_min": 1756166400 } ], "user_presence_completed": false } ``` ```json title="World ID 4.0 Session" theme={"system"} { "protocol_version": "4.0", "nonce": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "session_id": "session_5f3a9c2e...8d1b0a", "environment": "production", "responses": [ { "identifier": "proof_of_human", "signal_hash": "0x0", "proof": ["0x1a2b...", "0x3c4d...", "0x5e6f...", "0x7a8b...", "0x9c0d..."], "session_nullifier": ["0x04e5f6...session_nullifier", "0x07a8b9...generated_action"], "issuer_schema_id": 1, "expires_at_min": 1756166400 } ], "user_presence_completed": false } ``` # Step 5: Verify the proof in your backend After completion, send the returned payload to your backend and forward it directly to `POST https://developer.world.org/api/v4/verify/{rp_id}`. Forward the IDKit result payload as-is. No field remapping is required. ```typescript title="app/api/verify-proof/route.ts" theme={"system"} import { NextResponse } from "next/server"; import type { IDKitResult } from "@worldcoin/idkit"; export async function POST(request: Request): Promise { const { rp_id, idkitResponse } = (await request.json()) as { rp_id: string; idkitResponse: IDKitResult; }; const response = await fetch( `https://developer.world.org/api/v4/verify/${rp_id}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(idkitResponse), }, ); if (!response.ok) { return NextResponse.json({ error: "Verification failed" }, { status: 400 }); } // Proof is valid — now store the nullifier (see Step 6) return NextResponse.json({ success: true }); } ``` # Step 6: Store the nullifier Every World ID proof contains a nullifier — a value derived from the user's World ID, your app, and the action. The same person verifying the same action always produces the same nullifier, but different apps or actions produce different ones, making nullifiers unlinkable across apps. The Developer Portal confirms the proof is **cryptographically valid**, but your backend must check that the nullifier hasn't been used before. Otherwise, the same person could verify multiple times for the same action. Nullifiers are returned as 0x-prefixed hex strings representing 256-bit integers. Convert and store them as numbers to avoid parsing and casing issues that can lead to security vulnerabilities. For example, PostgreSQL doesn't natively support 256-bit integers, so convert the nullifier to a decimal and store it as `NUMERIC(78, 0)`. ```sql title="PostgreSQL schema" theme={"system"} CREATE TABLE nullifiers ( nullifier NUMERIC(78, 0) NOT NULL, action TEXT NOT NULL, verified_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (nullifier, action) ); ``` ## Architecture detail ```mermaid theme={"system"} sequenceDiagram participant User participant Client participant Backend participant Portal as Developer Portal Client->>Backend: 1. Request RP signature Backend->>Backend: Sign with signing_key Backend-->>Client: { sig, nonce, created_at, expires_at } Client->>Client: 2. Create IDKit request with RP signature Client->>User: 3. Show connect URL / QR code User->>User: 4. Prove uniqueness in World ID User-->>Client: 5. Proof returned via IDKit Client->>Backend: 6. Forward proof payload Backend->>Portal: POST /v4/verify/{rp_id} Portal-->>Backend: Verification result Backend->>Backend: 7. Check & store nullifier Backend-->>Client: Success / failure ``` ## Next pages * [RP Signatures](/world-id/idkit/signatures) — algorithm details, pseudocode, and test vectors * [POST /v4/verify reference](/api-reference/developer-portal/verify) # Build with LLMs Source: https://docs.world.org/world-id/idkit/integration-prompt Copy this prompt and paste it into Codex, Claude, Cursor, or your preferred AI coding assistant to integrate World ID into your existing project. Replace the placeholder values (`app_xxxxx`, `rp_xxxxx`, `my-action`) with your actual values from the [Developer Portal](https://developer.world.org). ```text title="Copy this prompt" theme={"system"} Integrate World ID into my project using IDKit. Here are my app details: - app_id: app_xxxxx - rp_id: rp_xxxxx - action: my-action - signing_key: (stored as RP_SIGNING_KEY env var) ## Steps 1. Install the IDKit SDK for my platform. 2. Create a backend endpoint that generates RP signatures. Signatures verify that proof requests come from my app. Use `signRequest({ signingKeyHex, action })` which returns `{ sig, nonce, createdAt, expiresAt }`. Never expose the signing key to the client. 3. On the client, fetch the RP signature from my backend, then create an IDKit request with: - `app_id`, `action`, and `rp_context` (containing `rp_id`, `nonce`, `created_at`, `expires_at`, `signature`; map the RP signature's `sig`->`signature`, `createdAt`->`created_at`, `expiresAt`->`expires_at`) - `allow_legacy_proofs: true` - `.preset(proofOfHuman())` for Proof of Human (World ID 4.0, with Orb fallback) - Signal is optional — use it to bind context like a user ID or wallet address into the proof. The backend should enforce the same value. 4. On success, send the IDKit result to my backend. The backend should forward the payload as-is to: POST https://developer.world.org/api/v4/verify/{rp_id} No field remapping is needed. ## Reference - Full docs: https://docs.world.org/llms.txt ``` # JavaScript Source: https://docs.world.org/world-id/idkit/javascript Reference for `@worldcoin/idkit-core` `@worldcoin/idkit-core` is the lowest-level JavaScript/TypeScript IDKit SDK. Use it when you want full control over UI and state management or when you're not using React. ## Install ```bash npm theme={"system"} npm i @worldcoin/idkit-core ``` ```bash pnpm theme={"system"} pnpm add @worldcoin/idkit-core ``` ```bash yarn theme={"system"} yarn add @worldcoin/idkit-core ``` ## Entry points * `IDKit.request(config)` for uniqueness proofs * `IDKit.requestWithInviteCode(config)` for invite-code mode * `orbLegacy`, `secureDocumentLegacy`, `documentLegacy`, `selfieCheckLegacy` for presets Each entry point returns a builder. Finalize it with `.preset(...)`. ## Request config ```ts theme={"system"} import { IDKit } from "@worldcoin/idkit-core"; const builder = IDKit.request({ app_id: "app_xxxxx", action: "my-action", rp_context: { rp_id: "rp_xxxxx", nonce: "0x...", created_at: 1735689600, expires_at: 1735689900, signature: "0x...", }, allow_legacy_proofs: true, environment: "production", // Only set this to staging for testing with the simulator return_to: "myapp://verify-done", // Optional: mobile deep-link callback URL bridge_url: undefined, // Optional: custom bridge URL }); ``` Generate `rp_context` in your backend only. Never expose your RP signing key in client code. ## Presets ```ts theme={"system"} import { IDKit, orbLegacy } from "@worldcoin/idkit-core"; const request = await IDKit.request({ app_id: "app_xxxxx", action: "my-action", rp_context, allow_legacy_proofs: true, }).preset(orbLegacy({ signal: "user-123" })); ``` ## Polling and status After `.preset(...)`, you get an `IDKitRequest` object: * `connectorURI` * `requestId` * `pollOnce()` * `pollUntilCompletion({ pollInterval, timeout })` * `getDebugReport()` ```ts theme={"system"} import { IDKitErrorCodes } from "@worldcoin/idkit-core"; const completion = await request.pollUntilCompletion({ pollInterval: 2_000, timeout: 120_000, }); if (!completion.success) { if (completion.error === IDKitErrorCodes.Timeout) { // UI timeout handling } if (completion.error === IDKitErrorCodes.Cancelled) { // User/app cancellation handling } } ``` When running inside World App, native transport is used and `connectorURI` may be empty. Outside World App, `connectorURI` is the URL you render as a QR code. ## Debug report Both `IDKitRequest` and `IDKitInviteCodeRequest` expose `getDebugReport(): IDKitDebugReport` with diagnostics for the latest request state. ```ts theme={"system"} import type { IDKitDebugReport } from "@worldcoin/idkit-core"; const completion = await request.pollUntilCompletion(); if (!completion.success) { const report = request.getDebugReport(); console.error(report.transport, report.request_id, report); } ``` `IDKitDebugReport` fields: `version`, `package_version`, `transport` (`"bridge" | "mini_app"`), `generated_at`, and optional `request_id`, `request_payload`, `response_payload`, and `mini_app` (`MiniAppDebugInfo`). For bridge transport `response_payload` is the decrypted plaintext response string once the request completes; for native (`mini_app`) transport it is a structured debug object. The `mini_app` object holds World App native-transport diagnostics: `verify_version`, `platform`, `send_channel`, `minikit_subscribed`, and `response_channel`. `setDebug(true)` (or `window.IDKIT_DEBUG = true`) and `isDebug()` toggle verbose `console.debug` logging. This is separate from `getDebugReport()`, which works regardless. ## Invite-code mode Use `IDKit.requestWithInviteCode(config)` to open a landing page that displays both an invite code and a QR code. Validation, the returned `Status` shape, and the poll loop are identical to `IDKit.request`. See [Invite-code mode](/world-id/idkit/verification-flows#with-invite-code-mode) for when to use it. ```ts theme={"system"} import { IDKit, selfieCheckLegacy } from "@worldcoin/idkit-core"; const request = await IDKit.requestWithInviteCode({ app_id: "app_xxxxx", action: "my-action", rp_context, allow_legacy_proofs: true, }).preset(selfieCheckLegacy({ signal: "user-123" })); const connectorURI = request.connectorURI; // URL with &c=&a= const expiresAt = request.expiresAt; // Unix seconds const completion = await request.pollUntilCompletion(); ``` `IDKitInviteCodeRequest` exposes: * `connectorURI` * `expiresAt` * `requestId` * `pollOnce()` * `pollUntilCompletion({ pollInterval, timeout })` * `getDebugReport()` ### Migrating from QR / connect-URL ```ts theme={"system"} // Before — QR / connect-URL flow const request = await IDKit.request({ app_id: "app_xxxxx", action: "my-action", rp_context, allow_legacy_proofs: true, }).preset(orbLegacy({ signal: "user-123" })); const connectorURI = request.connectorURI; // render as QR const completion = await request.pollUntilCompletion(); ``` ```ts theme={"system"} // After — invite-code mode const request = await IDKit.requestWithInviteCode({ app_id: "app_xxxxx", action: "my-action", rp_context, allow_legacy_proofs: true, }).preset(selfieCheckLegacy({ signal: "user-123" })); const connectorURI = request.connectorURI; // display to user (URL with code embedded) const expiresAt = request.expiresAt; // drive a countdown const completion = await request.pollUntilCompletion(); ``` The config object is unchanged. The `connectorURI` now includes `&c=&a=` params; use it alongside `expiresAt` in your UI. Polling, proof verification, and nullifier storage stay the same. ## Server-side helpers Use subpath exports on your backend: ```ts theme={"system"} import { signRequest } from "@worldcoin/idkit-core/signing"; import { hashSignal } from "@worldcoin/idkit-core/hashing"; const { sig, nonce, createdAt, expiresAt } = signRequest({ signingKeyHex: process.env.RP_SIGNING_KEY!, action: "my-action", ttl: 300, // optional, default 300s }); const signalHash = hashSignal("user-123"); ``` `signRequest` should only run in trusted server environments. See [RP Signatures](/world-id/idkit/signatures) for the full algorithm and test vectors. ## Related pages * [Getting started](/world-id/idkit/integrate) * [RP Signatures](/world-id/idkit/signatures) * [React](/world-id/idkit/react) * [Error Codes](/world-id/idkit/error-codes) * [POST /v4/verify reference](/api-reference/developer-portal/verify) # Kotlin Source: https://docs.world.org/world-id/idkit/kotlin Reference for the IDKit Kotlin API: request builders, presets, and polling. The Kotlin SDK is currently under active development. Expect minor API evolution while the SDK hardens. ## Install The package is published as `com.worldcoin:idkit` to GitHub Packages. ```kotlin theme={"system"} dependencyResolutionManagement { repositories { mavenCentral() maven { url = uri("https://maven.pkg.github.com/worldcoin/idkit") credentials { username = System.getenv("GITHUB_ACTOR") password = System.getenv("GITHUB_TOKEN") // requires read:packages } } } } dependencies { implementation("com.worldcoin:idkit:") } ``` ## Request flow ```kotlin theme={"system"} import com.worldcoin.idkit.IDKit import com.worldcoin.idkit.IDKitRequestConfig import com.worldcoin.idkit.IDKitPollOptions import com.worldcoin.idkit.IDKitCompletionResult import com.worldcoin.idkit.orbLegacy import uniffi.idkit_core.Environment import uniffi.idkit_core.RpContext val rpContext = RpContext( rpId = "rp_xxxxx", nonce = backend.nonce, createdAt = backend.createdAt.toULong(), expiresAt = backend.expiresAt.toULong(), signature = backend.sig, ) val config = IDKitRequestConfig( appId = "app_xxxxx", action = "my-action", rpContext = rpContext, allowLegacyProofs = true, returnTo = "myapp://verify-done", // Optional: mobile deep-link callback URL environment = Environment.PRODUCTION, ) val request = IDKit.request(config).preset(orbLegacy(signal = "user-123")) val connectorURI = request.connectorURI val completion = request.pollUntilCompletion( options = IDKitPollOptions(pollIntervalMs = 2_000u, timeoutMs = 120_000u) ) if (completion is IDKitCompletionResult.Success) { val result = completion.result } ``` ## Presets ```kotlin theme={"system"} import com.worldcoin.idkit.IDKit import com.worldcoin.idkit.orbLegacy val request = IDKit.request(config).preset(orbLegacy(signal = "user-123")) ``` ## Polling patterns * `pollStatusOnce()` for manual loops * `pollUntilCompletion(options)` for blocking until terminal state * `statusFlow(pollInterval)` extension for coroutine-driven updates ```kotlin theme={"system"} import com.worldcoin.idkit.IDKitStatus import com.worldcoin.idkit.statusFlow import kotlinx.coroutines.flow.collectLatest import kotlin.time.Duration.Companion.seconds request.statusFlow(2.seconds).collectLatest { status -> when (status) { IDKitStatus.WaitingForConnection -> Unit IDKitStatus.AwaitingConfirmation -> Unit is IDKitStatus.Confirmed -> println(status.result) is IDKitStatus.Failed -> println(status.error) } } ``` # Mini Apps Source: https://docs.world.org/world-id/idkit/mini-apps IDKit is the verification layer for Mini Apps. MiniKit handles native Mini App commands such as wallet auth, payments, transactions, sharing, and permissions. World ID verification, including Selfie Check, is implemented with `@worldcoin/idkit`. Inside World App, IDKit uses the native World App transport — no QR code is shown. The same widget also works outside World App, where it falls back to the normal connect URL and QR experience. Your backend responsibilities do not change: sign each request server-side, verify the proof server-side, and store nullifiers for replay protection. If you are migrating from MiniKit 1.x, replace `MiniKit.verify(...)` or `MiniKit.commandsAsync.verify(...)` with IDKit. MiniKit 2.x does not proxy verification requests. ## How IDKit fits a Mini App The integration is the standard IDKit flow — follow the [Integrate IDKit](/world-id/idkit/integrate) guide for installation, the server-side RP signature endpoint, backend proof verification, and nullifier storage. Nothing in those steps changes for Mini Apps. The Mini-App-specific details: * **Separate app IDs.** If your Mini App and World ID integration use separate Developer Portal apps, MiniKit initialization uses the Mini App `app_id`, while IDKit uses the World ID `app_id`, `rp_id`, action, and signing key. * **No transport configuration needed.** IDKit detects World App and uses the native transport automatically. * **Standalone websites work too.** If your Mini App also runs as a regular website, the same widget handles browser users. For iOS install-continuation flows outside World App, see [invite-code mode](/world-id/idkit/verification-flows#with-invite-code-mode). ## Choose a verification level | Goal | IDKit preset | | ------------------------------------------------------ | ------------------- | | Strong sybil resistance or one-human-one-action checks | `proofOfHuman` | | Lower-friction liveness or bot deterrence | `selfieCheckLegacy` | | Passport-backed checks | `passport` | Check out this [page](/world-id/idkit/credentials) to learn about the different World ID credentials and which preset to use for each. ## Example This is the same widget from [Step 4 of the integration guide](/world-id/idkit/integrate#step-4-generate-the-connect-url-and-collect-proof), using the `proofOfHuman` preset: ```tsx title="components/WorldIdGate.tsx" theme={"system"} { // Unlock the protected Mini App experience here. }} onError={(errorCode, debugReport) => { // Inside World App, debugReport?.transport is "mini_app". console.error("IDKit error", errorCode, debugReport); }} /> ``` Use a stable `signal` (user ID, wallet address, claim ID) to bind the proof to app-specific context, and [store the nullifier](/world-id/idkit/integrate#step-6-store-the-nullifier) in your backend to enforce uniqueness. ## Related pages * [Integrate IDKit](/world-id/idkit/integrate) * [Configure Credentials](/world-id/idkit/credentials) * [React reference](/world-id/idkit/react) * [MiniKit commands](/mini-apps/quick-start/commands) # On-chain Verification Source: https://docs.world.org/world-id/idkit/onchain-verification Verify World ID proofs directly in Solidity for web3-native flows. On-chain verification is best for web3 apps that need proof checks enforced by smart contracts (for example gating mints, voting, or claims without backend trust assumptions). If you do not need contract-level enforcement, use [POST /v4/verify](/api-reference/developer-portal/verify) instead. ## 1. Verifying Legacy proofs (World ID 3.0) If you are integrating World ID on-chain, we strongly recommend deploying your contract behind an upgradable proxy (e.g. UUPS or Transparent Proxy). This allows you to upgrade your verification logic as new World ID versions are released. See the [World ID 4.0 Migration guide](/world-id/4-0-migration) for details. For v3 proofs, verify against `WorldIDRouter.verifyProof(...)`. Use the Router address for the chain you're deploying to: | Chain | `WorldIDRouter` Mainnet | `WorldIDRouter` Testnet | | ----------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | World Chain | [0x17B354dD...A278](https://worldscan.org/address/0x17B354dD2595411ff79041f930e491A4Df39A278) | [0x57f92815...f611](https://sepolia.worldscan.org/address/0x57f928158C3EE7CDad1e4D8642503c4D0201f611) | | Ethereum | [id.worldcoin.eth](https://etherscan.io/address/0x163b09b4fe21177c455d850bd815b6d583732432#code) | [0x469449f2...2157](https://sepolia.etherscan.io/address/0x469449f251692e0779667583026b5a1e99512157#code) | | Base | [0xBCC7e591...4163](https://basescan.org/address/0xBCC7e5910178AFFEEeBA573ba6903E9869594163#code) | [0x42FF98C4...C02](https://sepolia.basescan.org/address/0x42FF98C4E85212a5D31358ACbFe76a621b50fC02#code) | | Optimism | [optimism.id.worldcoin.eth](https://optimistic.etherscan.io/address/0x57f928158C3EE7CDad1e4D8642503c4D0201f611#code) | [0x11cA3127...4334](https://sepolia-optimism.etherscan.io/address/0x11cA3127182f7583EfC416a8771BD4d11Fae4334#code) | | Polygon | [polygon.id.worldcoin.eth](https://polygonscan.com/address/0x515f06B36E6D3b707eAecBdeD18d8B384944c87f#code) | — | ```solidity theme={"system"} interface IWorldID { function verifyProof( uint256 root, uint256 groupId, uint256 signalHash, uint256 nullifierHash, uint256 externalNullifierHash, uint256[8] calldata proof ) external view; } contract VerifyLegacyV3 { IWorldID public immutable worldIdRouter; uint256 public constant GROUP_ID = 1; // Orb mapping(uint256 => bool) public nullifierHashes; error InvalidNullifier(); constructor(IWorldID _worldIdRouter) { worldIdRouter = _worldIdRouter; } function verifyLegacyAndExecute( uint256 root, uint256 signalHash, uint256 nullifierHash, uint256 externalNullifierHash, uint256[8] calldata proof ) external { if (nullifierHashes[nullifierHash]) revert InvalidNullifier(); worldIdRouter.verifyProof( root, GROUP_ID, signalHash, nullifierHash, externalNullifierHash, proof ); nullifierHashes[nullifierHash] = true; // Execute protected business logic here. } } ``` For legacy proofs, ensure `groupId = 1` (Orb-only on-chain path). If your v3 proof arrives as ABI-encoded bytes, decode it to `uint256[8]` before calling `verifyProof`: ```ts viem theme={"system"} import { decodeAbiParameters } from "viem"; const unpackedProof = decodeAbiParameters([{ type: "uint256[8]" }], proof)[0]; ``` ```ts ethers.js theme={"system"} import { defaultAbiCoder as abi } from "@ethersproject/abi"; const unpackedProof = abi.decode(["uint256[8]"], proof)[0]; ``` ## 2. Verifying Uniqueness proofs in `WorldIDVerifier.sol` (World ID 4.0) `WorldIDVerifier` is deployed on World Chain Mainnet as an upgradeable proxy. Use the proxy address for your environment: | Environment | Chain | `WorldIDVerifier` proxy | | ----------- | ----------- | ---------------------------------------------------------------------------------------------------------------------- | | Production | World Chain | [0x00000000009E00F9FE82CfeeBB4556686da094d7](https://worldscan.org/address/0x00000000009E00F9FE82CfeeBB4556686da094d7) | | Staging | World Chain | [0x703a6316c975DEabF30b637c155edD53e24657DB](https://worldscan.org/address/0x703a6316c975DEabF30b637c155edD53e24657DB) | For v4 uniqueness proofs, call `verify(...)` on the `WorldIDVerifier` proxy and store used nullifiers to enforce one-human-one-action semantics in your contract. ```solidity theme={"system"} interface IWorldIDVerifier { function verify( uint256 nullifier, uint256 action, uint64 rpId, uint256 nonce, uint256 signalHash, uint64 expiresAtMin, uint64 issuerSchemaId, uint256 credentialGenesisIssuedAtMin, uint256[5] calldata zeroKnowledgeProof ) external view; } contract VerifyUniquenessV4 { IWorldIDVerifier public immutable verifier; mapping(uint256 => bool) public nullifierUsed; error InvalidNullifier(); constructor(IWorldIDVerifier _verifier) { verifier = _verifier; } function verifyAndExecute( uint256 nullifier, uint256 action, uint64 rpId, uint256 nonce, uint256 signalHash, uint64 expiresAtMin, uint64 issuerSchemaId, uint256 credentialGenesisIssuedAtMin, uint256[5] calldata proof ) external { if (nullifierUsed[nullifier]) revert InvalidNullifier(); verifier.verify( nullifier, action, rpId, nonce, signalHash, expiresAtMin, issuerSchemaId, credentialGenesisIssuedAtMin, proof ); // Mark nullifier after successful verification (sybil resistance). nullifierUsed[nullifier] = true; // Execute protected business logic here. } } ``` Minimal mapping from IDKit result: * `nullifier` = `responses[i].nullifier` * `action` = `keccak256(action)` as `uint256` * `rpId` = numeric form of your `rp_context.rp_id` (the `rp_`-prefixed string from your RP context, not the result) * `nonce` = top-level `nonce` * `signalHash` = `responses[i].signal_hash` * `expiresAtMin` = `responses[i].expires_at_min` * `issuerSchemaId` = `responses[i].issuer_schema_id` * `credentialGenesisIssuedAtMin` = the request's `genesis_issued_at_min` constraint (`0` if unconstrained) — not returned in `responses[i]` * `proof` = `responses[i].proof` (`uint256[5]`) # React Source: https://docs.world.org/world-id/idkit/react Reference for @worldcoin/idkit React widgets and hooks, including request flows and presets. Use `@worldcoin/idkit` when you want React-native ergonomics on top of `@worldcoin/idkit-core`. ## Install ```bash npm theme={"system"} npm i @worldcoin/idkit ``` ```bash pnpm theme={"system"} pnpm add @worldcoin/idkit ``` ```bash yarn theme={"system"} yarn add @worldcoin/idkit ``` ## Choose your API * **Widgets**: quickest integration, built-in modal and state handling * **Hooks**: headless control for custom UI and flow orchestration ## Controlled widgets ### Request widget ```tsx theme={"system"} import { IDKitRequestWidget, orbLegacy, type RpContext } from "@worldcoin/idkit"; const rpContext: RpContext = { rp_id: "rp_xxxxx", nonce: "0x...", created_at: 1735689600, expires_at: 1735689900, signature: "0x...", }; { const response = await fetch("/api/verify-proof", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ rp_id: rpContext.rp_id, idkitResponse: result }), }); if (!response.ok) { throw new Error("Backend verification failed"); } }} onSuccess={() => { // Called after `handleVerify` resolves (or immediately if omitted). // Update your app state/UI here. }} onError={(errorCode, debugReport) => { console.error("IDKit error", errorCode, debugReport); }} />; ``` ### Widget callbacks * `onSuccess` is required for `IDKitRequestWidget` and `IDKitSessionWidget`. * `handleVerify` is optional and only available on widgets. Use it to verify the proof in your backend before success is emitted. * If `handleVerify` throws/rejects, the widget enters an error state, emits `onError("failed_by_host_app")`, and does not call `onSuccess`. This means World App already returned a proof, but your callback or backend did not accept it. * `onError` is optional. ## Headless hooks ### `useIDKitRequest` ```tsx theme={"system"} import { useIDKitRequest, orbLegacy } from "@worldcoin/idkit"; const flow = useIDKitRequest({ app_id: "app_xxxxx", action: "my-action", rp_context, allow_legacy_proofs: true, preset: orbLegacy({ signal: "user-123" }), polling: { interval: 2_000, timeout: 120_000, }, }); ; ``` Hook result fields: * `open()` * `reset()` * `isAwaitingUserConnection` * `isAwaitingUserConfirmation` * `isSuccess` * `isError` * `connectorURI` * `result` * `errorCode` * `getDebugReport()` ## Invite-code mode For invite-code flows, use `IDKitInviteCodeRequestWidget` (controlled) or `useIDKitInviteCodeRequest` (headless). Config matches `IDKitRequestWidget` / `useIDKitRequest` — invite-code mode adds no new required fields. See [Invite-code mode](/world-id/idkit/verification-flows#with-invite-code-mode) for when to use it. ```tsx theme={"system"} import { IDKitInviteCodeRequestWidget, selfieCheckLegacy } from "@worldcoin/idkit"; { /* ... */ }} onSuccess={() => { /* ... */ }} />; ``` `useIDKitInviteCodeRequest` result fields (sibling of `useIDKitRequest`'s): * `open()` * `reset()` * `isAwaitingUserConnection` * `isAwaitingUserConfirmation` * `isSuccess` * `isError` * `connectorURI` * `codeExpiresAt` * `result` * `errorCode` * `getDebugReport()` ### Migrating from QR / connect-URL ```tsx theme={"system"} // Before — QR / connect-URL widget import { IDKitRequestWidget, orbLegacy } from "@worldcoin/idkit"; ; ``` ```tsx theme={"system"} // After — invite-code widget import { IDKitInviteCodeRequestWidget, selfieCheckLegacy } from "@worldcoin/idkit"; ; ``` Swap the component and preset. Props, callbacks, and backend verification stay the same. The headless equivalent is `useIDKitInviteCodeRequest` in place of `useIDKitRequest`. ## Session flows Session verification uses `IDKitSessionWidget` (controlled) or `useIDKitSession` (headless); `onSuccess` returns an `IDKitResultSession`. Both share the request flows' widget callbacks and hook-result fields — including `onError(errorCode, debugReport?)` and `getDebugReport()`. ## Presets React hooks/widgets take `preset` directly in config. ## Localization and UX notes * Widgets support `language="en" | "es" | "th"` * Widgets default to `autoClose={true}` after success ## Backend utilities For RP signature generation in React/Next.js apps, use the pure JS subpath: ```ts theme={"system"} import { signRequest } from "@worldcoin/idkit/signing"; ``` See [RP Signatures](/world-id/idkit/signatures) for the full algorithm and test vectors. # RP Signatures Source: https://docs.world.org/world-id/idkit/signatures Spec for generating RP signatures, with pseudocode, SDK examples, and test vectors. Relying Party (RP) signatures prove a proof request comes from your app, preventing impersonation attacks. Your backend signs every request with the `signing_key` from the [Developer Portal](https://developer.world.org), and World ID verifies the signature before generating a proof. World ID enforces RP signatures for [World ID 4.0 requests](/world-id/4-0-migration). Never expose your signing key to client-side code. If the key leaks, rotate it immediately in the Developer Portal. ## Algorithm ```text title="Implement it yourself" theme={"system"} // IMPORTANT: Use Keccak-256, NOT SHA3-256. They have different padding. // Most Ethereum libraries (ethers, viem, web3) use Keccak-256. function hash_to_field(input_bytes) -> bytes32: h = keccak256(input_bytes) // 32 bytes n = big_endian_uint256(h) >> 8 // shift right 8 bits return uint256_to_32bytes_be(n) // always starts with 0x00 function compute_rp_signature_message(nonce_bytes32, created_at_u64, expires_at_u64, action?) -> bytes: size = 81 if action else 49 msg = new bytes(size) msg[0] = 0x01 // version byte msg[1..32] = nonce_bytes32 // 32-byte field element msg[33..40] = u64_to_be(created_at) // big-endian uint64 msg[41..48] = u64_to_be(expires_at) // big-endian uint64 if action is not null: msg[49..80] = hash_to_field(utf8_encode(action)) return msg function sign_request(signing_key_hex, action?, ttl_seconds = 300): // Accept signing keys with or without 0x prefix key = parse_hex_32_bytes(signing_key_hex) // 1. Generate nonce random = crypto_random_bytes(32) nonce_bytes = hash_to_field(random) // 2. Timestamps created_at = unix_time_seconds() expires_at = created_at + ttl_seconds // 3. Build message msg = compute_rp_signature_message(nonce_bytes, created_at, expires_at, action) // 4. EIP-191 prefix and hash // The prefix uses the DECIMAL byte length of the message (e.g. "49" or "81") prefix = "\x19Ethereum Signed Message:\n" + decimal_string(length(msg)) digest = keccak256(prefix + msg) // 5. Sign with recoverable ECDSA (secp256k1) (r, s, recovery_id) = ecdsa_secp256k1_sign(digest, key) // 6. Encode: r(32) || s(32) || v(1), where v = recovery_id + 27 sig65 = r + s + byte(recovery_id + 27) return { sig: "0x" + hex(sig65), nonce: "0x" + hex(nonce_bytes), created_at: created_at, expires_at: expires_at, } ``` ```typescript title="JavaScript / TypeScript" theme={"system"} // Also available from @worldcoin/idkit/signing and @worldcoin/idkit-core/signing import { signRequest } from "@worldcoin/idkit-server"; const sig = signRequest({ signingKeyHex: process.env.RP_SIGNING_KEY!, action: "my-action", ttl: 300, // optional, default 300s }); // sig = { sig, nonce, createdAt, expiresAt } ``` ```go title="Go" theme={"system"} import "github.com/worldcoin/idkit/go/idkit" // One-shot signing with options sig, err := idkit.SignRequest( os.Getenv("RP_SIGNING_KEY"), idkit.WithAction("my-action"), idkit.WithTTL(300), // optional, default 300s ) // Or create a reusable signer for high-throughput backends signer, err := idkit.NewSigner(os.Getenv("RP_SIGNING_KEY")) sig, err = signer.SignRequest(idkit.WithAction("my-action")) ``` ## Test vectors Verify your implementation against these. All vectors use deterministic inputs. ### `hash_to_field` ```text title="empty string" theme={"system"} input: "" (empty) output: 0x00c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4 ``` ```text title='"test_signal"' theme={"system"} input: "test_signal" output: 0x00c1636e0a961a3045054c4d61374422c31a95846b8442f0927ad2ff1d6112ed ``` ```text title="raw bytes" theme={"system"} input: [0x01, 0x02, 0x03] output: 0x00f1885eda54b7a053318cd41e2093220dab15d65381b1157a3633a83bfd5c92 ``` ```text title='"hello"' theme={"system"} input: "hello" (0x68656c6c6f) output: 0x001c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36dea ``` ### `compute_rp_signature_message` ```text title="without action (49 bytes)" theme={"system"} compute_rp_signature_message( nonce = 0x008ae1aa597fa146ebd3aa2ceddf360668dea5e526567e92b0321816a4e895bd, created_at = 1700000000, expires_at = 1700000300, ) output: 01008ae1aa597fa146ebd3aa2ceddf360668dea5e526567e92b0321816a4e895bd000000006553f100000000006553f22c ``` ```text title='with action "test-action" (81 bytes)' theme={"system"} compute_rp_signature_message( nonce = 0x008ae1aa597fa146ebd3aa2ceddf360668dea5e526567e92b0321816a4e895bd, created_at = 1700000000, expires_at = 1700000300, action = "test-action", ) output: 01008ae1aa597fa146ebd3aa2ceddf360668dea5e526567e92b0321816a4e895bd000000006553f100000000006553f22c00aa0ce59768ae5b1c52f07a9387f14f09f277422c0d2f8a268c7bad0c60a46a ``` ### `sign_request` ```text title="without action (session proof)" theme={"system"} sign_request( signing_key = 0xabababababababababababababababababababababababababababababababab, random = [0x00, 0x01, ..., 0x1f], // deterministic for testing created_at = 1700000000, // fixed clock for testing ttl = 300, ) nonce: 0x008ae1aa597fa146ebd3aa2ceddf360668dea5e526567e92b0321816a4e895bd msg length: 49 bytes sig: 0x14f693175773aed912852a601e9c0fd30f2afe2738d31388316232ce6f64ae9e4edbfb19d81c4229ba9c9fca78ede4b28956b7ba4415f08d957cbc1b3bdaa4021b ``` ```text title='with action "test-action" (uniqueness proof)' theme={"system"} sign_request( signing_key = 0xabababababababababababababababababababababababababababababababab, action = "test-action", random = [0x00, 0x01, ..., 0x1f], // deterministic for testing created_at = 1700000000, // fixed clock for testing ttl = 300, ) nonce: 0x008ae1aa597fa146ebd3aa2ceddf360668dea5e526567e92b0321816a4e895bd msg length: 81 bytes sig: 0x05594adb6c1495768a38d523d7d6ee6356b2c31231919198794ed022ade7d08f73753f83bd167067d99c9b969d28e9222315837c66af25867b041273a6d5056f1b ``` ## Related pages * [Integration guide](/world-id/idkit/integrate) * [JavaScript SDK reference](/world-id/idkit/javascript) * [Go SDK reference](/world-id/idkit/go) # Swift Source: https://docs.world.org/world-id/idkit/swift Reference for the IDKit Swift API: request builders, presets, and polling. Use `IDKit` for native iOS/macOS integrations backed by the same Rust core as other SDKs. ## Requirements * iOS 15+ / macOS 12+ * Xcode 16+ ## Install Use Swift Package Manager with the published `idkit-swift` repository: ```swift theme={"system"} .package(url: "https://github.com/worldcoin/idkit-swift.git", from: "") ``` ## Request flow ```swift theme={"system"} import IDKit let rpContext = try RpContext( rpId: "rp_xxxxx", nonce: backend.nonce, createdAt: backend.createdAt, expiresAt: backend.expiresAt, signature: backend.sig ) let config = IDKitRequestConfig( appId: "app_xxxxx", action: "my-action", rpContext: rpContext, allowLegacyProofs: true, returnTo: "myapp://verify-done", // Optional: mobile deep-link callback URL environment: .production ) let request = try IDKit.request(config: config).preset(orbLegacy(signal: "user-123")) let connectURL = request.connectorURL let requestID = request.requestID ``` ## Presets ```swift theme={"system"} import IDKit let request = try IDKit.request(config: config).preset( orbLegacy(signal: "user-123") ) ``` ## Polling API * `pollStatusOnce() async -> IDKitStatus` * `pollUntilCompletion(options:) async -> IDKitCompletionResult` * `IDKitPollOptions(pollIntervalMs:timeoutMs:)` ```swift theme={"system"} let completion = await request.pollUntilCompletion( options: IDKitPollOptions(pollIntervalMs: 2_000, timeoutMs: 120_000) ) switch completion { case .success(let result): print(result) case .failure(let error): print(error) } ``` ## Invite-code mode Use `presetWithInviteCode(_:)` on the builder to return an `IDKitInviteCodeRequest` instead of `IDKitRequest`. The polling surface is identical. See [Invite-code mode](/world-id/idkit/verification-flows#with-invite-code-mode) for when to use it. ```swift theme={"system"} let request = try IDKit.request(config: config) .presetWithInviteCode(selfieCheckLegacy(signal: "user-123")) let connectorURL = request.connectorURL // URL with &c=&a= let expiresAt = request.expiresAt // Date let completion = await request.pollUntilCompletion() ``` ### Migrating from QR / connect-URL ```swift theme={"system"} // Before — QR / connect-URL flow let request = try IDKit.request(config: config) .preset(orbLegacy(signal: "user-123")) let connectURL = request.connectorURL // render as QR let completion = await request.pollUntilCompletion() ``` ```swift theme={"system"} // After — invite-code mode let request = try IDKit.request(config: config) .presetWithInviteCode(selfieCheckLegacy(signal: "user-123")) let connectorURL = request.connectorURL // display to user (URL with code embedded) let expiresAt = request.expiresAt // drive a countdown let completion = await request.pollUntilCompletion() ``` The config object is unchanged. The `connectorURL` now includes `&c=&a=` params; use it alongside `expiresAt` in your UI. Polling, proof verification, and nullifier storage stay the same. `IDKitInviteCodeRequest` exposes: * `connectorURL: URL` * `expiresAt: Date` * `requestID: String` * `pollStatusOnce() async -> IDKitStatus` * `pollUntilCompletion(options:) async -> IDKitCompletionResult` # Verification Flows Source: https://docs.world.org/world-id/idkit/verification-flows The flows your integration can land users in when you request a World ID proof. When your app requests a World ID proof, the user lands in one of three flows, determined by whether they have World ID installed and — if not — whether they already have a World ID. The integration flow is the same for all three paths, but the user experience differs. | Flow | World ID installed | Existing account | What happens | | --------- | ------------------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Hot | Yes | — | World ID opens, shows a proof consent, and the user approves — typically under 10 seconds. If the user lacks the requested [credential](/world-id/idkit/credentials), World ID walks them through enrollment first; your integration sees no difference. | | Cold | No | No | The user installs World ID and completes account onboarding first. The experience differs by platform — see below. | | Semi-cold | No | Yes | The user installs World ID and logs in to their existing account. **Android:** the flow resumes automatically. **iOS:** the user must return to your app and rescan, unless you use invite-code mode — see below. | ## Cold and semi-cold flows Both flows require an install and behave differently per platform because of how each handles **deferred deep linking** — preserving a link's context through an app store install so the app can act on it at first launch. ### Android Android supports deferred deep linking through the Play Store. After downloading World ID, the user creates an account (cold) or logs in to an existing one (semi-cold), and World ID resumes the verification flow automatically. ```mermaid theme={"system"} sequenceDiagram participant App as Your App participant User participant Play as Play Store participant WA as World ID App->>User: IDKit request (connect URL / QR) User->>Play: Download World ID Play-->>WA: Deferred deep link preserved WA->>User: Account onboarding OR Login opt Credential not yet held WA->>User: Credential enrollment end WA->>User: Proof consent User->>WA: Approve WA-->>App: Proof returned ``` ### iOS iOS does not support deferred deep linking through the App Store. Install loses the original verification context — for both cold and semi-cold users — so resuming the flow requires additional mechanisms. #### Default behavior 1. The user downloads World ID from the App Store and creates an account or logs in to an existing one. 2. The user returns to your app, which re-triggers the IDKit verification request (e.g., by rescanning the QR code). 3. World ID opens and takes the user through the standard in-app flow. 4. The proof consent appears, the user approves, and the proof returns to your app. ```mermaid theme={"system"} sequenceDiagram participant App as Your App participant User participant Store as App Store participant WA as World ID App->>User: Prompt to install World ID User->>Store: Download World ID WA->>User: Account onboarding OR Login User->>App: Return to your app App->>User: IDKit request (rescan QR / connect URL) WA->>User: Standard in-app flow WA-->>App: Proof returned ``` #### With invite-code mode Invite-code mode shows a 6-character code in your app that the user enters into World ID. World ID treats the code as an entry point to the in-app onboarding flows the user must complete to satisfy your IDKit request, then returns the proof. Invite-code mode exists because iOS lacks deferred deep linking — Android preserves the context via the Play Store. 1. Your app triggers an IDKit invite-code request. 2. Your app opens the URL that IDKit provides. One of three paths follows: * **User has World ID (mobile):** World ID launches directly via deep link. * **User has World ID (desktop):** The user scans the QR code with World ID. * **User needs to install World ID:** The user installs World ID, completes onboarding or logs in, then enters the invite code to resume the request. 3. World ID restores the verification context, walks the user through credential enrollment if needed, and presents a proof consent. 4. The user approves and the proof returns to your app. The 6-character code persists across the App Store install, so once World ID is installed and onboarded the user can resume the flow without first returning to your app. This also covers cross-device scenarios (e.g., a desktop browser showing the QR for the user's phone) where deep linking cannot carry context. **Demo**
**Flow diagram** ```mermaid theme={"system"} sequenceDiagram participant App as Your App participant User participant LP as Landing Page participant Store as App Store participant WA as World ID App->>LP: Open IDKit-provided URL LP->>User: Display invite code + QR User->>Store: Download World ID WA->>User: Account onboarding OR Login User->>WA: Enter invite code WA->>WA: Resolve code & restore context WA->>User: Credential enrollment WA->>User: Proof consent User->>WA: Approve WA-->>App: Proof returned ```
**Lifecycle** * Codes expire after a short TTL (currently fifteen minutes). * Codes are one-shot — once redeemed, they cannot be reused. Re-running the request returns a fresh code with a fresh TTL. * After the user redeems the code, your existing poll loop receives the proof exactly as in QR mode. **Integrate** Setup is identical to the [standard integration](/world-id/idkit/integrate) — only the request call changes. Only the `selfieCheckLegacy` preset is supported for Selfie Check (Beta) today. It returns World ID 3.0 proofs; Selfie Check with World ID 4.0 is not available yet. For code samples and migration guides, see the per-SDK sections: [JavaScript](/world-id/idkit/javascript#invite-code-mode), [React](/world-id/idkit/react#invite-code-mode), [Swift](/world-id/idkit/swift#invite-code-mode). # Overview Source: https://docs.world.org/world-id/overview ## World ID World ID is a privacy-preserving protocol that lets people prove they are real and unique online without sharing personal information. It gives developers a high-assurance trust layer to stop bots, duplicate accounts, and abuse — while keeping onboarding fast and user data off your servers. Through credentials like Proof of Human, Document, and Selfie Check, World ID extends that trust layer with additional proofs without exposing underlying user data. ```tsx theme={"system"} import { IDKitRequestWidget } from "@worldcoin/idkit"; const rpContext = await getRpContext("verify-account"); ``` ## Credentials ## Start building ## Privacy by architecture # Authenticator Reference Source: https://docs.world.org/world-id/reference/authenticator ## Authenticator Requests & Responses This section describes how authenticators receive and respond to requests from RPs. This also reflects what is implemented in the . The authenticator receives a request from an RP in the following format. ### Request Schema The schema for the request is defined in the `world-id-primitives` crate as [`ProofRequest`](https://docs.rs/world-id-primitives/latest/world_id_primitives/request/struct.ProofRequest.html). ### Request Examples ```json theme={"system"} { "id": "req_18c0f7f03e7d", "version": 1, "created_at": "1771612953", "expires_at": "1771613013", "rp_id": "rp_0000000000000000000000000000000000001", "action": "0x0000000000000000000000000000000000000000000000000000000000000001", "nonce": "0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2", "signature": "304502205cce35752b1642327bebf9f203960dc83f92fa919a4567981ce0c157060ca04e022100f328e42ff2609ddc1fbc7da17896ded2687acb3a4eeb8847a6f3a87ce50ed016", "requests": [ { "identifier": "passport", "issuer_schema_id": 9303, "signal": "abcd-efgh-ijkl" } ] } ``` ```json theme={"system"} { "id": "req_18c0f7f03e7d", "version": 1, "created_at": "1771612953", "expires_at": "1771613013", "rp_id": "rp_0000000000000000000000000000000000001", "action": "0x0000000000000000000000000000000000000000000000000000000000000001", "nonce": "0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2", "signature": "304502205cce35752b1642327bebf9f203960dc83f92fa919a4567981ce0c157060ca04e022100f328e42ff2609ddc1fbc7da17896ded2687acb3a4eeb8847a6f3a87ce50ed016", "requests": [ { "identifier": "passport", "issuer_schema_id": 9303, "signal": "abcd-efgh-ijkl" }, { "identifier": "poh", "issuer_schema_id": 1, "signal": "abcd-efgh-ijkl" } ], "constraints": { "all": ["passport", "poh"] } } ``` ```json theme={"system"} { "id": "req_18c0f7f03e7d", "version": 1, "created_at": "1771612953", "expires_at": "1771613013", "rp_id": "rp_0000000000000000000000000000000000001", "action": "0x0000000000000000000000000000000000000000000000000000000000000001", "nonce": "0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2", "signature": "304502205cce35752b1642327bebf9f203960dc83f92fa919a4567981ce0c157060ca04e022100f328e42ff2609ddc1fbc7da17896ded2687acb3a4eeb8847a6f3a87ce50ed016", "requests": [ { "identifier": "passport", "issuer_schema_id": 9303, "signal": "abcd-efgh-ijkl" }, { "identifier": "my-number-card", "issuer_schema_id": 9310, "signal": "mnop-qrst-uvwx" }, { "identifier": "orb", "issuer_schema_id": 1, "signal": "abcd-efgh-ijkl" } ], "constraints": { "all": [ "orb", { "any": ["passport", "my-number-card"] } ] } } ``` ### Constraint Evaluation When using the `any` constraint, the order of credential types in the array determines **priority order**. The authenticator will attempt to provide the first available credential type in the list. If that credential is not available, it will fall back to the next type in the array, and so on. Priority ordering only applies to `any` constraints. For `all` constraints, all specified credential types must be provided regardless of order. **Example**: ```json theme={"system"} { "constraints": { "any": ["poh", "passport"] } } ``` In this case: * If the user has an PoH credential, the authenticator will provide it. * If the user does not have an PoH credential but has a passport credential, the authenticator will provide the passport instead. * The PoH credential type has priority over passport. This priority mechanism allows RPs to request their preferred credential type while still accepting fallback options if the preferred type is unavailable. ### Response Schema The schema for the response is defined in the `world-id-primitives` crate as [`ProofResponse`](https://docs.rs/world-id-primitives/latest/world_id_primitives/request/struct.ProofResponse.html). ### Response Examples ```json theme={"system"} { "id": "req_18c0f7f03e7d", "version": 1, "responses": [ { "identifier": "orb", "issuer_schema_id": 1, "proof": "0x0000000000000000000000000000000000000000000000000000000000000000000000000", "nullifier": "nil_00000000000000000000000000000000000000000000000001" } ] } ``` ```json theme={"system"} { "id": "req_18c0f7f03e7d", "version": 1, "error": "constraints_not_satisfied", "responses": [] } ``` ```json theme={"system"} { "id": "req_18c0f7f03e7d", "version": 1, "responses": [ { "identifier": "orb", "issuer_schema_id": 1, "proof": "0x0000000000000000000000000000000000000000000000000000000000000000000000000", "session_nullifier": "00000000000000000000000000000000000000000000000001" } ] } ``` # Contracts 3.0 Source: https://docs.world.org/world-id/reference/contracts World ID 3.0 smart contracts overview: supported chains, architecture, World ID Router verifyProof, and sybil resistance. This page documents World ID 3.0 (legacy) smart contracts. For the latest on-chain verification guide including World ID 4.0, see the [On-chain Verification page](/world-id/idkit/onchain-verification) and the [World ID 4.0 Migration guide](/world-id/4-0-migration). All of our smart contracts are available on GitHub: * [World ID Smart Contracts](https://github.com/worldcoin/world-id-contracts) * [State Bridge Smart Contracts](https://github.com/worldcoin/world-id-state-bridge) If you're interested in using World ID and verifying proofs on-chain, see our [On-Chain Verification guide](/world-id/idkit/onchain-verification). ## Supported Chains
Chain Testnet Role Identity Availability
World Chain World Chain World Chain Bridged \~5 Minutes after Ethereum
Ethereum Ethereum Sepolia Canonical \~60 minutes after verification
Optimism Optimism Optimism Sepolia Bridged \~5 Minutes after Ethereum
Polygon Polygon Polygon Bridged \~40 Minutes after Ethereum
Base Base Base Bridged \~5 Minutes after Ethereum
Find our smart contract addresses in the [On-chain Verification guide](/world-id/idkit/onchain-verification). ## 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
Parameter Type Description
root uint256 The World ID root to verify against.
groupId uint256 Determines which Credential Type to verify against. As only Orb credentials are supported on-chain, this must be 1.
signalHash uint256 The keccak256 hash of the signal to verify.
nullifierHash uint256 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.
externalNullifierHash uint256 The keccak256 hash of the externalNullifier to verify. The externalNullifier is computed from the app\_id and action.
proof uint256\[8] 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.
#### Example: groupId ```solidity title="Orb-Only groupId" theme={"system"} uint256 internal immutable groupId = 1; ``` #### Example: signalHash ```solidity title="signalHash" theme={"system"} abi.encodePacked(signal).hashToField(); ``` #### Example: externalNullifierHash ```solidity title="externalNullifierHash" theme={"system"} externalNullifier = abi.encodePacked(abi.encodePacked(appId).hashToField(), action) externalNullifierHash = externalNullifier.hashToField(); ``` Read more about the External Nullifier in Protocol Internals. #### Example: Unpacking Proof ```ts title="viem" theme={"system"} import { decodeAbiParameters } from 'viem' const unpackedProof = decodeAbiParameters([{ type: 'uint256[8]' }], proof)[0] ``` ```ts title="ethers.js" theme={"system"} import { defaultAbiCoder as abi } from '@ethers/utils' const unpackedProof = abi.decode(['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 `nullifier` of each user that has successfully verified a proof. * When a user attempts to verify a proof, check that the `nullifier` is not already in the list of used `nullifier`s. Here's an example function doing the above. You can also use the [World ID starter kits](/world-id/idkit/onchain-verification) to get started with sybil resistance. ```solidity theme={"system"} /// @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 nullifier The nullifier 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 nullifier, uint256[8] calldata proof ) public { // First make sure this person hasn't done this before if (nullifiers[nullifier]) revert InvalidNullifier(); // Verify the provided proof is valid and the user is verified by World ID worldId.verifyProof( root, groupId, abi.encodePacked(signal).hashToField(), nullifier, externalNullifierHash, proof ); // Record the user has done this, so they can't do it again (sybil-resistance) nullifiers[nullifier] = true; // Finally, execute your logic here, for example issue a token, NFT, etc... } ``` # NFC Issuer Source: https://docs.world.org/world-id/reference/nfc-issuer NFC issuer endpoints, including migration for World ID v3 NFC credentials. The NFC issuer exposes endpoints for issuing and migrating NFC credentials. For product-level credential semantics, Sybil-resistance guarantees, and validity details, see [NFC Credential](/world-id/credentials/9303). The base URL is environment-specific. Contact your World ID point of contact for environment endpoints and access. ## Migration This endpoint is intended for World ID v3 holders to obtain v4 NFC credentials. It is not a general re-issuance endpoint for v4 holders. /v2/migrate **Content-Type:** `application/json` ### Request #### Headers
Header Required Description
x-zkp-proof
string
yes Base64-encoded JSON containing the ZKP proof and identity commitment.
attestation-gateway-token
string
yes Attestation gateway token for device integrity verification.
#### Body fields
Field Required Description
identityCommitment
string
yes The holder's identity commitment (decimal or hex with 0x prefix). Must match the ZKP header.
sub
string
yes World ID 4.0 blinded subject identifier (hex with 0x prefix, 256-bit). Must match previous migrations for this identity.
credential
object
yes Flow-specific credential data (see below). The client decrypts PCP data locally before submission.
#### Credential fields The `credential` object contains data extracted and decrypted from the user's Personal Custody Package (PCP) by the client.
Field Description
credential.sod
string
Base64-encoded SOD (Security Object Document) in DER format from the identity document.
credential.verification\_metadata
string
Base64-encoded verification metadata from the original document check.
```json theme={"system"} { "identityCommitment": "0x000000000000000000000000000000000000000000000000000000000000000c", "sub": "0x000000000000000000000000000000000000000000000000000000000000002a", "credential": { "sod": "", "verification_metadata": "" } } ```
Field Description
credential.sd\_jwt
string
SD-JWT (Selective Disclosure JWT) from the MNC verification flow.
```json theme={"system"} { "identityCommitment": "0x000000000000000000000000000000000000000000000000000000000000000c", "sub": "0x000000000000000000000000000000000000000000000000000000000000002a", "credential": { "sd_jwt": "" } } ```
### Response #### Success response ```json theme={"system"} { "result": { "credential": "" } } ``` #### Error responses | Status | Error | Description | | ------ | ------------------ | --------------------------------------------------------------------------- | | 400 | `invalid_data` | Request payload is malformed or missing required fields. | | 400 | `sub_mismatch` | `sub` does not match the one used in previous migrations for this identity. | | 400 | `document_expired` | The identity document has expired and cannot be used for migration. | | 401 | `unauthorized` | Authentication failed. | | 404 | `not_found` | No matching enrollment record found for this credential. | ## Planned Endpoints ### Enrollment Coming soon Details coming soon. ### Re-issuance Coming soon Details coming soon. ## Implementation Notes This is advanced documentation about the internal workings of the NFC Credential and is not relevant for RP integration. ### Associated Data The associated data of this credential contains different data groups found in the original document. More information coming soon. #### Associated Data Commitment To ensure the associated data has guaranteed integrity and can be re-used in the future for credential re-issuance, the commitment is computed as follows: 1. For ICAO-9303 documents, the message digest of the `EF.SOD` signature is used. The message digest is obtained from `SignedData.SignerInfos[0].SignedAttrs` where the signed attribute for the digest is identified by the Object Identifier `1.2.840.113549.1.9.4`. This digest is then hashed with the `blake3` hashing function from the raw bytes. Finally, the `blake3` hash is converted to a field element with modulo reduction. 2. Information on MNC documents coming soon. # PoH Issuer Source: https://docs.world.org/world-id/reference/poh-issuer PoH issuer endpoints, including credential refresh for v3 to v4 migration. The PoH issuer provides endpoints that issue proof-of-human (PoH) credentials. The main endpoint is credential refresh, which re-issues a PoH credential using a Personal Custody Package (PCP) or a credential-only refresh flow. Base URL is environment-specific and served by the signup-service app-api. Contact your World ID point of contact for environment endpoints and access. The refresh endpoint is intended for World ID v3 holders to obtain v4 PoH credentials. It is not a general re-issuance endpoint for v4 holders. ## Credential refresh /api/v1/refresh **Content-Type:** `multipart/form-data` ## Request ### Headers | Header | Type | Required | Description | | ------------- | -------- | -------- | -------------------------------------------------------------- | | `x-zkp-proof` | `string` | yes | Base64-encoded ZKP string containing `idCommitment` and `sub`. | ### Query parameters | Query | Type | Required | Description | | -------- | -------- | -------- | ------------------------- | | `idComm` | `string` | yes | User identity commitment. | ### Form fields (always required) | Field | Type | Required | Description | | ------------------- | -------- | -------- | ---------------------------------------------------------------------------------------- | | `sub` | `string` | yes | User account ID (hex with `0x` prefix). Must match previous refreshes for this `idComm`. | | `encrypted_user_id` | `string` | no | Temporary field while operator rewards depend on it. | ### PCP form fields (required only when submitting a PCP) Include all fields below when refreshing with a Personal Custody Package. | Field | Type | Description | | ----------------------- | -------- | -------------------------- | | `signup_id` | `string` | Signup ID. | | `signup_id_salt` | `string` | Signup ID salt. | | `orb_id` | `string` | Orb ID. | | `orb_id_salt` | `string` | Orb ID salt. | | `operator_id` | `string` | Operator ID. | | `operator_id_salt` | `string` | Operator ID salt. | | `signup_reason` | `string` | Signup reason. | | `signup_reason_salt` | `string` | Signup reason salt. | | `timestamp` | `string` | Timestamp. | | `timestamp_salt` | `string` | Timestamp salt. | | `software_version` | `string` | Software version. | | `software_version_salt` | `string` | Software version salt. | | `orb_country` | `string` | Orb country. | | `orb_country_salt` | `string` | Orb country salt. | | `iris_code_shares_0` | `string` | Iris code share 0. | | `iris_code_shares_1` | `string` | Iris code share 1. | | `iris_code_shares_2` | `string` | Iris code share 2. | | `hashes.json` | `file` | PCP hashes JSON file. | | `hashes.sign` | `file` | PCP hashes signature file. | ### Example (credential-only refresh) ```bash theme={"system"} curl -X POST "https:///api/v1/refresh?idComm=0xabc123..." \ -H "x-zkp-proof: " \ -F "sub=0x1a2b3c" ``` ### Example (refresh with PCP) ```bash theme={"system"} curl -X POST "https:///api/v1/refresh?idComm=0xabc123..." \ -H "x-zkp-proof: " \ -F "sub=0x1a2b3c" \ -F "signup_id=signup_123" \ -F "signup_id_salt=..." \ -F "orb_id=orb_abc" \ -F "orb_id_salt=..." \ -F "operator_id=operator_123" \ -F "operator_id_salt=..." \ -F "signup_reason=..." \ -F "signup_reason_salt=..." \ -F "timestamp=1700000000" \ -F "timestamp_salt=..." \ -F "software_version=1.2.3" \ -F "software_version_salt=..." \ -F "orb_country=US" \ -F "orb_country_salt=..." \ -F "iris_code_shares_0=..." \ -F "iris_code_shares_1=..." \ -F "iris_code_shares_2=..." \ -F "hashes.json=@hashes.json" \ -F "hashes.sign=@hashes.sign" ``` ## Response ### Success response ```json theme={"system"} { "success": true, "credential": "", "message": "Credential refreshed successfully" } ``` ### Error responses | Status | Error | Description | | ------ | --------------------- | ---------------------------------------------------------- | | 400 | `INVALID_SUB` | `sub` is missing or invalid. | | 400 | `SUB_MISMATCH` | `sub` does not match the one used in previous refreshes. | | 404 | `NO_SIGNUP_RECORD` | No enrollment record found. User must re-enroll at an Orb. | | 429 | `RATE_LIMIT_EXCEEDED` | Refresh rate limit exceeded for the current window. | | 503 | - | Credential refresh is disabled. | If PCP validation fails, the endpoint returns an error status with `PCP_VALIDATION_FAILED`. ## Credential object format The `credential` response field is a base64-encoded JSON representation of the World ID `Credential` object defined in `world-id-protocol/crates/primitives/src/credential.rs`. ### Example (decoded) ```json theme={"system"} { "id": 123456789, "version": "V1", "issuer_schema_id": 42, "sub": "", "genesis_issued_at": 1733241600, "expires_at": 1764777600, "claims": ["", "", ""], "associated_data_hash": "", "signature": "", "issuer": { "pk": ["", ""] } } ``` ### Field definitions | Field | Type | Description | | ---------------------- | ---------------- | ------------------------------------------------------------------------------------------------------- | | `id` | `uint64` | Issuer-scoped reference identifier for the credential. | | `version` | `string` | Credential version. Current value is `V1`. | | `issuer_schema_id` | `uint64` | Identifier for the (issuer, schema) pair registered in `CredentialSchemaIssuerRegistry`. | | `sub` | `FieldElement` | Blinded subject identifier derived from the World ID leaf index and an issuer-specific blinding factor. | | `genesis_issued_at` | `uint64` | Unix timestamp (seconds) of the first issuance of this credential. | | `expires_at` | `uint64` | Unix timestamp (seconds) for expiration. | | `claims` | `FieldElement[]` | Up to 16 claim commitments. Unused indices are the zero field element. | | `associated_data_hash` | `FieldElement` | Poseidon2 hash of issuer-defined associated data. The associated data itself is not included. | | `signature` | `string` | 64-byte compressed EdDSA signature over the credential hash, hex-encoded (128 hex chars, no `0x`). | | `issuer` | `EdDSAPublicKey` | Issuer public key that signed the credential. | ### Field representations * **FieldElement** values (`sub`, `claims`, `associated_data_hash`) are hex strings with a `0x` prefix and 64 hex characters. * **Issuer public key** (`issuer.pk`) is serialized as `[x, y]` decimal strings for BabyJubJub affine coordinates. * **Signature** is hex-encoded compressed bytes (no `0x` prefix). ### PoH credential semantics * The user first obtains an Orb credential (currently a PCP in v2.3 format). * `claim[0]` is a commitment to the Orb credential, currently `H(hashes.json)`. * When refreshing without a PCP, `claims[0]` is derived from issuer-defined refresh data (a hash of `idCommitment`, `sub`, and a timestamp). * The PoH credential has no associated data, so `associated_data_hash` is the zero field element. * The PoH credential subject is blinded and differs from the Orb credential subject. * The issuer may require a proof for the requested `sub` to prevent bricking an identity. These details are issuer-specific and may evolve as the protocol migrates to the World ID 4.0 credential format. ## References * [World ID 4.0 specs](https://github.com/worldcoin/world-id-protocol/tree/main/docs/world-id-4-specs) # How to get access Source: https://docs.world.org/world-id/sandbox/sandbox-access Install the sandbox World ID app and point your integration at the Sandbox environment. Getting set up in Sandbox has two parts: install the **sandbox World ID app** on your test device, and point your **integration** at the Sandbox environment. Both are covered below. New here? Start with [What is Sandbox?](/world-id/sandbox/what-is-sandbox) for context. ## 1. Install the sandbox World ID app The sandbox apps are not publicly listed in the App Store or on Google Play. You install them through the testing links below. ### iOS — TestFlight The sandbox build is distributed through TestFlight, and gated behind an enrollment request. #### Request tester access 1. Install [TestFlight](https://apps.apple.com/app/testflight/id899247664) from the App Store if you don't have it. 2. In the [Developer Portal](https://developer.world.org), select **World ID Sandbox** from the sidebar, choose the **iOS** tab, and submit the Apple Account email you want enrolled. Enrollment is tied to a team, so open the sandbox panel from within a team. 3. Wait for approval. #### Install the build 1. Once your request is approved, you'll get a TestFlight invitation email. 2. Accept the invitation. 3. Install it. #### Troubleshooting FAQ * Confirm the email you submitted is the Apple Account you'll use to sign in to TestFlight, not any other email. * If your request was rejected or your access was revoked, submitting the same email again won't create a new request. Contact Sandbox Support at [sandbox.access@toolsforhumanity.org](mailto:sandbox.access@toolsforhumanity.org). ### Android — Google Play The sandbox build is distributed through a **private testing track on Google Play**. #### Request tester access 1. In the [Developer Portal](https://developer.world.org), select **World ID Sandbox** from the sidebar. 2. Enter the email address for the Google Account you use with the Google Play Store, then request tester access. 3. Wait until access is granted before opening the Google Play testing link. #### Install the build 1. On your Android device, open the Google Play Store and confirm that it is signed in with the exact Google Account for which you requested tester access. 2. If this is the account's first Google Play Store sign-in, complete the initial setup and accept all Google Play Terms of Service before continuing. 3. In the browser that will open the testing link, sign in to the same Google Account. The browser and Google Play Store must use the same account; otherwise, Google Play may report that the build is unavailable. 4. Return to **World ID Sandbox** in the [Developer Portal](https://developer.world.org). On your Android device, scan the QR code or open the Google Play testing link. 5. Install the **World ID (Sandbox)** build from Google Play. #### Troubleshooting FAQ * Confirm that the email used to request tester access matches the Google Account used by both the browser and Google Play Store. * If the account has just signed in to Google Play for the first time, Google may need time to recognize the new account. Wait at least **15 minutes** before opening the testing link again. ## 2. Point your integration at Sandbox 1. **Update IDKit** to the latest version. 2. **Set `environment: sandbox`** in your IDKit configuration. 3. To verify a proof, send it to the production verify endpoint (`https://developer.world.org/api/v4/verify/${rp_id}`). Nothing else is required — the handoff will open the sandbox World ID app and return a proof to your session over the bridge. ## Things to know * **Sandbox apps aren't publicly listed in the app stores.** Testers install via TestFlight (iOS) or a private Google Play testing link (Android). Because access is gated to testers, install links and store acquisition can behave differently from a public production listing. * **Proofs are non-production.** Identities and proofs issued in Sandbox are for integration validation only. * **Accounts are resettable.** You can delete and recreate accounts freely while testing. ## Next step Once you're installed and pointed at Sandbox, you're ready to run the Selfie Check integration flows. See [Testing Selfie Check in Sandbox](/world-id/sandbox/testing-selfie-check) for coverage, critical user journeys, and known limitations. # Testing Selfie Check (Beta) in Sandbox Source: https://docs.world.org/world-id/sandbox/testing-selfie-check Coverage, critical user journeys, and known limitations for testing your Selfie Check (Beta) integration in Sandbox. Sandbox lets you run the full [Selfie Check (Beta)](/world-id/credentials/11) relying-party journey end-to-end — from your surface, through IDKit, into the sandbox World ID app, and back — without touching production identities or real proofs. Selfie Check (Beta) must be enabled for your app before you can test it. To enable the feature flag, request access through your World point of contact. New to Sandbox? Start with [What is Sandbox?](/world-id/sandbox/what-is-sandbox) and [How to get access](/world-id/sandbox/sandbox-access) before working through this guide. In scope: the full relying-party journey — request handoff, consent, capture, enrollment and matching, proof generation, and delivery of the proof. Out of scope: production identity data and real-world uniqueness at scale. Sandbox accounts and proofs are for integration testing only, not load testing, security certification, or production sign-off. ## Coverage Testing is organized by entry surface and user state, using the same [Hot, Cold, and Semi-cold states](/world-id/idkit/verification-flows) as the rest of World ID: * **Hot** — the user already has World ID installed. If they're already Selfie Check enrolled, they go straight to face match; if not, World ID walks them through enrollment first, then match. (Selfie Check has no distinct Warm flow — enrollment happens inline within Hot, same as [Verification Flows](/world-id/idkit/verification-flows) describes.) * **Cold** — a new user with no World ID app: the full funnel, including install, account creation, date of birth, invite code (iOS), enrollment, and Selfie Check. * **Semi-cold** — an existing user without World ID on this device: reinstall and account recovery, then Selfie Check. | Entry surface | State | What it exercises | | ------------- | --------- | --------------------------------------------------------------------------------------------------- | | Native app | Hot | Same-device: validated user presents a face credential; deep link into World ID and back. | | Native app | Cold | Install → create account → add date of birth → redeem invite code → enroll → complete Selfie Check. | | Native app | Semi-cold | Existing user reinstalls and recovers their account on a fresh device, then completes Selfie Check. | | Web app | Hot | Cross-device: start on web, complete on phone via QR scan, proof returns to the web session. | | Web app | Cold | Same cold funnel, cross-device via QR; proof returns to the originating web session. | | Web app | Semi-cold | Same semi-cold recovery, cross-device via QR. | ## Known limitations * **Sandbox apps aren't publicly listed in the app stores.** Testers install through TestFlight on iOS or a private Google Play testing link on Android. Because those testing programs gate access, app-store acquisition can differ from a public production listing — see [How to get access](/world-id/sandbox/sandbox-access#things-to-know). * **iOS Semi-cold is currently limited.** The reinstall/login journey reliably works on Android today. On iOS, if the user taps "Sign in" instead of "Sign up" mid-flow, there's no path to add the invite code — they have to restart from a fresh QR or deep link. Expect iOS Semi-cold to behave differently from Android until this is closed. * **Invite-code handling in the Cold flow differs by platform.** Confirm how invite codes are presented and redeemed on the platform you're targeting — see [invite-code mode](/world-id/idkit/verification-flows#with-invite-code-mode). ## Next step Questions, or found a journey these scenarios don't cover? Reach out to your World point of contact. # What is Sandbox? Source: https://docs.world.org/world-id/sandbox/what-is-sandbox An isolated, production-like environment for testing your World ID integration end-to-end. Sandbox is a dedicated, production-like environment for building and testing your World ID integration. It runs alongside Production as its own environment, with its own backend and its own builds of the World ID app for iOS and Android, built from the same codebase and release process as Production — so Sandbox is just as stable and reliable. It lets you run a complete integration — from your app or website, through IDKit, into the World ID app, and back — without touching production identities or issuing real proofs. ## Why use Sandbox Sandbox is built for integration testing. It gives you: * **A full end-to-end round trip.** Sandbox is a valid IDKit destination, so you can drive a real request from your surface into the World ID app and receive a proof back over the bridge — the same path your production integration will use. * **Resettable accounts.** Delete an account and sign up again as often as you need. * **Simulated verification.** Exercise verification flows without real hardware or real-world credentials. * **Controllable gating.** You decide whether the system enforces fraud, risk, and attestation checks. Turn enforcement off to move quickly through integration testing, or turn it on to validate how your integration behaves under production-like gating. ## What you can test The full relying-party journey from your surface into the World ID app and back: request handoff, consent, capture, enrollment and matching, proof generation, and delivery of the proof to your session. Both same-device (deep link) and cross-device (QR) flows are supported, on both iOS and Android sandbox builds. ## What Sandbox is not * **Not production.** Accounts and proofs issued in Sandbox are for integration validation only — not load testing, security certification, or production sign-off. * **Not a source of real uniqueness.** Sandbox does not represent real-world identity or uniqueness at scale. * **Not publicly listed in the app stores.** The iOS sandbox app is available through TestFlight, while Android is distributed through a private Google Play testing track. See [How to get access](/world-id/sandbox/sandbox-access) for how to install them. ## Next step Ready to try it? Head to [How to get access](/world-id/sandbox/sandbox-access) to install the sandbox app and point your integration at Sandbox. # Understanding when to use World ID Source: https://docs.world.org/world-id/yc World ID is a privacy-preserving trust layer for companies that need to know whether a real, unique, present, or eligible human is behind an action. ## Example scenarios Enforce one-person, one-action on rewards, referrals, voting, reviews, promotions, and social participation. **Integrate Proof of Human** Require human presence when someone approves a payment, recovers an account, deploys software, or authorizes a high-risk transaction. **Add Selfie Check** Confirm someone meets a policy without asking your product to store their full identity documents. **Explore Identity Check** Distinguish people from agents, and make human approval an explicit boundary for consequential actions. **Integrate AgentKit** Reduce impersonation, fake accounts, reputation manipulation, and fraudulent content while preserving privacy. **Integrate Proof of Human** ## Where it fits in YC startups (based on Fall 2026 RFS guidelines) | If you're building... | The simplest place to use World ID | | ------------------------------------------ | -------------------------------------------------------------------------------------------------- | | Proving you're human | Require one human per account to reduce Sybil attacks. | | An AI consumer product | Protect a free trial, referral, reward, or limited promotion from duplicate claims. | | Multiplayer AI | Distinguish verified people from AI agents in a shared environment. | | An operating system for the physical world | Make sure tasks are assigned to real, unique workers. | | A real-world data product | Limit data contribution and rewards to one unique contributor. | | AI-native compliance | Add a privacy-preserving proof that a user is a real human before a regulated or high-risk action. | | Crypto or stablecoins | Run fair allocations, grants, governance, airdrops, or incentives. | | Cloud tools for small software | Reduce fake workspaces, spam, and abuse before privileged access. | | Education | Protect scholarships, subsidized access, rewards, and student benefits. | | Products for the aging population | Protect patients, family members, and caregivers from impersonation. | | Self-maintaining APIs | Add human accountability before critical code changes, merges, or deployments. | | Defense or physical infrastructure | Add a human trust signal to safety-critical operations. | ## Ready to build? [Start integrating World ID](/world-id/idkit/integrate) to bring trust to your product.