Version: v0.13
Transfer Manager
The TransferManager handles the data transfer phase of the mDoc protocol, managing the exchange of device requests and responses between holder and verifier.
Obtaining the Transfer Manager
The transfer manager is obtained by starting an engagement:
- Android/Kotlin
- iOS/Swift
val engagementManager = session.component.mdocEngagementManager
val engagement = engagementManager.activeEngagement.value
// Start engagement and get transfer manager
val transferManager: TransferManager = engagement!!.start()
// Or using Try interface for result-based error handling
val result = engagement.tryOps().start()
when (result) {
is IdkResult.Success -> {
val transferManager = result.value
// Use transfer manager
}
is IdkResult.Failure -> {
val error = result.error
// Handle error
}
}
let engagementManager = session.component.mdocEngagementManager
let engagement = engagementManager.activeEngagement.value
// Start engagement and get transfer manager
let transferManager: TransferManager = try await engagement!.start()
// Or using Try interface for result-based error handling
let result = try await engagement!.tryOps().start()
switch result {
case .success(let transferManager):
// Use transfer manager
case .failure(let error):
// Handle error
}
Receiving Device Requests
Receive and process the verifier's request:
- Android/Kotlin
- iOS/Swift
// Receive the device request from verifier
val deviceRequest: DeviceRequest = transferManager.receiveDeviceRequest()
// Examine what's being requested
for (docRequest in deviceRequest.docRequests) {
println("Document type: ${docRequest.docType}")
// Get requested namespaces and elements
val namespaces = docRequest.itemsRequest.nameSpaces
for ((namespace, elements) in namespaces) {
println(" Namespace: $namespace")
for ((elementId, intentToRetain) in elements) {
println(" - $elementId (retain: $intentToRetain)")
}
}
}
// Receive the device request from verifier
let deviceRequest: DeviceRequest = try await transferManager.receiveDeviceRequest()
// Examine what's being requested
for docRequest in deviceRequest.docRequests {
print("Document type: \(docRequest.docType)")
// Get requested namespaces and elements
let namespaces = docRequest.itemsRequest.nameSpaces
for (namespace, elements) in namespaces {
print(" Namespace: \(namespace)")
for (elementId, intentToRetain) in elements {
print(" - \(elementId) (retain: \(intentToRetain))")
}
}
}
Creating Device Responses
Create a response with the requested data:
- Android/Kotlin
- iOS/Swift
// Create response using a document provider
val deviceResponse = transferManager.createResponse(
deviceRequest = deviceRequest,
documentProvider = documentProvider
)
// The response contains signed documents
println("Response has ${deviceResponse.documents?.size ?: 0} documents")
// Create response using a document provider
let deviceResponse = try await transferManager.createResponse(
deviceRequest: deviceRequest,
documentProvider: documentProvider
)
// The response contains signed documents
print("Response has \(deviceResponse.documents?.count ?? 0) documents")
Sending Responses
Send the response to the verifier:
- Android/Kotlin
- iOS/Swift
// Send the device response
transferManager.sendDeviceResponse(deviceResponse)
// Send the device response
try await transferManager.sendDeviceResponse(deviceResponse: deviceResponse)
Complete Transfer Flow
Here's a complete holder-side transfer flow:
- Android/Kotlin
- iOS/Swift
class MdocTransferHandler(
private val engagementManager: MdocEngagementManager,
private val documentProvider: DocumentProvider
) {
suspend fun handleTransfer(engagement: EngagementInstance) {
try {
// 1. Start the transfer
val transferManager = engagement.start()
// 2. Receive device request
val deviceRequest = transferManager.receiveDeviceRequest()
// 3. Show consent UI to user
val userApproved = showConsentDialog(deviceRequest)
if (userApproved) {
// 4. Create response with user-approved data
val deviceResponse = transferManager.createResponse(
deviceRequest = deviceRequest,
documentProvider = documentProvider
)
// 5. Send response to verifier
transferManager.sendDeviceResponse(deviceResponse)
} else {
// User declined - send error response
val errorResponse = DeviceResponse.Builder()
.withStatus(DeviceResponseStatus(20u)) // User cancelled
.build()
transferManager.sendDeviceResponse(errorResponse)
}
} catch (e: Exception) {
handleError(e)
} finally {
// Clean up engagement
engagementManager.closeEngagementByInstance(engagement)
}
}
}
class MdocTransferHandler {
private let engagementManager: MdocEngagementManager
private let documentProvider: DocumentProvider
init(engagementManager: MdocEngagementManager, documentProvider: DocumentProvider) {
self.engagementManager = engagementManager
self.documentProvider = documentProvider
}
func handleTransfer(engagement: EngagementInstance) async {
do {
// 1. Start the transfer
let transferManager = try await engagement.start()
// 2. Receive device request
let deviceRequest = try await transferManager.receiveDeviceRequest()
// 3. Show consent UI to user
let userApproved = await showConsentDialog(request: deviceRequest)
if userApproved {
// 4. Create response with user-approved data
let deviceResponse = try await transferManager.createResponse(
deviceRequest: deviceRequest,
documentProvider: documentProvider
)
// 5. Send response to verifier
try await transferManager.sendDeviceResponse(deviceResponse: deviceResponse)
} else {
// User declined - send error response
let errorResponse = DeviceResponse.Builder()
.withStatus(DeviceResponseStatus(value: 20)) // User cancelled
.build()
try await transferManager.sendDeviceResponse(deviceResponse: errorResponse)
}
} catch {
handleError(error: error)
}
// Clean up engagement
engagementManager.closeEngagementByInstance(engagement: engagement)
}
}
Transfer Instance Tracking
The engagement manager tracks active transfer instances:
- Android/Kotlin
- iOS/Swift
// Track all active transfers
lifecycleScope.launch {
engagementManager.transferInstances.collect { transfers: Map<Uuid, TransferInstance> ->
for ((id, transfer) in transfers) {
println("Active transfer: $id")
}
}
}
// Track all active transfers
Task {
for await transfers in engagementManager.transferInstances {
for (id, transfer) in transfers {
print("Active transfer: \(id)")
}
}
}
DeviceResponse Builder
Build custom device responses:
- Android/Kotlin
- iOS/Swift
// Build a device response manually
val response = DeviceResponse.Builder()
.withVersion("1.0")
.withStatus(DeviceResponseStatus(0u)) // Success
.withDocuments(listOf(document))
.build()
// Build an error response
val errorResponse = DeviceResponse.Builder()
.withStatus(DeviceResponseStatus(10u)) // General error
.build()
// Build a device response manually
let response = DeviceResponse.Builder()
.withVersion("1.0")
.withStatus(DeviceResponseStatus(value: 0)) // Success
.withDocuments([document])
.build()
// Build an error response
let errorResponse = DeviceResponse.Builder()
.withStatus(DeviceResponseStatus(value: 10)) // General error
.build()
Response Status Codes
Standard device response status codes:
| Code | Meaning |
|---|---|
| 0 | Success |
| 10 | General error |
| 11 | CBOR decoding error |
| 20 | User cancelled |
Transfer Lifecycle
The transfer phase follows this sequence:
Best Practices
When implementing transfer handling:
- Always get user consent: Never send data without explicit user approval
- Use the document provider: Let the IDK handle credential signing
- Handle errors gracefully: Network interruptions and timeouts can occur during transfer
- Clean up after transfer: Close the engagement when complete
- Send appropriate status codes: Use correct status codes for error responses