Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
647059d
Fix Swift XCFramework header layout
Jun 16, 2026
7350d5e
Expose bridge debug payload in Swift and Kotlin
Jun 16, 2026
fb26df3
Avoid duplicate Swift modulemaps
Jun 16, 2026
0b10a07
Merge branch 'main' into expose-swift-debug-payload
kchaw2005 Jun 17, 2026
3d00219
"made cleaner looking entry points for the debugging payload functions"
Jun 17, 2026
f423f11
feat(swift): added uniffi bridgePaylod records for debugging and so …
Jun 17, 2026
62a93a3
made helper extensions to cleanly view possible credentials easier in…
Jun 17, 2026
401c95e
Merge branch 'expose-swift-debug-payload' of github.com:worldcoin/idk…
Jun 17, 2026
47f8a1e
Refactor bridge debug payload to project from the internal builder (B…
Jun 18, 2026
fb04672
removed duplicate bridgeDebugPayload functions in favor of extending …
Jun 18, 2026
c95cca3
moved extension into real idbuilder class with disclaimer
Jun 18, 2026
111c1ec
add errors flag for clippy
Jun 18, 2026
b6dfe3b
Merge branch 'main' into expose-swift-debug-payload
kchaw2005 Jun 18, 2026
260e72c
updated kotlin sdk to support debugging payload structs and methods
Jun 18, 2026
d3aa2f1
Merge branch 'expose-swift-debug-payload' of github.com:worldcoin/idk…
Jun 18, 2026
e104f1c
fix(kotlin): rename DebugSpecification variants to avoid Preset shado…
Jun 18, 2026
a87840c
refactor rust types
Takaros999 Jun 18, 2026
377b622
update swift/kotlin API
Takaros999 Jun 18, 2026
7beed69
style: run cargo fmt on bridge.rs
Jun 18, 2026
fcb6329
fix clippy foramtting
Jun 18, 2026
c7b7ea4
format for clippy
Jun 18, 2026
2775bbb
fix clippy foramtting
Jun 18, 2026
d6bd916
fix identityattributes -> identityAttributeWrapper ref
Jun 18, 2026
dc064a7
we didn't need a wrapper for IdentityAttribute type because it alread…
Jun 18, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,14 @@ class IDKitBuilder internal constructor(
fun constraints(constraints: uniffi.idkit_core.ConstraintNode): IDKitRequest =
IDKitRequest(inner.constraints(constraints))

fun bridgeDebugPayloadJson(constraints: uniffi.idkit_core.ConstraintNode): String =
inner.bridgeDebugPayloadJson(constraints)

fun preset(preset: Preset): IDKitRequest =
IDKitRequest(inner.preset(preset))

fun bridgeDebugPayloadJsonFromPreset(preset: Preset): String =
inner.bridgeDebugPayloadJsonFromPreset(preset)
}

class IDKitRequest internal constructor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ package com.worldcoin.idkit

import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
Expand Down Expand Up @@ -82,6 +88,66 @@ class IDKitTests {
// IDKit.proveSession("0x01", sessionConfig)
}

@Test
fun `bridge debug payload JSON exposes identity check contract fields`() {
val builder = IDKit.request(
IDKitRequestConfig(
appId = "app_staging_1234567890abcdef",
action = "test-action",
rpContext = sampleRpContext(),
actionDescription = "Identity check",
bridgeUrl = null,
allowLegacyProofs = false,
requireUserPresence = true,
overrideConnectBaseUrl = null,
returnTo = "idkitsample://callback",
environment = Environment.STAGING,
connectUrlMode = null,
),
)

val payload = Json.parseToJsonElement(
builder.bridgeDebugPayloadJsonFromPreset(
identityCheck(
attributes = listOf(
IdentityAttribute.MinimumAge(21u),
IdentityAttribute.Nationality("JPN"),
),
),
),
).jsonObject

assertEquals("app_staging_1234567890abcdef", payload["app_id"]?.jsonPrimitive?.content)
assertEquals("test-action", payload["action"]?.jsonPrimitive?.content)
assertEquals("Identity check", payload["action_description"]?.jsonPrimitive?.content)
assertEquals("document", payload["verification_level"]?.jsonPrimitive?.content)
assertEquals(true, payload["require_user_presence"]?.jsonPrimitive?.boolean)
assertEquals(true, payload["allow_legacy_proofs"]?.jsonPrimitive?.boolean)
assertEquals("idkitsample://callback", payload["return_to_url"]?.jsonPrimitive?.content)
assertEquals("staging", payload["environment"]?.jsonPrimitive?.content)
assertNull(payload["timestamp"])

val attributes = payload["identity_attributes"]!!.jsonArray
assertEquals(2, attributes.size)
assertEquals("minimum_age", attributes[0].jsonObject["type"]?.jsonPrimitive?.content)
assertEquals(21, attributes[0].jsonObject["value"]?.jsonPrimitive?.int)
assertEquals("nationality", attributes[1].jsonObject["type"]?.jsonPrimitive?.content)
assertEquals("JPN", attributes[1].jsonObject["value"]?.jsonPrimitive?.content)

val proofRequest = payload["proof_request"]!!.jsonObject
assertEquals("uniqueness", proofRequest["proof_type"]?.jsonPrimitive?.content)
assertEquals("rp_1234567890abcdef", proofRequest["rp_id"]?.jsonPrimitive?.content)
assertEquals(1_700_000_000, proofRequest["created_at"]?.jsonPrimitive?.int)
assertEquals(1_700_003_600, proofRequest["expires_at"]?.jsonPrimitive?.int)
assertTrue(proofRequest["id"]?.jsonPrimitive?.content?.isNotEmpty() == true)

val proofRequests = proofRequest["proof_requests"]!!.jsonArray
assertEquals(
listOf("passport", "mnc"),
proofRequests.map { it.jsonObject["identifier"]?.jsonPrimitive?.content },
)
}

@Test
fun `status mapping covers all canonical variants`() {
val result = sampleResult()
Expand Down
40 changes: 40 additions & 0 deletions rust/core/src/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,16 @@ impl IDKitConfig {
}
}

#[cfg(feature = "ffi")]
fn bridge_payload_json(
params: &BridgeConnectionParams,
) -> std::result::Result<String, crate::error::IdkitError> {
let payload = build_request_payload(params, false).map_err(crate::error::IdkitError::from)?;
serde_json::to_string(&payload).map_err(|err| crate::error::IdkitError::JsonError {
details: err.to_string(),
})
}

/// Unified builder for creating `IDKit` requests and sessions
#[cfg(feature = "ffi")]
#[derive(uniffi::Object)]
Expand Down Expand Up @@ -1576,6 +1586,21 @@ impl IDKitBuilder {
}))
}

