All pages
Powered by GitBook
1 of 9

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

iOS Async

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.

Void function example

agent.start() is a suspending void function. We can call it like so:

// Await
let error: ProvenError? = await agent.
[agent startStartUpTimeout: nil
    completion:

Returning function example

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:

Agent

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.

start
(
startUpTimeout
:
nil
)
if (error != nil) {
// Handle error
} else {
print("Agent Started!")
}
// Or use a completion handler
agent.start(startUpTimeout: nil) { error in
if (error != nil) {
// Handle error
} else {
print("Agent Started!")
}
}
^
(
ProvenmobileProvenError
*
_Nullable error
){
if(error){
// Handle error
}else{
NSLog(@"Agent Started!");
}
}
];

KMPNativeCoroutines

KMPNativeCoroutines

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.

Wallet Config

Mediation Config

Pool Config

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
)

Configuring an Agent

React Native

// 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();

Mobile SDK

Proven Mobile SDK Documentation

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.

Supported Aries protocols:

  • 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

Supported DID methods:

Supported credential formats:

Credentials

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 issuer typically offers the credential. In some configurations the Agent may auto-accept; otherwise present offers to the user for manual acceptance.

1

Accepting an offered credential

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) 
2

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.

3

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 offers = credEvents.events.filter {
it.credentialExchangeRecord.state == CredentialState.OfferReceived
}
offers.onEach{
// present to user
if(userAccept)
agent.credentials.acceptOffer(it.credentialExchangeRecord.id)
}.collect()
=>
{
if(event.credentialExchangeRecord.state === CredentialState.OfferReceived)
// Present to user somehow
if(userAccept)
await agent.credentials.acceptOffer(event.credentialExchangeRecord.id)
})

Confirming receipt of the issued credential

Accessing stored credentials

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")

Connections

Creating a Connection

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.

Parsing the invitation

// 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.

Receiving and waiting for a completed connection

// 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

While it is not recommended to disable auto acceptance, manually accepting a connection is also an option.

Manually accepting a connection (autoAcceptConnection = false)

// 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)

NOTE: In the above example you technically have two instances of the didExchange record, one in the records variable and one in the exchangeRecord variable. Since records can be updated asynchronously, always use the most recently returned value or fetch the record by ID if any operations have been done that could affect the Agent's records.

Retrieving and using the didExchange records

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

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()

, and
autoAccept
.
// Or if you have the message in json already
val invitation = OutOfBandInvitationMessage.fromJsonString(str)
=
agent
.
outOfBand
.
parseInvitation
(
invitationUrl
)
// Waits for the record to be in the completed state
agent.events.getDidExchangeEvents?.events?.first { event ->
if (records.didExchangeRecord.id != event.didExchangeRecord.id) {
event.didExchangeRecord.state == DidExchangeState.Completed
}
}
agent
.
outOfBand
.
receiveInvitation
(
invitation
)
const removeListener = agent?.events.registerDidExchangeHandler((event) => {
if (event.didExchangeRecord.id === records.didExchangeRecord.id && event.didExchangeRecord.isReady) {
console.log("Connection complete")
removeListener()
}
})

Removing a connection

