Convenience library based on okhttp and gson to interact with aries cloud agent python (aca-py) instances.
<dependency>
<groupId>network.idu.acapy</groupId>
<artifactId>aries-client-python</artifactId>
<version>0.10.0</version>
</dependency>Why don't you use swagger codegen?
For a long time aca-py's swagger.json was not really in sync with the code base. This has been hugely improved lately, so I started to generate model classes based on the stable releases found on dockerhub. There are still issues with complex structures, so one can not simply use the models 1:1, instead each one has to be checked manually before using it. This is tedious work and might take a while to complete. Also, the api is complex so that I found it useful to introduce helper methods directly in the model classes to make them more accessible.
Why is endpoint X, or field Y missing?
aca-py's api is changing rapidly with each release, and until most of the classes are using the generated models this can happen. So, if you are missing something create a PR with a fix or open an issue.
| Client Version | ACA-PY Version |
|---|---|
| 0.10.0 | 0.10.x |
| 0.8.0 | 0.8.0 |
| 0.7.0 | 0.7.0 |
| 0.7.6 | 0.7.1, 0.7.2 |
| >= 0.7.18 | 0.7.3 |
| >= 0.7.25 | 0.7.4 |
| >= 0.7.32 | 0.7.5 |
| Method | Endpoint | Implemented |
|---|---|---|
| action-menu | ||
| POST | /action-menu/{conn_id}/close | ✅ |
| POST | /action-menu/{conn_id}/fetch | ✅ |
| POST | /action-menu/{conn_id}/perform | ✅ |
| POST | /action-menu/{conn_id}/request | ✅ |
| POST | /action-menu/{conn_id}/send-menu | ✅ |
| basicmessage | ||
| POST | /connections/{conn_id}/send-message | ✅ |
| connection | ||
| GET | /connections | ✅ |
| POST | /connections/create-invitation | ✅ |
| POST | /connections/create-static | ✅ |
| POST | /connections/receive-invitation | ✅ |
| GET | /connections/{conn_id}} | ✅ |
| DELETE | /connections/{conn_id} | ✅ |
| POST | /connections/{conn_id}/accept-invitation | ✅ |
| POST | /connections/{conn_id}/accept-request | ✅ |
| GET | /connections/{conn_id}/endpoints | ✅ |
| POST | /connections/{conn_id}/establish-inbound/{ref_id} | ✅ |
| GET | /connections/{conn_id}/metadata | ✅ |
| POST | /connections/{conn_id}/metadata | ✅ |
| credential-definition | ||
| POST | /credential-definitions | ✅ |
| GET | /credential-definitions/created | ✅ |
| GET | /credential-definitions/{cred_def_id} | ✅ |
| POST | /credential-definitions/{cred_def_id}/write_record | ✅ |
| credentials | ||
| GET | /credentials/mime-types/{credential_id} | ✅ |
| GET | /credentials/revoked/{credential_id} | ✅ |
| GET | /credential/w3c/{credential_id} | ✅ |
| DELETE | /credential/w3c/{credential_id} | ✅ |
| GET | /credential/{credential_id} | ✅ |
| DELETE | /credential/{credential_id} | ✅ |
| GET | /credentials | ✅ |
| POST | /credentials/w3c | ✅ |
| did-exchange | ||
| POST | /didexchange/create-request | ✅ |
| POST | /didexchange/receive-request | ✅ |
| POST | /didexchange/{conn_id}/accept-invitation | ✅ |
| POST | /didexchange/{conn_id}/accept-request | ✅ |
| POST | /didexchange/{conn_id}/reject | ✅ |
| discover-features | ||
| GET | /discover-features/query | ✅ |
| GET | /discover-features/records | ✅ |
| discover-features v2.0 | ||
| GET | /discover-features-2.0/queries | ✅ |
| GET | /discover-features-2.0/records | ✅ |
| endorse-transaction | ||
| POST | /transaction/{tran_id}/resend | ✅ |
| POST | /transactions | ✅ |
| POST | /transactions/create-request | ✅ |
| POST | /transactions/{conn_id}/set-endorser-info | ✅ |
| POST | /transactions/{conn_id}/set-endorser-role | ✅ |
| POST | /transactions/{tran_id} | ✅ |
| POST | /transactions/{tran_id}/cancel | ✅ |
| POST | /transactions/{tran_id}/endorse | ✅ |
| POST | /transactions/{tran_id}/refuse | ✅ |
| POST | /transactions/{tran_id}/write | ✅ |
| introduction | ||
| POST | /connections/{conn_id}/start-introduction | ✅ |
| issue-credential v1.0 | ||
| POST | /issue-credential/create | ✅ |
| POST | /issue-credential/create-offer | ✅ |
| GET | /issue-credential/records | ✅ |
| GET | /issue-credential/records/{cred_ex_id} | ✅ |
| DELETE | /issue-credential/records/{cred_ex_id} | ✅ |
| POST | /issue-credential/records/{cred_ex_id}/issue | ✅ |
| POST | /issue-credential/records/{cred_ex_id}/problem-report | ✅ |
| POST | /issue-credential/records/{cred_ex_id}/send-offer | ✅ |
| POST | /issue-credential/records/{cred_ex_id}/send-request | ✅ |
| POST | /issue-credential/records/{cred_ex_id}/store | ✅ |
| POST | /issue-credential/send | ✅ |
| POST | /issue-credential/send-offer | ✅ |
| POST | /issue-credential/send-proposal | ✅ |
| issue-credential v2.0 | ||
| POST | /issue-credential-2.0/create | ✅ |
| POST | /issue-credential-2.0/create-offer | ✅ |
| GET | /issue-credential-2.0/records | ✅ |
| GET | /issue-credential-2.0/records/{cred_ex_id} | ✅ |
| DELETE | /issue-credential-2.0/records/{cred_ex_id} | ✅ |
| POST | /issue-credential-2.0/records/{cred_ex_id}/issue | ✅ |
| POST | /issue-credential-2.0/records/{cred_ex_id}/problem-report | ✅ |
| POST | /issue-credential-2.0/records/{cred_ex_id}/send-offer | ✅ |
| POST | /issue-credential-2.0/records/{cred_ex_id}/send-request | ✅ |
| POST | /issue-credential-2.0/records/{cred_ex_id}/store | ✅ |
| POST | /issue-credential-2.0/send | ✅ |
| POST | /issue-credential-2.0/send-offer | ✅ |
| POST | /issue-credential-2.0/send-proposal | ✅ |
| POST | /issue-credential-2.0/send-request | ✅ |
| jsonld | ||
| POST | /jsonld/sign | ✅ |
| POST | /jsonld/verify | ✅ |
| ledger | ||
| GET | /ledger/config | ✅ |
| GET | /ledger/did-endpoint | ✅ |
| GET | /ledger/did-verkey | ✅ |
| GET | /ledger/get-nym-role | ✅ |
| GET | /ledger/get-write-ledger | ✅ |
| GET | /ledger/get-write-ledgers | ✅ |
| POST | /ledger/register-nym | ✅ |
| PATCH | /ledger/rotate-public-did-keypair | ✅ |
| GET | /ledger/taa | ✅ |
| POST | /ledger/taa/accept | ✅ |
| POST | /ledger/{ledger_id}/set-write-ledger | ✅ |
| mediation | ||
| GET | /mediation/default-mediator | ✅ |
| DELETE | /mediation/default-mediator | ✅ |
| GET | /mediation/keylists | ✅ |
| POST | /mediation/keylists/{mediation_id}/send-keylist-query | ✅ |
| POST | /mediation/keylists/{mediation_id}/send-keylist-update | ✅ |
| POST | /mediation/request/{conn_id} | ✅ |
| GET | /mediation/requests | ✅ |
| GET | /mediation/requests/{mediation_id} | ✅ |
| DELETE | /mediation/requests/{mediation_id} | ✅ |
| POST | /mediation/requests/{mediation_id}/deny | ✅ |
| POST | /mediation/requests/{mediation_id}/grant | ✅ |
| POST | /mediation/update-keylist/{conn_id} | ✅ |
| PUT | /mediation/{mediation_id}/default-mediator | ✅ |
| multitenancy | ||
| POST | /multitenancy/wallet | ✅ |
| GET | /multitenancy/wallet/{wallet_id} | ✅ |
| PUT | /multitenancy/wallet/{wallet_id} | ✅ |
| POST | /multitenancy/wallet/{wallet_id}/remove | ✅ |
| POST | /multitenancy/wallet/{wallet_id}/token | ✅ |
| GET | /multitenancy/wallets | ✅ |
| out-of-band | ||
| POST | /out-of-band/create-invitation | ✅ |
| POST | /out-of-band/receive-invitation | ✅ |
| present-proof v1.0 | ||
| POST | /present-proof/create-request | ✅ |
| GET | /present-proof/records | ✅ |
| GET | /present-proof/records/{pres_ex_id} | ✅ |
| DELETE | /present-proof/records/{pres_ex_id} | ✅ |
| GET | /present-proof/records/{pres_ex_id}/credentials | ✅ |
| POST | /present-proof/records/{pres_ex_id}/problem-report | ✅ |
| POST | /present-proof/records/{pres_ex_id}/send-presentation | ✅ |
| POST | /present-proof/records/{pres_ex_id}/send-request | ✅ |
| POST | /present-proof/records/{pres_ex_id}/verify-presentation | ✅ |
| POST | /present-proof/send-proposal | ✅ |
| POST | /present-proof/send-request | ✅ |
| present-proof v2.0 | ||
| POST | /present-proof-2.0/create-request | ✅ |
| GET | /present-proof-2.0/records | ✅ |
| GET | /present-proof-2.0/records/{pres_ex_id} | ✅ |
| DELETE | /present-proof-2.0/records/{pres_ex_id} | ✅ |
| GET | /present-proof-2.0/records/{pres_ex_id}/credentials | ✅ |
| POST | /present-proof-2.0/records/{pres_ex_id}/problem-report | ✅ |
| POST | /present-proof-2.0/records/{pres_ex_id}/send-presentation | ✅ |
| POST | /present-proof-2.0/records/{pres_ex_id}/send-request | ✅ |
| POST | /present-proof-2.0/records/{pres_ex_id}/verify-presentation | ✅ |
| POST | /present-proof-2.0/send-proposal | ✅ |
| POST | /present-proof-2.0/send-request | ✅ |
| resolver | ||
| GET | /resolver/resolve/{did} | ✅ |
| revocation | ||
| GET | /revocation/active-registry/{cred_def_id} | ✅ |
| POST | /revocation/active-registry/{cred_def_id}/rotate | ✅ |
| POST | /revocation/clear-pending-revocations | ✅ |
| POST | /revocation/create-registry | ✅ |
| GET | /revocation/credential-record | ✅ |
| POST | /revocation/publish-revocations | ✅ |
| GET | /revocation/registries/created | ✅ |
| DELETE | /revocation/registry/delete-tails-file | ✅ |
| GET | /revocation/registry/{rev_reg_id} | ✅ |
| PATCH | /revocation/registry/{rev_reg_id} | ✅ |
| POST | /revocation/registry/{rev_reg_id}/definition | ✅ |
| POST | /revocation/registry/{rev_reg_id}/entry | ✅ |
| PUT | /revocation/registry/{rev_reg_id}/fix-revocation-entry-state | ✅ |
| GET | /revocation/registry/{rev_reg_id}/issued | ✅ |
| GET | /revocation/registry/{rev_reg_id}/issued/details | ✅ |
| GET | /revocation/registry/{rev_reg_id}/issued/indy_recs | ✅ |
| PATCH | /revocation/registry/{rev_reg_id}/set-state | ✅ |
| PUT | /revocation/registry/{rev_reg_id}/tails-file | ✅ |
| GET | /revocation/registry/{rev_reg_id}/tails-file | ✅ |
| POST | /revocation/revoke | ✅ |
| schema | ||
| POST | /schemas | ✅ |
| GET | /schemas/created | ✅ |
| GET | /schemas/{schema_id} | ✅ |
| POST | /schemas/{schema_id}/write_record | ✅ |
| settings | ||
| PUT | /settings | ✅ |
| GET | /settings | ✅ |
| trustping | ||
| POST | /connections/{conn_id}/send-ping | ✅ |
| wallet | ||
| GET | /wallet/did | ✅ |
| POST | /wallet/did/create | ✅ |
| PATCH | /wallet/did/local/rotate-keypair | ✅ |
| GET | /wallet/did/public | ✅ |
| POST | /wallet/did/public | ✅ |
| GET | /wallet/get-did-endpoint | ✅ |
| POST | /wallet/jwt/sign | ✅ |
| POST | /wallet/jwt/verify | ✅ |
| POST | /wallet/set-did-endpoint | ✅ |
| server | ||
| GET | /plugins | ✅ |
| GET | /shutdown | ✅ |
| GET | /status | ✅ |
| GET | /status/config | ✅ |
| GET | /status/live | ✅ |
| GET | /status/ready | ✅ |
| POST | /status/reset | ✅ |
The rest client is used to send requests against aca-py's admin rest endpoint.
Related aca-py config flags are: --admin <host> <port>, --admin-api-key <api-key>
The default assumes you are running against a single wallet. In case of multi tenancy with base and sub wallets the bearerToken needs to be set as well.
Example aca-py config flags:
--admin 0.0.0.0 8031
--admin-api-key secretExample client builder:
AriesClientac = AriesClient
.builder()
.url("http://localhost:8031") // optional - defaults to localhost:8031
.apiKey("secret") // optional - admin api key if set
.bearerToken("123.456.789") // optional - jwt token - only when running in multi tenant mode
.build();With aca-py you have three options on how to receive status changes:
- Poll the rest API - this is not recommended
- Register a webhook URL
- Connect to aca-py's websocket
Related aca-py config flag: --webhook-url <url#api_key>
If running a single wallet and not in multi tenant mode.
Example aca-py config flag: --webhook-url http://localhost:8080/webhook
@ControllerpublicclassWebhookController {
privateEventHandlerhandler = newEventHandler.DefaultEventHandler();
@Post("/webhook/topic/{topic}")
publicvoidhandleWebhookEvent(
@PathVariableStringtopic,
@BodyStringpayload) {
handler.handleEvent(topic, payload);
}
}If running in multi tenant mode.
Example aca-py config flags:
--webhook-url http://localhost:8080/webhook
--multitenant
--jwt-secret 1234
--multitenant-adminExample multi tenant webhook controller
@ControllerpublicclassWebhookController {
privateEventHandlerhandler = newTenantAwareEventHandler.DefaultTenantAwareEventHandler();
@Post("/webhook/topic/{topic}")
publicvoidhandleWebhookEvent(
@PathVariableStringtopic,
@BodyStringpayload,
HttpRequestrequest) {
StringwalletId = request.getHeaders().get("x-wallet-id");
handler.handleEvent(walletId, topic, payload);
}
}If the admin api is enabled aca-py also supports a websocket endpoint under ws(s)://<host>:<admin-port>/ws
Example aca-py config flag: --admin 0.0.0.0 8031
To connect with the websocket you can use the AriesWebSocketClient like:
@FactorypublicclassAriesSocketFactory {
@Value("${acapy.ws.url}")
privateStringurl;
@Value("${acapy.admin.apiKey}")
privateStringapiKey;
@Singleton@Bean(preDestroy = "closeWebsocket")
publicAriesWebSocketClientariesWebSocketClient() {
returnAriesWebSocketClient
.builder()
.url(url) // optional - defaults to ws://localhost:8031/ws
.apiKey(apiKey) // optional - admin api key if set
.handler(newEventHandler.DefaultEventHandler()) // optional - your handler implementation// .bearerToken(bearer) // optional - jwt token - only when running in multi tenant mode
.build();
}
}To add your own event handler implementation to use in webhooks or in the websocket client, you can either extend or instantiate one of the following classes:
EventHandlerTenantAwareEventHandlerReactiveEventHandler
All classes take care of type conversion so that you can immediately start implementing your business logic.
@SingletonpublicclassMyHandlerextendsEventHandler {
@OverridepublicvoidhandleProof(PresentationExchangeRecordproof) {
if (proof.roleIsVerifierAndVerified()) { // received a validated proofMyCredentialmyCredential = proof.from(MyCredential.class);
// If the presentation is based on multiple credentials this can be done multiple times// given that the POJO is annotated with @AttributeGroup e.g.MyOtherCredentialotherCredential = proof.from(MyOtherCredential.class);
}
}
}As the websocket client already implements the EventHandler interface you can directly use it like:
AriesWebSocketClientws = AriesWebSocketClient.builder().build();
// do some stuff, create a connection, or receive invitation// blocking wait ConnectionRecordactive = ws.connection()
.filter(ConnectionRecord::stateIsActive)
.blockFirst(Duration.ofSeconds(5));
// none blockingws.connection()
.filter(ConnectionRecord::stateIsActive)
.subscribe(System.out::println);The library assumes credentials are flat Pojo's like:
@Data@NoArgsConstructor@Builder@AttributeGroupName("referent") // the referent that should be matched in the proof requestpublicfinalclassMyCredential {
privateStringstreet;
@AttributeName("e-mail")
privateStringemail; // schema attribute name is e-mail@AttributeName(excluded = true)
privateStringcomment; // internal field
}How fields are serialised/deserialized can be changed by using the @AttributeName or @AttributeGroupName annotations.
ac.connectionsReceiveInvitation(
ReceiveInvitationRequest.builder()
.did(did)
.label(label)
.build(), ConnectionReceiveInvitationFilter
.builder()
.alias("alias")
.build()) .ifPresent(connection -> {
log.debug("{}", connection.getConnectionId());
});MyCredentialmyCredential = MyCredential
.builder()
.email("test@myexample.com")
.build();
ac.issueCredentialSend(
newV1CredentialProposalRequest(connectionId, credentialdefinitionId, myCredential));PresentProofRequestproofRequest = PresentProofRequestHelper.buildForEachAttribute(
connectionId,
MyCredential.class,
ProofRestrictions.builder()
.credentialDefinitionId(credentialDefinitionId)
.build());
ac.presentProofSendRequest(proofRequest);Connectionless proofs are more a thing of mobile wallets, because mostly they involve something that is presented to a human like a barcode, but the java client supports this by providing models and builders.
A flow has the usually following steps:
- The user is presented with a QRCode that contains an invitation URL like: https://myhost.com/url/1234
- The server side HTTP handler of this URL responds with an HTTP.FOUND response that has the proof request encoded in the m parameter
- The mobile wallet tries to match a stored credential, and then responds with a proof presentation if possible
- The server side WebhookHandler waits for the proof and then triggers further actions
@Get("/url/{requestId}")
publicHttpResponse<Object> connectionLessProof(@QueryValueStringrequestId) {
booleanmatchingRequest = false; // TODO manage request statesStringproofRequestBase64 = ""; // TODO on how to build this see the example belowif (matchingRequest) {
returnHttpResponse
.status(HttpStatus.FOUND)
.header("location", deploymentUri + "?m=" + proofRequestBase64;
}
returnHttpResponse.notFound();
}Proof Request Builder Example
ProofRequestPresentationBuilderbuilder = newProofRequestPresentationBuilder(ariesClient);
PresentProofRequestpresentProofRequest = PresentProofRequestHelper.buildForEachAttribute(
connectionId,
List.of("name", "email"),
ProofRestrictions
.builder()
.schemaId("WgWxqztrNooG92RXvxSTWv:2:schema_name:1.0")
.build());
Optional<String> base64 = builder.buildRequest(presentProofRequest);