We have written custom completion handlers for our asynchronous functions in iOS. All exposed suspending functions include an overload for a completion handler that expects a ProvenError? and the function result (if not a void function). If the ProvenError is not null then there was an error while executing the function and the expected return value will be null.
agent.start() is a suspending void function. We can call it like so:
// Await
let error: ProvenError? = await agent.
[agent startStartUpTimeout: nil
completion:
didExchange.getAll() is a suspend function that returns an Array of DidExchangeRecord. We can call it like so:
We also have support for (1.0.0-ALPHA-23) with Swift. To use it you will need to add it to your project and import the desired functions. Here are some examples:
To create an Agent you need to first create a wallet config object to tell the Agent how to initialize your wallet.
Next create a mediation config to pass to our Agent. The reconnection interval in the below section has two values that indicate the min and max amount of time in a back off strategy on which to try and contact the mediator.
The pool config is used to indicate what networks/ledgers the Agent can read from. Not all ledgers contain all schemas or credential definitions and some are more volatile than others. The standard way to declare a pool config is shown in the examples below and again the React Native portion of this document.
The first value used to create a pool config is the genesisUrl. This is a url point to the genesis file in a raw string format. This file is used to establish connection and provide data about the network/ledger.
The second value used to create a pool config is the isProduction boolean flag. This indicates to the agent whether to treat the network/ledger as a production environment or not.
The last value needed to create a pool config is the indyNamespace. This string value is the name by which the network/ledger is known and is prepended to schemas and credential definitions to identify what network/ledger they are on. Misspelling this value or having an incorrect variation will cause errors when reading from the ledger.
Once the Agent is created and configured you will have to call agent.start() before you can use the agent for anything. When the app is closing agent.stop() should be called to safely shut the agent down between sessions.
If you want to remove the Agent or reset the wallet you can call agent.delete() to remove all data the agent has saved.
Creating an Agent follows similar steps as Kotlin and Swift but has some additional steps specific to React Native.
Similar to Kotlin and Swift some config object must be created to customize the Agent. Additionally for creating a pool config from a url pointing to genesis files you must pass the ProvenReactNative object imported from the @proven-mobile/react-native package.
The actual Agent is created by passing the created config objects along with some other options. Again the ProvenReactNative object must be included in the Agent constructor along with the provenEventsFactory that facilitates getting events from the Agent to the React Native level.
Once the Agent has been constructed you can start the Agent with the following code. The React Native code allows the use of the Javascript Promise syntax for async operations:
Similarly, the agent.stop() and agent.delete() functions exist on the agent object along with access to all other modules and their respective functions mirroring the Kotlin and Swift implementations.
let walletConfig = WalletConfig(
uri: "sqlite://pathWhereYouWantDataStored/local.db",
keyMethod: "raw",
passkey: "TheActualKeyThatUnlocksTheWallet",
id: "Unique id for this wallet"
)let mediationConfig = MediationConfig(
mediatorInvitationUrl: "Mediation connection URL for the default mediator you want to use",
// Optional parameter to indicate how often, in milliseconds, the Agent should try to reconnect to the mediator
baseMediatorReconnectionIntervalMS: 500,
// Optional parameter to indicate the max amount of time between attempts to contact the mediator
maximumMediatorReconnectionIntervalMS: 10000
)val walletConfig: WalletConfig = WalletConfig(
uri = "sqlite://pathWhereYouWantDataStored/local.db",
// options are raw(raw random key), kdf:argon2i(Encrypted Key), none(testing only)
keyMethod = "raw",
// Should be randomly generated or derived from a pin or password, must be able to be repeatedly accessed
passkey = "TheActualKeyThatUnlocksTheWallet",
id = "Unique id for this wallet"
)val mediationConfig: MediationConfig = MediationConfig(
mediatorInvitationUrl = "Mediation connection url for the default mediator you want to use",
// Optional parameter to indicate how often, in milliseconds,the Agent should try to reconnect to the mediator
baseMediatorReconnectionIntervalMS = 500,
// Optional parameter to indicate the max amount of time between attempts to contact the mediator
maximumMediatorReconnectionIntervalMS: Int = 10000
)// Await
let (records, error) = await agent.didExchange.getAll()
if (error != nil) {
print("Error getting connections")
} else {
print("Connections: \(records!.count)")
}
// Or use a completion handler
agent.didExchange.getAll { records, error in
if (error != nil) {
print("Error getting connections")
} else {
print("Connections: \(records!.count)")
}
}[agent.didExchange
getAllCompletion:^(
NSArray<ProvenmobileDidExchangeRecord *> *_Nullable records,
ProvenmobileProvenError *_Nullable error
){
if(error){
// Handle error
}else{
NSLog(@"Connections: %lu", records.count);
}
}
];import KMPNativeCoroutinesAsync
// Agent.start() example
_ = try await asyncFunction(for: agent.start(startUpTimeout: nil))
// DidExchange.getAll() example
let records = try await asyncFunction(for: agent.didExchange.getAll())
print("Connections: \(records.count)") val indicioDemoNet = IndyVDRPoolConfig.fromUrl(
genesisUrl = "https://raw.githubusercontent.com/Indicio-tech/indicio-network/main/genesis_files/pool_transactions_demonet_genesis",
isProduction = false,
indyNamespace = "indicio-demo-net"
) let indicioDemoNet = IndyVDRPoolConfig.companion.fromUrl(
genesisUrl = "https://raw.githubusercontent.com/Indicio-tech/indicio-network/main/genesis_files/pool_transactions_demonet_genesis",
isProduction = false,
indyNamespace = "indicio-demo-net"
)val agent: Agent = Agent(
walletConfig = walletConfig,
mediationConfig = mediationConfig,
pools = ListOf(
indicioDemoNet
),
pickupBatchSize = 10, // max number of message that can be picked up at once from the mediator
defaultConnectionLabel = "Proven SDK", // The label the Agent will use in didComm communication
enableLogging = false,
autoAcceptConnections = true,
autoAcceptCredentials = false,
customLogger = null // (message: String)->Unit Callback to use for logging instead of `println()`
)let agent = Agent(
walletConfig: walletConfig,
mediationConfig: mediationConfig,
pools: [
indicioDemoNet
],
pickupBatchSize: 10, // max number of message that can be picked up at once from the mediator
defaultConnectionLabel: "Proven SDK", // The label the Agent will use in didComm communication
enableLogging: false,
autoAcceptConnections: true,
autoAcceptCredentials: false,
customLogger: nil // (message: String)->Void Callback to use for logging instead of `println()`
)
// Start agent (KMPNativeCoroutines)
try await asyncFunction(for: agent.start(startUpTimeout: 10000))
// Start agent (Completion handler)
agent.start(startUpTimeout: 10000){error in
if(error != nil){
// Handle error
}
// Code to be called when agent started
}
// Start agent (Without KMPNativeCoroutines)
let error: ProvenError? = await agent.start(startUpTimeout: 10000)
if(error != nil){
// Handle error
}import RNFS from 'react-native-fs';
import ProvenReactNative, {
provenEventsFactory,
} from '@proven-mobile/react-native';
import {
Agent,
IndyPoolConfig,
type MediationConfig,
type WalletConfig
} from '@proven-mobile/core';
const walletConfig: WalletConfig = {
uri: `sqlite://${RNFS.DocumentDirectoryPath}/local.db`,
keyMethod: 'raw',
passkey: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb',
id: 'ReactNativeTest',
profile: 'test',
};
const mediationConfig: MediationConfig = {
mediatorInvitationUrl:
'url invite to the mediator you want to use',
baseMediatorReconnectionIntervalMS: 50,
maximumMediatorReconnectionIntervalMS: 10000,
};
const poolConfig: IndyVDRPoolConfig = await IndyPoolConfig.fromURL(
ProvenReactNative,
'https://raw.githubusercontent.com/Indicio-tech/indicio-network/main/genesis_files/pool_transactions_demonet_genesis',
false,
'indicio-demo-net',
);import ProvenReactNative, {
provenEventsFactory,
} from '@proven-mobile/react-native';
const agent = new Agent(
ProvenReactNative,
provenEventsFactory,
walletConfig,
mediationConfig,
[poolConfig],
'RN',
10,
true,
true,
true,
console.info // (message: string)->Void Callback to use for logging, defaults to console.log
);await agent.start();The Indicio Holdr SDK (Software Development Kit) supports Android and iOS platforms and provides holder capabilities that are compatible with many Aries protocols. Consumption of the Holdr SDK will allow an application to have its own Aries Askar wallet for holding digital credentials that conform to the Anoncreds specification, communicate with ledgers through IndyVDR, and generate zero trust proofs for information verification through Anoncreds-RS.
Did Exchange 1.0
Out Of Band 1.1
Coordinate Mediation 1.0
Pickup 2.0
Issue Credential 2.0
Present Proof 2.0
Did:Peer:1
Did:Peer:2
Did:Key
Anoncreds
Depending on the configuration of the Agent that has been created, credentials will be auto-accepted and added to the wallet or they will have to be manually accepted. Credentials are generally offered by an external Agent upon connection or other business action; in some rare cases the receiving Agent will request a credential.
If credentials are not auto accepted, you can use the credential events to determine when a credential has been offered.
The below code accepts the offer coming from an external issuing Agent. The issuer will then send a credential issuance message that needs final confirmation before the credential is saved and the issuer is notified of completion of issuance.
val credEvents = agent.events.getCredentialEvents()!! // Assuming agent initialized normally
agent.events.registerCredentialHandler((event)
After the issuer sends the credential issuance message, your Agent must confirm that it accepts the issued credential. This second confirmation ensures the credential you were offered matches what you actually received. Accepting notifies the issuing Agent that the credential has been stored and the transaction should be recorded on the ledger.
Once the credential has been accepted it is stored in the wallet. The credentials module on the Agent has several accessor functions to get credential exchange records. The records have the previewed attributes and after being accepted will have a populated credential attribute that contains information about the accepted credential (for example: credential definition id or schema id).
val receivedCredentials = credEvents.events.filter{
it.credentialExchangeRecord.state == CredentialState.CredentialReceived
}
receivedCredentials.onEach{
val attributes = it.credentialExchangeRecord.attributes
// User reviews the attributes of the credential
if(userAccept)
agent.credentials.acceptCredential(it.credentialExchangeRecord.id)
else
agent.credentials.rejectCredential(it.credentialExchangeRecord.id)
}.collect()agent.events.registerCredentialHandler((event) => {
const attributes = event.credentialExchangeRecord.attributes
// Have user review credential
if(userAccept)
await agent.credentials.acceptCredential(event.credentialExchangeRecord.id)
else
await agent.credentials.rejectCredential(event.credentialExchangeRecord.id)
})val record = agent.credentials.findByRecordId("Some id")const record = await agent.credential.findByRecordId("Some id")All connections are made through the Out of Band protocol using the Out of Band module on the Agent.
First you have to get an Out of Band invitation. This is done using one of two methods: either through a QR code that resolves to a URL (most common), or from a JSON object in the form of a string.
// The url commonly comes from the result of scanning a QR code.
val invitation = OutOfBandInvitationMessage.fromUrl(url)
// parses the URL to a json string
const invitation: string Once you have the Out of Band invitation you will have the Agent accept the invitation. The receiveInvitation function has many options but to automatically make a connection with the provided invitation you can supply just the invitation like the examples below.
// Completed connection status may or may not be done after this call
val records = agent.outOfBand.receiveInvitation(invitation)
// Takes the json formatted string for the invitation
const records = await
// returns references to records needed to complete connection
val records = agent.outOfBand.receiveInvitation(invitation, autoAcceptConnection = false)
// Connection guaranteed to be completed after this call
val exchangeRecord = agent.didExchange.requestConnection(records.didExchangeRecord!!.id, records.outOfBandRecord.id)const record = await agent.outOfBand.receiveInvitation(invitation, false)
const exchangeRecord = await agent.didExchange.requestConnection(records.didExchangeRecord!!.id, records.outOfBandRecord.id)Once a connection has been made through the DidExchange protocol the agent will keep a record which holds information about the established connection. A list of all connections can be retrieved and used to create a "contact list".
The DidExchange record itself has several properties derived when making a connection based on information provided from the agent that you are connecting to. Some commonly used properties: did, state, role, theirDid, theirLabel, createdAt, updatedAt, alias, threadId, mediatorId, outOfBandId, invitationDid
Some properties (like createdAt, updatedAt, and id) are present on all records. With DidExchangeRecords you would likely use theirLabel or alias to create a contact card item for displaying the connection. Other properties like role and state can be used to filter connections to find a specific connection or locate ones that failed or still need to be accepted after processing the invitation.
If you no longer want to have a connection with another agent you can delete the DidExchangeRecord. If you need to reconnect you will have to receive another invitation from the agent, or in some instances invitations can be reused (NOTE: single-use invitations can only be used by one agent while multi-use invitations can be used by multiple).
Events are handled as flows in Kotlin and can be retrieved from the agent.
The getEventBus function will attempt to retrieve the event bus class passed to it. This exists because custom event flows can be registered and stored in the agent.events eventManager object.
There are eight base flows for different actions that the Agent manages. Their functions are:
getDidExchangeEvents()
autoAccept// This is a suspend function in Kotlin
val contacts: List<DidExchangeRecord> = agent.didExchange.getAll()const contacts = await agent.didExchange.getAll()val contacts = agent.didExchange.getAll()
// Deleting the first contact in the list
agent.didExchange.deleteById(contacts[0].id)const contacts = await agent.didExchange.getAll()
await agent.didExchange.deleteById(contacts[0].id)getAgentEvents()getCredentialEvents
getEventBusEvents()
getMessageEvents()
getProofEvents()
getTrustPingEvents()
getWebsocketEvents()
All of the listed functions exist on the events property of the agent. The event bus events pertain to record updates. The message events pertain to all incoming messages regardless of type. Agent events are emitted to indicate the state (Start, Running, Stop) of the Agent.
Both the named functions and the getEventBus may return null if, for some reason, the event bus has not yet been registered to the agent's eventManager. The only way the named function would return null is if the Agent initializes with errors.
Once you have the events you can perform operations on them to filter for certain IDs or states in order to complete processes or inform the user when things are occurring.
Example of what NOT to do:
If you need to wait for the agent to complete an action, you should wait for a specific event/state instead:
In Kotlin the events use flows; this allows the use of the Flow API on all events.
Swift uses KMPNativeCoroutines (https://github.com/rickclephas/KMP-NativeCoroutines) (1.0.0-ALPHA-23) as a dependency that allows the Kotlin flows to be turned into native Swift observables, AsyncSequence, and potentially more.
// Turns the didExchangeEvents into a swift asyncSequence
let credentialEvents = asyncSequence(for: agent.events.getCredentialEvents()!.events)Events are also wrapped with convenience methods so you can easily listen to events without a third party library or when using Objective-C:
onDidExchangeStateChanged
onCredentialStateChanged
onTrustPingEvent
onAgentEvent
onRecordEvent
onProofEvent
onWebsocketEvent
Objective-C example:
Swift example:
Events in React Native are handled differently than in Kotlin and Swift. The format of events on the Agent is similar but is done in a simpler manner.
Instead of getting an events object, you register a handler function that will be called on all events of the specified type the handler is registered to.
The return of registering a handler is a function that will remove or unregister the handler. Calling the removal function will stop the given handler from being called on any future events.
Note: Registered events will not persist after the app has been closed.
val didExchangeEvents? = agent.events.getEventBus(DidExchangeEvents::class)
// or
val didExchangeEvents = agent.events.getDidExchangeEvents()?: throw Error("Events not initialized by agent")// Example of what NOT to do
val didExchangeEvents = agent.events.getDidExchangeEvents()?: throw Error("Events not initialized by agent")
didExchangeEvents.events.onEach{
// Causes deadlock because requestConnection will not return until the response message is processed
// This can be avoided by calling this function in a separate thread
agent.didExchange.requestConnection(it.didExchangeRecord.id, it.outOfBandRecord.id)
}.collect()
// Auto accept should be used instead of trying to do this. This is an intentionally BAD exampleval didExchangeEvents = agent.events.getDidExchangeEvents()?: throw Error("Events not initialized by agent")
// Waits for the first didExchangeStateChangedEvent to be emitted that is in the completed state
didExchangeEvents.events.first{
it.didExchangeRecord.state == DidExchangeState.Completed
}
// Additional attributes (such as ID) on the record can be used to wait for a specific record to reach a certain statePerforming certain actions that wait for completion of a protocol inside of an event handler can cause a deadlock situation. It is not advised to wait for protocol completion inside a continuous event handler.
void (^removeListener)(ProvenmobileKotlinCancellationException * _Nullable) =
[agent.events onDidExchangeStateChangedCallback:^(
ProvenmobileDidExchangeStateChangedEvent *_Nonnull event
) {
// Handle DidExchangeStateChangedEvent here
}
// Remove listener when no longer needed
removeListener(nil);let removeListener = agent.events.onDidExchangeStateChanged { event in
// Handle DidExchangeStateChangedEvent here
}
// Remove listener when no longer needed
removeListener(nil)// Registers a handler that is called on all Did Exchange events
const didExchangeRemove = agent.events.registerDidExchangeHandler(
event => {
if (event.didExchangeRecord.state === DidExchangeState.COMPLETED) {
console.log(
'didExchange completed',
event.didExchangeRecord.theirLabel,
)
}
})
// Unregister the handler when no longer needed
didExchangeRemove()Select "Add Files…" in the bottom-left dropdown and select the Proven SDK XCFramework to add it to your project.
In order to consume the Proven mobile SDK for Android you must have access to Maven repos hosted on GitHub. Add your GitHub username and a GitHub personal access token with read permissions to your local.properties and access those values in your build.gradle.
Info for creating a personal access token can be .
Official documentation for reading values from the local.properties can be .
Add the below repos to your Android repository list.
Add the following implementation to the project dependencies in the build.gradle.kts file.
Make sure your AndroidManifest includes the following permissions.
You will want to add "@proven-mobile/core" and "@proven-mobile/react-native" to your project.
Place proven-mobile-core-v1.1.0.tgz and proven-mobile-react-native-v1.1.0.tgz in a directory parallel to your package.json.
In package.json add them both as dependencies.
Add module to dependencies in android/app/build.gradle:
Import and link package in android/app/src/main/java/com/example/MainApplication.java:
Older React Native versions (0.67.5) may need to upgrade their Gradle wrapper to 7.4 (declared in android/gradle/wrapper/gradle-wrapper.properties) and upgrade com.android.tools.build:gradle (declared in android/build.gradle) to 7.3.1.
Run:
yarn install
{% endstep %} {% endstepper %}
Older versions of React Native (0.67.5) may have trouble automatically linking the native modules. If you run into issues we recommend linking the package manually as described below.
{% stepper %} {% step %}
Add pod to ios/Podfile:
{% code title="ios/Podfile" %}
// Example function to read values from local.properties in a build.gradle.kts file
fun readLocalProperty(key: String): String? {
val localPropertiesFile = File(rootDir, "local.properties")
if (localPropertiesFile.exists()) {
val properties = Properties()
localPropertiesFile.inputStream().use { properties.load(it) }
return properties.getProperty(key)
}
return null
}
repositories {
maven {
setUrl("https://maven.pkg.github.com/indicio-tech/proven-mobile-sdk")
credentials {
username = readLocalProperty("githubUsername")
password = readLocalProperty("githubToken")
}
}
maven {
setUrl("https://maven.pkg.github.com/hyperledger/aries-uniffi-wrappers")
credentials {
username = readLocalProperty("githubUsername")
password = readLocalProperty("githubToken")
}
}
}dependencies {
implementation("tech.indicio:provenmobile-android:1.1")
}<manifest>
<uses-permission android:name="android.permission. READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission. WRITE_EXTERNAL_STORAGE" />
<!-- For Android 10 (API level 29) and above -->
<uses-permission android:name="android.permission. MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>"dependencies": {
"@proven-mobile/react-native": "file:proven-mobile-react-native-v1.1.0.tgz",
"@proven-mobile/core": "file:proven-mobile-core-v1.1.0.tgz",
}dependencies{
<!-- Other dependencies -->
+ implementation project(":proven-mobile-react-native")
}
// If you have other libraries that use libc++_shared.so or libfbjni.so
// you may need to add the following to your android configs
android{
<!-- Other configs -->
+ packagingOptions {
+ pickFirst '**/libc++_shared.so'
+ pickFirst '**/libfbjni.so'
+ }
}+ import com.rtnprovenmobilesdk.ProvenMobilePackage;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
+ packages.add(new ProvenMobilePackage());
return packages;
}{% endstep %}
{% endstepper %}
### Android
{% stepper %}
{% step %}
## Add repositories and confirm minSdkVersion
Add repositories to `android/build.gradle` and confirm `minSdkVersion >= 24`.
{% code title="android/build.gradle" %}
```groovy
buildscript {
ext {
minSdkVersion = 24
}
}
allprojects{
repositories{
maven {
setUrl("https://maven.pkg.github.com/indicio-tech/proven-mobile-sdk")
credentials {
username = readLocalProperty("githubUsername")
password = readLocalProperty("githubToken")
}
}
maven {
setUrl("https://maven.pkg.github.com/hyperledger/aries-uniffi-wrappers")
credentials {
username = readLocalProperty("githubUsername")
password = readLocalProperty("githubToken")
}
}
}
}target 'ExampleApp' do
<!-- Other configs -->
+ pod 'rtn-proven-mobile-sdk', :path => '../node_modules/@proven-mobile/react-native'
target 'ExampleAppTests' do
<!-- Other configs -->
Then reinstall pods.
</div>
<div data-gb-custom-block data-tag="step">
### Android: settings.gradle
Add project to `android/settings.gradle`:
<div data-gb-custom-block data-tag="code" data-title='android/settings.gradle'>
```diff
<!-- Other configs -->
+include ':proven-mobile-react-native'
+project(':proven-mobile-react-native').projectDir = new File(rootProject.projectDir, '../node_modules/@proven-mobile/react-native/android')Sudo Typescript syntax is used to express API. Any parameter that could be undefined is optional in Kotlin and React Native, due to limitations with Swift code generation not all Swift function have default parameters when a value is optional and will need to have nil provided explicitly. A function using the await keyword is async in React Native and Swift and suspend in Kotlin.
Starts the agent and connect to default mediator if provided
await agent.start(
timeout: 10_000 // Optional -- time limit in MS to connect to mediator
)Stops the agent cleanly so it can be used later
await agent.stop()Deletes the agent and all data associated with it, essentially resetting the wallet
await agent.delete()Sends a didExchange request to the agent associate with the provided out of band record
const didExchangeRecord = await agent.didExchange.acceptOutOfBandInvitation(
outOfBandRecord: OutOfBandRecord, // Record id (String) in React Native
autoAcceptConnection: Boolean | undefined,
label: String | undefined,
alias: String | undefined,
routingParam: Routing | undefined // Not available in React Native
)Completes the didExchange handshake by sending a complete message to agent associated with the provided didExchangeId
const didExchangeRecord = await agent.didExchange.acceptResponse(
didExchangeId: String
)Used when auto accept connection is disabled on the agent. This function is used to complete the started didExchange process once an out of band invitation has been processed and a didExchange record is created but not auto accepted
Used to send a trust ping to another agent to ensure we can reach the agent.
Waits until the provided didExchangeId reaches a state of Done or until the provided timeout is reached.
Gets all didExchange Records
Finds all records that have tags matching the given query.
Gets the record with the provided Id or throws if not found.
Finds the record with the given Id or returns null if not found.
Deletes the record with the given Id or throws if it does not exist.
Finds all records associated with the given out of band Id.
Finds the record associated with the provided Did.
Finds the record whose invitation contained the provided Did.
Creates an out of band invitation and corresponding out of band record that is returned.
Parses a url encoded invitation into an OutOfBandInvitationMessage.
Processes the provided invitation and potentially starts or completes didExchange protocol.
Processes and invitation where the invitation message is not present and the agent is implicitly invited.
Accepts the invitation of an existing Out of Band record. Not commonly used.
Finds the out of band record with the corresponding invitation id, or returns null.
Finds the out of band record that corresponds to the invitation with the given id that we have created, or returns null.
Gets all of the out of band records held by the agent.
Gets all out of band records that match the provided query.
Get the record with the provided id or throws if not found.
Tries to find the record with the given id or returns null.
Deletes the record with the given id or throws if no such record exists.
Finds all credentials whose schema id matches the provided schema id.
sends a proposal to the connection with the associated didExchangeId that we want the specified credential from them.
Accepts the offer associated with the provided credential exchange record.
Accepts the issue credential and saves it to the agent's wallet.
Tries to find the record with the given id or returns null.
Finds all credentials whose exchange state match the provided state.
Finds all records whose state matches the given state and came from the connection associated with the given didExchangeId
Gets all of the credentialExchangeRecords
Tries to find the credential exchange record that has matching thread id and didExchange id (optional), or returns null.
Gets the credential exchange record that has matching thread id and didExchange id (optional). Throws if not records found
Attempts to auto accept the proof with the provided id from the provided didExchange connection. Will throw if the proof cannot be satisfied.
Attempts to accept the given proof using the provided credential selections. Will throw if credential selection is invalid or insufficient.
Gets a mapping of proof requests to valid credentials for the request that require further selection. Throws if the proof cannot be satisfied with the agent's current credentials.
Finds credentials that satisfy the proof request and automatically selects valid credentials if there are multiple options.Throws if the proof cannot be satisfied with the agent's current credentials.
Gets the proof records for a given connection from the didExchange id.
Starts or resumes the mediation to the default mediator. Called in agent.start by default.
Instructs the agent to attempt to pick up messages from the provided mediator record or the default mediator if not provided.
Finds the default mediator if one is set, otherwise returns null.
Tries to find the default mediators record. If the default mediator is found but not granted this will throw.
Set the default mediator.
Request mediation from the given didExchange connection
Gets the mediation record with the provided didExchange id or throws if not found.
Tries to find the mediation record with the given didExchange id or returns null if not found.
Gets all of the meditation records.
Tries to find the didExchange record for the default mediator or returns null.
Attempts to complete mediation request from the provided didExchange record in the given time.
Gets the routing for this mediator. Not available in React Native.
Sends a basic message to another connection
Find basic message record by ID
Get all basic messages
Find all basic messages with matching comments
Find all basic messages from recipient
Find all basic messages that match the role
React Native events all take a call back function and return a function to remove the callback.
Example:
The events that can have a handler registered are: registerDidExchangeHandler, registerProofsHandler, registerCredentialsHandler, registerAgentHandler, registerBasicMessageHandler, registerRecordHandler, and registerWebSocketHandler
In Kotlin the events API allows you to directly retrieve the event flow and use Kotlin's flow API. There is also a provided coroutine context on the events object.
Example:
The events that can be retrieved in Kotlin are the following: getDidExchangeEvents, getAgentEvents, getCredentialEvents, getEventBusEvents(Record update events), getMessageEvents(Events for all messages), getProofEvents, getBasicMessageEvents, getTrustPingEvents, and getWebSocketEvents
We also wrapped events with the following methods so you can easily listen to events without a third party library or with Objective-C:
onDidExchangeStateChanged
onCredentialStateChanged
onTrustPingEvent
onAgentEvent
Swift
onProofEvent
onWebsocketEvent
onBasicMessageEvent
const didExchangeRecord = await agent.didExchange(
didExchangeId: String,
outOfBandId: String,
routingParam: Routing | undefined // Not available in React Native
)const trustPingMessage = await agent.didExchange.sendPing(
exchangeId: String,
responseRequested: Boolean,
returnRouting: Boolean
)const didExchangeStateChangedEvent: DidExchangeStateChangedEvent? = await agent.didExchange.returnWhenIsConnected(
didExchangeId: String,
timeOutMs: Number | Long
)const records: Array<DidExchangeRecord> = await agent.didExchange.getAll()const records: Array<DidExchangeRecord> = await agent.didExchange.findAllByQuery(
query: Query // Record<String, String> in React Native. Malformed Query will throw
)const record = await agent.didExchange.getById(
didExchangeId: String
)const record: DidExchangeRecord? = await agent.didExchange.findById(
didExchangeId: String
)await agent.didExchange.deleteById(
didExchangeId: String
)const records: Array<DidExchangeRecord> = await agent.didExchange.getAllByOutOfBandId(
outOfBandId: String
)const record: DidExchangeRecord? = await agent.didExchange.findByDid(
did: String
)const record: DidExchangeRecord? = await agent.didExchange.findByInvitationDid(
did: String
)const record = await agent.outOfBand.createInvitation(
label: String | undefined,
alias: String | undefined,
goalCode: String | undefined,
goal: String | undefined,
handShake: Boolean | undefined,
messages: Array<BaseMessage> | undefined,
multiUseInvitation: Boolean | undefined,
autoAcceptConnection: Boolean | undefined,
routingParam: Routing | undefined, // Not available in React Native
appendedAttachment: Array<Attachment> | undefined
)const message = agent.outOfBand.parseInvitation(
invitationUrl: String
)const invitationResponse = await agent.outOfBand.receiveInvitation(
invitation: OutOfBandInvitationMessage,
label: String | undefined,
alias: String | undefined,
autoAcceptConnection: Boolean | undefined,
reuseConnection: Boolean | undefined,
routingParam: Routing | undefined, // Not available in React Native
acceptInvitationTimeOutMs = Number | Long | undefined
)const invitationResponse = await agent.outOfBand.receiveImplicitInvitation(
label: String | undefined,
alias: String | undefined,
autoAcceptConnection: Boolean | undefined,
reuseConnection: Boolean | undefined,
routingParam: Routing | undefined, // Not available in React Native
acceptInvitationTimeOutMs: Number | Long | null,
did: String,
handShakeProtocol: Array<String> | null
)const invitationResponse = await agent.outOfBand.acceptInvitation(
outOfBand: String,
autoAcceptConnection: Boolean,
reuseConnection: Boolean,
label: String,
alias: String | null,
routingParam: Routing | null,
timeOutMs: Number | undefined
)const record: OutOfBandRecord? = await agent.outOfBand.findByReceivedInvitationId(
receivedInvitationId: String
)const record: OutOfBandRecord? = await agent.outOfBand.findByCreatedInvitationId(
createdInvitationId: String
)const records: Array<OutOfBandRecord> = await agent.outOfBand.getAll()const records: Array<OutOfBandRecord> = await agent.outOfBand.getAllByQuery(
query: Query // Record<String, String> in React Native. Malformed Query will throw
)const record = await agent.outOfBand.getById(
outOfBandId: String
)const record: OutOfBandRecord? = await agent.outOfBand.findById(
outOfBandId: String
)await agent.outOfBand.deleteById(
outOfBandId: String
)const records: Array<CredentialRecord> = await agent.credentials.findAllCredentialsBySchemaId(
schemaId: String
)const record = await agent.credential.proposeCredential(
didExchangeId: String,
formatServices: FormatServices,
autoAcceptCredential: Boolean | undefined,
comment: String | null | undefined
)const record = await agent.credentials.acceptOffer(
credentialExchangeId: String,
autoAcceptCredential: Boolean | undefined,
comment: String | null | undefined
)const record = agent.credentials.acceptCredential(
credentialExchangeId: String
)const record: CredentialExchangeRecord? = await agent.credentials.findByRecordId(
credentialExchangeId: String
)const records: Array<CredentialExchangeRecord> = await agent.credentials.findAllByState(
state: CredentialState
)const records: Array<CredentialExchangeRecord> = await agent.credentials.findAllByStateAndDidExchangeId(
state: CredentialState,
didExchangeId: String
)const records: Array<CredentialExchangeRecord> = await agent.credentials.getAll()const record = await agent.credentials.findByThreadIdAndDidExchangeId(
threadId: String,
didExchangeId: String | null
)const record = await agent.credentials.getByThreadIdAndDidExchangeId(
threadId: String,
didExchangeId: String | null
)const record = await agent.proofs.autoAcceptProof(
proofId: String,
exchangeId: String
)const record = await agent.proofs.acceptProof(
proofData: PresentationData
)const creds = await agent.proofs.getCredentialsForProofRequest(
proofId: String,
exchangeId: String,
nonRevoked: Boolean | undefined
)const creds = await agent.proofs.autoSelectCredentialsForProofRequest(
proofId: String,
exchangeId: String,
nonRevoked: Boolean | undefined
)const records: Array<ProofRecord> = await agent.proofs.getProofRequestsForConnection(
didExchangeId: String
)await agent.routing.initialize()await agent.routing.initiateMessagePickup(
mediator: MediationRecord | null | undefined // Record id is used in React Native
)const record: MediationRecord? = await agent.routing.findDefaultMediator()const record: MediationRecord? = await agent.routing.discoverMediation()const record = await agent.routing.setDefaultMediator(
mediation: MediationRecord | String // Provide the record or the record id. React native only uses Id
)const record = await agent.routing.requestMediation(
didExchange: DidExchangeRecord | String // Provide the record or the record id. React native only uses Id
)const record = await agent.routing.getByExchangeId(
exchangeId: String
)const record: MediationRecord? = await agent.routing.findByExchangeId(
exchangeId: String
)const records: Array<MediationRecord> = await agent.routing.getMediators()const record: DidExchangeRecord? = await agent.routing.findDefaultMediatorExchange()const record: MediationRecord? = await agent.routing.provision(
didExchangeRecord: DidExchangeRecord,
timeOutMs: Number | Long | undefined
)const routing = await agent.routing.getRouting(
mediatorId: String,
useDefaultMediator: Boolean | undefined
)const sentMessageRecord = await agent.basicMessages.send(
didExchangeId: String, // ID of target
content: String, // Message content to be sent
locale: String = "en", // L10nDecorator version (defaults to "en")
comment: String? // Comment on message
)const basicMessage = await agent.basicMessages.findById(
basicMessageRecordId: String // Record ID to be retrieved
)const basicMessages = await agent.basicMessages.getAll()const basicMessages = await agent.basicMessages.findByComment(
comment: String // Comment to search for
)const basicMessages = await agent.basicMessages.findByDidExchangeId(
didExchangeId: String // Exchange ID to look for
)const basicMessages = await agent.basicMessages.findByRole(
role: BasicMessageRole // Role to look for
)const remove = agent.events.registerDidExchangeHandler((event) => {
console.log("Got a didExchange event")
})
remove() // cancels the event callback// May not be defined if there were issues with agent initialization
val didExchange: DidExchangeEvents? = agent.events.getDidExchangeEvents()
agent.events.scope.launch {
didExchange.events.onEach{
println("Got a didExchange event")
}.collect() // Make sure to call collect or events will not be processed
}let removeListener = agent.events.onDidExchangeStateChanged { event in
// Handle DidExchangeStateChangedEvent here
}
// Remove listener when no longer needed
removeListener(nil)When a proof request is received an event will be emitted. There are two options for handling proofs: attempt to auto-accept and process the proof, or manually select credentials. A proof request can be automatically accepted if the request does not have any attributes that need to be self-attested (provided manually from the user) and there are sufficient credentials in the wallet.
A single proof request can contain multiple proofs that need to be satisfied and in turn can make manual selection complicated.
A proof event contains the proof record and the didExchangeId of the contact that it came from. The initial state for the proof record is requestReceived.
This flow attempts to automatically select credentials and accept the proof. It only succeeds if there are no required self-attested attributes and the wallet contains sufficient credentials.
// Gets the first proof event received, blocks whatever thread it is ran on.
val proofEvent = agent.events.getProofEvents().first()
try {
agent.proofs.autoAcceptProof(proofEvent.proofRecord.id, proofEvent.didExchangeId)
} catch (e: Throwable) {
println("An error occurred auto-accepting proof, message: ${e.message}")
}const removeProofHandler = agent.events.registerProofHandler(
(event) => {
try {
if (event.proofRecord.state === ProofState.REQUEST_RECEIVED) {
await agent.proofs.autoAcceptProof(
event.proofRecord.id,
event.exchangeId
);
}
} catch (error) {
console.log(`An error ocurred auto-accepting proof, message ${error.message}`)
}
}
)That is all it takes to fulfill an automatic proof request.
Reminder: this only works if there are not self-attested attributes for the request and the wallet contains sufficient credentials.
Manual acceptance has two possible flows:
Let the agent auto-select credentials and return them to you so you can fill in any self-attested values.
Have the agent return all potential credentials that satisfy the proof, so you manually choose which to use.
Both options will throw if the wallet does not contain sufficient credentials for the proof request.
The return from both of these functions is a PresentationData
Kotlin:
React Native / TypeScript:
Self-attested attributes function the same way for manual selection.
Manual selection requires that a credential from the returned list be selected for each referent in the proof. This allows the end user to select exactly what credentials are shared but is more complex to process.
Kotlin:
React Native / TypeScript:
val proofEvent = agent.events.getProofEvents().first()
// Returns a list of pairs containing first the proof and then the credential(s) selected
val proofAttributes: SelectedCredentialsForProof = agent.proofs.autoSelectCredentialsForProof(
proofEvent.proofRecord.id,
proofEvent.didExchangeId,
nonRevoked = false
) // nonRevoked indicates if you care if the credentials selected are revoked or not
// Or
// Returns a similar object but can potentially contain multiple credentials that need to be selected from
val proofAttributes: SelectedCredentials = agent.proofs.getCredentialsForProofRequest(
proofEvent.proofRecord.id,
proofEvent.didExchangeId,
nonRevoked = false
)const removeProofHandler = agent.events.registerProofHandler(async (event) => {
const proofAttributes = await agent.proofs.autoSelectCredentialsForProof(
event.proofRecord.id,
event.didExchangeId,
false
)
// Or
const proofAttributes = await agent.proofs.getCredentialsForProofRequest(
event.proofRecord.id,
event.didExchangeId,
false
)
})val proofData = agent.proofs.autoSelectCredentialsForProof(
proofEvent.proofRecord.id,
proofEvent.didExchangeId,
nonRevoked = false
)
// Check if there are any required self-attested attributes
val selfAttested = proofData.getRequiredSelfAttested()
if(selftAttested.size != 0){
selfAttested.forEach{ referent ->
referent.selfAttestedValue = "Data from somewhere else"
}
}
// Supply the originally returned object that has been modified in place
agent.proofs.acceptProofs(proofData)const proofData = await agent.proofs.autoSelectCredentialsForProof(
event.proofRecord.id,
event.didExchangeId,
false
)
const selfAttested = proofData.getRequiredSelfAttested()
if(selfAttested.size != 0) {
selfAttested.forEach((referent) => {
referent.selfAttestedValue = "Data from somewhere else"
})
}
await agent.proofs.acceptProofs(proofData)val proofData = agent.proofs.getCredentialsForProofRequest(
proofEvent.proofRecord.id,
proofEvent.didExchangeId,
nonRevoked = false
)
// Goes through all attributes and picks the first credential that matches
proofData.attributes.forEach{ attribute =>
attribute.selectCredential(0)
}
proofData.predicates.forEach{ predicate =>
predicate.selectCredential(0)
}
agent.proofs.acceptProofs(proofData)const proofData = await agent.proofs.getCredentialsForProofRequest(
proofEvent.proofRecord.id,
proofEvent.didExchangeId,
nonRevoked = false
)
proofData.attributes.forEach((attribute) => {
attribute.selectCredential(0)
})
proofData.predicates.forEach((predicate) => {
predicate.selectCredential(0)
})
await agent.proofs.acceptProofs(proofData)request is the actual object that indicates what data is being requested from the wallet.