/// Builds the plaintext bridge payload JSON for the given constraints without
/// creating a bridge request.
///
/// # Errors
///
/// Returns an error if constraints are invalid or payload construction fails.
#[allow(clippy::needless_pass_by_value)]
pub fn bridge_debug_payload_json(
&self,
constraints: Arc<ConstraintNode>,
) -> std::result::Result<String, crate::error::IdkitError> {
let params = self.config.to_params((*constraints).clone())?;
bridge_payload_json(&params)
}

/// Creates a `BridgeConnection` from a preset (works for all request types)
///
/// Presets provide a simplified way to create requests with predefined
Expand Down Expand Up @@ -1607,6 +1632,21 @@ impl IDKitBuilder {
}))
}

/// Builds the plaintext bridge payload JSON for the given preset without
/// creating a bridge request.
///
/// # Errors
///
/// Returns an error if the preset is invalid or payload construction fails.
#[allow(clippy::needless_pass_by_value)]
pub fn bridge_debug_payload_json_from_preset(
&self,
preset: Preset,
) -> std::result::Result<String, crate::error::IdkitError> {
let params = self.config.to_params_from_preset(preset)?;
bridge_payload_json(&params)
}

/// Creates an invite-code mode `BridgeConnection` with the given constraints (WDP-73).
///
/// Same proof-request shape as `constraints()`; the only difference is the
Expand Down
6 changes: 3 additions & 3 deletions scripts/package-swift.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ echo "📦 Packaging IDKit Swift artifacts"
rm -rf "$IOS_BUILD" "$PROJECT_ROOT/IDKitFFI.xcframework"
mkdir -p "$FFI_INCLUDE_DIR"
mkdir -p "$IOS_BUILD/bindings" \
"$IOS_BUILD/Headers/IDKit" \
"$IOS_BUILD/Headers" \
"$IOS_BUILD/target/universal-ios-sim/release" \
"$IOS_BUILD/target/universal-macos/release" \
"$GENERATED_DIR"
Expand Down Expand Up @@ -82,8 +82,8 @@ module idkitFFI {
}
EOF

