-
Notifications
You must be signed in to change notification settings - Fork 1
feat!: World ID 3.0 compatibility #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,20 +13,18 @@ pnpm add @worldcoin/idkit | |
| ## Quick Start | ||
|
|
||
| ```typescript | ||
| import { | ||
| useWorldBridgeStore, | ||
| VerificationLevel, | ||
| } from '@worldcoin/idkit' | ||
| import { useWorldBridgeStore } from '@worldcoin/idkit' | ||
|
|
||
| // 1. Get store instance | ||
| const store = useWorldBridgeStore() | ||
|
|
||
| // 2. Create client - returns immutable client object | ||
| // 2. Create client with explicit requests | ||
| const client = await store.createClient({ | ||
| app_id: 'app_staging_xxxxx', | ||
| action: 'my-action', | ||
| verification_level: VerificationLevel.Orb, | ||
| signal: 'user-id-123', | ||
| requests: [ | ||
| { credential_type: 'orb', signal: 'user-id-123' }, | ||
| ], | ||
| }) | ||
|
|
||
| // 3. Display QR code for World App | ||
|
|
@@ -41,38 +39,60 @@ try { | |
| } | ||
| ``` | ||
|
|
||
| ## V2 API | ||
| ## Multiple Credential Types | ||
|
|
||
| ```typescript | ||
| const store = useWorldBridgeStore() | ||
| You can request verification with multiple credential types. The user can satisfy the request with any of them: | ||
|
|
||
| await store.createClient({ | ||
| ```typescript | ||
| const client = await store.createClient({ | ||
| app_id: 'app_staging_xxxxx', | ||
| action: 'my-action', | ||
| verification_level: VerificationLevel.Orb, | ||
| signal: 'user-id-123', | ||
| requests: [ | ||
| { credential_type: 'orb', signal: 'user-id-123' }, | ||
| { credential_type: 'device', signal: 'user-id-123' }, | ||
| ], | ||
| }) | ||
| ``` | ||
|
|
||
| console.log('Scan this:', store.connectorURI) | ||
|
|
||
| await store.pollForUpdates() | ||
| ## Credential Types | ||
|
|
||
| if (store.result) { | ||
| console.log('Proof received:', store.result) | ||
| } | ||
| ``` | ||
| - `orb` - Verified via Orb biometric scan (highest trust) | ||
| - `face` - Verified via Face ID | ||
| - `device` - Verified via device binding | ||
| - `document` - Verified via document scan | ||
| - `secure_document` - Verified via secure document scan | ||
|
|
||
| ## API Reference | ||
|
|
||
| ### Creating a Client | ||
|
|
||
| ```typescript | ||
| const client = await store.createClient(config) | ||
| ``` | ||
|
|
||
| **Properties:** | ||
| **Config:** | ||
| ```typescript | ||
| interface IDKitConfig { | ||
| app_id: `app_${string}` // Your World ID app ID | ||
| action: string // Action identifier | ||
| requests: RequestConfig[] // Required: credential type requests | ||
| bridge_url?: string // Custom bridge URL (optional) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lol |
||
| partner?: boolean // Partner mode (optional) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we can refactor this too. |
||
| } | ||
|
|
||
| interface RequestConfig { | ||
| credential_type: CredentialType | ||
| signal?: string | AbiEncodedValue // Optional signal for this request | ||
| } | ||
|
|
||
| type CredentialType = 'orb' | 'face' | 'device' | 'document' | 'secure_document' | ||
| ``` | ||
|
|
||
| **Client Properties:** | ||
| - `connectorURI: string` - QR code URL for World App | ||
| - `requestId: string` - Unique request ID | ||
|
|
||
| **Methods:** | ||
| **Client Methods:** | ||
| - `pollForUpdates(options?: WaitOptions): Promise<ISuccessResult>` - Poll for proof (auto-polls) | ||
| - `pollOnce(): Promise<Status>` - Poll once for status (manual polling) | ||
|
|
||
|
|
@@ -91,44 +111,24 @@ interface WaitOptions { | |
| const store = useWorldBridgeStore() | ||
| ``` | ||
|
|
||
| **V3 Methods:** | ||
| **Methods:** | ||
| - `createClient(config: IDKitConfig): Promise<WorldBridgeClient>` - Create new client | ||
| - `reset(): void` - Clear state and start over | ||
|
|
||
| **V2 State (backward compat):** | ||
| **State (for reactive frameworks):** | ||
| - `verificationState: VerificationState` - Current verification state | ||
| - `connectorURI: string | null` - QR code URL for World App | ||
| - `result: ISuccessResult | null` - Proof data when verified | ||
| - `errorCode: AppErrorCodes | null` - Error code if failed | ||
|
|
||
| **V2 Methods (deprecated):** | ||
| - `pollForUpdates(): Promise<void>` - Check for proof (call repeatedly) ⚠️ Use `client.pollForUpdates()` with auto-polling instead | ||
| - `reset(): void` - Clear state and start over | ||
|
|
||
| ### Types | ||
| ### Result Types | ||
|
|
||
| ```typescript | ||
| interface IDKitConfig { | ||
| app_id: `app_${string}` | ||
| action: string | ||
| signal?: string | ||
| verification_level?: VerificationLevel | ||
| bridge_url?: string | ||
| partner?: boolean | ||
| } | ||
|
|
||
| interface ISuccessResult { | ||
| proof: string | ||
| merkle_root: string | ||
| nullifier_hash: string | ||
| verification_level: VerificationLevel | ||
| } | ||
|
|
||
| enum VerificationLevel { | ||
| Orb = 'orb', | ||
| Face = 'face', | ||
| Device = 'device', | ||
| Document = 'document', | ||
| SecureDocument = 'secure_document', | ||
| verification_level: CredentialType // The credential type used | ||
| } | ||
| ``` | ||
|
|
||
|
|
@@ -142,6 +142,31 @@ hashToField(input: string): HashFunctionOutput | |
| solidityEncode(types: string[], values: unknown[]): AbiEncodedValue | ||
| ``` | ||
|
|
||
| ## React Integration | ||
|
|
||
| ```tsx | ||
| import { useWorldBridgeStore, IDKitWidget } from '@worldcoin/idkit' | ||
|
|
||
| function MyComponent() { | ||
| const handleSuccess = (result) => { | ||
| console.log('Verified:', result) | ||
| } | ||
|
|
||
| return ( | ||
| <IDKitWidget | ||
| app_id="app_staging_xxxxx" | ||
| action="my-action" | ||
| requests={[ | ||
| { credential_type: 'orb', signal: 'user-123' }, | ||
| ]} | ||
| onSuccess={handleSuccess} | ||
| > | ||
| {({ open }) => <button onClick={open}>Verify with World ID</button>} | ||
| </IDKitWidget> | ||
| ) | ||
| } | ||
| ``` | ||
|
|
||
| ## Examples | ||
|
|
||
| See [examples/browser](../../examples/browser) for a complete working example. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,16 +4,11 @@ | |
| */ | ||
|
|
||
| import { create, type StateCreator } from 'zustand' | ||
| import type { IDKitConfig, VerificationLevel } from './types/config' | ||
| import type { IDKitConfig } from './types/config' | ||
| import type { ISuccessResult } from './types/result' | ||
| import { VerificationState, AppErrorCodes, ResponseStatus } from './types/bridge' | ||
| import { validate_bridge_url } from './lib/validation' | ||
| import { encodeAction, generateSignal } from './lib/hashing' | ||
| import { | ||
| DEFAULT_VERIFICATION_LEVEL, | ||
| credential_type_to_verification_level, | ||
| verification_level_to_credential_types, | ||
| } from './lib/utils' | ||
| import { encodeAction } from './lib/hashing' | ||
| import { WasmModule, initIDKit } from './lib/wasm' | ||
| import { WorldBridgeClient } from './client' | ||
|
|
||
|
|
@@ -29,11 +24,6 @@ type BridgeResponse = | |
| response: { iv: string; payload: string } | ||
| } | ||
|
|
||
| type BridgeResult = | ||
| | ISuccessResult | ||
| | (Omit<ISuccessResult, 'verification_level'> & { credential_type: VerificationLevel }) | ||
| | { error_code: AppErrorCodes } | ||
|
|
||
| export type WorldBridgeStore = { | ||
| bridge_url: string | ||
| encryption: typeof WasmModule.BridgeEncryption.prototype | null | ||
|
|
@@ -60,10 +50,15 @@ const createStoreImplementation: StateCreator<WorldBridgeStore> = (set, get) => | |
| bridge_url: DEFAULT_BRIDGE_URL, | ||
| verificationState: VerificationState.PreparingClient, | ||
|
|
||
| createClient: async ({ bridge_url, app_id, verification_level, action, signal, requests, constraints, action_description }) => { | ||
| createClient: async ({ bridge_url, app_id, action, requests, constraints, action_description }) => { | ||
| // Ensure WASM is initialized | ||
| await initIDKit() | ||
|
|
||
| // Validate requests | ||
| if (!requests || requests.length === 0) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like we should just decide on the policy/requests thing we are talking about a decision 1 day away |
||
| throw new Error('At least one request is required') | ||
| } | ||
|
|
||
| // Validate bridge URL | ||
| if (bridge_url) { | ||
| const validation = validate_bridge_url(bridge_url, app_id.includes('staging')) | ||
|
|
@@ -74,38 +69,22 @@ const createStoreImplementation: StateCreator<WorldBridgeStore> = (set, get) => | |
| } | ||
| } | ||
|
|
||
| let session: WasmModule.Session | ||
|
|
||
| if (requests && requests.length > 0) { | ||
| if (verification_level) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do think this warn is good. To say if they try to request a type of credential that won't be backwards comaptible |
||
| console.warn('`verification_level` is ignored when `requests` are provided. Use one or the other.') | ||
| } | ||
| const reqs = (requests as any[]).map((req) => ({ | ||
| credential_type: req.credential_type ?? req.credentialType ?? req.credential, | ||
| signal: typeof req.signal === 'string' ? req.signal : undefined, | ||
| signal_bytes: req.signal_bytes ?? req.signalBytes, | ||
| face_auth: req.face_auth ?? req.faceAuth, | ||
| })) | ||
|
|
||
| const constraintsPayload = constraints ? constraints : undefined | ||
|
|
||
| session = (await WasmModule.Session.createWithRequests( | ||
| app_id, | ||
| encodeAction(action), | ||
| reqs, | ||
| constraintsPayload ?? undefined, | ||
| action_description ?? null, | ||
| bridge_url ?? null | ||
| )) as unknown as WasmModule.Session | ||
| } else { | ||
| // Create WASM Session from verification level | ||
| session = (await new WasmModule.Session( | ||
| app_id, | ||
| encodeAction(action), | ||
| verification_level ?? DEFAULT_VERIFICATION_LEVEL, | ||
| signal ? generateSignal(signal).digest : null | ||
| )) as WasmModule.Session | ||
| } | ||
| // Map requests to WASM format | ||
| const reqs = requests.map((req) => ({ | ||
| credential_type: req.credential_type, | ||
| signal: typeof req.signal === 'string' ? req.signal : undefined, | ||
| signal_bytes: req.signal_bytes, | ||
|
Comment on lines
+72
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In Useful? React with 👍 / 👎.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will address in another PR |
||
| face_auth: req.face_auth, | ||
| })) | ||
|
|
||
| const session = (await WasmModule.Session.createWithRequests( | ||
| app_id, | ||
| encodeAction(action), | ||
| reqs, | ||
| constraints ?? undefined, | ||
| action_description ?? null, | ||
| bridge_url ?? null | ||
| )) as unknown as WasmModule.Session | ||
|
|
||
| const client = new WorldBridgeClient(session) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: not sure requests is the right word here. Maybe challenges?