// 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.

    Swift
    // 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.

    Kotlin
    val didExchangeEvents? = agent.events.getEventBus(DidExchangeEvents::class)
    // or
    val didExchangeEvents = agent.events.getDidExchangeEvents()?: throw Error("Events not initialized by agent")

    The examples of event flows below do not include the code needed to launch them in a background coroutine scope. You must add that code in your Kotlin code to take the handlers off of the main thread.

    Kotlin

    BAD example (Kotlin)
    // 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 example
    Kotlin
    val 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 state

    Performing 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.

    Objective-C
    void (^removeListener)(ProvenmobileKotlinCancellationException * _Nullable) =  
       [agent.events onDidExchangeStateChangedCallback:^(
          ProvenmobileDidExchangeStateChangedEvent *_Nonnull event
       ) {
          // Handle DidExchangeStateChangedEvent here
       }
    
    // Remove listener when no longer needed
    removeListener(nil);
    Swift
    let removeListener = agent.events.onDidExchangeStateChanged { event in
       // Handle DidExchangeStateChangedEvent here
    }
    // Remove listener when no longer needed
    removeListener(nil)
    TypeScript
    // 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()

    Installation

    iOS

    1

    Open your project in Xcode

    Open your iOS project in Xcode and click on your project name under the targets section.

    2

    Locate frameworks

    Scroll down in the General tab until you see the Frameworks section of the project.

    3

    Add the SDK

    Click on the + button under Frameworks.

    4

    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 .

    1

    Add the below repos to your Android repository list.

    2

    Add the following implementation to the project dependencies in the build.gradle.kts file.

    3

    Make sure your AndroidManifest includes the following permissions.

    You will want to add "@proven-mobile/core" and "@proven-mobile/react-native" to your project.

    1

    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.

    2

    In package.json add them both as dependencies.

    3

    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" %}

    build.gradle.kts
    // 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")
            }
        }
    }
    build.gradle.kts
    dependencies {
        implementation("tech.indicio:provenmobile-android:1.1")
    }
    AndroidManifest.xml
    <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>
    package.json
    "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",
    }
    android/app/build.gradle
    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'
    +    }
    }
    MainApplication.java
    + 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;
            }

    Select the XCFramework

    Android

    Recommended: Add GitHub authentication to local.properties (or other secrets file)

    Android Installation

    Add repositories

    Add dependency

    AndroidManifest permissions

    React Native

    Installation

    Place package files

    Add dependencies to package.json

    Android: app build.gradle

    Android: MainApplication.java

    Gradle / tooling upgrades (older RN versions)

    found here
    found here

    Install packages

    Manually link modules (If they are not getting automatically linked)

    iOS: Add pod

    {% 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')

    SDK Reference

    Overview

    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.

    Agent


    async agent.start(): void

    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
    )

    async agent.stop(): void

    Stops the agent cleanly so it can be used later

    await agent.stop()

    async agent.delete(): void

    Deletes the agent and all data associated with it, essentially resetting the wallet

    await agent.delete()

    DidExchange


    async didExchange.acceptOutOfBandInvitation(): DidExchangeRecord

    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
    )

    async didExchange.acceptResponse(): DidExchangeRecord

    Completes the didExchange handshake by sending a complete message to agent associated with the provided didExchangeId

    const didExchangeRecord = await agent.didExchange.acceptResponse(
        didExchangeId: String
    )

    async didExchange.requestConnection(): DidExchangeRecord

    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

    onRecordEvent
  • onProofEvent

  • onWebsocketEvent

  • onBasicMessageEvent

  • async didExchange.sendPing(): TrustPingMessage

    async didExchange.returnWhenIsConnected(): DidExchangeStateChangedEvent?

    async didExchange.getAll(): Array[DidExchangeRecord]

    async didExchange.findAllByQuery(): Array[DidExchangeRecord]

    async didExchange.getById(): DidExchangeRecord

    async didExchange.findById(): DidExchangeRecord?

    async didExchange.deleteById(): void

    async didExchange.findAllByOutOfBandId(): Array[DidExchangeRecord]

    async didExchange.findByDid(): DidExchangeRecord?

    async didExchange.findByInvitationDid(): DidExchangeRecord?

    Out Of Band

    async outOfBand.createInvitation(): OutOfBandRecord

    outOfBand.parseInvitation(): OutOfBandInvitationMessage

    async outOfBand.receiveInvitation(): AcceptInvitationResponse

    async outOfBand.receiveImplicitInvitation(): AcceptInvitationResponse

    async outOfBand.acceptInvitation(): AcceptInvitationResponse

    async outOfBand.findByInvitationId(): OutOfBandRecord?

    async outOfBand.findByCreatedInvitationId(): OutOfBandRecord?

    async outOfBand.getAll(): Array[OutOfBandRecord]

    async outOfBand.getAllByQuery(): Array[OutOfBandRecord]

    async outOfBand.getById(): OutOfBandRecord

    async outOfBand.findById(): OutOfBandRecord?

    async outOfBand.deleteById(): void

    Credentials

    async credentials.findAllCredentialsBySchemaId(): Array[CredentialRecord]

    async credentials.proposeCredential(): CredentialExchangeRecord

    async credentials.acceptOffer(): CredentialExchangeRecord

    async credentials.acceptCredential(): CredentialExchangeRecord

    async credentials.findByRecordId(): CredentialExchangeRecord?

    async credentials.findAllByState(): Array[CredentialExchangeRecord]

    async credentials.findAllByStateAndDidExchangeId(): Array[CredentialExchangeRecord]

    async credentials.getAll(): Array[CredentialExchangeRecord]

    async credentials.findByThreadIdAndDidExchangeId(): CredentialExchangeRecord?

    async credentials.getByThreadIdAndDidExchangeId(): CredentialExchangeRecord

    Proofs

    async proofs.autoAcceptProof(): ProofRecord

    async proofs.acceptProof(): ProofRecord

    async proofs.getCredentialsForProofRequest(): PresentationData

    async proofs.autoSelectCredentialsForProofRequest(): PresentationData

    async proofs.getProofRequestsForConnection(): Array[ProofRecord]

    Routing

    async routing.initialize(): void

    async routing.initiateMessagePickup(): void

    async routing.findDefaultMediator(): MediationRecord?

    async routing.discoverMediation(): MediationRecord?

    async routing.setDefaultMediator(): MediationRecord

    async routing.requestMediation(): MediationRecord

    async routing.getByExchangeId(): MediationRecord

    async routing.findByExchangeId(): MediationRecord

    async routing.getMediators(): Array[MediationRecord]

    async routing.findDefaultMediatorExchange(): DidExchangeRecord?

    async routing.provision(): MediationRecord?

    async routing.getRouting(): Routing

    Basic Messaging

    async basicMessages.send(): BasicMessageRecord

    async basicMessages.findById(): BasicMessageRecord

    async basicMessages.getAll(): Array

    async basicMessages.findByComment(): Array[BasicMessageRecord]

    async basicMessages.findByDidExchangeId(): Array[BasicMessageRecord]

    async basicMessages.findByRole(): Array[BasicMessageRecord]

    Events

    React Native

    Kotlin

    Swift

    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)

    Proofs

    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.

    1

    Auto Accepting

    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.

    Kotlin

    Auto-accept proof (Kotlin)
    // 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}")
    }

    React Native

    Auto-accept proof (React Native / TypeScript)
    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.

    2

    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

    object. With the auto-select function having some data already filled in.

    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:

    Manual acceptance - autoSelect or getCredentials (Kotlin)
    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
    )
    Manual acceptance - autoSelect or getCredentials (React Native / TypeScript)
    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
        )
    })

    Manual Acceptance

    Kotlin

    React Native

    Auto-selection handling (Kotlin)
    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)
    Auto-selection handling (React Native / TypeScript)
    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)
    Manual selection (Kotlin)
    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)
    Manual selection (React Native / TypeScript)
    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)
    PresentationData structure and helpers

    The PresentationData object contains four properties: proofId, exchangeId, attributes, and predicates (the last two being arrays). It also contains helper functions:

    • getRemaining — returns an array of the remaining proofReferent that do not have a selection.

    • isReady — returns a boolean that indicates if a valid selection has been made for all proofReferents.

    • autoSelect — does an in-place auto selection of the provided credentials and returns a boolean indicating if it successfully made selections for all request referents.

    • getRequiredSelfAttested — returns a list of all the attributes that are required to be self-attested.

    The attributes and predicates arrays contain objects called attributeReferent and predicateReferent (two types of proofReferent). These objects contain the properties canSelfAttest, requiredSelfAttest, and selfAttestedValue. These properties pertain to self-attesting during a proof (only available with an attribute and not a predicate).

    • canSelfAttest indicates if the value can be self-attested.

    • requiredSelfAttest indicates if the value must be self-attested.

    • selfAttestedValue is a mutable string that should be set to data not in a credential (e.g., user input or from elsewhere in the app).

    Each proofReferent also has three object properties: selection, credentials, and request.

    • selection is the credential match you want to use to satisfy the proofReferent. It can be set using the selectCredential function which takes a credentialMatch object or an index corresponding to a value in the credentials array.

    • credentials is an array of all the credentials that satisfy the proofReferent

    Differences between attributeReferent and predicateReferent:

    • predicateReferent also has a requirement string that indicates the relational requirement for the requested data.

    • Predicates should not be self-attested and thus should always return false for self-attested values.

    Filling in self-attested values is intended to be done in place. If an optional self-attested attribute is supplied it will be given priority over any supplied credential value.

    Auto selection flow (processing returned PresentationData)

    Manual Selection

    .
  • request is the actual object that indicates what data is being requested from the wallet.