Complete guide for implementing credential exchange protocols in TrustWeave.
TODO (audit 2026-05): The skeleton in Step 2: Implement CredentialExchangeProtocol and the @Test in Unit Tests describe the obsolete request/response API (offerCredential(CredentialOfferRequest) returning CredentialOfferResponse, raw protocolName: String, supportedOperations: Set<…> on the protocol). The real SPI signatures are:
interface CredentialExchangeProtocol { val protocolName: ExchangeProtocolName; val capabilities: ExchangeProtocolCapabilities; suspend fun offer(request: ExchangeRequest.Offer): ExchangeMessageEnvelope; suspend fun request(request: ExchangeRequest.Request): ExchangeMessageEnvelope; suspend fun issue(request: ExchangeRequest.Issue): Pair<VerifiableCredential, ExchangeMessageEnvelope>; suspend fun requestProof(request: ProofExchangeRequest.Request): ExchangeMessageEnvelope; suspend fun presentProof(request: ProofExchangeRequest.Presentation): Pair<VerifiablePresentation, ExchangeMessageEnvelope> }
Supported operations are declared via capabilities = ExchangeProtocolCapabilities(supportedOperations = setOf(ExchangeOperation.OFFER_CREDENTIAL, …)).
The SPI provider interface is org.trustweave.credential.spi.exchange.CredentialExchangeProtocolProvider with name: String, supportedProtocols: List<String>, and create(protocolName: String, options: Map<String, Any?>): CredentialExchangeProtocol?.
See credentials/plugins/didcomm/src/main/kotlin/org/trustweave/credential/didcomm/exchange/DidCommExchangeProtocol.kt and …/exchange/spi/DidCommExchangeProtocolProvider.kt for a working reference implementation.
Overview
This guide covers how to implement new credential exchange protocols using the protocol abstraction layer. All protocols implement the CredentialExchangeProtocol interface, allowing them to be used interchangeably.
packageorg.trustweave.credential.yourprotocol.exchangeimportorg.trustweave.credential.exchange.*classYourProtocolExchangeProtocol(privatevalservice:YourProtocolService):CredentialExchangeProtocol{overridevalprotocolName="yourprotocol"overridevalsupportedOperations=setOf(ExchangeOperation.OFFER_CREDENTIAL,ExchangeOperation.REQUEST_CREDENTIAL,ExchangeOperation.ISSUE_CREDENTIAL)overridesuspendfunofferCredential(request:CredentialOfferRequest):CredentialOfferResponse{valoffer=service.createOffer(issuerDid=request.issuerDid,holderDid=request.holderDid,preview=request.credentialPreview)returnCredentialOfferResponse(offerId=offer.id,offerData=offer,protocolName=protocolName)}// Implement other operations...}
packageorg.trustweave.credential.yourprotocol.exchange.spiimportorg.trustweave.credential.exchange.CredentialExchangeProtocolimportorg.trustweave.credential.exchange.spi.CredentialExchangeProtocolProviderclassYourProtocolExchangeProtocolProvider:CredentialExchangeProtocolProvider{overridevalname="yourprotocol"overridevalsupportedProtocols=listOf("yourprotocol")overridefuncreate(protocolName:String,options:Map<String,Any?>):CredentialExchangeProtocol?{if(protocolName!="yourprotocol")returnnullvalservice=YourProtocolService(// Initialize from options)returnYourProtocolExchangeProtocol(service)}}
importorg.trustweave.credential.exchange.options.ExchangeOptionsvaloptions=ExchangeOptions.Empty// CHAPI typically doesn't require additional options// Messages are generated for browser use
importorg.trustweave.credential.exchange.*importorg.trustweave.credential.exchange.registry.ExchangeProtocolRegistriesimportorg.trustweave.credential.exchange.request.ExchangeRequestimportorg.trustweave.credential.exchange.result.ExchangeResultimportorg.trustweave.credential.identifiers.*@Testfun`testcompleteexchangeflow`()=runTest{valregistry=ExchangeProtocolRegistries.default()registry.register(YourProtocolExchangeProtocol(service))valexchangeService=ExchangeServices.createExchangeService(protocolRegistry=registry,credentialService=credentialService,didResolver=didResolver)// Test full flowvalofferResult=exchangeService.offer(ExchangeRequest.Offer(protocolName="yourprotocol".requireExchangeProtocolName(),issuerDid=issuerDid,holderDid=holderDid,credentialPreview=preview,options=ExchangeOptions.builder().build()))valoffer=when(offerResult){isExchangeResult.Success->offerResult.valueelse->throwIllegalStateException("Offer failed: $offerResult")}valrequestResult=exchangeService.request(ExchangeRequest.Request(protocolName="yourprotocol".requireExchangeProtocolName(),holderDid=holderDid,issuerDid=issuerDid,offerId=offer.offerId,options=ExchangeOptions.builder().build()))valrequest=when(requestResult){isExchangeResult.Success->requestResult.valueelse->throwIllegalStateException("Request failed: $requestResult")}valissueResult=exchangeService.issue(ExchangeRequest.Issue(protocolName="yourprotocol".requireExchangeProtocolName(),issuerDid=issuerDid,holderDid=holderDid,credential=credential,requestId=request.requestId,options=ExchangeOptions.builder().build()))valissue=when(issueResult){isExchangeResult.Success->issueResult.valueelse->throwIllegalStateException("Issue failed: $issueResult")}assertNotNull(issue.credential)}
Best Practices
Error Handling: Always validate inputs and provide clear error messages
Type Safety: Use strongly-typed models for protocol-specific data
Documentation: Document protocol-specific options and limitations
Testing: Write comprehensive tests for all operations
SPI Registration: Always register SPI providers for auto-discovery
// Convert common preview to protocol-specificvalprotocolPreview=ProtocolPreview(attributes=request.credentialPreview.attributes.map{attr->ProtocolAttribute(name=attr.name,value=attr.value,mimeType=attr.mimeType)})
Handling Optional Operations
1
2
3
4
5
6
7
8
9
10
overridesuspendfunrequestProof(request:ProofRequestRequest):ProofRequestResponse{if(!supportedOperations.contains(ExchangeOperation.REQUEST_PROOF)){throwUnsupportedOperationException("Protocol '$protocolName' does not support proof requests")}// Implementation...}