All pages
Powered by GitBook
1 of 2

Loading...

Loading...

Holdr+

With Holdr+, Indicio has your digital wallet needs covered.

  • Holdr+ allows someone using a mobile device to receive, hold, and present verifiable credentials and to create and establish secure, mutually authenticated communication between parties.

    • Supports governance file establishing who trusted issuers of credentials are.

    • Supports a variety of credential types and protocols including OID4VCI, OID4VP, AnonCreds, SD-JWTs, JSON-LD, and Open Badges 3.0.

    • Compliant with W3C Verifiable Credential Standards and the only wallet compatible with the DIF Credential Trust Establishment specification.

  • Indicio’s fully deployed mobile digital wallet application is available to download in the app stores today!

  • Download on or .

A “must have” capability

Gartner Research describes a digital wallet as a “must-have” capability in its 2024 Market Report on Decentralized Identity, predicting that “by 2026, at least 500 million smartphone users will be regularly making verifiable claims using a digital identity wallet built on distributed ledger technology.

Apple
Android

Holdr Integration Tutorial

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.

Adding the Holdr SDK to Your Project

iOS

  1. Open your iOS project in XCode and click on your project name under the “TARGETS” section.

  2. Scroll down in the “General” tab until you see the “Frameworks, Libraries, and Embedded Content” section of the project.

  3. Click on the + button under “Frameworks, Libraries, and Embedded Content.”

  4. Select “Add Files…” in the bottom left dropdown and select “Proven SDK XCFramework” to add it to your project.

To consume the Holdr SDK in Android, you must have access to Maven repos hosted on GitHub. For this you must add your GitHub username and a GitHub personal access token with read permissions to your local.properties. You will access those values in your build.gradle.

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

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

  1. Make sure your AndroidManifest.xml file includes the following permissions.

You will want to add @holdr/core and @holdr/react-native to your project.

  1. Place holdr-core-v1.1.0.tgz and holdr-react-native-v1.1.0.tgz in a directory parallel to your package.json.

  2. In package.json, add them both as dependencies.

  1. Call yarn install.

  1. Add repositories to android/build.gradle and confirm minSdkVersion is greater than or equal to 24.

Manually Link Modules (If They Are Not Getting Automatically Linked)

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. Follow the instructions for either iOS or Android:

  1. Add the pod to ios/Podfile.

  1. Reinstall the pods.

  1. Add the project to android/settings.gradle.

  1. Add module to dependency in android/app/build.gradle.

  1. Import and link package in android/app/src/main/java/com/example/MainApplication.java.

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

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 minimum and maximum amount of time in a back-off strategy on which to try to contact the mediator.

The pool config is used to indicate what networks or 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 or ledger.

The second value 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 is the indyNamespace. This string value is the name by which the network or ledger is known and is prepended to schemas and credential definitions to identify what network or 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 objects must be created to customize the Agent. Additionally, to create a pool config from a URL pointing to genesis files, you must pass the ProvenReactNative object imported from the @holdr/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.

Events are handled as flows in Kotlin and can be retrieved from the agent.

Kotlin

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 as follows:

  • getDidExchangeEvents()

  • getAgentEvents()

  • getCredentialEvents

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 the 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 named function returns null only if the Agent initializes with errors.

Once you have the events, you can perform operations on them to filter for certain IDs or states to complete processes or inform the user when things are occurring.

If, for some reason, you need to wait for the agent to complete an action you should use the Kotlin or Swift (using KMPN NativeCoroutines) directions following this paragraph.

In Kotlin the events use flows, this allows the use of the on all events.

Swift uses (1.0.0-ALPHA-23) as a dependency that allows the Kotlin flows to be turned into native Swift observables, asyncSequences and potentially more.

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

Objective-C

Swift

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, register a handler function that will be called on all events of the specified type.

The return of registering a handler is a function that will remove or unregister the handler. Calling the removal function stops the given handler from being called on any future events.

All connections are made through the out-of-band protocol using the out-of-band module on the Agent.

  1. Get an out-of-band invitation. This is done using one of the two following methods: either through a QR code that resolves to a URL (most common) or from a JSON object in the form of a string.

  1. Have the Agent accept the invitation. The receiveInvitation function has many options. To automatically make a connection with the provided invitation, supply just the invitation like the example below.

  1. While it is not recommended to disable auto acceptance, manually accepting a connection is an option as shown in the following code example:

Once a connection has been made through the didExchange protocol, the agent keeps a record that holds information about the established connection. A list of all connections can be retrieved using the following code example. This list can be used to create a contact list.

The didExchange record itself has several properties that are derived when making a connection based on information provided from the connected agent. Likely properties are as follows:

  • Did

  • State

  • Role

