From 250c22a9f80d6971b31ef83523c76c3a445c7876 Mon Sep 17 00:00:00 2001 From: Takis Kakalis <80459599+Takaros999@users.noreply.github.com> Date: Thu, 14 May 2026 11:59:09 +0900 Subject: [PATCH] feat(sdk): expose 4.0 selfie credential --- js/examples/nextjs/app/ui.tsx | 4 +- js/packages/core/src/__tests__/smoke.test.ts | 8 +-- js/packages/core/src/request.ts | 18 +++---- .../core/src/transports/native.test.ts | 6 +-- .../main/kotlin/com/worldcoin/idkit/IdKit.kt | 5 +- rust/core/src/bridge.rs | 50 ++++++++++++++++++- rust/core/src/constraints.rs | 46 ++++++++--------- rust/core/src/types.rs | 30 +++++------ rust/core/src/wasm_bindings.rs | 14 +++--- swift/Sources/IDKit/IDKit.swift | 9 ++-- 10 files changed, 120 insertions(+), 70 deletions(-) diff --git a/js/examples/nextjs/app/ui.tsx b/js/examples/nextjs/app/ui.tsx index 21dc9408..630169ec 100644 --- a/js/examples/nextjs/app/ui.tsx +++ b/js/examples/nextjs/app/ui.tsx @@ -33,10 +33,11 @@ const RETURN_TO_TOOLTIP = type PresetKind = "orb" | "secure_document" | "document" | "device" | "selfie"; -type V4CredentialType = "proof_of_human" | "passport" | "mnc"; +type V4CredentialType = "proof_of_human" | "selfie" | "passport" | "mnc"; const V4_CREDENTIAL_TO_NAME: Record = { proof_of_human: "Proof of Human", + selfie: "Selfie", passport: "Passport", mnc: "MNC", }; @@ -402,6 +403,7 @@ export function DemoClient(): ReactElement { } > + diff --git a/js/packages/core/src/__tests__/smoke.test.ts b/js/packages/core/src/__tests__/smoke.test.ts index 39f75fc8..a151f60c 100644 --- a/js/packages/core/src/__tests__/smoke.test.ts +++ b/js/packages/core/src/__tests__/smoke.test.ts @@ -61,16 +61,16 @@ describe("IDKitRequest API", () => { it("should create any() constraint correctly", () => { const poh = CredentialRequest("proof_of_human"); - const face = CredentialRequest("face"); - const constraint = any(poh, face); + const selfie = CredentialRequest("selfie"); + const constraint = any(poh, selfie); expect(constraint).toHaveProperty("any"); expect(constraint.any).toHaveLength(2); }); it("should create enumerate() constraint correctly", () => { const poh = CredentialRequest("proof_of_human"); - const face = CredentialRequest("face"); - const constraint = enumerate(poh, face); + const selfie = CredentialRequest("selfie"); + const constraint = enumerate(poh, selfie); expect(constraint).toHaveProperty("enumerate"); expect(constraint.enumerate).toHaveLength(2); }); diff --git a/js/packages/core/src/request.ts b/js/packages/core/src/request.ts index bb48a7ec..3033693c 100644 --- a/js/packages/core/src/request.ts +++ b/js/packages/core/src/request.ts @@ -211,14 +211,14 @@ class IDKitInviteCodeRequestImpl implements IDKitInviteCodeRequest { /** * Creates a CredentialRequest for a credential type * - * @param credential_type - The type of credential to request (e.g., 'proof_of_human', 'face') + * @param credential_type - The type of credential to request (e.g., 'proof_of_human', 'selfie') * @param options - Optional signal, genesis_issued_at_min, and expires_at_min * @returns A CredentialRequest object * * @example * ```typescript * const orb = CredentialRequest('proof_of_human', { signal: 'user-123' }) - * const face = CredentialRequest('face') + * const selfie = CredentialRequest('selfie') * // Require credential to be valid for at least one year * const withExpiry = CredentialRequest('proof_of_human', { expires_at_min: Date.now() / 1000 + 60 * 60 * 60 * 24 * 365 }) * ``` @@ -247,7 +247,7 @@ export function CredentialRequest( * * @example * ```typescript - * const constraint = any(CredentialRequest('proof_of_human'), CredentialRequest('face')) + * const constraint = any(CredentialRequest('proof_of_human'), CredentialRequest('selfie')) * ``` */ export function any(...nodes: ConstraintNode[]): { any: ConstraintNode[] } { @@ -501,7 +501,7 @@ class IDKitBuilder { * @example * ```typescript * const request = await IDKit.request({ app_id, action, rp_context, allow_legacy_proofs: false }) - * .constraints(any(CredentialRequest('proof_of_human'), CredentialRequest('face'))); + * .constraints(any(CredentialRequest('proof_of_human'), CredentialRequest('selfie'))); * ``` */ async constraints(constraints: ConstraintNode): Promise { @@ -642,7 +642,7 @@ class IDKitInviteCodeBuilder { * @example * ```typescript * const request = await IDKit.requestWithInviteCode({ app_id, action, rp_context, allow_legacy_proofs: false }) - * .constraints(any(CredentialRequest('proof_of_human'), CredentialRequest('face'))); + * .constraints(any(CredentialRequest('proof_of_human'), CredentialRequest('selfie'))); * displayLink(request.connectorURI); * ``` */ @@ -716,7 +716,7 @@ class IDKitInviteCodeBuilder { * action: 'my-action', * rp_context: { ... }, * allow_legacy_proofs: false, - * }).constraints(enumerate(CredentialRequest('proof_of_human'), CredentialRequest('face'))); + * }).constraints(enumerate(CredentialRequest('proof_of_human'), CredentialRequest('selfie'))); * * // In World App: connectorURI is empty, result comes via postMessage * // On web: connectorURI is the QR URL to display @@ -774,7 +774,7 @@ function createRequest(config: IDKitRequestConfig): IDKitBuilder { * action: 'my-action', * rp_context: { ... }, * allow_legacy_proofs: false, - * }).constraints(any(CredentialRequest('proof_of_human'), CredentialRequest('face'))); + * }).constraints(any(CredentialRequest('proof_of_human'), CredentialRequest('selfie'))); * * displayLink(request.connectorURI); // user opens this URL on their phone * const proof = await request.pollUntilCompletion(); @@ -835,7 +835,7 @@ function createRequestWithInviteCode( * const request = await IDKit.createSession({ * app_id: 'app_staging_xxxxx', * rp_context: { ... }, - * }).constraints(any(CredentialRequest('proof_of_human'), CredentialRequest('face'))); + * }).constraints(any(CredentialRequest('proof_of_human'), CredentialRequest('selfie'))); * * // Display QR, wait for proof * const result = await request.pollUntilCompletion(); @@ -885,7 +885,7 @@ function createSession(config: IDKitSessionConfig): IDKitBuilder { * const request = await IDKit.proveSession(savedSessionId, { * app_id: 'app_staging_xxxxx', * rp_context: { ... }, - * }).constraints(any(CredentialRequest('proof_of_human'), CredentialRequest('face'))); + * }).constraints(any(CredentialRequest('proof_of_human'), CredentialRequest('selfie'))); * * const result = await request.pollUntilCompletion(); * // result.session_id -> same session diff --git a/js/packages/core/src/transports/native.test.ts b/js/packages/core/src/transports/native.test.ts index 619ae6a7..f16f32be 100644 --- a/js/packages/core/src/transports/native.test.ts +++ b/js/packages/core/src/transports/native.test.ts @@ -117,7 +117,7 @@ describe("native transport request lifecycle", () => { it("uses per-identifier signal hashes when response omits signal_hash", async () => { const signalHashes = { proof_of_human: hashSignal("poh-signal"), - face: hashSignal("face-signal"), + selfie: hashSignal("selfie-signal"), }; const req = createNativeRequest({}, baseConfig, signalHashes, ""); @@ -139,7 +139,7 @@ describe("native transport request lifecycle", () => { expires_at_min: 0, }, { - identifier: "face", + identifier: "selfie", proof: proofResponseProof, nullifier: proofResponseNullifier("b"), issuer_schema_id: 11, @@ -156,7 +156,7 @@ describe("native transport request lifecycle", () => { signalHashes.proof_of_human, ); expect(completion.result.responses[1]?.signal_hash).toBe( - signalHashes.face, + signalHashes.selfie, ); } }); diff --git a/kotlin/bindings/src/main/kotlin/com/worldcoin/idkit/IdKit.kt b/kotlin/bindings/src/main/kotlin/com/worldcoin/idkit/IdKit.kt index 5257b678..5b8591ab 100644 --- a/kotlin/bindings/src/main/kotlin/com/worldcoin/idkit/IdKit.kt +++ b/kotlin/bindings/src/main/kotlin/com/worldcoin/idkit/IdKit.kt @@ -126,9 +126,8 @@ data class IDKitPollOptions( class IDKitBuilder internal constructor( private val inner: IdKitBuilder, ) { - // TODO: Re-enable when World ID 4.0 is live - // fun constraints(constraints: ConstraintNode): IDKitRequest = - // IDKitRequest(inner.constraints(constraints)) + fun constraints(constraints: uniffi.idkit_core.ConstraintNode): IDKitRequest = + IDKitRequest(inner.constraints(constraints)) fun preset(preset: Preset): IDKitRequest = IDKitRequest(inner.preset(preset)) diff --git a/rust/core/src/bridge.rs b/rust/core/src/bridge.rs index add776bf..ab36b71a 100644 --- a/rust/core/src/bridge.rs +++ b/rust/core/src/bridge.rs @@ -1810,6 +1810,54 @@ mod tests { assert!(json.contains("allow_legacy_proofs")); } + #[test] + fn test_build_request_payload_serializes_selfie_v4_request() { + let app_id = AppId::new("app_test").unwrap(); + let signature = "0x".to_string() + &"00".repeat(64) + "1b"; + let rp_context = RpContext::new( + "rp_1234567890abcdef", + "0x0000000000000000000000000000000000000000000000000000000000000001", + 1_700_000_000, + 1_700_003_600, + &signature, + ) + .unwrap(); + let constraints = ConstraintNode::item(CredentialRequest::new( + CredentialType::Selfie, + Some(Signal::from_string("selfie-signal")), + )); + + let params = BridgeConnectionParams { + app_id, + kind: RequestKind::Uniqueness { + action: "test-action".to_string(), + }, + constraints: Some(constraints), + rp_context, + action_description: Some("Selfie check".to_string()), + legacy_verification_level: VerificationLevel::Device, + legacy_signal: String::new(), + bridge_url: None, + allow_legacy_proofs: false, + override_connect_base_url: None, + return_to: None, + environment: Some(Environment::Production), + identity_attributes: None, + }; + + let payload = build_request_payload(¶ms, false).unwrap(); + let proof_request = payload.get("proof_request").unwrap(); + assert_eq!( + proof_request["proof_requests"][0]["identifier"], + serde_json::json!("selfie") + ); + assert_eq!( + proof_request["proof_requests"][0]["issuer_schema_id"], + serde_json::json!(11) + ); + assert_eq!(payload["verification_level"], serde_json::json!("device")); + } + #[test] fn test_build_request_payload_serializes_identity_attributes() { let app_id = AppId::new("app_test").unwrap(); @@ -2312,7 +2360,7 @@ mod tests { ); assert_eq!( CredentialType::from_issuer_schema_id(11), - Some(CredentialType::Face) + Some(CredentialType::Selfie) ); assert_eq!( CredentialType::from_issuer_schema_id(9303), diff --git a/rust/core/src/constraints.rs b/rust/core/src/constraints.rs index c1785525..65f1d056 100644 --- a/rust/core/src/constraints.rs +++ b/rust/core/src/constraints.rs @@ -384,8 +384,8 @@ mod tests { CredentialRequest::new(CredentialType::ProofOfHuman, None) } - fn face_item() -> CredentialRequest { - CredentialRequest::new(CredentialType::Face, None) + fn selfie_item() -> CredentialRequest { + CredentialRequest::new(CredentialType::Selfie, None) } fn passport_item() -> CredentialRequest { @@ -413,32 +413,32 @@ mod tests { fn test_any_node() { let node = ConstraintNode::any(vec![ ConstraintNode::item(poh_item()), - ConstraintNode::item(face_item()), + ConstraintNode::item(selfie_item()), ConstraintNode::item(passport_item()), ]); let mut available = HashSet::new(); - available.insert(CredentialType::Face); + available.insert(CredentialType::Selfie); available.insert(CredentialType::Passport); assert!(node.evaluate(&available)); - // Should return Face because it's first in priority order + // Should return Selfie because it's first in priority order assert_eq!( node.first_satisfying(&available), - Some(CredentialType::Face) + Some(CredentialType::Selfie) ); } #[test] fn test_any_node_priority() { - // ProofOfHuman has highest priority, Face second + // ProofOfHuman has highest priority, Selfie second let node = ConstraintNode::any(vec![ ConstraintNode::item(poh_item()), - ConstraintNode::item(face_item()), + ConstraintNode::item(selfie_item()), ]); let mut available = HashSet::new(); - available.insert(CredentialType::Face); + available.insert(CredentialType::Selfie); available.insert(CredentialType::ProofOfHuman); // Even though both are available, ProofOfHuman should be selected (higher priority) @@ -452,7 +452,7 @@ mod tests { fn test_all_node() { let node = ConstraintNode::all(vec![ ConstraintNode::item(poh_item()), - ConstraintNode::item(face_item()), + ConstraintNode::item(selfie_item()), ]); let mut available = HashSet::new(); @@ -461,7 +461,7 @@ mod tests { // Only one is available, should fail assert!(!node.evaluate(&available)); - available.insert(CredentialType::Face); + available.insert(CredentialType::Selfie); // Both available, should succeed assert!(node.evaluate(&available)); @@ -471,7 +471,7 @@ mod tests { #[test] fn test_enumerate_node() { let node = ConstraintNode::enumerate(vec![ - ConstraintNode::item(face_item()), + ConstraintNode::item(selfie_item()), ConstraintNode::item(passport_item()), ]); @@ -511,17 +511,17 @@ mod tests { fn test_face_orb_example() { let node = ConstraintNode::any(vec![ ConstraintNode::item(poh_item()), - ConstraintNode::item(face_item()), + ConstraintNode::item(selfie_item()), ]); let mut available = HashSet::new(); - available.insert(CredentialType::Face); + available.insert(CredentialType::Selfie); - // Only face available + // Only selfie available assert!(node.evaluate(&available)); assert_eq!( node.first_satisfying(&available), - Some(CredentialType::Face) + Some(CredentialType::Selfie) ); // Both available - proof_of_human has priority @@ -555,7 +555,7 @@ mod tests { let node = ConstraintNode::any(vec![ ConstraintNode::item(poh_item()), ConstraintNode::all(vec![ - ConstraintNode::item(face_item()), + ConstraintNode::item(selfie_item()), ConstraintNode::item(passport_item()), ]), ]); @@ -563,7 +563,7 @@ mod tests { let credentials = node.collect_credential_types(); assert_eq!(credentials.len(), 3); assert!(credentials.contains(&CredentialType::ProofOfHuman)); - assert!(credentials.contains(&CredentialType::Face)); + assert!(credentials.contains(&CredentialType::Selfie)); assert!(credentials.contains(&CredentialType::Passport)); } @@ -572,7 +572,7 @@ mod tests { let node = ConstraintNode::any(vec![ ConstraintNode::item(poh_item()), ConstraintNode::all(vec![ - ConstraintNode::item(face_item()), + ConstraintNode::item(selfie_item()), ConstraintNode::item(passport_item()), ]), ]); @@ -597,7 +597,7 @@ mod tests { fn test_serialization() { let node = ConstraintNode::any(vec![ ConstraintNode::item(poh_item()), - ConstraintNode::item(face_item()), + ConstraintNode::item(selfie_item()), ]); let json = serde_json::to_string(&node).unwrap(); @@ -615,7 +615,7 @@ mod tests { // Test any constraint let node = ConstraintNode::any(vec![ ConstraintNode::item(poh_item()), - ConstraintNode::item(face_item()), + ConstraintNode::item(selfie_item()), ]); let (items, expr) = node.to_protocol().unwrap(); @@ -623,7 +623,7 @@ mod tests { let json = serde_json::to_string(&expr).unwrap(); assert!(json.contains("any")); assert!(json.contains("proof_of_human")); - assert!(json.contains("face")); + assert!(json.contains("selfie")); } #[test] @@ -656,7 +656,7 @@ mod tests { // Multiple items should have constraint expression let node = ConstraintNode::any(vec![ ConstraintNode::item(poh_item()), - ConstraintNode::item(face_item()), + ConstraintNode::item(selfie_item()), ]); let (items, expr) = node.to_protocol_top_level().unwrap(); diff --git a/rust/core/src/types.rs b/rust/core/src/types.rs index cfbe2da7..05cd38d7 100644 --- a/rust/core/src/types.rs +++ b/rust/core/src/types.rs @@ -29,8 +29,8 @@ use std::sync::Arc; pub enum CredentialType { /// Proof of human credential ProofOfHuman, - /// Face credential - Face, + /// Selfie credential + Selfie, /// Passport credential (ICAO 9303 compliant travel document) Passport, /// MNC (My Number Card) credential @@ -43,7 +43,7 @@ impl CredentialType { pub const fn issuer_schema_id(&self) -> u64 { match self { Self::ProofOfHuman => 1, - Self::Face => 11, + Self::Selfie => 11, Self::Passport => 9303, Self::Mnc => 9310, } @@ -56,7 +56,7 @@ impl CredentialType { pub const fn from_issuer_schema_id(id: u64) -> Option { match id { 1 => Some(Self::ProofOfHuman), - 11 => Some(Self::Face), + 11 => Some(Self::Selfie), 9303 => Some(Self::Passport), 9310 => Some(Self::Mnc), _ => None, @@ -625,7 +625,7 @@ impl<'de> Deserialize<'de> for BridgeResponseV1 { pub enum ResponseItem { /// Protocol version 4.0 (World ID v4) V4 { - /// Credential identifier (e.g., `proof_of_human`, `face`, `passport`, `mnc`) + /// Credential identifier (e.g., `proof_of_human`, `selfie`, `passport`, `mnc`) identifier: String, /// Signal hash (optional, included if signal was provided in request) #[serde(skip_serializing_if = "Option::is_none")] @@ -646,7 +646,7 @@ pub enum ResponseItem { }, /// Session proof (World ID v4 sessions) Session { - /// Credential identifier (e.g., `proof_of_human`, `face`, `passport`, `mnc`) + /// Credential identifier (e.g., `proof_of_human`, `selfie`, `passport`, `mnc`) identifier: String, /// Signal hash (optional, included if signal was provided in request) #[serde(skip_serializing_if = "Option::is_none")] @@ -670,7 +670,7 @@ pub enum ResponseItem { }, /// Protocol version 3.0 (World ID v3 - legacy format) V3 { - /// Credential identifier (e.g., `proof_of_human`, `face`) + /// Credential identifier (e.g., `proof_of_human`, `selfie`) identifier: String, /// Signal hash (hash of the signal provided in the request, or hash of empty signal) signal_hash: String, @@ -1166,7 +1166,7 @@ mod tests { assert_eq!(item.genesis_issued_at_min, None); // Test without signal - let no_signal = CredentialRequest::new(CredentialType::Face, None); + let no_signal = CredentialRequest::new(CredentialType::Selfie, None); assert_eq!(no_signal.signal, None); } @@ -1203,8 +1203,10 @@ mod tests { #[test] fn test_request_item_with_string_signal() { // Test creating request item with string signal - let item = - CredentialRequest::new(CredentialType::Face, Some(Signal::from_string("my_signal"))); + let item = CredentialRequest::new( + CredentialType::Selfie, + Some(Signal::from_string("my_signal")), + ); assert_eq!(item.signal, Some(Signal::from_string("my_signal"))); // String signals should also be retrievable as bytes @@ -1217,7 +1219,7 @@ mod tests { let signal = "0x3df41d9d0ba00d8fbe5a9896bb01efc4b3787b7c"; let expected = hex::decode(signal.strip_prefix("0x").unwrap()).unwrap(); let item = CredentialRequest::new( - CredentialType::Face, + CredentialType::Selfie, Some(Signal::String(signal.to_string())), ); @@ -1362,7 +1364,7 @@ mod tests { #[test] fn test_idkit_result_v3() { let responses = vec![ResponseItem::V3 { - identifier: "face".to_string(), + identifier: "selfie".to_string(), signal_hash: String::new(), proof: "0xproof".to_string(), merkle_root: "0xroot".to_string(), @@ -1388,7 +1390,7 @@ mod tests { #[test] fn test_credential_type_issuer_schema_id() { assert_eq!(CredentialType::ProofOfHuman.issuer_schema_id(), 1); - assert_eq!(CredentialType::Face.issuer_schema_id(), 11); + assert_eq!(CredentialType::Selfie.issuer_schema_id(), 11); assert_eq!(CredentialType::Passport.issuer_schema_id(), 9303); assert_eq!(CredentialType::Mnc.issuer_schema_id(), 9310); } @@ -1401,7 +1403,7 @@ mod tests { ); assert_eq!( CredentialType::from_issuer_schema_id(11), - Some(CredentialType::Face) + Some(CredentialType::Selfie) ); assert_eq!( CredentialType::from_issuer_schema_id(9303), diff --git a/rust/core/src/wasm_bindings.rs b/rust/core/src/wasm_bindings.rs index c55c56b7..48c8b8ed 100644 --- a/rust/core/src/wasm_bindings.rs +++ b/rust/core/src/wasm_bindings.rs @@ -38,7 +38,7 @@ impl CredentialRequestWasm { /// Creates a new request item /// /// # Arguments - /// * `credential_type` - The type of credential to request (e.g., `proof_of_human`, `face`) + /// * `credential_type` - The type of credential to request (e.g., `proof_of_human`, `selfie`) /// * `signal` - Optional signal string /// /// # Errors @@ -1333,7 +1333,7 @@ fn status_to_js_value(status: &crate::Status) -> Result { // TypeScript type definitions #[wasm_bindgen(typescript_custom_section)] const TS_TYPES: &str = r#" -export type CredentialType = "proof_of_human" | "face" | "passport" | "mnc"; +export type CredentialType = "proof_of_human" | "selfie" | "passport" | "mnc"; export interface CredentialRequestType { type: CredentialType; @@ -1363,7 +1363,7 @@ export function computeRpSignatureMessage(nonce: string, createdAt: bigint, expi const TS_IDKIT_RESULT: &str = r#" /** V4 response item for World ID v4 uniqueness proofs */ export interface ResponseItemV4 { - /** Credential identifier (e.g., "proof_of_human", "face", "passport", "mnc") */ + /** Credential identifier (e.g., "proof_of_human", "selfie", "passport", "mnc") */ identifier: string; /** Signal hash (optional, included if signal was provided in request) */ signal_hash?: string; @@ -1371,7 +1371,7 @@ export interface ResponseItemV4 { proof: string[]; /** RP-scoped nullifier (hex) */ nullifier: string; - /** Credential issuer schema ID (1=proof_of_human, 11=face, 9303=passport, 9310=mnc) */ + /** Credential issuer schema ID (1=proof_of_human, 11=selfie, 9303=passport, 9310=mnc) */ issuer_schema_id: number; /** Minimum expiration timestamp (unix seconds) */ expires_at_min: number; @@ -1379,7 +1379,7 @@ export interface ResponseItemV4 { /** V3 response item for World ID v3 (legacy format) */ export interface ResponseItemV3 { - /** Credential identifier (e.g., "proof_of_human", "face") */ + /** Credential identifier (e.g., "proof_of_human", "selfie") */ identifier: string; /** Signal hash (optional, included if signal was provided in request) */ signal_hash?: string; @@ -1393,7 +1393,7 @@ export interface ResponseItemV3 { /** Session response item for World ID v4 session proofs */ export interface ResponseItemSession { - /** Credential identifier (e.g., "proof_of_human", "face", "passport", "mnc") */ + /** Credential identifier (e.g., "proof_of_human", "selfie", "passport", "mnc") */ identifier: string; /** Signal hash (optional, included if signal was provided in request) */ signal_hash?: string; @@ -1401,7 +1401,7 @@ export interface ResponseItemSession { proof: string[]; /** Session nullifier: 1st element is the session nullifier, 2nd is the generated action (hex strings) */ session_nullifier: string[]; - /** Credential issuer schema ID (1=proof_of_human, 11=face, 9303=passport, 9310=mnc) */ + /** Credential issuer schema ID (1=proof_of_human, 11=selfie, 9303=passport, 9310=mnc) */ issuer_schema_id: number; /** Minimum expiration timestamp (unix seconds) */ expires_at_min: number; diff --git a/swift/Sources/IDKit/IDKit.swift b/swift/Sources/IDKit/IDKit.swift index 7313cc59..aaa77783 100644 --- a/swift/Sources/IDKit/IDKit.swift +++ b/swift/Sources/IDKit/IDKit.swift @@ -43,11 +43,10 @@ public final class IDKitBuilder { self.inner = inner } - // TODO: Re-enable when World ID 4.0 is live - // public func constraints(_ constraints: ConstraintNode) throws -> IDKitRequest { - // let request = try inner.constraints(constraints: constraints) - // return try IDKitRequest(inner: request) - // } + public func constraints(_ constraints: ConstraintNode) throws -> IDKitRequest { + let request = try inner.constraints(constraints: constraints) + return try IDKitRequest(inner: request) + } // TODO: Re-enable when World ID 4.0 is live // public func constraintsWithInviteCode(_ constraints: ConstraintNode) throws -> IDKitInviteCodeRequest {