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 secret
Example client builder:
AriesClient ac = 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
@Controller
public class WebhookController {
private EventHandler handler = new EventHandler.DefaultEventHandler();
@Post("/webhook/topic/{topic}")
public void handleWebhookEvent(
@PathVariable String topic,
@Body String payload) {
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-admin
Example multi tenant webhook controller
@Controller
public class WebhookController {
private EventHandler handler = new TenantAwareEventHandler.DefaultTenantAwareEventHandler();
@Post("/webhook/topic/{topic}")
public void handleWebhookEvent(
@PathVariable String topic,
@Body String payload,
HttpRequest request) {
String walletId = 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:
@Factory
public class AriesSocketFactory {
@Value("${acapy.ws.url}")
private String url;
@Value("${acapy.admin.apiKey}")
private String apiKey;
@Singleton
@Bean(preDestroy = "closeWebsocket")
public AriesWebSocketClient ariesWebSocketClient() {
return AriesWebSocketClient
.builder()
.url(url) // optional - defaults to ws://localhost:8031/ws
.apiKey(apiKey) // optional - admin api key if set
.handler(new EventHandler.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:
EventHandler
TenantAwareEventHandler
ReactiveEventHandler
All classes take care of type conversion so that you can immediately start implementing your business logic.
@Singleton
public class MyHandler extends EventHandler {
@Override
public void handleProof(PresentationExchangeRecord proof) {
if (proof.roleIsVerifierAndVerified()) { // received a validated proof
MyCredential myCredential = 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.
MyOtherCredential otherCredential = proof.from(MyOtherCredential.class);
}
}
}
As the websocket client already implements the EventHandler interface you can directly use it like:
AriesWebSocketClient ws = AriesWebSocketClient.builder().build();
// do some stuff, create a connection, or receive invitation
// blocking wait
ConnectionRecord active = ws.connection()
.filter(ConnectionRecord::stateIsActive)
.blockFirst(Duration.ofSeconds(5));
// none blocking
ws.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 request
public final class MyCredential {
private String street;
@AttributeName("e-mail")
private String email; // schema attribute name is e-mail
@AttributeName(excluded = true)
private String comment; // 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());
});
MyCredential myCredential = MyCredential
.builder()
.email("[email protected]")
.build();
ac.issueCredentialSend(
new V1CredentialProposalRequest(connectionId, credentialdefinitionId, myCredential));
PresentProofRequest proofRequest = 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}")
public HttpResponse<Object> connectionLessProof(@QueryValue String requestId) {
boolean matchingRequest = false; // TODO manage request states
String proofRequestBase64 = ""; // TODO on how to build this see the example below
if (matchingRequest) {
return HttpResponse
.status(HttpStatus.FOUND)
.header("location", deploymentUri "?m=" proofRequestBase64;
}
return HttpResponse.notFound();
}
Proof Request Builder Example
ProofRequestPresentationBuilder builder = new ProofRequestPresentationBuilder(ariesClient);
PresentProofRequest presentProofRequest = PresentProofRequestHelper.buildForEachAttribute(
connectionId,
List.of("name", "email"),
ProofRestrictions
.builder()
.schemaId("WgWxqztrNooG92RXvxSTWv:2:schema_name:1.0")
.build());
Optional<String> base64 = builder.buildRequest(presentProofRequest);