Accepting New Credentials

Accept and store incoming verifiable credentials from Issuers, and manage credentials mid-issuance.

After establishing an end-to-end encrypted connection with peers, the agent is ready to begin negotiating and receiving credentials from credential issuers they are connected with. The CredentialExchangeModule provides the functionality needed to easily walk through the Decentralized Identity protocol flow for accepting and storing a credential.

The functionality of the CredentialExchangeModule is accessed via the agent's field agent.credentials.exchange. The functionality provided is described below.

Receive a Credential Offer

A CredentialExchange (a pending credential) is created in the agent's wallet once a credential offer is received. The agent receives these offers as encrypted messages, either through the receiveMessage API, or via the the MessageSource of the agent's run loop. The credential offer must come from an established connection, and the CredentialExchange will contain information about which connection the offer came from.

Additionally, a CredentialExchange will show the metadata of what credential is being exchanged, and what the attributes/values of that credential are. These attributes/values can be reviewed by consumers before accepting the offer.

After an offer is decrypted and processed by the agent, a CredentialExchange is created in the agent's wallet and can be accessed and used by the APIs mentioned below. If the agent is subscribed to agent events (see here), a new CredentialExchange will invoke a credential update event.

Accepting a Credential Offer

With a CredentialExchange in the OFFER state, the agent can "accept" that offer, which will send off a credential request reply to the Issuer in accordance with Aries protocols.

Note that after calling the acceptOffer API, the credential has not yet been issued nor stored. The Issuer will still have to approve the request and respond again with the issued credential data. The status updates of the issuance process can be tracked via agent events.

The acceptOffer API takes the identifier of the CredentialExchange to accept, and a configuration for how the agent should behave after accepting.

The AcceptCredentialOfferConfiguration data object allows the following behavior to be adjusted:

  • autoStoreCredential - a flag for whether the credential should be automatically stored in the wallet once the Issuer responds with the issued credential. If false, then the consumer is responsible for calling the Store Credential API once the CredentialExchange receives the "issued" state.

  • storeCredentialConfiguration - a configuration for how the credential should be auto-stored (if autoStoreCredential is configured as true). Allows for custom behavior, such as automatically applying certain tags to the stored Credential.

let id: String // ID of the cred exchange to accept ([CredentialExchange.credentialExchangeId])

do {
    let configuration = AcceptCredentialOfferConfiguration()
    try await agent.credentials.exchange.acceptOffer(credentialExchangeId: id, configuration: configuration)
} catch {
    // handle error
}

Store a Credential

When a CredentialExchange reaches the ISSUED state, and has not been configured to auto-store, then the agent must call the storeCredential API before the Credential is stored in the wallet, completing the Aries protocol.

The API takes the identifier of the CredentialExchange to store the credential of, and a configuration for how the agent should behave when storing the credential.

The StoreCredentialConfiguration data object can be used to overwrite tags that the Credential will be stored with. It could also be left as null to leave the tags as their default.

The API will also return the new Credential that was stored as a result of the API call. This new credential will also now be available in the agent's Credential management module.

let id: String // ID of the cred exchange to store ([CredentialExchange.credentialExchangeId])

do {
    let configuration = StoreCredentialConfiguration()
    let newCredential: Credential = try await agent.credentials.exchange.storeCredential(
        credentialExchangeId: id,
        configuration: configuration
    )
} catch {
    // handle error
}

There is no need to call this API if the agent is configured to auto-store credentials.

Get a Pending Credential by ID

As mentioned above, a credential pending issuance is represented by CredentialExchange objects. To retrieve the current state of a specific CredentialExchange in the agent's wallet, the getById API can be used. If a credential exchange cannot be found by the given identifier, then null is returned:

let id: String // ID of the cred exchange to get ([CredentialExchange.credentialExchangeId])

do {
    let credentialExchange = try await agent.credentials.exchange.getById(credentialExchangeId: id)
} catch {
    // handle error
}

Delete a Pending Credential by ID

Similarly, a CredentialExchange in the wallet can be easily deleted via the deleteById API:

let id: String // ID of the cred exchange to delete ([CredentialExchange.credentialExchangeId])

do {
    try await agent.credentials.exchange.deleteById(credentialExchangeId: id)
} catch {
    // handle error
}

Updating the Metadata of a Pending Credential

CredentialExchange objects contain some metadata that can be controlled by SDK consumers, allowing custom information to be attached to each CredentialExchange, and allowing custom listing functionality to be leveraged.

Each CredentialExchange contains a list of RecordTag (CredentialExchange.tags) attached to it, where a RecordTag is simply a name-value pair stored with the record. By default, some tags are attached to a new CredentialExchange, this includes:

  • tag-name: ~started_timestamp, tag-value: The UNIX epoch seconds which this credential began exchange

The tags on a CredentialExchange can be replaced or updated by using the updateCredentialExchange API, and providing a new set to update. This will replace whatever the current set of tags is:

let id: String // ID of the credential exchange to update

// add a 'category' of 'work' to this credential, and a 'priority' of '1'
let update = CredentialExchangeUpdate(tags: [
    RecordTag(name: "category", value: "work"),
    RecordTag(name: "~priority", value: "1")
])

do {
    try await agent.credentials.exchange.updateCredentialExchange(credentialExchangeId: id, credentialExchangeUpdate: update)
} catch {
    // handle error
}

Like most data in the wallet, RecordTag will be stored encrypted. Unless, the tag name is prefixed with ~, then the tag value will be stored unencrypted. Storing a tag value as unencrypted will allow some additional listing queries to be performed (see below).

Listing Pending Credentials

To list all pending credentials in the agent's wallet, the listAll API can be used:

do {
    let credentialExchanges = try await agent.credentials.exchange.listAll(options: nil)
} catch {
    // handle error
}

Filtered Listing

More complicated CredentialExchange list queries can also be achieved by utilizing the ListCredentialExchangeFilters.

These filters allow for a list of CredentialExchange to be filtered by their state, tags, or both together.

Filtering by state can be achieved as follows.

// get all the pending credentials which are waiting for their offer to be accepted
let filters = ListCredentialExchangeFilters(state: CredentialExchangeState.offer)
let options = ListCredentialExchangeOptions(filters: filters)
do {
    let credentialExchanges = try await agent.credentials.exchange.listAll(options: options)
} catch {
    // handle error
}

To filter by tags applied to the CredentialExchange (i.e. applied via the update API), the tagFilter field of ListCredentialExchangeFilters should be used. This field takes a String in compliance with a Wallet Query Language (WQL) Query.

Continuing from the example in the Update API section:

// WQL Query, filter for 'category' == 'work'
let wqlQuery = "{ \"category\": \"work\" }"
let filters = ListCredentialExchangeFilters(tagFilter: wqlQuery)
let options = ListCredentialExchangeOptions(filters: filters)
do {
    let pendingWorkCredentials = try await agent.credentials.exchange.listAll(options: options)
} catch {
    // handle error
}
// WQL Query, filter for priority < 2 (e.g. 'high' priority items)
let wqlQuery = "{ \"~priority\": { \"$lt\": \"2\" } }"
let filters = ListCredentialExchangeFilters(tagFilter: wqlQuery)
let options = ListCredentialExchangeOptions(filters: filters)

do {
    let highPriorityPendingCredentials = try await agent.credentials.exchange.listAll(options: options)
} catch {
    // Handle error
}

Last updated