Credential Exchange Protocols - Quick Start
Get started with credential exchange protocols in 5 minutes! This guide will walk you through creating your first credential exchange using the protocol abstraction layer.
Version: 0.6.0 Kotlin: 2.2.0+ | Java: 21+ Prerequisites: See Installation
Complete Runnable Example
Here’s a complete, copy-paste ready example that demonstrates the full credential exchange workflow with proper error handling.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package com.example.credentialexchange.quickstart
import org.trustweave.credential.exchange.*
import org.trustweave.credential.exchange.registry.ExchangeProtocolRegistries
import org.trustweave.credential.exchange.request.ExchangeRequest
import org.trustweave.credential.exchange.response.ExchangeResponse
import org.trustweave.credential.exchange.result.ExchangeResult
import org.trustweave.credential.exchange.options.ExchangeOptions
import org.trustweave.credential.exchange.model.CredentialPreview
import org.trustweave.credential.exchange.model.CredentialAttribute
import org.trustweave.credential.didcomm.exchange.DidCommExchangeProtocol
import org.trustweave.credential.didcomm.DidCommFactory
import org.trustweave.credential.CredentialService
import org.trustweave.credential.credentialService
import org.trustweave.credential.model.vc.VerifiableCredential
import org.trustweave.credential.identifiers.*
import org.trustweave.kms.KeyManagementService
import org.trustweave.testkit.kms.InMemoryKeyManagementService
import org.trustweave.did.resolver.DidResolver
import org.trustweave.did.resolver.DidResolutionResult
import org.trustweave.did.model.DidDocument
import org.trustweave.did.identifiers.Did
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.*
import kotlinx.datetime.Clock
fun main() = runBlocking {
try {
// Step 1: Setup dependencies
val kms: KeyManagementService = InMemoryKeyManagementService()
val didResolver: DidResolver = object : DidResolver {
override suspend fun resolve(did: Did): DidResolutionResult {
// Mock DID resolution - replace with real resolver in production
return DidResolutionResult.Success(
DidDocument(id = did, verificationMethod = emptyList())
)
}
}
val credentialService: CredentialService = credentialService(didResolver = didResolver)
// Step 2: Create registry and register protocol
val registry = ExchangeProtocolRegistries.default()
val didCommService = DidCommFactory.createInMemoryServiceWithPlaceholderCrypto(kms) { didStr ->
val did = Did(didStr)
DidDocument(id = did, verificationMethod = emptyList())
}
registry.register(DidCommExchangeProtocol(didCommService))
// Step 3: Create ExchangeService
val exchangeService = ExchangeServices.createExchangeService(
protocolRegistry = registry,
credentialService = credentialService,
didResolver = didResolver
)
println("✅ Protocol registered: ${registry.getSupportedProtocols()}")
// Step 4: Create credential offer
val issuerDid = Did("did:key:issuer")
val holderDid = Did("did:key:holder")
val offerResult = exchangeService.offer(
ExchangeRequest.Offer(
protocolName = "didcomm".requireExchangeProtocolName(),
issuerDid = issuerDid,
holderDid = holderDid,
credentialPreview = CredentialPreview(
attributes = listOf(
CredentialAttribute("name", "Alice"),
CredentialAttribute("email", "alice@example.com")
)
),
options = ExchangeOptions.builder()
.addMetadata("fromKeyId", "$issuerDid#key-1")
.addMetadata("toKeyId", "$holderDid#key-1")
.build()
)
)
val offer = when (offerResult) {
is ExchangeResult.Success -> offerResult.value
is ExchangeResult.Failure.ProtocolNotSupported -> {
println("❌ Protocol not supported: ${offerResult.protocolName}")
return@runBlocking
}
is ExchangeResult.Failure.OperationNotSupported -> {
println("❌ Operation not supported: ${offerResult.operation}")
return@runBlocking
}
else -> {
println("❌ Offer failed: ${offerResult}")
return@runBlocking
}
}
println("✅ Offer created:")
println(" Offer ID: ${offer.offerId}")
println(" Protocol: ${offer.protocolName}")
// Step 5: Request credential
val requestResult = exchangeService.request(
ExchangeRequest.Request(
protocolName = "didcomm".requireExchangeProtocolName(),
holderDid = holderDid,
issuerDid = issuerDid,
offerId = offer.offerId,
options = ExchangeOptions.builder()
.addMetadata("fromKeyId", "$holderDid#key-1")
.addMetadata("toKeyId", "$issuerDid#key-1")
.build()
)
)
val request = when (requestResult) {
is ExchangeResult.Success -> requestResult.value
is ExchangeResult.Failure.ProtocolNotSupported -> {
println("❌ Protocol not supported: ${requestResult.protocolName}")
return@runBlocking
}
is ExchangeResult.Failure.OperationNotSupported -> {
println("❌ Operation not supported: ${requestResult.operation}")
return@runBlocking
}
else -> {
println("❌ Request failed: ${requestResult}")
return@runBlocking
}
}
println("✅ Request created:")
println(" Request ID: ${request.requestId}")
println(" Protocol: ${request.protocolName}")
// Step 6: Issue credential
val credential = VerifiableCredential(
type = listOf(CredentialType.fromString("VerifiableCredential"), CredentialType.fromString("PersonCredential")),
issuer = Issuer.IriIssuer(Iri(issuerDid.value)),
issuanceDate = Clock.System.now(),
credentialSubject = CredentialSubject(
id = Iri(holderDid.value), // CredentialSubject.id is Iri, not Did
claims = mapOf(
"name" to JsonPrimitive("Alice"),
"email" to JsonPrimitive("alice@example.com")
)
)
)
val issueResult = exchangeService.issue(
ExchangeRequest.Issue(
protocolName = "didcomm".requireExchangeProtocolName(),
issuerDid = issuerDid,
holderDid = holderDid,
credential = credential,
requestId = request.requestId,
options = ExchangeOptions.builder()
.addMetadata("fromKeyId", "$issuerDid#key-1")
.addMetadata("toKeyId", "$holderDid#key-1")
.build()
)
)
val issue = when (issueResult) {
is ExchangeResult.Success -> issueResult.value
is ExchangeResult.Failure.ProtocolNotSupported -> {
println("❌ Protocol not supported: ${issueResult.protocolName}")
return@runBlocking
}
is ExchangeResult.Failure.OperationNotSupported -> {
println("❌ Operation not supported: ${issueResult.operation}")
return@runBlocking
}
else -> {
println("❌ Issue failed: ${issueResult}")
return@runBlocking
}
}
println("✅ Credential issued:")
println(" Issue ID: ${issue.issueId}")
println(" Credential ID: ${issue.credential.id}")
println(" Protocol: ${issue.protocolName}")
} catch (e: Exception) {
println("❌ Unexpected error: ${e.message}")
e.printStackTrace()
}
}
Expected Output:
1
2
3
4
5
6
7
8
9
10
11
✅ Protocol registered: [didcomm]
✅ Offer created:
Offer ID: <offer-id>
Protocol: didcomm
✅ Request created:
Request ID: <request-id>
Protocol: didcomm
✅ Credential issued:
Issue ID: <issue-id>
Credential ID: <credential-id>
Protocol: didcomm
To run this example:
- Add dependencies (see Installation)
- Copy the code above into
src/main/kotlin/QuickStart.kt - Run with
./gradlew runor execute in your IDE
Step-by-Step Guide
Step 1: Add Dependencies
Add the credential exchange dependencies to your build.gradle.kts:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dependencies {
// Core credential exchange
implementation(project(":credentials:credential-core"))
// DIDComm protocol (optional - choose protocols you need)
implementation(project(":credentials:plugins:didcomm"))
// OIDC4VCI protocol (optional)
// implementation(project(":credentials:plugins:oidc4vci"))
// CHAPI protocol (optional)
// implementation(project(":credentials:plugins:chapi"))
// Test kit for in-memory implementations
testImplementation(project(":testkit"))
testImplementation(project(":kms:kms-core"))
}
Step 2: Setup Dependencies
You need two dependencies for credential exchange:
- Key Management Service (KMS): Manages cryptographic keys
- DID Resolver: Resolves DIDs to DID documents
1
2
3
4
5
6
7
8
9
10
// For testing/development: Use in-memory implementations
val kms: KeyManagementService = InMemoryKeyManagementService()
val resolveDid: suspend (String) -> DidDocument? = { did ->
// Mock resolver - replace with real resolver in production
DidDocument(id = did, verificationMethod = emptyList())
}
// For production: Use real implementations
// val kms = YourKmsImplementation()
// val resolveDid = YourDidResolver()
Step 3: Create Registry and Register Protocol
1
2
3
4
5
6
7
8
9
10
// Create registry
val registry = ExchangeProtocolRegistries.default()
// Create and register DIDComm protocol
val didCommService = DidCommFactory.createInMemoryServiceWithPlaceholderCrypto(kms, resolveDid)
registry.register(DidCommExchangeProtocol(didCommService))
// Verify registration
println("Registered protocols: ${registry.getSupportedProtocols()}")
// Output: Registered protocols: [ExchangeProtocolName("didcomm")]
Step 4: Create Credential Offer
An offer is a message from the issuer to the holder proposing a credential.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import org.trustweave.credential.exchange.request.ExchangeRequest
import org.trustweave.credential.exchange.options.ExchangeOptions
import org.trustweave.credential.exchange.model.CredentialPreview
import org.trustweave.credential.exchange.model.CredentialAttribute
import org.trustweave.credential.identifiers.*
import org.trustweave.did.identifiers.Did
import kotlinx.serialization.json.JsonPrimitive
val issuerDid = Did("did:key:issuer")
val holderDid = Did("did:key:holder")
val offerResult = exchangeService.offer(
ExchangeRequest.Offer(
protocolName = "didcomm".requireExchangeProtocolName(),
issuerDid = issuerDid,
holderDid = holderDid,
credentialPreview = CredentialPreview(
attributes = listOf(
CredentialAttribute("name", "Alice"),
CredentialAttribute("email", "alice@example.com")
)
),
options = ExchangeOptions.builder()
.addMetadata("fromKeyId", "$issuerDid#key-1") // Required for DIDComm
.addMetadata("toKeyId", "$holderDid#key-1") // Required for DIDComm
.build()
)
)
val offer = when (offerResult) {
is ExchangeResult.Success -> {
offerResult.value
}
else -> {
throw IllegalStateException("Offer failed: $offerResult")
}
}
println("Offer ID: ${offer.offerId}")
What this does:
- Creates a credential offer using the DIDComm protocol
- Returns an
ExchangeResultthat must be handled - On success, provides an
offerIdthat can be used to reference this offer - The offer contains a preview of the credential attributes
Step 5: Request Credential
After receiving an offer, the holder requests the credential.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
val requestResult = exchangeService.request(
ExchangeRequest.Request(
protocolName = "didcomm".requireExchangeProtocolName(),
holderDid = holderDid,
issuerDid = issuerDid,
offerId = offer.offerId, // Reference to the offer
options = ExchangeOptions.builder()
.addMetadata("fromKeyId", "$holderDid#key-1")
.addMetadata("toKeyId", "$issuerDid#key-1")
.build()
)
)
val request = when (requestResult) {
is ExchangeResult.Success -> {
requestResult.value
}
else -> {
throw IllegalStateException("Request failed: $requestResult")
}
}
println("Request ID: ${request.requestId}")
What this does:
- Creates a credential request referencing the offer
- Returns an
ExchangeResultthat must be handled - On success, provides a
requestIdthat can be used to reference this request - The request indicates the holder wants to receive the credential
Step 6: Issue Credential
After receiving a request, the issuer issues the credential.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import org.trustweave.credential.model.vc.VerifiableCredential
import org.trustweave.credential.model.vc.CredentialSubject
import org.trustweave.credential.model.vc.Issuer
import org.trustweave.credential.model.CredentialType
import org.trustweave.core.identifiers.Iri
import org.trustweave.credential.identifiers.*
import kotlinx.datetime.Clock
import kotlinx.serialization.json.JsonPrimitive
val credential = VerifiableCredential(
type = listOf(CredentialType.fromString("VerifiableCredential"), CredentialType.fromString("PersonCredential")),
issuer = Issuer.IriIssuer(Iri(issuerDid.value)),
issuanceDate = Clock.System.now(),
credentialSubject = CredentialSubject(
id = Iri(holderDid.value), // CredentialSubject.id is Iri, not Did
claims = mapOf(
"name" to JsonPrimitive("Alice"),
"email" to JsonPrimitive("alice@example.com")
)
)
)
val issueResult = exchangeService.issue(
ExchangeRequest.Issue(
protocolName = "didcomm".requireExchangeProtocolName(),
issuerDid = issuerDid,
holderDid = holderDid,
credential = credential,
requestId = request.requestId, // Reference to the request
options = ExchangeOptions.builder()
.addMetadata("fromKeyId", "$issuerDid#key-1")
.addMetadata("toKeyId", "$holderDid#key-1")
.build()
)
)
val issue = when (issueResult) {
is ExchangeResult.Success -> {
issueResult.value
}
else -> {
throw IllegalStateException("Issue failed: $issueResult")
}
}
println("Credential ID: ${issue.credential.id}")
What this does:
- Issues a verifiable credential to the holder
- Returns an
ExchangeResultthat must be handled - On success, provides the issued credential with proof
- The credential can now be stored, verified, and presented
Protocol-Specific Options
Each protocol requires different options. Here’s what you need for each:
DIDComm Options
1
2
3
4
5
6
7
8
9
import org.trustweave.credential.exchange.options.ExchangeOptions
import kotlinx.serialization.json.JsonPrimitive
val options = ExchangeOptions.builder()
.addMetadata("fromKeyId", "did:key:issuer#key-1") // Required: Sender's key ID
.addMetadata("toKeyId", "did:key:holder#key-1") // Required: Recipient's key ID
.addMetadata("encrypt", true) // Optional: Encrypt message (default: true)
.threadId("thread-id") // Optional: Thread ID for message threading
.build()
OIDC4VCI Options
1
2
3
4
5
val options = ExchangeOptions.builder()
.addMetadata("credentialIssuer", "https://issuer.example.com") // Required: OIDC issuer URL
.addMetadata("credentialTypes", JsonPrimitive("VerifiableCredential")) // Optional: Credential types
.addMetadata("redirectUri", "https://holder.example.com/callback") // Optional: Redirect URI
.build()
CHAPI Options
1
2
val options = ExchangeOptions.Empty // CHAPI typically doesn't require additional options
// Messages are generated for browser use
Error Handling
All ExchangeService methods return ExchangeResult sealed classes. Always handle the result:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
val offerResult = exchangeService.offer(
ExchangeRequest.Offer(...)
)
when (offerResult) {
is ExchangeResult.Success -> {
val offer = offerResult.value
// Handle success
}
is ExchangeResult.Failure.ProtocolNotSupported -> {
println("❌ Protocol not supported: ${offerResult.protocolName}")
println(" Available: ${offerResult.availableProtocols}")
}
is ExchangeResult.Failure.OperationNotSupported -> {
println("❌ Operation not supported: ${offerResult.operation}")
println(" Supported: ${offerResult.supportedOperations}")
}
is ExchangeResult.Failure.InvalidRequest -> {
println("❌ Invalid request: ${offerResult.reason}")
println(" Field: ${offerResult.field}")
}
is ExchangeResult.Failure.NetworkError -> {
println("❌ Network error: ${offerResult.reason}")
}
else -> {
println("❌ Exchange error: $offerResult")
}
}
Common Error Types:
ExchangeResult.Failure.ProtocolNotSupported: Protocol not registeredExchangeResult.Failure.OperationNotSupported: Protocol doesn’t support the operationExchangeResult.Failure.InvalidRequest: Invalid request fieldExchangeResult.Failure.NetworkError: Network-related errorsExchangeResult.Failure.MessageNotFound: Message reference not found- Protocol-specific errors: See Error Handling Guide
Next Steps
- Learn More: Read Core Concepts
- See Examples: Check Complete Examples
- API Reference: See API Reference
- Workflows: Follow Workflow Guides
- Troubleshooting: See Troubleshooting Guide
Related Documentation
- Overview - Protocol overview and comparison
- Core Concepts - Deep dive into protocol abstraction
- API Reference - Complete API documentation
- Examples - More code examples
- Workflows - Step-by-step workflows
- Error Handling - Error handling guide
- Troubleshooting - Common issues and solutions
- Glossary - Terms and concepts
- Best Practices - Security and performance guidelines
- Versioning - Version info and migration guides