Some properties are found on all records and some are specific to didExchange. The updatedAt and createdAt properties are on all records along with the id property that is a UUID to identify the record. 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 the connections so that you can sort through them to find a specific connection or in rare cases find connections 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 would simply have to delete the didExchangeRecord. If you need to reconnect, you will have to receive another invitation from the agent. Invitations can be reused in some instances (NOTE: single use invitations can only be used by one agent while multi-use can be used by multiple).

Deleting a connection is done as follows:

Agents can be configured to automatically or manually accept credentials. 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, use the credential events to determine when a credential has been offered.

The below block accepts the offer coming from an external issuing Agent. The issuer then sends a credential-issuance message that needs final confirmation before the credential is saved and the issuer is notified of completion of issuance.

The following code block has the Agent confirm that it accepts the issued credential. It also notifies the issuing Agent we have accepted and stored the credential, and the transaction should be recorded on the ledger. This second confirmation is done to ensure that the credential you were offered matches what you actually received.

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. After being accepted, they will have a populated credential attribute that contains information about the accepted credential. The credential attribute allows a developer using the SDK to fetch more detailed information about the credential, such as the credential definition ID or schema ID. (These values are not commonly used but may need to be checked in some rare use cases.)

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.

Manual acceptance has two possible flows. One, the agent can auto select credentials, return them to you, and allow you to fill in any self-attested values. Or two, the agent returns all potential credentials that satisfy the proof. Both options will throw a ProvenError if the wallet does not contain sufficient credentials for the proof request.

The return from both of these functions are a PresentationData object, with the auto select function having some data already filled in.

The PresentationData object contains four properties: proofId, exchangeId, attributes, and predicates. The last two are arrays. It also contains some 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

The attributes and predicates arrays contain two similar objects called attributeReferent and predicateReferent which are the two types of proofReferents. The objects contain three properties that all pertain to self-attesting during a proof, which is only available with an attribute and not a predicate value:

  • canSelfAttest: indicates if the value has to be self attested or not

  • requiredSelfAttest: indicates if the value must be self attested

  • selfAttesdedValue: is a mutable string that should be set to data not in a credential (ie. user input or from elsewhere in the app).

There are three object properties on the proofReferent objects.

  • selection: is the credential match that you 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

The differences between attributeReferent and predicateReferent are that the predicateReferent also has a requirement string that indicates the relationship the requested data needs to have to the requested value. Additionally predicates should not be self attested and as such should always return false self-attested values.

Self-attested values are intended to be entered in-place or as part of your written code. They are not automatically provided by the credential. If an optional self-attested attribute is supplied, it will be given priority over any supplied credential value.

The code below will go through processing the return from both functions starting with auto selection.

Self-attested attributes function the same way for manual selection.

Manual selection requires that a credential from the return 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.