cp "$IOS_BUILD/bindings"/idkit_coreFFI.h "$IOS_BUILD/Headers/IDKit/"
cp "$IOS_BUILD/bindings"/idkit_coreFFI.modulemap "$IOS_BUILD/Headers/IDKit/module.modulemap"
cp "$IOS_BUILD/bindings"/idkit_coreFFI.h "$IOS_BUILD/Headers/"
cp "$IOS_BUILD/bindings"/idkit_coreFFI.modulemap "$IOS_BUILD/Headers/module.modulemap"
Comment thread
cursor[bot] marked this conversation as resolved.

echo "🏗️ Creating XCFramework"
xcodebuild -create-xcframework \
Expand Down
10 changes: 10 additions & 0 deletions swift/Sources/IDKit/IDKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ public final class IDKitBuilder {
return try IDKitRequest(inner: request)
}

/// Builds the plaintext bridge payload JSON without creating a bridge request.
public func bridgeDebugPayloadJSON(_ constraints: ConstraintNode) throws -> String {
try inner.bridgeDebugPayloadJson(constraints: constraints)
}

// TODO: Re-enable when World ID 4.0 is live
// public func constraintsWithInviteCode(_ constraints: ConstraintNode) throws -> IDKitInviteCodeRequest {
// let request = try inner.constraintsWithInviteCode(constraints: constraints)
Expand All @@ -159,6 +164,11 @@ public final class IDKitBuilder {
return try IDKitRequest(inner: request)
}

/// Builds the plaintext bridge payload JSON for a preset without creating a bridge request.
public func bridgeDebugPayloadJSON(from preset: Preset) throws -> String {
try inner.bridgeDebugPayloadJsonFromPreset(preset: preset)
}

/// Builds the request in invite-code mode.
///
/// Returns an `IDKitInviteCodeRequest` exposing the canonical 6-character
Expand Down
58 changes: 58 additions & 0 deletions swift/Tests/IDKitTests/IDKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,64 @@ func idkitEntrypoints() throws {
#expect(Bool(true))
}

@Test("bridge debug payload JSON exposes identity check contract fields")
func bridgeDebugPayloadJSONIdentityCheck() throws {
let builder = IDKit.request(
config: IDKitRequestConfig(
appId: "app_staging_1234567890abcdef",
action: "test-action",
rpContext: try sampleRpContext(),
actionDescription: "Identity check",
bridgeUrl: nil,
allowLegacyProofs: false,
requireUserPresence: true,
overrideConnectBaseUrl: nil,
returnTo: "idkitsample://callback",
environment: .staging,
connectUrlMode: nil
)
)

let payloadJSON = try builder.bridgeDebugPayloadJSON(
from: identityCheck(
attributes: [
.minimumAge(21),
.nationality("JPN")
]
)
)
let payload = try #require(
try JSONSerialization.jsonObject(with: Data(payloadJSON.utf8)) as? [String: Any]
)

#expect(payload["app_id"] as? String == "app_staging_1234567890abcdef")
#expect(payload["action"] as? String == "test-action")
#expect(payload["action_description"] as? String == "Identity check")
#expect(payload["verification_level"] as? String == "document")
#expect(payload["require_user_presence"] as? Bool == true)
#expect(payload["allow_legacy_proofs"] as? Bool == true)
#expect(payload["return_to_url"] as? String == "idkitsample://callback")
#expect(payload["environment"] as? String == "staging")
#expect(payload["timestamp"] == nil)

let attributes = try #require(payload["identity_attributes"] as? [[String: Any]])
#expect(attributes.count == 2)
#expect(attributes[0]["type"] as? String == "minimum_age")
#expect(attributes[0]["value"] as? Int == 21)
#expect(attributes[1]["type"] as? String == "nationality")
#expect(attributes[1]["value"] as? String == "JPN")

let proofRequest = try #require(payload["proof_request"] as? [String: Any])
#expect(proofRequest["proof_type"] as? String == "uniqueness")
#expect(proofRequest["rp_id"] as? String == "rp_1234567890abcdef")
#expect(proofRequest["created_at"] as? Int == 1_700_000_000)
#expect(proofRequest["expires_at"] as? Int == 1_700_003_600)
#expect(proofRequest["id"] is String)

let proofRequests = try #require(proofRequest["proof_requests"] as? [[String: Any]])
#expect(proofRequests.map { $0["identifier"] as? String } == ["passport", "mnc"])
}

@Test("Status mapping covers all canonical variants")
func statusMapping() {
let result = sampleResult()
Expand Down
Loading