Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions js/examples/browser/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ <h3>Error</h3>

<script type="module">
// Import IDKit (alias resolves to local dist or npm package)
import { useWorldBridgeStore, VerificationLevel } from '@worldcoin/idkit';
import { useWorldBridgeStore } from '@worldcoin/idkit';

// Demo app configuration (WASM auto-initializes on first use)
const APP_ID = 'app_ce4cb73cb75fc3b73b71ffb4de178410'; // Replace with your actual app ID
Expand All @@ -62,8 +62,8 @@ <h3>Error</h3>
errorDiv.hidden = true;
};

// Function to verify with World ID
async function verify(verificationLevel) {
// Function to verify with World ID using explicit requests
async function verify(requests) {
try {
hideAll();

Expand All @@ -73,12 +73,11 @@ <h3>Error</h3>
statusDiv.hidden = false;
statusText.textContent = 'Creating verification request...';

// createClient returns a client with the connectorURI
// createClient accepts explicit requests array
const client = await store.createClient({
app_id: APP_ID,
action: ACTION,
verification_level: verificationLevel,
signal: 'demo-signal-' + Date.now(),
requests: requests,
Copy link
Collaborator

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?

});

// Use client.connectorURI (not store.connectorURI which is a stale snapshot)
Expand Down Expand Up @@ -111,10 +110,21 @@ <h3>Error</h3>
}
}

// Button event listeners
document.getElementById('verifyOrb').addEventListener('click', () => verify(VerificationLevel.Orb));
document.getElementById('verifyFace').addEventListener('click', () => verify(VerificationLevel.Face));
document.getElementById('verifyDevice').addEventListener('click', () => verify(VerificationLevel.Device));
// Helper to create a signal with timestamp
const makeSignal = () => 'demo-signal-' + Date.now();

//TODO: Will refactor this
// Button event listeners - using explicit requests pattern
document.getElementById('verifyOrb').addEventListener('click', () =>
verify([{ credential_type: 'orb', signal: makeSignal() }])
);
document.getElementById('verifyFace').addEventListener('click', () =>
verify([{ credential_type: 'face', signal: makeSignal() }])
);
document.getElementById('verifyDevice').addEventListener('click', () =>
verify([{ credential_type: 'device', signal: makeSignal() }])
);

</script>
</body>
</html>
119 changes: 72 additions & 47 deletions js/packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol

partner?: boolean // Partner mode (optional)
Copy link
Collaborator

Choose a reason for hiding this comment

The 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)

Expand All @@ -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
}
```

Expand All @@ -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.
Expand Down
9 changes: 1 addition & 8 deletions js/packages/core/src/__tests__/smoke.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import {
buffer_decode,
isNode,
isWeb,
VerificationLevel,
AppErrorCodes,
VerificationState,
} from '../index'
import type { CredentialType } from '../index'

describe('WASM Initialization', () => {
it('should initialize WASM via initIDKit', async () => {
Expand Down Expand Up @@ -175,13 +175,6 @@ describe('Bridge Store', () => {
})

describe('Enums', () => {
it('should export VerificationLevel enum', () => {
expect(VerificationLevel.Orb).toBe('orb')
expect(VerificationLevel.Face).toBe('face')
expect(VerificationLevel.Device).toBe('device')
expect(VerificationLevel.SecureDocument).toBe('secure_document')
expect(VerificationLevel.Document).toBe('document')
})

it('should export AppErrorCodes enum', () => {
expect(AppErrorCodes.ConnectionFailed).toBe('connection_failed')
Expand Down
69 changes: 24 additions & 45 deletions js/packages/core/src/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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
Expand All @@ -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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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'))
Expand All @@ -74,38 +69,22 @@ const createStoreImplementation: StateCreator<WorldBridgeStore> = (set, get) =>
}
}

let session: WasmModule.Session

if (requests && requests.length > 0) {
if (verification_level) {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve ABI-encoded request signals

In createClient you only forward req.signal when it is a string. RequestConfig.signal still allows AbiEncodedValue, but those values are dropped here, so the request is sent without a signal. With verification_level/top-level signal removed in this commit, there is no remaining path to pass ABI‑encoded signals, which means proofs will be bound to an empty signal and on‑chain verification that expects the ABI signal will fail. Consider converting AbiEncodedValue to signal_bytes (or rejecting non‑string signals unless signal_bytes is provided).

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)

Expand Down
Loading