getEventBusEvents()
  • getMessageEvents()

  • getProofEvents()

  • getTrustPingEvents()

  • getWebsocketEvents()

  • onAgentEvent
  • onRecordEvent

  • onProofEvent

  • onWebsocketEvent

  • theirDid
  • theirLabel

  • createdAt

  • updatedAt

  • Alias

  • threadId

  • mediatorId

  • outOfBandId

  • invitationDid

  • autoAccept

  • : does an in-place auto-selection of the provided credentials and returns a Boolean indicating if it successfully made a selection for all requests referents.
  • getRequiredSelfAttested: returns a list of all attributes required to be self-attested.

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

  • // 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/holdr-sdk-release")
            credentials {
                username = readLocalProperty("githubUsername")
                password = readLocalProperty("githubToken")
            }
        }
        maven {
            setUrl("https://maven.pkg.github.com/LF-Decentralized-Trust-labs/aries-uniffi-wrappers")
            credentials {
                username = readLocalProperty("githubUsername")
                password = readLocalProperty("githubToken")
            }
        }
    }
    
    dependencies {
        implementation("tech.indicio:holdr: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": {
        "@holdr/react-native": "file:holdr-react-native-v1.1.0.tgz",
        "@holdr/core": "file:holdr-core-v1.1.0.tgz",
    }
    
    buildscript {
        ext {
            minSdkVersion = 24
        }
    }   
    allprojects{
        repositories{
            maven {
                setUrl("https://maven.pkg.github.com/indicio-tech/holdr-sdk-release")
                credentials {
                    username = readLocalProperty("githubUsername")
                    password = readLocalProperty("githubToken")
                }
            }
            maven {
                setUrl("https://maven.pkg.github.com/LF-Decentralized-Trust-labs/aries-uniffi-wrappers")
                credentials {
                    username = readLocalProperty("githubUsername")
                    password = readLocalProperty("githubToken")
                }
            }
        }
    }
    target 'ExampleApp' do
        <!-- Other configs -->
    +   pod 'rtn-proven-mobile-sdk', :path => '../node_modules/@holdr/react-native'
    
        target 'ExampleAppTests' do
    
            <!-- Other configs -->
    <!-- Other configs -->
    +include ':holdr-react-native
    +project(':holdr-react-native').projectDir = new File(rootProject.projectDir, '../node_modules/@holdr/react-native/android')
    
    dependencies{
        <!-- Other dependencies -->
    +    implementation project(":holdr-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;
            }
    
    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"
    )
    let walletConfig = WalletConfig(
        uri: "sqlite://pathWhereYouWantDataStored/local.db",
        keyMethod: "raw",
        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
    )
    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 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(
        license: licenseString, // SDK license as a string
        walletConfig = walletConfig,
        mediationConfig = mediationConfig, // Set to null if you do not need didcomm
        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(
        license: licenseString, // SDK license as a string
        walletConfig: walletConfig,
        mediationConfig: mediationConfig, // Set to nil if not using didcomm
        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 '@holdr/react-native';
    import {
        Agent,
        IndyPoolConfig,
        type MediationConfig,
        type WalletConfig
    } from '@holdr/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 '@holdr/react-native';
    
    const agent = new Agent(
        ProvenReactNative,
        provenEventsFactory,
        licenseString,
        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();
    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 request connection 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
    
    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
    // Turns the didExchangeEvents into a swift asyncSequence
    let credentialEvents = asyncSequence(for: agent.events.getCredentialEvents()!.events)
    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,
            )
          }
        })
    
    didExchangeRemove()
      // The url commonly comes from the result of scanning a QR code.
        val invitation = OutOfBandInvitationMessage.fromUrl(url)
        // Or if you have the message in json already
        val invitation = OutOfBandInvitationMessage.fromJsonString(str)
      // parses the URL to a json string
        const invitation: string = agent.outOfBand.parseInvitation(invitationUrl)
    
      // Completed connection status may or may not be done after this call
        val records = agent.outOfBand.receiveInvitation(invitation)
    
        //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
            }
        }
    // Takes the json formatted string for the invitation
        const records = await 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()
                  }
        })
    
    // 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)
    // 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)
      val credEvents = agent.events.getCredentialEvents()!! // Assuming agent initialized normally
    
        val offers = credEvents.events.filter {
            it.credentialExchangeRecord.state == CredentialState.OfferReceived
        }
    
        offers.onEach{
            // present to user
            if(userAccept)
                agent.credentials.acceptOffer(it.credentialExchangeRecord.id)
        }.collect()
      agent.events.registerCredentialHandler((event) => {
            if(event.credentialExchangeRecord.state === CredentialState.OfferReceived)
                // Present to user somehow
                if(userAccept)
                    await agent.credentials.acceptOffer(event.credentialExchangeRecord.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")
      //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}`)
                }
            }
        )
      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"
            }
        }
    
        // This code provides a self attested value for all values that can be self attested but not all values have to be.
    
        // Supply the originally returned object that has been modified in place
        agent.proofs.acceptProofs(proofData)
      await const proofData = 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)
      await const proofData = 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)

    Android

    We recommend you add Github Authentication to local.properties or other secrets file.

    Android Installation

    React Native

    Installation

    Android

    iOS

    Android

    Creating an Agent

    Wallet Config

    Kotlin

    Swift

    Mediation Config

    If you are not using DIDcomm, you can pass null for the mediation config because you do not need a mediator.

    Kotlin

    Swift

    Pool Config

    Kotlin

    Swift

    Configuring an Agent

    Kotlin

    Swift

    React Native

    Events

    The following examples of event flows do not include the code needed for launching them in a background coroutine scope. That code needs to be added in your Kotlin code to take the handlers off of the main thread.

    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.

    Kotlin

    Swift (Using KMPNativeCoroutines)

    Swift/Objective-C (Without KMPNativeCoroutines)

    React Native

    Registered events will not persist after the app has been closed.

    Creating a Connection

    Kotlin

    React Native

    Kotlin

    React Native

    Kotlin

    React Native

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

    Retrieving and Using the didExchange Records

    Kotlin

    React Native

    Removing a Connection

    Kotlin

    React Native

    Credentials

    Kotlin

    React Native

    Kotlin

    React Native

    Kotlin

    React Native

    Proofs

    Auto Accepting

    Kotlin

    React Native

    This only works if there are not self-attested attributes for the request and the wallet contains sufficient credentials.

    Manual Acceptance

    Kotlin

    React Native

    Auto selection

    React Native

    Manual Selection

    React Native

    found here
    found here
    Kotlin Asynchronous Flow
    KMPNativeCoroutines