diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 555bcf34e5..4f4cfed1e3 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -68,9 +68,12 @@ jobs: - name: Prettier run: yarn check-format - - name: Compile + - name: Check Types run: yarn check-types + - name: Compile + run: yarn build + integration-test: runs-on: ubuntu-20.04 name: Integration Tests diff --git a/demo/src/Alice.ts b/demo/src/Alice.ts index 4c6cdc16a0..7822257b36 100644 --- a/demo/src/Alice.ts +++ b/demo/src/Alice.ts @@ -1,5 +1,5 @@ /*eslint import/no-cycle: [2, { maxDepth: 1 }]*/ -import type { CredentialRecord, ProofRecord } from '@aries-framework/core' +import type { CredentialExchangeRecord, ProofRecord } from '@aries-framework/core' import { BaseAgent } from './BaseAgent' import { greenText, Output, redText } from './OutputClass' @@ -53,9 +53,10 @@ export class Alice extends BaseAgent { await this.waitForConnection() } - public async acceptCredentialOffer(credentialRecord: CredentialRecord) { - await this.agent.credentials.acceptOffer(credentialRecord.id) - console.log(greenText('\nCredential offer accepted!\n')) + public async acceptCredentialOffer(credentialRecord: CredentialExchangeRecord) { + await this.agent.credentials.acceptOffer({ + credentialRecordId: credentialRecord.id, + }) } public async acceptProofRequest(proofRecord: ProofRecord) { diff --git a/demo/src/AliceInquirer.ts b/demo/src/AliceInquirer.ts index a4b463905d..44ae7432c4 100644 --- a/demo/src/AliceInquirer.ts +++ b/demo/src/AliceInquirer.ts @@ -1,4 +1,4 @@ -import type { CredentialRecord, ProofRecord } from '@aries-framework/core' +import type { CredentialExchangeRecord, ProofRecord } from '@aries-framework/core' import { clear } from 'console' import { textSync } from 'figlet' @@ -69,7 +69,7 @@ export class AliceInquirer extends BaseInquirer { await this.processAnswer() } - public async acceptCredentialOffer(credentialRecord: CredentialRecord) { + public async acceptCredentialOffer(credentialRecord: CredentialExchangeRecord) { const confirm = await inquirer.prompt([this.inquireConfirmation(Title.CredentialOfferTitle)]) if (confirm.options === ConfirmOptions.No) { await this.alice.agent.credentials.declineOffer(credentialRecord.id) diff --git a/demo/src/Faber.ts b/demo/src/Faber.ts index 22c8f941a3..019d751631 100644 --- a/demo/src/Faber.ts +++ b/demo/src/Faber.ts @@ -2,7 +2,13 @@ import type { ConnectionRecord } from '@aries-framework/core' import type { CredDef, Schema } from 'indy-sdk' import type BottomBar from 'inquirer/lib/ui/bottom-bar' -import { AttributeFilter, CredentialPreview, ProofAttributeInfo, utils } from '@aries-framework/core' +import { + CredentialProtocolVersion, + V1CredentialPreview, + AttributeFilter, + ProofAttributeInfo, + utils, +} from '@aries-framework/core' import { ui } from 'inquirer' import { BaseAgent } from './BaseAgent' @@ -62,23 +68,23 @@ export class Faber extends BaseAgent { this.printSchema(schemaTemplate.name, schemaTemplate.version, schemaTemplate.attributes) this.ui.updateBottomBar(greenText('\nRegistering schema...\n', false)) const schema = await this.agent.ledger.registerSchema(schemaTemplate) - this.ui.updateBottomBar('\nSchema registerd!\n') + this.ui.updateBottomBar('\nSchema registered!\n') return schema } - private async registerCredentialDefiniton(schema: Schema) { + private async registerCredentialDefinition(schema: Schema) { this.ui.updateBottomBar('\nRegistering credential definition...\n') this.credentialDefinition = await this.agent.ledger.registerCredentialDefinition({ schema, tag: 'latest', supportRevocation: false, }) - this.ui.updateBottomBar('\nCredential definition registerd!!\n') + this.ui.updateBottomBar('\nCredential definition registered!!\n') return this.credentialDefinition } private getCredentialPreview() { - const credentialPreview = CredentialPreview.fromRecord({ + const credentialPreview = V1CredentialPreview.fromRecord({ name: 'Alice Smith', degree: 'Computer Science', date: '01/01/2022', @@ -88,14 +94,21 @@ export class Faber extends BaseAgent { public async issueCredential() { const schema = await this.registerSchema() - const credDef = await this.registerCredentialDefiniton(schema) + const credDef = await this.registerCredentialDefinition(schema) const credentialPreview = this.getCredentialPreview() const connectionRecord = await this.getConnectionRecord() this.ui.updateBottomBar('\nSending credential offer...\n') - await this.agent.credentials.offerCredential(connectionRecord.id, { - credentialDefinitionId: credDef.id, - preview: credentialPreview, + + await this.agent.credentials.offerCredential({ + connectionId: connectionRecord.id, + protocolVersion: CredentialProtocolVersion.V1, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: credDef.id, + }, + }, }) this.ui.updateBottomBar( `\nCredential offer sent!\n\nGo to the Alice agent to accept the credential offer\n\n${Color.Reset}` diff --git a/demo/src/Listener.ts b/demo/src/Listener.ts index 393edf3919..97f98c741a 100644 --- a/demo/src/Listener.ts +++ b/demo/src/Listener.ts @@ -5,7 +5,7 @@ import type { FaberInquirer } from './FaberInquirer' import type { Agent, BasicMessageStateChangedEvent, - CredentialRecord, + CredentialExchangeRecord, CredentialStateChangedEvent, ProofRecord, ProofStateChangedEvent, @@ -41,7 +41,7 @@ export class Listener { this.on = false } - private printCredentialAttributes(credentialRecord: CredentialRecord) { + private printCredentialAttributes(credentialRecord: CredentialExchangeRecord) { if (credentialRecord.credentialAttributes) { const attribute = credentialRecord.credentialAttributes console.log('\n\nCredential preview:') @@ -51,7 +51,7 @@ export class Listener { } } - private async newCredentialPrompt(credentialRecord: CredentialRecord, aliceInquirer: AliceInquirer) { + private async newCredentialPrompt(credentialRecord: CredentialExchangeRecord, aliceInquirer: AliceInquirer) { this.printCredentialAttributes(credentialRecord) this.turnListenerOn() await aliceInquirer.acceptCredentialOffer(credentialRecord) diff --git a/docs/getting-started/0-agent.md b/docs/getting-started/0-agent.md index 07ca2edaa8..007b30e8ad 100644 --- a/docs/getting-started/0-agent.md +++ b/docs/getting-started/0-agent.md @@ -137,7 +137,7 @@ const agentConfig: InitConfig = { isProduction: false, }, ], - logger: new ConsoleLogger(LogLevel.debug), + logger: new ConsoleLogger(LogLevel.info), } const agent = new Agent(agentConfig, agentDependencies) diff --git a/docs/getting-started/7-logging.md b/docs/getting-started/7-logging.md index 0026681def..508f634700 100644 --- a/docs/getting-started/7-logging.md +++ b/docs/getting-started/7-logging.md @@ -9,7 +9,7 @@ import { ConsoleLogger, LogLevel } from '@aries-framework/core' const agentConfig = { // ... other config properties ... - logger: new ConsoleLogger(LogLevel.debug), + logger: new ConsoleLogger(LogLevel.info), } ``` diff --git a/package.json b/package.json index da3330b92b..6adc64771c 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@types/jest": "^26.0.23", "@types/node": "^15.14.4", "@types/uuid": "^8.3.1", + "@types/varint": "^6.0.0", "@types/ws": "^7.4.6", "@typescript-eslint/eslint-plugin": "^4.26.1", "@typescript-eslint/parser": "^4.26.1", diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index cf3b97c0c3..62252529cf 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -254,7 +254,7 @@ export class Agent { } public async receiveMessage(inboundMessage: unknown, session?: TransportSession) { - return await this.messageReceiver.receiveMessage(inboundMessage, session) + await this.messageReceiver.receiveMessage(inboundMessage, session) } public get injectionContainer() { diff --git a/packages/core/src/agent/__tests__/Agent.test.ts b/packages/core/src/agent/__tests__/Agent.test.ts index 588b2442f7..b56eb476fe 100644 --- a/packages/core/src/agent/__tests__/Agent.test.ts +++ b/packages/core/src/agent/__tests__/Agent.test.ts @@ -8,7 +8,7 @@ import { ConnectionsModule } from '../../modules/connections/ConnectionsModule' import { ConnectionRepository } from '../../modules/connections/repository/ConnectionRepository' import { ConnectionService } from '../../modules/connections/services/ConnectionService' import { TrustPingService } from '../../modules/connections/services/TrustPingService' -import { CredentialRepository, CredentialService } from '../../modules/credentials' +import { CredentialRepository } from '../../modules/credentials' import { CredentialsModule } from '../../modules/credentials/CredentialsModule' import { IndyLedgerService } from '../../modules/ledger' import { LedgerModule } from '../../modules/ledger/LedgerModule' @@ -123,7 +123,6 @@ describe('Agent', () => { expect(container.resolve(ProofRepository)).toBeInstanceOf(ProofRepository) expect(container.resolve(CredentialsModule)).toBeInstanceOf(CredentialsModule) - expect(container.resolve(CredentialService)).toBeInstanceOf(CredentialService) expect(container.resolve(CredentialRepository)).toBeInstanceOf(CredentialRepository) expect(container.resolve(BasicMessagesModule)).toBeInstanceOf(BasicMessagesModule) @@ -167,7 +166,6 @@ describe('Agent', () => { expect(container.resolve(ProofRepository)).toBe(container.resolve(ProofRepository)) expect(container.resolve(CredentialsModule)).toBe(container.resolve(CredentialsModule)) - expect(container.resolve(CredentialService)).toBe(container.resolve(CredentialService)) expect(container.resolve(CredentialRepository)).toBe(container.resolve(CredentialRepository)) expect(container.resolve(BasicMessagesModule)).toBe(container.resolve(BasicMessagesModule)) diff --git a/packages/core/src/decorators/ack/AckDecorator.test.ts b/packages/core/src/decorators/ack/AckDecorator.test.ts index 152b014c3a..fe0ccba759 100644 --- a/packages/core/src/decorators/ack/AckDecorator.test.ts +++ b/packages/core/src/decorators/ack/AckDecorator.test.ts @@ -14,7 +14,13 @@ describe('Decorators | AckDecoratorExtension', () => { test('transforms AckDecorator class to JSON', () => { const message = new TestMessage() message.setPleaseAck() - expect(message.toJSON()).toEqual({ '~please_ack': {} }) + expect(message.toJSON()).toEqual({ + '@id': undefined, + '@type': undefined, + '~please_ack': { + on: ['RECEIPT'], + }, + }) }) test('transforms Json to AckDecorator class', () => { diff --git a/packages/core/src/decorators/ack/AckDecorator.ts b/packages/core/src/decorators/ack/AckDecorator.ts index cb04be571a..a647a1a49f 100644 --- a/packages/core/src/decorators/ack/AckDecorator.ts +++ b/packages/core/src/decorators/ack/AckDecorator.ts @@ -1,4 +1,21 @@ +import { IsArray, IsEnum } from 'class-validator' + +export enum AckValues { + Receipt = 'RECEIPT', + Outcome = 'OUTCOME', +} + /** * Represents `~please_ack` decorator */ -export class AckDecorator {} +export class AckDecorator { + public constructor(options: { on: [AckValues.Receipt] }) { + if (options) { + this.on = options.on + } + } + + @IsEnum(AckValues, { each: true }) + @IsArray() + public on!: AckValues[] +} diff --git a/packages/core/src/decorators/ack/AckDecoratorExtension.ts b/packages/core/src/decorators/ack/AckDecoratorExtension.ts index 8185c5ea38..059c734bcc 100644 --- a/packages/core/src/decorators/ack/AckDecoratorExtension.ts +++ b/packages/core/src/decorators/ack/AckDecoratorExtension.ts @@ -3,7 +3,7 @@ import type { BaseMessageConstructor } from '../../agent/BaseMessage' import { Expose, Type } from 'class-transformer' import { IsInstance, IsOptional, ValidateNested } from 'class-validator' -import { AckDecorator } from './AckDecorator' +import { AckDecorator, AckValues } from './AckDecorator' export function AckDecorated(Base: T) { class AckDecoratorExtension extends Base { @@ -14,8 +14,8 @@ export function AckDecorated(Base: T) { @IsOptional() public pleaseAck?: AckDecorator - public setPleaseAck() { - this.pleaseAck = new AckDecorator() + public setPleaseAck(on?: [AckValues.Receipt]) { + this.pleaseAck = new AckDecorator({ on: on ?? [AckValues.Receipt] }) } public getPleaseAck(): AckDecorator | undefined { diff --git a/packages/core/src/decorators/attachment/AttachmentExtension.ts b/packages/core/src/decorators/attachment/AttachmentExtension.ts index 58cec91ee0..67ab28d578 100644 --- a/packages/core/src/decorators/attachment/AttachmentExtension.ts +++ b/packages/core/src/decorators/attachment/AttachmentExtension.ts @@ -15,17 +15,17 @@ export function AttachmentDecorated(Base: T) { @ValidateNested() @IsInstance(Attachment, { each: true }) @IsOptional() - public attachments?: Attachment[] + public appendedAttachments?: Attachment[] - public getAttachmentById(id: string): Attachment | undefined { - return this.attachments?.find((attachment) => attachment.id === id) + public getAppendedAttachmentById(id: string): Attachment | undefined { + return this.appendedAttachments?.find((attachment) => attachment.id === id) } - public addAttachment(attachment: Attachment): void { - if (this.attachments) { - this.attachments?.push(attachment) + public addAppendedAttachment(attachment: Attachment): void { + if (this.appendedAttachments) { + this.appendedAttachments.push(attachment) } else { - this.attachments = [attachment] + this.appendedAttachments = [attachment] } } } diff --git a/packages/core/src/logger/ConsoleLogger.ts b/packages/core/src/logger/ConsoleLogger.ts index 0575e4cfff..5895f26ad8 100644 --- a/packages/core/src/logger/ConsoleLogger.ts +++ b/packages/core/src/logger/ConsoleLogger.ts @@ -2,7 +2,6 @@ import { BaseLogger } from './BaseLogger' import { LogLevel } from './Logger' - /* * The replacer parameter allows you to specify a function that replaces values with your own. We can use it to control what gets stringified. */ diff --git a/packages/core/src/modules/connections/errors/ConnectionProblemReportError.ts b/packages/core/src/modules/connections/errors/ConnectionProblemReportError.ts index d58d1bd14f..764be043f9 100644 --- a/packages/core/src/modules/connections/errors/ConnectionProblemReportError.ts +++ b/packages/core/src/modules/connections/errors/ConnectionProblemReportError.ts @@ -1,5 +1,5 @@ -import type { ConnectionProblemReportReason } from '.' import type { ProblemReportErrorOptions } from '../../problem-reports' +import type { ConnectionProblemReportReason } from './ConnectionProblemReportReason' import { ProblemReportError } from '../../problem-reports' import { ConnectionProblemReportMessage } from '../messages' diff --git a/packages/core/src/modules/connections/services/ConnectionService.ts b/packages/core/src/modules/connections/services/ConnectionService.ts index bc3cef2c26..7492ee7215 100644 --- a/packages/core/src/modules/connections/services/ConnectionService.ts +++ b/packages/core/src/modules/connections/services/ConnectionService.ts @@ -501,7 +501,8 @@ export class ConnectionService { } // Check if the inbound message recipient key is present - // in the recipientKeys of previously sent message ~service decorator + // in the recipientKeys of previously sent message ~service decorator() + if ( !previousSentMessage?.service || !previousSentMessage.service.recipientKeys.includes(messageContext.recipientVerkey) @@ -522,6 +523,7 @@ export class ConnectionService { // Check if the inbound message sender key is present // in the recipientKeys of previously received message ~service decorator + if ( !previousReceivedMessage.service || !previousReceivedMessage.service.recipientKeys.includes(messageContext.senderVerkey) diff --git a/packages/core/src/modules/credentials/CredentialEvents.ts b/packages/core/src/modules/credentials/CredentialEvents.ts index 29a136a14b..f49dd964ac 100644 --- a/packages/core/src/modules/credentials/CredentialEvents.ts +++ b/packages/core/src/modules/credentials/CredentialEvents.ts @@ -1,6 +1,6 @@ import type { BaseEvent } from '../../agent/Events' import type { CredentialState } from './CredentialState' -import type { CredentialRecord } from './repository/CredentialRecord' +import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' export enum CredentialEventTypes { CredentialStateChanged = 'CredentialStateChanged', @@ -9,7 +9,7 @@ export enum CredentialEventTypes { export interface CredentialStateChangedEvent extends BaseEvent { type: typeof CredentialEventTypes.CredentialStateChanged payload: { - credentialRecord: CredentialRecord + credentialRecord: CredentialExchangeRecord previousState: CredentialState | null } } @@ -17,6 +17,6 @@ export interface CredentialStateChangedEvent extends BaseEvent { export interface RevocationNotificationReceivedEvent extends BaseEvent { type: typeof CredentialEventTypes.RevocationNotificationReceived payload: { - credentialRecord: CredentialRecord + credentialRecord: CredentialExchangeRecord } } diff --git a/packages/core/src/modules/credentials/CredentialProtocolVersion.ts b/packages/core/src/modules/credentials/CredentialProtocolVersion.ts new file mode 100644 index 0000000000..5806577c30 --- /dev/null +++ b/packages/core/src/modules/credentials/CredentialProtocolVersion.ts @@ -0,0 +1,4 @@ +export enum CredentialProtocolVersion { + V1 = 'v1', + V2 = 'v2', +} diff --git a/packages/core/src/modules/credentials/CredentialResponseCoordinator.ts b/packages/core/src/modules/credentials/CredentialResponseCoordinator.ts index a463555665..4bb3ce4ebb 100644 --- a/packages/core/src/modules/credentials/CredentialResponseCoordinator.ts +++ b/packages/core/src/modules/credentials/CredentialResponseCoordinator.ts @@ -1,11 +1,9 @@ -import type { CredentialRecord } from './repository' - import { scoped, Lifecycle } from 'tsyringe' import { AgentConfig } from '../../agent/AgentConfig' +import { DidCommMessageRepository } from '../../storage' import { AutoAcceptCredential } from './CredentialAutoAcceptType' -import { CredentialUtils } from './CredentialUtils' /** * This class handles all the automation with all the messages in the issue credential protocol @@ -14,9 +12,11 @@ import { CredentialUtils } from './CredentialUtils' @scoped(Lifecycle.ContainerScoped) export class CredentialResponseCoordinator { private agentConfig: AgentConfig + private didCommMessageRepository: DidCommMessageRepository - public constructor(agentConfig: AgentConfig) { + public constructor(agentConfig: AgentConfig, didCommMessageRepository: DidCommMessageRepository) { this.agentConfig = agentConfig + this.didCommMessageRepository = didCommMessageRepository } /** @@ -25,144 +25,10 @@ export class CredentialResponseCoordinator { * - Otherwise the agent config * - Otherwise {@link AutoAcceptCredential.Never} is returned */ - private static composeAutoAccept( + public static composeAutoAccept( recordConfig: AutoAcceptCredential | undefined, agentConfig: AutoAcceptCredential | undefined ) { return recordConfig ?? agentConfig ?? AutoAcceptCredential.Never } - - /** - * Checks whether it should automatically respond to a proposal - */ - public shouldAutoRespondToProposal(credentialRecord: CredentialRecord) { - const autoAccept = CredentialResponseCoordinator.composeAutoAccept( - credentialRecord.autoAcceptCredential, - this.agentConfig.autoAcceptCredentials - ) - - if (autoAccept === AutoAcceptCredential.Always) { - return true - } else if (autoAccept === AutoAcceptCredential.ContentApproved) { - return ( - this.areProposalValuesValid(credentialRecord) && this.areProposalAndOfferDefinitionIdEqual(credentialRecord) - ) - } - return false - } - - /** - * Checks whether it should automatically respond to an offer - */ - public shouldAutoRespondToOffer(credentialRecord: CredentialRecord) { - const autoAccept = CredentialResponseCoordinator.composeAutoAccept( - credentialRecord.autoAcceptCredential, - this.agentConfig.autoAcceptCredentials - ) - - if (autoAccept === AutoAcceptCredential.Always) { - return true - } else if (autoAccept === AutoAcceptCredential.ContentApproved) { - return this.areOfferValuesValid(credentialRecord) && this.areProposalAndOfferDefinitionIdEqual(credentialRecord) - } - return false - } - - /** - * Checks whether it should automatically respond to a request - */ - public shouldAutoRespondToRequest(credentialRecord: CredentialRecord) { - const autoAccept = CredentialResponseCoordinator.composeAutoAccept( - credentialRecord.autoAcceptCredential, - this.agentConfig.autoAcceptCredentials - ) - - if (autoAccept === AutoAcceptCredential.Always) { - return true - } else if (autoAccept === AutoAcceptCredential.ContentApproved) { - return this.isRequestDefinitionIdValid(credentialRecord) - } - return false - } - - /** - * Checks whether it should automatically respond to the issuance of a credential - */ - public shouldAutoRespondToIssue(credentialRecord: CredentialRecord) { - const autoAccept = CredentialResponseCoordinator.composeAutoAccept( - credentialRecord.autoAcceptCredential, - this.agentConfig.autoAcceptCredentials - ) - - if (autoAccept === AutoAcceptCredential.Always) { - return true - } else if (autoAccept === AutoAcceptCredential.ContentApproved) { - return this.areCredentialValuesValid(credentialRecord) - } - return false - } - - private areProposalValuesValid(credentialRecord: CredentialRecord) { - const { proposalMessage, credentialAttributes } = credentialRecord - - if (proposalMessage && proposalMessage.credentialProposal && credentialAttributes) { - const proposalValues = CredentialUtils.convertAttributesToValues(proposalMessage.credentialProposal.attributes) - const defaultValues = CredentialUtils.convertAttributesToValues(credentialAttributes) - if (CredentialUtils.checkValuesMatch(proposalValues, defaultValues)) { - return true - } - } - return false - } - - private areOfferValuesValid(credentialRecord: CredentialRecord) { - const { offerMessage, credentialAttributes } = credentialRecord - - if (offerMessage && credentialAttributes) { - const offerValues = CredentialUtils.convertAttributesToValues(offerMessage.credentialPreview.attributes) - const defaultValues = CredentialUtils.convertAttributesToValues(credentialAttributes) - if (CredentialUtils.checkValuesMatch(offerValues, defaultValues)) { - return true - } - } - return false - } - - private areCredentialValuesValid(credentialRecord: CredentialRecord) { - if (credentialRecord.credentialAttributes && credentialRecord.credentialMessage) { - const indyCredential = credentialRecord.credentialMessage.indyCredential - - if (!indyCredential) { - this.agentConfig.logger.error(`Missing required base64 or json encoded attachment data for credential`) - return false - } - - const credentialMessageValues = indyCredential.values - const defaultValues = CredentialUtils.convertAttributesToValues(credentialRecord.credentialAttributes) - - if (CredentialUtils.checkValuesMatch(credentialMessageValues, defaultValues)) { - return true - } - } - return false - } - - private areProposalAndOfferDefinitionIdEqual(credentialRecord: CredentialRecord) { - const proposalCredentialDefinitionId = credentialRecord.proposalMessage?.credentialDefinitionId - const offerCredentialDefinitionId = credentialRecord.offerMessage?.indyCredentialOffer?.cred_def_id - return proposalCredentialDefinitionId === offerCredentialDefinitionId - } - - private isRequestDefinitionIdValid(credentialRecord: CredentialRecord) { - if (credentialRecord.proposalMessage || credentialRecord.offerMessage) { - const previousCredentialDefinitionId = - credentialRecord.offerMessage?.indyCredentialOffer?.cred_def_id ?? - credentialRecord.proposalMessage?.credentialDefinitionId - - if (previousCredentialDefinitionId === credentialRecord.requestMessage?.indyCredentialRequest?.cred_def_id) { - return true - } - } - return false - } } diff --git a/packages/core/src/modules/credentials/CredentialServiceOptions.ts b/packages/core/src/modules/credentials/CredentialServiceOptions.ts new file mode 100644 index 0000000000..e9af147adc --- /dev/null +++ b/packages/core/src/modules/credentials/CredentialServiceOptions.ts @@ -0,0 +1,87 @@ +import type { AgentMessage } from '../../agent/AgentMessage' +import type { Attachment } from '../../decorators/attachment/Attachment' +import type { LinkedAttachment } from '../../utils/LinkedAttachment' +import type { AutoAcceptCredential } from './CredentialAutoAcceptType' +import type { + AcceptOfferOptions, + AcceptProposalOptions, + AcceptRequestOptions, + NegotiateOfferOptions, + NegotiateProposalOptions, + OfferCredentialOptions, + RequestCredentialOptions, +} from './CredentialsModuleOptions' +import type { CredentialPreviewAttribute } from './models/CredentialPreviewAttributes' +import type { V1CredentialPreview } from './protocol/v1/V1CredentialPreview' +import type { ProposeCredentialMessageOptions } from './protocol/v1/messages' +import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' + +export interface IndyCredentialPreview { + credentialDefinitionId?: string + attributes?: CredentialPreviewAttribute[] +} + +export interface CredentialProtocolMsgReturnType { + message: MessageType + credentialRecord: CredentialExchangeRecord +} + +export interface CredentialOfferTemplate { + credentialDefinitionId: string + comment?: string + preview: V1CredentialPreview + autoAcceptCredential?: AutoAcceptCredential + attachments?: Attachment[] + linkedAttachments?: LinkedAttachment[] +} + +export interface ServiceAcceptOfferOptions extends AcceptOfferOptions { + attachId?: string + credentialFormats: { + indy?: IndyCredentialPreview + jsonld?: { + // todo + } + } +} + +export interface ServiceOfferCredentialOptions extends OfferCredentialOptions { + connectionId?: string + attachId?: string + // offerAttachment?: Attachment +} + +export interface ServiceAcceptProposalOptions extends AcceptProposalOptions { + offerAttachment?: Attachment + proposalAttachment?: Attachment +} + +export interface ServiceAcceptRequestOptions extends AcceptRequestOptions { + attachId?: string +} +export interface ServiceNegotiateProposalOptions extends NegotiateProposalOptions { + offerAttachment?: Attachment +} + +export interface ServiceNegotiateOfferOptions extends NegotiateOfferOptions { + offerAttachment?: Attachment +} + +export interface ServiceRequestCredentialOptions extends RequestCredentialOptions { + attachId?: string + offerAttachment?: Attachment + requestAttachment?: Attachment +} + +export interface ServiceAcceptCredentialOptions { + credentialAttachment?: Attachment +} + +export type CredentialProposeOptions = Omit & { + linkedAttachments?: LinkedAttachment[] + autoAcceptCredential?: AutoAcceptCredential +} + +export interface DeleteCredentialOptions { + deleteAssociatedCredentials: boolean +} diff --git a/packages/core/src/modules/credentials/CredentialUtils.ts b/packages/core/src/modules/credentials/CredentialUtils.ts index 0f25db5dc8..791cd3bb5d 100644 --- a/packages/core/src/modules/credentials/CredentialUtils.ts +++ b/packages/core/src/modules/credentials/CredentialUtils.ts @@ -1,4 +1,6 @@ import type { LinkedAttachment } from '../../utils/LinkedAttachment' +import type { V1CredentialPreview } from './protocol/v1/V1CredentialPreview' +import type { V2CredentialPreview } from './protocol/v2/V2CredentialPreview' import type { CredValues, Schema } from 'indy-sdk' import BigNumber from 'bn.js' @@ -9,7 +11,7 @@ import { encodeAttachment } from '../../utils/attachment' import { Buffer } from '../../utils/buffer' import { isBoolean, isNumber, isString } from '../../utils/type' -import { CredentialPreview, CredentialPreviewAttribute } from './messages/CredentialPreview' +import { CredentialPreviewAttribute } from './models/CredentialPreviewAttributes' export class CredentialUtils { /** @@ -20,26 +22,28 @@ export class CredentialUtils { * * @returns a modified version of the credential preview with the linked credentials * */ - public static createAndLinkAttachmentsToPreview(attachments: LinkedAttachment[], preview: CredentialPreview) { - const credentialPreview = new CredentialPreview({ attributes: [...preview.attributes] }) + public static createAndLinkAttachmentsToPreview( + attachments: LinkedAttachment[], + credentialPreview: V1CredentialPreview | V2CredentialPreview + ) { const credentialPreviewAttributeNames = credentialPreview.attributes.map((attribute) => attribute.name) attachments.forEach((linkedAttachment) => { if (credentialPreviewAttributeNames.includes(linkedAttachment.attributeName)) { throw new AriesFrameworkError( `linkedAttachment ${linkedAttachment.attributeName} already exists in the preview` ) + } else { + const credentialPreviewAttribute = new CredentialPreviewAttribute({ + name: linkedAttachment.attributeName, + mimeType: linkedAttachment.attachment.mimeType, + value: encodeAttachment(linkedAttachment.attachment), + }) + credentialPreview.attributes.push(credentialPreviewAttribute) } - const credentialPreviewAttribute = new CredentialPreviewAttribute({ - name: linkedAttachment.attributeName, - mimeType: linkedAttachment.attachment.mimeType, - value: encodeAttachment(linkedAttachment.attachment), - }) - credentialPreview.attributes.push(credentialPreviewAttribute) }) return credentialPreview } - /** * Converts int value to string * Converts string value: @@ -168,15 +172,7 @@ export class CredentialUtils { return new BigNumber(Hasher.hash(Buffer.from(value as string), 'sha2-256')).toString() } - private static isInt32(number: number) { - const minI32 = -2147483648 - const maxI32 = 2147483647 - - // Check if number is integer and in range of int32 - return Number.isInteger(number) && number >= minI32 && number <= maxI32 - } - - public static checkAttributesMatch(schema: Schema, credentialPreview: CredentialPreview) { + public static checkAttributesMatch(schema: Schema, credentialPreview: V1CredentialPreview | V2CredentialPreview) { const schemaAttributes = schema.attrNames const credAttributes = credentialPreview.attributes.map((a) => a.name) @@ -190,4 +186,11 @@ export class CredentialUtils { ) } } + private static isInt32(number: number) { + const minI32 = -2147483648 + const maxI32 = 2147483647 + + // Check if number is integer and in range of int32 + return Number.isInteger(number) && number >= minI32 && number <= maxI32 + } } diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index bc18c03b0b..45c97a12e4 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -1,300 +1,291 @@ -import type { AutoAcceptCredential } from './CredentialAutoAcceptType' -import type { OfferCredentialMessage, CredentialPreview } from './messages' -import type { CredentialRecord } from './repository/CredentialRecord' -import type { CredentialOfferTemplate, CredentialProposeOptions } from './services' +import type { AgentMessage } from '../../agent/AgentMessage' +import type { Logger } from '../../logger' +import type { + AcceptOfferOptions, + AcceptProposalOptions, + AcceptRequestOptions, + NegotiateOfferOptions, + NegotiateProposalOptions, + OfferCredentialOptions, + ProposeCredentialOptions, + RequestCredentialOptions, +} from './CredentialsModuleOptions' +import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' +import type { CredentialService } from './services/CredentialService' import { Lifecycle, scoped } from 'tsyringe' import { AgentConfig } from '../../agent/AgentConfig' -import { Dispatcher } from '../../agent/Dispatcher' import { MessageSender } from '../../agent/MessageSender' import { createOutboundMessage } from '../../agent/helpers' import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../error' -import { isLinkedAttachment } from '../../utils/attachment' -import { ConnectionService } from '../connections/services/ConnectionService' +import { DidCommMessageRole } from '../../storage' +import { DidCommMessageRepository } from '../../storage/didcomm/DidCommMessageRepository' +import { ConnectionService } from '../connections/services' import { MediationRecipientService } from '../routing' -import { CredentialResponseCoordinator } from './CredentialResponseCoordinator' -import { CredentialProblemReportReason } from './errors' -import { - CredentialAckHandler, - IssueCredentialHandler, - OfferCredentialHandler, - ProposeCredentialHandler, - RequestCredentialHandler, - V1RevocationNotificationHandler, - V2RevocationNotificationHandler, - CredentialProblemReportHandler, -} from './handlers' -import { CredentialProblemReportMessage } from './messages' -import { CredentialService, RevocationService } from './services' +import { CredentialProtocolVersion } from './CredentialProtocolVersion' +import { CredentialState } from './CredentialState' +import { V1CredentialService } from './protocol/v1/V1CredentialService' +import { V2CredentialService } from './protocol/v2/V2CredentialService' +import { CredentialRepository } from './repository/CredentialRepository' + +export interface CredentialsModule { + // Proposal methods + proposeCredential(options: ProposeCredentialOptions): Promise + acceptProposal(options: AcceptProposalOptions): Promise + negotiateProposal(options: NegotiateProposalOptions): Promise + + // Offer methods + offerCredential(options: OfferCredentialOptions): Promise + acceptOffer(options: AcceptOfferOptions): Promise + declineOffer(credentialRecordId: string): Promise + negotiateOffer(options: NegotiateOfferOptions): Promise + // out of band + createOutOfBandOffer(options: OfferCredentialOptions): Promise<{ + message: AgentMessage + credentialRecord: CredentialExchangeRecord + }> + // Request + // This is for beginning the exchange with a request (no proposal or offer). Only possible + // (currently) with W3C. We will not implement this in phase I + // requestCredential(credentialOptions: RequestCredentialOptions): Promise + + // when the issuer accepts the request he issues the credential to the holder + acceptRequest(options: AcceptRequestOptions): Promise + + // Credential + acceptCredential(credentialRecordId: string): Promise + + // Record Methods + getAll(): Promise + getById(credentialRecordId: string): Promise + findById(credentialRecordId: string): Promise + deleteById(credentialRecordId: string): Promise +} @scoped(Lifecycle.ContainerScoped) -export class CredentialsModule { +export class CredentialsModule implements CredentialsModule { private connectionService: ConnectionService - private credentialService: CredentialService private messageSender: MessageSender + private credentialRepository: CredentialRepository private agentConfig: AgentConfig - private credentialResponseCoordinator: CredentialResponseCoordinator - private mediationRecipientService: MediationRecipientService - private revocationService: RevocationService - + private didCommMessageRepo: DidCommMessageRepository + private v1Service: V1CredentialService + private v2Service: V2CredentialService + private mediatorRecipientService: MediationRecipientService + private logger: Logger + private serviceMap: { [key in CredentialProtocolVersion]: CredentialService } + + // note some of the parameters passed in here are temporary, as we intend + // to eventually remove CredentialsModule public constructor( - dispatcher: Dispatcher, - connectionService: ConnectionService, - credentialService: CredentialService, messageSender: MessageSender, + connectionService: ConnectionService, agentConfig: AgentConfig, - credentialResponseCoordinator: CredentialResponseCoordinator, + credentialRepository: CredentialRepository, mediationRecipientService: MediationRecipientService, - revocationService: RevocationService + didCommMessageRepository: DidCommMessageRepository, + v1Service: V1CredentialService, + v2Service: V2CredentialService ) { - this.connectionService = connectionService - this.credentialService = credentialService this.messageSender = messageSender + this.connectionService = connectionService + this.credentialRepository = credentialRepository this.agentConfig = agentConfig - this.credentialResponseCoordinator = credentialResponseCoordinator - this.mediationRecipientService = mediationRecipientService - this.revocationService = revocationService - this.registerHandlers(dispatcher) + this.mediatorRecipientService = mediationRecipientService + this.didCommMessageRepo = didCommMessageRepository + this.logger = agentConfig.logger + + this.v1Service = v1Service + this.v2Service = v2Service + + this.serviceMap = { + [CredentialProtocolVersion.V1]: this.v1Service, + [CredentialProtocolVersion.V2]: this.v2Service, + } + this.logger.debug(`Initializing Credentials Module for agent ${this.agentConfig.label}`) } - /** - * Initiate a new credential exchange as holder by sending a credential proposal message - * to the connection with the specified connection id. - * - * @param connectionId The connection to send the credential proposal to - * @param config Additional configuration to use for the proposal - * @returns Credential record associated with the sent proposal message - */ - public async proposeCredential(connectionId: string, config?: CredentialProposeOptions) { - const connection = await this.connectionService.getById(connectionId) + public getService(protocolVersion: CredentialProtocolVersion): CredentialService { + return this.serviceMap[protocolVersion] + } - const { message, credentialRecord } = await this.credentialService.createProposal(connection, config) + public async declineOffer(credentialRecordId: string): Promise { + const credentialRecord = await this.getById(credentialRecordId) + credentialRecord.assertState(CredentialState.OfferReceived) - const outbound = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outbound) + // with version we can get the Service + const service = this.getService(credentialRecord.protocolVersion) + await service.updateState(credentialRecord, CredentialState.Declined) return credentialRecord } - /** - * Accept a credential proposal as issuer (by sending a credential offer message) to the connection - * associated with the credential record. - * - * @param credentialRecordId The id of the credential record for which to accept the proposal - * @param config Additional configuration to use for the offer - * @returns Credential record associated with the credential offer - * - */ - public async acceptProposal( - credentialRecordId: string, - config?: { - comment?: string - credentialDefinitionId?: string - autoAcceptCredential?: AutoAcceptCredential - } - ) { - const credentialRecord = await this.credentialService.getById(credentialRecordId) - if (!credentialRecord.connectionId) { - throw new AriesFrameworkError( - `No connectionId found for credential record '${credentialRecord.id}'. Connection-less issuance does not support credential proposal or negotiation.` - ) + public async negotiateOffer(options: NegotiateOfferOptions): Promise { + if (!options.credentialRecordId) { + throw new AriesFrameworkError(`No credential record id found in negotiateCredentialOffer`) } + const credentialRecord = await this.getById(options.credentialRecordId) + const version = credentialRecord.protocolVersion - const connection = await this.connectionService.getById(credentialRecord.connectionId) - - const credentialProposalMessage = credentialRecord.proposalMessage - if (!credentialProposalMessage?.credentialProposal) { - throw new AriesFrameworkError( - `Credential record with id ${credentialRecordId} is missing required credential proposal` - ) - } + const service = this.getService(version) + const { message } = await service.negotiateOffer(options, credentialRecord) - const credentialDefinitionId = config?.credentialDefinitionId ?? credentialProposalMessage.credentialDefinitionId - - credentialRecord.linkedAttachments = credentialProposalMessage.attachments?.filter((attachment) => - isLinkedAttachment(attachment) - ) - - if (!credentialDefinitionId) { + if (!credentialRecord.connectionId) { throw new AriesFrameworkError( - 'Missing required credential definition id. If credential proposal message contains no credential definition id it must be passed to config.' + `No connection id for credential record ${credentialRecord.id} not found. Connection-less issuance does not support negotiation` ) } - - // TODO: check if it is possible to issue credential based on proposal filters - const { message } = await this.credentialService.createOfferAsResponse(credentialRecord, { - preview: credentialProposalMessage.credentialProposal, - credentialDefinitionId, - comment: config?.comment, - autoAcceptCredential: config?.autoAcceptCredential, - attachments: credentialRecord.linkedAttachments, - }) + const connection = await this.connectionService.getById(credentialRecord.connectionId) const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(outboundMessage) return credentialRecord } /** - * Negotiate a credential proposal as issuer (by sending a credential offer message) to the connection - * associated with the credential record. - * - * @param credentialRecordId The id of the credential record for which to accept the proposal - * @param preview The new preview for negotiation - * @param config Additional configuration to use for the offer - * @returns Credential record associated with the credential offer + * Initiate a new credential exchange as holder by sending a credential proposal message + * to the connection with the specified credential options * + * @param options configuration to use for the proposal + * @returns Credential exchange record associated with the sent proposal message */ - public async negotiateProposal( - credentialRecordId: string, - preview: CredentialPreview, - config?: { - comment?: string - credentialDefinitionId?: string - autoAcceptCredential?: AutoAcceptCredential - } - ) { - const credentialRecord = await this.credentialService.getById(credentialRecordId) - if (!credentialRecord.connectionId) { - throw new AriesFrameworkError( - `No connectionId found for credential record '${credentialRecord.id}'. Connection-less issuance does not support negotiation.` - ) - } - const connection = await this.connectionService.getById(credentialRecord.connectionId) - - const credentialProposalMessage = credentialRecord.proposalMessage + public async proposeCredential(options: ProposeCredentialOptions): Promise { + // get the version + const version = options.protocolVersion - if (!credentialProposalMessage?.credentialProposal) { - throw new AriesFrameworkError( - `Credential record with id ${credentialRecordId} is missing required credential proposal` - ) + // with version we can get the Service + if (!version) { + throw new AriesFrameworkError('Missing Protocol Version') } + const service = this.getService(version) - const credentialDefinitionId = config?.credentialDefinitionId ?? credentialProposalMessage.credentialDefinitionId + this.logger.debug(`Got a CredentialService object for version ${version}`) - if (!credentialDefinitionId) { - throw new AriesFrameworkError( - 'Missing required credential definition id. If credential proposal message contains no credential definition id it must be passed to config.' - ) - } + const connection = await this.connectionService.getById(options.connectionId) - const { message } = await this.credentialService.createOfferAsResponse(credentialRecord, { - preview, - credentialDefinitionId, - comment: config?.comment, - autoAcceptCredential: config?.autoAcceptCredential, - attachments: credentialRecord.linkedAttachments, - }) + // will get back a credential record -> map to Credential Exchange Record + const { credentialRecord, message } = await service.createProposal(options) - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outboundMessage) + this.logger.debug('We have a message (sending outbound): ', message) + // send the message here + const outbound = createOutboundMessage(connection, message) + + this.logger.debug('In proposeCredential: Send Proposal to Issuer') + await this.messageSender.sendMessage(outbound) return credentialRecord } /** - * Initiate a new credential exchange as issuer by sending a credential offer message - * to the connection with the specified connection id. + * Accept a credential proposal as issuer (by sending a credential offer message) to the connection + * associated with the credential record. + * + * @param options config object for the proposal (and subsequent offer) which replaces previous named parameters + * @returns Credential exchange record associated with the credential offer * - * @param connectionId The connection to send the credential offer to - * @param credentialTemplate The credential template to use for the offer - * @returns Credential record associated with the sent credential offer message */ - public async offerCredential( - connectionId: string, - credentialTemplate: CredentialOfferTemplate - ): Promise { - const connection = await this.connectionService.getById(connectionId) + public async acceptProposal(options: AcceptProposalOptions): Promise { + const credentialRecord = await this.getById(options.credentialRecordId) - const { message, credentialRecord } = await this.credentialService.createOffer(credentialTemplate, connection) + if (!credentialRecord.connectionId) { + throw new AriesFrameworkError('Missing connection id in v2 acceptCredentialProposal') + } + const version = credentialRecord.protocolVersion - const outboundMessage = createOutboundMessage(connection, message) - await this.messageSender.sendMessage(outboundMessage) + // with version we can get the Service + const service = this.getService(version) - return credentialRecord - } + // will get back a credential record -> map to Credential Exchange Record + const { message } = await service.acceptProposal(options, credentialRecord) - /** - * Initiate a new credential exchange as issuer by creating a credential offer - * not bound to any connection. The offer must be delivered out-of-band to the holder - * - * @param credentialTemplate The credential template to use for the offer - * @returns The credential record and credential offer message - */ - public async createOutOfBandOffer(credentialTemplate: CredentialOfferTemplate): Promise<{ - offerMessage: OfferCredentialMessage - credentialRecord: CredentialRecord - }> { - const { message, credentialRecord } = await this.credentialService.createOffer(credentialTemplate) - - // Create and set ~service decorator - const routing = await this.mediationRecipientService.getRouting() - message.service = new ServiceDecorator({ - serviceEndpoint: routing.endpoints[0], - recipientKeys: [routing.verkey], - routingKeys: routing.routingKeys, - }) + const connection = await this.connectionService.getById(credentialRecord.connectionId) + + this.logger.debug('We have an offer message (sending outbound): ', message) + + // send the message here + const outbound = createOutboundMessage(connection, message) - // Save ~service decorator to record (to remember our verkey) - credentialRecord.offerMessage = message - await this.credentialService.update(credentialRecord) + this.logger.debug('In acceptCredentialProposal: Send Proposal to Issuer') + await this.messageSender.sendMessage(outbound) - return { credentialRecord, offerMessage: message } + return credentialRecord } /** * Accept a credential offer as holder (by sending a credential request message) to the connection * associated with the credential record. * - * @param credentialRecordId The id of the credential record for which to accept the offer - * @param config Additional configuration to use for the request - * @returns Credential record associated with the sent credential request message - * + * @param options The object containing config options of the offer to be accepted + * @returns Object containing offer associated credential record */ - public async acceptOffer( - credentialRecordId: string, - config?: { comment?: string; autoAcceptCredential?: AutoAcceptCredential } - ): Promise { - const record = await this.credentialService.getById(credentialRecordId) + public async acceptOffer(options: AcceptOfferOptions): Promise { + const record = await this.getById(options.credentialRecordId) + + const service = this.getService(record.protocolVersion) + + this.logger.debug(`Got a CredentialService object for this version; version = ${service.getVersion()}`) + + const offerMessage = await service.getOfferMessage(record.id) // Use connection if present if (record.connectionId) { const connection = await this.connectionService.getById(record.connectionId) - const { message, credentialRecord } = await this.credentialService.createRequest(record, { - ...config, - holderDid: connection.did, + const requestOptions: RequestCredentialOptions = { + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + } + const { message, credentialRecord } = await service.createRequest(record, requestOptions, connection.did) + + await this.didCommMessageRepo.saveAgentMessage({ + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, }) + this.logger.debug('We have sent a credential request') const outboundMessage = createOutboundMessage(connection, message) + this.logger.debug('We have a proposal message (sending outbound): ', message) + await this.messageSender.sendMessage(outboundMessage) + await this.credentialRepository.update(credentialRecord) return credentialRecord } // Use ~service decorator otherwise - else if (record.offerMessage?.service) { + else if (offerMessage?.service) { // Create ~service decorator - const routing = await this.mediationRecipientService.getRouting() + const routing = await this.mediatorRecipientService.getRouting() const ourService = new ServiceDecorator({ serviceEndpoint: routing.endpoints[0], recipientKeys: [routing.verkey], routingKeys: routing.routingKeys, }) - const recipientService = record.offerMessage.service - - const { message, credentialRecord } = await this.credentialService.createRequest(record, { - ...config, - holderDid: ourService.recipientKeys[0], - }) + const recipientService = offerMessage.service + + const requestOptions: RequestCredentialOptions = { + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + } + const { message, credentialRecord } = await service.createRequest( + record, + requestOptions, + ourService.recipientKeys[0] + ) // Set and save ~service decorator to record (to remember our verkey) message.service = ourService - credentialRecord.requestMessage = message - await this.credentialService.update(credentialRecord) + await this.didCommMessageRepo.saveAgentMessage({ + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + await this.credentialRepository.update(credentialRecord) await this.messageSender.sendMessageToService({ message, @@ -314,83 +305,101 @@ export class CredentialsModule { } /** - * Declines an offer as holder - * @param credentialRecordId the id of the credential to be declined - * @returns credential record that was declined - */ - public async declineOffer(credentialRecordId: string) { - const credentialRecord = await this.credentialService.getById(credentialRecordId) - await this.credentialService.declineOffer(credentialRecord) - return credentialRecord - } - - /** - * Negotiate a credential offer as holder (by sending a credential proposal message) to the connection + * Negotiate a credential proposal as issuer (by sending a credential offer message) to the connection * associated with the credential record. * - * @param credentialRecordId The id of the credential record for which to accept the offer - * @param preview The new preview for negotiation - * @param config Additional configuration to use for the request - * @returns Credential record associated with the sent credential request message + * @param options configuration for the offer see {@link NegotiateProposalOptions} + * @returns Credential exchange record associated with the credential offer * */ - public async negotiateOffer( - credentialRecordId: string, - preview: CredentialPreview, - config?: { comment?: string; autoAcceptCredential?: AutoAcceptCredential } - ) { - const credentialRecord = await this.credentialService.getById(credentialRecordId) + public async negotiateProposal(options: NegotiateProposalOptions): Promise { + const credentialRecord = await this.getById(options.credentialRecordId) + + // get the version + const version = credentialRecord.protocolVersion + + // with version we can get the Service + const service = this.getService(version) + const { message } = await service.negotiateProposal(options, credentialRecord) if (!credentialRecord.connectionId) { throw new AriesFrameworkError( - `No connectionId found for credential record '${credentialRecord.id}'. Connection-less issuance does not support negotiation.` + `No connection id for credential record ${credentialRecord.id} not found. Connection-less issuance does not support negotiation` ) } const connection = await this.connectionService.getById(credentialRecord.connectionId) - - const { message } = await this.credentialService.createProposalAsResponse(credentialRecord, { - ...config, - credentialProposal: preview, - }) + // use record connection id to get the connection const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(outboundMessage) return credentialRecord } /** - * Accept a credential request as issuer (by sending a credential message) to the connection - * associated with the credential record. + * Initiate a new credential exchange as issuer by sending a credential offer message + * to the connection with the specified connection id. * - * @param credentialRecordId The id of the credential record for which to accept the request - * @param config Additional configuration to use for the credential - * @returns Credential record associated with the sent presentation message + * @param options config options for the credential offer + * @returns Credential exchange record associated with the sent credential offer message + */ + public async offerCredential(options: OfferCredentialOptions): Promise { + if (!options.connectionId) { + throw new AriesFrameworkError('Missing connectionId on offerCredential') + } + const connection = await this.connectionService.getById(options.connectionId) + + const service = this.getService(options.protocolVersion) + + this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) + const { message, credentialRecord } = await service.createOffer(options) + + this.logger.debug('Offer Message successfully created; message= ', message) + const outboundMessage = createOutboundMessage(connection, message) + await this.messageSender.sendMessage(outboundMessage) + return credentialRecord + } + + /** + * Accept a credential request as holder (by sending a credential request message) to the connection + * associated with the credential record. * + * @param options The object containing config options of the request + * @returns CredentialExchangeRecord updated with information pertaining to this request */ - public async acceptRequest( - credentialRecordId: string, - config?: { comment?: string; autoAcceptCredential?: AutoAcceptCredential } - ) { - const record = await this.credentialService.getById(credentialRecordId) - const { message, credentialRecord } = await this.credentialService.createCredential(record, config) + public async acceptRequest(options: AcceptRequestOptions): Promise { + if (!options.credentialRecordId) { + throw new AriesFrameworkError('Missing credential record id in acceptRequest') + } + const record = await this.getById(options.credentialRecordId) + + // with version we can get the Service + const service = this.getService(record.protocolVersion) + + this.logger.debug(`Got a CredentialService object for version ${record.protocolVersion}`) + + const { message, credentialRecord } = await service.createCredential(record, options) + this.logger.debug('We have a credential message (sending outbound): ', message) + + const requestMessage = await service.getRequestMessage(credentialRecord.id) + const offerMessage = await service.getOfferMessage(credentialRecord.id) // Use connection if present if (credentialRecord.connectionId) { const connection = await this.connectionService.getById(credentialRecord.connectionId) + const outboundMessage = createOutboundMessage(connection, message) await this.messageSender.sendMessage(outboundMessage) } // Use ~service decorator otherwise - else if (credentialRecord.requestMessage?.service && credentialRecord.offerMessage?.service) { - const recipientService = credentialRecord.requestMessage.service - const ourService = credentialRecord.offerMessage.service + else if (requestMessage?.service && offerMessage?.service) { + const recipientService = requestMessage.service + const ourService = offerMessage.service - // Set ~service, update message in record (for later use) - message.setService(ourService) - credentialRecord.credentialMessage = message - await this.credentialService.update(credentialRecord) + message.service = ourService + await this.credentialRepository.update(credentialRecord) await this.messageSender.sendMessageToService({ message, @@ -405,6 +414,11 @@ export class CredentialsModule { `Cannot accept request for credential record without connectionId or ~service decorator on credential offer / request.` ) } + await this.didCommMessageRepo.saveAgentMessage({ + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) return credentialRecord } @@ -414,12 +428,21 @@ export class CredentialsModule { * associated with the credential record. * * @param credentialRecordId The id of the credential record for which to accept the credential - * @returns credential record associated with the sent credential acknowledgement message + * @returns credential exchange record associated with the sent credential acknowledgement message * */ - public async acceptCredential(credentialRecordId: string) { - const record = await this.credentialService.getById(credentialRecordId) - const { message, credentialRecord } = await this.credentialService.createAck(record) + public async acceptCredential(credentialRecordId: string): Promise { + const record = await this.getById(credentialRecordId) + + // with version we can get the Service + const service = this.getService(record.protocolVersion) + + this.logger.debug(`Got a CredentialService object for version ${record.protocolVersion}`) + + const { message, credentialRecord } = await service.createAck(record) + + const requestMessage = await service.getRequestMessage(credentialRecord.id) + const credentialMessage = await service.getCredentialMessage(credentialRecord.id) if (credentialRecord.connectionId) { const connection = await this.connectionService.getById(credentialRecord.connectionId) @@ -428,9 +451,9 @@ export class CredentialsModule { await this.messageSender.sendMessage(outboundMessage) } // Use ~service decorator otherwise - else if (credentialRecord.credentialMessage?.service && credentialRecord.requestMessage?.service) { - const recipientService = credentialRecord.credentialMessage.service - const ourService = credentialRecord.requestMessage.service + else if (credentialMessage?.service && requestMessage?.service) { + const recipientService = credentialMessage.service + const ourService = requestMessage.service await this.messageSender.sendMessageToService({ message, @@ -445,35 +468,19 @@ export class CredentialsModule { `Cannot accept credential without connectionId or ~service decorator on credential message.` ) } - return credentialRecord } /** - * Send problem report message for a credential record - * @param credentialRecordId The id of the credential record for which to send problem report - * @param message message to send - * @returns credential record associated with credential problem report message + * Retrieve a credential record by id + * + * @param credentialRecordId The credential record id + * @throws {RecordNotFoundError} If no record is found + * @return The credential record + * */ - public async sendProblemReport(credentialRecordId: string, message: string) { - const record = await this.credentialService.getById(credentialRecordId) - if (!record.connectionId) { - throw new AriesFrameworkError(`No connectionId found for credential record '${record.id}'.`) - } - const connection = await this.connectionService.getById(record.connectionId) - const credentialProblemReportMessage = new CredentialProblemReportMessage({ - description: { - en: message, - code: CredentialProblemReportReason.IssuanceAbandoned, - }, - }) - credentialProblemReportMessage.setThread({ - threadId: record.threadId, - }) - const outboundMessage = createOutboundMessage(connection, credentialProblemReportMessage) - await this.messageSender.sendMessage(outboundMessage) - - return record + public getById(credentialRecordId: string): Promise { + return this.credentialRepository.getById(credentialRecordId) } /** @@ -481,20 +488,8 @@ export class CredentialsModule { * * @returns List containing all credential records */ - public getAll(): Promise { - return this.credentialService.getAll() - } - - /** - * Retrieve a credential record by id - * - * @param credentialRecordId The credential record id - * @throws {RecordNotFoundError} If no record is found - * @return The credential record - * - */ - public getById(credentialRecordId: string) { - return this.credentialService.getById(credentialRecordId) + public getAll(): Promise { + return this.credentialRepository.getAll() } /** @@ -503,40 +498,40 @@ export class CredentialsModule { * @param credentialRecordId the credential record id * @returns The credential record or null if not found */ - public findById(connectionId: string): Promise { - return this.credentialService.findById(connectionId) + public findById(credentialRecordId: string): Promise { + return this.credentialRepository.findById(credentialRecordId) } - /** * Delete a credential record by id * * @param credentialId the credential record id */ - public async deleteById(credentialId: string, options?: { deleteAssociatedCredential: boolean }) { - return this.credentialService.deleteById(credentialId, options) + public async deleteById(credentialId: string) { + const credentialRecord = await this.getById(credentialId) + return this.credentialRepository.delete(credentialRecord) } - private registerHandlers(dispatcher: Dispatcher) { - dispatcher.registerHandler( - new ProposeCredentialHandler(this.credentialService, this.agentConfig, this.credentialResponseCoordinator) - ) - dispatcher.registerHandler( - new OfferCredentialHandler( - this.credentialService, - this.agentConfig, - this.credentialResponseCoordinator, - this.mediationRecipientService - ) - ) - dispatcher.registerHandler( - new RequestCredentialHandler(this.credentialService, this.agentConfig, this.credentialResponseCoordinator) - ) - dispatcher.registerHandler( - new IssueCredentialHandler(this.credentialService, this.agentConfig, this.credentialResponseCoordinator) - ) - dispatcher.registerHandler(new CredentialAckHandler(this.credentialService)) - dispatcher.registerHandler(new V1RevocationNotificationHandler(this.revocationService)) - dispatcher.registerHandler(new V2RevocationNotificationHandler(this.revocationService)) - dispatcher.registerHandler(new CredentialProblemReportHandler(this.credentialService)) + /** + * Initiate a new credential exchange as issuer by creating a credential offer + * not bound to any connection. The offer must be delivered out-of-band to the holder + * @param options The credential options to use for the offer + * @returns The credential record and credential offer message + */ + public async createOutOfBandOffer(options: OfferCredentialOptions): Promise<{ + message: AgentMessage + credentialRecord: CredentialExchangeRecord + }> { + // with version we can get the Service + if (!options.protocolVersion) { + throw new AriesFrameworkError('Missing protocol version in createOutOfBandOffer') + } + const service = this.getService(options.protocolVersion) + + this.logger.debug(`Got a CredentialService object for version ${options.protocolVersion}`) + const { message, credentialRecord } = await service.createOutOfBandOffer(options) + + this.logger.debug('Offer Message successfully created; message= ', message) + + return { message, credentialRecord } } } diff --git a/packages/core/src/modules/credentials/CredentialsModuleOptions.ts b/packages/core/src/modules/credentials/CredentialsModuleOptions.ts new file mode 100644 index 0000000000..751bef079d --- /dev/null +++ b/packages/core/src/modules/credentials/CredentialsModuleOptions.ts @@ -0,0 +1,75 @@ +import type { AutoAcceptCredential } from './CredentialAutoAcceptType' +import type { CredentialProtocolVersion } from './CredentialProtocolVersion' +import type { + FormatServiceAcceptProposeCredentialFormats, + FormatServiceOfferCredentialFormats, + FormatServiceProposeCredentialFormats as FormatServiceProposeCredentialFormats, + FormatServiceRequestCredentialFormats, +} from './formats/models/CredentialFormatServiceOptions' + +// keys used to create a format service +export enum CredentialFormatType { + Indy = 'Indy', + // others to follow +} + +interface BaseOptions { + autoAcceptCredential?: AutoAcceptCredential + comment?: string +} + +// CREDENTIAL PROPOSAL +interface ProposeCredentialOptions extends BaseOptions { + connectionId: string + protocolVersion?: CredentialProtocolVersion + credentialFormats: FormatServiceProposeCredentialFormats +} + +interface AcceptProposalOptions extends BaseOptions { + connectionId?: string + protocolVersion: CredentialProtocolVersion + credentialRecordId: string + credentialFormats: FormatServiceAcceptProposeCredentialFormats +} + +interface NegotiateProposalOptions extends BaseOptions { + credentialRecordId: string + protocolVersion: CredentialProtocolVersion + credentialFormats: FormatServiceOfferCredentialFormats +} +// CREDENTIAL OFFER +interface OfferCredentialOptions extends BaseOptions { + credentialRecordId?: string + connectionId?: string + protocolVersion: CredentialProtocolVersion + credentialFormats: FormatServiceAcceptProposeCredentialFormats +} + +interface AcceptOfferOptions extends BaseOptions { + credentialRecordId: string +} + +interface NegotiateOfferOptions extends ProposeCredentialOptions { + credentialRecordId: string +} + +// CREDENTIAL REQUEST +interface RequestCredentialOptions extends BaseOptions { + connectionId?: string + credentialFormats?: FormatServiceRequestCredentialFormats +} + +interface AcceptRequestOptions extends BaseOptions { + credentialRecordId?: string +} + +export { + OfferCredentialOptions, + ProposeCredentialOptions, + AcceptProposalOptions, + NegotiateProposalOptions, + NegotiateOfferOptions, + AcceptOfferOptions, + RequestCredentialOptions, + AcceptRequestOptions, +} diff --git a/packages/core/src/modules/credentials/__tests__/CredentialInfo.test.ts b/packages/core/src/modules/credentials/__tests__/CredentialInfo.test.ts index b264950ed2..0a77e49958 100644 --- a/packages/core/src/modules/credentials/__tests__/CredentialInfo.test.ts +++ b/packages/core/src/modules/credentials/__tests__/CredentialInfo.test.ts @@ -1,4 +1,4 @@ -import { CredentialInfo } from '../models/CredentialInfo' +import { CredentialInfo } from '../protocol/v1/models/CredentialInfo' describe('CredentialInfo', () => { it('should return the correct property values', () => { diff --git a/packages/core/src/modules/credentials/__tests__/CredentialRecord.test.ts b/packages/core/src/modules/credentials/__tests__/CredentialRecord.test.ts index d0459411ca..ce74620d69 100644 --- a/packages/core/src/modules/credentials/__tests__/CredentialRecord.test.ts +++ b/packages/core/src/modules/credentials/__tests__/CredentialRecord.test.ts @@ -1,12 +1,13 @@ +import { CredentialProtocolVersion } from '../CredentialProtocolVersion' import { CredentialState } from '../CredentialState' -import { CredentialPreviewAttribute } from '../messages' -import { CredentialRecord } from '../repository/CredentialRecord' -import { CredentialMetadataKeys } from '../repository/credentialMetadataTypes' +import { CredentialPreviewAttribute } from '../models/CredentialPreviewAttributes' +import { CredentialExchangeRecord } from '../repository/CredentialExchangeRecord' +import { CredentialMetadataKeys } from '../repository/CredentialMetadataTypes' describe('CredentialRecord', () => { describe('getCredentialInfo()', () => { test('creates credential info object from credential record data', () => { - const credentialRecord = new CredentialRecord({ + const credentialRecord = new CredentialExchangeRecord({ connectionId: '28790bfe-1345-4c64-b21a-7d98982b3894', threadId: 'threadId', state: CredentialState.Done, @@ -16,6 +17,7 @@ describe('CredentialRecord', () => { value: '25', }), ], + protocolVersion: CredentialProtocolVersion.V1, }) credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { diff --git a/packages/core/src/modules/credentials/__tests__/CredentialUtils.test.ts b/packages/core/src/modules/credentials/__tests__/CredentialUtils.test.ts index 6a391014f5..dc562db80e 100644 --- a/packages/core/src/modules/credentials/__tests__/CredentialUtils.test.ts +++ b/packages/core/src/modules/credentials/__tests__/CredentialUtils.test.ts @@ -1,5 +1,5 @@ import { CredentialUtils } from '../CredentialUtils' -import { CredentialPreviewAttribute } from '../messages/CredentialPreview' +import { CredentialPreviewAttribute } from '../models/CredentialPreviewAttributes' /** * Sample test cases for encoding/decoding of verifiable credential claims - Aries RFCs 0036 and 0037 diff --git a/packages/core/src/modules/credentials/__tests__/CredentialService.test.ts b/packages/core/src/modules/credentials/__tests__/V1CredentialService.cred.test.ts similarity index 66% rename from packages/core/src/modules/credentials/__tests__/CredentialService.test.ts rename to packages/core/src/modules/credentials/__tests__/V1CredentialService.cred.test.ts index c1a87438bf..2a88f46be8 100644 --- a/packages/core/src/modules/credentials/__tests__/CredentialService.test.ts +++ b/packages/core/src/modules/credentials/__tests__/V1CredentialService.cred.test.ts @@ -1,66 +1,81 @@ -import type { Logger } from '../../../logger' +import type { Logger } from '../../../../src/logger' +import type { AgentConfig } from '../../../agent/AgentConfig' import type { ConnectionRecord } from '../../connections' import type { ConnectionService } from '../../connections/services/ConnectionService' import type { StoreCredentialOptions } from '../../indy/services/IndyHolderService' import type { RevocationNotificationReceivedEvent, CredentialStateChangedEvent } from '../CredentialEvents' -import type { CredentialPreviewAttribute } from '../messages' -import type { IndyCredentialMetadata } from '../models/CredentialInfo' -import type { CustomCredentialTags } from '../repository/CredentialRecord' -import type { CredentialOfferTemplate } from '../services' +import type { ServiceAcceptRequestOptions } from '../CredentialServiceOptions' +import type { RequestCredentialOptions } from '../CredentialsModuleOptions' +import type { CredentialPreviewAttribute } from '../models/CredentialPreviewAttributes' +import type { IndyCredentialMetadata } from '../protocol/v1/models/CredentialInfo' +import type { CustomCredentialTags } from '../repository/CredentialExchangeRecord' import { getAgentConfig, getMockConnection, mockFunction } from '../../../../tests/helpers' +import { Dispatcher } from '../../../agent/Dispatcher' import { EventEmitter } from '../../../agent/EventEmitter' +import { MessageSender } from '../../../agent/MessageSender' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' import { AriesFrameworkError, RecordNotFoundError } from '../../../error' +import { DidCommMessageRepository } from '../../../storage' import { JsonEncoder } from '../../../utils/JsonEncoder' import { AckStatus } from '../../common' import { ConnectionState } from '../../connections' import { IndyHolderService } from '../../indy/services/IndyHolderService' import { IndyIssuerService } from '../../indy/services/IndyIssuerService' import { IndyLedgerService } from '../../ledger/services' +import { MediationRecipientService } from '../../routing/services/MediationRecipientService' import { CredentialEventTypes } from '../CredentialEvents' +import { CredentialProtocolVersion } from '../CredentialProtocolVersion' import { CredentialState } from '../CredentialState' import { CredentialUtils } from '../CredentialUtils' +import { CredentialFormatType } from '../CredentialsModuleOptions' import { CredentialProblemReportReason } from '../errors/CredentialProblemReportReason' +import { IndyCredentialFormatService } from '../formats/indy/IndyCredentialFormatService' +import { V1CredentialPreview } from '../protocol/v1/V1CredentialPreview' +import { V1CredentialService } from '../protocol/v1/V1CredentialService' import { - V2RevocationNotificationMessage, - V1RevocationNotificationMessage, - CredentialAckMessage, - CredentialPreview, + V1RequestCredentialMessage, + V1CredentialAckMessage, INDY_CREDENTIAL_ATTACHMENT_ID, INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, - IssueCredentialMessage, - OfferCredentialMessage, - RequestCredentialMessage, -} from '../messages' -import { CredentialRecord } from '../repository/CredentialRecord' + V1OfferCredentialMessage, + V1IssueCredentialMessage, + V1CredentialProblemReportMessage, +} from '../protocol/v1/messages' +import { V1RevocationNotificationMessage } from '../protocol/v1/messages/V1RevocationNotificationMessage' +import { V2RevocationNotificationMessage } from '../protocol/v2/messages/V2RevocationNotificationMessage' +import { CredentialExchangeRecord } from '../repository/CredentialExchangeRecord' +import { CredentialMetadataKeys } from '../repository/CredentialMetadataTypes' import { CredentialRepository } from '../repository/CredentialRepository' -import { CredentialMetadataKeys } from '../repository/credentialMetadataTypes' -import { CredentialService, RevocationService } from '../services' +import { RevocationService } from '../services' -import { CredentialProblemReportMessage } from './../messages/CredentialProblemReportMessage' -import { credDef, credOffer, credReq, schema } from './fixtures' +import { credDef, credReq, credOffer, schema } from './fixtures' // Mock classes jest.mock('../repository/CredentialRepository') jest.mock('../../../modules/ledger/services/IndyLedgerService') jest.mock('../../indy/services/IndyHolderService') jest.mock('../../indy/services/IndyIssuerService') +jest.mock('../../../../src/storage/didcomm/DidCommMessageRepository') +jest.mock('../../routing/services/MediationRecipientService') // Mock typed object const CredentialRepositoryMock = CredentialRepository as jest.Mock const IndyLedgerServiceMock = IndyLedgerService as jest.Mock const IndyHolderServiceMock = IndyHolderService as jest.Mock const IndyIssuerServiceMock = IndyIssuerService as jest.Mock +const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock +const MessageSenderMock = MessageSender as jest.Mock +const MediationRecipientServiceMock = MediationRecipientService as jest.Mock const connection = getMockConnection({ id: '123', state: ConnectionState.Complete, }) -const credentialPreview = CredentialPreview.fromRecord({ +const credentialPreview = V1CredentialPreview.fromRecord({ name: 'John', age: '99', }) @@ -92,15 +107,19 @@ const credentialAttachment = new Attachment({ }), }) +const acceptRequestOptions: ServiceAcceptRequestOptions = { + attachId: INDY_CREDENTIAL_ATTACHMENT_ID, + comment: 'credential response comment', + credentialRecordId: undefined, +} + // A record is deserialized to JSON when it's stored into the storage. We want to simulate this behaviour for `offer` // object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. const mockCredentialRecord = ({ state, - requestMessage, metadata, threadId, connectionId, - credentialId, tags, id, credentialAttributes, @@ -108,7 +127,7 @@ const mockCredentialRecord = ({ indyCredentialRevocationId, }: { state?: CredentialState - requestMessage?: RequestCredentialMessage + requestMessage?: V1RequestCredentialMessage metadata?: IndyCredentialMetadata & { indyRequest: Record } tags?: CustomCredentialTags threadId?: string @@ -119,22 +138,26 @@ const mockCredentialRecord = ({ indyRevocationRegistryId?: string indyCredentialRevocationId?: string } = {}) => { - const offerMessage = new OfferCredentialMessage({ + const offerMessage = new V1OfferCredentialMessage({ comment: 'some comment', credentialPreview: credentialPreview, offerAttachments: [offerAttachment], }) - const credentialRecord = new CredentialRecord({ - offerMessage, + const credentialRecord = new CredentialExchangeRecord({ id, credentialAttributes: credentialAttributes || credentialPreview.attributes, - requestMessage, state: state || CredentialState.OfferSent, threadId: threadId ?? offerMessage.id, connectionId: connectionId ?? '123', - credentialId: credentialId ?? '123', + credentials: [ + { + credentialRecordType: CredentialFormatType.Indy, + credentialRecordId: '123456', + }, + ], tags, + protocolVersion: CredentialProtocolVersion.V1, }) if (metadata?.indyRequest) { @@ -161,242 +184,116 @@ const mockCredentialRecord = ({ return credentialRecord } +let credentialRequestMessage: V1RequestCredentialMessage +let credentialOfferMessage: V1OfferCredentialMessage +let credentialIssueMessage: V1IssueCredentialMessage +let revocationService: RevocationService +let logger: Logger + describe('CredentialService', () => { let credentialRepository: CredentialRepository - let credentialService: CredentialService - let revocationService: RevocationService - let ledgerService: IndyLedgerService + let indyLedgerService: IndyLedgerService let indyIssuerService: IndyIssuerService let indyHolderService: IndyHolderService let eventEmitter: EventEmitter - let logger: Logger + let didCommMessageRepository: DidCommMessageRepository + let mediationRecipientService: MediationRecipientService + let messageSender: MessageSender + let agentConfig: AgentConfig + + let dispatcher: Dispatcher + let credentialService: V1CredentialService + + const initMessages = () => { + credentialRequestMessage = new V1RequestCredentialMessage({ + comment: 'abcd', + requestAttachments: [requestAttachment], + }) + credentialOfferMessage = new V1OfferCredentialMessage({ + comment: 'some comment', + credentialPreview: credentialPreview, + offerAttachments: [offerAttachment], + }) + credentialIssueMessage = new V1IssueCredentialMessage({ + comment: 'some comment', + credentialAttachments: [offerAttachment], + }) - beforeEach(() => { - const agentConfig = getAgentConfig('CredentialServiceTest') + mockFunction(didCommMessageRepository.findAgentMessage).mockImplementation(async (options) => { + if (options.messageClass === V1OfferCredentialMessage) { + return credentialOfferMessage + } + if (options.messageClass === V1RequestCredentialMessage) { + return credentialRequestMessage + } + if (options.messageClass === V1IssueCredentialMessage) { + return credentialIssueMessage + } + return null + }) + } + + beforeEach(async () => { credentialRepository = new CredentialRepositoryMock() indyIssuerService = new IndyIssuerServiceMock() + didCommMessageRepository = new DidCommMessageRepositoryMock() + messageSender = new MessageSenderMock() + agentConfig = getAgentConfig('CredentialServiceTest') + mediationRecipientService = new MediationRecipientServiceMock() indyHolderService = new IndyHolderServiceMock() - ledgerService = new IndyLedgerServiceMock() + indyLedgerService = new IndyLedgerServiceMock() + mockFunction(indyLedgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) + eventEmitter = new EventEmitter(agentConfig) + + dispatcher = new Dispatcher(messageSender, eventEmitter, agentConfig) + revocationService = new RevocationService(credentialRepository, eventEmitter, agentConfig) logger = agentConfig.logger - credentialService = new CredentialService( - credentialRepository, + credentialService = new V1CredentialService( { getById: () => Promise.resolve(connection), assertConnectionOrServiceDecorator: () => true, } as unknown as ConnectionService, - ledgerService, + didCommMessageRepository, agentConfig, - indyIssuerService, - indyHolderService, - eventEmitter + mediationRecipientService, + dispatcher, + eventEmitter, + credentialRepository, + new IndyCredentialFormatService( + credentialRepository, + eventEmitter, + indyIssuerService, + indyLedgerService, + indyHolderService, + agentConfig + ), + revocationService ) - - revocationService = new RevocationService(credentialRepository, eventEmitter, agentConfig) - - mockFunction(ledgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) - mockFunction(ledgerService.getSchema).mockReturnValue(Promise.resolve(schema)) - }) - - describe('createCredentialOffer', () => { - let credentialTemplate: CredentialOfferTemplate - - beforeEach(() => { - credentialTemplate = { - credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', - comment: 'some comment', - preview: credentialPreview, - } - }) - - test(`creates credential record in ${CredentialState.OfferSent} state with offer, thread ID`, async () => { - // given - const repositorySaveSpy = jest.spyOn(credentialRepository, 'save') - - // when - const { message: credentialOffer } = await credentialService.createOffer(credentialTemplate, connection) - - // then - expect(repositorySaveSpy).toHaveBeenCalledTimes(1) - const [[createdCredentialRecord]] = repositorySaveSpy.mock.calls - expect(createdCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - offerMessage: credentialOffer, - threadId: createdCredentialRecord.offerMessage?.id, - connectionId: connection.id, - state: CredentialState.OfferSent, - }) - }) - - test(`emits stateChange event with a new credential in ${CredentialState.OfferSent} state`, async () => { - const eventListenerMock = jest.fn() - eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) - - await credentialService.createOffer(credentialTemplate, connection) - - expect(eventListenerMock).toHaveBeenCalledWith({ - type: 'CredentialStateChanged', - payload: { - previousState: null, - credentialRecord: expect.objectContaining({ - state: CredentialState.OfferSent, - }), - }, - }) - }) - - test('returns credential offer message', async () => { - const { message: credentialOffer } = await credentialService.createOffer(credentialTemplate, connection) - - expect(credentialOffer.toJSON()).toMatchObject({ - '@id': expect.any(String), - '@type': 'https://didcomm.org/issue-credential/1.0/offer-credential', - comment: 'some comment', - credential_preview: { - '@type': 'https://didcomm.org/issue-credential/1.0/credential-preview', - attributes: [ - { - name: 'name', - 'mime-type': 'text/plain', - value: 'John', - }, - { - name: 'age', - 'mime-type': 'text/plain', - value: '99', - }, - ], - }, - 'offers~attach': [ - { - '@id': expect.any(String), - 'mime-type': 'application/json', - data: { - base64: expect.any(String), - }, - }, - ], - }) - }) - - test('throw error if credential preview attributes do not match with schema attributes', async () => { - const credentialPreview = CredentialPreview.fromRecord({ - test: 'credential', - error: 'yes', - }) - - expect( - credentialService.createOffer( - { - ...credentialTemplate, - preview: credentialPreview, - }, - connection - ) - ).rejects.toThrowError( - `The credential preview attributes do not match the schema attributes (difference is: test,error,name,age, needs: name,age)` - ) - - const credentialPreviewWithExtra = CredentialPreview.fromRecord({ - test: 'credential', - error: 'yes', - name: 'John', - age: '99', - }) - - await expect( - credentialService.createOffer( - { - ...credentialTemplate, - preview: credentialPreviewWithExtra, - }, - connection - ) - ).rejects.toThrowError( - `The credential preview attributes do not match the schema attributes (difference is: test,error, needs: name,age)` - ) - }) - }) - - describe('processCredentialOffer', () => { - let messageContext: InboundMessageContext - let credentialOfferMessage: OfferCredentialMessage - - beforeEach(() => { - credentialOfferMessage = new OfferCredentialMessage({ - comment: 'some comment', - credentialPreview: credentialPreview, - offerAttachments: [offerAttachment], - }) - messageContext = new InboundMessageContext(credentialOfferMessage, { - connection, - }) - messageContext.connection = connection - }) - - test(`creates and return credential record in ${CredentialState.OfferReceived} state with offer, thread ID`, async () => { - const repositorySaveSpy = jest.spyOn(credentialRepository, 'save') - - // when - const returnedCredentialRecord = await credentialService.processOffer(messageContext) - - // then - const expectedCredentialRecord = { - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - offerMessage: credentialOfferMessage, - threadId: credentialOfferMessage.id, - connectionId: connection.id, - state: CredentialState.OfferReceived, - } - expect(repositorySaveSpy).toHaveBeenCalledTimes(1) - const [[createdCredentialRecord]] = repositorySaveSpy.mock.calls - expect(createdCredentialRecord).toMatchObject(expectedCredentialRecord) - expect(returnedCredentialRecord).toMatchObject(expectedCredentialRecord) - }) - - test(`emits stateChange event with ${CredentialState.OfferReceived}`, async () => { - const eventListenerMock = jest.fn() - eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) - - // when - await credentialService.processOffer(messageContext) - - // then - expect(eventListenerMock).toHaveBeenCalledWith({ - type: 'CredentialStateChanged', - payload: { - previousState: null, - credentialRecord: expect.objectContaining({ - state: CredentialState.OfferReceived, - }), - }, - }) - }) + mockFunction(indyLedgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) + mockFunction(indyLedgerService.getSchema).mockReturnValue(Promise.resolve(schema)) }) describe('createCredentialRequest', () => { - let credentialRecord: CredentialRecord - + let credentialRecord: CredentialExchangeRecord beforeEach(() => { credentialRecord = mockCredentialRecord({ state: CredentialState.OfferReceived, threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) + initMessages() }) test(`updates state to ${CredentialState.RequestSent}, set request metadata`, async () => { const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') + // mock offer so that the request works + // when - await credentialService.createRequest(credentialRecord, { - holderDid: connection.did, - }) + const options: RequestCredentialOptions = {} + await credentialService.createRequest(credentialRecord, options, 'holderDid') // then expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) @@ -407,36 +304,20 @@ describe('CredentialService', () => { }) }) - test(`emits stateChange event with ${CredentialState.RequestSent}`, async () => { - const eventListenerMock = jest.fn() - eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) - - // when - await credentialService.createRequest(credentialRecord, { - holderDid: connection.did, - }) - - // then - expect(eventListenerMock).toHaveBeenCalledWith({ - type: 'CredentialStateChanged', - payload: { - previousState: CredentialState.OfferReceived, - credentialRecord: expect.objectContaining({ - state: CredentialState.RequestSent, - }), - }, - }) - }) - test('returns credential request message base on existing credential offer message', async () => { // given const comment = 'credential request comment' + const options: RequestCredentialOptions = { + connectionId: credentialRecord.connectionId, + comment: 'credential request comment', + } // when - const { message: credentialRequest } = await credentialService.createRequest(credentialRecord, { - comment, - holderDid: connection.did, - }) + const { message: credentialRequest } = await credentialService.createRequest( + credentialRecord, + options, + 'holderDid' + ) // then expect(credentialRequest.toJSON()).toMatchObject({ @@ -464,7 +345,7 @@ describe('CredentialService', () => { await Promise.all( invalidCredentialStates.map(async (state) => { await expect( - credentialService.createRequest(mockCredentialRecord({ state }), { holderDid: connection.id }) + credentialService.createRequest(mockCredentialRecord({ state }), {}, 'holderDid') ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) }) ) @@ -472,13 +353,12 @@ describe('CredentialService', () => { }) describe('processCredentialRequest', () => { - let credential: CredentialRecord - let messageContext: InboundMessageContext - + let credential: CredentialExchangeRecord + let messageContext: InboundMessageContext beforeEach(() => { credential = mockCredentialRecord({ state: CredentialState.OfferSent }) - const credentialRequest = new RequestCredentialMessage({ + const credentialRequest = new V1RequestCredentialMessage({ comment: 'abcd', requestAttachments: [requestAttachment], }) @@ -486,6 +366,7 @@ describe('CredentialService', () => { messageContext = new InboundMessageContext(credentialRequest, { connection, }) + initMessages() }) test(`updates state to ${CredentialState.RequestReceived}, set request and returns credential record`, async () => { @@ -502,14 +383,8 @@ describe('CredentialService', () => { threadId: 'somethreadid', connectionId: connection.id, }) - - const expectedCredentialRecord = { - state: CredentialState.RequestReceived, - requestMessage: messageContext.message, - } expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) - expect(repositoryUpdateSpy).toHaveBeenNthCalledWith(1, expect.objectContaining(expectedCredentialRecord)) - expect(returnedCredentialRecord).toMatchObject(expectedCredentialRecord) + expect(returnedCredentialRecord.state).toEqual(CredentialState.RequestReceived) }) test(`emits stateChange event from ${CredentialState.OfferSent} to ${CredentialState.RequestReceived}`, async () => { @@ -517,17 +392,15 @@ describe('CredentialService', () => { eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) - await credentialService.processRequest(messageContext) + // mock offer so that the request works + const returnedCredentialRecord = await credentialService.processRequest(messageContext) - expect(eventListenerMock).toHaveBeenCalledWith({ - type: 'CredentialStateChanged', - payload: { - previousState: CredentialState.OfferSent, - credentialRecord: expect.objectContaining({ - state: CredentialState.RequestReceived, - }), - }, + // then + expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, { + threadId: 'somethreadid', + connectionId: connection.id, }) + expect(returnedCredentialRecord.state).toEqual(CredentialState.RequestReceived) }) const validState = CredentialState.OfferSent @@ -548,25 +421,24 @@ describe('CredentialService', () => { describe('createCredential', () => { const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249746' - let credential: CredentialRecord - + let credential: CredentialExchangeRecord beforeEach(() => { credential = mockCredentialRecord({ state: CredentialState.RequestReceived, - requestMessage: new RequestCredentialMessage({ + requestMessage: new V1RequestCredentialMessage({ comment: 'abcd', requestAttachments: [requestAttachment], }), threadId, connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) + initMessages() }) - test(`updates state to ${CredentialState.CredentialIssued}`, async () => { const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') // when - await credentialService.createCredential(credential) + await credentialService.createCredential(credential, acceptRequestOptions) // then expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) @@ -578,13 +450,13 @@ describe('CredentialService', () => { test(`emits stateChange event from ${CredentialState.RequestReceived} to ${CredentialState.CredentialIssued}`, async () => { const eventListenerMock = jest.fn() - eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // given mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credential)) + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) // when - await credentialService.createCredential(credential) + await credentialService.createCredential(credential, acceptRequestOptions) // then expect(eventListenerMock).toHaveBeenCalledWith({ @@ -604,8 +476,8 @@ describe('CredentialService', () => { const comment = 'credential response comment' // when - const { message: credentialResponse } = await credentialService.createCredential(credential, { comment }) + const { message: credentialResponse } = await credentialService.createCredential(credential, acceptRequestOptions) // then expect(credentialResponse.toJSON()).toMatchObject({ '@id': expect.any(String), @@ -633,61 +505,23 @@ describe('CredentialService', () => { credentialValues: {}, }) const [responseAttachment] = credentialResponse.credentialAttachments - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(JsonEncoder.fromBase64(responseAttachment.data.base64!)).toEqual(cred) - }) - - test('throws error when credential record has no request', async () => { - // when, then - await expect( - credentialService.createCredential( - mockCredentialRecord({ - state: CredentialState.RequestReceived, - threadId, - connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', - }) - ) - ).rejects.toThrowError( - `Missing required base64 or json encoded attachment data for credential request with thread id ${threadId}` - ) - }) - - const validState = CredentialState.RequestReceived - const invalidCredentialStates = Object.values(CredentialState).filter((state) => state !== validState) - test(`throws an error when state transition is invalid`, async () => { - await Promise.all( - invalidCredentialStates.map(async (state) => { - await expect( - credentialService.createCredential( - mockCredentialRecord({ - state, - threadId, - connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', - requestMessage: new RequestCredentialMessage({ - requestAttachments: [requestAttachment], - }), - }) - ) - ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) - }) - ) + expect(responseAttachment.getDataAsJson()).toEqual(cred) }) }) describe('processCredential', () => { - let credential: CredentialRecord - let messageContext: InboundMessageContext - + let credential: CredentialExchangeRecord + let messageContext: InboundMessageContext beforeEach(() => { credential = mockCredentialRecord({ state: CredentialState.RequestSent, - requestMessage: new RequestCredentialMessage({ + requestMessage: new V1RequestCredentialMessage({ requestAttachments: [requestAttachment], }), metadata: { indyRequest: { cred_req: 'meta-data' } }, }) - const credentialResponse = new IssueCredentialMessage({ + const credentialResponse = new V1IssueCredentialMessage({ comment: 'abcd', credentialAttachments: [credentialAttachment], }) @@ -695,6 +529,7 @@ describe('CredentialService', () => { messageContext = new InboundMessageContext(credentialResponse, { connection, }) + initMessages() }) test('finds credential record by thread ID and saves credential attachment into the wallet', async () => { @@ -702,10 +537,8 @@ describe('CredentialService', () => { Promise, [StoreCredentialOptions] > - // given mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) - // when await credentialService.processCredential(messageContext) @@ -722,105 +555,11 @@ describe('CredentialService', () => { credentialDefinition: credDef, }) }) - - test(`updates state to ${CredentialState.CredentialReceived}, set credentialId and returns credential record`, async () => { - const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') - - // given - mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) - - // when - const updatedCredential = await credentialService.processCredential(messageContext) - - // then - const expectedCredentialRecord = { - credentialId: expect.any(String), - state: CredentialState.CredentialReceived, - } - expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) - const [[updatedCredentialRecord]] = repositoryUpdateSpy.mock.calls - expect(updatedCredentialRecord).toMatchObject(expectedCredentialRecord) - expect(updatedCredential).toMatchObject(expectedCredentialRecord) - }) - - test(`emits stateChange event from ${CredentialState.RequestSent} to ${CredentialState.CredentialReceived}`, async () => { - const eventListenerMock = jest.fn() - eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) - - // given - mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) - - // when - await credentialService.processCredential(messageContext) - - // then - expect(eventListenerMock).toHaveBeenCalledWith({ - type: 'CredentialStateChanged', - payload: { - previousState: CredentialState.RequestSent, - credentialRecord: expect.objectContaining({ - state: CredentialState.CredentialReceived, - }), - }, - }) - }) - - test('throws error when credential record has no request metadata', async () => { - // given - mockFunction(credentialRepository.getSingleByQuery).mockReturnValue( - Promise.resolve( - mockCredentialRecord({ - state: CredentialState.RequestSent, - id: 'id', - }) - ) - ) - - // when, then - await expect(credentialService.processCredential(messageContext)).rejects.toThrowError( - `Missing required request metadata for credential with id id` - ) - }) - - test('throws error when credential attribute values does not match received credential values', async () => { - mockFunction(credentialRepository.getSingleByQuery).mockReturnValue( - Promise.resolve( - mockCredentialRecord({ - state: CredentialState.RequestSent, - id: 'id', - // Take only first value from credential - credentialAttributes: [credentialPreview.attributes[0]], - }) - ) - ) - - await expect(credentialService.processCredential(messageContext)).rejects.toThrowError() - }) - - const validState = CredentialState.RequestSent - const invalidCredentialStates = Object.values(CredentialState).filter((state) => state !== validState) - test(`throws an error when state transition is invalid`, async () => { - await Promise.all( - invalidCredentialStates.map(async (state) => { - mockFunction(credentialRepository.getSingleByQuery).mockReturnValue( - Promise.resolve( - mockCredentialRecord({ - state, - metadata: { indyRequest: { cred_req: 'meta-data' } }, - }) - ) - ) - await expect(credentialService.processCredential(messageContext)).rejects.toThrowError( - `Credential record is in invalid state ${state}. Valid states are: ${validState}.` - ) - }) - ) - }) }) describe('createAck', () => { const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249746' - let credential: CredentialRecord + let credential: CredentialExchangeRecord beforeEach(() => { credential = mockCredentialRecord({ @@ -897,21 +636,22 @@ describe('CredentialService', () => { }) describe('processAck', () => { - let credential: CredentialRecord - let messageContext: InboundMessageContext + let credential: CredentialExchangeRecord + let messageContext: InboundMessageContext beforeEach(() => { credential = mockCredentialRecord({ state: CredentialState.CredentialIssued, }) - const credentialRequest = new CredentialAckMessage({ + const credentialRequest = new V1CredentialAckMessage({ status: AckStatus.OK, threadId: 'somethreadid', }) messageContext = new InboundMessageContext(credentialRequest, { connection, }) + initMessages() }) test(`updates state to ${CredentialState.Done} and returns credential record`, async () => { @@ -936,58 +676,11 @@ describe('CredentialService', () => { expect(updatedCredentialRecord).toMatchObject(expectedCredentialRecord) expect(returnedCredentialRecord).toMatchObject(expectedCredentialRecord) }) - - test(`emits stateChange event from ${CredentialState.CredentialIssued} to ${CredentialState.Done}`, async () => { - const eventListenerMock = jest.fn() - eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) - - // given - mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) - - // when - await credentialService.processAck(messageContext) - - // then - expect(eventListenerMock).toHaveBeenCalledWith({ - type: 'CredentialStateChanged', - payload: { - previousState: CredentialState.CredentialIssued, - credentialRecord: expect.objectContaining({ - state: CredentialState.Done, - }), - }, - }) - }) - - test('throws error when there is no credential found by thread ID', async () => { - // given - mockFunction(credentialRepository.getSingleByQuery).mockReturnValue( - Promise.reject(new RecordNotFoundError('not found', { recordType: CredentialRecord.type })) - ) - - // when, then - await expect(credentialService.processAck(messageContext)).rejects.toThrowError(RecordNotFoundError) - }) - - const validState = CredentialState.CredentialIssued - const invalidCredentialStates = Object.values(CredentialState).filter((state) => state !== validState) - test(`throws an error when state transition is invalid`, async () => { - await Promise.all( - invalidCredentialStates.map(async (state) => { - mockFunction(credentialRepository.getSingleByQuery).mockReturnValue( - Promise.resolve(mockCredentialRecord({ state })) - ) - await expect(credentialService.processAck(messageContext)).rejects.toThrowError( - `Credential record is in invalid state ${state}. Valid states are: ${validState}.` - ) - }) - ) - }) }) describe('createProblemReport', () => { const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249746' - let credential: CredentialRecord + let credential: CredentialExchangeRecord beforeEach(() => { credential = mockCredentialRecord({ @@ -1002,7 +695,7 @@ describe('CredentialService', () => { mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credential)) // when - const credentialProblemReportMessage = await new CredentialProblemReportMessage({ + const credentialProblemReportMessage = new V1CredentialProblemReportMessage({ description: { en: 'Indy error', code: CredentialProblemReportReason.IssuanceAbandoned, @@ -1022,15 +715,15 @@ describe('CredentialService', () => { }) describe('processProblemReport', () => { - let credential: CredentialRecord - let messageContext: InboundMessageContext + let credential: CredentialExchangeRecord + let messageContext: InboundMessageContext beforeEach(() => { credential = mockCredentialRecord({ state: CredentialState.OfferReceived, }) - const credentialProblemReportMessage = new CredentialProblemReportMessage({ + const credentialProblemReportMessage = new V1CredentialProblemReportMessage({ description: { en: 'Indy error', code: CredentialProblemReportReason.IssuanceAbandoned, @@ -1125,15 +818,15 @@ describe('CredentialService', () => { mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credential)) await credentialService.deleteById(credential.id, { - deleteAssociatedCredential: true, + deleteAssociatedCredentials: true, }) - expect(storeCredentialMock).toHaveBeenNthCalledWith(1, credential.credentialId) + expect(storeCredentialMock).toHaveBeenNthCalledWith(1, credential.credentials[0].credentialRecordId) }) }) describe('declineOffer', () => { const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249754' - let credential: CredentialRecord + let credential: CredentialExchangeRecord beforeEach(() => { credential = mockCredentialRecord({ @@ -1195,7 +888,7 @@ describe('CredentialService', () => { }) describe('revocationNotification', () => { - let credential: CredentialRecord + let credential: CredentialExchangeRecord beforeEach(() => { credential = mockCredentialRecord({ @@ -1205,6 +898,7 @@ describe('CredentialService', () => { indyCredentialRevocationId: '1', connectionId: connection.id, }) + logger = agentConfig.logger }) test('Test revocation notification event being emitted for V1', async () => { @@ -1259,7 +953,7 @@ describe('CredentialService', () => { const recordNotFoundError = new RecordNotFoundError( `No record found for given query '${JSON.stringify({ revocationRegistryId, credentialRevocationId })}'`, { - recordType: CredentialRecord.type, + recordType: CredentialExchangeRecord.type, } ) @@ -1356,7 +1050,7 @@ describe('CredentialService', () => { const recordNotFoundError = new RecordNotFoundError( `No record found for given query '${JSON.stringify({ revocationRegistryId, credentialRevocationId })}'`, { - recordType: CredentialRecord.type, + recordType: CredentialExchangeRecord.type, } ) @@ -1388,7 +1082,7 @@ describe('CredentialService', () => { const revocationNotificationMessage = new V2RevocationNotificationMessage({ credentialId: invalidCredentialId, revocationFormat: 'indy', - comment: 'Credenti1al has been revoked', + comment: 'Credential has been revoked', }) const messageContext = new InboundMessageContext(revocationNotificationMessage) diff --git a/packages/core/src/modules/credentials/__tests__/V1CredentialService.offer.test.ts b/packages/core/src/modules/credentials/__tests__/V1CredentialService.offer.test.ts new file mode 100644 index 0000000000..03c3471048 --- /dev/null +++ b/packages/core/src/modules/credentials/__tests__/V1CredentialService.offer.test.ts @@ -0,0 +1,335 @@ +import type { AgentConfig } from '../../../agent/AgentConfig' +import type { ConnectionService } from '../../connections/services/ConnectionService' +import type { CredentialStateChangedEvent } from '../CredentialEvents' +import type { OfferCredentialOptions } from '../CredentialsModuleOptions' + +import { Agent } from '../../../../src/agent/Agent' +import { Dispatcher } from '../../../../src/agent/Dispatcher' +import { DidCommMessageRepository } from '../../../../src/storage' +import { getAgentConfig, getBaseConfig, getMockConnection, mockFunction } from '../../../../tests/helpers' +import { EventEmitter } from '../../../agent/EventEmitter' +import { MessageSender } from '../../../agent/MessageSender' +import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' +import { ConnectionState } from '../../connections' +import { IndyHolderService } from '../../indy/services/IndyHolderService' +import { IndyIssuerService } from '../../indy/services/IndyIssuerService' +import { IndyLedgerService } from '../../ledger/services' +import { MediationRecipientService } from '../../routing/services/MediationRecipientService' +import { CredentialEventTypes } from '../CredentialEvents' +import { CredentialProtocolVersion } from '../CredentialProtocolVersion' +import { CredentialState } from '../CredentialState' +import { IndyCredentialFormatService } from '../formats' +import { V1CredentialPreview } from '../protocol/v1/V1CredentialPreview' +import { V1CredentialService } from '../protocol/v1/V1CredentialService' +import { INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, V1OfferCredentialMessage } from '../protocol/v1/messages' +import { CredentialExchangeRecord } from '../repository/CredentialExchangeRecord' +import { CredentialRepository } from '../repository/CredentialRepository' +import { RevocationService } from '../services' + +import { schema, credDef } from './fixtures' + +// Mock classes +jest.mock('../repository/CredentialRepository') +jest.mock('../../../../src/storage/didcomm/DidCommMessageRepository') +jest.mock('../../../modules/ledger/services/IndyLedgerService') +jest.mock('../../indy/services/IndyHolderService') +jest.mock('../../indy/services/IndyIssuerService') +jest.mock('../../routing/services/MediationRecipientService') + +// Mock typed object +const CredentialRepositoryMock = CredentialRepository as jest.Mock +const IndyLedgerServiceMock = IndyLedgerService as jest.Mock +const IndyHolderServiceMock = IndyHolderService as jest.Mock +const IndyIssuerServiceMock = IndyIssuerService as jest.Mock +const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock +const MessageSenderMock = MessageSender as jest.Mock +const MediationRecipientServiceMock = MediationRecipientService as jest.Mock + +const connection = getMockConnection({ + id: '123', + state: ConnectionState.Complete, +}) + +const credentialPreview = V1CredentialPreview.fromRecord({ + name: 'John', + age: '99', +}) + +const badCredentialPreview = V1CredentialPreview.fromRecord({ + test: 'credential', + error: 'yes', +}) +const offerAttachment = new Attachment({ + id: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + mimeType: 'application/json', + data: new AttachmentData({ + base64: + 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0', + }), +}) + +const { config, agentDependencies: dependencies } = getBaseConfig('Agent Class Test V1 Cred') + +describe('CredentialService', () => { + let agent: Agent + let credentialRepository: CredentialRepository + let indyLedgerService: IndyLedgerService + let indyIssuerService: IndyIssuerService + let indyHolderService: IndyHolderService + let eventEmitter: EventEmitter + let didCommMessageRepository: DidCommMessageRepository + let mediationRecipientService: MediationRecipientService + let messageSender: MessageSender + let agentConfig: AgentConfig + + let dispatcher: Dispatcher + let credentialService: V1CredentialService + let revocationService: RevocationService + + beforeEach(async () => { + credentialRepository = new CredentialRepositoryMock() + indyIssuerService = new IndyIssuerServiceMock() + didCommMessageRepository = new DidCommMessageRepositoryMock() + messageSender = new MessageSenderMock() + mediationRecipientService = new MediationRecipientServiceMock() + indyHolderService = new IndyHolderServiceMock() + indyLedgerService = new IndyLedgerServiceMock() + mockFunction(indyLedgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) + agentConfig = getAgentConfig('CredentialServiceTest') + eventEmitter = new EventEmitter(agentConfig) + + dispatcher = new Dispatcher(messageSender, eventEmitter, agentConfig) + revocationService = new RevocationService(credentialRepository, eventEmitter, agentConfig) + + credentialService = new V1CredentialService( + { + getById: () => Promise.resolve(connection), + assertConnectionOrServiceDecorator: () => true, + } as unknown as ConnectionService, + didCommMessageRepository, + agentConfig, + mediationRecipientService, + dispatcher, + eventEmitter, + credentialRepository, + new IndyCredentialFormatService( + credentialRepository, + eventEmitter, + indyIssuerService, + indyLedgerService, + indyHolderService, + agentConfig + ), + revocationService + ) + mockFunction(indyLedgerService.getSchema).mockReturnValue(Promise.resolve(schema)) + }) + + describe('createCredentialOffer', () => { + let offerOptions: OfferCredentialOptions + + beforeEach(async () => { + offerOptions = { + comment: 'some comment', + connectionId: connection.id, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', + }, + }, + protocolVersion: CredentialProtocolVersion.V1, + } + }) + + test(`creates credential record in ${CredentialState.OfferSent} state with offer, thread ID`, async () => { + const repositorySaveSpy = jest.spyOn(credentialRepository, 'save') + + await credentialService.createOffer(offerOptions) + + // then + expect(repositorySaveSpy).toHaveBeenCalledTimes(1) + + const [[createdCredentialRecord]] = repositorySaveSpy.mock.calls + expect(createdCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: createdCredentialRecord.threadId, + connectionId: connection.id, + state: CredentialState.OfferSent, + }) + }) + + test(`emits stateChange event with a new credential in ${CredentialState.OfferSent} state`, async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) + + await credentialService.createOffer(offerOptions) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'CredentialStateChanged', + payload: { + previousState: null, + credentialRecord: expect.objectContaining({ + state: CredentialState.OfferSent, + }), + }, + }) + }) + + test('returns credential offer message', async () => { + const { message: credentialOffer } = await credentialService.createOffer(offerOptions) + expect(credentialOffer.toJSON()).toMatchObject({ + '@id': expect.any(String), + '@type': 'https://didcomm.org/issue-credential/1.0/offer-credential', + comment: 'some comment', + credential_preview: { + '@type': 'https://didcomm.org/issue-credential/1.0/credential-preview', + attributes: [ + { + name: 'name', + 'mime-type': 'text/plain', + value: 'John', + }, + { + name: 'age', + 'mime-type': 'text/plain', + value: '99', + }, + ], + }, + 'offers~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + }) + }) + + test('throw error if credential preview attributes do not match with schema attributes', async () => { + offerOptions = { + ...offerOptions, + credentialFormats: { + indy: { + attributes: badCredentialPreview.attributes, + credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', + }, + }, + } + expect(credentialService.createOffer(offerOptions)).rejects.toThrowError( + `The credential preview attributes do not match the schema attributes (difference is: test,error,name,age, needs: name,age)` + ) + const credentialPreviewWithExtra = V1CredentialPreview.fromRecord({ + test: 'credential', + error: 'yes', + name: 'John', + age: '99', + }) + + offerOptions = { + ...offerOptions, + credentialFormats: { + indy: { + attributes: credentialPreviewWithExtra.attributes, + credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', + }, + }, + } + expect(credentialService.createOffer(offerOptions)).rejects.toThrowError( + `The credential preview attributes do not match the schema attributes (difference is: test,error, needs: name,age)` + ) + }) + }) + + describe('processCredentialOffer', () => { + let messageContext: InboundMessageContext + let credentialOfferMessage: V1OfferCredentialMessage + + beforeEach(async () => { + credentialOfferMessage = new V1OfferCredentialMessage({ + comment: 'some comment', + credentialPreview: credentialPreview, + offerAttachments: [offerAttachment], + }) + messageContext = new InboundMessageContext(credentialOfferMessage, { + connection, + }) + messageContext.connection = connection + }) + + test(`creates and return credential record in ${CredentialState.OfferReceived} state with offer, thread ID`, async () => { + const repositorySaveSpy = jest.spyOn(credentialRepository, 'save') + agent = new Agent(config, dependencies) + await agent.initialize() + expect(agent.isInitialized).toBe(true) + const agentConfig = getAgentConfig('CredentialServiceTest') + eventEmitter = new EventEmitter(agentConfig) + + const dispatcher = agent.injectionContainer.resolve(Dispatcher) + const mediationRecipientService = agent.injectionContainer.resolve(MediationRecipientService) + + credentialService = new V1CredentialService( + { + getById: () => Promise.resolve(connection), + assertConnectionOrServiceDecorator: () => true, + } as unknown as ConnectionService, + didCommMessageRepository, + agentConfig, + mediationRecipientService, + dispatcher, + eventEmitter, + credentialRepository, + new IndyCredentialFormatService( + credentialRepository, + eventEmitter, + indyIssuerService, + indyLedgerService, + indyHolderService, + agentConfig + ), + revocationService + ) + // when + const returnedCredentialRecord = await credentialService.processOffer(messageContext) + + // then + const expectedCredentialRecord = { + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: credentialOfferMessage.id, + connectionId: connection.id, + state: CredentialState.OfferReceived, + } + expect(repositorySaveSpy).toHaveBeenCalledTimes(1) + const [[createdCredentialRecord]] = repositorySaveSpy.mock.calls + expect(createdCredentialRecord).toMatchObject(expectedCredentialRecord) + expect(returnedCredentialRecord).toMatchObject(expectedCredentialRecord) + }) + + test(`emits stateChange event with ${CredentialState.OfferReceived}`, async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) + + // when + await credentialService.processOffer(messageContext) + + // then + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'CredentialStateChanged', + payload: { + previousState: null, + credentialRecord: expect.objectContaining({ + state: CredentialState.OfferReceived, + }), + }, + }) + }) + }) +}) diff --git a/packages/core/src/modules/credentials/__tests__/V2CredentialService.cred.test.ts b/packages/core/src/modules/credentials/__tests__/V2CredentialService.cred.test.ts new file mode 100644 index 0000000000..201a781c97 --- /dev/null +++ b/packages/core/src/modules/credentials/__tests__/V2CredentialService.cred.test.ts @@ -0,0 +1,879 @@ +import type { AgentConfig } from '../../../../src/agent/AgentConfig' +import type { ConnectionService } from '../../connections/services/ConnectionService' +import type { CredentialStateChangedEvent } from '../CredentialEvents' +import type { AcceptRequestOptions, RequestCredentialOptions } from '../CredentialsModuleOptions' +import type { + CredentialFormatSpec, + FormatServiceRequestCredentialFormats, +} from '../formats/models/CredentialFormatServiceOptions' +import type { CredentialPreviewAttribute } from '../models/CredentialPreviewAttributes' +import type { IndyCredentialMetadata } from '../protocol/v1/models/CredentialInfo' +import type { V2IssueCredentialMessageProps } from '../protocol/v2/messages/V2IssueCredentialMessage' +import type { V2OfferCredentialMessageOptions } from '../protocol/v2/messages/V2OfferCredentialMessage' +import type { V2RequestCredentialMessageOptions } from '../protocol/v2/messages/V2RequestCredentialMessage' +import type { CustomCredentialTags } from '../repository/CredentialExchangeRecord' + +import { getAgentConfig, getBaseConfig, getMockConnection, mockFunction } from '../../../../tests/helpers' +import { Agent } from '../../../agent/Agent' +import { Dispatcher } from '../../../agent/Dispatcher' +import { EventEmitter } from '../../../agent/EventEmitter' +import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' +import { DidCommMessageRepository, DidCommMessageRole } from '../../../storage' +import { JsonEncoder } from '../../../utils/JsonEncoder' +import { AckStatus } from '../../common/messages/AckMessage' +import { ConnectionState } from '../../connections' +import { IndyHolderService } from '../../indy/services/IndyHolderService' +import { IndyIssuerService } from '../../indy/services/IndyIssuerService' +import { IndyLedgerService } from '../../ledger/services' +import { MediationRecipientService } from '../../routing/services/MediationRecipientService' +import { CredentialEventTypes } from '../CredentialEvents' +import { CredentialProtocolVersion } from '../CredentialProtocolVersion' +import { CredentialState } from '../CredentialState' +import { CredentialUtils } from '../CredentialUtils' +import { CredentialFormatType } from '../CredentialsModuleOptions' +import { CredentialProblemReportReason } from '../errors/CredentialProblemReportReason' +import { IndyCredentialFormatService } from '../formats' +import { V1CredentialPreview } from '../protocol/v1/V1CredentialPreview' +import { + INDY_CREDENTIAL_ATTACHMENT_ID, + INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + V1OfferCredentialMessage, +} from '../protocol/v1/messages' +import { V2CredentialService } from '../protocol/v2/V2CredentialService' +import { V2CredentialAckMessage } from '../protocol/v2/messages/V2CredentialAckMessage' +import { V2CredentialProblemReportMessage } from '../protocol/v2/messages/V2CredentialProblemReportMessage' +import { V2IssueCredentialMessage } from '../protocol/v2/messages/V2IssueCredentialMessage' +import { V2OfferCredentialMessage } from '../protocol/v2/messages/V2OfferCredentialMessage' +import { V2RequestCredentialMessage } from '../protocol/v2/messages/V2RequestCredentialMessage' +import { CredentialExchangeRecord } from '../repository/CredentialExchangeRecord' +import { CredentialMetadataKeys } from '../repository/CredentialMetadataTypes' +import { CredentialRepository } from '../repository/CredentialRepository' +import { RevocationService } from '../services' + +import { credDef, credReq, credOffer } from './fixtures' + +// Mock classes +jest.mock('../repository/CredentialRepository') +jest.mock('../../../modules/ledger/services/IndyLedgerService') +jest.mock('../../indy/services/IndyHolderService') +jest.mock('../../indy/services/IndyIssuerService') +jest.mock('../../../../src/storage/didcomm/DidCommMessageRepository') +jest.mock('../../routing/services/MediationRecipientService') + +// Mock typed object +const CredentialRepositoryMock = CredentialRepository as jest.Mock +const IndyLedgerServiceMock = IndyLedgerService as jest.Mock +const IndyHolderServiceMock = IndyHolderService as jest.Mock +const IndyIssuerServiceMock = IndyIssuerService as jest.Mock +const MediationRecipientServiceMock = MediationRecipientService as jest.Mock +const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock + +const connection = getMockConnection({ + id: '123', + state: ConnectionState.Complete, +}) + +const credentialPreview = V1CredentialPreview.fromRecord({ + name: 'John', + age: '99', +}) + +const offerAttachment = new Attachment({ + id: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + mimeType: 'application/json', + data: new AttachmentData({ + base64: + 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0', + }), +}) + +const requestAttachment = new Attachment({ + id: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(credReq), + }), +}) + +const credentialAttachment = new Attachment({ + id: INDY_CREDENTIAL_ATTACHMENT_ID, + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64({ + values: CredentialUtils.convertAttributesToValues(credentialPreview.attributes), + }), + }), +}) + +const v2CredentialRequest: FormatServiceRequestCredentialFormats = { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', + }, +} + +const offerOptions: V2OfferCredentialMessageOptions = { + id: '', + formats: [ + { + attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + format: 'hlindy/cred-abstract@v2.0', + }, + ], + comment: 'some comment', + credentialPreview: credentialPreview, + offerAttachments: [offerAttachment], + replacementId: undefined, +} +const requestFormat: CredentialFormatSpec = { + attachId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + format: 'hlindy/cred-req@v2.0', +} + +const requestOptions: V2RequestCredentialMessageOptions = { + id: '', + formats: [requestFormat], + requestsAttach: [requestAttachment], +} + +// A record is deserialized to JSON when it's stored into the storage. We want to simulate this behaviour for `offer` +// object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. +const mockCredentialRecord = ({ + state, + metadata, + threadId, + connectionId, + tags, + id, + credentialAttributes, +}: { + state?: CredentialState + metadata?: IndyCredentialMetadata & { indyRequest: Record } + tags?: CustomCredentialTags + threadId?: string + connectionId?: string + id?: string + credentialAttributes?: CredentialPreviewAttribute[] +} = {}) => { + const offerMessage = new V1OfferCredentialMessage({ + comment: 'some comment', + credentialPreview: credentialPreview, + offerAttachments: [offerAttachment], + }) + + const credentialRecord = new CredentialExchangeRecord({ + id, + credentialAttributes: credentialAttributes || credentialPreview.attributes, + state: state || CredentialState.OfferSent, + threadId: threadId ?? offerMessage.id, + connectionId: connectionId ?? '123', + credentials: [ + { + credentialRecordType: CredentialFormatType.Indy, + credentialRecordId: '123456', + }, + ], + tags, + protocolVersion: CredentialProtocolVersion.V2, + }) + + if (metadata?.indyRequest) { + credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, { ...metadata.indyRequest }) + } + + if (metadata?.schemaId) { + credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { + schemaId: metadata.schemaId, + }) + } + + if (metadata?.credentialDefinitionId) { + credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { + credentialDefinitionId: metadata.credentialDefinitionId, + }) + } + + return credentialRecord +} + +const { config, agentDependencies: dependencies } = getBaseConfig('Agent Class Test V2 Cred') + +let credentialRequestMessage: V2RequestCredentialMessage +let credentialOfferMessage: V2OfferCredentialMessage +describe('CredentialService', () => { + let agent: Agent + let credentialRepository: CredentialRepository + let indyLedgerService: IndyLedgerService + let indyIssuerService: IndyIssuerService + let indyHolderService: IndyHolderService + let eventEmitter: EventEmitter + let didCommMessageRepository: DidCommMessageRepository + let mediationRecipientService: MediationRecipientService + let agentConfig: AgentConfig + + let dispatcher: Dispatcher + let credentialService: V2CredentialService + let revocationService: RevocationService + + const initMessages = () => { + credentialRequestMessage = new V2RequestCredentialMessage(requestOptions) + credentialOfferMessage = new V2OfferCredentialMessage(offerOptions) + mockFunction(didCommMessageRepository.findAgentMessage).mockImplementation(async (options) => { + if (options.messageClass === V2OfferCredentialMessage) { + return credentialOfferMessage + } + if (options.messageClass === V2RequestCredentialMessage) { + return credentialRequestMessage + } + return null + }) + } + beforeEach(async () => { + credentialRepository = new CredentialRepositoryMock() + indyIssuerService = new IndyIssuerServiceMock() + mediationRecipientService = new MediationRecipientServiceMock() + indyHolderService = new IndyHolderServiceMock() + indyLedgerService = new IndyLedgerServiceMock() + mockFunction(indyLedgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) + agent = new Agent(config, dependencies) + agentConfig = getAgentConfig('CredentialServiceTest') + eventEmitter = new EventEmitter(agentConfig) + dispatcher = agent.injectionContainer.resolve(Dispatcher) + didCommMessageRepository = new DidCommMessageRepositoryMock() + revocationService = new RevocationService(credentialRepository, eventEmitter, agentConfig) + + credentialService = new V2CredentialService( + { + getById: () => Promise.resolve(connection), + assertConnectionOrServiceDecorator: () => true, + } as unknown as ConnectionService, + credentialRepository, + eventEmitter, + dispatcher, + agentConfig, + mediationRecipientService, + didCommMessageRepository, + new IndyCredentialFormatService( + credentialRepository, + eventEmitter, + indyIssuerService, + indyLedgerService, + indyHolderService, + agentConfig + ), + revocationService + ) + }) + + describe('createCredentialRequest', () => { + let credentialRecord: CredentialExchangeRecord + let credentialOfferMessage: V2OfferCredentialMessage + beforeEach(() => { + credentialRecord = mockCredentialRecord({ + state: CredentialState.OfferReceived, + threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', + connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', + }) + initMessages() + }) + + test(`updates state to ${CredentialState.RequestSent}, set request metadata`, async () => { + mediationRecipientService = agent.injectionContainer.resolve(MediationRecipientService) + const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') + + // mock offer so that the request works + + await didCommMessageRepository.saveAgentMessage({ + agentMessage: credentialOfferMessage, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + + const requestOptions: RequestCredentialOptions = { + credentialFormats: v2CredentialRequest, + } + + // when + + await credentialService.createRequest(credentialRecord, requestOptions, 'holderDid') + + // then + expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) + const [[updatedCredentialRecord]] = repositoryUpdateSpy.mock.calls + expect(updatedCredentialRecord.toJSON()).toMatchObject({ + metadata: { '_internal/indyRequest': { cred_req: 'meta-data' } }, + state: CredentialState.RequestSent, + }) + }) + + test('returns credential request message base on existing credential offer message', async () => { + // given + const comment = 'credential request comment' + const options: RequestCredentialOptions = { + connectionId: credentialRecord.connectionId, + comment: 'credential request comment', + } + // when + const { message: credentialRequest } = await credentialService.createRequest( + credentialRecord, + options, + 'holderDid' + ) + + // then + expect(credentialRequest.toJSON()).toMatchObject({ + '@id': expect.any(String), + '@type': 'https://didcomm.org/issue-credential/2.0/request-credential', + '~thread': { + thid: credentialRecord.threadId, + }, + comment, + 'requests~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + }) + }) + + const validState = CredentialState.OfferReceived + const invalidCredentialStates = Object.values(CredentialState).filter((state) => state !== validState) + test(`throws an error when state transition is invalid`, async () => { + await Promise.all( + invalidCredentialStates.map(async (state) => { + await expect( + credentialService.createRequest(mockCredentialRecord({ state }), {}, 'mockDid') + ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) + }) + ) + }) + }) + + describe('processCredentialRequest', () => { + let credential: CredentialExchangeRecord + let messageContext: InboundMessageContext + beforeEach(() => { + credential = mockCredentialRecord({ state: CredentialState.OfferSent }) + initMessages() + credentialRequestMessage.setThread({ threadId: 'somethreadid' }) + messageContext = new InboundMessageContext(credentialRequestMessage, { + connection, + }) + }) + + test(`updates state to ${CredentialState.RequestReceived}, set request and returns credential record`, async () => { + const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') + // given + mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) + + // when + const returnedCredentialRecord = await credentialService.processRequest(messageContext) + + // then + expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, { + threadId: 'somethreadid', + connectionId: connection.id, + }) + expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) + expect(returnedCredentialRecord.state).toEqual(CredentialState.RequestReceived) + }) + + test(`emits stateChange event from ${CredentialState.OfferSent} to ${CredentialState.RequestReceived}`, async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) + mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) + const returnedCredentialRecord = await credentialService.processRequest(messageContext) + + // then + expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, { + threadId: 'somethreadid', + connectionId: connection.id, + }) + expect(returnedCredentialRecord.state).toEqual(CredentialState.RequestReceived) + }) + + const validState = CredentialState.OfferSent + const invalidCredentialStates = Object.values(CredentialState).filter((state) => state !== validState) + test(`throws an error when state transition is invalid`, async () => { + await Promise.all( + invalidCredentialStates.map(async (state) => { + mockFunction(credentialRepository.getSingleByQuery).mockReturnValue( + Promise.resolve(mockCredentialRecord({ state })) + ) + await expect(credentialService.processRequest(messageContext)).rejects.toThrowError( + `Credential record is in invalid state ${state}. Valid states are: ${validState}.` + ) + }) + ) + }) + }) + + describe('createCredential', () => { + const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249746' + let credential: CredentialExchangeRecord + + beforeEach(() => { + initMessages() + credential = mockCredentialRecord({ + state: CredentialState.RequestReceived, + threadId, + connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', + }) + }) + + test(`updates state to ${CredentialState.CredentialIssued}`, async () => { + const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') + // when + + const acceptRequestOptions: AcceptRequestOptions = { + credentialRecordId: credential.id, + comment: 'credential response comment', + } + await credentialService.createCredential(credential, acceptRequestOptions) + + // then + expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) + const [[updatedCredentialRecord]] = repositoryUpdateSpy.mock.calls + expect(updatedCredentialRecord).toMatchObject({ + state: CredentialState.CredentialIssued, + }) + }) + + test(`emits stateChange event from ${CredentialState.RequestReceived} to ${CredentialState.CredentialIssued}`, async () => { + const eventListenerMock = jest.fn() + + // given + mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credential)) + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) + + // when + const acceptRequestOptions: AcceptRequestOptions = { + credentialRecordId: credential.id, + comment: 'credential response comment', + } + await credentialService.createCredential(credential, acceptRequestOptions) + + // then + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'CredentialStateChanged', + payload: { + previousState: CredentialState.RequestReceived, + credentialRecord: expect.objectContaining({ + state: CredentialState.CredentialIssued, + }), + }, + }) + }) + + test('returns credential response message base on credential request message', async () => { + // given + mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credential)) + const comment = 'credential response comment' + + // when + const options: AcceptRequestOptions = { + comment: 'credential response comment', + credentialRecordId: credential.id, + } + const { message: credentialResponse } = await credentialService.createCredential(credential, options) + + const v2CredentialResponse = credentialResponse as V2IssueCredentialMessage + // then + expect(credentialResponse.toJSON()).toMatchObject({ + '@id': expect.any(String), + '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', + '~thread': { + thid: credential.threadId, + }, + comment, + 'credentials~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + '~please_ack': expect.any(Object), + }) + + // Value of `cred` should be as same as in the credential response message. + const [cred] = await indyIssuerService.createCredential({ + credentialOffer: credOffer, + credentialRequest: credReq, + credentialValues: {}, + }) + const [responseAttachment] = v2CredentialResponse.messageAttachment + expect(responseAttachment.getDataAsJson()).toEqual(cred) + }) + }) + + describe('processCredential', () => { + let credential: CredentialExchangeRecord + let messageContext: InboundMessageContext + beforeEach(() => { + credential = mockCredentialRecord({ + state: CredentialState.RequestSent, + metadata: { indyRequest: { cred_req: 'meta-data' } }, + }) + + const props: V2IssueCredentialMessageProps = { + comment: 'abcd', + credentialsAttach: [credentialAttachment], + formats: [], + } + + const credentialResponse = new V2IssueCredentialMessage(props) + credentialResponse.setThread({ threadId: 'somethreadid' }) + messageContext = new InboundMessageContext(credentialResponse, { + connection, + }) + initMessages() + }) + + test('finds credential record by thread ID and saves credential attachment into the wallet', async () => { + // given + mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) + // when + const record = await credentialService.processCredential(messageContext) + + expect(record.credentialAttributes?.length).toBe(2) + }) + }) + + describe('createAck', () => { + const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249746' + let credential: CredentialExchangeRecord + + beforeEach(() => { + credential = mockCredentialRecord({ + state: CredentialState.CredentialReceived, + threadId, + connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', + }) + }) + + test(`updates state to ${CredentialState.Done}`, async () => { + // given + const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') + + // when + await credentialService.createAck(credential) + + // then + expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) + const [[updatedCredentialRecord]] = repositoryUpdateSpy.mock.calls + expect(updatedCredentialRecord).toMatchObject({ + state: CredentialState.Done, + }) + }) + + test(`emits stateChange event from ${CredentialState.CredentialReceived} to ${CredentialState.Done}`, async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) + + // when + await credentialService.createAck(credential) + + // then + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'CredentialStateChanged', + payload: { + previousState: CredentialState.CredentialReceived, + credentialRecord: expect.objectContaining({ + state: CredentialState.Done, + }), + }, + }) + }) + + test('returns credential response message base on credential request message', async () => { + // given + mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credential)) + + // when + const { message: ackMessage } = await credentialService.createAck(credential) + + // then + expect(ackMessage.toJSON()).toMatchObject({ + '@id': expect.any(String), + '@type': 'https://didcomm.org/issue-credential/2.0/ack', + '~thread': { + thid: 'fd9c5ddb-ec11-4acd-bc32-540736249746', + }, + }) + }) + + const validState = CredentialState.CredentialReceived + const invalidCredentialStates = Object.values(CredentialState).filter((state) => state !== validState) + test(`throws an error when state transition is invalid`, async () => { + await Promise.all( + invalidCredentialStates.map(async (state) => { + await expect( + credentialService.createAck( + mockCredentialRecord({ state, threadId, connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190' }) + ) + ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) + }) + ) + }) + }) + + describe('processAck', () => { + let credential: CredentialExchangeRecord + let messageContext: InboundMessageContext + beforeEach(() => { + credential = mockCredentialRecord({ + state: CredentialState.CredentialIssued, + }) + + const credentialRequest = new V2CredentialAckMessage({ + status: AckStatus.OK, + threadId: 'somethreadid', + }) + messageContext = new InboundMessageContext(credentialRequest, { + connection, + }) + }) + + test(`updates state to ${CredentialState.Done} and returns credential record`, async () => { + const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') + + initMessages() + // given + mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) + + // when + const returnedCredentialRecord = await credentialService.processAck(messageContext) + + // then + const expectedCredentialRecord = { + state: CredentialState.Done, + } + expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, { + threadId: 'somethreadid', + connectionId: connection.id, + }) + expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) + const [[updatedCredentialRecord]] = repositoryUpdateSpy.mock.calls + expect(updatedCredentialRecord).toMatchObject(expectedCredentialRecord) + expect(returnedCredentialRecord).toMatchObject(expectedCredentialRecord) + }) + }) + + describe('createProblemReport', () => { + const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249746' + let credential: CredentialExchangeRecord + + beforeEach(() => { + credential = mockCredentialRecord({ + state: CredentialState.OfferReceived, + threadId, + connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', + }) + }) + + test('returns problem report message base once get error', async () => { + // given + mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credential)) + + // when + const credentialProblemReportMessage = new V2CredentialProblemReportMessage({ + description: { + en: 'Indy error', + code: CredentialProblemReportReason.IssuanceAbandoned, + }, + }) + + credentialProblemReportMessage.setThread({ threadId }) + // then + expect(credentialProblemReportMessage.toJSON()).toMatchObject({ + '@id': expect.any(String), + '@type': 'https://didcomm.org/issue-credential/2.0/problem-report', + '~thread': { + thid: 'fd9c5ddb-ec11-4acd-bc32-540736249746', + }, + }) + }) + }) + + describe('processProblemReport', () => { + let credential: CredentialExchangeRecord + let messageContext: InboundMessageContext + + beforeEach(() => { + credential = mockCredentialRecord({ + state: CredentialState.OfferReceived, + }) + + const credentialProblemReportMessage = new V2CredentialProblemReportMessage({ + description: { + en: 'Indy error', + code: CredentialProblemReportReason.IssuanceAbandoned, + }, + }) + credentialProblemReportMessage.setThread({ threadId: 'somethreadid' }) + messageContext = new InboundMessageContext(credentialProblemReportMessage, { + connection, + }) + }) + + test(`updates problem report error message and returns credential record`, async () => { + const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') + + // given + mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) + + // when + const returnedCredentialRecord = await credentialService.processProblemReport(messageContext) + + // then + const expectedCredentialRecord = { + errorMessage: 'issuance-abandoned: Indy error', + } + expect(credentialRepository.getSingleByQuery).toHaveBeenNthCalledWith(1, { + threadId: 'somethreadid', + connectionId: connection.id, + }) + expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) + const [[updatedCredentialRecord]] = repositoryUpdateSpy.mock.calls + expect(updatedCredentialRecord).toMatchObject(expectedCredentialRecord) + expect(returnedCredentialRecord).toMatchObject(expectedCredentialRecord) + }) + }) + + describe('repository methods', () => { + it('getById should return value from credentialRepository.getById', async () => { + const expected = mockCredentialRecord() + mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(expected)) + const result = await credentialService.getById(expected.id) + expect(credentialRepository.getById).toBeCalledWith(expected.id) + + expect(result).toBe(expected) + }) + + it('getById should return value from credentialRepository.getSingleByQuery', async () => { + const expected = mockCredentialRecord() + mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(expected)) + const result = await credentialService.getByThreadAndConnectionId('threadId', 'connectionId') + expect(credentialRepository.getSingleByQuery).toBeCalledWith({ + threadId: 'threadId', + connectionId: 'connectionId', + }) + + expect(result).toBe(expected) + }) + + it('findById should return value from credentialRepository.findById', async () => { + const expected = mockCredentialRecord() + mockFunction(credentialRepository.findById).mockReturnValue(Promise.resolve(expected)) + const result = await credentialService.findById(expected.id) + expect(credentialRepository.findById).toBeCalledWith(expected.id) + + expect(result).toBe(expected) + }) + + it('getAll should return value from credentialRepository.getAll', async () => { + const expected = [mockCredentialRecord(), mockCredentialRecord()] + + mockFunction(credentialRepository.getAll).mockReturnValue(Promise.resolve(expected)) + const result = await credentialService.getAll() + expect(credentialRepository.getAll).toBeCalledWith() + + expect(result).toEqual(expect.arrayContaining(expected)) + }) + }) + + describe('deleteCredential', () => { + it('should call delete from repository', async () => { + const credential = mockCredentialRecord() + mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credential)) + + const repositoryDeleteSpy = jest.spyOn(credentialRepository, 'delete') + await credentialService.deleteById(credential.id) + expect(repositoryDeleteSpy).toHaveBeenNthCalledWith(1, credential) + }) + + it('deleteAssociatedCredential parameter should call deleteCredential in indyHolderService with credentialId', async () => { + const storeCredentialMock = indyHolderService.deleteCredential as jest.Mock, [string]> + + const credential = mockCredentialRecord() + mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credential)) + + await credentialService.deleteById(credential.id, { + deleteAssociatedCredentials: true, + }) + expect(storeCredentialMock).toHaveBeenNthCalledWith(1, credential.credentials[0].credentialRecordId) + }) + }) + + describe('declineOffer', () => { + const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249754' + let credential: CredentialExchangeRecord + + beforeEach(() => { + credential = mockCredentialRecord({ + state: CredentialState.OfferReceived, + tags: { threadId }, + }) + }) + + test(`updates state to ${CredentialState.Declined}`, async () => { + // given + const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') + + // when + await credentialService.declineOffer(credential) + + // then + const expectedCredentialState = { + state: CredentialState.Declined, + } + expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) + expect(repositoryUpdateSpy).toHaveBeenNthCalledWith(1, expect.objectContaining(expectedCredentialState)) + }) + + test(`emits stateChange event from ${CredentialState.OfferReceived} to ${CredentialState.Declined}`, async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) + + // given + mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) + + // when + await credentialService.declineOffer(credential) + + // then + expect(eventListenerMock).toHaveBeenCalledTimes(1) + const [[event]] = eventListenerMock.mock.calls + expect(event).toMatchObject({ + type: 'CredentialStateChanged', + payload: { + previousState: CredentialState.OfferReceived, + credentialRecord: expect.objectContaining({ + state: CredentialState.Declined, + }), + }, + }) + }) + + const validState = CredentialState.OfferReceived + const invalidCredentialStates = Object.values(CredentialState).filter((state) => state !== validState) + test(`throws an error when state transition is invalid`, async () => { + await Promise.all( + invalidCredentialStates.map(async (state) => { + await expect( + credentialService.declineOffer(mockCredentialRecord({ state, tags: { threadId } })) + ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) + }) + ) + }) + }) +}) diff --git a/packages/core/src/modules/credentials/__tests__/V2CredentialService.offer.test.ts b/packages/core/src/modules/credentials/__tests__/V2CredentialService.offer.test.ts new file mode 100644 index 0000000000..da594c4a5a --- /dev/null +++ b/packages/core/src/modules/credentials/__tests__/V2CredentialService.offer.test.ts @@ -0,0 +1,353 @@ +import type { AgentConfig } from '../../../agent/AgentConfig' +import type { ConnectionService } from '../../connections/services/ConnectionService' +import type { CredentialStateChangedEvent } from '../CredentialEvents' +import type { OfferCredentialOptions } from '../CredentialsModuleOptions' +import type { V2OfferCredentialMessageOptions } from '../protocol/v2/messages/V2OfferCredentialMessage' + +import { getAgentConfig, getBaseConfig, getMockConnection, mockFunction } from '../../../../tests/helpers' +import { Agent } from '../../../agent/Agent' +import { Dispatcher } from '../../../agent/Dispatcher' +import { EventEmitter } from '../../../agent/EventEmitter' +import { MessageSender } from '../../../agent/MessageSender' +import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' +import { DidCommMessageRepository } from '../../../storage' +import { ConnectionState } from '../../connections' +import { IndyHolderService } from '../../indy/services/IndyHolderService' +import { IndyIssuerService } from '../../indy/services/IndyIssuerService' +import { IndyLedgerService } from '../../ledger/services' +import { MediationRecipientService } from '../../routing/services/MediationRecipientService' +import { CredentialEventTypes } from '../CredentialEvents' +import { CredentialProtocolVersion } from '../CredentialProtocolVersion' +import { CredentialState } from '../CredentialState' +import { IndyCredentialFormatService } from '../formats/indy/IndyCredentialFormatService' +import { V1CredentialPreview } from '../protocol/v1/V1CredentialPreview' +import { INDY_CREDENTIAL_OFFER_ATTACHMENT_ID } from '../protocol/v1/messages' +import { V2CredentialPreview } from '../protocol/v2/V2CredentialPreview' +import { V2CredentialService } from '../protocol/v2/V2CredentialService' +import { V2OfferCredentialMessage } from '../protocol/v2/messages/V2OfferCredentialMessage' +import { CredentialExchangeRecord } from '../repository/CredentialExchangeRecord' +import { CredentialRepository } from '../repository/CredentialRepository' +import { RevocationService } from '../services' + +import { credDef, schema } from './fixtures' + +// Mock classes +jest.mock('../repository/CredentialRepository') +jest.mock('../../../../src/storage/didcomm/DidCommMessageRepository') +jest.mock('../../../modules/ledger/services/IndyLedgerService') +jest.mock('../../indy/services/IndyHolderService') +jest.mock('../../indy/services/IndyIssuerService') +jest.mock('../../routing/services/MediationRecipientService') + +// Mock typed object +const CredentialRepositoryMock = CredentialRepository as jest.Mock +const IndyLedgerServiceMock = IndyLedgerService as jest.Mock +const IndyHolderServiceMock = IndyHolderService as jest.Mock +const IndyIssuerServiceMock = IndyIssuerService as jest.Mock +const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock +const MessageSenderMock = MessageSender as jest.Mock +const MediationRecipientServiceMock = MediationRecipientService as jest.Mock + +const connection = getMockConnection({ + id: '123', + state: ConnectionState.Complete, +}) + +const credentialPreview = V1CredentialPreview.fromRecord({ + name: 'John', + age: '99', +}) + +const offerAttachment = new Attachment({ + id: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + mimeType: 'application/json', + data: new AttachmentData({ + base64: + 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0', + }), +}) + +const { config, agentDependencies: dependencies } = getBaseConfig('Agent Class Test V2 Offer') + +describe('CredentialService', () => { + let agent: Agent + let credentialRepository: CredentialRepository + let indyLedgerService: IndyLedgerService + let indyIssuerService: IndyIssuerService + let indyHolderService: IndyHolderService + let eventEmitter: EventEmitter + let didCommMessageRepository: DidCommMessageRepository + let mediationRecipientService: MediationRecipientService + let messageSender: MessageSender + let agentConfig: AgentConfig + + let dispatcher: Dispatcher + let credentialService: V2CredentialService + let revocationService: RevocationService + + beforeEach(async () => { + credentialRepository = new CredentialRepositoryMock() + indyIssuerService = new IndyIssuerServiceMock() + didCommMessageRepository = new DidCommMessageRepositoryMock() + messageSender = new MessageSenderMock() + mediationRecipientService = new MediationRecipientServiceMock() + indyHolderService = new IndyHolderServiceMock() + indyLedgerService = new IndyLedgerServiceMock() + mockFunction(indyLedgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) + agentConfig = getAgentConfig('CredentialServiceTest') + eventEmitter = new EventEmitter(agentConfig) + + dispatcher = new Dispatcher(messageSender, eventEmitter, agentConfig) + revocationService = new RevocationService(credentialRepository, eventEmitter, agentConfig) + + credentialService = new V2CredentialService( + { + getById: () => Promise.resolve(connection), + assertConnectionOrServiceDecorator: () => true, + } as unknown as ConnectionService, + credentialRepository, + eventEmitter, + dispatcher, + agentConfig, + mediationRecipientService, + didCommMessageRepository, + new IndyCredentialFormatService( + credentialRepository, + eventEmitter, + indyIssuerService, + indyLedgerService, + indyHolderService, + agentConfig + ), + revocationService + ) + mockFunction(indyLedgerService.getSchema).mockReturnValue(Promise.resolve(schema)) + }) + + describe('createCredentialOffer', () => { + let offerOptions: OfferCredentialOptions + + beforeEach(async () => { + offerOptions = { + comment: 'some comment', + connectionId: connection.id, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', + }, + }, + protocolVersion: CredentialProtocolVersion.V1, + } + }) + + test(`creates credential record in ${CredentialState.OfferSent} state with offer, thread ID`, async () => { + // given + // agent = new Agent(config, dependencies) + // await agent.initialize() + // expect(agent.isInitialized).toBe(true) + const repositorySaveSpy = jest.spyOn(credentialRepository, 'save') + + await credentialService.createOffer(offerOptions) + + // then + expect(repositorySaveSpy).toHaveBeenCalledTimes(1) + + const [[createdCredentialRecord]] = repositorySaveSpy.mock.calls + expect(createdCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: createdCredentialRecord.threadId, + connectionId: connection.id, + state: CredentialState.OfferSent, + }) + }) + + test(`emits stateChange event with a new credential in ${CredentialState.OfferSent} state`, async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) + + await credentialService.createOffer(offerOptions) + + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'CredentialStateChanged', + payload: { + previousState: null, + credentialRecord: expect.objectContaining({ + state: CredentialState.OfferSent, + }), + }, + }) + }) + + test('returns credential offer message', async () => { + const { message: credentialOffer } = await credentialService.createOffer(offerOptions) + + expect(credentialOffer.toJSON()).toMatchObject({ + '@id': expect.any(String), + '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', + comment: 'some comment', + credential_preview: { + '@type': 'https://didcomm.org/issue-credential/2.0/credential-preview', + attributes: [ + { + name: 'name', + 'mime-type': 'text/plain', + value: 'John', + }, + { + name: 'age', + 'mime-type': 'text/plain', + value: '99', + }, + ], + }, + 'offers~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: { + base64: expect.any(String), + }, + }, + ], + }) + }) + + test('throw error if credential preview attributes do not match with schema attributes', async () => { + const badCredentialPreview = V2CredentialPreview.fromRecord({ + test: 'credential', + error: 'yes', + }) + + offerOptions = { + ...offerOptions, + credentialFormats: { + indy: { + attributes: badCredentialPreview.attributes, + credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', + }, + }, + } + expect(credentialService.createOffer(offerOptions)).rejects.toThrowError( + `The credential preview attributes do not match the schema attributes (difference is: test,error,name,age, needs: name,age)` + ) + const credentialPreviewWithExtra = V2CredentialPreview.fromRecord({ + test: 'credential', + error: 'yes', + name: 'John', + age: '99', + }) + + offerOptions = { + ...offerOptions, + credentialFormats: { + indy: { + attributes: credentialPreviewWithExtra.attributes, + credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', + }, + }, + } + expect(credentialService.createOffer(offerOptions)).rejects.toThrowError( + `The credential preview attributes do not match the schema attributes (difference is: test,error, needs: name,age)` + ) + }) + }) + describe('processCredentialOffer', () => { + let messageContext: InboundMessageContext + let credentialOfferMessage: V2OfferCredentialMessage + + beforeEach(async () => { + const offerOptions: V2OfferCredentialMessageOptions = { + id: '', + formats: [ + { + attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + format: 'hlindy/cred-abstract@v2.0', + }, + ], + comment: 'some comment', + credentialPreview: credentialPreview, + offerAttachments: [offerAttachment], + replacementId: undefined, + } + credentialOfferMessage = new V2OfferCredentialMessage(offerOptions) + messageContext = new InboundMessageContext(credentialOfferMessage, { + connection, + }) + messageContext.connection = connection + }) + + test(`creates and return credential record in ${CredentialState.OfferReceived} state with offer, thread ID`, async () => { + const repositorySaveSpy = jest.spyOn(credentialRepository, 'save') + agent = new Agent(config, dependencies) + await agent.initialize() + expect(agent.isInitialized).toBe(true) + const agentConfig = getAgentConfig('CredentialServiceTest') + eventEmitter = new EventEmitter(agentConfig) + + const dispatcher = agent.injectionContainer.resolve(Dispatcher) + const mediationRecipientService = agent.injectionContainer.resolve(MediationRecipientService) + revocationService = new RevocationService(credentialRepository, eventEmitter, agentConfig) + + credentialService = new V2CredentialService( + { + getById: () => Promise.resolve(connection), + assertConnectionOrServiceDecorator: () => true, + } as unknown as ConnectionService, + credentialRepository, + eventEmitter, + dispatcher, + agentConfig, + mediationRecipientService, + didCommMessageRepository, + new IndyCredentialFormatService( + credentialRepository, + eventEmitter, + indyIssuerService, + indyLedgerService, + indyHolderService, + agentConfig + ), + revocationService + ) + // when + const returnedCredentialRecord = await credentialService.processOffer(messageContext) + + // then + const expectedCredentialRecord = { + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: credentialOfferMessage.id, + connectionId: connection.id, + state: CredentialState.OfferReceived, + } + expect(repositorySaveSpy).toHaveBeenCalledTimes(1) + const [[createdCredentialRecord]] = repositorySaveSpy.mock.calls + expect(createdCredentialRecord).toMatchObject(expectedCredentialRecord) + expect(returnedCredentialRecord).toMatchObject(expectedCredentialRecord) + }) + + test(`emits stateChange event with ${CredentialState.OfferReceived}`, async () => { + const eventListenerMock = jest.fn() + eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) + + // when + await credentialService.processOffer(messageContext) + + // then + expect(eventListenerMock).toHaveBeenCalledWith({ + type: 'CredentialStateChanged', + payload: { + previousState: null, + credentialRecord: expect.objectContaining({ + state: CredentialState.OfferReceived, + }), + }, + }) + }) + }) +}) diff --git a/packages/core/src/modules/credentials/errors/CredentialProblemReportError.ts b/packages/core/src/modules/credentials/errors/CredentialProblemReportError.ts index 4b10c3fa2c..ffbe633004 100644 --- a/packages/core/src/modules/credentials/errors/CredentialProblemReportError.ts +++ b/packages/core/src/modules/credentials/errors/CredentialProblemReportError.ts @@ -1,7 +1,7 @@ import type { ProblemReportErrorOptions } from '../../problem-reports' import type { CredentialProblemReportReason } from './CredentialProblemReportReason' -import { CredentialProblemReportMessage } from '../messages' +import { V1CredentialProblemReportMessage } from '../protocol/v1/messages' import { ProblemReportError } from './../../problem-reports/errors/ProblemReportError' @@ -9,11 +9,11 @@ interface CredentialProblemReportErrorOptions extends ProblemReportErrorOptions problemCode: CredentialProblemReportReason } export class CredentialProblemReportError extends ProblemReportError { - public problemReport: CredentialProblemReportMessage + public problemReport: V1CredentialProblemReportMessage public constructor(message: string, { problemCode }: CredentialProblemReportErrorOptions) { super(message, { problemCode }) - this.problemReport = new CredentialProblemReportMessage({ + this.problemReport = new V1CredentialProblemReportMessage({ description: { en: message, code: problemCode, diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts new file mode 100644 index 0000000000..23fbcc1385 --- /dev/null +++ b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts @@ -0,0 +1,102 @@ +import type { EventEmitter } from '../../../agent/EventEmitter' +import type { + DeleteCredentialOptions, + ServiceAcceptCredentialOptions, + ServiceAcceptProposalOptions, + ServiceOfferCredentialOptions, +} from '../CredentialServiceOptions' +import type { + AcceptRequestOptions, + ProposeCredentialOptions, + RequestCredentialOptions, +} from '../CredentialsModuleOptions' +import type { CredentialExchangeRecord, CredentialRepository } from '../repository' +import type { + FormatServiceCredentialAttachmentFormats, + CredentialFormatSpec, + HandlerAutoAcceptOptions, + FormatServiceOfferAttachmentFormats, + FormatServiceProposeAttachmentFormats, +} from './models/CredentialFormatServiceOptions' + +import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' +import { JsonEncoder } from '../../../utils/JsonEncoder' + +export abstract class CredentialFormatService { + protected credentialRepository: CredentialRepository + protected eventEmitter: EventEmitter + + public constructor(credentialRepository: CredentialRepository, eventEmitter: EventEmitter) { + this.credentialRepository = credentialRepository + this.eventEmitter = eventEmitter + } + + abstract createProposal(options: ProposeCredentialOptions): FormatServiceProposeAttachmentFormats + + abstract processProposal( + options: ServiceAcceptProposalOptions, + credentialRecord: CredentialExchangeRecord + ): Promise + + abstract createOffer(options: ServiceOfferCredentialOptions): Promise + + abstract processOffer(attachment: Attachment, credentialRecord: CredentialExchangeRecord): Promise + + abstract createRequest( + options: RequestCredentialOptions, + credentialRecord: CredentialExchangeRecord, + holderDid?: string + ): Promise + + abstract processRequest(options: RequestCredentialOptions, credentialRecord: CredentialExchangeRecord): void + + abstract createCredential( + options: AcceptRequestOptions, + credentialRecord: CredentialExchangeRecord, + requestAttachment: Attachment, + offerAttachment?: Attachment + ): Promise + + abstract processCredential( + options: ServiceAcceptCredentialOptions, + credentialRecord: CredentialExchangeRecord + ): Promise + + abstract shouldAutoRespondToProposal(options: HandlerAutoAcceptOptions): boolean + abstract shouldAutoRespondToRequest(options: HandlerAutoAcceptOptions): boolean + abstract shouldAutoRespondToCredential(options: HandlerAutoAcceptOptions): boolean + + abstract deleteCredentialById( + credentialRecord: CredentialExchangeRecord, + options: DeleteCredentialOptions + ): Promise + + /** + * + * Returns an object of type {@link Attachment} for use in credential exchange messages. + * It looks up the correct format identifier and encodes the data as a base64 attachment. + * + * @param data The data to include in the attach object + * @param id the attach id from the formats component of the message + * @returns attachment to the credential proposal + */ + public getFormatData(data: unknown, id: string): Attachment { + const attachment: Attachment = new Attachment({ + id, + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(data), + }), + }) + return attachment + } + + /** + * Gets the attachment object for a given attachId. We need to get out the correct attachId for + * indy and then find the corresponding attachment (if there is one) + * @param formats the formats object containing the attachid + * @param messageAttachment the attachment containing the payload + * @returns The Attachment if found or undefined + */ + abstract getAttachment(formats: CredentialFormatSpec[], messageAttachment: Attachment[]): Attachment | undefined +} diff --git a/packages/core/src/modules/credentials/formats/index.ts b/packages/core/src/modules/credentials/formats/index.ts new file mode 100644 index 0000000000..c33b5cce20 --- /dev/null +++ b/packages/core/src/modules/credentials/formats/index.ts @@ -0,0 +1,2 @@ +export * from './CredentialFormatService' +export * from './indy/IndyCredentialFormatService' diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts new file mode 100644 index 0000000000..f5c77071d1 --- /dev/null +++ b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts @@ -0,0 +1,591 @@ +import type { Attachment } from '../../../../decorators/attachment/Attachment' +import type { Logger } from '../../../../logger' +import type { + NegotiateProposalOptions, + OfferCredentialOptions, + ProposeCredentialOptions, + RequestCredentialOptions, +} from '../../CredentialsModuleOptions' +import type { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttributes' +import type { + DeleteCredentialOptions, + ServiceAcceptCredentialOptions, + ServiceAcceptOfferOptions as ServiceOfferOptions, + ServiceAcceptProposalOptions, + ServiceAcceptRequestOptions, + ServiceOfferCredentialOptions, + ServiceRequestCredentialOptions, +} from '../../protocol' +import type { V1CredentialPreview } from '../../protocol/v1/V1CredentialPreview' +import type { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' +import type { CredPropose } from '../models/CredPropose' +import type { + FormatServiceCredentialAttachmentFormats, + CredentialFormatSpec, + HandlerAutoAcceptOptions, + FormatServiceOfferAttachmentFormats, + FormatServiceProposeAttachmentFormats, + RevocationRegistry, +} from '../models/CredentialFormatServiceOptions' +import type { Cred, CredDef, CredOffer, CredReq, CredReqMetadata } from 'indy-sdk' + +import { Lifecycle, scoped } from 'tsyringe' + +import { AgentConfig } from '../../../../agent/AgentConfig' +import { EventEmitter } from '../../../../agent/EventEmitter' +import { AriesFrameworkError } from '../../../../error' +import { MessageValidator } from '../../../../utils/MessageValidator' +import { uuid } from '../../../../utils/uuid' +import { IndyHolderService, IndyIssuerService } from '../../../indy' +import { IndyLedgerService } from '../../../ledger' +import { AutoAcceptCredential } from '../../CredentialAutoAcceptType' +import { CredentialResponseCoordinator } from '../../CredentialResponseCoordinator' +import { CredentialUtils } from '../../CredentialUtils' +import { CredentialFormatType } from '../../CredentialsModuleOptions' +import { CredentialProblemReportError, CredentialProblemReportReason } from '../../errors' +import { V2CredentialPreview } from '../../protocol/v2/V2CredentialPreview' +import { CredentialMetadataKeys } from '../../repository/CredentialMetadataTypes' +import { CredentialRepository } from '../../repository/CredentialRepository' +import { CredentialFormatService } from '../CredentialFormatService' + +@scoped(Lifecycle.ContainerScoped) +export class IndyCredentialFormatService extends CredentialFormatService { + private indyIssuerService: IndyIssuerService + private indyLedgerService: IndyLedgerService + private indyHolderService: IndyHolderService + protected credentialRepository: CredentialRepository // protected as in base class + private logger: Logger + + public constructor( + credentialRepository: CredentialRepository, + eventEmitter: EventEmitter, + indyIssuerService: IndyIssuerService, + indyLedgerService: IndyLedgerService, + indyHolderService: IndyHolderService, + agentConfig: AgentConfig + ) { + super(credentialRepository, eventEmitter) + this.credentialRepository = credentialRepository + this.indyIssuerService = indyIssuerService + this.indyLedgerService = indyLedgerService + this.indyHolderService = indyHolderService + this.logger = agentConfig.logger + } + + /** + * Create a {@link AttachmentFormats} object dependent on the message type. + * + * @param options The object containing all the options for the proposed credential + * @param messageType the type of message which can be Indy, JsonLd etc eg "CRED_20_PROPOSAL" + * @returns object containing associated attachment, formats and filtersAttach elements + * + */ + public createProposal(options: ProposeCredentialOptions): FormatServiceProposeAttachmentFormats { + const formats: CredentialFormatSpec = { + attachId: this.generateId(), + format: 'hlindy/cred-filter@v2.0', + } + if (!options.credentialFormats.indy?.payload) { + throw new AriesFrameworkError('Missing payload in createProposal') + } + + const attachment: Attachment = this.getFormatData(options.credentialFormats.indy?.payload, formats.attachId) + const { previewWithAttachments } = this.getCredentialLinkedAttachments(options) + + return { format: formats, attachment, preview: previewWithAttachments } + } + + public async processProposal( + options: ServiceAcceptProposalOptions, + credentialRecord: CredentialExchangeRecord + ): Promise { + const credPropose = options.proposalAttachment?.getDataAsJson() + if (!credPropose) { + throw new AriesFrameworkError('Missing indy credential proposal data payload') + } + await MessageValidator.validate(credPropose) + + if (credPropose.credentialDefinitionId) { + options.credentialFormats = { + indy: { + credentialDefinitionId: credPropose?.credentialDefinitionId, + attributes: [], + }, + } + } + + credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { + schemaId: credPropose.schemaId, + credentialDefinitionId: credPropose.credentialDefinitionId, + }) + } + + /** + * Create a {@link AttachmentFormats} object dependent on the message type. + * + * @param options The object containing all the options for the credential offer + * @param messageType the type of message which can be Indy, JsonLd etc eg "CRED_20_OFFER" + * @returns object containing associated attachment, formats and offersAttach elements + * + */ + public async createOffer(options: ServiceOfferCredentialOptions): Promise { + const formats: CredentialFormatSpec = { + attachId: this.generateId(), + format: 'hlindy/cred-abstract@v2.0', + } + const offer = await this.createCredentialOffer(options) + + let preview: V2CredentialPreview | undefined + + if (options?.credentialFormats.indy?.attributes) { + preview = new V2CredentialPreview({ + attributes: options?.credentialFormats.indy?.attributes, + }) + } + + // if the proposal has an attachment Id use that, otherwise the generated id of the formats object + const attachmentId = options.attachId ? options.attachId : formats.attachId + + const offersAttach: Attachment = this.getFormatData(offer, attachmentId) + + // with credential preview now being a required field (as per spec) + // attributes could be empty + if (preview && preview.attributes.length > 0) { + await this.checkPreviewAttributesMatchSchemaAttributes(offersAttach, preview) + } + + return { format: formats, attachment: offersAttach, preview } + } + public async processOffer(attachment: Attachment, credentialRecord: CredentialExchangeRecord) { + if (!attachment) { + throw new AriesFrameworkError('Missing offer attachment in processOffer') + } + this.logger.debug(`Save metadata for credential record ${credentialRecord.id}`) + + const credOffer: CredOffer = attachment.getDataAsJson() + + credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { + schemaId: credOffer.schema_id, + credentialDefinitionId: credOffer.cred_def_id, + }) + } + + /** + * Create a {@link AttachmentFormats} object dependent on the message type. + * + * @param requestOptions The object containing all the options for the credential request + * @param credentialRecord the credential record containing the offer from which this request + * is derived + * @returns object containing associated attachment, formats and requestAttach elements + * + */ + public async createRequest( + options: ServiceRequestCredentialOptions, + credentialRecord: CredentialExchangeRecord, + holderDid: string + ): Promise { + if (!options.offerAttachment) { + throw new AriesFrameworkError( + `Missing attachment from offer message, credential record id = ${credentialRecord.id}` + ) + } + const offer = options.offerAttachment.getDataAsJson() + const credDef = await this.getCredentialDefinition(offer) + + const { credReq, credReqMetadata } = await this.createIndyCredentialRequest(offer, credDef, holderDid) + credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, credReqMetadata) + + const formats: CredentialFormatSpec = { + attachId: this.generateId(), + format: 'hlindy/cred-req@v2.0', + } + + const attachmentId = options.attachId ?? formats.attachId + const requestAttach: Attachment = this.getFormatData(credReq, attachmentId) + return { format: formats, attachment: requestAttach } + } + + /** + * Not implemented; there for future versions + */ + public async processRequest( + /* eslint-disable @typescript-eslint/no-unused-vars */ + _options: RequestCredentialOptions, + _credentialRecord: CredentialExchangeRecord + /* eslint-enable @typescript-eslint/no-unused-vars */ + ): Promise { + // not needed for Indy + } + + private async getCredentialDefinition(credOffer: CredOffer): Promise { + const indyCredDef = await this.indyLedgerService.getCredentialDefinition(credOffer.cred_def_id) + return indyCredDef + } + + /** + * Get linked attachments for indy format from a proposal message. This allows attachments + * to be copied across to old style credential records + * + * @param options ProposeCredentialOptions object containing (optionally) the linked attachments + * @return array of linked attachments or undefined if none present + */ + private getCredentialLinkedAttachments(options: ProposeCredentialOptions): { + attachments: Attachment[] | undefined + previewWithAttachments: V2CredentialPreview + } { + // Add the linked attachments to the credentialProposal + if (!options.credentialFormats.indy?.payload) { + throw new AriesFrameworkError('Missing payload in getCredentialLinkedAttachments') + } + + let attachments: Attachment[] | undefined + let previewWithAttachments: V2CredentialPreview | undefined + if (options.credentialFormats.indy.attributes) { + previewWithAttachments = new V2CredentialPreview({ + attributes: options.credentialFormats.indy.attributes, + }) + } + + if (options.credentialFormats.indy && options.credentialFormats.indy.linkedAttachments) { + // there are linked attachments so transform into the attribute field of the CredentialPreview object for + // this proposal + if (options.credentialFormats.indy.attributes) { + previewWithAttachments = CredentialUtils.createAndLinkAttachmentsToPreview( + options.credentialFormats.indy.linkedAttachments, + new V2CredentialPreview({ + attributes: options.credentialFormats.indy.attributes, + }) + ) + } + attachments = options.credentialFormats.indy.linkedAttachments.map( + (linkedAttachment) => linkedAttachment.attachment + ) + } + if (!previewWithAttachments) { + throw new AriesFrameworkError('No previewWithAttachments') + } + return { attachments, previewWithAttachments } + } + + /** + * Gets the attachment object for a given attachId. We need to get out the correct attachId for + * indy and then find the corresponding attachment (if there is one) + * @param formats the formats object containing the attachid + * @param messageAttachment the attachment containing the payload + * @returns The Attachment if found or undefined + */ + + public getAttachment(formats: CredentialFormatSpec[], messageAttachment: Attachment[]): Attachment | undefined { + const formatId = formats.find((f) => f.format.includes('indy')) + const attachment = messageAttachment?.find((attachment) => attachment.id === formatId?.attachId) + return attachment + } + /** + * Create a credential offer for the given credential definition id. + * + * @param credentialDefinitionId The credential definition to create an offer for + * @returns The created credential offer + */ + private async createCredentialOffer( + proposal: ServiceOfferOptions | NegotiateProposalOptions | OfferCredentialOptions + ): Promise { + if (!proposal.credentialFormats?.indy?.credentialDefinitionId) { + throw new AriesFrameworkError('Missing Credential Definition id') + } + const credOffer: CredOffer = await this.indyIssuerService.createCredentialOffer( + proposal.credentialFormats.indy.credentialDefinitionId + ) + return credOffer + } + + /** + * Create a credential offer for the given credential definition id. + * + * @param options RequestCredentialOptions the config options for the credential request + * @throws Error if unable to create the request + * @returns The created credential offer + */ + private async createIndyCredentialRequest( + offer: CredOffer, + credentialDefinition: CredDef, + holderDid: string + ): Promise<{ credReq: CredReq; credReqMetadata: CredReqMetadata }> { + const [credReq, credReqMetadata] = await this.indyHolderService.createCredentialRequest({ + holderDid: holderDid, + credentialOffer: offer, + credentialDefinition, + }) + return { credReq, credReqMetadata } + } + + /** + * Create a {@link AttachmentFormats} object dependent on the message type. + * + * @param requestOptions The object containing all the options for the credential request + * @param credentialRecord the credential record containing the offer from which this request + * is derived + * @returns object containing associated attachment, formats and requestAttach elements + * + */ + public async createCredential( + options: ServiceAcceptRequestOptions, + record: CredentialExchangeRecord, + requestAttachment: Attachment, + offerAttachment?: Attachment + ): Promise { + // Assert credential attributes + const credentialAttributes = record.credentialAttributes + if (!credentialAttributes) { + throw new CredentialProblemReportError( + `Missing required credential attribute values on credential record with id ${record.id}`, + { problemCode: CredentialProblemReportReason.IssuanceAbandoned } + ) + } + + const credOffer = offerAttachment?.getDataAsJson() + const credRequest = requestAttachment?.getDataAsJson() + + if (!credOffer || !credRequest) { + throw new AriesFrameworkError('Missing CredOffer or CredReq in createCredential') + } + if (!this.indyIssuerService) { + throw new AriesFrameworkError('Missing indyIssuerService in createCredential') + } + + const [credential] = await this.indyIssuerService.createCredential({ + credentialOffer: credOffer, + credentialRequest: credRequest, + credentialValues: CredentialUtils.convertAttributesToValues(credentialAttributes), + }) + + const formats: CredentialFormatSpec = { + attachId: this.generateId(), + format: 'hlindy/cred-abstract@v2.0', + } + + const attachmentId = options.attachId ? options.attachId : formats.attachId + const issueAttachment: Attachment = this.getFormatData(credential, attachmentId) + return { format: formats, attachment: issueAttachment } + } + /** + * Processes an incoming credential - retrieve metadata, retrieve payload and store it in the Indy wallet + * @param message the issue credential message + */ + + /** + * Processes an incoming credential - retrieve metadata, retrieve payload and store it in the Indy wallet + * @param options the issue credential message wrapped inside this object + * @param credentialRecord the credential exchange record for this credential + */ + public async processCredential( + options: ServiceAcceptCredentialOptions, + credentialRecord: CredentialExchangeRecord + ): Promise { + const credentialRequestMetadata = credentialRecord.metadata.get(CredentialMetadataKeys.IndyRequest) + + if (!credentialRequestMetadata) { + throw new CredentialProblemReportError( + `Missing required request metadata for credential with id ${credentialRecord.id}`, + { problemCode: CredentialProblemReportReason.IssuanceAbandoned } + ) + } + if (!options.credentialAttachment) { + throw new AriesFrameworkError(`Missing credential for record id ${credentialRecord.id}`) + } + const indyCredential: Cred = options.credentialAttachment.getDataAsJson() + + const credentialDefinition = await this.indyLedgerService.getCredentialDefinition(indyCredential.cred_def_id) + + if (!options.credentialAttachment) { + throw new AriesFrameworkError('Missing credential attachment in processCredential') + } + const revocationRegistry = await this.getRevocationRegistry(options.credentialAttachment) + const credentialId = await this.indyHolderService.storeCredential({ + credentialId: this.generateId(), + credentialRequestMetadata, + credential: indyCredential, + credentialDefinition, + revocationRegistryDefinition: revocationRegistry?.indy?.revocationRegistryDefinition, + }) + credentialRecord.credentials.push({ + credentialRecordType: CredentialFormatType.Indy, + credentialRecordId: credentialId, + }) + } + + /** + * Checks whether it should automatically respond to a proposal. Moved from CredentialResponseCoordinator + * as this contains format-specific logic + * @param credentialRecord The credential record for which we are testing whether or not to auto respond + * @param agentConfig config object for the agent, used to hold auto accept state for the agent + * @returns true if we should auto respond, false otherwise + */ + + public shouldAutoRespondToProposal(handlerOptions: HandlerAutoAcceptOptions): boolean { + const autoAccept = CredentialResponseCoordinator.composeAutoAccept( + handlerOptions.credentialRecord.autoAcceptCredential, + handlerOptions.autoAcceptType + ) + + if (autoAccept === AutoAcceptCredential.ContentApproved) { + return ( + this.areProposalValuesValid(handlerOptions.credentialRecord, handlerOptions.messageAttributes) && + this.areProposalAndOfferDefinitionIdEqual(handlerOptions.proposalAttachment, handlerOptions.offerAttachment) + ) + } + return false + } + + /** + * Checks whether it should automatically respond to a request. Moved from CredentialResponseCoordinator + * as this contains format-specific logic + * @param credentialRecord The credential record for which we are testing whether or not to auto respond + * @param autoAcceptType auto accept type for this credential exchange - normal auto or content approved + * @returns true if we should auto respond, false otherwise + + */ + + public shouldAutoRespondToRequest(options: HandlerAutoAcceptOptions): boolean { + const autoAccept = CredentialResponseCoordinator.composeAutoAccept( + options.credentialRecord.autoAcceptCredential, + options.autoAcceptType + ) + + if (!options.requestAttachment) { + throw new AriesFrameworkError(`Missing Request Attachment for Credential Record ${options.credentialRecord.id}`) + } + if (autoAccept === AutoAcceptCredential.ContentApproved) { + return this.isRequestDefinitionIdValid( + options.requestAttachment, + options.offerAttachment, + options.proposalAttachment + ) + } + return false + } + /** + * Checks whether it should automatically respond to a request. Moved from CredentialResponseCoordinator + * as this contains format-specific logic + * @param credentialRecord The credential record for which we are testing whether or not to auto respond + * @param autoAcceptType auto accept type for this credential exchange - normal auto or content approved + * @returns true if we should auto respond, false otherwise + */ + + public shouldAutoRespondToCredential(options: HandlerAutoAcceptOptions): boolean { + const autoAccept = CredentialResponseCoordinator.composeAutoAccept( + options.credentialRecord.autoAcceptCredential, + options.autoAcceptType + ) + + if (autoAccept === AutoAcceptCredential.ContentApproved) { + if (options.credentialAttachment) { + return this.areCredentialValuesValid(options.credentialRecord, options.credentialAttachment) + } + } + return false + } + private areProposalValuesValid( + credentialRecord: CredentialExchangeRecord, + proposeMessageAttributes?: CredentialPreviewAttribute[] + ) { + const { credentialAttributes } = credentialRecord + + if (proposeMessageAttributes && credentialAttributes) { + const proposeValues = CredentialUtils.convertAttributesToValues(proposeMessageAttributes) + const defaultValues = CredentialUtils.convertAttributesToValues(credentialAttributes) + if (CredentialUtils.checkValuesMatch(proposeValues, defaultValues)) { + return true + } + } + return false + } + + private areProposalAndOfferDefinitionIdEqual(proposalAttachment?: Attachment, offerAttachment?: Attachment) { + const credOffer = offerAttachment?.getDataAsJson() + const credPropose = proposalAttachment?.getDataAsJson() + + const proposalCredentialDefinitionId = credPropose?.credentialDefinitionId + const offerCredentialDefinitionId = credOffer?.cred_def_id + + return proposalCredentialDefinitionId === offerCredentialDefinitionId + } + + private areCredentialValuesValid(credentialRecord: CredentialExchangeRecord, credentialAttachment: Attachment) { + const indyCredential = credentialAttachment.getDataAsJson() + + if (!indyCredential) { + new AriesFrameworkError(`Missing required base64 encoded attachment data for credential`) + return false + } + + const credentialMessageValues = indyCredential.values + + if (credentialRecord.credentialAttributes) { + const defaultValues = CredentialUtils.convertAttributesToValues(credentialRecord.credentialAttributes) + + if (CredentialUtils.checkValuesMatch(credentialMessageValues, defaultValues)) { + return true + } + } + return false + } + public async deleteCredentialById( + credentialRecord: CredentialExchangeRecord, + options: DeleteCredentialOptions + ): Promise { + const indyCredential = credentialRecord.credentials.filter((binding) => { + return binding.credentialRecordType == CredentialFormatType.Indy + }) + if (indyCredential.length != 1) { + throw new AriesFrameworkError(`Could not find Indy record id for credential record ${credentialRecord.id}`) + } + if (options?.deleteAssociatedCredentials && indyCredential[0].credentialRecordId) { + await this.indyHolderService.deleteCredential(indyCredential[0].credentialRecordId) + } + } + + public async checkPreviewAttributesMatchSchemaAttributes( + offerAttachment: Attachment, + preview: V1CredentialPreview | V2CredentialPreview + ): Promise { + const credOffer = offerAttachment?.getDataAsJson() + + const schema = await this.indyLedgerService.getSchema(credOffer.schema_id) + + CredentialUtils.checkAttributesMatch(schema, preview) + } + + private isRequestDefinitionIdValid( + requestAttachment: Attachment, + offerAttachment?: Attachment, + proposeAttachment?: Attachment + ) { + const indyCredentialRequest = requestAttachment?.getDataAsJson() + const indyCredentialProposal = proposeAttachment?.getDataAsJson() + + const indyCredentialOffer = offerAttachment?.getDataAsJson() + + if (indyCredentialProposal || indyCredentialOffer) { + const previousCredentialDefinitionId = + indyCredentialOffer?.cred_def_id ?? indyCredentialProposal?.credentialDefinitionId + + if (previousCredentialDefinitionId === indyCredentialRequest.cred_def_id) { + return true + } + return false + } + return false + } + private generateId(): string { + return uuid() + } + + private async getRevocationRegistry(issueAttachment: Attachment): Promise { + const credential: Cred = issueAttachment.getDataAsJson() + let indyRegistry + if (credential.rev_reg_id) { + indyRegistry = await this.indyLedgerService.getRevocationRegistryDefinition(credential.rev_reg_id) + } + return { indy: indyRegistry } + } +} diff --git a/packages/core/src/modules/credentials/formats/models/CredPropose.ts b/packages/core/src/modules/credentials/formats/models/CredPropose.ts new file mode 100644 index 0000000000..0476cce949 --- /dev/null +++ b/packages/core/src/modules/credentials/formats/models/CredPropose.ts @@ -0,0 +1,81 @@ +import { Expose } from 'class-transformer' +import { IsOptional, IsString } from 'class-validator' + +export interface CredProposeOptions { + schemaIssuerDid?: string + schemaId?: string + schemaName?: string + schemaVersion?: string + credentialDefinitionId?: string + issuerDid?: string +} + +/** + * Class providing validation for the V2 credential proposal payload. + * + * The v1 message contains the properties directly in the message, which means they are + * validated using the class validator decorators. In v2 the attachments content is not transformed + * when transforming the message to a class instance so the content is not verified anymore, hence this + * class. + * + */ +export class CredPropose { + public constructor(options: CredProposeOptions) { + if (options) { + this.schemaIssuerDid = options.schemaIssuerDid + this.schemaId = options.schemaId + this.schemaName = options.schemaName + this.schemaVersion = options.schemaVersion + this.credentialDefinitionId = options.credentialDefinitionId + this.issuerDid = options.issuerDid + } + } + + /** + * Filter to request credential based on a particular Schema issuer DID. + */ + @Expose({ name: 'schema_issuer_did' }) + @IsString() + @IsOptional() + public schemaIssuerDid?: string + + /** + * Filter to request credential based on a particular Schema. + */ + @Expose({ name: 'schema_id' }) + @IsString() + @IsOptional() + public schemaId?: string + + /** + * Filter to request credential based on a schema name. + */ + @Expose({ name: 'schema_name' }) + @IsString() + @IsOptional() + public schemaName?: string + + /** + * Filter to request credential based on a schema version. + */ + @Expose({ name: 'schema_version' }) + @IsString() + @IsOptional() + public schemaVersion?: string + + /** + * Filter to request credential based on a particular Credential Definition. + */ + @Expose({ name: 'cred_def_id' }) + @IsString() + @IsOptional() + public credentialDefinitionId?: string + + /** + * Filter to request a credential issued by the owner of a particular DID. + */ + @Expose({ name: 'issuer_did' }) + @IsString() + @IsOptional() + public issuerDid?: string +} diff --git a/packages/core/src/modules/credentials/formats/models/CredentialFormatServiceOptions.ts b/packages/core/src/modules/credentials/formats/models/CredentialFormatServiceOptions.ts new file mode 100644 index 0000000000..3172fa9236 --- /dev/null +++ b/packages/core/src/modules/credentials/formats/models/CredentialFormatServiceOptions.ts @@ -0,0 +1,115 @@ +import type { Attachment } from '../../../../decorators/attachment/Attachment' +import type { LinkedAttachment } from '../../../../utils/LinkedAttachment' +import type { ParseRevocationRegistryDefinitionTemplate } from '../../../ledger/services' +import type { AutoAcceptCredential } from '../../CredentialAutoAcceptType' +import type { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttributes' +import type { V2CredentialPreview } from '../../protocol/v2/V2CredentialPreview' +import type { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' +import type { CredPropose } from './CredPropose' + +import { Expose } from 'class-transformer' +import { IsString } from 'class-validator' + +import { CredentialFormatType } from '../../CredentialsModuleOptions' + +export type CredentialFormats = + | FormatServiceOfferCredentialFormats + | FormatServiceProposeCredentialFormats + | FormatServiceRequestCredentialFormats +export interface IndyCredentialPreview { + credentialDefinitionId?: string + attributes?: CredentialPreviewAttribute[] +} + +export interface IndyProposeCredentialFormat { + attributes?: CredentialPreviewAttribute[] + linkedAttachments?: LinkedAttachment[] + payload?: CredPropose +} +export interface IndyOfferCredentialFormat { + credentialDefinitionId: string + attributes: CredentialPreviewAttribute[] + linkedAttachments?: LinkedAttachment[] +} +export interface IndyRequestCredentialFormat { + credentialDefinitionId?: string + attributes?: CredentialPreviewAttribute[] +} +export interface IndyIssueCredentialFormat { + credentialDefinitionId?: string + attributes?: CredentialPreviewAttribute[] +} + +export class CredentialFormatSpec { + @Expose({ name: 'attach_id' }) + @IsString() + public attachId!: string + + @IsString() + public format!: string +} + +export type FormatKeys = { + [id: string]: CredentialFormatType +} + +export interface FormatServiceCredentialAttachmentFormats { + format: CredentialFormatSpec + attachment: Attachment +} + +export interface FormatServiceProposeAttachmentFormats extends FormatServiceCredentialAttachmentFormats { + preview?: V2CredentialPreview +} + +export interface FormatServiceOfferAttachmentFormats extends FormatServiceCredentialAttachmentFormats { + preview?: V2CredentialPreview +} +export const FORMAT_KEYS: FormatKeys = { + indy: CredentialFormatType.Indy, +} + +export interface FormatServiceOfferCredentialFormats { + indy?: IndyOfferCredentialFormat + jsonld?: undefined +} + +export interface FormatServiceProposeCredentialFormats { + indy?: IndyProposeCredentialFormat + jsonld?: undefined +} + +export interface FormatServiceAcceptProposeCredentialFormats { + indy?: { + credentialDefinitionId?: string + attributes: CredentialPreviewAttribute[] + linkedAttachments?: LinkedAttachment[] + } + jsonld?: undefined +} + +export interface FormatServiceRequestCredentialFormats { + indy?: IndyRequestCredentialFormat + jsonld?: undefined +} + +export interface FormatServiceIssueCredentialFormats { + indy?: IndyIssueCredentialFormat + jsonld?: undefined +} + +export interface HandlerAutoAcceptOptions { + credentialRecord: CredentialExchangeRecord + autoAcceptType: AutoAcceptCredential + messageAttributes?: CredentialPreviewAttribute[] + proposalAttachment?: Attachment + offerAttachment?: Attachment + requestAttachment?: Attachment + credentialAttachment?: Attachment + credentialDefinitionId?: string +} + +export interface RevocationRegistry { + indy?: ParseRevocationRegistryDefinitionTemplate + jsonld?: undefined +} diff --git a/packages/core/src/modules/credentials/handlers/CredentialAckHandler.ts b/packages/core/src/modules/credentials/handlers/CredentialAckHandler.ts deleted file mode 100644 index cfecea6026..0000000000 --- a/packages/core/src/modules/credentials/handlers/CredentialAckHandler.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' -import type { CredentialService } from '../services' - -import { CredentialAckMessage } from '../messages' - -export class CredentialAckHandler implements Handler { - private credentialService: CredentialService - public supportedMessages = [CredentialAckMessage] - - public constructor(credentialService: CredentialService) { - this.credentialService = credentialService - } - - public async handle(messageContext: HandlerInboundMessage) { - await this.credentialService.processAck(messageContext) - } -} diff --git a/packages/core/src/modules/credentials/handlers/CredentialProblemReportHandler.ts b/packages/core/src/modules/credentials/handlers/CredentialProblemReportHandler.ts deleted file mode 100644 index b89a620d07..0000000000 --- a/packages/core/src/modules/credentials/handlers/CredentialProblemReportHandler.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' -import type { CredentialService } from '../services' - -import { CredentialProblemReportMessage } from '../messages' - -export class CredentialProblemReportHandler implements Handler { - private credentialService: CredentialService - public supportedMessages = [CredentialProblemReportMessage] - - public constructor(credentialService: CredentialService) { - this.credentialService = credentialService - } - - public async handle(messageContext: HandlerInboundMessage) { - await this.credentialService.processProblemReport(messageContext) - } -} diff --git a/packages/core/src/modules/credentials/handlers/IssueCredentialHandler.ts b/packages/core/src/modules/credentials/handlers/IssueCredentialHandler.ts deleted file mode 100644 index 293475f96e..0000000000 --- a/packages/core/src/modules/credentials/handlers/IssueCredentialHandler.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { AgentConfig } from '../../../agent/AgentConfig' -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' -import type { CredentialResponseCoordinator } from '../CredentialResponseCoordinator' -import type { CredentialRecord } from '../repository/CredentialRecord' -import type { CredentialService } from '../services' - -import { createOutboundMessage, createOutboundServiceMessage } from '../../../agent/helpers' -import { IssueCredentialMessage } from '../messages' - -export class IssueCredentialHandler implements Handler { - private credentialService: CredentialService - private agentConfig: AgentConfig - private credentialResponseCoordinator: CredentialResponseCoordinator - public supportedMessages = [IssueCredentialMessage] - - public constructor( - credentialService: CredentialService, - agentConfig: AgentConfig, - credentialResponseCoordinator: CredentialResponseCoordinator - ) { - this.credentialService = credentialService - this.agentConfig = agentConfig - this.credentialResponseCoordinator = credentialResponseCoordinator - } - - public async handle(messageContext: HandlerInboundMessage) { - const credentialRecord = await this.credentialService.processCredential(messageContext) - if (this.credentialResponseCoordinator.shouldAutoRespondToIssue(credentialRecord)) { - return await this.createAck(credentialRecord, messageContext) - } - } - - private async createAck(record: CredentialRecord, messageContext: HandlerInboundMessage) { - this.agentConfig.logger.info( - `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptCredentials}` - ) - const { message, credentialRecord } = await this.credentialService.createAck(record) - - if (messageContext.connection) { - return createOutboundMessage(messageContext.connection, message) - } else if (credentialRecord.credentialMessage?.service && credentialRecord.requestMessage?.service) { - const recipientService = credentialRecord.credentialMessage.service - const ourService = credentialRecord.requestMessage.service - - return createOutboundServiceMessage({ - payload: message, - service: recipientService.toDidCommService(), - senderKey: ourService.recipientKeys[0], - }) - } - - this.agentConfig.logger.error(`Could not automatically create credential ack`) - } -} diff --git a/packages/core/src/modules/credentials/handlers/OfferCredentialHandler.ts b/packages/core/src/modules/credentials/handlers/OfferCredentialHandler.ts deleted file mode 100644 index e00efdf7c7..0000000000 --- a/packages/core/src/modules/credentials/handlers/OfferCredentialHandler.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { AgentConfig } from '../../../agent/AgentConfig' -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' -import type { MediationRecipientService } from '../../routing/services/MediationRecipientService' -import type { CredentialResponseCoordinator } from '../CredentialResponseCoordinator' -import type { CredentialRecord } from '../repository/CredentialRecord' -import type { CredentialService } from '../services' - -import { createOutboundMessage, createOutboundServiceMessage } from '../../../agent/helpers' -import { ServiceDecorator } from '../../../decorators/service/ServiceDecorator' -import { OfferCredentialMessage } from '../messages' - -export class OfferCredentialHandler implements Handler { - private credentialService: CredentialService - private agentConfig: AgentConfig - private credentialResponseCoordinator: CredentialResponseCoordinator - private mediationRecipientService: MediationRecipientService - public supportedMessages = [OfferCredentialMessage] - - public constructor( - credentialService: CredentialService, - agentConfig: AgentConfig, - credentialResponseCoordinator: CredentialResponseCoordinator, - mediationRecipientService: MediationRecipientService - ) { - this.credentialService = credentialService - this.agentConfig = agentConfig - this.credentialResponseCoordinator = credentialResponseCoordinator - this.mediationRecipientService = mediationRecipientService - } - - public async handle(messageContext: HandlerInboundMessage) { - const credentialRecord = await this.credentialService.processOffer(messageContext) - - if (this.credentialResponseCoordinator.shouldAutoRespondToOffer(credentialRecord)) { - return await this.createRequest(credentialRecord, messageContext) - } - } - - private async createRequest(record: CredentialRecord, messageContext: HandlerInboundMessage) { - this.agentConfig.logger.info( - `Automatically sending request with autoAccept on ${this.agentConfig.autoAcceptCredentials}` - ) - - if (messageContext.connection) { - const { message } = await this.credentialService.createRequest(record, { - holderDid: messageContext.connection.did, - }) - - return createOutboundMessage(messageContext.connection, message) - } else if (record.offerMessage?.service) { - const routing = await this.mediationRecipientService.getRouting() - const ourService = new ServiceDecorator({ - serviceEndpoint: routing.endpoints[0], - recipientKeys: [routing.verkey], - routingKeys: routing.routingKeys, - }) - const recipientService = record.offerMessage.service - - const { message, credentialRecord } = await this.credentialService.createRequest(record, { - holderDid: ourService.recipientKeys[0], - }) - - // Set and save ~service decorator to record (to remember our verkey) - message.service = ourService - credentialRecord.requestMessage = message - await this.credentialService.update(credentialRecord) - - return createOutboundServiceMessage({ - payload: message, - service: recipientService.toDidCommService(), - senderKey: ourService.recipientKeys[0], - }) - } - - this.agentConfig.logger.error(`Could not automatically create credential request`) - } -} diff --git a/packages/core/src/modules/credentials/handlers/ProposeCredentialHandler.ts b/packages/core/src/modules/credentials/handlers/ProposeCredentialHandler.ts deleted file mode 100644 index 48eb7dd11e..0000000000 --- a/packages/core/src/modules/credentials/handlers/ProposeCredentialHandler.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { AgentConfig } from '../../../agent/AgentConfig' -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' -import type { CredentialResponseCoordinator } from '../CredentialResponseCoordinator' -import type { CredentialRecord } from '../repository/CredentialRecord' -import type { CredentialService } from '../services' - -import { createOutboundMessage } from '../../../agent/helpers' -import { ProposeCredentialMessage } from '../messages' - -export class ProposeCredentialHandler implements Handler { - private credentialService: CredentialService - private agentConfig: AgentConfig - private credentialAutoResponseCoordinator: CredentialResponseCoordinator - public supportedMessages = [ProposeCredentialMessage] - - public constructor( - credentialService: CredentialService, - agentConfig: AgentConfig, - responseCoordinator: CredentialResponseCoordinator - ) { - this.credentialAutoResponseCoordinator = responseCoordinator - this.credentialService = credentialService - this.agentConfig = agentConfig - } - - public async handle(messageContext: HandlerInboundMessage) { - const credentialRecord = await this.credentialService.processProposal(messageContext) - if (this.credentialAutoResponseCoordinator.shouldAutoRespondToProposal(credentialRecord)) { - return await this.createOffer(credentialRecord, messageContext) - } - } - - private async createOffer( - credentialRecord: CredentialRecord, - messageContext: HandlerInboundMessage - ) { - this.agentConfig.logger.info( - `Automatically sending offer with autoAccept on ${this.agentConfig.autoAcceptCredentials}` - ) - - if (!messageContext.connection) { - this.agentConfig.logger.error('No connection on the messageContext, aborting auto accept') - return - } - - if (!credentialRecord.proposalMessage?.credentialProposal) { - this.agentConfig.logger.error( - `Credential record with id ${credentialRecord.id} is missing required credential proposal` - ) - return - } - - if (!credentialRecord.proposalMessage.credentialDefinitionId) { - this.agentConfig.logger.error('Missing required credential definition id') - return - } - - const { message } = await this.credentialService.createOfferAsResponse(credentialRecord, { - credentialDefinitionId: credentialRecord.proposalMessage.credentialDefinitionId, - preview: credentialRecord.proposalMessage.credentialProposal, - }) - - return createOutboundMessage(messageContext.connection, message) - } -} diff --git a/packages/core/src/modules/credentials/handlers/RequestCredentialHandler.ts b/packages/core/src/modules/credentials/handlers/RequestCredentialHandler.ts deleted file mode 100644 index 7043ddaea1..0000000000 --- a/packages/core/src/modules/credentials/handlers/RequestCredentialHandler.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { AgentConfig } from '../../../agent/AgentConfig' -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' -import type { CredentialResponseCoordinator } from '../CredentialResponseCoordinator' -import type { CredentialRecord } from '../repository/CredentialRecord' -import type { CredentialService } from '../services' - -import { createOutboundMessage, createOutboundServiceMessage } from '../../../agent/helpers' -import { RequestCredentialMessage } from '../messages' - -export class RequestCredentialHandler implements Handler { - private agentConfig: AgentConfig - private credentialService: CredentialService - private credentialResponseCoordinator: CredentialResponseCoordinator - public supportedMessages = [RequestCredentialMessage] - - public constructor( - credentialService: CredentialService, - agentConfig: AgentConfig, - credentialResponseCoordinator: CredentialResponseCoordinator - ) { - this.credentialService = credentialService - this.agentConfig = agentConfig - this.credentialResponseCoordinator = credentialResponseCoordinator - } - - public async handle(messageContext: HandlerInboundMessage) { - const credentialRecord = await this.credentialService.processRequest(messageContext) - if (this.credentialResponseCoordinator.shouldAutoRespondToRequest(credentialRecord)) { - return await this.createCredential(credentialRecord, messageContext) - } - } - - private async createCredential( - record: CredentialRecord, - messageContext: HandlerInboundMessage - ) { - this.agentConfig.logger.info( - `Automatically sending credential with autoAccept on ${this.agentConfig.autoAcceptCredentials}` - ) - - const { message, credentialRecord } = await this.credentialService.createCredential(record) - if (messageContext.connection) { - return createOutboundMessage(messageContext.connection, message) - } else if (credentialRecord.requestMessage?.service && credentialRecord.offerMessage?.service) { - const recipientService = credentialRecord.requestMessage.service - const ourService = credentialRecord.offerMessage.service - - // Set ~service, update message in record (for later use) - message.setService(ourService) - credentialRecord.credentialMessage = message - await this.credentialService.update(credentialRecord) - - return createOutboundServiceMessage({ - payload: message, - service: recipientService.toDidCommService(), - senderKey: ourService.recipientKeys[0], - }) - } - this.agentConfig.logger.error(`Could not automatically create credential request`) - } -} diff --git a/packages/core/src/modules/credentials/handlers/RevocationNotificationHandler.ts b/packages/core/src/modules/credentials/handlers/RevocationNotificationHandler.ts deleted file mode 100644 index 799a43b3e2..0000000000 --- a/packages/core/src/modules/credentials/handlers/RevocationNotificationHandler.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' -import type { RevocationService } from '../services' - -import { V1RevocationNotificationMessage, V2RevocationNotificationMessage } from '../messages' - -export class V1RevocationNotificationHandler implements Handler { - private revocationService: RevocationService - public supportedMessages = [V1RevocationNotificationMessage] - - public constructor(revocationService: RevocationService) { - this.revocationService = revocationService - } - - public async handle(messageContext: HandlerInboundMessage) { - await this.revocationService.v1ProcessRevocationNotification(messageContext) - } -} - -export class V2RevocationNotificationHandler implements Handler { - private revocationService: RevocationService - public supportedMessages = [V2RevocationNotificationMessage] - - public constructor(revocationService: RevocationService) { - this.revocationService = revocationService - } - - public async handle(messageContext: HandlerInboundMessage) { - await this.revocationService.v2ProcessRevocationNotification(messageContext) - } -} diff --git a/packages/core/src/modules/credentials/handlers/index.ts b/packages/core/src/modules/credentials/handlers/index.ts deleted file mode 100644 index 1516216c29..0000000000 --- a/packages/core/src/modules/credentials/handlers/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './CredentialAckHandler' -export * from './IssueCredentialHandler' -export * from './OfferCredentialHandler' -export * from './ProposeCredentialHandler' -export * from './RequestCredentialHandler' -export * from './RevocationNotificationHandler' -export * from './CredentialProblemReportHandler' diff --git a/packages/core/src/modules/credentials/index.ts b/packages/core/src/modules/credentials/index.ts index 890024a880..d7b5b67b3a 100644 --- a/packages/core/src/modules/credentials/index.ts +++ b/packages/core/src/modules/credentials/index.ts @@ -1,9 +1,10 @@ -export * from './messages' -export * from './services' +export * from './CredentialsModule' +export * from './protocol/v1/messages' export * from './CredentialUtils' -export * from './models' +export * from './protocol/v1/models' export * from './repository' export * from './CredentialState' export * from './CredentialEvents' -export * from './CredentialsModule' export * from './CredentialAutoAcceptType' +export * from './CredentialProtocolVersion' +export * from './CredentialResponseCoordinator' diff --git a/packages/core/src/modules/credentials/messages/index.ts b/packages/core/src/modules/credentials/messages/index.ts deleted file mode 100644 index 0b78a2d4a1..0000000000 --- a/packages/core/src/modules/credentials/messages/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from './CredentialAckMessage' -export * from './CredentialPreview' -export * from './RequestCredentialMessage' -export * from './IssueCredentialMessage' -export * from './OfferCredentialMessage' -export * from './ProposeCredentialMessage' -export * from './RevocationNotificationMessage' -export * from './CredentialProblemReportMessage' diff --git a/packages/core/src/modules/credentials/models/CredentialPreviewAttributes.ts b/packages/core/src/modules/credentials/models/CredentialPreviewAttributes.ts new file mode 100644 index 0000000000..89c3397b09 --- /dev/null +++ b/packages/core/src/modules/credentials/models/CredentialPreviewAttributes.ts @@ -0,0 +1,39 @@ +import { Expose } from 'class-transformer' +import { IsMimeType, IsOptional, IsString } from 'class-validator' + +import { JsonTransformer } from '../../../utils/JsonTransformer' + +export interface CredentialPreviewAttributeOptions { + name: string + mimeType?: string + value: string +} + +export class CredentialPreviewAttribute { + public constructor(options: CredentialPreviewAttributeOptions) { + if (options) { + this.name = options.name + this.mimeType = options.mimeType + this.value = options.value + } + } + + @IsString() + public name!: string + + @Expose({ name: 'mime-type' }) + @IsOptional() + @IsMimeType() + public mimeType?: string = 'text/plain' + + @IsString() + public value!: string + + public toJSON(): Record { + return JsonTransformer.toJSON(this) + } +} + +export interface CredentialPreviewOptions { + attributes: CredentialPreviewAttribute[] +} diff --git a/packages/core/src/modules/credentials/models/index.ts b/packages/core/src/modules/credentials/models/index.ts index 9e47b2ca8d..f6d0750b49 100644 --- a/packages/core/src/modules/credentials/models/index.ts +++ b/packages/core/src/modules/credentials/models/index.ts @@ -1,4 +1,2 @@ -export * from './Credential' -export * from './IndyCredentialInfo' -export * from './RevocationInterval' export * from './RevocationNotification' +export * from './CredentialPreviewAttributes' diff --git a/packages/core/src/modules/credentials/protocol/index.ts b/packages/core/src/modules/credentials/protocol/index.ts new file mode 100644 index 0000000000..88af3b8591 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/index.ts @@ -0,0 +1,3 @@ +export * from '../CredentialServiceOptions' +export * from './v1/V1CredentialService' +export * from './v2/V2CredentialService' diff --git a/packages/core/src/modules/credentials/messages/CredentialPreview.ts b/packages/core/src/modules/credentials/protocol/v1/V1CredentialPreview.ts similarity index 54% rename from packages/core/src/modules/credentials/messages/CredentialPreview.ts rename to packages/core/src/modules/credentials/protocol/v1/V1CredentialPreview.ts index e4feed3234..48052a2632 100644 --- a/packages/core/src/modules/credentials/messages/CredentialPreview.ts +++ b/packages/core/src/modules/credentials/protocol/v1/V1CredentialPreview.ts @@ -1,43 +1,11 @@ -import { Expose, Transform, Type } from 'class-transformer' -import { Equals, IsInstance, IsMimeType, IsOptional, IsString, ValidateNested } from 'class-validator' - -import { JsonTransformer } from '../../../utils/JsonTransformer' -import { replaceLegacyDidSovPrefix } from '../../../utils/messageType' - -interface CredentialPreviewAttributeOptions { - name: string - mimeType?: string - value: string -} - -export class CredentialPreviewAttribute { - public constructor(options: CredentialPreviewAttributeOptions) { - if (options) { - this.name = options.name - this.mimeType = options.mimeType - this.value = options.value - } - } - - @IsString() - public name!: string +import type { CredentialPreviewOptions } from '../../models/CredentialPreviewAttributes' - @Expose({ name: 'mime-type' }) - @IsOptional() - @IsMimeType() - public mimeType?: string = 'text/plain' - - @IsString() - public value!: string - - public toJSON(): Record { - return JsonTransformer.toJSON(this) - } -} +import { Expose, Transform, Type } from 'class-transformer' +import { Equals, IsInstance, ValidateNested } from 'class-validator' -export interface CredentialPreviewOptions { - attributes: CredentialPreviewAttribute[] -} +import { JsonTransformer } from '../../../../utils/JsonTransformer' +import { replaceLegacyDidSovPrefix } from '../../../../utils/messageType' +import { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttributes' /** * Credential preview inner message class. @@ -46,7 +14,7 @@ export interface CredentialPreviewOptions { * * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0036-issue-credential/README.md#preview-credential */ -export class CredentialPreview { +export class V1CredentialPreview { public constructor(options: CredentialPreviewOptions) { if (options) { this.attributes = options.attributes @@ -54,12 +22,12 @@ export class CredentialPreview { } @Expose({ name: '@type' }) - @Equals(CredentialPreview.type) + @Equals(V1CredentialPreview.type) @Transform(({ value }) => replaceLegacyDidSovPrefix(value), { toClassOnly: true, }) - public readonly type = CredentialPreview.type - public static readonly type = 'https://didcomm.org/issue-credential/1.0/credential-preview' + public type = V1CredentialPreview.type + public static type = `https://didcomm.org/issue-credential/1.0/credential-preview` @Type(() => CredentialPreviewAttribute) @ValidateNested({ each: true }) @@ -89,7 +57,7 @@ export class CredentialPreview { }) ) - return new CredentialPreview({ + return new V1CredentialPreview({ attributes, }) } diff --git a/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts b/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts new file mode 100644 index 0000000000..3f04dbd6ef --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v1/V1CredentialService.ts @@ -0,0 +1,1251 @@ +import type { AgentMessage } from '../../../../agent/AgentMessage' +import type { HandlerInboundMessage } from '../../../../agent/Handler' +import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import type { Attachment } from '../../../../decorators/attachment/Attachment' +import type { ConnectionRecord } from '../../../connections' +import type { CredentialStateChangedEvent } from '../../CredentialEvents' +import type { + ServiceAcceptCredentialOptions, + CredentialOfferTemplate, + CredentialProposeOptions, + CredentialProtocolMsgReturnType, + ServiceAcceptRequestOptions, + ServiceRequestCredentialOptions, + ServiceOfferCredentialOptions, +} from '../../CredentialServiceOptions' +import type { + AcceptProposalOptions, + NegotiateProposalOptions, + OfferCredentialOptions, + ProposeCredentialOptions, + RequestCredentialOptions, +} from '../../CredentialsModuleOptions' +import type { CredentialFormatService } from '../../formats/CredentialFormatService' +import type { HandlerAutoAcceptOptions } from '../../formats/models/CredentialFormatServiceOptions' +import type { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttributes' +import type { CredOffer } from 'indy-sdk' + +import { Lifecycle, scoped } from 'tsyringe' + +import { AgentConfig } from '../../../../agent/AgentConfig' +import { Dispatcher } from '../../../../agent/Dispatcher' +import { EventEmitter } from '../../../../agent/EventEmitter' +import { ServiceDecorator } from '../../../../decorators/service/ServiceDecorator' +import { AriesFrameworkError } from '../../../../error' +import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage' +import { isLinkedAttachment } from '../../../../utils/attachment' +import { AckStatus } from '../../../common' +import { ConnectionService } from '../../../connections/services' +import { MediationRecipientService } from '../../../routing' +import { AutoAcceptCredential } from '../../CredentialAutoAcceptType' +import { CredentialEventTypes } from '../../CredentialEvents' +import { CredentialProtocolVersion } from '../../CredentialProtocolVersion' +import { CredentialResponseCoordinator } from '../../CredentialResponseCoordinator' +import { CredentialState } from '../../CredentialState' +import { CredentialUtils } from '../../CredentialUtils' +import { CredentialProblemReportError, CredentialProblemReportReason } from '../../errors' +import { IndyCredentialFormatService } from '../../formats/indy/IndyCredentialFormatService' +import { CredentialRepository, CredentialMetadataKeys, CredentialExchangeRecord } from '../../repository' +import { CredentialService, RevocationService } from '../../services' + +import { V1CredentialPreview } from './V1CredentialPreview' +import { + V1CredentialAckHandler, + V1CredentialProblemReportHandler, + V1IssueCredentialHandler, + V1OfferCredentialHandler, + V1ProposeCredentialHandler, + V1RequestCredentialHandler, + V1RevocationNotificationHandler, +} from './handlers' +import { + INDY_CREDENTIAL_ATTACHMENT_ID, + INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + V1ProposeCredentialMessage, + V1IssueCredentialMessage, + V1RequestCredentialMessage, + V1OfferCredentialMessage, + V1CredentialAckMessage, +} from './messages' + +@scoped(Lifecycle.ContainerScoped) +export class V1CredentialService extends CredentialService { + private connectionService: ConnectionService + private formatService: IndyCredentialFormatService + + public constructor( + connectionService: ConnectionService, + didCommMessageRepository: DidCommMessageRepository, + agentConfig: AgentConfig, + mediationRecipientService: MediationRecipientService, + dispatcher: Dispatcher, + eventEmitter: EventEmitter, + credentialRepository: CredentialRepository, + formatService: IndyCredentialFormatService, + revocationService: RevocationService + ) { + super( + credentialRepository, + eventEmitter, + dispatcher, + agentConfig, + mediationRecipientService, + didCommMessageRepository, + revocationService + ) + this.connectionService = connectionService + this.formatService = formatService + } + + /** + * Create a {@link ProposeCredentialMessage} not bound to an existing credential exchange. + * To create a proposal as response to an existing credential exchange, use {@link createProposalAsResponse}. + * + * @param proposal The object containing config options + * @returns Object containing proposal message and associated credential record + * + */ + public async createProposal( + proposal: ProposeCredentialOptions + ): Promise> { + const connection = await this.connectionService.getById(proposal.connectionId) + connection.assertReady() + + let credentialProposal: V1CredentialPreview | undefined + + const credPropose = proposal.credentialFormats.indy?.payload + + if (proposal.credentialFormats.indy?.attributes) { + credentialProposal = new V1CredentialPreview({ attributes: proposal.credentialFormats.indy?.attributes }) + } + + const config: CredentialProposeOptions = { + credentialProposal: credentialProposal, + credentialDefinitionId: credPropose?.credentialDefinitionId, + linkedAttachments: proposal.credentialFormats.indy?.linkedAttachments, + schemaId: credPropose?.schemaId, + } + + const options = { ...config } + + const { attachment: filtersAttach } = this.formatService.createProposal(proposal) + + if (!filtersAttach) { + throw new AriesFrameworkError('Missing filters attach in Proposal') + } + options.attachments = [] + options.attachments?.push(filtersAttach) + + // Create message + const message = new V1ProposeCredentialMessage(options ?? {}) + + // Create record + const credentialRecord = new CredentialExchangeRecord({ + connectionId: connection.id, + threadId: message.threadId, + state: CredentialState.ProposalSent, + linkedAttachments: config?.linkedAttachments?.map((linkedAttachment) => linkedAttachment.attachment), + credentialAttributes: message.credentialProposal?.attributes, + autoAcceptCredential: config?.autoAcceptCredential, + protocolVersion: CredentialProtocolVersion.V1, + credentials: [], + }) + + // Set the metadata + credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { + schemaId: options.schemaId, + credentialDefinitionId: options.credentialDefinitionId, + }) + await this.credentialRepository.save(credentialRecord) + + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + + this.eventEmitter.emit({ + type: CredentialEventTypes.CredentialStateChanged, + payload: { + credentialRecord, + previousState: null, + }, + }) + + return { credentialRecord, message } + } + + /** + * Processing an incoming credential message and create a credential offer as a response + * @param proposal The object containing config options + * @param credentialRecord the credential exchange record for this proposal + * @returns Object containing proposal message and associated credential record + */ + public async acceptProposal( + options: AcceptProposalOptions, + credentialRecord: CredentialExchangeRecord + ): Promise> { + const proposalCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1ProposeCredentialMessage, + }) + + if (!proposalCredentialMessage?.credentialProposal) { + throw new AriesFrameworkError( + `Credential record with id ${options.credentialRecordId} is missing required credential proposal` + ) + } + + if (!options.credentialFormats) { + throw new AriesFrameworkError('Missing credential formats in V1 acceptProposal') + } + + const credentialDefinitionId = + options.credentialFormats.indy?.credentialDefinitionId ?? proposalCredentialMessage.credentialDefinitionId + + if (!credentialDefinitionId) { + throw new AriesFrameworkError( + 'Missing required credential definition id. If credential proposal message contains no credential definition id it must be passed to config.' + ) + } + const { message } = await this.createOfferAsResponse(credentialRecord, { + preview: proposalCredentialMessage.credentialProposal, + credentialDefinitionId, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + attachments: credentialRecord.linkedAttachments, + }) + + return { credentialRecord, message } + } + + /** + * Negotiate a credential proposal as issuer (by sending a credential offer message) to the connection + * associated with the credential record. + * + * @param credentialOptions configuration for the offer see {@link NegotiateProposalOptions} + * @param credentialRecord the credential exchange record for this proposal + * @returns Credential record associated with the credential offer and the corresponding new offer message + * + */ + public async negotiateProposal( + credentialOptions: NegotiateProposalOptions, + credentialRecord: CredentialExchangeRecord + ): Promise> { + if (!credentialRecord.connectionId) { + throw new AriesFrameworkError( + `No connectionId found for credential record '${credentialRecord.id}'. Connection-less issuance does not support negotiation.` + ) + } + + const credentialProposalMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1ProposeCredentialMessage, + }) + + if (!credentialProposalMessage?.credentialProposal) { + throw new AriesFrameworkError( + `Credential record with id ${credentialOptions.credentialRecordId} is missing required credential proposal` + ) + } + + const credentialDefinitionId = + credentialOptions.credentialFormats.indy?.credentialDefinitionId ?? + credentialProposalMessage.credentialDefinitionId + + if (!credentialDefinitionId) { + throw new AriesFrameworkError( + 'Missing required credential definition id. If credential proposal message contains no credential definition id it must be passed to config.' + ) + } + + if (!credentialOptions?.credentialFormats.indy?.attributes) { + throw new AriesFrameworkError('No proposal attributes in the negotiation options!') + } + const newCredentialProposal = new V1CredentialPreview({ + attributes: credentialOptions?.credentialFormats.indy?.attributes, + }) + + const { message } = await this.createOfferAsResponse(credentialRecord, { + preview: newCredentialProposal, + credentialDefinitionId, + comment: credentialOptions.comment, + autoAcceptCredential: credentialOptions.autoAcceptCredential, + attachments: credentialRecord.linkedAttachments, + }) + return { credentialRecord, message } + } + /** + * Process a received {@link ProposeCredentialMessage}. This will not accept the credential proposal + * or send a credential offer. It will only create a new, or update the existing credential record with + * the information from the credential proposal message. Use {@link createOfferAsResponse} + * after calling this method to create a credential offer. + * + * @param messageContext The message context containing a credential proposal message + * @returns credential record associated with the credential proposal message + * + */ + public async processProposal( + messageContext: InboundMessageContext + ): Promise { + let credentialRecord: CredentialExchangeRecord + const { message: proposalMessage, connection } = messageContext + + this.logger.debug(`Processing credential proposal with id ${proposalMessage.id}`) + + try { + // Credential record already exists + credentialRecord = await this.getByThreadAndConnectionId(proposalMessage.threadId, connection?.id) + // Assert + credentialRecord.assertState(CredentialState.OfferSent) + + const proposalCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1ProposeCredentialMessage, + }) + const offerCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1OfferCredentialMessage, + }) + + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: proposalCredentialMessage ?? undefined, + previousSentMessage: offerCredentialMessage ?? undefined, + }) + + // Update record + await this.updateState(credentialRecord, CredentialState.ProposalReceived) + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: proposalMessage, + role: DidCommMessageRole.Receiver, + associatedRecordId: credentialRecord.id, + }) + } catch { + // No credential record exists with thread id + credentialRecord = new CredentialExchangeRecord({ + connectionId: connection?.id, + threadId: proposalMessage.threadId, + credentialAttributes: proposalMessage.credentialProposal?.attributes, + state: CredentialState.ProposalReceived, + protocolVersion: CredentialProtocolVersion.V1, + credentials: [], + }) + + credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { + schemaId: proposalMessage.schemaId, + credentialDefinitionId: proposalMessage.credentialDefinitionId, + }) + + // Assert + this.connectionService.assertConnectionOrServiceDecorator(messageContext) + + // Save record + await this.credentialRepository.save(credentialRecord) + this.eventEmitter.emit({ + type: CredentialEventTypes.CredentialStateChanged, + payload: { + credentialRecord, + previousState: null, + }, + }) + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: proposalMessage, + role: DidCommMessageRole.Receiver, + associatedRecordId: credentialRecord.id, + }) + } + return credentialRecord + } + + /** + * Create a {@link OfferCredentialMessage} as response to a received credential proposal. + * To create an offer not bound to an existing credential exchange, use {@link createOffer}. + * + * @param credentialRecord The credential record for which to create the credential offer + * @param credentialTemplate The credential template to use for the offer + * @returns Object containing offer message and associated credential record + * + */ + public async createOfferAsResponse( + credentialRecord: CredentialExchangeRecord, + credentialTemplate: CredentialOfferTemplate + ): Promise> { + // Assert + credentialRecord.assertState(CredentialState.ProposalReceived) + + // Create message + const { credentialDefinitionId, comment, preview, attachments } = credentialTemplate + + const options: ServiceOfferCredentialOptions = { + attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + credentialFormats: { + indy: { + credentialDefinitionId, + attributes: preview.attributes, + }, + }, + protocolVersion: CredentialProtocolVersion.V1, + } + + const { attachment: offersAttach } = await this.formatService.createOffer(options) + + if (!offersAttach) { + throw new AriesFrameworkError('No offer attachment for credential') + } + + const credOffer = offersAttach.getDataAsJson() + + if (!offersAttach) { + throw new AriesFrameworkError('Missing offers attach in Offer') + } + + const offerMessage = new V1OfferCredentialMessage({ + comment, + offerAttachments: [offersAttach], + credentialPreview: preview, + attachments, + }) + + offerMessage.setThread({ + threadId: credentialRecord.threadId, + }) + + credentialRecord.credentialAttributes = preview.attributes + credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { + schemaId: credOffer.schema_id, + credentialDefinitionId: credOffer.cred_def_id, + }) + credentialRecord.linkedAttachments = attachments?.filter((attachment) => isLinkedAttachment(attachment)) + credentialRecord.autoAcceptCredential = + credentialTemplate.autoAcceptCredential ?? credentialRecord.autoAcceptCredential + + await this.updateState(credentialRecord, CredentialState.OfferSent) + + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: offerMessage, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + return { message: offerMessage, credentialRecord } + } + /** + * Process a received {@link RequestCredentialMessage}. This will not accept the credential request + * or send a credential. It will only update the existing credential record with + * the information from the credential request message. Use {@link createCredential} + * after calling this method to create a credential. + * + * @param messageContext The message context containing a credential request message + * @returns credential record associated with the credential request message + * + */ + + public async negotiateOffer( + credentialOptions: ProposeCredentialOptions, + credentialRecord: CredentialExchangeRecord + ): Promise> { + if (!credentialRecord.connectionId) { + throw new AriesFrameworkError( + `No connectionId found for credential record '${credentialRecord.id}'. Connection-less issuance does not support negotiation.` + ) + } + if (!credentialOptions.credentialFormats.indy?.attributes) { + throw new AriesFrameworkError('Missing attributes in V1 Negotiate Offer Options') + } + const credentialPreview = new V1CredentialPreview({ + attributes: credentialOptions.credentialFormats.indy?.attributes, + }) + const options: CredentialProposeOptions = { + credentialProposal: credentialPreview, + } + + credentialRecord.assertState(CredentialState.OfferReceived) + + // Create message + const message = new V1ProposeCredentialMessage(options ?? {}) + + message.setThread({ threadId: credentialRecord.threadId }) + + // Update record + credentialRecord.credentialAttributes = message.credentialProposal?.attributes + await this.updateState(credentialRecord, CredentialState.ProposalSent) + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + + return { credentialRecord, message } + } + + /** + * Create a {@link OfferCredentialMessage} not bound to an existing credential exchange. + * To create an offer as response to an existing credential exchange, use {@link V1CredentialService#createOfferAsResponse}. + * + * @param credentialOptions The options containing config params for creating the credential offer + * @returns Object containing offer message and associated credential record + * + */ + public async createOffer( + credentialOptions: OfferCredentialOptions + ): Promise> { + if (!credentialOptions.connectionId) { + throw new AriesFrameworkError('Connection id missing from offer credential options') + } + const connection = await this.connectionService.getById(credentialOptions.connectionId) + + if ( + !credentialOptions?.credentialFormats.indy?.attributes || + !credentialOptions?.credentialFormats.indy?.credentialDefinitionId + ) { + throw new AriesFrameworkError('Missing properties from OfferCredentialOptions object: cannot create Offer!') + } + const preview: V1CredentialPreview = new V1CredentialPreview({ + attributes: credentialOptions.credentialFormats.indy?.attributes, + }) + + const linkedAttachments = credentialOptions.credentialFormats.indy?.linkedAttachments + + const template: CredentialOfferTemplate = { + ...credentialOptions, + preview: preview, + credentialDefinitionId: credentialOptions?.credentialFormats.indy?.credentialDefinitionId, + linkedAttachments, + } + + const { credentialRecord, message } = await this.createOfferProcessing(template, connection) + + await this.credentialRepository.save(credentialRecord) + this.eventEmitter.emit({ + type: CredentialEventTypes.CredentialStateChanged, + payload: { + credentialRecord, + previousState: null, + }, + }) + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + return { credentialRecord, message } + } + /** + * Process a received {@link OfferCredentialMessage}. This will not accept the credential offer + * or send a credential request. It will only create a new credential record with + * the information from the credential offer message. Use {@link createRequest} + * after calling this method to create a credential request. + * + * @param messageContext The message context containing a credential request message + * @returns credential record associated with the credential offer message + * + */ + public async processOffer( + messageContext: HandlerInboundMessage + ): Promise { + let credentialRecord: CredentialExchangeRecord + const { message: offerMessage, connection } = messageContext + + this.logger.debug(`Processing credential offer with id ${offerMessage.id}`) + + const indyCredentialOffer = offerMessage.indyCredentialOffer + + if (!indyCredentialOffer) { + throw new CredentialProblemReportError( + `Missing required base64 or json encoded attachment data for credential offer with thread id ${offerMessage.threadId}`, + { problemCode: CredentialProblemReportReason.IssuanceAbandoned } + ) + } + + try { + // Credential record already exists + credentialRecord = await this.getByThreadAndConnectionId(offerMessage.threadId, connection?.id) + + const proposalCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1ProposeCredentialMessage, + }) + const offerCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1OfferCredentialMessage, + }) + + // Assert + credentialRecord.assertState(CredentialState.ProposalSent) + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: offerCredentialMessage ?? undefined, + previousSentMessage: proposalCredentialMessage ?? undefined, + }) + + credentialRecord.linkedAttachments = offerMessage.appendedAttachments?.filter(isLinkedAttachment) + + const attachment = offerCredentialMessage + ? offerCredentialMessage.getAttachmentById(INDY_CREDENTIAL_OFFER_ATTACHMENT_ID) + : undefined + if (attachment) { + await this.formatService.processOffer(attachment, credentialRecord) + } + + credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { + schemaId: indyCredentialOffer.schema_id, + credentialDefinitionId: indyCredentialOffer.cred_def_id, + }) + + await this.updateState(credentialRecord, CredentialState.OfferReceived) + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: offerMessage, + role: DidCommMessageRole.Receiver, + associatedRecordId: credentialRecord.id, + }) + } catch { + // No credential record exists with thread id + credentialRecord = new CredentialExchangeRecord({ + connectionId: connection?.id, + threadId: offerMessage.id, + credentialAttributes: offerMessage.credentialPreview.attributes, + state: CredentialState.OfferReceived, + protocolVersion: CredentialProtocolVersion.V1, + credentials: [], + }) + + credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { + schemaId: indyCredentialOffer.schema_id, + credentialDefinitionId: indyCredentialOffer.cred_def_id, + }) + // Assert + this.connectionService.assertConnectionOrServiceDecorator(messageContext) + + // Save in repository + await this.credentialRepository.save(credentialRecord) + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: offerMessage, + role: DidCommMessageRole.Receiver, + associatedRecordId: credentialRecord.id, + }) + this.eventEmitter.emit({ + type: CredentialEventTypes.CredentialStateChanged, + payload: { + credentialRecord, + previousState: null, + }, + }) + } + + return credentialRecord + } + + private async createOfferProcessing( + credentialTemplate: CredentialOfferTemplate, + connectionRecord?: ConnectionRecord + ): Promise> { + // Assert + connectionRecord?.assertReady() + + // Create message + const { credentialDefinitionId, comment, preview, linkedAttachments } = credentialTemplate + + // Create and link credential to attachment + const credentialPreview = linkedAttachments + ? CredentialUtils.createAndLinkAttachmentsToPreview(linkedAttachments, preview) + : preview + + const options: ServiceOfferCredentialOptions = { + attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + credentialFormats: { + indy: { + credentialDefinitionId, + attributes: credentialPreview.attributes, + }, + }, + protocolVersion: CredentialProtocolVersion.V1, + } + + const { attachment: offersAttach } = await this.formatService.createOffer(options) + + if (!offersAttach) { + throw new AriesFrameworkError('Missing offers attach in Offer') + } + + // Construct offer message + const offerMessage = new V1OfferCredentialMessage({ + comment, + offerAttachments: [offersAttach], + credentialPreview, + attachments: linkedAttachments?.map((linkedAttachment) => linkedAttachment.attachment), + }) + + // Create record + const credentialRecord = new CredentialExchangeRecord({ + connectionId: connectionRecord?.id, + threadId: offerMessage.id, + credentialAttributes: credentialPreview.attributes, + linkedAttachments: linkedAttachments?.map((linkedAttachments) => linkedAttachments.attachment), + state: CredentialState.OfferSent, + autoAcceptCredential: credentialTemplate.autoAcceptCredential, + protocolVersion: CredentialProtocolVersion.V1, + credentials: [], + }) + + const offer = offersAttach.getDataAsJson() + credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { + credentialDefinitionId: credentialDefinitionId, + schemaId: offer.schema_id, + }) + + return { message: offerMessage, credentialRecord } + } + + public async createOutOfBandOffer( + credentialOptions: OfferCredentialOptions + ): Promise> { + if (!credentialOptions.credentialFormats.indy?.credentialDefinitionId) { + throw new AriesFrameworkError('Missing credential definition id for out of band credential') + } + const v1Preview = new V1CredentialPreview({ + attributes: credentialOptions.credentialFormats.indy?.attributes, + }) + const template: CredentialOfferTemplate = { + credentialDefinitionId: credentialOptions.credentialFormats.indy?.credentialDefinitionId, + comment: credentialOptions.comment, + preview: v1Preview, + autoAcceptCredential: credentialOptions.autoAcceptCredential, + } + + const { credentialRecord, message } = await this.createOfferProcessing(template) + + // Create and set ~service decorator + const routing = await this.mediationRecipientService.getRouting() + message.service = new ServiceDecorator({ + serviceEndpoint: routing.endpoints[0], + recipientKeys: [routing.verkey], + routingKeys: routing.routingKeys, + }) + await this.credentialRepository.save(credentialRecord) + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: message, + role: DidCommMessageRole.Receiver, + associatedRecordId: credentialRecord.id, + }) + this.eventEmitter.emit({ + type: CredentialEventTypes.CredentialStateChanged, + payload: { + credentialRecord, + previousState: null, + }, + }) + return { credentialRecord, message } + } + /** + * Create a {@link RequestCredentialMessage} as response to a received credential offer. + * + * @param record The credential record for which to create the credential request + * @param options Additional configuration to use for the credential request + * @returns Object containing request message and associated credential record + * + */ + public async createRequest( + record: CredentialExchangeRecord, + options: ServiceRequestCredentialOptions, + holderDid: string + ): Promise> { + // Assert credential + record.assertState(CredentialState.OfferReceived) + + const offerCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: record.id, + messageClass: V1OfferCredentialMessage, + }) + + // remove + if (!offerCredentialMessage) { + throw new CredentialProblemReportError(`Missing required credential offer with thread id ${record.threadId}`, { + problemCode: CredentialProblemReportReason.IssuanceAbandoned, + }) + } + + const attachment = offerCredentialMessage.getAttachmentById(INDY_CREDENTIAL_OFFER_ATTACHMENT_ID) + if (attachment) { + options.offerAttachment = attachment + } else { + throw new AriesFrameworkError(`Missing data payload in attachment in credential Record ${record.id}`) + } + options.attachId = INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID + const { attachment: requestAttach } = await this.formatService.createRequest(options, record, holderDid) + if (!requestAttach) { + throw new AriesFrameworkError(`Failed to create attachment for request; credential record = ${record.id}`) + } + + const requestMessage = new V1RequestCredentialMessage({ + comment: options?.comment, + requestAttachments: [requestAttach], + attachments: offerCredentialMessage?.appendedAttachments?.filter((attachment) => isLinkedAttachment(attachment)), + }) + requestMessage.setThread({ threadId: record.threadId }) + + record.autoAcceptCredential = options?.autoAcceptCredential ?? record.autoAcceptCredential + + record.linkedAttachments = offerCredentialMessage?.appendedAttachments?.filter((attachment) => + isLinkedAttachment(attachment) + ) + await this.updateState(record, CredentialState.RequestSent) + + return { message: requestMessage, credentialRecord: record } + } + /** + * Process a received {@link IssueCredentialMessage}. This will not accept the credential + * or send a credential acknowledgement. It will only update the existing credential record with + * the information from the issue credential message. Use {@link createAck} + * after calling this method to create a credential acknowledgement. + * + * @param messageContext The message context containing an issue credential message + * + * @returns credential record associated with the issue credential message + * + */ + + public async processRequest( + messageContext: InboundMessageContext + ): Promise { + const { message: requestMessage, connection } = messageContext + + this.logger.debug(`Processing credential request with id ${requestMessage.id}`) + + const credentialRecord = await this.getByThreadAndConnectionId(requestMessage.threadId, connection?.id) + + const proposalMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1ProposeCredentialMessage, + }) + const offerMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1OfferCredentialMessage, + }) + + // Assert + credentialRecord.assertState(CredentialState.OfferSent) + + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: proposalMessage ?? undefined, + previousSentMessage: offerMessage ?? undefined, + }) + + const requestOptions: RequestCredentialOptions = { + connectionId: messageContext.connection?.id, + } + await this.formatService.processRequest(requestOptions, credentialRecord) + + this.logger.trace('Credential record found when processing credential request', credentialRecord) + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: requestMessage, + role: DidCommMessageRole.Receiver, + associatedRecordId: credentialRecord.id, + }) + await this.updateState(credentialRecord, CredentialState.RequestReceived) + return credentialRecord + } + /** + * Create a {@link IssueCredentialMessage} as response to a received credential request. + * + * @param record The credential record for which to create the credential + * @param options Additional configuration to use for the credential + * @returns Object containing issue credential message and associated credential record + * + */ + public async createCredential( + record: CredentialExchangeRecord, + options: ServiceAcceptRequestOptions + ): Promise> { + // Assert + record.assertState(CredentialState.RequestReceived) + const offerMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: record.id, + messageClass: V1OfferCredentialMessage, + }) + const requestMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: record.id, + messageClass: V1RequestCredentialMessage, + }) + // Assert offer message + if (!offerMessage) { + throw new AriesFrameworkError( + `Missing credential offer for credential exchange with thread id ${record.threadId}` + ) + } + + if (!requestMessage) { + throw new AriesFrameworkError(`Missing request message in credential Record ${record.id}`) + } + let offerAttachment: Attachment | undefined + + if (offerMessage) { + offerAttachment = offerMessage.getAttachmentById(INDY_CREDENTIAL_OFFER_ATTACHMENT_ID) + } else { + throw new AriesFrameworkError(`Missing data payload in attachment in credential Record ${record.id}`) + } + const requestAttachment = requestMessage.getAttachmentById(INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID) + + if (!requestAttachment) { + throw new AriesFrameworkError('Missing requestAttachment in v1 createCredential') + } + options.attachId = INDY_CREDENTIAL_ATTACHMENT_ID + + // Assert credential attributes + const credentialAttributes = record.credentialAttributes + if (!credentialAttributes) { + throw new CredentialProblemReportError( + `Missing required credential attribute values on credential record with id ${record.id}`, + { problemCode: CredentialProblemReportReason.IssuanceAbandoned } + ) + } + + const { attachment: credentialsAttach } = await this.formatService.createCredential( + options, + record, + requestAttachment, + offerAttachment + ) + if (!credentialsAttach) { + throw new AriesFrameworkError(`Failed to create attachment for request; credential record = ${record.id}`) + } + + const issueMessage = new V1IssueCredentialMessage({ + comment: options?.comment, + credentialAttachments: [credentialsAttach], + attachments: + offerMessage?.appendedAttachments?.filter((attachment) => isLinkedAttachment(attachment)) || + requestMessage?.appendedAttachments?.filter((attachment: Attachment) => isLinkedAttachment(attachment)), + }) + issueMessage.setThread({ + threadId: record.threadId, + }) + issueMessage.setPleaseAck() + + record.autoAcceptCredential = options?.autoAcceptCredential ?? record.autoAcceptCredential + + await this.updateState(record, CredentialState.CredentialIssued) + return { message: issueMessage, credentialRecord: record } + } + + /** + * Process an incoming {@link IssueCredentialMessage} + * + * @param messageContext The message context containing a credential acknowledgement message + * @returns credential record associated with the credential acknowledgement message + * + */ + public async processCredential( + messageContext: InboundMessageContext + ): Promise { + const { message: issueMessage, connection } = messageContext + + this.logger.debug(`Processing credential with id ${issueMessage.id}`) + + const credentialRecord = await this.getByThreadAndConnectionId(issueMessage.threadId, connection?.id) + + const requestCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1RequestCredentialMessage, + }) + const offerCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1OfferCredentialMessage, + }) + // Assert + credentialRecord.assertState(CredentialState.RequestSent) + + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: offerCredentialMessage ?? undefined, + previousSentMessage: requestCredentialMessage ?? undefined, + }) + + const credentialRequestMetadata = credentialRecord.metadata.get(CredentialMetadataKeys.IndyRequest) + + if (!credentialRequestMetadata) { + throw new CredentialProblemReportError( + `Missing required request metadata for credential with id ${credentialRecord.id}`, + { problemCode: CredentialProblemReportReason.IssuanceAbandoned } + ) + } + + const indyCredential = issueMessage.indyCredential + if (!indyCredential) { + throw new CredentialProblemReportError( + `Missing required base64 or json encoded attachment data for credential with thread id ${issueMessage.threadId}`, + { problemCode: CredentialProblemReportReason.IssuanceAbandoned } + ) + } + + // get the revocation registry and pass it to the process (store) credential method + const issueAttachment = issueMessage.getAttachmentById(INDY_CREDENTIAL_ATTACHMENT_ID) + if (!issueAttachment) { + throw new AriesFrameworkError('Missing credential attachment in processCredential') + } + const options: ServiceAcceptCredentialOptions = { + credentialAttachment: issueAttachment, + } + + await this.formatService.processCredential(options, credentialRecord) + + await this.updateState(credentialRecord, CredentialState.CredentialReceived) + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: issueMessage, + role: DidCommMessageRole.Receiver, + associatedRecordId: credentialRecord.id, + }) + return credentialRecord + } + /** + * Process a received {@link CredentialAckMessage}. + * + * @param messageContext The message context containing a credential acknowledgement message + * @returns credential record associated with the credential acknowledgement message + * + */ + + /** + * Create a {@link CredentialAckMessage} as response to a received credential. + * + * @param credentialRecord The credential record for which to create the credential acknowledgement + * @returns Object containing credential acknowledgement message and associated credential record + * + */ + public async createAck( + credentialRecord: CredentialExchangeRecord + ): Promise> { + credentialRecord.assertState(CredentialState.CredentialReceived) + + // Create message + const ackMessage = new V1CredentialAckMessage({ + status: AckStatus.OK, + threadId: credentialRecord.threadId, + }) + + await this.updateState(credentialRecord, CredentialState.Done) + + return { message: ackMessage, credentialRecord } + } + public async processAck( + messageContext: InboundMessageContext + ): Promise { + const { message: credentialAckMessage, connection } = messageContext + + this.logger.debug(`Processing credential ack with id ${credentialAckMessage.id}`) + + const credentialRecord = await this.getByThreadAndConnectionId(credentialAckMessage.threadId, connection?.id) + + const requestCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1RequestCredentialMessage, + }) + const issueCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1IssueCredentialMessage, + }) + // Assert + credentialRecord.assertState(CredentialState.CredentialIssued) + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: requestCredentialMessage ?? undefined, + previousSentMessage: issueCredentialMessage ?? undefined, + }) + + // Update record + await this.updateState(credentialRecord, CredentialState.Done) + + return credentialRecord + } + + public registerHandlers() { + this.dispatcher.registerHandler( + new V1ProposeCredentialHandler(this, this.agentConfig, this.didCommMessageRepository) + ) + this.dispatcher.registerHandler( + new V1OfferCredentialHandler( + this, + this.agentConfig, + this.mediationRecipientService, + this.didCommMessageRepository + ) + ) + this.dispatcher.registerHandler( + new V1RequestCredentialHandler(this, this.agentConfig, this.didCommMessageRepository) + ) + this.dispatcher.registerHandler(new V1IssueCredentialHandler(this, this.agentConfig, this.didCommMessageRepository)) + this.dispatcher.registerHandler(new V1CredentialAckHandler(this)) + this.dispatcher.registerHandler(new V1CredentialProblemReportHandler(this)) + + this.dispatcher.registerHandler(new V1RevocationNotificationHandler(this.revocationService)) + } + + /** + * + * Get the version of Issue Credentials according to AIP1.0 or AIP2.0 + * @returns the version of this credential service + */ + public getVersion(): CredentialProtocolVersion { + return CredentialProtocolVersion.V1 + } + + /** + * Negotiate a credential offer as holder (by sending a credential proposal message) to the connection + * associated with the credential record. + * + * @param credentialOptions configuration for the offer see {@link NegotiateProposalOptions} + * @param credentialRecord the credential exchange record for this proposal + * @returns Credential record associated with the credential offer and the corresponding new offer message + * + */ + + // AUTO RESPOND METHODS + public shouldAutoRespondToCredential( + credentialRecord: CredentialExchangeRecord, + credentialMessage: V1IssueCredentialMessage + ): boolean { + const formatService: CredentialFormatService = this.getFormatService() + + let credentialAttachment: Attachment | undefined + if (credentialMessage) { + credentialAttachment = credentialMessage.getAttachmentById(INDY_CREDENTIAL_ATTACHMENT_ID) + } + const handlerOptions: HandlerAutoAcceptOptions = { + credentialRecord, + autoAcceptType: this.agentConfig.autoAcceptCredentials, + credentialAttachment, + } + + const shouldAutoReturn = + this.agentConfig.autoAcceptCredentials === AutoAcceptCredential.Always || + credentialRecord.autoAcceptCredential === AutoAcceptCredential.Always || + formatService.shouldAutoRespondToCredential(handlerOptions) + + return shouldAutoReturn + } + + public async shouldAutoRespondToProposal(handlerOptions: HandlerAutoAcceptOptions): Promise { + const autoAccept = CredentialResponseCoordinator.composeAutoAccept( + handlerOptions.credentialRecord.autoAcceptCredential, + handlerOptions.autoAcceptType + ) + + if (autoAccept === AutoAcceptCredential.ContentApproved) { + return ( + this.areProposalValuesValid(handlerOptions.credentialRecord, handlerOptions.messageAttributes) && + this.areProposalAndOfferDefinitionIdEqual(handlerOptions.credentialDefinitionId, handlerOptions.offerAttachment) + ) + } + return false + } + private areProposalValuesValid( + credentialRecord: CredentialExchangeRecord, + proposeMessageAttributes?: CredentialPreviewAttribute[] + ) { + const { credentialAttributes } = credentialRecord + + if (proposeMessageAttributes && credentialAttributes) { + const proposeValues = CredentialUtils.convertAttributesToValues(proposeMessageAttributes) + const defaultValues = CredentialUtils.convertAttributesToValues(credentialAttributes) + if (CredentialUtils.checkValuesMatch(proposeValues, defaultValues)) { + return true + } + } + return false + } + private areProposalAndOfferDefinitionIdEqual(proposalCredentialDefinitionId?: string, offerAttachment?: Attachment) { + let credOffer: CredOffer | undefined + + if (offerAttachment) { + credOffer = offerAttachment.getDataAsJson() + } + const offerCredentialDefinitionId = credOffer?.cred_def_id + return proposalCredentialDefinitionId === offerCredentialDefinitionId + } + public shouldAutoRespondToRequest( + credentialRecord: CredentialExchangeRecord, + requestMessage: V1RequestCredentialMessage, + proposeMessage?: V1ProposeCredentialMessage, + offerMessage?: V1OfferCredentialMessage + ): boolean { + const formatService: CredentialFormatService = this.getFormatService() + + let proposalAttachment, offerAttachment, requestAttachment: Attachment | undefined + + if (offerMessage) { + offerAttachment = offerMessage.getAttachmentById(INDY_CREDENTIAL_OFFER_ATTACHMENT_ID) + } + if (requestMessage) { + requestAttachment = requestMessage.getAttachmentById(INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID) + } + const handlerOptions: HandlerAutoAcceptOptions = { + credentialRecord, + autoAcceptType: this.agentConfig.autoAcceptCredentials, + proposalAttachment, + offerAttachment, + requestAttachment, + } + const shouldAutoReturn = + this.agentConfig.autoAcceptCredentials === AutoAcceptCredential.Always || + credentialRecord.autoAcceptCredential === AutoAcceptCredential.Always || + formatService.shouldAutoRespondToRequest(handlerOptions) + + return shouldAutoReturn + } + + public shouldAutoRespondToOffer( + credentialRecord: CredentialExchangeRecord, + offerMessage: V1OfferCredentialMessage, + proposeMessage?: V1ProposeCredentialMessage + ): boolean { + const formatService: CredentialFormatService = this.getFormatService() + let proposalAttachment: Attachment | undefined + + const offerAttachment = offerMessage.getAttachmentById(INDY_CREDENTIAL_OFFER_ATTACHMENT_ID) + if (proposeMessage && proposeMessage.appendedAttachments) { + proposalAttachment = proposeMessage.getAttachment() + } + const offerValues = offerMessage.credentialPreview?.attributes + + const handlerOptions: HandlerAutoAcceptOptions = { + credentialRecord, + autoAcceptType: this.agentConfig.autoAcceptCredentials, + messageAttributes: offerValues, + proposalAttachment, + offerAttachment, + } + const shouldAutoReturn = + this.agentConfig.autoAcceptCredentials === AutoAcceptCredential.Always || + credentialRecord.autoAcceptCredential === AutoAcceptCredential.Always || + formatService.shouldAutoRespondToProposal(handlerOptions) + + return shouldAutoReturn + } + + // REPOSITORY METHODS + + public async getOfferMessage(id: string): Promise { + return await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: id, + messageClass: V1OfferCredentialMessage, + }) + } + + public async getRequestMessage(id: string): Promise { + return await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: id, + messageClass: V1RequestCredentialMessage, + }) + } + + public async getCredentialMessage(id: string): Promise { + return await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: id, + messageClass: V1IssueCredentialMessage, + }) + } + + public getFormats(): CredentialFormatService[] { + throw new Error('Method not implemented.') + } + + public getFormatService(): CredentialFormatService { + return this.formatService + } +} diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts new file mode 100644 index 0000000000..9bcd934ad9 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts @@ -0,0 +1,17 @@ +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { V1CredentialService } from '../V1CredentialService' + +import { V1CredentialAckMessage } from '../messages' + +export class V1CredentialAckHandler implements Handler { + private credentialService: V1CredentialService + public supportedMessages = [V1CredentialAckMessage] + + public constructor(credentialService: V1CredentialService) { + this.credentialService = credentialService + } + + public async handle(messageContext: HandlerInboundMessage) { + await this.credentialService.processAck(messageContext) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts new file mode 100644 index 0000000000..184be10163 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts @@ -0,0 +1,17 @@ +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { V1CredentialService } from '../V1CredentialService' + +import { V1CredentialProblemReportMessage } from '../messages' + +export class V1CredentialProblemReportHandler implements Handler { + private credentialService: V1CredentialService + public supportedMessages = [V1CredentialProblemReportMessage] + + public constructor(credentialService: V1CredentialService) { + this.credentialService = credentialService + } + + public async handle(messageContext: HandlerInboundMessage) { + await this.credentialService.processProblemReport(messageContext) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts new file mode 100644 index 0000000000..c8dc066f30 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts @@ -0,0 +1,70 @@ +import type { AgentConfig } from '../../../../../agent/AgentConfig' +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { DidCommMessageRepository } from '../../../../../storage' +import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import type { V1CredentialService } from '../V1CredentialService' + +import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' +import { V1IssueCredentialMessage, V1RequestCredentialMessage } from '../messages' + +export class V1IssueCredentialHandler implements Handler { + private credentialService: V1CredentialService + private agentConfig: AgentConfig + private didCommMessageRepository: DidCommMessageRepository + public supportedMessages = [V1IssueCredentialMessage] + + public constructor( + credentialService: V1CredentialService, + agentConfig: AgentConfig, + didCommMessageRepository: DidCommMessageRepository + ) { + this.credentialService = credentialService + this.agentConfig = agentConfig + this.didCommMessageRepository = didCommMessageRepository + } + + public async handle(messageContext: HandlerInboundMessage) { + const credentialRecord = await this.credentialService.processCredential(messageContext) + const credentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1IssueCredentialMessage, + }) + if (!credentialMessage) { + throw new AriesFrameworkError('Missing credential message in V2RequestCredentialHandler') + } + if (this.credentialService.shouldAutoRespondToCredential(credentialRecord, credentialMessage)) { + return await this.createAck(credentialRecord, credentialMessage, messageContext) + } + } + + private async createAck( + record: CredentialExchangeRecord, + credentialMessage: V1IssueCredentialMessage | null, + messageContext: HandlerInboundMessage + ) { + this.agentConfig.logger.info( + `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + ) + const { message, credentialRecord } = await this.credentialService.createAck(record) + + const requestMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1RequestCredentialMessage, + }) + if (messageContext.connection) { + return createOutboundMessage(messageContext.connection, message) + } else if (credentialMessage?.service && requestMessage?.service) { + const recipientService = credentialMessage.service + const ourService = requestMessage.service + + return createOutboundServiceMessage({ + payload: message, + service: recipientService.toDidCommService(), + senderKey: ourService.recipientKeys[0], + }) + } + + this.agentConfig.logger.error(`Could not automatically create credential ack`) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts new file mode 100644 index 0000000000..ce9c9ab37d --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts @@ -0,0 +1,111 @@ +import type { AgentConfig } from '../../../../../agent/AgentConfig' +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { DidCommMessageRepository } from '../../../../../storage' +import type { MediationRecipientService } from '../../../../routing/services/MediationRecipientService' +import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import type { V1CredentialService } from '../V1CredentialService' + +import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' +import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' +import { DidCommMessageRole } from '../../../../../storage' +import { V1OfferCredentialMessage, V1ProposeCredentialMessage } from '../messages' + +export class V1OfferCredentialHandler implements Handler { + private credentialService: V1CredentialService + private agentConfig: AgentConfig + private mediationRecipientService: MediationRecipientService + private didCommMessageRepository: DidCommMessageRepository + public supportedMessages = [V1OfferCredentialMessage] + + public constructor( + credentialService: V1CredentialService, + agentConfig: AgentConfig, + mediationRecipientService: MediationRecipientService, + didCommMessageRepository: DidCommMessageRepository + ) { + this.credentialService = credentialService + this.agentConfig = agentConfig + this.mediationRecipientService = mediationRecipientService + this.didCommMessageRepository = didCommMessageRepository + } + + public async handle(messageContext: HandlerInboundMessage) { + const credentialRecord = await this.credentialService.processOffer(messageContext) + + const offerMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1OfferCredentialMessage, + }) + if (!offerMessage) { + throw new AriesFrameworkError('Missing offerMessage in V1OfferCredentialHandler') + } + const proposeMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1ProposeCredentialMessage, + }) + + const shouldAutoRespond = this.credentialService.shouldAutoRespondToOffer( + credentialRecord, + offerMessage, + proposeMessage ?? undefined + ) + if (shouldAutoRespond) { + return await this.createRequest(credentialRecord, messageContext, offerMessage) + } + } + + private async createRequest( + record: CredentialExchangeRecord, + messageContext: HandlerInboundMessage, + offerMessage?: V1OfferCredentialMessage + ) { + this.agentConfig.logger.info( + `Automatically sending request with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + ) + if (messageContext.connection) { + const { message, credentialRecord } = await this.credentialService.createRequest( + record, + {}, + messageContext.connection.did + ) + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + + return createOutboundMessage(messageContext.connection, message) + } else if (offerMessage?.service) { + const routing = await this.mediationRecipientService.getRouting() + const ourService = new ServiceDecorator({ + serviceEndpoint: routing.endpoints[0], + recipientKeys: [routing.verkey], + routingKeys: routing.routingKeys, + }) + const recipientService = offerMessage.service + + const { message, credentialRecord } = await this.credentialService.createRequest( + record, + {}, + ourService.recipientKeys[0] + ) + + // Set and save ~service decorator to record (to remember our verkey) + message.service = ourService + await this.credentialService.update(credentialRecord) + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + return createOutboundServiceMessage({ + payload: message, + service: recipientService.toDidCommService(), + senderKey: ourService.recipientKeys[0], + }) + } + + this.agentConfig.logger.error(`Could not automatically create credential request`) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts new file mode 100644 index 0000000000..3ecc0763f2 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts @@ -0,0 +1,105 @@ +import type { AgentConfig } from '../../../../../agent/AgentConfig' +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { Attachment } from '../../../../../decorators/attachment/Attachment' +import type { DidCommMessageRepository } from '../../../../../storage' +import type { HandlerAutoAcceptOptions } from '../../../formats/models/CredentialFormatServiceOptions' +import type { CredentialPreviewAttribute } from '../../../models/CredentialPreviewAttributes' +import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import type { V1CredentialService } from '../V1CredentialService' + +import { createOutboundMessage } from '../../../../../agent/helpers' +import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' +import { AutoAcceptCredential } from '../../../CredentialAutoAcceptType' +import { V1OfferCredentialMessage, V1ProposeCredentialMessage } from '../messages' + +export class V1ProposeCredentialHandler implements Handler { + private credentialService: V1CredentialService + private agentConfig: AgentConfig + private didCommMessageRepository: DidCommMessageRepository + public supportedMessages = [V1ProposeCredentialMessage] + + public constructor( + credentialService: V1CredentialService, + agentConfig: AgentConfig, + didCommMessageRepository: DidCommMessageRepository + ) { + this.credentialService = credentialService + this.agentConfig = agentConfig + this.didCommMessageRepository = didCommMessageRepository + } + + public async handle(messageContext: HandlerInboundMessage) { + const credentialRecord = await this.credentialService.processProposal(messageContext) + + // note that these two messages can be present (or not) and there is no + // guarantee which one is present so we need two try-catch blocks + const proposalMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1ProposeCredentialMessage, + }) + + const offerMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1OfferCredentialMessage, + }) + + let proposalValues: CredentialPreviewAttribute[] | undefined + + if (!proposalMessage || !proposalMessage.credentialProposal || !proposalMessage.credentialProposal.attributes) { + throw new AriesFrameworkError('Missing attributes in proposal message') + } + let proposalAttachment, offerAttachment: Attachment | undefined + if (proposalMessage) { + proposalValues = proposalMessage.credentialProposal.attributes + } + if (offerMessage) { + offerAttachment = offerMessage.getAttachmentById('indy') + } + const handlerOptions: HandlerAutoAcceptOptions = { + credentialRecord, + autoAcceptType: this.agentConfig.autoAcceptCredentials, + messageAttributes: proposalValues, + proposalAttachment, + offerAttachment, + credentialDefinitionId: proposalMessage.credentialDefinitionId, + } + if ( + this.agentConfig.autoAcceptCredentials === AutoAcceptCredential.Always || + credentialRecord.autoAcceptCredential === AutoAcceptCredential.Always || + (await this.credentialService.shouldAutoRespondToProposal(handlerOptions)) + ) { + return await this.createOffer(credentialRecord, messageContext, proposalMessage) + } + } + private async createOffer( + credentialRecord: CredentialExchangeRecord, + messageContext: HandlerInboundMessage, + proposalMessage?: V1ProposeCredentialMessage + ) { + this.agentConfig.logger.info( + `Automatically sending offer with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + ) + + if (!messageContext.connection) { + this.agentConfig.logger.error('No connection on the messageContext, aborting auto accept') + return + } + if (!proposalMessage?.credentialProposal) { + this.agentConfig.logger.error( + `Proposal message with id ${credentialRecord.id} is missing required credential proposal` + ) + return + } + + if (!proposalMessage.credentialDefinitionId) { + this.agentConfig.logger.error('Missing required credential definition id') + return + } + + const { message } = await this.credentialService.createOfferAsResponse(credentialRecord, { + credentialDefinitionId: proposalMessage.credentialDefinitionId, + preview: proposalMessage.credentialProposal, + }) + return createOutboundMessage(messageContext.connection, message) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts new file mode 100644 index 0000000000..7d55fded8e --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts @@ -0,0 +1,130 @@ +import type { AgentConfig } from '../../../../../agent/AgentConfig' +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { Attachment } from '../../../../../decorators/attachment/Attachment' +import type { DidCommMessageRepository } from '../../../../../storage' +import type { ServiceAcceptRequestOptions } from '../../../CredentialServiceOptions' +import type { CredentialFormatService } from '../../../formats/CredentialFormatService' +import type { HandlerAutoAcceptOptions } from '../../../formats/models/CredentialFormatServiceOptions' +import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import type { V1CredentialService } from '../V1CredentialService' + +import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { DidCommMessageRole } from '../../../../../storage' +import { AutoAcceptCredential } from '../../../CredentialAutoAcceptType' +import { + INDY_CREDENTIAL_ATTACHMENT_ID, + V1ProposeCredentialMessage, + V1RequestCredentialMessage, + V1OfferCredentialMessage, + INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, +} from '../messages' + +export class V1RequestCredentialHandler implements Handler { + private agentConfig: AgentConfig + private credentialService: V1CredentialService + private didCommMessageRepository: DidCommMessageRepository + public supportedMessages = [V1RequestCredentialMessage] + + public constructor( + credentialService: V1CredentialService, + agentConfig: AgentConfig, + didCommMessageRepository: DidCommMessageRepository + ) { + this.credentialService = credentialService + this.agentConfig = agentConfig + this.didCommMessageRepository = didCommMessageRepository + } + + public async handle(messageContext: HandlerInboundMessage) { + const credentialRecord = await this.credentialService.processRequest(messageContext) + + const requestMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1RequestCredentialMessage, + }) + + const offerMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1OfferCredentialMessage, + }) + + const proposeMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V1ProposeCredentialMessage, + }) + + const formatService: CredentialFormatService = this.credentialService.getFormatService() + + let proposalAttachment, offerAttachment, requestAttachment: Attachment | undefined + if (proposeMessage && proposeMessage.appendedAttachments) { + proposalAttachment = proposeMessage.appendedAttachments[0] + } + if (offerMessage) { + offerAttachment = offerMessage.getAttachmentById(INDY_CREDENTIAL_OFFER_ATTACHMENT_ID) + } + if (requestMessage) { + requestAttachment = requestMessage.getAttachmentById(INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID) + } + const handlerOptions: HandlerAutoAcceptOptions = { + credentialRecord, + autoAcceptType: this.agentConfig.autoAcceptCredentials, + proposalAttachment, + offerAttachment, + requestAttachment, + } + if ( + this.agentConfig.autoAcceptCredentials === AutoAcceptCredential.Always || + credentialRecord.autoAcceptCredential === AutoAcceptCredential.Always || + formatService.shouldAutoRespondToRequest(handlerOptions) + ) { + return await this.createCredential(credentialRecord, messageContext, offerMessage, requestMessage) + } + } + + private async createCredential( + record: CredentialExchangeRecord, + messageContext: HandlerInboundMessage, + offerMessage?: V1OfferCredentialMessage | null, + requestMessage?: V1RequestCredentialMessage | null + ) { + this.agentConfig.logger.info( + `Automatically sending credential with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + ) + + const options: ServiceAcceptRequestOptions = { + attachId: INDY_CREDENTIAL_ATTACHMENT_ID, + credentialRecordId: record.id, + comment: 'V1 Indy Credential', + } + const { message, credentialRecord } = await this.credentialService.createCredential(record, options) + + if (messageContext.connection) { + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + return createOutboundMessage(messageContext.connection, message) + } else if (requestMessage?.service && offerMessage?.service) { + const recipientService = requestMessage.service + const ourService = offerMessage.service + + // Set ~service, update message in record (for later use) + message.setService(ourService) + + await this.credentialService.update(credentialRecord) + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + return createOutboundServiceMessage({ + payload: message, + service: recipientService.toDidCommService(), + senderKey: ourService.recipientKeys[0], + }) + } + this.agentConfig.logger.error(`Could not automatically create credential request`) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RevocationNotificationHandler.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/V1RevocationNotificationHandler.ts new file mode 100644 index 0000000000..263ccf4976 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/V1RevocationNotificationHandler.ts @@ -0,0 +1,17 @@ +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { RevocationService } from '../../../services' + +import { V1RevocationNotificationMessage } from '../messages/V1RevocationNotificationMessage' + +export class V1RevocationNotificationHandler implements Handler { + private revocationService: RevocationService + public supportedMessages = [V1RevocationNotificationMessage] + + public constructor(revocationService: RevocationService) { + this.revocationService = revocationService + } + + public async handle(messageContext: HandlerInboundMessage) { + await this.revocationService.v1ProcessRevocationNotification(messageContext) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/index.ts b/packages/core/src/modules/credentials/protocol/v1/handlers/index.ts new file mode 100644 index 0000000000..bd6a99e42c --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v1/handlers/index.ts @@ -0,0 +1,7 @@ +export * from './V1CredentialAckHandler' +export * from './V1IssueCredentialHandler' +export * from './V1OfferCredentialHandler' +export * from './V1ProposeCredentialHandler' +export * from './V1RequestCredentialHandler' +export * from './V1CredentialProblemReportHandler' +export * from './V1RevocationNotificationHandler' diff --git a/packages/core/src/modules/credentials/messages/CredentialAckMessage.ts b/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialAckMessage.ts similarity index 64% rename from packages/core/src/modules/credentials/messages/CredentialAckMessage.ts rename to packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialAckMessage.ts index 1011addde9..40688a1e29 100644 --- a/packages/core/src/modules/credentials/messages/CredentialAckMessage.ts +++ b/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialAckMessage.ts @@ -1,15 +1,15 @@ -import type { AckMessageOptions } from '../../common' +import type { AckMessageOptions } from '../../../../common' import { Equals } from 'class-validator' -import { AckMessage } from '../../common' +import { AckMessage } from '../../../../common' export type CredentialAckMessageOptions = AckMessageOptions /** * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0015-acks/README.md#explicit-acks */ -export class CredentialAckMessage extends AckMessage { +export class V1CredentialAckMessage extends AckMessage { /** * Create new CredentialAckMessage instance. * @param options @@ -18,7 +18,7 @@ export class CredentialAckMessage extends AckMessage { super(options) } - @Equals(CredentialAckMessage.type) - public readonly type = CredentialAckMessage.type + @Equals(V1CredentialAckMessage.type) + public readonly type = V1CredentialAckMessage.type public static readonly type = 'https://didcomm.org/issue-credential/1.0/ack' } diff --git a/packages/core/src/modules/credentials/messages/CredentialProblemReportMessage.ts b/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialProblemReportMessage.ts similarity index 56% rename from packages/core/src/modules/credentials/messages/CredentialProblemReportMessage.ts rename to packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialProblemReportMessage.ts index 2ceec3d788..ac5ae348c3 100644 --- a/packages/core/src/modules/credentials/messages/CredentialProblemReportMessage.ts +++ b/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialProblemReportMessage.ts @@ -1,15 +1,15 @@ -import type { ProblemReportMessageOptions } from '../../problem-reports/messages/ProblemReportMessage' +import type { ProblemReportMessageOptions } from '../../../../problem-reports/messages/ProblemReportMessage' import { Equals } from 'class-validator' -import { ProblemReportMessage } from '../../problem-reports/messages/ProblemReportMessage' +import { ProblemReportMessage } from '../../../../problem-reports/messages/ProblemReportMessage' export type CredentialProblemReportMessageOptions = ProblemReportMessageOptions /** * @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0035-report-problem/README.md */ -export class CredentialProblemReportMessage extends ProblemReportMessage { +export class V1CredentialProblemReportMessage extends ProblemReportMessage { /** * Create new CredentialProblemReportMessage instance. * @param options @@ -18,7 +18,7 @@ export class CredentialProblemReportMessage extends ProblemReportMessage { super(options) } - @Equals(CredentialProblemReportMessage.type) - public readonly type = CredentialProblemReportMessage.type + @Equals(V1CredentialProblemReportMessage.type) + public readonly type = V1CredentialProblemReportMessage.type public static readonly type = 'https://didcomm.org/issue-credential/1.0/problem-report' } diff --git a/packages/core/src/modules/credentials/messages/IssueCredentialMessage.ts b/packages/core/src/modules/credentials/protocol/v1/messages/V1IssueCredentialMessage.ts similarity index 72% rename from packages/core/src/modules/credentials/messages/IssueCredentialMessage.ts rename to packages/core/src/modules/credentials/protocol/v1/messages/V1IssueCredentialMessage.ts index f24a0b7625..f4310957a0 100644 --- a/packages/core/src/modules/credentials/messages/IssueCredentialMessage.ts +++ b/packages/core/src/modules/credentials/protocol/v1/messages/V1IssueCredentialMessage.ts @@ -3,8 +3,8 @@ import type { Cred } from 'indy-sdk' import { Expose, Type } from 'class-transformer' import { Equals, IsArray, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' -import { AgentMessage } from '../../../agent/AgentMessage' -import { Attachment } from '../../../decorators/attachment/Attachment' +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { Attachment } from '../../../../../decorators/attachment/Attachment' export const INDY_CREDENTIAL_ATTACHMENT_ID = 'libindy-cred-0' @@ -15,7 +15,7 @@ interface IssueCredentialMessageOptions { attachments?: Attachment[] } -export class IssueCredentialMessage extends AgentMessage { +export class V1IssueCredentialMessage extends AgentMessage { public constructor(options: IssueCredentialMessageOptions) { super() @@ -23,12 +23,12 @@ export class IssueCredentialMessage extends AgentMessage { this.id = options.id ?? this.generateId() this.comment = options.comment this.credentialAttachments = options.credentialAttachments - this.attachments = options.attachments + this.appendedAttachments = options.attachments } } - @Equals(IssueCredentialMessage.type) - public readonly type = IssueCredentialMessage.type + @Equals(V1IssueCredentialMessage.type) + public readonly type = V1IssueCredentialMessage.type public static readonly type = 'https://didcomm.org/issue-credential/1.0/issue-credential' @IsString() @@ -52,4 +52,8 @@ export class IssueCredentialMessage extends AgentMessage { return credentialJson } + + public getAttachmentById(id: string): Attachment | undefined { + return this.credentialAttachments?.find((attachment) => attachment.id == id) + } } diff --git a/packages/core/src/modules/credentials/messages/OfferCredentialMessage.ts b/packages/core/src/modules/credentials/protocol/v1/messages/V1OfferCredentialMessage.ts similarity index 61% rename from packages/core/src/modules/credentials/messages/OfferCredentialMessage.ts rename to packages/core/src/modules/credentials/protocol/v1/messages/V1OfferCredentialMessage.ts index cb3e5e05aa..fb1d5c9c78 100644 --- a/packages/core/src/modules/credentials/messages/OfferCredentialMessage.ts +++ b/packages/core/src/modules/credentials/protocol/v1/messages/V1OfferCredentialMessage.ts @@ -3,10 +3,9 @@ import type { CredOffer } from 'indy-sdk' import { Expose, Type } from 'class-transformer' import { Equals, IsArray, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' -import { AgentMessage } from '../../../agent/AgentMessage' -import { Attachment } from '../../../decorators/attachment/Attachment' - -import { CredentialPreview } from './CredentialPreview' +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { Attachment } from '../../../../../decorators/attachment/Attachment' +import { V1CredentialPreview } from '../V1CredentialPreview' export const INDY_CREDENTIAL_OFFER_ATTACHMENT_ID = 'libindy-cred-offer-0' @@ -14,7 +13,7 @@ export interface OfferCredentialMessageOptions { id?: string comment?: string offerAttachments: Attachment[] - credentialPreview: CredentialPreview + credentialPreview: V1CredentialPreview attachments?: Attachment[] } @@ -23,7 +22,7 @@ export interface OfferCredentialMessageOptions { * * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0036-issue-credential/README.md#offer-credential */ -export class OfferCredentialMessage extends AgentMessage { +export class V1OfferCredentialMessage extends AgentMessage { public constructor(options: OfferCredentialMessageOptions) { super() @@ -31,13 +30,13 @@ export class OfferCredentialMessage extends AgentMessage { this.id = options.id || this.generateId() this.comment = options.comment this.credentialPreview = options.credentialPreview - this.offerAttachments = options.offerAttachments - this.attachments = options.attachments + this.messageAttachment = options.offerAttachments + this.appendedAttachments = options.attachments } } - @Equals(OfferCredentialMessage.type) - public readonly type = OfferCredentialMessage.type + @Equals(V1OfferCredentialMessage.type) + public readonly type = V1OfferCredentialMessage.type public static readonly type = 'https://didcomm.org/issue-credential/1.0/offer-credential' @IsString() @@ -45,10 +44,10 @@ export class OfferCredentialMessage extends AgentMessage { public comment?: string @Expose({ name: 'credential_preview' }) - @Type(() => CredentialPreview) + @Type(() => V1CredentialPreview) @ValidateNested() - @IsInstance(CredentialPreview) - public credentialPreview!: CredentialPreview + @IsInstance(V1CredentialPreview) + public credentialPreview!: V1CredentialPreview @Expose({ name: 'offers~attach' }) @Type(() => Attachment) @@ -57,14 +56,20 @@ export class OfferCredentialMessage extends AgentMessage { each: true, }) @IsInstance(Attachment, { each: true }) - public offerAttachments!: Attachment[] + public messageAttachment!: Attachment[] public get indyCredentialOffer(): CredOffer | null { - const attachment = this.offerAttachments.find((attachment) => attachment.id === INDY_CREDENTIAL_OFFER_ATTACHMENT_ID) + const attachment = this.messageAttachment.find( + (attachment) => attachment.id === INDY_CREDENTIAL_OFFER_ATTACHMENT_ID + ) // Extract credential offer from attachment const credentialOfferJson = attachment?.getDataAsJson() ?? null return credentialOfferJson } + + public getAttachmentById(id: string): Attachment | undefined { + return this.messageAttachment?.find((attachment) => attachment.id == id) + } } diff --git a/packages/core/src/modules/credentials/messages/ProposeCredentialMessage.ts b/packages/core/src/modules/credentials/protocol/v1/messages/V1ProposeCredentialMessage.ts similarity index 78% rename from packages/core/src/modules/credentials/messages/ProposeCredentialMessage.ts rename to packages/core/src/modules/credentials/protocol/v1/messages/V1ProposeCredentialMessage.ts index 2ecacf5851..5665ef55fb 100644 --- a/packages/core/src/modules/credentials/messages/ProposeCredentialMessage.ts +++ b/packages/core/src/modules/credentials/protocol/v1/messages/V1ProposeCredentialMessage.ts @@ -1,17 +1,16 @@ -import type { Attachment } from '../../../decorators/attachment/Attachment' +import type { Attachment } from '../../../../../decorators/attachment/Attachment' import { Expose, Type } from 'class-transformer' import { Equals, IsInstance, IsOptional, IsString, Matches, ValidateNested } from 'class-validator' -import { AgentMessage } from '../../../agent/AgentMessage' -import { credDefIdRegex, indyDidRegex, schemaIdRegex, schemaVersionRegex } from '../../../utils' - -import { CredentialPreview } from './CredentialPreview' +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { credDefIdRegex, indyDidRegex, schemaIdRegex, schemaVersionRegex } from '../../../../../utils' +import { V1CredentialPreview } from '../V1CredentialPreview' export interface ProposeCredentialMessageOptions { id?: string comment?: string - credentialProposal?: CredentialPreview + credentialProposal?: V1CredentialPreview schemaIssuerDid?: string schemaId?: string schemaName?: string @@ -26,7 +25,7 @@ export interface ProposeCredentialMessageOptions { * * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0036-issue-credential/README.md#propose-credential */ -export class ProposeCredentialMessage extends AgentMessage { +export class V1ProposeCredentialMessage extends AgentMessage { public constructor(options: ProposeCredentialMessageOptions) { super() @@ -40,12 +39,12 @@ export class ProposeCredentialMessage extends AgentMessage { this.schemaVersion = options.schemaVersion this.credentialDefinitionId = options.credentialDefinitionId this.issuerDid = options.issuerDid - this.attachments = options.attachments + this.appendedAttachments = options.attachments } } - @Equals(ProposeCredentialMessage.type) - public readonly type = ProposeCredentialMessage.type + @Equals(V1ProposeCredentialMessage.type) + public readonly type = V1ProposeCredentialMessage.type public static readonly type = 'https://didcomm.org/issue-credential/1.0/propose-credential' /** @@ -60,11 +59,11 @@ export class ProposeCredentialMessage extends AgentMessage { * Represents the credential data that Prover wants to receive. */ @Expose({ name: 'credential_proposal' }) - @Type(() => CredentialPreview) + @Type(() => V1CredentialPreview) @ValidateNested() @IsOptional() - @IsInstance(CredentialPreview) - public credentialProposal?: CredentialPreview + @IsInstance(V1CredentialPreview) + public credentialProposal?: V1CredentialPreview /** * Filter to request credential based on a particular Schema issuer DID. @@ -120,4 +119,12 @@ export class ProposeCredentialMessage extends AgentMessage { @IsOptional() @Matches(indyDidRegex) public issuerDid?: string + + public getAttachment(): Attachment | undefined { + if (this.appendedAttachments) { + return this.appendedAttachments[0] + } else { + return undefined + } + } } diff --git a/packages/core/src/modules/credentials/messages/RequestCredentialMessage.ts b/packages/core/src/modules/credentials/protocol/v1/messages/V1RequestCredentialMessage.ts similarity index 64% rename from packages/core/src/modules/credentials/messages/RequestCredentialMessage.ts rename to packages/core/src/modules/credentials/protocol/v1/messages/V1RequestCredentialMessage.ts index 57373f010f..2fab841920 100644 --- a/packages/core/src/modules/credentials/messages/RequestCredentialMessage.ts +++ b/packages/core/src/modules/credentials/protocol/v1/messages/V1RequestCredentialMessage.ts @@ -3,8 +3,8 @@ import type { CredReq } from 'indy-sdk' import { Expose, Type } from 'class-transformer' import { Equals, IsArray, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' -import { AgentMessage } from '../../../agent/AgentMessage' -import { Attachment } from '../../../decorators/attachment/Attachment' +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { Attachment } from '../../../../../decorators/attachment/Attachment' export const INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID = 'libindy-cred-request-0' @@ -15,20 +15,20 @@ interface RequestCredentialMessageOptions { attachments?: Attachment[] } -export class RequestCredentialMessage extends AgentMessage { +export class V1RequestCredentialMessage extends AgentMessage { public constructor(options: RequestCredentialMessageOptions) { super() if (options) { this.id = options.id || this.generateId() this.comment = options.comment - this.requestAttachments = options.requestAttachments - this.attachments = options.attachments + this.messageAttachment = options.requestAttachments + this.appendedAttachments = options.attachments } } - @Equals(RequestCredentialMessage.type) - public readonly type = RequestCredentialMessage.type + @Equals(V1RequestCredentialMessage.type) + public readonly type = V1RequestCredentialMessage.type public static readonly type = 'https://didcomm.org/issue-credential/1.0/request-credential' @IsString() @@ -42,10 +42,10 @@ export class RequestCredentialMessage extends AgentMessage { each: true, }) @IsInstance(Attachment, { each: true }) - public requestAttachments!: Attachment[] + public messageAttachment!: Attachment[] public get indyCredentialRequest(): CredReq | null { - const attachment = this.requestAttachments.find( + const attachment = this.messageAttachment.find( (attachment) => attachment.id === INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID ) // Extract proof request from attachment @@ -53,4 +53,8 @@ export class RequestCredentialMessage extends AgentMessage { return credentialReqJson } + + public getAttachmentById(id: string): Attachment | undefined { + return this.messageAttachment?.find((attachment) => attachment.id === id) + } } diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1RevocationNotificationMessage.ts b/packages/core/src/modules/credentials/protocol/v1/messages/V1RevocationNotificationMessage.ts new file mode 100644 index 0000000000..6481f83f40 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v1/messages/V1RevocationNotificationMessage.ts @@ -0,0 +1,37 @@ +import type { AckDecorator } from '../../../../../decorators/ack/AckDecorator' + +import { Expose } from 'class-transformer' +import { Equals, IsOptional, IsString } from 'class-validator' + +import { AgentMessage } from '../../../../../agent/AgentMessage' + +export interface RevocationNotificationMessageV1Options { + issueThread: string + id?: string + comment?: string + pleaseAck?: AckDecorator +} + +export class V1RevocationNotificationMessage extends AgentMessage { + public constructor(options: RevocationNotificationMessageV1Options) { + super() + if (options) { + this.issueThread = options.issueThread + this.id = options.id ?? this.generateId() + this.comment = options.comment + this.pleaseAck = options.pleaseAck + } + } + + @Equals(V1RevocationNotificationMessage.type) + public readonly type = V1RevocationNotificationMessage.type + public static readonly type = 'https://didcomm.org/revocation_notification/1.0/revoke' + + @IsString() + @IsOptional() + public comment?: string + + @Expose({ name: 'thread_id' }) + @IsString() + public issueThread!: string +} diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/index.ts b/packages/core/src/modules/credentials/protocol/v1/messages/index.ts new file mode 100644 index 0000000000..4b39feb0b1 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v1/messages/index.ts @@ -0,0 +1,7 @@ +export * from './V1CredentialAckMessage' +export * from '../V1CredentialPreview' +export * from './V1RequestCredentialMessage' +export * from './V1IssueCredentialMessage' +export * from './V1OfferCredentialMessage' +export * from './V1ProposeCredentialMessage' +export * from './V1CredentialProblemReportMessage' diff --git a/packages/core/src/modules/credentials/models/Credential.ts b/packages/core/src/modules/credentials/protocol/v1/models/Credential.ts similarity index 92% rename from packages/core/src/modules/credentials/models/Credential.ts rename to packages/core/src/modules/credentials/protocol/v1/models/Credential.ts index 75dbc9ff87..7b0046d7bb 100644 --- a/packages/core/src/modules/credentials/models/Credential.ts +++ b/packages/core/src/modules/credentials/protocol/v1/models/Credential.ts @@ -3,7 +3,7 @@ import type { IndyCredential } from 'indy-sdk' import { Expose, Type } from 'class-transformer' import { IsInstance, IsOptional, ValidateNested } from 'class-validator' -import { JsonTransformer } from '../../../utils/JsonTransformer' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' import { IndyCredentialInfo } from './IndyCredentialInfo' import { RevocationInterval } from './RevocationInterval' diff --git a/packages/core/src/modules/credentials/models/CredentialInfo.ts b/packages/core/src/modules/credentials/protocol/v1/models/CredentialInfo.ts similarity index 87% rename from packages/core/src/modules/credentials/models/CredentialInfo.ts rename to packages/core/src/modules/credentials/protocol/v1/models/CredentialInfo.ts index 98c69f1836..b0e40666e7 100644 --- a/packages/core/src/modules/credentials/models/CredentialInfo.ts +++ b/packages/core/src/modules/credentials/protocol/v1/models/CredentialInfo.ts @@ -1,4 +1,4 @@ -import type { Attachment } from '../../../decorators/attachment/Attachment' +import type { Attachment } from '../../../../../decorators/attachment/Attachment' export interface CredentialInfoOptions { metadata?: IndyCredentialMetadata | null diff --git a/packages/core/src/modules/credentials/models/IndyCredentialInfo.ts b/packages/core/src/modules/credentials/protocol/v1/models/IndyCredentialInfo.ts similarity index 83% rename from packages/core/src/modules/credentials/models/IndyCredentialInfo.ts rename to packages/core/src/modules/credentials/protocol/v1/models/IndyCredentialInfo.ts index 72ee614879..cf8b594ebe 100644 --- a/packages/core/src/modules/credentials/models/IndyCredentialInfo.ts +++ b/packages/core/src/modules/credentials/protocol/v1/models/IndyCredentialInfo.ts @@ -1,10 +1,9 @@ import type { IndyCredentialInfo as IndySDKCredentialInfo } from 'indy-sdk' import { Expose } from 'class-transformer' -import { IsOptional, IsString, Matches } from 'class-validator' +import { IsOptional, IsString } from 'class-validator' -import { credDefIdRegex, schemaIdRegex } from '../../../utils' -import { JsonTransformer } from '../../../utils/JsonTransformer' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' export class IndyCredentialInfo { public constructor(options: IndyCredentialInfo) { @@ -30,12 +29,10 @@ export class IndyCredentialInfo { @Expose({ name: 'schema_id' }) @IsString() - @Matches(schemaIdRegex) public schemaId!: string @Expose({ name: 'cred_def_id' }) @IsString() - @Matches(credDefIdRegex) public credentialDefinitionId!: string @Expose({ name: 'rev_reg_id' }) diff --git a/packages/core/src/modules/credentials/models/RevocationInterval.ts b/packages/core/src/modules/credentials/protocol/v1/models/RevocationInterval.ts similarity index 100% rename from packages/core/src/modules/credentials/models/RevocationInterval.ts rename to packages/core/src/modules/credentials/protocol/v1/models/RevocationInterval.ts diff --git a/packages/core/src/modules/credentials/protocol/v1/models/index.ts b/packages/core/src/modules/credentials/protocol/v1/models/index.ts new file mode 100644 index 0000000000..cae218929d --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v1/models/index.ts @@ -0,0 +1,3 @@ +export * from './Credential' +export * from './IndyCredentialInfo' +export * from './RevocationInterval' diff --git a/packages/core/src/modules/credentials/protocol/v2/CredentialMessageBuilder.ts b/packages/core/src/modules/credentials/protocol/v2/CredentialMessageBuilder.ts new file mode 100644 index 0000000000..8f0d72128b --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/CredentialMessageBuilder.ts @@ -0,0 +1,350 @@ +import type { Attachment } from '../../../../decorators/attachment/Attachment' +import type { + CredentialProtocolMsgReturnType, + ServiceAcceptRequestOptions, + ServiceOfferCredentialOptions, + ServiceRequestCredentialOptions, +} from '../../CredentialServiceOptions' +import type { ProposeCredentialOptions } from '../../CredentialsModuleOptions' +import type { CredentialFormatService } from '../../formats/CredentialFormatService' +import type { CredentialFormatSpec } from '../../formats/models/CredentialFormatServiceOptions' +import type { CredentialExchangeRecordProps } from '../../repository/CredentialExchangeRecord' +import type { V2IssueCredentialMessageProps } from './messages/V2IssueCredentialMessage' +import type { V2OfferCredentialMessageOptions } from './messages/V2OfferCredentialMessage' +import type { V2ProposeCredentialMessageProps } from './messages/V2ProposeCredentialMessage' +import type { V2RequestCredentialMessageOptions } from './messages/V2RequestCredentialMessage' + +import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' +import { uuid } from '../../../../utils/uuid' +import { CredentialProtocolVersion } from '../../CredentialProtocolVersion' +import { CredentialState } from '../../CredentialState' +import { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' + +import { V2CredentialPreview } from './V2CredentialPreview' +import { V2IssueCredentialMessage } from './messages/V2IssueCredentialMessage' +import { V2OfferCredentialMessage } from './messages/V2OfferCredentialMessage' +import { V2ProposeCredentialMessage } from './messages/V2ProposeCredentialMessage' +import { V2RequestCredentialMessage } from './messages/V2RequestCredentialMessage' + +export interface CreateRequestOptions { + formatServices: CredentialFormatService[] + record: CredentialExchangeRecord + requestOptions: ServiceRequestCredentialOptions + offerMessage: V2OfferCredentialMessage + holderDid?: string +} + +export class CredentialMessageBuilder { + /** + * Create a v2 credential proposal message according to the logic contained in the format service. The format services + * contain specific logic related to indy, jsonld etc. with others to come. + * + * @param formats {@link CredentialFormatService} array of format service objects each containing format-specific logic + * @param proposal {@link ProposeCredentialOptions} object containing (optionally) the linked attachments + * @param _threadId optional thread id for this message service + * @return a version 2.0 credential propose message see {@link V2ProposeCredentialMessage} + */ + public createProposal( + formatServices: CredentialFormatService[], + proposal: ProposeCredentialOptions + ): CredentialProtocolMsgReturnType { + if (formatServices.length === 0) { + throw new AriesFrameworkError('no format services provided to createProposal') + } + + // create message + // there are two arrays in each message, one for formats the other for attachments + const formatsArray: CredentialFormatSpec[] = [] + const filtersAttachArray: Attachment[] | undefined = [] + let previewAttachments: V2CredentialPreview | undefined + for (const formatService of formatServices) { + const { format: formats, attachment, preview } = formatService.createProposal(proposal) + if (attachment) { + filtersAttachArray.push(attachment) + } else { + throw new AriesFrameworkError('attachment not initialized for credential proposal') + } + if (preview) { + previewAttachments = preview + } + formatsArray.push(formats) + } + const options: V2ProposeCredentialMessageProps = { + id: this.generateId(), + formats: formatsArray, + filtersAttach: filtersAttachArray, + comment: proposal.comment, + credentialProposal: previewAttachments, + } + + const message: V2ProposeCredentialMessage = new V2ProposeCredentialMessage(options) + + const props: CredentialExchangeRecordProps = { + connectionId: proposal.connectionId, + threadId: message.threadId, + state: CredentialState.ProposalSent, + autoAcceptCredential: proposal?.autoAcceptCredential, + protocolVersion: CredentialProtocolVersion.V2, + credentials: [], + } + + // Create the v2 record + const credentialRecord = new CredentialExchangeRecord(props) + + return { message, credentialRecord } + } + + /** + * accept a v2 credential proposal message according to the logic contained in the format service. The format services + * contain specific logic related to indy, jsonld etc. with others to come. + * + * @param message {@link V2ProposeCredentialMessage} object containing (optionally) the linked attachments + * @param connectionId optional connection id for the agent to agent connection + * @return a version 2.0 credential record object see {@link CredentialRecord} + */ + public processProposal(message: V2ProposeCredentialMessage, connectionId?: string): CredentialExchangeRecord { + const props: CredentialExchangeRecordProps = { + connectionId: connectionId, + threadId: message.threadId, + state: CredentialState.ProposalReceived, + credentialAttributes: message.credentialProposal?.attributes, + protocolVersion: CredentialProtocolVersion.V2, + credentials: [], + } + return new CredentialExchangeRecord(props) + } + + public async createOfferAsResponse( + formatServices: CredentialFormatService[], + credentialRecord: CredentialExchangeRecord, + options: ServiceOfferCredentialOptions + ): Promise { + if (formatServices.length === 0) { + throw new AriesFrameworkError('no format services provided to createProposal') + } + // create message + // there are two arrays in each message, one for formats the other for attachments + const formatsArray: CredentialFormatSpec[] = [] + const offersAttachArray: Attachment[] | undefined = [] + let previewAttachments: V2CredentialPreview = new V2CredentialPreview({ + attributes: [], + }) + + for (const formatService of formatServices) { + const { attachment: offersAttach, preview, format } = await formatService.createOffer(options) + if (offersAttach === undefined) { + throw new AriesFrameworkError('offersAttach not initialized for credential offer') + } + if (offersAttach) { + offersAttachArray.push(offersAttach) + } else { + throw new AriesFrameworkError('offersAttach not initialized for credential proposal') + } + if (preview && preview.attributes.length > 0) { + previewAttachments = preview + } + formatsArray.push(format) + + await formatService.processOffer(offersAttach, credentialRecord) + } + + const messageProps: V2OfferCredentialMessageOptions = { + id: this.generateId(), + formats: formatsArray, + comment: options.comment, + offerAttachments: offersAttachArray, + credentialPreview: previewAttachments, + } + const credentialOfferMessage: V2OfferCredentialMessage = new V2OfferCredentialMessage(messageProps) + + credentialOfferMessage.setThread({ + threadId: credentialRecord.threadId, + }) + + credentialRecord.credentialAttributes = previewAttachments?.attributes + + return credentialOfferMessage + } + + /** + * Create a {@link V2RequestCredentialMessage} + * + * @param formatService correct service for format, indy, w3c etc. + * @param record The credential record for which to create the credential request + * @param offer Additional configuration for the offer if present (might not be for W3C) + * @returns Object containing request message and associated credential record + * + */ + public async createRequest( + options: CreateRequestOptions + ): Promise> { + if (options.formatServices.length === 0) { + throw new AriesFrameworkError('no format services provided to createProposal') + } + + const formatsArray: CredentialFormatSpec[] = [] + const requestAttachArray: Attachment[] | undefined = [] + for (const format of options.formatServices) { + // use the attach id in the formats object to find the correct attachment + const attachment = format.getAttachment(options.offerMessage.formats, options.offerMessage.messageAttachment) + + if (attachment) { + options.requestOptions.offerAttachment = attachment + } else { + throw new AriesFrameworkError(`Missing data payload in attachment in credential Record ${options.record.id}`) + } + const { format: formats, attachment: requestAttach } = await format.createRequest( + options.requestOptions, + options.record, + options.holderDid + ) + + options.requestOptions.requestAttachment = requestAttach + if (formats && requestAttach) { + formatsArray.push(formats) + requestAttachArray.push(requestAttach) + } + } + const messageOptions: V2RequestCredentialMessageOptions = { + id: this.generateId(), + formats: formatsArray, + requestsAttach: requestAttachArray, + comment: options.requestOptions.comment, + } + const credentialRequestMessage = new V2RequestCredentialMessage(messageOptions) + credentialRequestMessage.setThread({ threadId: options.record.threadId }) + + options.record.autoAcceptCredential = + options.requestOptions.autoAcceptCredential ?? options.record.autoAcceptCredential + + return { message: credentialRequestMessage, credentialRecord: options.record } + } + + /** + * Create a {@link V2OfferCredentialMessage} as beginning of protocol process. + * + * @param formatService {@link CredentialFormatService} the format service object containing format-specific logic + * @param options attributes of the original offer + * @returns Object containing offer message and associated credential record + * + */ + public async createOffer( + formatServices: CredentialFormatService[], + options: ServiceOfferCredentialOptions + ): Promise<{ credentialRecord: CredentialExchangeRecord; message: V2OfferCredentialMessage }> { + if (formatServices.length === 0) { + throw new AriesFrameworkError('no format services provided to createProposal') + } + const formatsArray: CredentialFormatSpec[] = [] + const offersAttachArray: Attachment[] | undefined = [] + let previewAttachments: V2CredentialPreview = new V2CredentialPreview({ + attributes: [], + }) + + const offerMap = new Map() + for (const formatService of formatServices) { + const { attachment: offersAttach, preview, format } = await formatService.createOffer(options) + + if (offersAttach) { + offersAttachArray.push(offersAttach) + offerMap.set(offersAttach, formatService) + } else { + throw new AriesFrameworkError('offersAttach not initialized for credential proposal') + } + if (preview) { + previewAttachments = preview + } + formatsArray.push(format) + } + + const messageProps: V2OfferCredentialMessageOptions = { + id: this.generateId(), + formats: formatsArray, + comment: options.comment, + offerAttachments: offersAttachArray, + replacementId: undefined, + credentialPreview: previewAttachments, + } + + // Construct v2 offer message + const credentialOfferMessage: V2OfferCredentialMessage = new V2OfferCredentialMessage(messageProps) + + const recordProps: CredentialExchangeRecordProps = { + connectionId: options.connectionId, + threadId: credentialOfferMessage.threadId, + autoAcceptCredential: options?.autoAcceptCredential, + state: CredentialState.OfferSent, + credentialAttributes: previewAttachments?.attributes, + protocolVersion: CredentialProtocolVersion.V2, + credentials: [], + } + + const credentialRecord = new CredentialExchangeRecord(recordProps) + + for (const offersAttach of offerMap.keys()) { + const service = offerMap.get(offersAttach) + if (!service) { + throw new AriesFrameworkError(`No service found for attachment: ${offersAttach.id}`) + } + await service.processOffer(offersAttach, credentialRecord) + } + return { credentialRecord, message: credentialOfferMessage } + } + + /** + * Create a {@link V2IssueCredentialMessage} - we issue the credentials to the holder with this message + * + * @param formatService {@link CredentialFormatService} the format service object containing format-specific logic + * @param offerMessage the original offer message + * @returns Object containing offer message and associated credential record + * + */ + public async createCredential( + credentialFormats: CredentialFormatService[], + record: CredentialExchangeRecord, + serviceOptions: ServiceAcceptRequestOptions, + requestMessage: V2RequestCredentialMessage, + offerMessage: V2OfferCredentialMessage + ): Promise> { + const formatsArray: CredentialFormatSpec[] = [] + const credAttachArray: Attachment[] | undefined = [] + + for (const formatService of credentialFormats) { + const offerAttachment = formatService.getAttachment(offerMessage.formats, offerMessage.messageAttachment) + const requestAttachment = formatService.getAttachment(requestMessage.formats, requestMessage.messageAttachment) + + if (!requestAttachment) { + throw new Error(`Missing request attachment in createCredential`) + } + + const { format: formats, attachment: credentialsAttach } = await formatService.createCredential( + serviceOptions, + record, + requestAttachment, + offerAttachment + ) + + if (!formats) { + throw new AriesFrameworkError('formats not initialized for credential') + } + formatsArray.push(formats) + if (!credentialsAttach) { + throw new AriesFrameworkError('credentialsAttach not initialized for credential') + } + credAttachArray.push(credentialsAttach) + } + const messageOptions: V2IssueCredentialMessageProps = { + id: this.generateId(), + formats: formatsArray, + credentialsAttach: credAttachArray, + comment: serviceOptions.comment, + } + + const message: V2IssueCredentialMessage = new V2IssueCredentialMessage(messageOptions) + + return { message, credentialRecord: record } + } + public generateId(): string { + return uuid() + } +} diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialPreview.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialPreview.ts new file mode 100644 index 0000000000..8202ec5542 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialPreview.ts @@ -0,0 +1,59 @@ +import type { CredentialPreviewOptions } from '../../models/CredentialPreviewAttributes' + +import { Expose, Type } from 'class-transformer' +import { Equals, IsInstance, ValidateNested } from 'class-validator' + +import { JsonTransformer } from '../../../../utils/JsonTransformer' +import { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttributes' + +/** + * Credential preview inner message class. + * + * This is not a message but an inner object for other messages in this protocol. It is used construct a preview of the data for the credential. + * + * @see https://github.com/hyperledger/aries-rfcs/tree/main/features/0453-issue-credential-v2#preview-credential + */ +export class V2CredentialPreview { + public constructor(options: CredentialPreviewOptions) { + if (options) { + this.attributes = options.attributes + } + } + + @Expose({ name: '@type' }) + @Equals(V2CredentialPreview.type) + public type = V2CredentialPreview.type + public static type = 'https://didcomm.org/issue-credential/2.0/credential-preview' + + @Type(() => CredentialPreviewAttribute) + @ValidateNested({ each: true }) + @IsInstance(CredentialPreviewAttribute, { each: true }) + public attributes!: CredentialPreviewAttribute[] + + public toJSON(): Record { + return JsonTransformer.toJSON(this) + } + + /** + * Create a credential preview from a record with name and value entries. + * + * @example + * const preview = CredentialPreview.fromRecord({ + * name: "Bob", + * age: "20" + * }) + */ + public static fromRecord(record: Record) { + const attributes = Object.entries(record).map( + ([name, value]) => + new CredentialPreviewAttribute({ + name, + mimeType: 'text/plain', + value, + }) + ) + return new V2CredentialPreview({ + attributes, + }) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts new file mode 100644 index 0000000000..469f7dcfb5 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialService.ts @@ -0,0 +1,1109 @@ +import type { AgentMessage } from '../../../../agent/AgentMessage' +import type { HandlerInboundMessage } from '../../../../agent/Handler' +import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import type { Attachment } from '../../../../decorators/attachment/Attachment' +import type { CredentialStateChangedEvent } from '../../CredentialEvents' +import type { + ServiceAcceptCredentialOptions, + CredentialProtocolMsgReturnType, + ServiceAcceptProposalOptions, + ServiceOfferCredentialOptions, +} from '../../CredentialServiceOptions' +import type { + AcceptProposalOptions, + AcceptRequestOptions, + NegotiateOfferOptions, + NegotiateProposalOptions, + OfferCredentialOptions, + ProposeCredentialOptions, + RequestCredentialOptions, +} from '../../CredentialsModuleOptions' +import type { CredentialFormatService } from '../../formats/CredentialFormatService' +import type { + CredentialFormats, + CredentialFormatSpec, + HandlerAutoAcceptOptions, +} from '../../formats/models/CredentialFormatServiceOptions' +import type { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttributes' +import type { CreateRequestOptions } from './CredentialMessageBuilder' + +import { Lifecycle, scoped } from 'tsyringe' + +import { AgentConfig } from '../../../../agent/AgentConfig' +import { Dispatcher } from '../../../../agent/Dispatcher' +import { EventEmitter } from '../../../../agent/EventEmitter' +import { ServiceDecorator } from '../../../../decorators/service/ServiceDecorator' +import { AriesFrameworkError } from '../../../../error' +import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage' +import { AckStatus } from '../../../common' +import { ConnectionService } from '../../../connections/services/ConnectionService' +import { MediationRecipientService } from '../../../routing' +import { AutoAcceptCredential } from '../../CredentialAutoAcceptType' +import { CredentialEventTypes } from '../../CredentialEvents' +import { CredentialProtocolVersion } from '../../CredentialProtocolVersion' +import { CredentialState } from '../../CredentialState' +import { CredentialFormatType } from '../../CredentialsModuleOptions' +import { CredentialProblemReportError, CredentialProblemReportReason } from '../../errors' +import { IndyCredentialFormatService } from '../../formats/indy/IndyCredentialFormatService' +import { FORMAT_KEYS } from '../../formats/models/CredentialFormatServiceOptions' +import { CredentialRepository, CredentialExchangeRecord } from '../../repository' +import { RevocationService } from '../../services' +import { CredentialService } from '../../services/CredentialService' + +import { CredentialMessageBuilder } from './CredentialMessageBuilder' +import { V2CredentialAckHandler } from './handlers/V2CredentialAckHandler' +import { V2CredentialProblemReportHandler } from './handlers/V2CredentialProblemReportHandler' +import { V2IssueCredentialHandler } from './handlers/V2IssueCredentialHandler' +import { V2OfferCredentialHandler } from './handlers/V2OfferCredentialHandler' +import { V2ProposeCredentialHandler } from './handlers/V2ProposeCredentialHandler' +import { V2RequestCredentialHandler } from './handlers/V2RequestCredentialHandler' +import { V2CredentialAckMessage } from './messages/V2CredentialAckMessage' +import { V2IssueCredentialMessage } from './messages/V2IssueCredentialMessage' +import { V2OfferCredentialMessage } from './messages/V2OfferCredentialMessage' +import { V2ProposeCredentialMessage } from './messages/V2ProposeCredentialMessage' +import { V2RequestCredentialMessage } from './messages/V2RequestCredentialMessage' + +@scoped(Lifecycle.ContainerScoped) +export class V2CredentialService extends CredentialService { + private connectionService: ConnectionService + private credentialMessageBuilder: CredentialMessageBuilder + private indyCredentialFormatService: IndyCredentialFormatService + private serviceFormatMap: { Indy: IndyCredentialFormatService } // jsonld todo + + public constructor( + connectionService: ConnectionService, + credentialRepository: CredentialRepository, + eventEmitter: EventEmitter, + dispatcher: Dispatcher, + agentConfig: AgentConfig, + mediationRecipientService: MediationRecipientService, + didCommMessageRepository: DidCommMessageRepository, + indyCredentialFormatService: IndyCredentialFormatService, + revocationService: RevocationService + ) { + super( + credentialRepository, + eventEmitter, + dispatcher, + agentConfig, + mediationRecipientService, + didCommMessageRepository, + revocationService + ) + this.connectionService = connectionService + this.indyCredentialFormatService = indyCredentialFormatService + this.credentialMessageBuilder = new CredentialMessageBuilder() + this.serviceFormatMap = { + [CredentialFormatType.Indy]: this.indyCredentialFormatService, + } + } + + /** + * Create a {@link V2ProposeCredentialMessage} not bound to an existing credential exchange. + * + * @param proposal The ProposeCredentialOptions object containing the important fields for the credential message + * @returns Object containing proposal message and associated credential record + * + */ + public async createProposal( + proposal: ProposeCredentialOptions + ): Promise> { + this.logger.debug('Get the Format Service and Create Proposal Message') + + const formats: CredentialFormatService[] = this.getFormats(proposal.credentialFormats) + + if (!formats || formats.length === 0) { + throw new AriesFrameworkError(`Unable to create proposal. No supported formats`) + } + const { message: proposalMessage, credentialRecord } = this.credentialMessageBuilder.createProposal( + formats, + proposal + ) + + credentialRecord.credentialAttributes = proposalMessage.credentialProposal?.attributes + credentialRecord.connectionId = proposal.connectionId + + this.logger.debug('Save meta data and emit state change event') + + await this.credentialRepository.save(credentialRecord) + + this.eventEmitter.emit({ + type: CredentialEventTypes.CredentialStateChanged, + payload: { + credentialRecord, + previousState: null, + }, + }) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + agentMessage: proposalMessage, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + + for (const format of formats) { + const options: ServiceAcceptProposalOptions = { + credentialRecordId: credentialRecord.id, + credentialFormats: {}, + protocolVersion: CredentialProtocolVersion.V2, + } + options.proposalAttachment = format.getAttachment(proposalMessage.formats, proposalMessage.messageAttachment) + await format.processProposal(options, credentialRecord) + } + return { credentialRecord, message: proposalMessage } + } + + /** + * Method called by {@link V2ProposeCredentialHandler} on reception of a propose credential message + * We do the necessary processing here to accept the proposal and do the state change, emit event etc. + * @param messageContext the inbound propose credential message + * @returns credential record appropriate for this incoming message (once accepted) + */ + public async processProposal( + messageContext: HandlerInboundMessage + ): Promise { + let credentialRecord: CredentialExchangeRecord + const { message: proposalMessage, connection } = messageContext + + this.logger.debug(`Processing credential proposal with id ${proposalMessage.id}`) + + try { + // Credential record already exists + credentialRecord = await this.getByThreadAndConnectionId(proposalMessage.threadId, connection?.id) + + // this may not be the first proposal message... + // let proposalCredentialMessage, offerCredentialMessage + // try { + const proposalCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2ProposeCredentialMessage, + }) + const offerCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2OfferCredentialMessage, + }) + credentialRecord.assertState(CredentialState.OfferSent) + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: proposalCredentialMessage ?? undefined, + previousSentMessage: offerCredentialMessage ?? undefined, + }) + + // Update record + await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + agentMessage: proposalMessage, + role: DidCommMessageRole.Receiver, + associatedRecordId: credentialRecord.id, + }) + await this.updateState(credentialRecord, CredentialState.ProposalReceived) + } catch { + // No credential record exists with thread id + // get the format service objects for the formats found in the message + + credentialRecord = this.credentialMessageBuilder.processProposal(proposalMessage, connection?.id) + + // Save record and emit event + this.connectionService.assertConnectionOrServiceDecorator(messageContext) + + await this.credentialRepository.save(credentialRecord) + await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + agentMessage: proposalMessage, + role: DidCommMessageRole.Receiver, + associatedRecordId: credentialRecord.id, + }) + await this.emitEvent(credentialRecord) + } + return credentialRecord + } + + public async acceptProposal( + proposal: AcceptProposalOptions, + credentialRecord: CredentialExchangeRecord + ): Promise> { + const options: ServiceOfferCredentialOptions = { + connectionId: proposal.connectionId ?? undefined, + protocolVersion: proposal.protocolVersion, + credentialFormats: proposal.credentialFormats, + comment: proposal.comment, + } + const message = await this.createOfferAsResponse(credentialRecord, options) + + return { credentialRecord, message } + } + + /** + * Create a {@link AcceptProposalOptions} object used by handler + * + * @param credentialRecord {@link CredentialRecord} the record containing the proposal + * @return options attributes of the proposal + * + */ + private async createAcceptProposalOptions( + credentialRecord: CredentialExchangeRecord + ): Promise { + const proposalMessage: V2ProposeCredentialMessage | null = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2ProposeCredentialMessage, + }) + + if (!proposalMessage) { + throw new AriesFrameworkError(`Missing proposal message for credential record ${credentialRecord.id}`) + } + const formats: CredentialFormatService[] = this.getFormatsFromMessage(proposalMessage.formats) + + if (!formats || formats.length === 0) { + throw new AriesFrameworkError(`Unable to create accept proposal options. No supported formats`) + } + const options: ServiceAcceptProposalOptions = { + credentialRecordId: credentialRecord.id, + credentialFormats: {}, + protocolVersion: CredentialProtocolVersion.V2, + } + + for (const formatService of formats) { + options.proposalAttachment = formatService.getAttachment( + proposalMessage.formats, + proposalMessage.messageAttachment + ) + // should fill in the credential formats + await formatService.processProposal(options, credentialRecord) + } + return options + } + + /** + * Negotiate a credential proposal as issuer (by sending a credential offer message) to the connection + * associated with the credential record. + * + * @param options configuration for the offer see {@link NegotiateProposalOptions} + * @returns Credential exchange record associated with the credential offer + * + */ + public async negotiateProposal( + options: NegotiateProposalOptions, + credentialRecord: CredentialExchangeRecord + ): Promise> { + if (!credentialRecord.connectionId) { + throw new AriesFrameworkError( + `No connectionId found for credential record '${credentialRecord.id}'. Connection-less issuance does not support negotiation.` + ) + } + + const message = await this.createOfferAsResponse(credentialRecord, options) + + return { credentialRecord, message } + } + + /** + * Create a {@link ProposePresentationMessage} as response to a received credential offer. + * To create a proposal not bound to an existing credential exchange, use {@link createProposal}. + * + * @param credentialRecord The credential record for which to create the credential proposal + * @param config Additional configuration to use for the proposal + * @returns Object containing proposal message and associated credential record + * + */ + public async negotiateOffer( + options: NegotiateOfferOptions, + credentialRecord: CredentialExchangeRecord + ): Promise> { + // Assert + credentialRecord.assertState(CredentialState.OfferReceived) + + // Create message + + const formats: CredentialFormatService[] = this.getFormats(options.credentialFormats) + + if (!formats || formats.length === 0) { + throw new AriesFrameworkError(`Unable to negotiate offer. No supported formats`) + } + const { message: credentialProposalMessage } = this.credentialMessageBuilder.createProposal(formats, options) + credentialProposalMessage.setThread({ threadId: credentialRecord.threadId }) + + // Update record + await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + agentMessage: credentialProposalMessage, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + credentialRecord.credentialAttributes = credentialProposalMessage.credentialProposal?.attributes + await this.updateState(credentialRecord, CredentialState.ProposalSent) + + return { message: credentialProposalMessage, credentialRecord } + } + /** + * Create a {@link V2OfferCredentialMessage} as beginning of protocol process. + * + * @param formatService {@link CredentialFormatService} the format service object containing format-specific logic + * @param options attributes of the original offer + * @returns Object containing offer message and associated credential record + * + */ + public async createOffer( + options: OfferCredentialOptions + ): Promise> { + if (!options.connectionId) { + throw new AriesFrameworkError('Connection id missing from offer credential options') + } + const connection = await this.connectionService.getById(options.connectionId) + + connection?.assertReady() + + const formats: CredentialFormatService[] = this.getFormats(options.credentialFormats) + + if (!formats || formats.length === 0) { + throw new AriesFrameworkError(`Unable to create offer. No supported formats`) + } + // Create message + const { credentialRecord, message: credentialOfferMessage } = await this.credentialMessageBuilder.createOffer( + formats, + options + ) + credentialRecord.connectionId = options.connectionId + + await this.credentialRepository.save(credentialRecord) + await this.emitEvent(credentialRecord) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + agentMessage: credentialOfferMessage, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + + return { credentialRecord, message: credentialOfferMessage } + } + + /** + * Create an offer message for an out-of-band (connectionless) credential + * @param credentialOptions the options (parameters) object for the offer + * @returns the credential record and the offer message + */ + public async createOutOfBandOffer( + credentialOptions: OfferCredentialOptions + ): Promise> { + const formats: CredentialFormatService[] = this.getFormats(credentialOptions.credentialFormats) + + if (!formats || formats.length === 0) { + throw new AriesFrameworkError(`Unable to create out of band offer. No supported formats`) + } + // Create message + const { credentialRecord, message: offerCredentialMessage } = await this.credentialMessageBuilder.createOffer( + formats, + credentialOptions + ) + + // Create and set ~service decorator + const routing = await this.mediationRecipientService.getRouting() + offerCredentialMessage.service = new ServiceDecorator({ + serviceEndpoint: routing.endpoints[0], + recipientKeys: [routing.verkey], + routingKeys: routing.routingKeys, + }) + await this.credentialRepository.save(credentialRecord) + await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + agentMessage: offerCredentialMessage, + role: DidCommMessageRole.Receiver, + associatedRecordId: credentialRecord.id, + }) + await this.emitEvent(credentialRecord) + return { credentialRecord, message: offerCredentialMessage } + } + /** + * Create a {@link OfferCredentialMessage} as response to a received credential proposal. + * To create an offer not bound to an existing credential exchange, use {@link V2CredentialService#createOffer}. + * + * @param credentialRecord The credential record for which to create the credential offer + * @param credentialTemplate The credential template to use for the offer + * @returns Object containing offer message and associated credential record + * + */ + public async createOfferAsResponse( + credentialRecord: CredentialExchangeRecord, + proposal?: ServiceOfferCredentialOptions | NegotiateProposalOptions + ): Promise { + // Assert + credentialRecord.assertState(CredentialState.ProposalReceived) + + let options: ServiceOfferCredentialOptions | undefined + if (!proposal) { + const acceptProposalOptions: AcceptProposalOptions = await this.createAcceptProposalOptions(credentialRecord) + + options = { + credentialFormats: acceptProposalOptions.credentialFormats, + protocolVersion: CredentialProtocolVersion.V2, + credentialRecordId: acceptProposalOptions.connectionId, + comment: acceptProposalOptions.comment, + } + } else { + options = proposal + } + const formats: CredentialFormatService[] = this.getFormats(options.credentialFormats as Record) + + if (!formats || formats.length === 0) { + throw new AriesFrameworkError(`Unable to create offer as response. No supported formats`) + } + // Create the offer message + this.logger.debug(`Get the Format Service and Create Offer Message for credential record ${credentialRecord.id}`) + + const proposeCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2ProposeCredentialMessage, + }) + + const credentialOfferMessage = await this.credentialMessageBuilder.createOfferAsResponse( + formats, + credentialRecord, + options + ) + + credentialOfferMessage.credentialPreview = proposeCredentialMessage?.credentialProposal + credentialRecord.credentialAttributes = proposeCredentialMessage?.credentialProposal?.attributes + + await this.updateState(credentialRecord, CredentialState.OfferSent) + await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + agentMessage: credentialOfferMessage, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + + return credentialOfferMessage + } + /** + * Method called by {@link V2OfferCredentialHandler} on reception of a offer credential message + * We do the necessary processing here to accept the offer and do the state change, emit event etc. + * @param messageContext the inbound offer credential message + * @returns credential record appropriate for this incoming message (once accepted) + */ + public async processOffer( + messageContext: HandlerInboundMessage + ): Promise { + let credentialRecord: CredentialExchangeRecord + const { message: credentialOfferMessage, connection } = messageContext + + this.logger.debug(`Processing credential offer with id ${credentialOfferMessage.id}`) + + const formats: CredentialFormatService[] = this.getFormatsFromMessage(credentialOfferMessage.formats) + if (!formats || formats.length === 0) { + throw new AriesFrameworkError(`Unable to create offer. No supported formats`) + } + try { + // Credential record already exists + credentialRecord = await this.getByThreadAndConnectionId(credentialOfferMessage.threadId, connection?.id) + + const proposeCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2ProposeCredentialMessage, + }) + const offerCredentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2OfferCredentialMessage, + }) + credentialRecord.assertState(CredentialState.ProposalSent) + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: offerCredentialMessage ?? undefined, + previousSentMessage: proposeCredentialMessage ?? undefined, + }) + + for (const format of formats) { + const attachment = format.getAttachment( + credentialOfferMessage.formats, + credentialOfferMessage.messageAttachment + ) + + if (!attachment) { + throw new AriesFrameworkError(`Missing offer attachment in credential offer message`) + } + await format.processOffer(attachment, credentialRecord) + } + await this.updateState(credentialRecord, CredentialState.OfferReceived) + await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + agentMessage: credentialOfferMessage, + role: DidCommMessageRole.Receiver, + associatedRecordId: credentialRecord.id, + }) + } catch (error) { + // No credential record exists with thread id + + this.logger.debug('No credential record found for this offer - create a new one') + credentialRecord = new CredentialExchangeRecord({ + connectionId: connection?.id, + threadId: credentialOfferMessage.id, + credentialAttributes: credentialOfferMessage.credentialPreview?.attributes, + state: CredentialState.OfferReceived, + protocolVersion: CredentialProtocolVersion.V2, + credentials: [], + }) + + for (const format of formats) { + const attachment = format.getAttachment( + credentialOfferMessage.formats, + credentialOfferMessage.messageAttachment + ) + + if (!attachment) { + throw new AriesFrameworkError(`Missing offer attachment in credential offer message`) + } + await format.processOffer(attachment, credentialRecord) + } + + // Save in repository + this.logger.debug('Saving credential record and emit offer-received event') + await this.credentialRepository.save(credentialRecord) + + await this.didCommMessageRepository.saveOrUpdateAgentMessage({ + agentMessage: credentialOfferMessage, + role: DidCommMessageRole.Receiver, + associatedRecordId: credentialRecord.id, + }) + this.eventEmitter.emit({ + type: CredentialEventTypes.CredentialStateChanged, + payload: { + credentialRecord, + previousState: null, + }, + }) + } + + return credentialRecord + } + + /** + * Create a {@link V2RequestCredentialMessage} + * + * @param credentialRecord The credential record for which to create the credential request + * @param options request options for creating this request + * @returns Object containing request message and associated credential record + * + */ + public async createRequest( + record: CredentialExchangeRecord, + options: RequestCredentialOptions, + holderDid?: string // temporary workaround + ): Promise> { + this.logger.debug('Get the Format Service and Create Request Message') + + record.assertState(CredentialState.OfferReceived) + + const offerMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: record.id, + messageClass: V2OfferCredentialMessage, + }) + + if (!offerMessage) { + throw new CredentialProblemReportError( + `Missing required base64 or json encoded attachment data for credential offer with thread id ${record.threadId}`, + { problemCode: CredentialProblemReportReason.IssuanceAbandoned } + ) + } + const formats: CredentialFormatService[] = this.getFormatsFromMessage(offerMessage.formats) + if (!formats || formats.length == 0) { + throw new AriesFrameworkError('No format keys found on the RequestCredentialOptions object') + } + + const optionsForRequest: CreateRequestOptions = { + formatServices: formats, + record, + requestOptions: options, + offerMessage, + holderDid, + } + const { message, credentialRecord } = await this.credentialMessageBuilder.createRequest(optionsForRequest) + + await this.updateState(credentialRecord, CredentialState.RequestSent) + return { message, credentialRecord } + } + + /** + * Process a received {@link RequestCredentialMessage}. This will not accept the credential request + * or send a credential. It will only update the existing credential record with + * the information from the credential request message. Use {@link createCredential} + * after calling this method to create a credential. + * + * @param messageContext The message context containing a v2 credential request message + * @returns credential record associated with the credential request message + * + */ + public async processRequest( + messageContext: InboundMessageContext + ): Promise { + const { message: credentialRequestMessage, connection } = messageContext + + const credentialRecord = await this.getByThreadAndConnectionId(credentialRequestMessage.threadId, connection?.id) + credentialRecord.connectionId = connection?.id + + const proposalMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2ProposeCredentialMessage, + }) + + const offerMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2OfferCredentialMessage, + }) + + // Assert + credentialRecord.assertState(CredentialState.OfferSent) + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: proposalMessage ?? undefined, + previousSentMessage: offerMessage ?? undefined, + }) + + this.logger.debug('Credential record found when processing credential request', credentialRecord) + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: credentialRequestMessage, + role: DidCommMessageRole.Receiver, + associatedRecordId: credentialRecord.id, + }) + await this.updateState(credentialRecord, CredentialState.RequestReceived) + + return credentialRecord + } + + /** + * Create a {@link IssueCredentialMessage} as response to a received credential request. + * + * @param credentialRecord The credential record for which to create the credential + * @param options Additional configuration to use for the credential + * @returns Object containing issue credential message and associated credential record + * + */ + public async createCredential( + record: CredentialExchangeRecord, + options: AcceptRequestOptions + ): Promise> { + record.assertState(CredentialState.RequestReceived) + + const requestMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: record.id, + messageClass: V2RequestCredentialMessage, + }) + + if (!requestMessage) { + throw new AriesFrameworkError( + `Missing credential request for credential exchange with thread id ${record.threadId}` + ) + } + const offerMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: record.id, + messageClass: V2OfferCredentialMessage, + }) + if (!offerMessage) { + throw new AriesFrameworkError('Missing Offer Message in create credential') + } + const credentialFormats: CredentialFormatService[] = this.getFormatsFromMessage(requestMessage.formats) + if (!credentialFormats || credentialFormats.length === 0) { + throw new AriesFrameworkError(`Unable to create credential. No supported formats`) + } + const { message: issueCredentialMessage, credentialRecord } = await this.credentialMessageBuilder.createCredential( + credentialFormats, + record, + options, + requestMessage, + offerMessage + ) + + issueCredentialMessage.setThread({ + threadId: credentialRecord.threadId, + }) + issueCredentialMessage.setPleaseAck() + + credentialRecord.autoAcceptCredential = options?.autoAcceptCredential ?? credentialRecord.autoAcceptCredential + + await this.updateState(credentialRecord, CredentialState.CredentialIssued) + + return { message: issueCredentialMessage, credentialRecord } + } + + /** + * Process a received {@link IssueCredentialMessage}. This will not accept the credential + * or send a credential acknowledgement. It will only update the existing credential record with + * the information from the issue credential message. Use {@link createAck} + * after calling this method to create a credential acknowledgement. + * + * @param messageContext The message context containing an issue credential message + * + * @returns credential record associated with the issue credential message + * + */ + public async processCredential( + messageContext: InboundMessageContext + ): Promise { + const { message: issueCredentialMessage, connection } = messageContext + + this.logger.debug(`Processing credential with id ${issueCredentialMessage.id}`) + + const credentialRecord = await this.getByThreadAndConnectionId(issueCredentialMessage.threadId, connection?.id) + + credentialRecord.connectionId = connection?.id + + const requestMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2RequestCredentialMessage, + }) + const offerMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2OfferCredentialMessage, + }) + + // Assert + credentialRecord.assertState(CredentialState.RequestSent) + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: offerMessage ?? undefined, + previousSentMessage: requestMessage ?? undefined, + }) + + const formatServices: CredentialFormatService[] = this.getFormatsFromMessage(issueCredentialMessage.formats) + + for (const formatService of formatServices) { + // get the revocation registry and pass it to the process (store) credential method + const issueAttachment = formatService.getAttachment( + issueCredentialMessage.formats, + issueCredentialMessage.messageAttachment + ) + + if (!issueAttachment) { + throw new AriesFrameworkError('Missing credential attachment in processCredential') + } + const options: ServiceAcceptCredentialOptions = { + credentialAttachment: issueAttachment, + } + await formatService.processCredential(options, credentialRecord) + } + + await this.updateState(credentialRecord, CredentialState.CredentialReceived) + + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: issueCredentialMessage, + role: DidCommMessageRole.Receiver, + associatedRecordId: credentialRecord.id, + }) + return credentialRecord + } + /** + * Create a {@link V2CredentialAckMessage} as response to a received credential. + * + * @param credentialRecord The credential record for which to create the credential acknowledgement + * @returns Object containing credential acknowledgement message and associated credential record + * + */ + public async createAck( + credentialRecord: CredentialExchangeRecord + ): Promise> { + credentialRecord.assertState(CredentialState.CredentialReceived) + + // Create message + const ackMessage = new V2CredentialAckMessage({ + status: AckStatus.OK, + threadId: credentialRecord.threadId, + }) + + await this.updateState(credentialRecord, CredentialState.Done) + + return { message: ackMessage, credentialRecord } + } + + /** + * Process a received {@link CredentialAckMessage}. + * + * @param messageContext The message context containing a credential acknowledgement message + * @returns credential record associated with the credential acknowledgement message + * + */ + public async processAck( + messageContext: InboundMessageContext + ): Promise { + const { message: credentialAckMessage, connection } = messageContext + + this.logger.debug(`Processing credential ack with id ${credentialAckMessage.id}`) + + const credentialRecord = await this.getByThreadAndConnectionId(credentialAckMessage.threadId, connection?.id) + credentialRecord.connectionId = connection?.id + + const requestMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2RequestCredentialMessage, + }) + + const credentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2IssueCredentialMessage, + }) + + // Assert + credentialRecord.assertState(CredentialState.CredentialIssued) + this.connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: requestMessage ?? undefined, + previousSentMessage: credentialMessage ?? undefined, + }) + + // Update record + await this.updateState(credentialRecord, CredentialState.Done) + + return credentialRecord + } + /** + * Register the v2 handlers. These handlers supplement, ie are created in addition to, the existing + * v1 handlers. + */ + public registerHandlers() { + this.logger.debug('Registering V2 handlers') + + this.dispatcher.registerHandler( + new V2ProposeCredentialHandler(this, this.agentConfig, this.didCommMessageRepository) + ) + + this.dispatcher.registerHandler( + new V2OfferCredentialHandler( + this, + this.agentConfig, + this.mediationRecipientService, + this.didCommMessageRepository + ) + ) + + this.dispatcher.registerHandler( + new V2RequestCredentialHandler(this, this.agentConfig, this.didCommMessageRepository) + ) + + this.dispatcher.registerHandler(new V2IssueCredentialHandler(this, this.agentConfig, this.didCommMessageRepository)) + this.dispatcher.registerHandler(new V2CredentialAckHandler(this)) + this.dispatcher.registerHandler(new V2CredentialProblemReportHandler(this)) + } + + // AUTO ACCEPT METHODS + public async shouldAutoRespondToProposal(options: HandlerAutoAcceptOptions): Promise { + if (this.agentConfig.autoAcceptCredentials === AutoAcceptCredential.Never) { + return false + } + if (options.credentialRecord.autoAcceptCredential === AutoAcceptCredential.Never) { + return false + } + const proposalMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: options.credentialRecord.id, + messageClass: V2ProposeCredentialMessage, + }) + if (!proposalMessage) { + throw new AriesFrameworkError('Missing proposal message in V2ProposeCredentialHandler') + } + const formatServices: CredentialFormatService[] = this.getFormatsFromMessage(proposalMessage.formats) + let shouldAutoRespond = true + for (const formatService of formatServices) { + const formatShouldAutoRespond = + this.agentConfig.autoAcceptCredentials == AutoAcceptCredential.Always || + formatService.shouldAutoRespondToProposal(options) + + shouldAutoRespond = shouldAutoRespond && formatShouldAutoRespond + } + return shouldAutoRespond + } + + public shouldAutoRespondToOffer( + credentialRecord: CredentialExchangeRecord, + offerMessage: V2OfferCredentialMessage, + proposeMessage?: V2ProposeCredentialMessage + ): boolean { + if (this.agentConfig.autoAcceptCredentials === AutoAcceptCredential.Never) { + return false + } + let offerValues: CredentialPreviewAttribute[] | undefined + let shouldAutoRespond = true + const formatServices: CredentialFormatService[] = this.getFormatsFromMessage(offerMessage.formats) + for (const formatService of formatServices) { + let proposalAttachment: Attachment | undefined + + if (proposeMessage) { + proposalAttachment = formatService.getAttachment(proposeMessage.formats, proposeMessage.messageAttachment) + } + const offerAttachment = formatService.getAttachment(offerMessage.formats, offerMessage.messageAttachment) + + offerValues = offerMessage.credentialPreview?.attributes + + const handlerOptions: HandlerAutoAcceptOptions = { + credentialRecord, + autoAcceptType: this.agentConfig.autoAcceptCredentials, + messageAttributes: offerValues, + proposalAttachment, + offerAttachment, + } + const formatShouldAutoRespond = + this.agentConfig.autoAcceptCredentials == AutoAcceptCredential.Always || + formatService.shouldAutoRespondToProposal(handlerOptions) + + shouldAutoRespond = shouldAutoRespond && formatShouldAutoRespond + } + + return shouldAutoRespond + } + + public shouldAutoRespondToRequest( + credentialRecord: CredentialExchangeRecord, + requestMessage: V2RequestCredentialMessage, + proposeMessage?: V2ProposeCredentialMessage, + offerMessage?: V2OfferCredentialMessage + ): boolean { + const formatServices: CredentialFormatService[] = this.getFormatsFromMessage(requestMessage.formats) + let shouldAutoRespond = true + + for (const formatService of formatServices) { + let proposalAttachment, offerAttachment, requestAttachment: Attachment | undefined + if (proposeMessage) { + proposalAttachment = formatService.getAttachment(proposeMessage.formats, proposeMessage.messageAttachment) + } + if (offerMessage) { + offerAttachment = formatService.getAttachment(offerMessage.formats, offerMessage.messageAttachment) + } + if (requestMessage) { + requestAttachment = formatService.getAttachment(requestMessage.formats, requestMessage.messageAttachment) + } + const handlerOptions: HandlerAutoAcceptOptions = { + credentialRecord, + autoAcceptType: this.agentConfig.autoAcceptCredentials, + proposalAttachment, + offerAttachment, + requestAttachment, + } + const formatShouldAutoRespond = + this.agentConfig.autoAcceptCredentials == AutoAcceptCredential.Always || + formatService.shouldAutoRespondToRequest(handlerOptions) + + shouldAutoRespond = shouldAutoRespond && formatShouldAutoRespond + } + return shouldAutoRespond + } + + public shouldAutoRespondToCredential( + credentialRecord: CredentialExchangeRecord, + credentialMessage: V2IssueCredentialMessage + ): boolean { + // 1. Get all formats for this message + const formatServices: CredentialFormatService[] = this.getFormatsFromMessage(credentialMessage.formats) + + // 2. loop through found formats + let shouldAutoRespond = true + let credentialAttachment: Attachment | undefined + + for (const formatService of formatServices) { + if (credentialMessage) { + credentialAttachment = formatService.getAttachment( + credentialMessage.formats, + credentialMessage.messageAttachment + ) + } + const handlerOptions: HandlerAutoAcceptOptions = { + credentialRecord, + autoAcceptType: this.agentConfig.autoAcceptCredentials, + credentialAttachment, + } + // 3. Call format.shouldRespondToProposal for each one + + const formatShouldAutoRespond = + this.agentConfig.autoAcceptCredentials == AutoAcceptCredential.Always || + formatService.shouldAutoRespondToCredential(handlerOptions) + + shouldAutoRespond = shouldAutoRespond && formatShouldAutoRespond + } + return shouldAutoRespond + } + public async getOfferMessage(id: string): Promise { + return await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: id, + messageClass: V2OfferCredentialMessage, + }) + } + public async getRequestMessage(id: string): Promise { + return await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: id, + messageClass: V2RequestCredentialMessage, + }) + } + + public async getCredentialMessage(id: string): Promise { + return await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: id, + messageClass: V2IssueCredentialMessage, + }) + } + + public update(credentialRecord: CredentialExchangeRecord) { + return this.credentialRepository.update(credentialRecord) + } + + /** + * Returns the protocol version for this credential service + * @returns v2 as this is the v2 service + */ + public getVersion(): CredentialProtocolVersion { + return CredentialProtocolVersion.V2 + } + + /** + * Gets the correct formatting service for this credential record type, eg indy or jsonld. Others may be + * added in the future. + * Each formatting service knows how to format the message structure for the specific record type + * @param credentialFormatType the format type, indy, jsonld, jwt etc. + * @returns the formatting service. + */ + public getFormatService(credentialFormatType: CredentialFormatType): CredentialFormatService { + return this.serviceFormatMap[credentialFormatType] + } + + private async emitEvent(credentialRecord: CredentialExchangeRecord) { + this.eventEmitter.emit({ + type: CredentialEventTypes.CredentialStateChanged, + payload: { + credentialRecord, + previousState: null, + }, + }) + } + /** + * Retrieve a credential record by connection id and thread id + * + * @param connectionId The connection id + * @param threadId The thread id + * @throws {RecordNotFoundError} If no record is found + * @throws {RecordDuplicateError} If multiple records are found + * @returns The credential record + */ + public getByThreadAndConnectionId(threadId: string, connectionId?: string): Promise { + return this.credentialRepository.getSingleByQuery({ + connectionId, + threadId, + }) + } + + /** + * Get all the format service objects for a given credential format from an incoming message + * @param messageFormats the format objects containing the format name (eg indy) + * @return the credential format service objects in an array - derived from format object keys + */ + public getFormatsFromMessage(messageFormats: CredentialFormatSpec[]): CredentialFormatService[] { + const formats: CredentialFormatService[] = [] + + for (const msg of messageFormats) { + if (msg.format.includes('indy')) { + formats.push(this.getFormatService(CredentialFormatType.Indy)) + } else if (msg.format.includes('aries')) { + // todo + } else { + throw new AriesFrameworkError(`Unknown Message Format: ${msg.format}`) + } + } + return formats + } + /** + * Get all the format service objects for a given credential format + * @param credentialFormats the format object containing various optional parameters + * @return the credential format service objects in an array - derived from format object keys + */ + public getFormats(credentialFormats: CredentialFormats): CredentialFormatService[] { + const formats: CredentialFormatService[] = [] + const formatKeys = Object.keys(credentialFormats) + + for (const key of formatKeys) { + const credentialFormatType: CredentialFormatType = FORMAT_KEYS[key] + const formatService: CredentialFormatService = this.getFormatService(credentialFormatType) + formats.push(formatService) + } + return formats + } +} diff --git a/packages/core/tests/connectionless-credentials.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v1connectionless-credentials.test.ts similarity index 62% rename from packages/core/tests/connectionless-credentials.test.ts rename to packages/core/src/modules/credentials/protocol/v2/__tests__/v1connectionless-credentials.test.ts index 814e529977..cb0890b56c 100644 --- a/packages/core/tests/connectionless-credentials.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v1connectionless-credentials.test.ts @@ -1,21 +1,24 @@ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' -import type { CredentialStateChangedEvent } from '../src/modules/credentials' +import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import type { CredentialStateChangedEvent } from '../../../CredentialEvents' +import type { + AcceptOfferOptions, + AcceptRequestOptions, + OfferCredentialOptions, +} from '../../../CredentialsModuleOptions' import { ReplaySubject, Subject } from 'rxjs' -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { Agent } from '../src/agent/Agent' -import { - CredentialPreview, - AutoAcceptCredential, - CredentialEventTypes, - CredentialRecord, - CredentialState, -} from '../src/modules/credentials' - -import { getBaseConfig, prepareForIssuance, waitForCredentialRecordSubject } from './helpers' -import testLogger from './logger' +import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' +import { prepareForIssuance, waitForCredentialRecordSubject, getBaseConfig } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { Agent } from '../../../../../agent/Agent' +import { AutoAcceptCredential } from '../../../CredentialAutoAcceptType' +import { CredentialEventTypes } from '../../../CredentialEvents' +import { CredentialProtocolVersion } from '../../../CredentialProtocolVersion' +import { CredentialState } from '../../../CredentialState' +import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import { V1CredentialPreview } from '../../v1/V1CredentialPreview' const faberConfig = getBaseConfig('Faber connection-less Credentials', { endpoints: ['rxjs:faber'], @@ -25,7 +28,7 @@ const aliceConfig = getBaseConfig('Alice connection-less Credentials', { endpoints: ['rxjs:alice'], }) -const credentialPreview = CredentialPreview.fromRecord({ +const credentialPreview = V1CredentialPreview.fromRecord({ name: 'John', age: '99', }) @@ -78,14 +81,24 @@ describe('credentials', () => { test('Faber starts with connection-less credential offer to Alice', async () => { testLogger.test('Faber sends credential offer to Alice') + + const offerOptions: OfferCredentialOptions = { + comment: 'V1 Out of Band offer', + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: credDefId, + }, + }, + protocolVersion: CredentialProtocolVersion.V1, + connectionId: '', + } // eslint-disable-next-line prefer-const - let { offerMessage, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOutOfBandOffer({ - preview: credentialPreview, - credentialDefinitionId: credDefId, - comment: 'some comment about credential', - }) + let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOutOfBandOffer( + offerOptions + ) - await aliceAgent.receiveMessage(offerMessage.toJSON()) + await aliceAgent.receiveMessage(message.toJSON()) let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, @@ -93,16 +106,23 @@ describe('credentials', () => { }) testLogger.test('Alice sends credential request to Faber') - aliceCredentialRecord = await aliceAgent.credentials.acceptOffer(aliceCredentialRecord.id) + const acceptOfferOptions: AcceptOfferOptions = { + credentialRecordId: aliceCredentialRecord.id, + } + const credentialRecord = await aliceAgent.credentials.acceptOffer(acceptOfferOptions) testLogger.test('Faber waits for credential request from Alice') faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { - threadId: aliceCredentialRecord.threadId, + threadId: credentialRecord.threadId, state: CredentialState.RequestReceived, }) testLogger.test('Faber sends credential to Alice') - faberCredentialRecord = await faberAgent.credentials.acceptRequest(faberCredentialRecord.id) + const options: AcceptRequestOptions = { + credentialRecordId: faberCredentialRecord.id, + comment: 'V1 Indy Credential', + } + faberCredentialRecord = await faberAgent.credentials.acceptRequest(options) testLogger.test('Alice waits for credential from Faber') aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { @@ -120,11 +140,9 @@ describe('credentials', () => { }) expect(aliceCredentialRecord).toMatchObject({ - type: CredentialRecord.name, + type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), metadata: { data: { '_internal/indyCredential': { @@ -132,17 +150,20 @@ describe('credentials', () => { }, }, }, - credentialId: expect.any(String), + credentials: [ + { + credentialRecordType: 'Indy', + credentialRecordId: expect.any(String), + }, + ], state: CredentialState.Done, threadId: expect.any(String), }) expect(faberCredentialRecord).toMatchObject({ - type: CredentialRecord.name, + type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), metadata: { data: { '_internal/indyCredential': { @@ -156,16 +177,25 @@ describe('credentials', () => { }) test('Faber starts with connection-less credential offer to Alice with auto-accept enabled', async () => { - // eslint-disable-next-line prefer-const - let { offerMessage, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOutOfBandOffer({ - preview: credentialPreview, - credentialDefinitionId: credDefId, - comment: 'some comment about credential', + const offerOptions: OfferCredentialOptions = { + comment: 'V1 Out of Band offer', + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: credDefId, + }, + }, + protocolVersion: CredentialProtocolVersion.V1, autoAcceptCredential: AutoAcceptCredential.ContentApproved, - }) + connectionId: '', + } + // eslint-disable-next-line prefer-const + let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOutOfBandOffer( + offerOptions + ) // Receive Message - await aliceAgent.receiveMessage(offerMessage.toJSON()) + await aliceAgent.receiveMessage(message.toJSON()) // Wait for it to be processed let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { @@ -173,9 +203,12 @@ describe('credentials', () => { state: CredentialState.OfferReceived, }) - await aliceAgent.credentials.acceptOffer(aliceCredentialRecord.id, { + const acceptOfferOptions: AcceptOfferOptions = { + credentialRecordId: aliceCredentialRecord.id, autoAcceptCredential: AutoAcceptCredential.ContentApproved, - }) + } + + await aliceAgent.credentials.acceptOffer(acceptOfferOptions) aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, @@ -188,11 +221,9 @@ describe('credentials', () => { }) expect(aliceCredentialRecord).toMatchObject({ - type: CredentialRecord.name, + type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), metadata: { data: { '_internal/indyCredential': { @@ -200,17 +231,20 @@ describe('credentials', () => { }, }, }, - credentialId: expect.any(String), + credentials: [ + { + credentialRecordType: 'Indy', + credentialRecordId: expect.any(String), + }, + ], state: CredentialState.Done, threadId: expect.any(String), }) expect(faberCredentialRecord).toMatchObject({ - type: CredentialRecord.name, + type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), state: CredentialState.Done, threadId: expect.any(String), }) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v1credentials-auto-accept.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v1credentials-auto-accept.test.ts new file mode 100644 index 0000000000..231f9532ad --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v1credentials-auto-accept.test.ts @@ -0,0 +1,484 @@ +import type { Agent } from '../../../../../agent/Agent' +import type { ConnectionRecord } from '../../../../connections' +import type { + AcceptOfferOptions, + AcceptProposalOptions, + NegotiateOfferOptions, + NegotiateProposalOptions, + OfferCredentialOptions, + ProposeCredentialOptions, +} from '../../../CredentialsModuleOptions' +import type { Schema } from 'indy-sdk' + +import { AriesFrameworkError } from '../../../../../../src/error/AriesFrameworkError' +import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { sleep } from '../../../../../utils/sleep' +import { AutoAcceptCredential } from '../../../CredentialAutoAcceptType' +import { CredentialProtocolVersion } from '../../../CredentialProtocolVersion' +import { CredentialState } from '../../../CredentialState' +import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import { V1CredentialPreview } from '../../v1/V1CredentialPreview' + +describe('credentials', () => { + let faberAgent: Agent + let aliceAgent: Agent + let credDefId: string + let schema: Schema + let faberConnection: ConnectionRecord + let aliceConnection: ConnectionRecord + const credentialPreview = V1CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'some x-ray', + profile_picture: 'profile picture', + }) + const newCredentialPreview = V1CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'another x-ray value', + profile_picture: 'another profile picture', + }) + + describe('Auto accept on `always`', () => { + beforeAll(async () => { + ;({ faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection } = await setupCredentialTests( + 'faber agent: always', + 'alice agent: always', + AutoAcceptCredential.Always + )) + }) + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + // ============================== + // TESTS v1 BEGIN + // ========================== + test('Alice starts with V1 credential proposal to Faber, both with autoAcceptCredential on `always`', async () => { + testLogger.test('Alice sends credential proposal to Faber') + let aliceCredentialRecord: CredentialExchangeRecord + + const proposeOptions: ProposeCredentialOptions = { + connectionId: aliceConnection.id, + protocolVersion: CredentialProtocolVersion.V1, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + payload: { + credentialDefinitionId: credDefId, + }, + }, + }, + comment: 'v1 propose credential test', + } + const schemaId = schema.id + const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential(proposeOptions) + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + testLogger.test('Faber waits for credential ack from Alice') + aliceCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.Done, + }) + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_internal/indyCredential': { + schemaId, + credentialDefinitionId: credDefId, + }, + }, + }, + state: CredentialState.Done, + }) + }) + test('Faber starts with V1 credential offer to Alice, both with autoAcceptCredential on `always`', async () => { + testLogger.test('Faber sends credential offer to Alice') + const schemaId = schema.id + const offerOptions: OfferCredentialOptions = { + comment: 'some comment about credential', + connectionId: faberConnection.id, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: credDefId, + }, + }, + protocolVersion: CredentialProtocolVersion.V1, + } + const faberCredentialExchangeRecord: CredentialExchangeRecord = await faberAgent.credentials.offerCredential( + offerOptions + ) + testLogger.test('Alice waits for credential from Faber') + const aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + testLogger.test('Faber waits for credential ack from Alice') + const faberCredentialRecord: CredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.Done, + }) + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_internal/indyRequest': expect.any(Object), + '_internal/indyCredential': { + schemaId, + credentialDefinitionId: credDefId, + }, + }, + }, + credentials: [ + { + credentialRecordType: 'Indy', + credentialRecordId: expect.any(String), + }, + ], + state: CredentialState.Done, + }) + expect(faberCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + }) + }) + }) + + describe('Auto accept on `contentApproved`', () => { + beforeAll(async () => { + ;({ faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection } = await setupCredentialTests( + 'faber agent: contentApproved', + 'alice agent: contentApproved', + AutoAcceptCredential.ContentApproved + )) + }) + + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + // ============================== + // TESTS v1 BEGIN + // ========================== + test('Alice starts with V1 credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { + testLogger.test('Alice sends credential proposal to Faber') + const schemaId = schema.id + let faberCredentialExchangeRecord: CredentialExchangeRecord + let aliceCredentialExchangeRecord: CredentialExchangeRecord + + const proposeOptions: ProposeCredentialOptions = { + connectionId: aliceConnection.id, + protocolVersion: CredentialProtocolVersion.V1, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + payload: { + credentialDefinitionId: credDefId, + }, + }, + }, + } + aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential(proposeOptions) + + testLogger.test('Faber waits for credential proposal from Alice') + faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + const options: AcceptProposalOptions = { + credentialRecordId: faberCredentialExchangeRecord.id, + comment: 'V1 Indy Offer', + credentialFormats: { + indy: { + credentialDefinitionId: credDefId, + attributes: credentialPreview.attributes, + }, + }, + protocolVersion: CredentialProtocolVersion.V1, + } + testLogger.test('Faber sends credential offer to Alice') + options.credentialRecordId = faberCredentialExchangeRecord.id + faberCredentialExchangeRecord = await faberAgent.credentials.acceptProposal(options) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialExchangeRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_internal/indyRequest': expect.any(Object), + '_internal/indyCredential': { + schemaId, + credentialDefinitionId: credDefId, + }, + }, + }, + credentials: [ + { + credentialRecordType: 'Indy', + credentialRecordId: expect.any(String), + }, + ], + state: CredentialState.Done, + }) + + expect(faberCredentialExchangeRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_internal/indyCredential': { + schemaId, + credentialDefinitionId: credDefId, + }, + }, + }, + state: CredentialState.Done, + }) + }) + + test('Faber starts with V1 credential offer to Alice, both with autoAcceptCredential on `contentApproved`', async () => { + testLogger.test('Faber sends credential offer to Alice') + const schemaId = schema.id + let aliceCredentialExchangeRecord: CredentialExchangeRecord + let faberCredentialExchangeRecord: CredentialExchangeRecord + + const offerOptions: OfferCredentialOptions = { + comment: 'some comment about credential', + connectionId: faberConnection.id, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: credDefId, + }, + }, + protocolVersion: CredentialProtocolVersion.V1, + } + faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential(offerOptions) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + + expect(JsonTransformer.toJSON(aliceCredentialExchangeRecord)).toMatchObject({ + state: CredentialState.OfferReceived, + }) + + // below values are not in json object + expect(aliceCredentialExchangeRecord.id).not.toBeNull() + expect(aliceCredentialExchangeRecord.getTags()).toEqual({ + threadId: aliceCredentialExchangeRecord.threadId, + state: aliceCredentialExchangeRecord.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + + if (aliceCredentialExchangeRecord.connectionId) { + const acceptOfferOptions: AcceptOfferOptions = { + credentialRecordId: aliceCredentialExchangeRecord.id, + } + testLogger.test('alice sends credential request to faber') + faberCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer(acceptOfferOptions) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialExchangeRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_internal/indyRequest': expect.any(Object), + '_internal/indyCredential': { + schemaId, + credentialDefinitionId: credDefId, + }, + }, + }, + credentials: [ + { + credentialRecordType: 'Indy', + credentialRecordId: expect.any(String), + }, + ], + state: CredentialState.Done, + }) + + expect(faberCredentialExchangeRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + }) + } else { + throw new AriesFrameworkError('missing alice connection id') + } + }) + + test('Alice starts with V1 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + const proposeOptions: ProposeCredentialOptions = { + connectionId: aliceConnection.id, + protocolVersion: CredentialProtocolVersion.V1, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + payload: { + credentialDefinitionId: credDefId, + }, + }, + }, + comment: 'v1 propose credential test', + } + testLogger.test('Alice sends credential proposal to Faber') + const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential(proposeOptions) + + testLogger.test('Faber waits for credential proposal from Alice') + let faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + const negotiateOptions: NegotiateProposalOptions = { + credentialRecordId: faberCredentialExchangeRecord.id, + credentialFormats: { + indy: { + credentialDefinitionId: credDefId, + attributes: newCredentialPreview.attributes, + }, + }, + protocolVersion: CredentialProtocolVersion.V1, + } + await faberAgent.credentials.negotiateProposal(negotiateOptions) + + testLogger.test('Alice waits for credential offer from Faber') + + const record = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + + // below values are not in json object + expect(record.id).not.toBeNull() + expect(record.getTags()).toEqual({ + threadId: record.threadId, + state: record.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + + // Check if the state of the credential records did not change + faberCredentialExchangeRecord = await faberAgent.credentials.getById(faberCredentialExchangeRecord.id) + faberCredentialExchangeRecord.assertState(CredentialState.OfferSent) + + const aliceRecord = await aliceAgent.credentials.getById(record.id) + aliceRecord.assertState(CredentialState.OfferReceived) + }) + + test('Faber starts with V1 credential offer to Alice, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + testLogger.test('Faber sends credential offer to Alice') + const offerOptions: OfferCredentialOptions = { + comment: 'some comment about credential', + connectionId: faberConnection.id, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: credDefId, + }, + }, + protocolVersion: CredentialProtocolVersion.V1, + } + let faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential(offerOptions) + + testLogger.test('Alice waits for credential offer from Faber') + let aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + + // below values are not in json object + expect(aliceCredentialExchangeRecord.id).not.toBeNull() + expect(aliceCredentialExchangeRecord.getTags()).toEqual({ + threadId: aliceCredentialExchangeRecord.threadId, + state: aliceCredentialExchangeRecord.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + + testLogger.test('Alice sends credential request to Faber') + const negotiateOfferOptions: NegotiateOfferOptions = { + connectionId: aliceConnection.id, + protocolVersion: CredentialProtocolVersion.V1, + credentialRecordId: aliceCredentialExchangeRecord.id, + credentialFormats: { + indy: { + attributes: newCredentialPreview.attributes, + payload: { + credentialDefinitionId: credDefId, + }, + }, + }, + comment: 'v1 propose credential test', + } + const aliceExchangeCredentialRecord = await aliceAgent.credentials.negotiateOffer(negotiateOfferOptions) + + testLogger.test('Faber waits for credential proposal from Alice') + faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceExchangeCredentialRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + await sleep(5000) + + // Check if the state of fabers credential record did not change + const faberRecord = await faberAgent.credentials.getById(faberCredentialExchangeRecord.id) + faberRecord.assertState(CredentialState.ProposalReceived) + + aliceCredentialExchangeRecord = await aliceAgent.credentials.getById(aliceCredentialExchangeRecord.id) + aliceCredentialExchangeRecord.assertState(CredentialState.ProposalSent) + }) + }) +}) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2connectionless-credentials.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2connectionless-credentials.test.ts new file mode 100644 index 0000000000..a72179ddc1 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2connectionless-credentials.test.ts @@ -0,0 +1,243 @@ +import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import type { CredentialStateChangedEvent } from '../../../CredentialEvents' +import type { + AcceptOfferOptions, + AcceptRequestOptions, + OfferCredentialOptions, +} from '../../../CredentialsModuleOptions' + +import { ReplaySubject, Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' +import { prepareForIssuance, waitForCredentialRecordSubject, getBaseConfig } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { Agent } from '../../../../../agent/Agent' +import { AutoAcceptCredential } from '../../../CredentialAutoAcceptType' +import { CredentialEventTypes } from '../../../CredentialEvents' +import { CredentialProtocolVersion } from '../../../CredentialProtocolVersion' +import { CredentialState } from '../../../CredentialState' +import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import { V2CredentialPreview } from '../V2CredentialPreview' + +const faberConfig = getBaseConfig('Faber connection-less Credentials V2', { + endpoints: ['rxjs:faber'], +}) + +const aliceConfig = getBaseConfig('Alice connection-less Credentials V2', { + endpoints: ['rxjs:alice'], +}) + +const credentialPreview = V2CredentialPreview.fromRecord({ + name: 'John', + age: '99', +}) + +describe('credentials', () => { + let faberAgent: Agent + let aliceAgent: Agent + let faberReplay: ReplaySubject + let aliceReplay: ReplaySubject + let credDefId: string + let credSchemaId: string + + beforeEach(async () => { + const faberMessages = new Subject() + const aliceMessages = new Subject() + + const subjectMap = { + 'rxjs:faber': faberMessages, + 'rxjs:alice': aliceMessages, + } + faberAgent = new Agent(faberConfig.config, faberConfig.agentDependencies) + faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await faberAgent.initialize() + + aliceAgent = new Agent(aliceConfig.config, aliceConfig.agentDependencies) + aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + + const { definition, schema } = await prepareForIssuance(faberAgent, ['name', 'age']) + credDefId = definition.id + credSchemaId = schema.id + + faberReplay = new ReplaySubject() + aliceReplay = new ReplaySubject() + + faberAgent.events + .observable(CredentialEventTypes.CredentialStateChanged) + .subscribe(faberReplay) + aliceAgent.events + .observable(CredentialEventTypes.CredentialStateChanged) + .subscribe(aliceReplay) + }) + + afterEach(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Faber starts with V2 Indy connection-less credential offer to Alice', async () => { + testLogger.test('Faber sends credential offer to Alice') + + const offerOptions: OfferCredentialOptions = { + comment: 'V2 Out of Band offer', + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: credDefId, + }, + }, + protocolVersion: CredentialProtocolVersion.V2, + connectionId: '', + } + // eslint-disable-next-line prefer-const + let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOutOfBandOffer( + offerOptions + ) + + await aliceAgent.receiveMessage(message.toJSON()) + + let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + testLogger.test('Alice sends credential request to Faber') + const acceptOfferOptions: AcceptOfferOptions = { + credentialRecordId: aliceCredentialRecord.id, + } + + const credentialRecord = await aliceAgent.credentials.acceptOffer(acceptOfferOptions) + + testLogger.test('Faber waits for credential request from Alice') + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + threadId: credentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + testLogger.test('Faber sends credential to Alice') + const options: AcceptRequestOptions = { + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Credential', + } + faberCredentialRecord = await faberAgent.credentials.acceptRequest(options) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Alice sends credential ack to Faber') + aliceCredentialRecord = await aliceAgent.credentials.acceptCredential(aliceCredentialRecord.id) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_internal/indyCredential': { + schemaId: credSchemaId, + }, + }, + }, + state: CredentialState.Done, + threadId: expect.any(String), + }) + + expect(faberCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_internal/indyCredential': { + schemaId: credSchemaId, + }, + }, + }, + state: CredentialState.Done, + threadId: expect.any(String), + }) + }) + + test('Faber starts with V2 Indy connection-less credential offer to Alice with auto-accept enabled', async () => { + const offerOptions: OfferCredentialOptions = { + comment: 'V2 Out of Band offer', + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: credDefId, + }, + }, + protocolVersion: CredentialProtocolVersion.V2, + autoAcceptCredential: AutoAcceptCredential.ContentApproved, + connectionId: '', + } + // eslint-disable-next-line prefer-const + let { message, credentialRecord: faberCredentialRecord } = await faberAgent.credentials.createOutOfBandOffer( + offerOptions + ) + + // Receive Message + await aliceAgent.receiveMessage(message.toJSON()) + + // Wait for it to be processed + let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + const acceptOfferOptions: AcceptOfferOptions = { + credentialRecordId: aliceCredentialRecord.id, + autoAcceptCredential: AutoAcceptCredential.ContentApproved, + } + + await aliceAgent.credentials.acceptOffer(acceptOfferOptions) + + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_internal/indyCredential': { + schemaId: credSchemaId, + }, + }, + }, + state: CredentialState.Done, + threadId: expect.any(String), + }) + + expect(faberCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + threadId: expect.any(String), + }) + }) +}) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2credentials-architecture.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2credentials-architecture.test.ts new file mode 100644 index 0000000000..538d730658 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2credentials-architecture.test.ts @@ -0,0 +1,121 @@ +import type { ProposeCredentialOptions } from '../../../CredentialsModuleOptions' +import type { CredentialFormatService } from '../../../formats/CredentialFormatService' +import type { + FormatServiceProposeCredentialFormats, + IndyProposeCredentialFormat, +} from '../../../formats/models/CredentialFormatServiceOptions' +import type { CredentialService } from '../../../services/CredentialService' + +import { getBaseConfig } from '../../../../../../tests/helpers' +import { Agent } from '../../../../../agent/Agent' +import { CredentialProtocolVersion } from '../../../CredentialProtocolVersion' +import { CredentialsModule } from '../../../CredentialsModule' +import { CredentialFormatType } from '../../../CredentialsModuleOptions' +import { V1CredentialPreview } from '../../v1/V1CredentialPreview' +import { CredentialMessageBuilder } from '../CredentialMessageBuilder' + +const { config, agentDependencies: dependencies } = getBaseConfig('Format Service Test') + +const credentialPreview = V1CredentialPreview.fromRecord({ + name: 'John', + age: '99', +}) + +const testAttributes: IndyProposeCredentialFormat = { + attributes: credentialPreview.attributes, + payload: { + schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + schemaName: 'ahoy', + schemaVersion: '1.0', + schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', + issuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + credentialDefinitionId: 'GMm4vMw8LLrLJjp81kRRLp:3:CL:12:tag', + }, +} + +const proposal: ProposeCredentialOptions = { + connectionId: '', + protocolVersion: CredentialProtocolVersion.V1, + credentialFormats: { + indy: testAttributes, + }, + comment: 'v2 propose credential test', +} + +const multiFormatProposal: ProposeCredentialOptions = { + connectionId: '', + protocolVersion: CredentialProtocolVersion.V2, + credentialFormats: { + indy: testAttributes, + }, + comment: 'v2 propose credential test', +} + +describe('V2 Credential Architecture', () => { + const agent = new Agent(config, dependencies) + const container = agent.injectionContainer + const api = container.resolve(CredentialsModule) + + describe('Credential Service', () => { + test('returns the correct credential service for a protocol version 1.0', () => { + const version: CredentialProtocolVersion = CredentialProtocolVersion.V1 + expect(container.resolve(CredentialsModule)).toBeInstanceOf(CredentialsModule) + const service: CredentialService = api.getService(version) + expect(service.getVersion()).toEqual(CredentialProtocolVersion.V1) + }) + + test('returns the correct credential service for a protocol version 2.0', () => { + const version: CredentialProtocolVersion = CredentialProtocolVersion.V2 + const service: CredentialService = api.getService(version) + expect(service.getVersion()).toEqual(CredentialProtocolVersion.V2) + }) + }) + + describe('Credential Format Service', () => { + test('returns the correct credential format service for indy', () => { + const version: CredentialProtocolVersion = CredentialProtocolVersion.V2 + const service: CredentialService = api.getService(version) + const formatService: CredentialFormatService = service.getFormatService(CredentialFormatType.Indy) + expect(formatService).not.toBeNull() + const type: string = formatService.constructor.name + expect(type).toEqual('IndyCredentialFormatService') + }) + + test('propose credential format service returns correct format and filters~attach', () => { + const version: CredentialProtocolVersion = CredentialProtocolVersion.V2 + const service: CredentialService = api.getService(version) + const formatService: CredentialFormatService = service.getFormatService(CredentialFormatType.Indy) + const { format: formats, attachment: filtersAttach } = formatService.createProposal(proposal) + + expect(formats.attachId.length).toBeGreaterThan(0) + expect(formats.format).toEqual('hlindy/cred-filter@v2.0') + expect(filtersAttach).toBeTruthy() + }) + test('propose credential format service transforms and validates CredPropose payload correctly', () => { + const version: CredentialProtocolVersion = CredentialProtocolVersion.V2 + const service: CredentialService = api.getService(version) + const formatService: CredentialFormatService = service.getFormatService(CredentialFormatType.Indy) + const { format: formats, attachment: filtersAttach } = formatService.createProposal(proposal) + + expect(formats.attachId.length).toBeGreaterThan(0) + expect(formats.format).toEqual('hlindy/cred-filter@v2.0') + expect(filtersAttach).toBeTruthy() + }) + test('propose credential format service creates message with multiple formats', () => { + const version: CredentialProtocolVersion = CredentialProtocolVersion.V2 + const service: CredentialService = api.getService(version) + + const credFormats: FormatServiceProposeCredentialFormats = + multiFormatProposal.credentialFormats as FormatServiceProposeCredentialFormats + const formats: CredentialFormatService[] = service.getFormats(credFormats) + expect(formats.length).toBe(1) // for now will be added to with jsonld + const messageBuilder: CredentialMessageBuilder = new CredentialMessageBuilder() + + const v2Proposal = messageBuilder.createProposal(formats, multiFormatProposal) + + expect(v2Proposal.message.formats.length).toBe(1) + expect(v2Proposal.message.formats[0].format).toEqual('hlindy/cred-filter@v2.0') + // expect(v2Proposal.message.formats[1].format).toEqual('aries/ld-proof-vc-detail@v1.0') + }) + }) +}) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2credentials-auto-accept.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2credentials-auto-accept.test.ts new file mode 100644 index 0000000000..3a7fc9bb7f --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2credentials-auto-accept.test.ts @@ -0,0 +1,477 @@ +import type { Agent } from '../../../../../agent/Agent' +import type { ConnectionRecord } from '../../../../connections' +import type { + AcceptOfferOptions, + AcceptProposalOptions, + NegotiateOfferOptions, + NegotiateProposalOptions, + OfferCredentialOptions, + ProposeCredentialOptions, +} from '../../../CredentialsModuleOptions' +import type { CredPropose } from '../../../formats/models/CredPropose' +import type { Schema } from 'indy-sdk' + +import { AriesFrameworkError } from '../../../../../../src/error/AriesFrameworkError' +import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { sleep } from '../../../../../utils/sleep' +import { AutoAcceptCredential } from '../../../CredentialAutoAcceptType' +import { CredentialProtocolVersion } from '../../../CredentialProtocolVersion' +import { CredentialState } from '../../../CredentialState' +import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import { V2CredentialPreview } from '../V2CredentialPreview' + +describe('credentials', () => { + let faberAgent: Agent + let aliceAgent: Agent + let credDefId: string + let schema: Schema + let faberConnection: ConnectionRecord + let aliceConnection: ConnectionRecord + // let faberCredentialRecord: CredentialRecord + let aliceCredentialRecord: CredentialExchangeRecord + const credentialPreview = V2CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'some x-ray', + profile_picture: 'profile picture', + }) + const newCredentialPreview = V2CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'another x-ray value', + profile_picture: 'another profile picture', + }) + + describe('Auto accept on `always`', () => { + beforeAll(async () => { + ;({ faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection } = await setupCredentialTests( + 'faber agent: always v2', + 'alice agent: always v2', + AutoAcceptCredential.Always + )) + }) + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + // ============================== + // TESTS v2 BEGIN + // ========================== + test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `always`', async () => { + testLogger.test('Alice sends credential proposal to Faber') + const schemaId = schema.id + const proposeOptions: ProposeCredentialOptions = { + connectionId: aliceConnection.id, + protocolVersion: CredentialProtocolVersion.V2, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + payload: { + schemaIssuerDid: faberAgent.publicDid?.did, + schemaName: schema.name, + schemaVersion: schema.version, + schemaId: schema.id, + issuerDid: faberAgent.publicDid?.did, + credentialDefinitionId: credDefId, + }, + }, + }, + comment: 'v propose credential test', + } + const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential(proposeOptions) + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + testLogger.test('Faber waits for credential ack from Alice') + aliceCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.Done, + }) + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_internal/indyCredential': { + schemaId, + }, + }, + }, + state: CredentialState.Done, + }) + }) + test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `always`', async () => { + testLogger.test('Faber sends V2 credential offer to Alice as start of protocol process') + const schemaId = schema.id + const offerOptions: OfferCredentialOptions = { + comment: 'V2 Offer Credential', + connectionId: faberConnection.id, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: credDefId, + }, + }, + protocolVersion: CredentialProtocolVersion.V2, + } + const faberCredentialExchangeRecord: CredentialExchangeRecord = await faberAgent.credentials.offerCredential( + offerOptions + ) + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + testLogger.test('Faber waits for credential ack from Alice') + const faberCredentialRecord: CredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.Done, + }) + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_internal/indyRequest': expect.any(Object), + '_internal/indyCredential': { + schemaId, + }, + }, + }, + state: CredentialState.Done, + }) + expect(faberCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + }) + }) + }) + + describe('Auto accept on `contentApproved`', () => { + beforeAll(async () => { + ;({ faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection } = await setupCredentialTests( + 'faber agent: contentApproved v2', + 'alice agent: contentApproved v2', + AutoAcceptCredential.ContentApproved + )) + }) + + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { + testLogger.test('Alice sends credential proposal to Faber') + const schemaId = schema.id + + const proposeOptions: ProposeCredentialOptions = { + connectionId: aliceConnection.id, + protocolVersion: CredentialProtocolVersion.V2, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + payload: { + schemaIssuerDid: faberAgent.publicDid?.did, + schemaName: schema.name, + schemaVersion: schema.version, + schemaId: schema.id, + issuerDid: faberAgent.publicDid?.did, + credentialDefinitionId: credDefId, + }, + }, + }, + comment: 'v2 propose credential test', + } + const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential(proposeOptions) + + testLogger.test('Faber waits for credential proposal from Alice') + let faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + testLogger.test('Faber sends credential offer to Alice') + const options: AcceptProposalOptions = { + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Offer', + protocolVersion: CredentialProtocolVersion.V2, + credentialFormats: { + indy: { + attributes: [], + credentialDefinitionId: credDefId, + }, + }, + } + const faberCredentialExchangeRecord = await faberAgent.credentials.acceptProposal(options) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_internal/indyRequest': expect.any(Object), + '_internal/indyCredential': { + schemaId, + }, + }, + }, + state: CredentialState.Done, + }) + + expect(faberCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_internal/indyCredential': { + schemaId, + }, + }, + }, + state: CredentialState.Done, + }) + }) + test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `contentApproved`', async () => { + testLogger.test('Faber sends credential offer to Alice') + const schemaId = schema.id + + const offerOptions: OfferCredentialOptions = { + comment: 'some comment about credential', + connectionId: faberConnection.id, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: credDefId, + }, + }, + protocolVersion: CredentialProtocolVersion.V2, + } + const faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential(offerOptions) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + + // below values are not in json object + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.getTags()).toEqual({ + threadId: aliceCredentialRecord.threadId, + state: aliceCredentialRecord.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) + + if (aliceCredentialRecord.connectionId) { + // we do not need to specify connection id in this object + // it is either connectionless or included in the offer message + const acceptOfferOptions: AcceptOfferOptions = { + credentialRecordId: aliceCredentialRecord.id, + // connectionId: aliceCredentialRecord.connectionId, + // credentialRecordType: CredentialRecordType.Indy, + // protocolVersion: CredentialProtocolVersion.V2, + } + testLogger.test('Alice sends credential request to faber') + const faberCredentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.acceptOffer( + acceptOfferOptions + ) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Faber waits for credential ack from Alice') + const faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_internal/indyRequest': expect.any(Object), + '_internal/indyCredential': { + schemaId, + }, + }, + }, + state: CredentialState.Done, + }) + + expect(faberCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + }) + } else { + throw new AriesFrameworkError('missing alice connection id') + } + }) + test('Alice starts with V2 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + const credPropose: CredPropose = { + schemaIssuerDid: faberAgent.publicDid?.did, + schemaName: schema.name, + schemaVersion: schema.version, + schemaId: schema.id, + issuerDid: faberAgent.publicDid?.did, + credentialDefinitionId: credDefId, + } + const proposeOptions: ProposeCredentialOptions = { + connectionId: aliceConnection.id, + protocolVersion: CredentialProtocolVersion.V2, + credentialFormats: { + indy: { + payload: credPropose, + attributes: credentialPreview.attributes, + }, + }, + comment: 'v2 propose credential test', + } + testLogger.test('Alice sends credential proposal to Faber') + const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential(proposeOptions) + + testLogger.test('Faber waits for credential proposal from Alice') + let faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + const negotiateOptions: NegotiateProposalOptions = { + credentialRecordId: faberCredentialRecord.id, + protocolVersion: CredentialProtocolVersion.V2, + credentialFormats: { + indy: { + credentialDefinitionId: credDefId, + attributes: newCredentialPreview.attributes, + }, + }, + } + await faberAgent.credentials.negotiateProposal(negotiateOptions) + + testLogger.test('Alice waits for credential offer from Faber') + + const record = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + // below values are not in json object + expect(record.id).not.toBeNull() + expect(record.getTags()).toEqual({ + threadId: record.threadId, + state: record.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + + // Check if the state of the credential records did not change + faberCredentialRecord = await faberAgent.credentials.getById(faberCredentialRecord.id) + faberCredentialRecord.assertState(CredentialState.OfferSent) + + const aliceRecord = await aliceAgent.credentials.getById(record.id) + aliceRecord.assertState(CredentialState.OfferReceived) + }) + test('Faber starts with V2 credential offer to Alice, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + testLogger.test('Faber sends credential offer to Alice') + const offerOptions: OfferCredentialOptions = { + comment: 'some comment about credential', + connectionId: faberConnection.id, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: credDefId, + }, + }, + protocolVersion: CredentialProtocolVersion.V2, + } + const faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential(offerOptions) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + + // below values are not in json object + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.getTags()).toEqual({ + threadId: aliceCredentialRecord.threadId, + state: aliceCredentialRecord.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) + + testLogger.test('Alice sends credential request to Faber') + const proposeOptions: NegotiateOfferOptions = { + connectionId: aliceConnection.id, + protocolVersion: CredentialProtocolVersion.V2, + credentialRecordId: aliceCredentialRecord.id, + credentialFormats: { + indy: { + attributes: newCredentialPreview.attributes, + payload: { + credentialDefinitionId: credDefId, + }, + }, + }, + comment: 'v2 propose credential test', + } + await sleep(5000) + + const aliceExchangeCredentialRecord = await aliceAgent.credentials.negotiateOffer(proposeOptions) + + testLogger.test('Faber waits for credential proposal from Alice') + const faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceExchangeCredentialRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + // Check if the state of fabers credential record did not change + const faberRecord = await faberAgent.credentials.getById(faberCredentialRecord.id) + faberRecord.assertState(CredentialState.ProposalReceived) + + aliceCredentialRecord = await aliceAgent.credentials.getById(aliceCredentialRecord.id) + aliceCredentialRecord.assertState(CredentialState.ProposalSent) + }) + }) +}) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2credentials.propose-offer.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2credentials.propose-offer.test.ts new file mode 100644 index 0000000000..c93f0e5a40 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2credentials.propose-offer.test.ts @@ -0,0 +1,700 @@ +import type { Agent } from '../../../../../agent/Agent' +import type { ConnectionRecord } from '../../../../connections' +import type { ServiceAcceptOfferOptions } from '../../../CredentialServiceOptions' +import type { + AcceptOfferOptions, + AcceptProposalOptions, + AcceptRequestOptions, + NegotiateOfferOptions, + NegotiateProposalOptions, + OfferCredentialOptions, + ProposeCredentialOptions, +} from '../../../CredentialsModuleOptions' +import type { CredPropose } from '../../../formats/models/CredPropose' + +import { AriesFrameworkError } from '../../../../../../src/error/AriesFrameworkError' +import { DidCommMessageRepository } from '../../../../../../src/storage' +import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { JsonTransformer } from '../../../../../utils' +import { CredentialProtocolVersion } from '../../../CredentialProtocolVersion' +import { CredentialState } from '../../../CredentialState' +import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import { V1CredentialPreview } from '../../v1/V1CredentialPreview' +import { V1OfferCredentialMessage } from '../../v1/messages/V1OfferCredentialMessage' +import { V2CredentialPreview } from '../V2CredentialPreview' +import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' + +describe('credentials', () => { + let faberAgent: Agent + let aliceAgent: Agent + let credDefId: string + let faberConnection: ConnectionRecord + let aliceConnection: ConnectionRecord + let aliceCredentialRecord: CredentialExchangeRecord + let faberCredentialRecord: CredentialExchangeRecord + let credPropose: CredPropose + + const newCredentialPreview = V2CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'another x-ray value', + profile_picture: 'another profile picture', + }) + + let didCommMessageRepository: DidCommMessageRepository + beforeAll(async () => { + ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection } = await setupCredentialTests( + 'Faber Agent Credentials', + 'Alice Agent Credential' + )) + credPropose = { + credentialDefinitionId: credDefId, + schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + schemaName: 'ahoy', + schemaVersion: '1.0', + schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', + issuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + } + }) + + afterAll(async () => { + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + // ============================== + // TEST v1 BEGIN + // ========================== + test('Alice starts with V1 credential proposal to Faber', async () => { + const credentialPreview = V1CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'some x-ray', + profile_picture: 'profile picture', + }) + + const testAttributes = { + attributes: credentialPreview.attributes, + credentialDefinitionId: 'GMm4vMw8LLrLJjp81kRRLp:3:CL:12:tag', + payload: { + schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + schemaName: 'ahoy', + schemaVersion: '1.0', + schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', + issuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + }, + } + testLogger.test('Alice sends (v1) credential proposal to Faber') + // set the propose options + const proposeOptions: ProposeCredentialOptions = { + connectionId: aliceConnection.id, + protocolVersion: CredentialProtocolVersion.V1, + credentialFormats: { + indy: testAttributes, + }, + comment: 'v1 propose credential test', + } + + const credentialExchangeRecord = await aliceAgent.credentials.proposeCredential(proposeOptions) + + expect(credentialExchangeRecord.connectionId).toEqual(proposeOptions.connectionId) + expect(credentialExchangeRecord.protocolVersion).toEqual(CredentialProtocolVersion.V1) + expect(credentialExchangeRecord.state).toEqual(CredentialState.ProposalSent) + expect(credentialExchangeRecord.threadId).not.toBeNull() + testLogger.test('Faber waits for credential proposal from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: credentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + const options: AcceptProposalOptions = { + credentialRecordId: faberCredentialRecord.id, + comment: 'V1 Indy Proposal', + credentialFormats: { + indy: { + credentialDefinitionId: credDefId, + attributes: credentialPreview.attributes, + }, + }, + protocolVersion: CredentialProtocolVersion.V2, + } + + testLogger.test('Faber sends credential offer to Alice') + await faberAgent.credentials.acceptProposal(options) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const offerMessage = await didCommMessageRepository.findAgentMessage({ + associatedRecordId: faberCredentialRecord.id, + messageClass: V1OfferCredentialMessage, + }) + + expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ + '@id': expect.any(String), + '@type': 'https://didcomm.org/issue-credential/1.0/offer-credential', + comment: 'V1 Indy Proposal', + credential_preview: { + '@type': 'https://didcomm.org/issue-credential/1.0/credential-preview', + attributes: [ + { + name: 'name', + 'mime-type': 'text/plain', + value: 'John', + }, + { + name: 'age', + 'mime-type': 'text/plain', + value: '99', + }, + { + name: 'x-ray', + 'mime-type': 'text/plain', + value: 'some x-ray', + }, + { + name: 'profile_picture', + 'mime-type': 'text/plain', + value: 'profile picture', + }, + ], + }, + 'offers~attach': expect.any(Array), + }) + // below values are not in json object + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.getTags()).toEqual({ + threadId: faberCredentialRecord.threadId, + connectionId: aliceCredentialRecord.connectionId, + state: aliceCredentialRecord.state, + credentialIds: [], + }) + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) + if (aliceCredentialRecord.connectionId) { + const acceptOfferOptions: AcceptOfferOptions = { + credentialRecordId: aliceCredentialRecord.id, + } + const offerCredentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.acceptOffer( + acceptOfferOptions + ) + + expect(offerCredentialExchangeRecord.connectionId).toEqual(proposeOptions.connectionId) + expect(offerCredentialExchangeRecord.protocolVersion).toEqual(CredentialProtocolVersion.V1) + expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) + expect(offerCredentialExchangeRecord.threadId).not.toBeNull() + testLogger.test('Faber waits for credential request from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + const options: AcceptRequestOptions = { + credentialRecordId: faberCredentialRecord.id, + comment: 'V1 Indy Credential', + } + testLogger.test('Faber sends credential to Alice') + await faberAgent.credentials.acceptRequest(options) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.CredentialReceived, + }) + } else { + throw new AriesFrameworkError('Missing Connection Id') + } + }) + // ============================== + // TEST v1 END + // ========================== + + // -------------------------- V2 TEST BEGIN -------------------------------------------- + + test('Alice starts with V2 (Indy format) credential proposal to Faber', async () => { + const credentialPreview = V2CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'some x-ray', + profile_picture: 'profile picture', + }) + const testAttributes = { + attributes: credentialPreview.attributes, + schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + schemaName: 'ahoy', + schemaVersion: '1.0', + schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', + issuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + credentialDefinitionId: 'GMm4vMw8LLrLJjp81kRRLp:3:CL:12:tag', + payload: { + schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + schemaName: 'ahoy', + schemaVersion: '1.0', + schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', + issuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + }, + } + testLogger.test('Alice sends (v2) credential proposal to Faber') + // set the propose options + // we should set the version to V1.0 and V2.0 in separate tests, one as a regression test + const proposeOptions: ProposeCredentialOptions = { + connectionId: aliceConnection.id, + protocolVersion: CredentialProtocolVersion.V2, + credentialFormats: { + indy: testAttributes, + }, + comment: 'v2 propose credential test', + } + testLogger.test('Alice sends (v2, Indy) credential proposal to Faber') + + const credentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.proposeCredential( + proposeOptions + ) + + expect(credentialExchangeRecord.connectionId).toEqual(proposeOptions.connectionId) + expect(credentialExchangeRecord.protocolVersion).toEqual(CredentialProtocolVersion.V2) + expect(credentialExchangeRecord.state).toEqual(CredentialState.ProposalSent) + expect(credentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential proposal from Alice') + let faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: credentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + const options: AcceptProposalOptions = { + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Offer', + credentialFormats: { + indy: { + credentialDefinitionId: credDefId, + attributes: credentialPreview.attributes, + }, + }, + protocolVersion: CredentialProtocolVersion.V2, + } + testLogger.test('Faber sends credential offer to Alice') + await faberAgent.credentials.acceptProposal(options) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + didCommMessageRepository = faberAgent.injectionContainer.resolve(DidCommMessageRepository) + + const offerMessage = await didCommMessageRepository.findAgentMessage({ + associatedRecordId: faberCredentialRecord.id, + messageClass: V2OfferCredentialMessage, + }) + + expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', + comment: 'V2 Indy Offer', + credential_preview: { + '@type': 'https://didcomm.org/issue-credential/2.0/credential-preview', + attributes: [ + { + name: 'name', + 'mime-type': 'text/plain', + value: 'John', + }, + { + name: 'age', + 'mime-type': 'text/plain', + value: '99', + }, + { + name: 'x-ray', + 'mime-type': 'text/plain', + value: 'some x-ray', + }, + { + name: 'profile_picture', + 'mime-type': 'text/plain', + value: 'profile picture', + }, + ], + }, + }) + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.getTags()).toEqual({ + threadId: faberCredentialRecord.threadId, + credentialIds: [], + connectionId: aliceCredentialRecord.connectionId, + state: aliceCredentialRecord.state, + }) + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) + + if (aliceCredentialRecord.connectionId) { + const acceptOfferOptions: ServiceAcceptOfferOptions = { + credentialRecordId: aliceCredentialRecord.id, + credentialFormats: { + indy: undefined, + }, + } + const offerCredentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.acceptOffer( + acceptOfferOptions + ) + + expect(offerCredentialExchangeRecord.connectionId).toEqual(proposeOptions.connectionId) + expect(offerCredentialExchangeRecord.protocolVersion).toEqual(CredentialProtocolVersion.V2) + expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) + expect(offerCredentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential request from Alice') + await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + testLogger.test('Faber sends credential to Alice') + + const options: AcceptRequestOptions = { + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Credential', + } + await faberAgent.credentials.acceptRequest(options) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + await aliceAgent.credentials.acceptCredential(aliceCredentialRecord.id) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + } else { + throw new AriesFrameworkError('Missing Connection Id') + } + }) + + test('Alice starts with propose - Faber counter offer - Alice second proposal- Faber sends second offer', async () => { + // proposeCredential -> negotiateProposal -> negotiateOffer -> negotiateProposal -> acceptOffer -> acceptRequest -> DONE (credential issued) + const credentialPreview = V2CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'some x-ray', + profile_picture: 'profile picture', + }) + + const proposeOptions: ProposeCredentialOptions = { + connectionId: aliceConnection.id, + protocolVersion: CredentialProtocolVersion.V2, + credentialFormats: { + indy: { + payload: credPropose, + attributes: credentialPreview.attributes, + }, + }, + comment: 'v2 propose credential test', + } + testLogger.test('Alice sends credential proposal to Faber') + let aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential(proposeOptions) + expect(aliceCredentialExchangeRecord.state).toBe(CredentialState.ProposalSent) + + testLogger.test('Faber waits for credential proposal from Alice') + let faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + const negotiateOptions: NegotiateProposalOptions = { + credentialRecordId: faberCredentialRecord.id, + credentialFormats: { + indy: { + credentialDefinitionId: credDefId, + attributes: newCredentialPreview.attributes, + }, + }, + protocolVersion: CredentialProtocolVersion.V2, + } + faberCredentialRecord = await faberAgent.credentials.negotiateProposal(negotiateOptions) + + testLogger.test('Alice waits for credential offer from Faber') + + let record = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + // below values are not in json object + expect(record.id).not.toBeNull() + expect(record.getTags()).toEqual({ + threadId: record.threadId, + state: record.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + + // // Check if the state of the credential records did not change + faberCredentialRecord = await faberAgent.credentials.getById(faberCredentialRecord.id) + faberCredentialRecord.assertState(CredentialState.OfferSent) + + const aliceRecord = await aliceAgent.credentials.getById(record.id) + aliceRecord.assertState(CredentialState.OfferReceived) + + // // second proposal + const negotiateOfferOptions: NegotiateOfferOptions = { + credentialRecordId: aliceRecord.id, + credentialFormats: { + indy: { + payload: credPropose, + attributes: newCredentialPreview.attributes, + }, + }, + connectionId: aliceConnection.id, + } + aliceCredentialExchangeRecord = await aliceAgent.credentials.negotiateOffer(negotiateOfferOptions) + + // aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential(proposeOptions) + expect(aliceCredentialExchangeRecord.state).toBe(CredentialState.ProposalSent) + + testLogger.test('Faber waits for credential proposal from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + faberCredentialRecord = await faberAgent.credentials.negotiateProposal(negotiateOptions) + + testLogger.test('Alice waits for credential offer from Faber') + + record = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + const acceptOfferOptions: AcceptOfferOptions = { + credentialRecordId: aliceCredentialExchangeRecord.id, + } + const offerCredentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.acceptOffer( + acceptOfferOptions + ) + + expect(offerCredentialExchangeRecord.connectionId).toEqual(proposeOptions.connectionId) + expect(offerCredentialExchangeRecord.protocolVersion).toEqual(CredentialProtocolVersion.V2) + expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) + expect(offerCredentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential request from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialExchangeRecord.threadId, + state: CredentialState.RequestReceived, + }) + testLogger.test('Faber sends credential to Alice') + + const options: AcceptRequestOptions = { + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Credential', + } + await faberAgent.credentials.acceptRequest(options) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + // testLogger.test('Alice sends credential ack to Faber') + await aliceAgent.credentials.acceptCredential(aliceCredentialRecord.id) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: expect.any(String), + connectionId: expect.any(String), + state: CredentialState.CredentialReceived, + }) + }) + + test('Faber starts with offer - Alice counter proposal - Faber second offer - Alice sends second proposal', async () => { + testLogger.test('Faber sends credential offer to Alice') + const credentialPreview = V2CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'some x-ray', + profile_picture: 'profile picture', + }) + const offerOptions: OfferCredentialOptions = { + comment: 'some comment about credential', + connectionId: faberConnection.id, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: credDefId, + }, + }, + protocolVersion: CredentialProtocolVersion.V2, + } + const faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential(offerOptions) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + + const negotiateOfferOptions: NegotiateOfferOptions = { + credentialRecordId: aliceCredentialRecord.id, + credentialFormats: { + indy: { + payload: credPropose, + attributes: newCredentialPreview.attributes, + }, + }, + connectionId: aliceConnection.id, + } + aliceCredentialRecord = await aliceAgent.credentials.negotiateOffer(negotiateOfferOptions) + + // aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential(proposeOptions) + expect(aliceCredentialRecord.state).toBe(CredentialState.ProposalSent) + + testLogger.test('Faber waits for credential proposal from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.ProposalReceived, + }) + const negotiateOptions: NegotiateProposalOptions = { + credentialRecordId: faberCredentialRecord.id, + credentialFormats: { + indy: { + credentialDefinitionId: credDefId, + attributes: newCredentialPreview.attributes, + }, + }, + protocolVersion: CredentialProtocolVersion.V2, + } + faberCredentialRecord = await faberAgent.credentials.negotiateProposal(negotiateOptions) + + testLogger.test('Alice waits for credential offer from Faber') + + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + aliceCredentialRecord = await aliceAgent.credentials.negotiateOffer(negotiateOfferOptions) + + // aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential(proposeOptions) + expect(aliceCredentialRecord.state).toBe(CredentialState.ProposalSent) + + testLogger.test('Faber waits for credential proposal from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.ProposalReceived, + }) + + const options: AcceptProposalOptions = { + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Proposal', + credentialFormats: { + indy: { + credentialDefinitionId: credDefId, + attributes: credentialPreview.attributes, + }, + }, + protocolVersion: CredentialProtocolVersion.V2, + } + + testLogger.test('Faber sends credential offer to Alice') + await faberAgent.credentials.acceptProposal(options) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.OfferReceived, + }) + + const acceptOfferOptions: AcceptOfferOptions = { + credentialRecordId: aliceCredentialRecord.id, + } + const offerCredentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.acceptOffer( + acceptOfferOptions + ) + + expect(offerCredentialExchangeRecord.protocolVersion).toEqual(CredentialProtocolVersion.V2) + expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) + expect(offerCredentialExchangeRecord.threadId).not.toBeNull() + testLogger.test('Faber waits for credential request from Alice') + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + const acceptRequestOptions: AcceptRequestOptions = { + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Credential', + } + testLogger.test('Faber sends credential to Alice') + await faberAgent.credentials.acceptRequest(acceptRequestOptions) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.CredentialReceived, + }) + }) + + test('Faber starts with V2 offer; Alice declines', async () => { + testLogger.test('Faber sends credential offer to Alice') + const credentialPreview = V2CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'some x-ray', + profile_picture: 'profile picture', + }) + const offerOptions: OfferCredentialOptions = { + comment: 'some comment about credential', + connectionId: faberConnection.id, + credentialFormats: { + indy: { + attributes: credentialPreview.attributes, + credentialDefinitionId: credDefId, + }, + }, + protocolVersion: CredentialProtocolVersion.V2, + } + const faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential(offerOptions) + + testLogger.test('Alice waits for credential offer from Faber') + aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + // below values are not in json object + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.getTags()).toEqual({ + threadId: aliceCredentialRecord.threadId, + state: aliceCredentialRecord.state, + connectionId: aliceConnection.id, + credentialIds: [], + }) + expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) + testLogger.test('Alice declines offer') + if (aliceCredentialRecord.id) { + await aliceAgent.credentials.declineOffer(aliceCredentialRecord.id) + } else { + throw new AriesFrameworkError('Missing credential record id') + } + }) +}) +// -------------------------- V2 TEST END -------------------------------------------- diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialAckHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialAckHandler.ts new file mode 100644 index 0000000000..3794e260ba --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialAckHandler.ts @@ -0,0 +1,17 @@ +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { V2CredentialService } from '../V2CredentialService' + +import { V2CredentialAckMessage } from '../messages/V2CredentialAckMessage' + +export class V2CredentialAckHandler implements Handler { + private credentialService: V2CredentialService + public supportedMessages = [V2CredentialAckMessage] + + public constructor(credentialService: V2CredentialService) { + this.credentialService = credentialService + } + + public async handle(messageContext: HandlerInboundMessage) { + await this.credentialService.processAck(messageContext) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialProblemReportHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialProblemReportHandler.ts new file mode 100644 index 0000000000..914c902691 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2CredentialProblemReportHandler.ts @@ -0,0 +1,17 @@ +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { V2CredentialService } from '../V2CredentialService' + +import { V2CredentialProblemReportMessage } from '../messages/V2CredentialProblemReportMessage' + +export class V2CredentialProblemReportHandler implements Handler { + private credentialService: V2CredentialService + public supportedMessages = [V2CredentialProblemReportMessage] + + public constructor(credentialService: V2CredentialService) { + this.credentialService = credentialService + } + + public async handle(messageContext: HandlerInboundMessage) { + await this.credentialService.processProblemReport(messageContext) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts new file mode 100644 index 0000000000..6ff4e63b75 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2IssueCredentialHandler.ts @@ -0,0 +1,77 @@ +import type { AgentConfig } from '../../../../../agent/AgentConfig' +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import type { DidCommMessageRepository } from '../../../../../storage' +import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import type { V2CredentialService } from '../V2CredentialService' + +import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' +import { V2IssueCredentialMessage } from '../messages/V2IssueCredentialMessage' +import { V2RequestCredentialMessage } from '../messages/V2RequestCredentialMessage' + +export class V2IssueCredentialHandler implements Handler { + private credentialService: V2CredentialService + private agentConfig: AgentConfig + private didCommMessageRepository: DidCommMessageRepository + + public supportedMessages = [V2IssueCredentialMessage] + + public constructor( + credentialService: V2CredentialService, + agentConfig: AgentConfig, + didCommMessageRepository: DidCommMessageRepository + ) { + this.credentialService = credentialService + this.agentConfig = agentConfig + this.didCommMessageRepository = didCommMessageRepository + } + public async handle(messageContext: InboundMessageContext) { + const credentialRecord = await this.credentialService.processCredential(messageContext) + const credentialMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2IssueCredentialMessage, + }) + + const requestMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2RequestCredentialMessage, + }) + + if (!credentialMessage) { + throw new AriesFrameworkError(`Missing credential message from credential record ${credentialRecord.id}`) + } + + const shouldAutoRespond = this.credentialService.shouldAutoRespondToCredential(credentialRecord, credentialMessage) + if (shouldAutoRespond) { + return await this.createAck(credentialRecord, messageContext, requestMessage ?? undefined, credentialMessage) + } + } + + private async createAck( + record: CredentialExchangeRecord, + messageContext: HandlerInboundMessage, + requestMessage?: V2RequestCredentialMessage, + credentialMessage?: V2IssueCredentialMessage + ) { + this.agentConfig.logger.info( + `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + ) + const { message } = await this.credentialService.createAck(record) + + if (messageContext.connection) { + return createOutboundMessage(messageContext.connection, message) + } else if (requestMessage?.service && credentialMessage?.service) { + const recipientService = credentialMessage.service + const ourService = requestMessage.service + + return createOutboundServiceMessage({ + payload: message, + service: recipientService.toDidCommService(), + senderKey: ourService.recipientKeys[0], + }) + } + + this.agentConfig.logger.error(`Could not automatically create credential ack`) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts new file mode 100644 index 0000000000..4c6ec1be02 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts @@ -0,0 +1,118 @@ +import type { AgentConfig } from '../../../../../agent/AgentConfig' +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import type { DidCommMessageRepository } from '../../../../../storage' +import type { MediationRecipientService } from '../../../../routing/services/MediationRecipientService' +import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import type { V2CredentialService } from '../V2CredentialService' + +import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' +import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' +import { DidCommMessageRole } from '../../../../../storage' +import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' +import { V2ProposeCredentialMessage } from '../messages/V2ProposeCredentialMessage' + +export class V2OfferCredentialHandler implements Handler { + private credentialService: V2CredentialService + private agentConfig: AgentConfig + private mediationRecipientService: MediationRecipientService + public supportedMessages = [V2OfferCredentialMessage] + + private didCommMessageRepository: DidCommMessageRepository + + public constructor( + credentialService: V2CredentialService, + agentConfig: AgentConfig, + mediationRecipientService: MediationRecipientService, + didCommMessageRepository: DidCommMessageRepository + ) { + this.credentialService = credentialService + this.agentConfig = agentConfig + this.mediationRecipientService = mediationRecipientService + this.didCommMessageRepository = didCommMessageRepository + } + + public async handle(messageContext: InboundMessageContext) { + const credentialRecord = await this.credentialService.processOffer(messageContext) + + const offerMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2OfferCredentialMessage, + }) + + const proposeMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2ProposeCredentialMessage, + }) + + if (!offerMessage) { + throw new AriesFrameworkError('Missing offer message in V2OfferCredentialHandler') + } + + const shouldAutoRespond = this.credentialService.shouldAutoRespondToOffer( + credentialRecord, + offerMessage, + proposeMessage ?? undefined + ) + + if (shouldAutoRespond) { + return await this.createRequest(credentialRecord, messageContext, offerMessage) + } + } + + private async createRequest( + record: CredentialExchangeRecord, + messageContext: HandlerInboundMessage, + offerMessage?: V2OfferCredentialMessage + ) { + this.agentConfig.logger.info( + `Automatically sending request with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + ) + + if (messageContext.connection) { + const { message, credentialRecord } = await this.credentialService.createRequest( + record, + {}, + messageContext.connection.did + ) + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: message, + role: DidCommMessageRole.Receiver, + associatedRecordId: credentialRecord.id, + }) + return createOutboundMessage(messageContext.connection, message) + } else if (offerMessage?.service) { + const routing = await this.mediationRecipientService.getRouting() + const ourService = new ServiceDecorator({ + serviceEndpoint: routing.endpoints[0], + recipientKeys: [routing.verkey], + routingKeys: routing.routingKeys, + }) + const recipientService = offerMessage.service + + const { message, credentialRecord } = await this.credentialService.createRequest( + record, + {}, + ourService.recipientKeys[0] + ) + + // Set and save ~service decorator to record (to remember our verkey) + message.service = ourService + + await this.credentialService.update(credentialRecord) + await this.didCommMessageRepository.saveAgentMessage({ + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: credentialRecord.id, + }) + return createOutboundServiceMessage({ + payload: message, + service: recipientService.toDidCommService(), + senderKey: ourService.recipientKeys[0], + }) + } + + this.agentConfig.logger.error(`Could not automatically create credential request`) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts new file mode 100644 index 0000000000..4e97bc36cf --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2ProposeCredentialHandler.ts @@ -0,0 +1,60 @@ +import type { AgentConfig } from '../../../../../agent/AgentConfig' +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import type { DidCommMessageRepository } from '../../../../../storage' +import type { HandlerAutoAcceptOptions } from '../../../formats/models/CredentialFormatServiceOptions' +import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import type { V2CredentialService } from '../V2CredentialService' + +import { createOutboundMessage } from '../../../../../agent/helpers' +import { V2ProposeCredentialMessage } from '../messages/V2ProposeCredentialMessage' + +export class V2ProposeCredentialHandler implements Handler { + private credentialService: V2CredentialService + private agentConfig: AgentConfig + private didCommMessageRepository: DidCommMessageRepository + + public supportedMessages = [V2ProposeCredentialMessage] + + public constructor( + credentialService: V2CredentialService, + agentConfig: AgentConfig, + didCommMessageRepository: DidCommMessageRepository + ) { + this.credentialService = credentialService + this.agentConfig = agentConfig + this.didCommMessageRepository = didCommMessageRepository + } + + public async handle(messageContext: InboundMessageContext) { + const credentialRecord = await this.credentialService.processProposal(messageContext) + + const handlerOptions: HandlerAutoAcceptOptions = { + credentialRecord, + autoAcceptType: this.agentConfig.autoAcceptCredentials, + } + + const shouldAutoRespond = await this.credentialService.shouldAutoRespondToProposal(handlerOptions) + if (shouldAutoRespond) { + return await this.createOffer(credentialRecord, messageContext) + } + } + + private async createOffer( + credentialRecord: CredentialExchangeRecord, + messageContext: HandlerInboundMessage + ) { + this.agentConfig.logger.info( + `Automatically sending offer with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + ) + + if (!messageContext.connection) { + this.agentConfig.logger.error('No connection on the messageContext, aborting auto accept') + return + } + + const message = await this.credentialService.createOfferAsResponse(credentialRecord) + + return createOutboundMessage(messageContext.connection, message) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts new file mode 100644 index 0000000000..abcdb10ad7 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RequestCredentialHandler.ts @@ -0,0 +1,96 @@ +import type { AgentConfig } from '../../../../../agent/AgentConfig' +import type { Handler } from '../../../../../agent/Handler' +import type { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import type { DidCommMessageRepository } from '../../../../../storage' +import type { AcceptRequestOptions } from '../../../CredentialsModuleOptions' +import type { CredentialExchangeRecord } from '../../../repository' +import type { V2CredentialService } from '../V2CredentialService' + +import { createOutboundMessage, createOutboundServiceMessage } from '../../../../../agent/helpers' +import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' +import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' +import { V2ProposeCredentialMessage } from '../messages/V2ProposeCredentialMessage' +import { V2RequestCredentialMessage } from '../messages/V2RequestCredentialMessage' + +export class V2RequestCredentialHandler implements Handler { + private credentialService: V2CredentialService + private agentConfig: AgentConfig + private didCommMessageRepository: DidCommMessageRepository + public supportedMessages = [V2RequestCredentialMessage] + + public constructor( + credentialService: V2CredentialService, + agentConfig: AgentConfig, + didCommMessageRepository: DidCommMessageRepository + ) { + this.credentialService = credentialService + this.agentConfig = agentConfig + this.didCommMessageRepository = didCommMessageRepository + } + + public async handle(messageContext: InboundMessageContext) { + const credentialRecord = await this.credentialService.processRequest(messageContext) + const requestMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2RequestCredentialMessage, + }) + + if (!requestMessage) { + throw new AriesFrameworkError('Missing request message in V2RequestCredentialHandler') + } + const offerMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2OfferCredentialMessage, + }) + + const proposeMessage = await this.didCommMessageRepository.findAgentMessage({ + associatedRecordId: credentialRecord.id, + messageClass: V2ProposeCredentialMessage, + }) + + const shouldAutoRespond = this.credentialService.shouldAutoRespondToRequest( + credentialRecord, + requestMessage, + proposeMessage ?? undefined, + offerMessage ?? undefined + ) + if (shouldAutoRespond) { + return await this.createCredential(credentialRecord, messageContext, requestMessage, offerMessage) + } + } + + private async createCredential( + record: CredentialExchangeRecord, + messageContext: InboundMessageContext, + requestMessage: V2RequestCredentialMessage, + offerMessage?: V2OfferCredentialMessage | null + ) { + this.agentConfig.logger.info( + `Automatically sending credential with autoAccept on ${this.agentConfig.autoAcceptCredentials}` + ) + const options: AcceptRequestOptions = { + comment: requestMessage.comment, + autoAcceptCredential: record.autoAcceptCredential, + credentialRecordId: record.id, + } + + const { message, credentialRecord } = await this.credentialService.createCredential(record, options) + if (messageContext.connection) { + return createOutboundMessage(messageContext.connection, message) + } else if (requestMessage.service && offerMessage?.service) { + const recipientService = requestMessage.service + const ourService = offerMessage.service + + // Set ~service, update message in record (for later use) + message.setService(ourService) + await this.credentialService.update(credentialRecord) + + return createOutboundServiceMessage({ + payload: message, + service: recipientService.toDidCommService(), + senderKey: ourService.recipientKeys[0], + }) + } + this.agentConfig.logger.error(`Could not automatically create credential request`) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2RevocationNotificationHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RevocationNotificationHandler.ts new file mode 100644 index 0000000000..54fcdfbc44 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2RevocationNotificationHandler.ts @@ -0,0 +1,17 @@ +import type { Handler, HandlerInboundMessage } from '../../../../../agent/Handler' +import type { RevocationService } from '../../../services' + +import { V2RevocationNotificationMessage } from '../messages/V2RevocationNotificationMessage' + +export class V2RevocationNotificationHandler implements Handler { + private revocationService: RevocationService + public supportedMessages = [V2RevocationNotificationMessage] + + public constructor(revocationService: RevocationService) { + this.revocationService = revocationService + } + + public async handle(messageContext: HandlerInboundMessage) { + await this.revocationService.v2ProcessRevocationNotification(messageContext) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/index.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/index.ts new file mode 100644 index 0000000000..9a291bf883 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/index.ts @@ -0,0 +1,5 @@ +export * from './V2CredentialAckHandler' +export * from './V2IssueCredentialHandler' +export * from './V2OfferCredentialHandler' +export * from './V2ProposeCredentialHandler' +export * from './V2RequestCredentialHandler' diff --git a/packages/core/src/modules/credentials/protocol/v2/index.ts b/packages/core/src/modules/credentials/protocol/v2/index.ts new file mode 100644 index 0000000000..4a587629e5 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/index.ts @@ -0,0 +1,2 @@ +export * from '../../CredentialServiceOptions' +export * from './V2CredentialService' diff --git a/packages/core/src/modules/credentials/protocol/v2/messages/V2CredentialAckMessage.ts b/packages/core/src/modules/credentials/protocol/v2/messages/V2CredentialAckMessage.ts new file mode 100644 index 0000000000..3f2a1420c0 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/messages/V2CredentialAckMessage.ts @@ -0,0 +1,24 @@ +import type { AckMessageOptions } from '../../../../common' + +import { Equals } from 'class-validator' + +import { AckMessage } from '../../../../common' + +export type CredentialAckMessageOptions = AckMessageOptions + +/** + * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0015-acks/README.md#explicit-acks + */ +export class V2CredentialAckMessage extends AckMessage { + /** + * Create new CredentialAckMessage instance. + * @param options + */ + public constructor(options: CredentialAckMessageOptions) { + super(options) + } + + @Equals(V2CredentialAckMessage.type) + public readonly type = V2CredentialAckMessage.type + public static readonly type = 'https://didcomm.org/issue-credential/2.0/ack' +} diff --git a/packages/core/src/modules/credentials/protocol/v2/messages/V2CredentialProblemReportMessage.ts b/packages/core/src/modules/credentials/protocol/v2/messages/V2CredentialProblemReportMessage.ts new file mode 100644 index 0000000000..01a8506e33 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/messages/V2CredentialProblemReportMessage.ts @@ -0,0 +1,24 @@ +import type { ProblemReportMessageOptions } from '../../../../problem-reports/messages/ProblemReportMessage' + +import { Equals } from 'class-validator' + +import { ProblemReportMessage } from '../../../../problem-reports/messages/ProblemReportMessage' + +export type CredentialProblemReportMessageOptions = ProblemReportMessageOptions + +/** + * @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0035-report-problem/README.md + */ +export class V2CredentialProblemReportMessage extends ProblemReportMessage { + /** + * Create new CredentialProblemReportMessage instance. + * @param options + */ + public constructor(options: CredentialProblemReportMessageOptions) { + super(options) + } + + @Equals(V2CredentialProblemReportMessage.type) + public readonly type = V2CredentialProblemReportMessage.type + public static readonly type = 'https://didcomm.org/issue-credential/2.0/problem-report' +} diff --git a/packages/core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage.ts b/packages/core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage.ts new file mode 100644 index 0000000000..9a84f05ac5 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage.ts @@ -0,0 +1,48 @@ +import { Expose, Type } from 'class-transformer' +import { Equals, IsArray, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' + +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { Attachment } from '../../../../../decorators/attachment/Attachment' +import { CredentialFormatSpec } from '../../../formats/models/CredentialFormatServiceOptions' + +export interface V2IssueCredentialMessageProps { + id?: string + comment?: string + formats: CredentialFormatSpec[] + credentialsAttach: Attachment[] +} + +export class V2IssueCredentialMessage extends AgentMessage { + public constructor(options: V2IssueCredentialMessageProps) { + super() + + if (options) { + this.id = options.id ?? this.generateId() + this.comment = options.comment + this.formats = options.formats + this.messageAttachment = options.credentialsAttach + } + } + @Type(() => CredentialFormatSpec) + @ValidateNested() + @IsArray() + // @IsInstance(CredentialFormatSpec, { each: true }) -> this causes message validation to fail + public formats!: CredentialFormatSpec[] + + @Equals(V2IssueCredentialMessage.type) + public readonly type = V2IssueCredentialMessage.type + public static readonly type = 'https://didcomm.org/issue-credential/2.0/issue-credential' + + @IsString() + @IsOptional() + public comment?: string + + @Expose({ name: 'credentials~attach' }) + @Type(() => Attachment) + @IsArray() + @ValidateNested({ + each: true, + }) + @IsInstance(Attachment, { each: true }) + public messageAttachment!: Attachment[] +} diff --git a/packages/core/src/modules/credentials/protocol/v2/messages/V2OfferCredentialMessage.ts b/packages/core/src/modules/credentials/protocol/v2/messages/V2OfferCredentialMessage.ts new file mode 100644 index 0000000000..76abc63fb5 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/messages/V2OfferCredentialMessage.ts @@ -0,0 +1,63 @@ +import { Expose, Type } from 'class-transformer' +import { Equals, IsArray, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' + +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { Attachment } from '../../../../../decorators/attachment/Attachment' +import { CredentialFormatSpec } from '../../../formats/models/CredentialFormatServiceOptions' +import { V2CredentialPreview } from '../V2CredentialPreview' + +export interface V2OfferCredentialMessageOptions { + id?: string + formats: CredentialFormatSpec[] + offerAttachments: Attachment[] + credentialPreview: V2CredentialPreview + replacementId?: string + comment?: string +} + +export class V2OfferCredentialMessage extends AgentMessage { + public constructor(options: V2OfferCredentialMessageOptions) { + super() + if (options) { + this.id = options.id ?? this.generateId() + this.comment = options.comment + this.formats = options.formats + this.credentialPreview = options.credentialPreview + this.messageAttachment = options.offerAttachments + } + } + + @Type(() => CredentialFormatSpec) + @ValidateNested() + @IsArray() + // @IsInstance(CredentialFormatSpec, { each: true }) -> this causes message validation to fail + public formats!: CredentialFormatSpec[] + + @Equals(V2OfferCredentialMessage.type) + public readonly type = V2OfferCredentialMessage.type + public static readonly type = 'https://didcomm.org/issue-credential/2.0/offer-credential' + + @IsString() + @IsOptional() + public comment?: string + + @Expose({ name: 'credential_preview' }) + @Type(() => V2CredentialPreview) + @ValidateNested() + @IsInstance(V2CredentialPreview) + public credentialPreview?: V2CredentialPreview + + @Expose({ name: 'offers~attach' }) + @Type(() => Attachment) + @IsArray() + @ValidateNested({ + each: true, + }) + @IsInstance(Attachment, { each: true }) + public messageAttachment!: Attachment[] + + @Expose({ name: 'replacement_id' }) + @IsString() + @IsOptional() + public replacementId?: string +} diff --git a/packages/core/src/modules/credentials/protocol/v2/messages/V2ProposeCredentialMessage.ts b/packages/core/src/modules/credentials/protocol/v2/messages/V2ProposeCredentialMessage.ts new file mode 100644 index 0000000000..88a8e3a6f3 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/messages/V2ProposeCredentialMessage.ts @@ -0,0 +1,63 @@ +import { Expose, Type } from 'class-transformer' +import { Equals, IsArray, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' + +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { Attachment } from '../../../../../decorators/attachment/Attachment' +import { CredentialFormatSpec } from '../../../formats/models/CredentialFormatServiceOptions' +import { V2CredentialPreview } from '../V2CredentialPreview' + +export interface V2ProposeCredentialMessageProps { + id?: string + formats: CredentialFormatSpec[] + filtersAttach: Attachment[] + comment?: string + credentialProposal?: V2CredentialPreview + attachments?: Attachment[] +} + +export class V2ProposeCredentialMessage extends AgentMessage { + public constructor(props: V2ProposeCredentialMessageProps) { + super() + if (props) { + this.id = props.id ?? this.generateId() + this.comment = props.comment + this.credentialProposal = props.credentialProposal + this.formats = props.formats + this.messageAttachment = props.filtersAttach + this.appendedAttachments = props.attachments + } + } + + @Type(() => CredentialFormatSpec) + @ValidateNested() + @IsArray() + public formats!: CredentialFormatSpec[] + + @Equals(V2ProposeCredentialMessage.type) + public readonly type = V2ProposeCredentialMessage.type + public static readonly type = 'https://didcomm.org/issue-credential/2.0/propose-credential' + + @Expose({ name: 'credential_proposal' }) + @Type(() => V2CredentialPreview) + @ValidateNested() + @IsOptional() + @IsInstance(V2CredentialPreview) + public credentialProposal?: V2CredentialPreview + + @Expose({ name: 'filters~attach' }) + @Type(() => Attachment) + @IsArray() + @ValidateNested({ + each: true, + }) + @IsInstance(Attachment, { each: true }) + public messageAttachment!: Attachment[] + + /** + * Human readable information about this Credential Proposal, + * so the proposal can be evaluated by human judgment. + */ + @IsOptional() + @IsString() + public comment?: string +} diff --git a/packages/core/src/modules/credentials/protocol/v2/messages/V2RequestCredentialMessage.ts b/packages/core/src/modules/credentials/protocol/v2/messages/V2RequestCredentialMessage.ts new file mode 100644 index 0000000000..7c93c953de --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/messages/V2RequestCredentialMessage.ts @@ -0,0 +1,52 @@ +import { Expose, Type } from 'class-transformer' +import { Equals, IsArray, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' + +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { Attachment } from '../../../../../decorators/attachment/Attachment' +import { CredentialFormatSpec } from '../../../formats/models/CredentialFormatServiceOptions' + +export interface V2RequestCredentialMessageOptions { + id?: string + formats: CredentialFormatSpec[] + requestsAttach: Attachment[] + comment?: string +} + +export class V2RequestCredentialMessage extends AgentMessage { + public constructor(options: V2RequestCredentialMessageOptions) { + super() + if (options) { + this.id = options.id ?? this.generateId() + this.comment = options.comment + this.formats = options.formats + this.messageAttachment = options.requestsAttach + } + } + + @Type(() => CredentialFormatSpec) + @ValidateNested() + @IsArray() + // @IsInstance(CredentialFormatSpec, { each: true }) -> this causes message validation to fail + public formats!: CredentialFormatSpec[] + + @Equals(V2RequestCredentialMessage.type) + public readonly type = V2RequestCredentialMessage.type + public static readonly type = 'https://didcomm.org/issue-credential/2.0/request-credential' + + @Expose({ name: 'requests~attach' }) + @Type(() => Attachment) + @IsArray() + @ValidateNested({ + each: true, + }) + @IsInstance(Attachment, { each: true }) + public messageAttachment!: Attachment[] + + /** + * Human readable information about this Credential Request, + * so the proposal can be evaluated by human judgment. + */ + @IsOptional() + @IsString() + public comment?: string +} diff --git a/packages/core/src/modules/credentials/messages/RevocationNotificationMessage.ts b/packages/core/src/modules/credentials/protocol/v2/messages/V2RevocationNotificationMessage.ts similarity index 53% rename from packages/core/src/modules/credentials/messages/RevocationNotificationMessage.ts rename to packages/core/src/modules/credentials/protocol/v2/messages/V2RevocationNotificationMessage.ts index 7e41a2eba3..dd5fee3967 100644 --- a/packages/core/src/modules/credentials/messages/RevocationNotificationMessage.ts +++ b/packages/core/src/modules/credentials/protocol/v2/messages/V2RevocationNotificationMessage.ts @@ -1,40 +1,9 @@ -import type { AckDecorator } from '../../../decorators/ack/AckDecorator' +import type { AckDecorator } from '../../../../../decorators/ack/AckDecorator' import { Expose } from 'class-transformer' import { Equals, IsOptional, IsString } from 'class-validator' -import { AgentMessage } from '../../../agent/AgentMessage' - -export interface RevocationNotificationMessageV1Options { - issueThread: string - id?: string - comment?: string - pleaseAck?: AckDecorator -} - -export class V1RevocationNotificationMessage extends AgentMessage { - public constructor(options: RevocationNotificationMessageV1Options) { - super() - if (options) { - this.issueThread = options.issueThread - this.id = options.id ?? this.generateId() - this.comment = options.comment - this.pleaseAck = options.pleaseAck - } - } - - @Equals(V1RevocationNotificationMessage.type) - public readonly type = V1RevocationNotificationMessage.type - public static readonly type = 'https://didcomm.org/revocation_notification/1.0/revoke' - - @IsString() - @IsOptional() - public comment?: string - - @Expose({ name: 'thread_id' }) - @IsString() - public issueThread!: string -} +import { AgentMessage } from '../../../../../agent/AgentMessage' export interface RevocationNotificationMessageV2Options { revocationFormat: string diff --git a/packages/core/src/modules/credentials/repository/CredentialRecord.ts b/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts similarity index 68% rename from packages/core/src/modules/credentials/repository/CredentialRecord.ts rename to packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts index 53614d5960..e37608ed7e 100644 --- a/packages/core/src/modules/credentials/repository/CredentialRecord.ts +++ b/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts @@ -1,8 +1,10 @@ import type { TagsBase } from '../../../storage/BaseRecord' import type { AutoAcceptCredential } from '../CredentialAutoAcceptType' +import type { CredentialProtocolVersion } from '../CredentialProtocolVersion' import type { CredentialState } from '../CredentialState' -import type { RevocationNotification } from '../models/' -import type { CredentialMetadata } from './credentialMetadataTypes' +import type { CredentialFormatType } from '../CredentialsModuleOptions' +import type { RevocationNotification } from '../models/RevocationNotification' +import type { CredentialMetadata } from './CredentialMetadataTypes' import { Type } from 'class-transformer' @@ -10,35 +12,26 @@ import { Attachment } from '../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../error' import { BaseRecord } from '../../../storage/BaseRecord' import { uuid } from '../../../utils/uuid' -import { - OfferCredentialMessage, - IssueCredentialMessage, - ProposeCredentialMessage, - RequestCredentialMessage, - CredentialPreviewAttribute, -} from '../messages' -import { CredentialInfo } from '../models/CredentialInfo' - -import { CredentialMetadataKeys } from './credentialMetadataTypes' - -export interface CredentialRecordProps { +import { CredentialPreviewAttribute } from '../models/CredentialPreviewAttributes' +import { CredentialInfo } from '../protocol/v1/models/CredentialInfo' + +import { CredentialMetadataKeys } from './CredentialMetadataTypes' + +export interface CredentialExchangeRecordProps { id?: string createdAt?: Date state: CredentialState connectionId?: string threadId: string + protocolVersion: CredentialProtocolVersion - credentialId?: string tags?: CustomCredentialTags - proposalMessage?: ProposeCredentialMessage - offerMessage?: OfferCredentialMessage - requestMessage?: RequestCredentialMessage - credentialMessage?: IssueCredentialMessage credentialAttributes?: CredentialPreviewAttribute[] autoAcceptCredential?: AutoAcceptCredential linkedAttachments?: Attachment[] revocationNotification?: RevocationNotification errorMessage?: string + credentials?: CredentialRecordBinding[] } export type CustomCredentialTags = TagsBase @@ -46,29 +39,30 @@ export type DefaultCredentialTags = { threadId: string connectionId?: string state: CredentialState + credentialIds: string[] +} + +export interface CredentialRecordBinding { + credentialRecordType: CredentialFormatType + credentialRecordId: string credentialId?: string indyRevocationRegistryId?: string indyCredentialRevocationId?: string } -export class CredentialRecord extends BaseRecord { +export class CredentialExchangeRecord extends BaseRecord< + DefaultCredentialTags, + CustomCredentialTags, + CredentialMetadata +> { public connectionId?: string public threadId!: string - public credentialId?: string public state!: CredentialState public autoAcceptCredential?: AutoAcceptCredential public revocationNotification?: RevocationNotification public errorMessage?: string - - // message data - @Type(() => ProposeCredentialMessage) - public proposalMessage?: ProposeCredentialMessage - @Type(() => OfferCredentialMessage) - public offerMessage?: OfferCredentialMessage - @Type(() => RequestCredentialMessage) - public requestMessage?: RequestCredentialMessage - @Type(() => IssueCredentialMessage) - public credentialMessage?: IssueCredentialMessage + public protocolVersion!: CredentialProtocolVersion + public credentials!: CredentialRecordBinding[] @Type(() => CredentialPreviewAttribute) public credentialAttributes?: CredentialPreviewAttribute[] @@ -76,10 +70,11 @@ export class CredentialRecord extends BaseRecord Attachment) public linkedAttachments?: Attachment[] + // Type is CredentialRecord on purpose (without Exchange) as this is how the record was initially called. public static readonly type = 'CredentialRecord' - public readonly type = CredentialRecord.type + public readonly type = CredentialExchangeRecord.type - public constructor(props: CredentialRecordProps) { + public constructor(props: CredentialExchangeRecordProps) { super() if (props) { @@ -87,30 +82,31 @@ export class CredentialRecord extends BaseRecord c.credentialRecordId) + } return { ...this._tags, threadId: this.threadId, connectionId: this.connectionId, state: this.state, - credentialId: this.credentialId, + credentialIds: Ids, indyRevocationRegistryId: metadata?.indyRevocationRegistryId, indyCredentialRevocationId: metadata?.indyCredentialRevocationId, } @@ -134,6 +130,14 @@ export class CredentialRecord extends BaseRecord { - public constructor(@inject(InjectionSymbols.StorageService) storageService: StorageService) { - super(CredentialRecord, storageService) +export class CredentialRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService + ) { + super(CredentialExchangeRecord, storageService) } } diff --git a/packages/core/src/modules/credentials/repository/index.ts b/packages/core/src/modules/credentials/repository/index.ts index 6f51849580..b7b986ad3e 100644 --- a/packages/core/src/modules/credentials/repository/index.ts +++ b/packages/core/src/modules/credentials/repository/index.ts @@ -1,3 +1,3 @@ -export * from './CredentialRecord' +export * from './CredentialExchangeRecord' export * from './CredentialRepository' -export * from './credentialMetadataTypes' +export * from './CredentialMetadataTypes' diff --git a/packages/core/src/modules/credentials/services/CredentialService.ts b/packages/core/src/modules/credentials/services/CredentialService.ts index 4fd07c4efa..2f64fa4b6a 100644 --- a/packages/core/src/modules/credentials/services/CredentialService.ts +++ b/packages/core/src/modules/credentials/services/CredentialService.ts @@ -1,760 +1,148 @@ +import type { AgentConfig } from '../../../agent/AgentConfig' import type { AgentMessage } from '../../../agent/AgentMessage' +import type { Dispatcher } from '../../../agent/Dispatcher' +import type { EventEmitter } from '../../../agent/EventEmitter' +import type { Handler, HandlerInboundMessage } from '../../../agent/Handler' import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import type { Logger } from '../../../logger' -import type { LinkedAttachment } from '../../../utils/LinkedAttachment' -import type { ConnectionRecord } from '../../connections' -import type { AutoAcceptCredential } from '../CredentialAutoAcceptType' -import type { CredentialStateChangedEvent } from '../CredentialEvents' -import type { CredentialProblemReportMessage, ProposeCredentialMessageOptions } from '../messages' - -import { Lifecycle, scoped } from 'tsyringe' - -import { AgentConfig } from '../../../agent/AgentConfig' -import { EventEmitter } from '../../../agent/EventEmitter' -import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' -import { AriesFrameworkError } from '../../../error' -import { JsonEncoder } from '../../../utils/JsonEncoder' -import { isLinkedAttachment } from '../../../utils/attachment' -import { uuid } from '../../../utils/uuid' -import { AckStatus } from '../../common' -import { ConnectionService } from '../../connections/services/ConnectionService' -import { IndyHolderService, IndyIssuerService } from '../../indy' -import { IndyLedgerService } from '../../ledger/services/IndyLedgerService' -import { CredentialEventTypes } from '../CredentialEvents' -import { CredentialState } from '../CredentialState' -import { CredentialUtils } from '../CredentialUtils' -import { CredentialProblemReportError, CredentialProblemReportReason } from '../errors' -import { - CredentialAckMessage, - CredentialPreview, - INDY_CREDENTIAL_ATTACHMENT_ID, - INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, - INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, - IssueCredentialMessage, - OfferCredentialMessage, - ProposeCredentialMessage, - RequestCredentialMessage, -} from '../messages' -import { CredentialRepository } from '../repository' -import { CredentialRecord } from '../repository/CredentialRecord' -import { CredentialMetadataKeys } from '../repository/credentialMetadataTypes' - -@scoped(Lifecycle.ContainerScoped) -export class CredentialService { - private credentialRepository: CredentialRepository - private connectionService: ConnectionService - private ledgerService: IndyLedgerService - private logger: Logger - private indyIssuerService: IndyIssuerService - private indyHolderService: IndyHolderService - private eventEmitter: EventEmitter +import type { DidCommMessageRepository } from '../../../storage' +import type { MediationRecipientService } from '../../routing' +import type { CredentialStateChangedEvent } from './../CredentialEvents' +import type { CredentialProtocolVersion } from './../CredentialProtocolVersion' +import type { + CredentialProtocolMsgReturnType, + DeleteCredentialOptions, + ServiceRequestCredentialOptions, +} from './../CredentialServiceOptions' +import type { + AcceptProposalOptions, + AcceptRequestOptions, + CredentialFormatType, + NegotiateOfferOptions, + NegotiateProposalOptions, + OfferCredentialOptions, + ProposeCredentialOptions, +} from './../CredentialsModuleOptions' +import type { CredentialFormatService } from './../formats/CredentialFormatService' +import type { CredentialFormats, HandlerAutoAcceptOptions } from './../formats/models/CredentialFormatServiceOptions' +import type { + V1CredentialProblemReportMessage, + V1IssueCredentialMessage, + V1OfferCredentialMessage, + V1ProposeCredentialMessage, + V1RequestCredentialMessage, +} from './../protocol/v1/messages' +import type { V2CredentialProblemReportMessage } from './../protocol/v2/messages/V2CredentialProblemReportMessage' +import type { V2IssueCredentialMessage } from './../protocol/v2/messages/V2IssueCredentialMessage' +import type { V2OfferCredentialMessage } from './../protocol/v2/messages/V2OfferCredentialMessage' +import type { V2ProposeCredentialMessage } from './../protocol/v2/messages/V2ProposeCredentialMessage' +import type { V2RequestCredentialMessage } from './../protocol/v2/messages/V2RequestCredentialMessage' +import type { CredentialExchangeRecord, CredentialRepository } from './../repository' +import type { RevocationService } from './RevocationService' + +import { CredentialEventTypes } from './../CredentialEvents' +import { CredentialState } from './../CredentialState' + +export abstract class CredentialService { + protected credentialRepository: CredentialRepository + protected eventEmitter: EventEmitter + protected dispatcher: Dispatcher + protected agentConfig: AgentConfig + protected mediationRecipientService: MediationRecipientService + protected didCommMessageRepository: DidCommMessageRepository + protected logger: Logger + protected revocationService: RevocationService public constructor( credentialRepository: CredentialRepository, - connectionService: ConnectionService, - ledgerService: IndyLedgerService, + eventEmitter: EventEmitter, + dispatcher: Dispatcher, agentConfig: AgentConfig, - indyIssuerService: IndyIssuerService, - indyHolderService: IndyHolderService, - eventEmitter: EventEmitter + mediationRecipientService: MediationRecipientService, + didCommMessageRepository: DidCommMessageRepository, + revocationService: RevocationService ) { this.credentialRepository = credentialRepository - this.connectionService = connectionService - this.ledgerService = ledgerService - this.logger = agentConfig.logger - this.indyIssuerService = indyIssuerService - this.indyHolderService = indyHolderService this.eventEmitter = eventEmitter + this.dispatcher = dispatcher + this.agentConfig = agentConfig + this.mediationRecipientService = mediationRecipientService + this.didCommMessageRepository = didCommMessageRepository + this.logger = this.agentConfig.logger + this.revocationService = revocationService + + this.registerHandlers() } - /** - * Create a {@link ProposeCredentialMessage} not bound to an existing credential exchange. - * To create a proposal as response to an existing credential exchange, use {@link CredentialService#createProposalAsResponse}. - * - * @param connectionRecord The connection for which to create the credential proposal - * @param config Additional configuration to use for the proposal - * @returns Object containing proposal message and associated credential record - * - */ - public async createProposal( - connectionRecord: ConnectionRecord, - config?: CredentialProposeOptions - ): Promise> { - // Assert - connectionRecord.assertReady() - - const options = { ...config } - - // Add the linked attachments to the credentialProposal - if (config?.linkedAttachments) { - options.credentialProposal = CredentialUtils.createAndLinkAttachmentsToPreview( - config.linkedAttachments, - config.credentialProposal ?? new CredentialPreview({ attributes: [] }) - ) - options.attachments = config.linkedAttachments.map((linkedAttachment) => linkedAttachment.attachment) - } - - // Create message - const proposalMessage = new ProposeCredentialMessage(options ?? {}) - - // Create record - const credentialRecord = new CredentialRecord({ - connectionId: connectionRecord.id, - threadId: proposalMessage.threadId, - state: CredentialState.ProposalSent, - proposalMessage, - linkedAttachments: config?.linkedAttachments?.map((linkedAttachment) => linkedAttachment.attachment), - credentialAttributes: proposalMessage.credentialProposal?.attributes, - autoAcceptCredential: config?.autoAcceptCredential, - }) - - // Set the metadata - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - schemaId: options.schemaId, - credentialDefinitionId: options.credentialDefinitionId, - }) - - await this.credentialRepository.save(credentialRecord) - this.eventEmitter.emit({ - type: CredentialEventTypes.CredentialStateChanged, - payload: { - credentialRecord, - previousState: null, - }, - }) - - return { message: proposalMessage, credentialRecord } - } - - /** - * Create a {@link ProposePresentationMessage} as response to a received credential offer. - * To create a proposal not bound to an existing credential exchange, use {@link CredentialService#createProposal}. - * - * @param credentialRecord The credential record for which to create the credential proposal - * @param config Additional configuration to use for the proposal - * @returns Object containing proposal message and associated credential record - * - */ - public async createProposalAsResponse( - credentialRecord: CredentialRecord, - config?: CredentialProposeOptions - ): Promise> { - // Assert - credentialRecord.assertState(CredentialState.OfferReceived) - - // Create message - const proposalMessage = new ProposeCredentialMessage(config ?? {}) - proposalMessage.setThread({ threadId: credentialRecord.threadId }) - - // Update record - credentialRecord.proposalMessage = proposalMessage - credentialRecord.credentialAttributes = proposalMessage.credentialProposal?.attributes - await this.updateState(credentialRecord, CredentialState.ProposalSent) - - return { message: proposalMessage, credentialRecord } - } - - /** - * Process a received {@link ProposeCredentialMessage}. This will not accept the credential proposal - * or send a credential offer. It will only create a new, or update the existing credential record with - * the information from the credential proposal message. Use {@link CredentialService#createOfferAsResponse} - * after calling this method to create a credential offer. - * - * @param messageContext The message context containing a credential proposal message - * @returns credential record associated with the credential proposal message - * - */ - public async processProposal( - messageContext: InboundMessageContext - ): Promise { - let credentialRecord: CredentialRecord - const { message: proposalMessage, connection } = messageContext - - this.logger.debug(`Processing credential proposal with id ${proposalMessage.id}`) - - try { - // Credential record already exists - credentialRecord = await this.getByThreadAndConnectionId(proposalMessage.threadId, connection?.id) - - // Assert - credentialRecord.assertState(CredentialState.OfferSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: credentialRecord.proposalMessage, - previousSentMessage: credentialRecord.offerMessage, - }) - - // Update record - credentialRecord.proposalMessage = proposalMessage - await this.updateState(credentialRecord, CredentialState.ProposalReceived) - } catch { - // No credential record exists with thread id - credentialRecord = new CredentialRecord({ - connectionId: connection?.id, - threadId: proposalMessage.threadId, - proposalMessage, - credentialAttributes: proposalMessage.credentialProposal?.attributes, - state: CredentialState.ProposalReceived, - }) - - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - schemaId: proposalMessage.schemaId, - credentialDefinitionId: proposalMessage.credentialDefinitionId, - }) - - // Assert - this.connectionService.assertConnectionOrServiceDecorator(messageContext) - - // Save record - await this.credentialRepository.save(credentialRecord) - this.eventEmitter.emit({ - type: CredentialEventTypes.CredentialStateChanged, - payload: { - credentialRecord, - previousState: null, - }, - }) - } - return credentialRecord - } - - /** - * Create a {@link OfferCredentialMessage} as response to a received credential proposal. - * To create an offer not bound to an existing credential exchange, use {@link CredentialService#createOffer}. - * - * @param credentialRecord The credential record for which to create the credential offer - * @param credentialTemplate The credential template to use for the offer - * @returns Object containing offer message and associated credential record - * - */ - public async createOfferAsResponse( - credentialRecord: CredentialRecord, - credentialTemplate: CredentialOfferTemplate - ): Promise> { - // Assert - credentialRecord.assertState(CredentialState.ProposalReceived) - - // Create message - const { credentialDefinitionId, comment, preview, attachments } = credentialTemplate - const credOffer = await this.indyIssuerService.createCredentialOffer(credentialDefinitionId) - const offerAttachment = new Attachment({ - id: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64(credOffer), - }), - }) - - const credentialOfferMessage = new OfferCredentialMessage({ - comment, - offerAttachments: [offerAttachment], - credentialPreview: preview, - attachments, - }) - - credentialOfferMessage.setThread({ - threadId: credentialRecord.threadId, - }) - - credentialRecord.offerMessage = credentialOfferMessage - credentialRecord.credentialAttributes = preview.attributes - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - schemaId: credOffer.schema_id, - credentialDefinitionId: credOffer.cred_def_id, - }) - credentialRecord.linkedAttachments = attachments?.filter((attachment) => isLinkedAttachment(attachment)) - credentialRecord.autoAcceptCredential = - credentialTemplate.autoAcceptCredential ?? credentialRecord.autoAcceptCredential - - // Check if credential preview attributes match the schema attributes - const schema = await this.ledgerService.getSchema(credOffer.schema_id) - CredentialUtils.checkAttributesMatch(schema, preview) - - await this.updateState(credentialRecord, CredentialState.OfferSent) - - return { message: credentialOfferMessage, credentialRecord } - } - - /** - * Create a {@link OfferCredentialMessage} not bound to an existing credential exchange. - * To create an offer as response to an existing credential exchange, use {@link CredentialService#createOfferAsResponse}. - * - * @param connectionRecord The connection for which to create the credential offer - * @param credentialTemplate The credential template to use for the offer - * @returns Object containing offer message and associated credential record - * - */ - public async createOffer( - credentialTemplate: CredentialOfferTemplate, - connectionRecord?: ConnectionRecord - ): Promise> { - // Assert - connectionRecord?.assertReady() - - // Create message - const { credentialDefinitionId, comment, preview, linkedAttachments } = credentialTemplate - const credOffer = await this.indyIssuerService.createCredentialOffer(credentialDefinitionId) - const offerAttachment = new Attachment({ - id: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64(credOffer), - }), - }) - - // Create and link credential to attacment - const credentialPreview = linkedAttachments - ? CredentialUtils.createAndLinkAttachmentsToPreview(linkedAttachments, preview) - : preview - - // Check if credential preview attributes match the schema attributes - const schema = await this.ledgerService.getSchema(credOffer.schema_id) - CredentialUtils.checkAttributesMatch(schema, credentialPreview) - - // Construct offer message - const credentialOfferMessage = new OfferCredentialMessage({ - comment, - offerAttachments: [offerAttachment], - credentialPreview, - attachments: linkedAttachments?.map((linkedAttachment) => linkedAttachment.attachment), - }) - - // Create record - const credentialRecord = new CredentialRecord({ - connectionId: connectionRecord?.id, - threadId: credentialOfferMessage.id, - offerMessage: credentialOfferMessage, - credentialAttributes: credentialPreview.attributes, - linkedAttachments: linkedAttachments?.map((linkedAttachments) => linkedAttachments.attachment), - state: CredentialState.OfferSent, - autoAcceptCredential: credentialTemplate.autoAcceptCredential, - }) - - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - credentialDefinitionId: credOffer.cred_def_id, - schemaId: credOffer.schema_id, - }) - - await this.credentialRepository.save(credentialRecord) - this.eventEmitter.emit({ - type: CredentialEventTypes.CredentialStateChanged, - payload: { - credentialRecord, - previousState: null, - }, - }) - - return { message: credentialOfferMessage, credentialRecord } - } - - /** - * Process a received {@link OfferCredentialMessage}. This will not accept the credential offer - * or send a credential request. It will only create a new credential record with - * the information from the credential offer message. Use {@link CredentialService#createRequest} - * after calling this method to create a credential request. - * - * @param messageContext The message context containing a credential request message - * @returns credential record associated with the credential offer message - * - */ - public async processOffer(messageContext: InboundMessageContext): Promise { - let credentialRecord: CredentialRecord - const { message: credentialOfferMessage, connection } = messageContext - - this.logger.debug(`Processing credential offer with id ${credentialOfferMessage.id}`) - - const indyCredentialOffer = credentialOfferMessage.indyCredentialOffer - if (!indyCredentialOffer) { - throw new CredentialProblemReportError( - `Missing required base64 or json encoded attachment data for credential offer with thread id ${credentialOfferMessage.threadId}`, - { problemCode: CredentialProblemReportReason.IssuanceAbandoned } - ) - } - - try { - // Credential record already exists - credentialRecord = await this.getByThreadAndConnectionId(credentialOfferMessage.threadId, connection?.id) - - // Assert - credentialRecord.assertState(CredentialState.ProposalSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: credentialRecord.offerMessage, - previousSentMessage: credentialRecord.proposalMessage, - }) - - credentialRecord.offerMessage = credentialOfferMessage - credentialRecord.linkedAttachments = credentialOfferMessage.attachments?.filter(isLinkedAttachment) - - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - schemaId: indyCredentialOffer.schema_id, - credentialDefinitionId: indyCredentialOffer.cred_def_id, - }) - - await this.updateState(credentialRecord, CredentialState.OfferReceived) - } catch { - // No credential record exists with thread id - credentialRecord = new CredentialRecord({ - connectionId: connection?.id, - threadId: credentialOfferMessage.id, - offerMessage: credentialOfferMessage, - credentialAttributes: credentialOfferMessage.credentialPreview.attributes, - state: CredentialState.OfferReceived, - }) - - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - schemaId: indyCredentialOffer.schema_id, - credentialDefinitionId: indyCredentialOffer.cred_def_id, - }) - - // Assert - this.connectionService.assertConnectionOrServiceDecorator(messageContext) - - // Save in repository - await this.credentialRepository.save(credentialRecord) - this.eventEmitter.emit({ - type: CredentialEventTypes.CredentialStateChanged, - payload: { - credentialRecord, - previousState: null, - }, - }) - } - - return credentialRecord - } - - /** - * Create a {@link RequestCredentialMessage} as response to a received credential offer. - * - * @param credentialRecord The credential record for which to create the credential request - * @param options Additional configuration to use for the credential request - * @returns Object containing request message and associated credential record - * - */ - public async createRequest( - credentialRecord: CredentialRecord, - options: CredentialRequestOptions - ): Promise> { - // Assert credential - credentialRecord.assertState(CredentialState.OfferReceived) - - const credentialOffer = credentialRecord.offerMessage?.indyCredentialOffer - - if (!credentialOffer) { - throw new CredentialProblemReportError( - `Missing required base64 or json encoded attachment data for credential offer with thread id ${credentialRecord.threadId}`, - { problemCode: CredentialProblemReportReason.IssuanceAbandoned } - ) - } - - const credentialDefinition = await this.ledgerService.getCredentialDefinition(credentialOffer.cred_def_id) - - const [credReq, credReqMetadata] = await this.indyHolderService.createCredentialRequest({ - holderDid: options.holderDid, - credentialOffer, - credentialDefinition, - }) + abstract getVersion(): CredentialProtocolVersion - const requestAttachment = new Attachment({ - id: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64(credReq), - }), - }) + abstract getFormats(cred: CredentialFormats): CredentialFormatService[] - const credentialRequest = new RequestCredentialMessage({ - comment: options?.comment, - requestAttachments: [requestAttachment], - attachments: credentialRecord.offerMessage?.attachments?.filter((attachment) => isLinkedAttachment(attachment)), - }) - credentialRequest.setThread({ threadId: credentialRecord.threadId }) + // methods for proposal + abstract createProposal(proposal: ProposeCredentialOptions): Promise> + abstract processProposal(messageContext: HandlerInboundMessage): Promise + abstract acceptProposal( + proposal: AcceptProposalOptions, + credentialRecord: CredentialExchangeRecord + ): Promise> + abstract negotiateProposal( + options: NegotiateProposalOptions, + credentialRecord: CredentialExchangeRecord + ): Promise> - credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, credReqMetadata) - credentialRecord.requestMessage = credentialRequest - credentialRecord.autoAcceptCredential = options?.autoAcceptCredential ?? credentialRecord.autoAcceptCredential + // methods for offer + abstract createOffer(options: OfferCredentialOptions): Promise> + abstract processOffer(messageContext: HandlerInboundMessage): Promise - credentialRecord.linkedAttachments = credentialRecord.offerMessage?.attachments?.filter((attachment) => - isLinkedAttachment(attachment) - ) - await this.updateState(credentialRecord, CredentialState.RequestSent) + abstract createOutOfBandOffer(options: OfferCredentialOptions): Promise> - return { message: credentialRequest, credentialRecord } - } + // methods for request + abstract createRequest( + credentialRecord: CredentialExchangeRecord, + options: ServiceRequestCredentialOptions, + holderDid: string + ): Promise> - /** - * Process a received {@link RequestCredentialMessage}. This will not accept the credential request - * or send a credential. It will only update the existing credential record with - * the information from the credential request message. Use {@link CredentialService#createCredential} - * after calling this method to create a credential. - * - * @param messageContext The message context containing a credential request message - * @returns credential record associated with the credential request message - * - */ - public async processRequest( - messageContext: InboundMessageContext - ): Promise { - const { message: credentialRequestMessage, connection } = messageContext + abstract processAck(messageContext: InboundMessageContext): Promise - this.logger.debug(`Processing credential request with id ${credentialRequestMessage.id}`) + abstract negotiateOffer( + options: NegotiateOfferOptions, + credentialRecord: CredentialExchangeRecord + ): Promise> - const indyCredentialRequest = credentialRequestMessage?.indyCredentialRequest + // methods for issue - if (!indyCredentialRequest) { - throw new CredentialProblemReportError( - `Missing required base64 or json encoded attachment data for credential request with thread id ${credentialRequestMessage.threadId}`, - { problemCode: CredentialProblemReportReason.IssuanceAbandoned } - ) - } + abstract processRequest( + messageContext: InboundMessageContext + ): Promise - const credentialRecord = await this.getByThreadAndConnectionId(credentialRequestMessage.threadId, connection?.id) + // methods for issue + abstract createCredential( + credentialRecord: CredentialExchangeRecord, + options?: AcceptRequestOptions + ): Promise> - // Assert - credentialRecord.assertState(CredentialState.OfferSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: credentialRecord.proposalMessage, - previousSentMessage: credentialRecord.offerMessage, - }) + abstract processCredential( + messageContext: InboundMessageContext + ): Promise - this.logger.debug('Credential record found when processing credential request', credentialRecord) + abstract createAck(credentialRecord: CredentialExchangeRecord): Promise> - credentialRecord.requestMessage = credentialRequestMessage - await this.updateState(credentialRecord, CredentialState.RequestReceived) + abstract registerHandlers(): void - return credentialRecord - } - - /** - * Create a {@link IssueCredentialMessage} as response to a received credential request. - * - * @param credentialRecord The credential record for which to create the credential - * @param options Additional configuration to use for the credential - * @returns Object containing issue credential message and associated credential record - * - */ - public async createCredential( - credentialRecord: CredentialRecord, - options?: CredentialResponseOptions - ): Promise> { - // Assert - credentialRecord.assertState(CredentialState.RequestReceived) - - const requestMessage = credentialRecord.requestMessage - const offerMessage = credentialRecord.offerMessage - - // Assert offer message - if (!offerMessage) { - throw new AriesFrameworkError( - `Missing credential offer for credential exchange with thread id ${credentialRecord.threadId}` - ) - } - - // Assert credential attributes - const credentialAttributes = credentialRecord.credentialAttributes - if (!credentialAttributes) { - throw new CredentialProblemReportError( - `Missing required credential attribute values on credential record with id ${credentialRecord.id}`, - { problemCode: CredentialProblemReportReason.IssuanceAbandoned } - ) - } - - // Assert Indy offer - const indyCredentialOffer = offerMessage?.indyCredentialOffer - if (!indyCredentialOffer) { - throw new CredentialProblemReportError( - `Missing required base64 or json encoded attachment data for credential offer with thread id ${credentialRecord.threadId}`, - { problemCode: CredentialProblemReportReason.IssuanceAbandoned } - ) - } - - // Assert Indy request - const indyCredentialRequest = requestMessage?.indyCredentialRequest - if (!indyCredentialRequest) { - throw new CredentialProblemReportError( - `Missing required base64 or json encoded attachment data for credential request with thread id ${credentialRecord.threadId}`, - { problemCode: CredentialProblemReportReason.IssuanceAbandoned } - ) - } - - const [credential] = await this.indyIssuerService.createCredential({ - credentialOffer: indyCredentialOffer, - credentialRequest: indyCredentialRequest, - credentialValues: CredentialUtils.convertAttributesToValues(credentialAttributes), - }) - - const credentialAttachment = new Attachment({ - id: INDY_CREDENTIAL_ATTACHMENT_ID, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64(credential), - }), - }) - - const issueCredentialMessage = new IssueCredentialMessage({ - comment: options?.comment, - credentialAttachments: [credentialAttachment], - attachments: - offerMessage?.attachments?.filter((attachment) => isLinkedAttachment(attachment)) || - requestMessage?.attachments?.filter((attachment) => isLinkedAttachment(attachment)), - }) - issueCredentialMessage.setThread({ - threadId: credentialRecord.threadId, - }) - issueCredentialMessage.setPleaseAck() - - credentialRecord.credentialMessage = issueCredentialMessage - credentialRecord.autoAcceptCredential = options?.autoAcceptCredential ?? credentialRecord.autoAcceptCredential - - await this.updateState(credentialRecord, CredentialState.CredentialIssued) - - return { message: issueCredentialMessage, credentialRecord } - } - - /** - * Process a received {@link IssueCredentialMessage}. This will not accept the credential - * or send a credential acknowledgement. It will only update the existing credential record with - * the information from the issue credential message. Use {@link CredentialService#createAck} - * after calling this method to create a credential acknowledgement. - * - * @param messageContext The message context containing an issue credential message - * - * @returns credential record associated with the issue credential message - * - */ - public async processCredential( - messageContext: InboundMessageContext - ): Promise { - const { message: issueCredentialMessage, connection } = messageContext - - this.logger.debug(`Processing credential with id ${issueCredentialMessage.id}`) - - const credentialRecord = await this.getByThreadAndConnectionId(issueCredentialMessage.threadId, connection?.id) - - // Assert - credentialRecord.assertState(CredentialState.RequestSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: credentialRecord.offerMessage, - previousSentMessage: credentialRecord.requestMessage, - }) - - const credentialRequestMetadata = credentialRecord.metadata.get(CredentialMetadataKeys.IndyRequest) - - if (!credentialRequestMetadata) { - throw new CredentialProblemReportError( - `Missing required request metadata for credential with id ${credentialRecord.id}`, - { problemCode: CredentialProblemReportReason.IssuanceAbandoned } - ) - } - - const indyCredential = issueCredentialMessage.indyCredential - if (!indyCredential) { - throw new CredentialProblemReportError( - `Missing required base64 or json encoded attachment data for credential with thread id ${issueCredentialMessage.threadId}`, - { problemCode: CredentialProblemReportReason.IssuanceAbandoned } - ) - } - - const credentialDefinition = await this.ledgerService.getCredentialDefinition(indyCredential.cred_def_id) - - //Fetch Revocation Registry Definition if the issued credential has an associated revocation registry id - let revocationRegistryDefinition - if (indyCredential.rev_reg_id) { - const revocationRegistryDefinitionData = await this.ledgerService.getRevocationRegistryDefinition( - indyCredential.rev_reg_id - ) - revocationRegistryDefinition = revocationRegistryDefinitionData.revocationRegistryDefinition - } - - const credentialId = await this.indyHolderService.storeCredential({ - credentialId: uuid(), - credentialRequestMetadata, - credential: indyCredential, - credentialDefinition, - revocationRegistryDefinition, - }) - - // If we have the rev_reg_id then we also want to set the cred_rev_id - if (indyCredential.rev_reg_id) { - const credential = await this.indyHolderService.getCredential(credentialId) - - const indyCredentialRevocationId = credential.cred_rev_id - const indyRevocationRegistryId = indyCredential.rev_reg_id - - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - indyCredentialRevocationId, - indyRevocationRegistryId, - }) - } - - credentialRecord.credentialId = credentialId - credentialRecord.credentialMessage = issueCredentialMessage - await this.updateState(credentialRecord, CredentialState.CredentialReceived) - - return credentialRecord - } - - /** - * Create a {@link CredentialAckMessage} as response to a received credential. - * - * @param credentialRecord The credential record for which to create the credential acknowledgement - * @returns Object containing credential acknowledgement message and associated credential record - * - */ - public async createAck( - credentialRecord: CredentialRecord - ): Promise> { - credentialRecord.assertState(CredentialState.CredentialReceived) - - // Create message - const ackMessage = new CredentialAckMessage({ - status: AckStatus.OK, - threadId: credentialRecord.threadId, - }) - - await this.updateState(credentialRecord, CredentialState.Done) - - return { message: ackMessage, credentialRecord } - } + abstract getFormatService(credentialFormatType?: CredentialFormatType): CredentialFormatService /** * Decline a credential offer * @param credentialRecord The credential to be declined */ - public async declineOffer(credentialRecord: CredentialRecord): Promise { + public async declineOffer(credentialRecord: CredentialExchangeRecord): Promise { credentialRecord.assertState(CredentialState.OfferReceived) await this.updateState(credentialRecord, CredentialState.Declined) return credentialRecord } - - /** - * Process a received {@link CredentialAckMessage}. - * - * @param messageContext The message context containing a credential acknowledgement message - * @returns credential record associated with the credential acknowledgement message - * - */ - public async processAck(messageContext: InboundMessageContext): Promise { - const { message: credentialAckMessage, connection } = messageContext - - this.logger.debug(`Processing credential ack with id ${credentialAckMessage.id}`) - - const credentialRecord = await this.getByThreadAndConnectionId(credentialAckMessage.threadId, connection?.id) - - // Assert - credentialRecord.assertState(CredentialState.CredentialIssued) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: credentialRecord.requestMessage, - previousSentMessage: credentialRecord.credentialMessage, - }) - - // Update record - await this.updateState(credentialRecord, CredentialState.Done) - - return credentialRecord - } - /** * Process a received {@link ProblemReportMessage}. * @@ -763,8 +151,8 @@ export class CredentialService { * */ public async processProblemReport( - messageContext: InboundMessageContext - ): Promise { + messageContext: InboundMessageContext + ): Promise { const { message: credentialProblemReportMessage } = messageContext const connection = messageContext.assertReadyConnection() @@ -773,7 +161,7 @@ export class CredentialService { const credentialRecord = await this.getByThreadAndConnectionId( credentialProblemReportMessage.threadId, - connection?.id + connection.id ) // Update record @@ -781,16 +169,53 @@ export class CredentialService { await this.update(credentialRecord) return credentialRecord } + abstract shouldAutoRespondToProposal(options: HandlerAutoAcceptOptions): Promise + + abstract shouldAutoRespondToOffer( + credentialRecord: CredentialExchangeRecord, + offerMessage: V1OfferCredentialMessage | V2OfferCredentialMessage, + proposeMessage?: V1ProposeCredentialMessage | V2ProposeCredentialMessage + ): boolean + + abstract shouldAutoRespondToRequest( + credentialRecord: CredentialExchangeRecord, + requestMessage: V1RequestCredentialMessage | V2RequestCredentialMessage, + proposeMessage?: V1ProposeCredentialMessage | V2ProposeCredentialMessage, + offerMessage?: V1OfferCredentialMessage | V2OfferCredentialMessage + ): boolean + + abstract shouldAutoRespondToCredential( + credentialRecord: CredentialExchangeRecord, + credentialMessage: V1IssueCredentialMessage | V2IssueCredentialMessage + ): boolean + + abstract getOfferMessage(id: string): Promise + + abstract getRequestMessage(id: string): Promise + + abstract getCredentialMessage(id: string): Promise /** - * Retrieve all credential records + * Update the record to a new state and emit an state changed event. Also updates the record + * in storage. + * + * @param credentialRecord The credential record to update the state for + * @param newState The state to update to * - * @returns List containing all credential records */ - public getAll(): Promise { - return this.credentialRepository.getAll() - } + public async updateState(credentialRecord: CredentialExchangeRecord, newState: CredentialState) { + const previousState = credentialRecord.state + credentialRecord.state = newState + await this.credentialRepository.update(credentialRecord) + this.eventEmitter.emit({ + type: CredentialEventTypes.CredentialStateChanged, + payload: { + credentialRecord, + previousState: previousState, + }, + }) + } /** * Retrieve a credential record by id * @@ -799,35 +224,41 @@ export class CredentialService { * @return The credential record * */ - public getById(credentialRecordId: string): Promise { + public getById(credentialRecordId: string): Promise { return this.credentialRepository.getById(credentialRecordId) } + /** + * Retrieve all credential records + * + * @returns List containing all credential records + */ + public getAll(): Promise { + return this.credentialRepository.getAll() + } + /** * Find a credential record by id * * @param credentialRecordId the credential record id * @returns The credential record or null if not found */ - public findById(connectionId: string): Promise { + public findById(connectionId: string): Promise { return this.credentialRepository.findById(connectionId) } - /** - * Delete a credential record by id - * - * @param credentialId the credential record id - */ public async deleteById(credentialId: string, options?: DeleteCredentialOptions): Promise { const credentialRecord = await this.getById(credentialId) await this.credentialRepository.delete(credentialRecord) - if (options?.deleteAssociatedCredential && credentialRecord.credentialId) { - await this.indyHolderService.deleteCredential(credentialRecord.credentialId) + if (options?.deleteAssociatedCredentials) { + for (const credential of credentialRecord.credentials) { + const formatService: CredentialFormatService = this.getFormatService(credential.credentialRecordType) + await formatService.deleteCredentialById(credentialRecord, options) + } } } - /** * Retrieve a credential record by connection id and thread id * @@ -837,70 +268,14 @@ export class CredentialService { * @throws {RecordDuplicateError} If multiple records are found * @returns The credential record */ - public getByThreadAndConnectionId(threadId: string, connectionId?: string): Promise { + public getByThreadAndConnectionId(threadId: string, connectionId?: string): Promise { return this.credentialRepository.getSingleByQuery({ connectionId, threadId, }) } - public update(credentialRecord: CredentialRecord) { - return this.credentialRepository.update(credentialRecord) + public async update(credentialRecord: CredentialExchangeRecord) { + return await this.credentialRepository.update(credentialRecord) } - - /** - * Update the record to a new state and emit an state changed event. Also updates the record - * in storage. - * - * @param credentialRecord The credential record to update the state for - * @param newState The state to update to - * - */ - private async updateState(credentialRecord: CredentialRecord, newState: CredentialState) { - const previousState = credentialRecord.state - credentialRecord.state = newState - await this.credentialRepository.update(credentialRecord) - - this.eventEmitter.emit({ - type: CredentialEventTypes.CredentialStateChanged, - payload: { - credentialRecord, - previousState: previousState, - }, - }) - } -} - -export interface DeleteCredentialOptions { - deleteAssociatedCredential: boolean -} - -export interface CredentialProtocolMsgReturnType { - message: MessageType - credentialRecord: CredentialRecord -} - -export interface CredentialOfferTemplate { - credentialDefinitionId: string - comment?: string - preview: CredentialPreview - autoAcceptCredential?: AutoAcceptCredential - attachments?: Attachment[] - linkedAttachments?: LinkedAttachment[] -} - -export interface CredentialRequestOptions { - holderDid: string - comment?: string - autoAcceptCredential?: AutoAcceptCredential -} - -export interface CredentialResponseOptions { - comment?: string - autoAcceptCredential?: AutoAcceptCredential -} - -export type CredentialProposeOptions = Omit & { - linkedAttachments?: LinkedAttachment[] - autoAcceptCredential?: AutoAcceptCredential } diff --git a/packages/core/src/modules/credentials/services/RevocationService.ts b/packages/core/src/modules/credentials/services/RevocationService.ts index 21f9390652..129d38d4ef 100644 --- a/packages/core/src/modules/credentials/services/RevocationService.ts +++ b/packages/core/src/modules/credentials/services/RevocationService.ts @@ -2,7 +2,8 @@ import type { InboundMessageContext } from '../../../agent/models/InboundMessage import type { Logger } from '../../../logger' import type { ConnectionRecord } from '../../connections' import type { RevocationNotificationReceivedEvent } from '../CredentialEvents' -import type { V1RevocationNotificationMessage, V2RevocationNotificationMessage } from '../messages' +import type { V1RevocationNotificationMessage } from '../protocol/v1/messages/V1RevocationNotificationMessage' +import type { V2RevocationNotificationMessage } from '../protocol/v2/messages/V2RevocationNotificationMessage' import { scoped, Lifecycle } from 'tsyringe' @@ -10,7 +11,7 @@ import { AgentConfig } from '../../../agent/AgentConfig' import { EventEmitter } from '../../../agent/EventEmitter' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' import { CredentialEventTypes } from '../CredentialEvents' -import { RevocationNotification } from '../models' +import { RevocationNotification } from '../models/RevocationNotification' import { CredentialRepository } from '../repository' @scoped(Lifecycle.ContainerScoped) @@ -32,6 +33,7 @@ export class RevocationService { comment?: string ) { const query = { indyRevocationRegistryId, indyCredentialRevocationId } + this.logger.trace(`Getting record by query for revocation notification:`, query) const credentialRecord = await this.credentialRepository.getSingleByQuery(query) @@ -50,7 +52,7 @@ export class RevocationService { } /** - * Process a recieved {@link V1RevocationNotificationMessage}. This will create a + * Process a received {@link V1RevocationNotificationMessage}. This will create a * {@link RevocationNotification} and store it in the corresponding {@link CredentialRecord} * * @param messageContext message context of RevocationNotificationMessageV1 @@ -69,6 +71,7 @@ export class RevocationService { const [, , indyRevocationRegistryId, indyCredentialRevocationId] = threadIdGroups const comment = messageContext.message.comment const connection = messageContext.assertReadyConnection() + await this.processRevocationNotification( indyRevocationRegistryId, indyCredentialRevocationId, @@ -86,7 +89,7 @@ export class RevocationService { } /** - * Process a recieved {@link V2RevocationNotificationMessage}. This will create a + * Process a received {@link V2RevocationNotificationMessage}. This will create a * {@link RevocationNotification} and store it in the corresponding {@link CredentialRecord} * * @param messageContext message context of RevocationNotificationMessageV2 @@ -95,6 +98,7 @@ export class RevocationService { messageContext: InboundMessageContext ): Promise { this.logger.info('Processing revocation notification v2', { message: messageContext.message }) + // CredentialId = :: const credentialIdRegex = /((?:[\dA-z]{21,22}):4:(?:[\dA-z]{21,22}):3:[Cc][Ll]:(?:(?:[1-9][0-9]*)|(?:[\dA-z]{21,22}:2:.+:[0-9.]+))(?::[\dA-z]+)?:CL_ACCUM:(?:[\dA-z-]+))::(\d+)$/ diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1.json index 459a3cf420..c0c7ff387f 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1.json @@ -1,7 +1,5 @@ { "@context": ["https://w3id.org/did/v1"], - "controller": [], - "alsoKnownAs": [], "id": "did:key:z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA", "verificationMethod": [ { @@ -22,7 +20,5 @@ ], "capabilityInvocation": [ "did:key:z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA#z3tEFALUKUzzCAvytMHX8X4SnsNsq6T5tC5Zb18oQEt1FqNcJXqJ3AA9umgzA9yoqPBeWA" - ], - "keyAgreement": [], - "service": [] + ] } diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1g2.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1g2.json index 0b8edff2a0..22ec25f045 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1g2.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g1g2.json @@ -1,7 +1,5 @@ { "@context": ["https://w3id.org/did/v1"], - "controller": [], - "alsoKnownAs": [], "id": "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s", "verificationMethod": [ { @@ -32,7 +30,5 @@ "capabilityInvocation": [ "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s#z3tEG5qmJZX29jJSX5kyhDR5YJNnefJFdwTxRqk6zbEPv4Pf2xF12BpmXv9NExxSRFGfxd", "did:key:z5TcESXuYUE9aZWYwSdrUEGK1HNQFHyTt4aVpaCTVZcDXQmUheFwfNZmRksaAbBneNm5KyE52SdJeRCN1g6PJmF31GsHWwFiqUDujvasK3wTiDr3vvkYwEJHt7H5RGEKYEp1ErtQtcEBgsgY2DA9JZkHj1J9HZ8MRDTguAhoFtR4aTBQhgnkP4SwVbxDYMEZoF2TMYn3s#zUC7LTa4hWtaE9YKyDsMVGiRNqPMN3s4rjBdB3MFi6PcVWReNfR72y3oGW2NhNcaKNVhMobh7aHp8oZB3qdJCs7RebM2xsodrSm8MmePbN25NTGcpjkJMwKbcWfYDX7eHCJjPGM" - ], - "keyAgreement": [], - "service": [] + ] } diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g2.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g2.json index 5c3a7dd3f4..e22c053e79 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g2.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyBls12381g2.json @@ -1,7 +1,5 @@ { "@context": ["https://w3id.org/did/v1"], - "controller": [], - "alsoKnownAs": [], "id": "did:key:zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT", "verificationMethod": [ { @@ -22,7 +20,5 @@ ], "capabilityInvocation": [ "did:key:zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT#zUC71nmwvy83x1UzNKbZbS7N9QZx8rqpQx3Ee3jGfKiEkZngTKzsRoqobX6wZdZF5F93pSGYYco3gpK9tc53ruWUo2tkBB9bxPCFBUjq2th8FbtT4xih6y6Q1K9EL4Th86NiCGT" - ], - "keyAgreement": [], - "service": [] + ] } diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyEd25519.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyEd25519.json index 45c8ca8d2a..8cfad8b6d1 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyEd25519.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyEd25519.json @@ -4,8 +4,6 @@ "https://w3id.org/security/suites/ed25519-2018/v1", "https://w3id.org/security/suites/x25519-2019/v1" ], - "controller": [], - "alsoKnownAs": [], "id": "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th", "verificationMethod": [ { @@ -34,6 +32,5 @@ "controller": "did:key:z6MkmjY8GnV5i9YTDtPETC2uUAW6ejw3nk5mXF5yci5ab7th", "publicKeyBase58": "79CXkde3j8TNuMXxPdV7nLUrT2g7JAEjH5TreyVY7GEZ" } - ], - "service": [] + ] } diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyX25519.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyX25519.json index 6b7310cd8c..689cdefc57 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyX25519.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didKeyX25519.json @@ -1,13 +1,6 @@ { "@context": ["https://w3id.org/did/v1"], - "controller": [], - "alsoKnownAs": [], "id": "did:key:z6LShLeXRTzevtwcfehaGEzCMyL3bNsAeKCwcqwJxyCo63yE", - "verificationMethod": [], - "authentication": [], - "assertionMethod": [], - "capabilityDelegation": [], - "capabilityInvocation": [], "keyAgreement": [ { "id": "did:key:z6LShLeXRTzevtwcfehaGEzCMyL3bNsAeKCwcqwJxyCo63yE#z6LShLeXRTzevtwcfehaGEzCMyL3bNsAeKCwcqwJxyCo63yE", @@ -15,6 +8,5 @@ "controller": "did:key:z6LShLeXRTzevtwcfehaGEzCMyL3bNsAeKCwcqwJxyCo63yE", "publicKeyBase58": "6fUMuABnqSDsaGKojbUF3P7ZkEL3wi2njsDdUWZGNgCU" } - ], - "service": [] + ] } diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didPeer1zQmY.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didPeer1zQmY.json index 5a92c2fbf2..4a33648df6 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didPeer1zQmY.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didPeer1zQmY.json @@ -1,9 +1,6 @@ { "@context": ["https://w3id.org/did/v1"], - "id": "did:peer:1zQmYtsAsQhwEjjFkcJ2zpbHuE1ESuDkTEwm6KQd65HRNtAq", - "alsoKnownAs": [], - "controller": [], - "verificationMethod": [], + "id": "did:peer:1zQmchWGXSsHohSMrgts5oxG76zAfG49RkMZbhrYqPJeVXc1", "service": [ { "id": "#service-0", @@ -25,7 +22,6 @@ "publicKeyBase58": "CQZzRfoJMRzoESU2VtWrgx3rTsk9yjrjqXL2UdxWjX2q" } ], - "assertionMethod": [], "keyAgreement": [ { "id": "#08673492-3c44-47fe-baa4-a1780c585d75", @@ -33,7 +29,5 @@ "controller": "#id", "publicKeyBase58": "7SbWSgJgjSvSTc7ZAKHJiaZbTBwNM9TdFUAU1UyZfJn8" } - ], - "capabilityInvocation": [], - "capabilityDelegation": [] + ] } diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json index 29c18fde07..6a6e4ed706 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json @@ -5,8 +5,6 @@ "https://w3id.org/security/suites/x25519-2019/v1" ], "id": "did:sov:R1xKJw17sUoXhejEpugMYJ", - "alsoKnownAs": [], - "controller": [], "verificationMethod": [ { "type": "Ed25519VerificationKey2018", @@ -24,8 +22,6 @@ "authentication": ["did:sov:R1xKJw17sUoXhejEpugMYJ#key-1"], "assertionMethod": ["did:sov:R1xKJw17sUoXhejEpugMYJ#key-1"], "keyAgreement": ["did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1"], - "capabilityDelegation": [], - "capabilityInvocation": [], "service": [ { "id": "did:sov:R1xKJw17sUoXhejEpugMYJ#endpoint", diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json b/packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json index 8d975b8304..7b74e0587f 100644 --- a/packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json +++ b/packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json @@ -5,8 +5,6 @@ "https://w3id.org/security/suites/x25519-2019/v1", "https://didcomm.org/messaging/contexts/v2" ], - "alsoKnownAs": [], - "controller": [], "id": "did:sov:WJz9mHyW9BZksioQnRsrAo", "verificationMethod": [ { @@ -22,8 +20,6 @@ "publicKeyBase58": "S3AQEEKkGYrrszT9D55ozVVX2XixYp8uynqVm4okbud" } ], - "capabilityDelegation": [], - "capabilityInvocation": [], "authentication": ["did:sov:WJz9mHyW9BZksioQnRsrAo#key-1"], "assertionMethod": ["did:sov:WJz9mHyW9BZksioQnRsrAo#key-1"], "keyAgreement": ["did:sov:WJz9mHyW9BZksioQnRsrAo#key-agreement-1"], diff --git a/packages/core/src/modules/dids/__tests__/peer-did.test.ts b/packages/core/src/modules/dids/__tests__/peer-did.test.ts index fe4eb519a6..098b16d745 100644 --- a/packages/core/src/modules/dids/__tests__/peer-did.test.ts +++ b/packages/core/src/modules/dids/__tests__/peer-did.test.ts @@ -10,7 +10,8 @@ import { DidDocumentRole } from '../domain/DidDocumentRole' import { convertPublicKeyToX25519, getEd25519VerificationMethod } from '../domain/key-type/ed25519' import { getX25519VerificationMethod } from '../domain/key-type/x25519' import { DidKey } from '../methods/key' -import { DidPeer, PeerDidNumAlgo } from '../methods/peer/DidPeer' +import { getNumAlgoFromPeerDid, PeerDidNumAlgo } from '../methods/peer/didPeer' +import { didDocumentJsonToNumAlgo1Did } from '../methods/peer/peerDidNumAlgo1' import { DidRecord, DidRepository } from '../repository' import { DidResolverService } from '../services' @@ -96,10 +97,15 @@ describe('peer dids', () => { .addService(service) .build() - const peerDid = DidPeer.fromDidDocument(didDocument, PeerDidNumAlgo.GenesisDoc) + const didDocumentJson = didDocument.toJSON() + const did = didDocumentJsonToNumAlgo1Did(didDocumentJson) - expect(peerDid.did).toBe(didPeer1zQmY.id) - expect(peerDid.didDocument).toMatchObject(didPeer1zQmY) + expect(did).toBe(didPeer1zQmY.id) + + // Set did after generating it + didDocument.id = did + + expect(didDocument.toJSON()).toMatchObject(didPeer1zQmY) // Save the record to storage const didDocumentRecord = new DidRecord({ @@ -107,11 +113,11 @@ describe('peer dids', () => { role: DidDocumentRole.Created, // It is important to take the did document from the PeerDid class // as it will have the id property - didDocument: peerDid.didDocument, + didDocument: didDocument, tags: { // We need to save the recipientKeys, so we can find the associated did // of a key when we receive a message from another connection. - recipientKeys: peerDid.didDocument.recipientKeys, + recipientKeys: didDocument.recipientKeys, }, }) @@ -122,33 +128,33 @@ describe('peer dids', () => { // This flow assumes peer dids. When implementing for did exchange other did methods could be used // We receive the did and did document from the did exchange message (request or response) + // It is important to not parse the did document to a DidDocument class yet as we need the raw json + // to consistently verify the hash of the did document const did = didPeer1zQmY.id + const numAlgo = getNumAlgoFromPeerDid(did) // Note that the did document could be undefined (if inlined did:peer or public did) const didDocument = JsonTransformer.fromJSON(didPeer1zQmY, DidDocument) - // Create a did peer instance from the did document document, or only the did if no did document provided - const didPeer = didDocument ? DidPeer.fromDidDocument(didDocument) : DidPeer.fromDid(did) - // make sure the dids are valid by matching them against our encoded variants - expect(didPeer.did).toBe(did) + expect(didDocumentJsonToNumAlgo1Did(didPeer1zQmY)).toBe(did) // If a did document was provided, we match it against the did document of the peer did // This validates whether we get the same did document if (didDocument) { - expect(didPeer.didDocument.toJSON()).toMatchObject(didPeer1zQmY) + expect(didDocument.toJSON()).toMatchObject(didPeer1zQmY) } const didDocumentRecord = new DidRecord({ - id: didPeer.did, + id: did, role: DidDocumentRole.Received, // If the method is a genesis doc (did:peer:1) we should store the document // Otherwise we only need to store the did itself (as the did can be generated) - didDocument: didPeer.numAlgo === PeerDidNumAlgo.GenesisDoc ? didPeer.didDocument : undefined, + didDocument: numAlgo === PeerDidNumAlgo.GenesisDoc ? didDocument : undefined, tags: { // We need to save the recipientKeys, so we can find the associated did // of a key when we receive a message from another connection. - recipientKeys: didPeer.didDocument.recipientKeys, + recipientKeys: didDocument.recipientKeys, }, }) @@ -158,7 +164,7 @@ describe('peer dids', () => { // connectionRecord.theirDid = didPeer.did // Then when we want to send a message we can resolve the did document - const { didDocument: resolvedDidDocument } = await didResolverService.resolve(didPeer.did) + const { didDocument: resolvedDidDocument } = await didResolverService.resolve(did) expect(resolvedDidDocument).toBeInstanceOf(DidDocument) expect(resolvedDidDocument?.toJSON()).toMatchObject(didPeer1zQmY) }) diff --git a/packages/core/src/modules/dids/domain/DidDocument.ts b/packages/core/src/modules/dids/domain/DidDocument.ts index 7602bc06ad..502536715d 100644 --- a/packages/core/src/modules/dids/domain/DidDocument.ts +++ b/packages/core/src/modules/dids/domain/DidDocument.ts @@ -1,15 +1,16 @@ import type { DidDocumentService } from './service' -import { Expose, Transform, Type } from 'class-transformer' -import { IsArray, IsString, ValidateNested } from 'class-validator' +import { Expose, Type } from 'class-transformer' +import { IsArray, IsOptional, IsString, ValidateNested } from 'class-validator' import { JsonTransformer } from '../../../utils/JsonTransformer' +import { IsStringOrStringArray } from '../../../utils/transformers' import { IndyAgentService, ServiceTransformer, DidCommService } from './service' import { VerificationMethodTransformer, VerificationMethod, IsStringOrVerificationMethod } from './verificationMethod' interface DidDocumentOptions { - context?: string[] + context?: string | string[] id: string alsoKnownAs?: string[] controller?: string[] @@ -24,69 +25,75 @@ interface DidDocumentOptions { export class DidDocument { @Expose({ name: '@context' }) - @IsArray() - @Transform((o) => (typeof o.value === 'string' ? [o.value] : o.value), { toClassOnly: true }) - public context = ['https://w3id.org/did/v1'] + @IsStringOrStringArray() + public context: string | string[] = ['https://w3id.org/did/v1'] @IsString() public id!: string @IsArray() @IsString({ each: true }) - public alsoKnownAs: string[] = [] + @IsOptional() + public alsoKnownAs?: string[] - @IsArray() - @IsString({ each: true }) - @Transform((o) => (typeof o.value === 'string' ? [o.value] : o.value), { toClassOnly: true }) - public controller: string[] = [] + @IsStringOrStringArray() + @IsOptional() + public controller?: string | string[] @IsArray() @ValidateNested({ each: true }) @Type(() => VerificationMethod) - public verificationMethod: VerificationMethod[] = [] + @IsOptional() + public verificationMethod?: VerificationMethod[] @IsArray() @ServiceTransformer() - public service: DidDocumentService[] = [] + @IsOptional() + public service?: DidDocumentService[] @IsArray() @VerificationMethodTransformer() @IsStringOrVerificationMethod({ each: true }) - public authentication: Array = [] + @IsOptional() + public authentication?: Array @IsArray() @VerificationMethodTransformer() @IsStringOrVerificationMethod({ each: true }) - public assertionMethod: Array = [] + @IsOptional() + public assertionMethod?: Array @IsArray() @VerificationMethodTransformer() @IsStringOrVerificationMethod({ each: true }) - public keyAgreement: Array = [] + @IsOptional() + public keyAgreement?: Array @IsArray() @VerificationMethodTransformer() @IsStringOrVerificationMethod({ each: true }) - public capabilityInvocation: Array = [] + @IsOptional() + public capabilityInvocation?: Array @IsArray() @VerificationMethodTransformer() @IsStringOrVerificationMethod({ each: true }) - public capabilityDelegation: Array = [] + @IsOptional() + public capabilityDelegation?: Array public constructor(options: DidDocumentOptions) { if (options) { this.context = options.context ?? this.context this.id = options.id - this.alsoKnownAs = options.alsoKnownAs ?? this.alsoKnownAs - this.controller = options.controller ?? this.controller - this.verificationMethod = options.verificationMethod ?? this.verificationMethod - this.service = options.service ?? this.service - this.authentication = options.authentication ?? this.authentication - this.assertionMethod = options.assertionMethod ?? this.assertionMethod - this.keyAgreement = options.keyAgreement ?? this.keyAgreement - this.capabilityInvocation = options.capabilityInvocation ?? this.capabilityInvocation - this.capabilityDelegation = options.capabilityDelegation ?? this.capabilityDelegation + this.alsoKnownAs = options.alsoKnownAs + this.controller = options.controller + this.verificationMethod = options.verificationMethod + this.service = options.service + this.authentication = options.authentication + this.assertionMethod = options.assertionMethod + this.keyAgreement = options.keyAgreement + this.capabilityInvocation = options.capabilityInvocation + this.capabilityDelegation = options.capabilityDelegation } } @@ -94,7 +101,7 @@ export class DidDocument { // TODO: once we use JSON-LD we should use that to resolve references in did documents. // for now we check whether the key id ends with the keyId. // so if looking for #123 and key.id is did:key:123#123, it is valid. But #123 as key.id is also valid - const verificationMethod = this.verificationMethod.find((key) => key.id.endsWith(keyId)) + const verificationMethod = this.verificationMethod?.find((key) => key.id.endsWith(keyId)) if (!verificationMethod) { throw new Error(`Unable to locate verification with id '${keyId}'`) @@ -109,7 +116,7 @@ export class DidDocument { * @param type The type of service(s) to query. */ public getServicesByType(type: string): S[] { - return this.service.filter((service) => service.type === type) as S[] + return (this.service?.filter((service) => service.type === type) ?? []) as S[] } /** @@ -120,7 +127,7 @@ export class DidDocument { public getServicesByClassType( classType: new (...args: never[]) => S ): S[] { - return this.service.filter((service) => service instanceof classType) as S[] + return (this.service?.filter((service) => service instanceof classType) ?? []) as S[] } /** @@ -129,7 +136,7 @@ export class DidDocument { */ public get didCommServices(): Array { const didCommServiceTypes = [IndyAgentService.type, DidCommService.type] - const services = this.service.filter((service) => didCommServiceTypes.includes(service.type)) as Array< + const services = (this.service?.filter((service) => didCommServiceTypes.includes(service.type)) ?? []) as Array< IndyAgentService | DidCommService > diff --git a/packages/core/src/modules/dids/domain/DidDocumentBuilder.ts b/packages/core/src/modules/dids/domain/DidDocumentBuilder.ts index b7e82daa0a..503a1f2759 100644 --- a/packages/core/src/modules/dids/domain/DidDocumentBuilder.ts +++ b/packages/core/src/modules/dids/domain/DidDocumentBuilder.ts @@ -13,76 +13,107 @@ export class DidDocumentBuilder { } public addContext(context: string) { - this.didDocument.context = [...this.didDocument.context, context] + if (typeof this.didDocument.context === 'string') { + this.didDocument.context = [this.didDocument.context, context] + } else { + this.didDocument.context.push(context) + } return this } public addService(service: DidDocumentService) { - this.didDocument.service = [...this.didDocument.service, service] + if (!this.didDocument.service) { + this.didDocument.service = [] + } + + this.didDocument.service.push(service) return this } public addVerificationMethod(verificationMethod: VerificationMethod) { - this.didDocument.verificationMethod = [ - ...this.didDocument.verificationMethod, - verificationMethod instanceof VerificationMethod - ? verificationMethod - : new VerificationMethod(verificationMethod), - ] + if (!this.didDocument.verificationMethod) { + this.didDocument.verificationMethod = [] + } + + this.didDocument.verificationMethod.push( + verificationMethod instanceof VerificationMethod ? verificationMethod : new VerificationMethod(verificationMethod) + ) return this } public addAuthentication(authentication: string | VerificationMethod) { + if (!this.didDocument.authentication) { + this.didDocument.authentication = [] + } + const verificationMethod = authentication instanceof VerificationMethod || typeof authentication === 'string' ? authentication : new VerificationMethod(authentication) - this.didDocument.authentication = [...this.didDocument.authentication, verificationMethod] + this.didDocument.authentication.push(verificationMethod) return this } public addAssertionMethod(assertionMethod: string | VerificationMethod) { + if (!this.didDocument.assertionMethod) { + this.didDocument.assertionMethod = [] + } + const verificationMethod = assertionMethod instanceof VerificationMethod || typeof assertionMethod === 'string' ? assertionMethod : new VerificationMethod(assertionMethod) - this.didDocument.assertionMethod = [...this.didDocument.assertionMethod, verificationMethod] + this.didDocument.assertionMethod.push(verificationMethod) return this } + public addCapabilityDelegation(capabilityDelegation: string | VerificationMethod) { + if (!this.didDocument.capabilityDelegation) { + this.didDocument.capabilityDelegation = [] + } + const verificationMethod = capabilityDelegation instanceof VerificationMethod || typeof capabilityDelegation === 'string' ? capabilityDelegation : new VerificationMethod(capabilityDelegation) - this.didDocument.capabilityDelegation = [...this.didDocument.capabilityDelegation, verificationMethod] + this.didDocument.capabilityDelegation.push(verificationMethod) return this } public addCapabilityInvocation(capabilityInvocation: string | VerificationMethod) { + if (!this.didDocument.capabilityInvocation) { + this.didDocument.capabilityInvocation = [] + } + const verificationMethod = capabilityInvocation instanceof VerificationMethod || typeof capabilityInvocation === 'string' ? capabilityInvocation : new VerificationMethod(capabilityInvocation) - this.didDocument.capabilityInvocation = [...this.didDocument.capabilityInvocation, verificationMethod] + this.didDocument.capabilityInvocation.push(verificationMethod) return this } + public addKeyAgreement(keyAgreement: string | VerificationMethod) { + if (!this.didDocument.keyAgreement) { + this.didDocument.keyAgreement = [] + } + const verificationMethod = keyAgreement instanceof VerificationMethod || typeof keyAgreement === 'string' ? keyAgreement : new VerificationMethod(keyAgreement) - this.didDocument.keyAgreement = [...this.didDocument.keyAgreement, verificationMethod] + this.didDocument.keyAgreement.push(verificationMethod) return this } diff --git a/packages/core/src/modules/dids/domain/__tests__/DidDocument.test.ts b/packages/core/src/modules/dids/domain/__tests__/DidDocument.test.ts index 54c53afb5d..9d1cf36599 100644 --- a/packages/core/src/modules/dids/domain/__tests__/DidDocument.test.ts +++ b/packages/core/src/modules/dids/domain/__tests__/DidDocument.test.ts @@ -109,34 +109,41 @@ describe('Did | DidDocument', () => { expect(didDocument.controller).toEqual(didExample123Fixture.controller) // Check verification method - expect(didDocument.verificationMethod[0]).toBeInstanceOf(VerificationMethod) - expect(didDocument.verificationMethod[1]).toBeInstanceOf(VerificationMethod) - expect(didDocument.verificationMethod[2]).toBeInstanceOf(VerificationMethod) + const verificationMethods = didDocument.verificationMethod ?? [] + expect(verificationMethods[0]).toBeInstanceOf(VerificationMethod) + expect(verificationMethods[1]).toBeInstanceOf(VerificationMethod) + expect(verificationMethods[2]).toBeInstanceOf(VerificationMethod) // Check Service - expect(didDocument.service[0]).toBeInstanceOf(DidDocumentService) - expect(didDocument.service[1]).toBeInstanceOf(IndyAgentService) - expect(didDocument.service[2]).toBeInstanceOf(DidCommService) + const services = didDocument.service ?? [] + expect(services[0]).toBeInstanceOf(DidDocumentService) + expect(services[1]).toBeInstanceOf(IndyAgentService) + expect(services[2]).toBeInstanceOf(DidCommService) // Check Authentication - expect(typeof didDocument.authentication[0]).toBe('string') - expect(didDocument.authentication[1]).toBeInstanceOf(VerificationMethod) + const authentication = didDocument.authentication ?? [] + expect(typeof authentication[0]).toBe('string') + expect(authentication[1]).toBeInstanceOf(VerificationMethod) // Check assertionMethod - expect(typeof didDocument.assertionMethod[0]).toBe('string') - expect(didDocument.assertionMethod[1]).toBeInstanceOf(VerificationMethod) + const assertionMethod = didDocument.assertionMethod ?? [] + expect(typeof assertionMethod[0]).toBe('string') + expect(assertionMethod[1]).toBeInstanceOf(VerificationMethod) // Check capabilityDelegation - expect(typeof didDocument.capabilityDelegation[0]).toBe('string') - expect(didDocument.capabilityDelegation[1]).toBeInstanceOf(VerificationMethod) + const capabilityDelegation = didDocument.capabilityDelegation ?? [] + expect(typeof capabilityDelegation[0]).toBe('string') + expect(capabilityDelegation[1]).toBeInstanceOf(VerificationMethod) // Check capabilityInvocation - expect(typeof didDocument.capabilityInvocation[0]).toBe('string') - expect(didDocument.capabilityInvocation[1]).toBeInstanceOf(VerificationMethod) + const capabilityInvocation = didDocument.capabilityInvocation ?? [] + expect(typeof capabilityInvocation[0]).toBe('string') + expect(capabilityInvocation[1]).toBeInstanceOf(VerificationMethod) // Check keyAgreement - expect(typeof didDocument.keyAgreement[0]).toBe('string') - expect(didDocument.keyAgreement[1]).toBeInstanceOf(VerificationMethod) + const keyAgreement = didDocument.keyAgreement ?? [] + expect(typeof keyAgreement[0]).toBe('string') + expect(keyAgreement[1]).toBeInstanceOf(VerificationMethod) }) it('validation should throw an error if the did document is invalid', async () => { @@ -245,7 +252,7 @@ describe('Did | DidDocument', () => { describe('getServicesByType', () => { it('returns all services with specified type', async () => { expect(didDocumentInstance.getServicesByType('IndyAgent')).toEqual( - didDocumentInstance.service.filter((service) => service.type === 'IndyAgent') + didDocumentInstance.service?.filter((service) => service.type === 'IndyAgent') ) }) }) @@ -253,23 +260,22 @@ describe('Did | DidDocument', () => { describe('getServicesByClassType', () => { it('returns all services with specified class', async () => { expect(didDocumentInstance.getServicesByClassType(IndyAgentService)).toEqual( - didDocumentInstance.service.filter((service) => service instanceof IndyAgentService) + didDocumentInstance.service?.filter((service) => service instanceof IndyAgentService) ) }) }) describe('didCommServices', () => { it('returns all IndyAgentService and DidCommService instances', async () => { - expect(didDocumentInstance.didCommServices).toEqual( - expect.arrayContaining([didDocumentInstance.service[1], didDocumentInstance.service[2]]) - ) + const services = didDocumentInstance.service ?? [] + + expect(didDocumentInstance.didCommServices).toEqual(expect.arrayContaining([services[1], services[2]])) }) it('returns all IndyAgentService and DidCommService instances sorted by priority', async () => { - expect(didDocumentInstance.didCommServices).toEqual([ - didDocumentInstance.service[2], - didDocumentInstance.service[1], - ]) + const services = didDocumentInstance.service ?? [] + + expect(didDocumentInstance.didCommServices).toEqual([services[2], services[1]]) }) }) }) diff --git a/packages/core/src/modules/dids/domain/service/ServiceTransformer.ts b/packages/core/src/modules/dids/domain/service/ServiceTransformer.ts index 6803273476..31689ac980 100644 --- a/packages/core/src/modules/dids/domain/service/ServiceTransformer.ts +++ b/packages/core/src/modules/dids/domain/service/ServiceTransformer.ts @@ -24,8 +24,8 @@ export const serviceTypes: { [key: string]: unknown | undefined } = { */ export function ServiceTransformer() { return Transform( - ({ value }: { value: { type: string }[] }) => { - return value.map((serviceJson) => { + ({ value }: { value?: Array<{ type: string }> }) => { + return value?.map((serviceJson) => { const serviceClass = (serviceTypes[serviceJson.type] ?? DidDocumentService) as ClassConstructor const service = plainToInstance(serviceClass, serviceJson) diff --git a/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethodTransformer.ts b/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethodTransformer.ts index d0ee8ae976..43dfd2c1c0 100644 --- a/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethodTransformer.ts +++ b/packages/core/src/modules/dids/domain/verificationMethod/VerificationMethodTransformer.ts @@ -36,9 +36,9 @@ function IsStringOrVerificationMethod(validationOptions?: ValidationOptions): Pr * } */ function VerificationMethodTransformer() { - return Transform(({ value, type }: { value: Array; type: TransformationType }) => { + return Transform(({ value, type }: { value?: Array; type: TransformationType }) => { if (type === TransformationType.PLAIN_TO_CLASS) { - return value.map((auth) => { + return value?.map((auth) => { // referenced verification method if (typeof auth === 'string') { return String(auth) @@ -48,7 +48,7 @@ function VerificationMethodTransformer() { return JsonTransformer.fromJSON(auth, VerificationMethod) }) } else if (type === TransformationType.CLASS_TO_PLAIN) { - return value.map((auth) => (typeof auth === 'string' ? auth : JsonTransformer.toJSON(auth))) + return value?.map((auth) => (typeof auth === 'string' ? auth : JsonTransformer.toJSON(auth))) } // PLAIN_TO_PLAIN diff --git a/packages/core/src/modules/dids/index.ts b/packages/core/src/modules/dids/index.ts index aec5563aca..573f20b4cd 100644 --- a/packages/core/src/modules/dids/index.ts +++ b/packages/core/src/modules/dids/index.ts @@ -3,4 +3,3 @@ export * from './domain' export * from './DidsModule' export * from './services' export { DidKey } from './methods/key/DidKey' -export { DidPeer } from './methods/peer/DidPeer' diff --git a/packages/core/src/modules/dids/methods/peer/DidPeer.ts b/packages/core/src/modules/dids/methods/peer/DidPeer.ts deleted file mode 100644 index 33a5a8bd78..0000000000 --- a/packages/core/src/modules/dids/methods/peer/DidPeer.ts +++ /dev/null @@ -1,136 +0,0 @@ -import type { DidDocument } from '../../domain' -import type { ParsedDid } from '../../types' - -import { instanceToInstance } from 'class-transformer' - -import { JsonEncoder, MultiBaseEncoder, MultiHashEncoder } from '../../../../utils' -import { Key } from '../../domain/Key' -import { getKeyDidMappingByKeyType } from '../../domain/key-type' -import { parseDid } from '../../domain/parse' - -import { didDocumentToNumAlgo2Did, didToNumAlgo2DidDocument } from './peerDidNumAlgo2' - -const PEER_DID_REGEX = new RegExp( - '^did:peer:(([01](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))|(2((.[AEVID](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))+(.(S)[0-9a-zA-Z=]*)?)))$' -) - -export const enum PeerDidNumAlgo { - InceptionKeyWithoutDoc = 0, - GenesisDoc = 1, - MultipleInceptionKeyWithoutDoc = 2, -} - -function getNumAlgoFromPeerDid(did: string) { - return Number(did[9]) -} - -export class DidPeer { - private readonly parsedDid: ParsedDid - - // If numAlgo 1 is used, the did document always has a did document - private readonly _didDocument?: DidDocument - - private constructor({ didDocument, did }: { did: string; didDocument?: DidDocument }) { - const parsed = parseDid(did) - - if (!this.isValidPeerDid(did)) { - throw new Error(`Invalid peer did '${did}'`) - } - - this.parsedDid = parsed - this._didDocument = didDocument - } - - public static fromKey(key: Key) { - const did = `did:peer:0${key.fingerprint}` - return new DidPeer({ did }) - } - - public static fromDid(did: string) { - return new DidPeer({ - did, - }) - } - - public static fromDidDocument( - didDocument: DidDocument, - numAlgo?: PeerDidNumAlgo.GenesisDoc | PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc - ): DidPeer { - if (!numAlgo && didDocument.id.startsWith('did:peer:')) { - numAlgo = getNumAlgoFromPeerDid(didDocument.id) - } - - if (!numAlgo) { - throw new Error( - 'Could not determine numAlgo. The did document must either have a full id property containing the numAlgo, or the numAlgo must be provided as a separate property' - ) - } - - if (numAlgo === PeerDidNumAlgo.GenesisDoc) { - // FIXME: We should do this on the JSON value of the did document, as the DidDocument class - // adds a lot of properties and default values that will mess with the hash value - // Remove id from did document as the id should be generated without an id. - const didDocumentBuffer = JsonEncoder.toBuffer({ ...didDocument.toJSON(), id: undefined }) - - const didIdentifier = MultiBaseEncoder.encode(MultiHashEncoder.encode(didDocumentBuffer, 'sha2-256'), 'base58btc') - - const did = `did:peer:1${didIdentifier}` - - return new DidPeer({ did, didDocument }) - } else if (numAlgo === PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc) { - const did = didDocumentToNumAlgo2Did(didDocument) - return new DidPeer({ did }) - } else { - throw new Error(`Unsupported numAlgo: ${numAlgo}. Not all peer did methods support parsing a did document`) - } - } - - public get did() { - return this.parsedDid.did - } - - public get numAlgo(): PeerDidNumAlgo { - // numalgo is the first digit of the method specific identifier - return Number(this.parsedDid.id[0]) as PeerDidNumAlgo - } - - private get identifierWithoutNumAlgo() { - return this.parsedDid.id.substring(1) - } - - private isValidPeerDid(did: string): boolean { - const isValid = PEER_DID_REGEX.test(did) - - return isValid - } - - public get didDocument() { - // Method 1 (numAlgo 0) - if (this.numAlgo === PeerDidNumAlgo.InceptionKeyWithoutDoc) { - const key = Key.fromFingerprint(this.identifierWithoutNumAlgo) - const { getDidDocument } = getKeyDidMappingByKeyType(key.keyType) - - return getDidDocument(this.parsedDid.did, key) - } - // Method 2 (numAlgo 1) - else if (this.numAlgo === PeerDidNumAlgo.GenesisDoc) { - if (!this._didDocument) { - throw new Error('No did document provided for method 1 peer did') - } - - // Clone the document, and set the id - const didDocument = instanceToInstance(this._didDocument) - didDocument.id = this.did - - return didDocument - } - // Method 3 (numAlgo 2) - else if (this.numAlgo === PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc) { - const didDocument = didToNumAlgo2DidDocument(this.parsedDid.did) - - return didDocument - } - - throw new Error(`Unsupported numAlgo '${this.numAlgo}'`) - } -} diff --git a/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts b/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts index 2c6ae9a0dd..6aebfda5f2 100644 --- a/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts +++ b/packages/core/src/modules/dids/methods/peer/PeerDidResolver.ts @@ -5,7 +5,9 @@ import type { DidResolutionResult } from '../../types' import { AriesFrameworkError } from '../../../../error' -import { DidPeer, PeerDidNumAlgo } from './DidPeer' +import { getNumAlgoFromPeerDid, isValidPeerDid, PeerDidNumAlgo } from './didPeer' +import { didToNumAlgo0DidDocument } from './peerDidNumAlgo0' +import { didToNumAlgo2DidDocument } from './peerDidNumAlgo2' export class PeerDidResolver implements DidResolver { public readonly supportedMethods = ['peer'] @@ -20,12 +22,20 @@ export class PeerDidResolver implements DidResolver { const didDocumentMetadata = {} try { - const didPeer = DidPeer.fromDid(did) - let didDocument: DidDocument + if (!isValidPeerDid(did)) { + throw new AriesFrameworkError(`did ${did} is not a valid peer did`) + } + + const numAlgo = getNumAlgoFromPeerDid(did) + + // For method 0, generate from did + if (numAlgo === PeerDidNumAlgo.InceptionKeyWithoutDoc) { + didDocument = didToNumAlgo0DidDocument(did) + } // For Method 1, retrieve from storage - if (didPeer.numAlgo === PeerDidNumAlgo.GenesisDoc) { + else if (numAlgo === PeerDidNumAlgo.GenesisDoc) { const didDocumentRecord = await this.didRepository.getById(did) if (!didDocumentRecord.didDocument) { @@ -33,8 +43,10 @@ export class PeerDidResolver implements DidResolver { } didDocument = didDocumentRecord.didDocument - } else { - didDocument = didPeer.didDocument + } + // For Method 2, generate from did + else { + didDocument = didToNumAlgo2DidDocument(did) } return { diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/DidPeer.test.ts b/packages/core/src/modules/dids/methods/peer/__tests__/DidPeer.test.ts deleted file mode 100644 index 43be0a0484..0000000000 --- a/packages/core/src/modules/dids/methods/peer/__tests__/DidPeer.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { JsonTransformer } from '../../../../../utils' -import didKeyBls12381g1 from '../../../__tests__/__fixtures__/didKeyBls12381g1.json' -import didKeyBls12381g1g2 from '../../../__tests__/__fixtures__/didKeyBls12381g1g2.json' -import didKeyBls12381g2 from '../../../__tests__/__fixtures__/didKeyBls12381g2.json' -import didKeyEd25519 from '../../../__tests__/__fixtures__/didKeyEd25519.json' -import didKeyX25519 from '../../../__tests__/__fixtures__/didKeyX25519.json' -import { DidDocument, Key } from '../../../domain' -import { DidPeer, PeerDidNumAlgo } from '../DidPeer' - -import didPeer1zQmR from './__fixtures__/didPeer1zQmR.json' -import didPeer1zQmZ from './__fixtures__/didPeer1zQmZ.json' -import didPeer2Ez6L from './__fixtures__/didPeer2Ez6L.json' - -describe('DidPeer', () => { - test('transforms a key correctly into a peer did method 0 did document', async () => { - const didDocuments = [didKeyEd25519, didKeyBls12381g1, didKeyX25519, didKeyBls12381g1g2, didKeyBls12381g2] - - for (const didDocument of didDocuments) { - const key = Key.fromFingerprint(didDocument.id.split(':')[2]) - - const didPeer = DidPeer.fromKey(key) - const expectedDidPeerDocument = JSON.parse( - JSON.stringify(didDocument).replace(new RegExp('did:key:', 'g'), 'did:peer:0') - ) - - expect(didPeer.didDocument.toJSON()).toMatchObject(expectedDidPeerDocument) - } - }) - - test('transforms a method 2 did correctly into a did document', () => { - expect(DidPeer.fromDid(didPeer2Ez6L.id).didDocument.toJSON()).toMatchObject(didPeer2Ez6L) - }) - - test('transforms a method 0 did correctly into a did document', () => { - const didDocuments = [didKeyEd25519, didKeyBls12381g1, didKeyX25519, didKeyBls12381g1g2, didKeyBls12381g2] - - for (const didDocument of didDocuments) { - const didPeer = DidPeer.fromDid(didDocument.id.replace('did:key:', 'did:peer:0')) - const expectedDidPeerDocument = JSON.parse( - JSON.stringify(didDocument).replace(new RegExp('did:key:', 'g'), 'did:peer:0') - ) - - expect(didPeer.didDocument.toJSON()).toMatchObject(expectedDidPeerDocument) - } - }) - - test('transforms a did document into a valid method 2 did', () => { - const didPeer2 = DidPeer.fromDidDocument( - JsonTransformer.fromJSON(didPeer2Ez6L, DidDocument), - PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc - ) - - expect(didPeer2.did).toBe(didPeer2Ez6L.id) - }) - - test('transforms a did document into a valid method 1 did', () => { - const didPeer1 = DidPeer.fromDidDocument( - JsonTransformer.fromJSON(didPeer1zQmR, DidDocument), - PeerDidNumAlgo.GenesisDoc - ) - - expect(didPeer1.did).toBe(didPeer1zQmR.id) - }) - - // FIXME: we need some input data from AFGO for this test to succeed (we create a hash of the document, so any inconsistency is fatal) - xtest('transforms a did document from aries-framework-go into a valid method 1 did', () => { - const didPeer1 = DidPeer.fromDidDocument( - JsonTransformer.fromJSON(didPeer1zQmZ, DidDocument), - PeerDidNumAlgo.GenesisDoc - ) - - expect(didPeer1.did).toBe(didPeer1zQmZ.id) - }) - - test('extracts the numAlgo from the peer did', async () => { - // NumAlgo 0 - const key = Key.fromFingerprint(didKeyEd25519.id.split(':')[2]) - const didPeerNumAlgo0 = DidPeer.fromKey(key) - - expect(didPeerNumAlgo0.numAlgo).toBe(PeerDidNumAlgo.InceptionKeyWithoutDoc) - expect(DidPeer.fromDid('did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL').numAlgo).toBe( - PeerDidNumAlgo.InceptionKeyWithoutDoc - ) - - // NumAlgo 1 - const peerDidNumAlgo1 = 'did:peer:1zQmZMygzYqNwU6Uhmewx5Xepf2VLp5S4HLSwwgf2aiKZuwa' - expect(DidPeer.fromDid(peerDidNumAlgo1).numAlgo).toBe(PeerDidNumAlgo.GenesisDoc) - - // NumAlgo 2 - const peerDidNumAlgo2 = - 'did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0' - expect(DidPeer.fromDid(peerDidNumAlgo2).numAlgo).toBe(PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc) - expect(DidPeer.fromDidDocument(JsonTransformer.fromJSON(didPeer2Ez6L, DidDocument)).numAlgo).toBe( - PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc - ) - }) -}) diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmR.json b/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmR.json index 75d2e1d6eb..f20f5c2aab 100644 --- a/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmR.json +++ b/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmR.json @@ -1,6 +1,6 @@ { "@context": ["https://w3id.org/did/v1"], - "id": "did:peer:1zQmRYBx1pL86DrsxoJ2ZD3w42d7Ng92ErPgFsCSqg8Q1h4i", + "id": "did:peer:1zQmXv3d2vqC2Q9JrnrFqqj5h8vzcNAumL1UZbb1TGh58j2c", "authentication": [ { "id": "#6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V", diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmZ.json b/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmZ.json index 17d3d00c53..659ccf98d4 100644 --- a/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmZ.json +++ b/packages/core/src/modules/dids/methods/peer/__tests__/__fixtures__/didPeer1zQmZ.json @@ -1,6 +1,14 @@ { "@context": ["https://w3id.org/did/v1", "https://w3id.org/did/v2"], "id": "did:peer:1zQmZdT2jawCX5T1RKUB7ro83gQuiKbuHwuHi8G1NypB8BTr", + "authentication": [ + { + "id": "did:example:123456789abcdefghs#key3", + "type": "RsaVerificationKey2018", + "controller": "did:example:123456789abcdefghs", + "publicKeyHex": "02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71" + } + ], "verificationMethod": [ { "id": "did:example:123456789abcdefghi#keys-1", @@ -15,13 +23,5 @@ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAryQICCl6NZ5gDKrnSztO\n3Hy8PEUcuyvg/ikC+VcIo2SFFSf18a3IMYldIugqqqZCs4/4uVW3sbdLs/6PfgdX\n7O9D22ZiFWHPYA2k2N744MNiCD1UE+tJyllUhSblK48bn+v1oZHCM0nYQ2NqUkvS\nj+hwUU3RiWl7x3D2s9wSdNt7XUtW05a/FXehsPSiJfKvHJJnGOX0BgTvkLnkAOTd\nOrUZ/wK69Dzu4IvrN4vs9Nes8vbwPa/ddZEzGR0cQMt0JBkhk9kU/qwqUseP1QRJ\n5I1jR4g8aYPL/ke9K35PxZWuDp3U0UPAZ3PjFAh+5T+fc7gzCs9dPzSHloruU+gl\nFQIDAQAB\n-----END PUBLIC KEY-----" } ], - "authentication": [ - { - "id": "did:example:123456789abcdefghs#key3", - "type": "RsaVerificationKey2018", - "controller": "did:example:123456789abcdefghs", - "publicKeyHex": "02b97c30de767f084ce3080168ee293053ba33b235d7116a3263d29f1450936b71" - } - ], "created": "0001-01-01 00:00:00 +0000 UTC" } diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/didPeer.test.ts b/packages/core/src/modules/dids/methods/peer/__tests__/didPeer.test.ts new file mode 100644 index 0000000000..99716995f5 --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/__tests__/didPeer.test.ts @@ -0,0 +1,40 @@ +import { isValidPeerDid, getNumAlgoFromPeerDid, PeerDidNumAlgo } from '../didPeer' + +describe('didPeer', () => { + test('isValidPeerDid', () => { + expect(isValidPeerDid('did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL')).toBe(true) + expect(isValidPeerDid('did:peer:1zQmZMygzYqNwU6Uhmewx5Xepf2VLp5S4HLSwwgf2aiKZuwa')).toBe(true) + expect( + isValidPeerDid( + 'did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0' + ) + ).toBe(true) + + expect( + isValidPeerDid( + 'did:peer:4.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0' + ) + ).toBe(false) + }) + + describe('getNumAlgoFromPeerDid', () => { + test('extracts the numAlgo from the peer did', async () => { + // NumAlgo 0 + expect(getNumAlgoFromPeerDid('did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL')).toBe( + PeerDidNumAlgo.InceptionKeyWithoutDoc + ) + + // NumAlgo 1 + expect(getNumAlgoFromPeerDid('did:peer:1zQmZMygzYqNwU6Uhmewx5Xepf2VLp5S4HLSwwgf2aiKZuwa')).toBe( + PeerDidNumAlgo.GenesisDoc + ) + + // NumAlgo 2 + expect( + getNumAlgoFromPeerDid( + 'did:peer:2.Ez6LSbysY2xFMRpGMhb7tFTLMpeuPRaqaWM1yECx2AtzE3KCc.Vz6MkqRYqQiSgvZQdnBytw86Qbs2ZWUkGv22od935YF4s8M7V.Vz6MkgoLTnTypo3tDRwCkZXSccTPHRLhF4ZnjhueYAFpEX6vg.SeyJ0IjoiZG0iLCJzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9lbmRwb2ludCIsInIiOlsiZGlkOmV4YW1wbGU6c29tZW1lZGlhdG9yI3NvbWVrZXkiXSwiYSI6WyJkaWRjb21tL3YyIiwiZGlkY29tbS9haXAyO2Vudj1yZmM1ODciXX0' + ) + ).toBe(PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc) + }) + }) +}) diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/peerDidNumAlgo0.test.ts b/packages/core/src/modules/dids/methods/peer/__tests__/peerDidNumAlgo0.test.ts new file mode 100644 index 0000000000..7433903849 --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/__tests__/peerDidNumAlgo0.test.ts @@ -0,0 +1,41 @@ +import didKeyBls12381g1 from '../../../__tests__/__fixtures__/didKeyBls12381g1.json' +import didKeyBls12381g1g2 from '../../../__tests__/__fixtures__/didKeyBls12381g1g2.json' +import didKeyBls12381g2 from '../../../__tests__/__fixtures__/didKeyBls12381g2.json' +import didKeyEd25519 from '../../../__tests__/__fixtures__/didKeyEd25519.json' +import didKeyX25519 from '../../../__tests__/__fixtures__/didKeyX25519.json' +import { Key } from '../../../domain' +import { didToNumAlgo0DidDocument, keyToNumAlgo0DidDocument } from '../peerDidNumAlgo0' + +describe('peerDidNumAlgo0', () => { + describe('keyToNumAlgo0DidDocument', () => { + test('transforms a key correctly into a peer did method 0 did document', async () => { + const didDocuments = [didKeyEd25519, didKeyBls12381g1, didKeyX25519, didKeyBls12381g1g2, didKeyBls12381g2] + + for (const didDocument of didDocuments) { + const key = Key.fromFingerprint(didDocument.id.split(':')[2]) + + const didPeerDocument = keyToNumAlgo0DidDocument(key) + const expectedDidPeerDocument = JSON.parse( + JSON.stringify(didDocument).replace(new RegExp('did:key:', 'g'), 'did:peer:0') + ) + + expect(didPeerDocument.toJSON()).toMatchObject(expectedDidPeerDocument) + } + }) + }) + + describe('didToNumAlgo0DidDocument', () => { + test('transforms a method 0 did correctly into a did document', () => { + const didDocuments = [didKeyEd25519, didKeyBls12381g1, didKeyX25519, didKeyBls12381g1g2, didKeyBls12381g2] + + for (const didDocument of didDocuments) { + const didPeer = didToNumAlgo0DidDocument(didDocument.id.replace('did:key:', 'did:peer:0')) + const expectedDidPeerDocument = JSON.parse( + JSON.stringify(didDocument).replace(new RegExp('did:key:', 'g'), 'did:peer:0') + ) + + expect(didPeer.toJSON()).toMatchObject(expectedDidPeerDocument) + } + }) + }) +}) diff --git a/packages/core/src/modules/dids/methods/peer/__tests__/peerDidNumAlgo1.test.ts b/packages/core/src/modules/dids/methods/peer/__tests__/peerDidNumAlgo1.test.ts new file mode 100644 index 0000000000..c4cd88219f --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/__tests__/peerDidNumAlgo1.test.ts @@ -0,0 +1,17 @@ +import { didDocumentJsonToNumAlgo1Did } from '../peerDidNumAlgo1' + +import didPeer1zQmR from './__fixtures__/didPeer1zQmR.json' +import didPeer1zQmZ from './__fixtures__/didPeer1zQmZ.json' + +describe('peerDidNumAlgo1', () => { + describe('didDocumentJsonToNumAlgo1Did', () => { + test('transforms a did document into a valid method 1 did', async () => { + expect(didDocumentJsonToNumAlgo1Did(didPeer1zQmR)).toEqual(didPeer1zQmR.id) + }) + + // FIXME: we need some input data from AFGO for this test to succeed (we create a hash of the document, so any inconsistency is fatal) + xtest('transforms a did document from aries-framework-go into a valid method 1 did', () => { + expect(didDocumentJsonToNumAlgo1Did(didPeer1zQmZ)).toEqual(didPeer1zQmZ.id) + }) + }) +}) diff --git a/packages/core/src/modules/dids/methods/peer/didPeer.ts b/packages/core/src/modules/dids/methods/peer/didPeer.ts new file mode 100644 index 0000000000..b21aa77306 --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/didPeer.ts @@ -0,0 +1,29 @@ +const PEER_DID_REGEX = new RegExp( + '^did:peer:(([01](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))|(2((.[AEVID](z)([1-9a-km-zA-HJ-NP-Z]{5,200}))+(.(S)[0-9a-zA-Z=]*)?)))$' +) + +export function isValidPeerDid(did: string): boolean { + const isValid = PEER_DID_REGEX.test(did) + + return isValid +} + +export const enum PeerDidNumAlgo { + InceptionKeyWithoutDoc = 0, + GenesisDoc = 1, + MultipleInceptionKeyWithoutDoc = 2, +} + +export function getNumAlgoFromPeerDid(did: string) { + const numAlgo = Number(did[9]) + + if ( + numAlgo !== PeerDidNumAlgo.InceptionKeyWithoutDoc && + numAlgo !== PeerDidNumAlgo.GenesisDoc && + numAlgo !== PeerDidNumAlgo.MultipleInceptionKeyWithoutDoc + ) { + throw new Error(`Invalid peer did numAlgo: ${numAlgo}`) + } + + return numAlgo as PeerDidNumAlgo +} diff --git a/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo0.ts b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo0.ts new file mode 100644 index 0000000000..11a8dcbe14 --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo0.ts @@ -0,0 +1,32 @@ +import { Key } from '../../domain/Key' +import { getKeyDidMappingByKeyType } from '../../domain/key-type' +import { parseDid } from '../../domain/parse' + +import { getNumAlgoFromPeerDid, isValidPeerDid, PeerDidNumAlgo } from './didPeer' + +export function keyToNumAlgo0DidDocument(key: Key) { + const { getDidDocument } = getKeyDidMappingByKeyType(key.keyType) + + const did = `did:peer:0${key.fingerprint}` + + return getDidDocument(did, key) +} + +export function didToNumAlgo0DidDocument(did: string) { + const parsed = parseDid(did) + const numAlgo = getNumAlgoFromPeerDid(did) + + if (!isValidPeerDid(did)) { + throw new Error(`Invalid peer did '${did}'`) + } + + if (numAlgo !== PeerDidNumAlgo.InceptionKeyWithoutDoc) { + throw new Error(`Invalid numAlgo ${numAlgo}, expected ${PeerDidNumAlgo.InceptionKeyWithoutDoc}`) + } + + const key = Key.fromFingerprint(parsed.id.substring(1)) + + const { getDidDocument } = getKeyDidMappingByKeyType(key.keyType) + + return getDidDocument(did, key) +} diff --git a/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo1.ts b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo1.ts new file mode 100644 index 0000000000..bcbb5db2bc --- /dev/null +++ b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo1.ts @@ -0,0 +1,12 @@ +import { JsonEncoder, MultiBaseEncoder, MultiHashEncoder } from '../../../../utils' + +export function didDocumentJsonToNumAlgo1Did(didDocumentJson: Record): string { + // We need to remove the id property before hashing + const didDocumentBuffer = JsonEncoder.toBuffer({ ...didDocumentJson, id: undefined }) + + const didIdentifier = MultiBaseEncoder.encode(MultiHashEncoder.encode(didDocumentBuffer, 'sha2-256'), 'base58btc') + + const did = `did:peer:1${didIdentifier}` + + return did +} diff --git a/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts index 502a1344ba..c873a9b508 100644 --- a/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts +++ b/packages/core/src/modules/dids/methods/peer/peerDidNumAlgo2.ts @@ -103,6 +103,9 @@ export function didDocumentToNumAlgo2Did(didDocument: DidDocument) { let did = 'did:peer:2' for (const [purpose, entries] of Object.entries(purposeMapping)) { + // Not all entries are required to be defined + if (entries === undefined) continue + // Dereference all entries to full verification methods const dereferenced = entries.map((entry) => (typeof entry === 'string' ? didDocument.dereferenceKey(entry) : entry)) @@ -121,7 +124,7 @@ export function didDocumentToNumAlgo2Did(didDocument: DidDocument) { did += encoded.join('') } - if (didDocument.service.length > 0) { + if (didDocument.service && didDocument.service.length > 0) { const abbreviatedServices = didDocument.service.map((service) => { // Transform to JSON, remove id property const serviceJson = JsonTransformer.toJSON(service) diff --git a/packages/core/src/modules/indy/services/IndyRevocationService.ts b/packages/core/src/modules/indy/services/IndyRevocationService.ts index 6d9b4b6e90..4b88e88413 100644 --- a/packages/core/src/modules/indy/services/IndyRevocationService.ts +++ b/packages/core/src/modules/indy/services/IndyRevocationService.ts @@ -1,6 +1,6 @@ import type { Logger } from '../../../logger' import type { FileSystem } from '../../../storage/FileSystem' -import type { RevocationInterval } from '../../credentials/models/RevocationInterval' +import type { RevocationInterval } from '../../credentials' import type { RequestedCredentials } from '../../proofs' import type { default as Indy } from 'indy-sdk' diff --git a/packages/core/src/modules/ledger/services/IndyLedgerService.ts b/packages/core/src/modules/ledger/services/IndyLedgerService.ts index eb31b8b1d5..7e6cf89fa9 100644 --- a/packages/core/src/modules/ledger/services/IndyLedgerService.ts +++ b/packages/core/src/modules/ledger/services/IndyLedgerService.ts @@ -267,7 +267,7 @@ export class IndyLedgerService { public async getRevocationRegistryDefinition( revocationRegistryDefinitionId: string - ): Promise { + ): Promise { const did = didFromRevocationRegistryDefinitionId(revocationRegistryDefinitionId) const { pool } = await this.indyPoolService.getPoolForDid(did) @@ -527,7 +527,7 @@ export interface CredentialDefinitionTemplate { supportRevocation: boolean } -export interface ParseRevocationRegistryDefitinionTemplate { +export interface ParseRevocationRegistryDefinitionTemplate { revocationRegistryDefinition: Indy.RevocRegDef revocationRegistryDefinitionTxnTime: number } diff --git a/packages/core/src/modules/proofs/messages/PresentationMessage.ts b/packages/core/src/modules/proofs/messages/PresentationMessage.ts index a306026abe..cecc333d79 100644 --- a/packages/core/src/modules/proofs/messages/PresentationMessage.ts +++ b/packages/core/src/modules/proofs/messages/PresentationMessage.ts @@ -29,7 +29,7 @@ export class PresentationMessage extends AgentMessage { this.id = options.id ?? this.generateId() this.comment = options.comment this.presentationAttachments = options.presentationAttachments - this.attachments = options.attachments + this.appendedAttachments = options.attachments } } diff --git a/packages/core/src/modules/proofs/services/ProofService.ts b/packages/core/src/modules/proofs/services/ProofService.ts index f0f9a490e2..24ce0ab284 100644 --- a/packages/core/src/modules/proofs/services/ProofService.ts +++ b/packages/core/src/modules/proofs/services/ProofService.ts @@ -16,9 +16,9 @@ import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../error' -import { checkProofRequestForDuplicates } from '../../../utils' import { JsonEncoder } from '../../../utils/JsonEncoder' import { JsonTransformer } from '../../../utils/JsonTransformer' +import { checkProofRequestForDuplicates } from '../../../utils/indyProofRequest' import { uuid } from '../../../utils/uuid' import { Wallet } from '../../../wallet/Wallet' import { AckStatus } from '../../common' @@ -730,7 +730,7 @@ export class ProofService { for (const credentialId of credentialIds) { // Get the credentialRecord that matches the ID - const credentialRecord = await this.credentialRepository.getSingleByQuery({ credentialId }) + const credentialRecord = await this.credentialRepository.getSingleByQuery({ credentialIds: [credentialId] }) if (credentialRecord.linkedAttachments) { // Get the credentials that have a hashlink as value and are requested diff --git a/packages/core/src/modules/routing/messages/MessageDeliveryMessage.ts b/packages/core/src/modules/routing/messages/MessageDeliveryMessage.ts index a5dbf8f57f..7749260391 100644 --- a/packages/core/src/modules/routing/messages/MessageDeliveryMessage.ts +++ b/packages/core/src/modules/routing/messages/MessageDeliveryMessage.ts @@ -19,7 +19,7 @@ export class MessageDeliveryMessage extends AgentMessage { if (options) { this.id = options.id || this.generateId() this.recipientKey = options.recipientKey - this.attachments = options.attachments + this.appendedAttachments = options.attachments } this.setReturnRouting(ReturnRouteTypes.all) } diff --git a/packages/core/src/modules/routing/services/MediationRecipientService.ts b/packages/core/src/modules/routing/services/MediationRecipientService.ts index 268e7a4e78..1682842007 100644 --- a/packages/core/src/modules/routing/services/MediationRecipientService.ts +++ b/packages/core/src/modules/routing/services/MediationRecipientService.ts @@ -278,15 +278,15 @@ export class MediationRecipientService { } public async processDelivery(messageDeliveryMessage: MessageDeliveryMessage) { - const { attachments } = messageDeliveryMessage + const { appendedAttachments } = messageDeliveryMessage - if (!attachments) + if (!appendedAttachments) throw new ProblemReportError('Error processing attachments', { problemCode: RoutingProblemReportReason.ErrorProcessingAttachments, }) const ids: string[] = [] - for (const attachment of attachments) { + for (const attachment of appendedAttachments) { ids.push(attachment.id) try { await this.messageReceiver.receiveMessage(attachment.getDataAsJson()) diff --git a/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts b/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts index 13b4a10e3e..d847e7980c 100644 --- a/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts +++ b/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts @@ -39,7 +39,7 @@ describe('Repository', () => { const record = getRecord({ id: 'test-id' }) mockFunction(storageMock.findByQuery).mockReturnValue(Promise.resolve([record])) - const invitation = await repository.getAgentMessage({ + const invitation = await repository.findAgentMessage({ messageClass: ConnectionInvitationMessage, associatedRecordId: '04a2c382-999e-4de9-a1d2-9dec0b2fa5e4', }) diff --git a/packages/core/src/storage/migration/StorageUpdateService.ts b/packages/core/src/storage/migration/StorageUpdateService.ts index 0e568a458e..fa05241d0f 100644 --- a/packages/core/src/storage/migration/StorageUpdateService.ts +++ b/packages/core/src/storage/migration/StorageUpdateService.ts @@ -7,7 +7,7 @@ import { AgentConfig } from '../../agent/AgentConfig' import { StorageVersionRecord } from './repository/StorageVersionRecord' import { StorageVersionRepository } from './repository/StorageVersionRepository' -import { INITIAL_STORAGE_VERSION, CURRENT_FRAMEWORK_STORAGE_VERSION } from './updates' +import { CURRENT_FRAMEWORK_STORAGE_VERSION, INITIAL_STORAGE_VERSION } from './updates' @scoped(Lifecycle.ContainerScoped) export class StorageUpdateService { diff --git a/packages/core/src/storage/migration/__tests__/0.1.test.ts b/packages/core/src/storage/migration/__tests__/0.1.test.ts index 483e21c0e7..6c6acff475 100644 --- a/packages/core/src/storage/migration/__tests__/0.1.test.ts +++ b/packages/core/src/storage/migration/__tests__/0.1.test.ts @@ -5,8 +5,8 @@ import path from 'path' import { container as baseContainer } from 'tsyringe' import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' +import { Agent } from '../../../../src' import { agentDependencies } from '../../../../tests/helpers' -import { Agent } from '../../../agent/Agent' import { InjectionSymbols } from '../../../constants' import { UpdateAssistant } from '../UpdateAssistant' diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap index 3d0d23f77c..d8eb689ffe 100644 --- a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap @@ -6,7 +6,7 @@ Object { "id": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "tags": Object { "connectionId": "0b6de73d-b376-430f-b2b4-f6e51407bb66", - "credentialId": undefined, + "credentialIds": Array [], "indyCredentialRevocationId": undefined, "indyRevocationRegistryId": undefined, "state": "done", @@ -40,26 +40,16 @@ Object { "credentials~attach": Array [ Object { "@id": "libindy-cred-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImRhdGVPZkJpcnRoIjp7InJhdyI6IjIwMjAtMDEtMDEiLCJlbmNvZGVkIjoiNDEwNjEyOTM3ODA0NjIwNzU1MTQyMDgyMjM4OTc1OTA0MDc4MDA3NDg1NDMwMTQ5OTE1ODg1MjI3MjExOTM4ODY4OTgxMTk3NTM2MjQifSwiYWdlIjp7InJhdyI6IjI1IiwiZW5jb2RlZCI6IjI1In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6Ijg5NjQzNDI5NjIzMzI5NjQ1MDM2NDU5MjU4NzU2Nzc5MDY3ODczOTc4MDg1ODc3MDM3MzU1NzMxMjk1MzA0NzY5ODAxNDM5MjI2MDI4IiwiYSI6IjUzOTQ3MDU1MTU0OTEyMTE2MzM0MzU2NTMyMjEyMTM5MjMxNjE2NTcwMzU3MjkwNzcyOTkxNjM4NTU1MjU2Mjc2NjgyODY5NDM2MTI5OTEzNzk2MzQwOTA1Nzk3Mzc2OTI5NDE0MTIwNzMwMjI5NjQzNjMwNTI2OTcyOTUxNDk1NjA5NDcwMDU0NzEzOTY1OTE5NTU3MzM3NDkwMjUyNTI1NTM5NzI4MjA0NTI2NTU0MDcyMDYyOTcxMTA0MTM3NTQ4NzEzMzIzNTUzMTYwOTEzODQ0MDk0MDczOTQ3MjM2OTQyNzgyODIzNDA2NDUyNzIzODY2NjMzMzc2MjI4Mzk2ODE5ODI0MTQ2MjgyNTI4NDIyOTIzMjM1NzEzNTI5MjQ3ODkxMTQzMDE4OTM3ODQ0ODM3NzQ2MDE4MTc5Mjk5ODI5ODQ1MTI4MTgxMDUxOTE4MjE0ODU5Mzg5MjIzOTEzNjUzMjE0MjIxMTk2NDI2OTA2NDM1NDYwNDQwNTgxNDkxNTg5MzMxMzIyMDU1MDU1NjE5NDY0OTEwNTc3OTcyODAyNDM1MDY3OTMxMDczOTI5OTgyOTQ2Njg4NDg5MTIwNTQ1MjA1MzQ0MjQ1NTIzNTExNDc5NjUzNjI5ODIxNTA4NTc3MjI2MzU5MjUyMDA5MjUyNTU2NDA5NTg0MDgwNDY5NDI4NzE1NDQ0MDkyOTA4NzAxNjMwMzE3NzI5MTE3NTYwMzg2NjAyNDA4OTE3Mzg2NjM4NzQ2MDY1NjU0NzQ2OTAxOTA1NjA4MjE5MzgzNzczNjg3NDcxODI3NjE2OTU5MDk3NDU4IiwiZSI6IjI1OTM0NDcyMzA1NTA2MjA1OTkwNzAyNTQ5MTQ4MDY5NzU3MTkzODI3Nzg4OTUxNTE1MjMwNjI0OTcyODU4MzEwNTY2NTgwMDcxMzMwNjc1OTE0OTk4MTY5MDU1OTE5Mzk4NzE0MzAxMjM2NzkxMzIwNjI5OTMyMzg5OTY5Njk0MjIxMzIzNTk1Njc0MjkzMDMwMzU1ODY2NDUxMDY0MDQ4Mzk4OTcyMjU0Nzk2ODY2NDA2OSIsInYiOiI4NzE4MTIwMTY2NjA3NTg4MjIxMDczMTE0NTgxNTcxMDA5NTEwNDUwMzkyNDk0MDM1MTg3MDk5MTQyNzA5NDcyMjYxMDAyMjg5MzI5MzcyMzk1MDUwMzgzOTA3ODUxODc1MjIxODU0NjI4ODc4OTcxNzQ0Njg3MDgxMDM4Mzk2Njc2MzgyMTI0NjEyNDY0NjQxMDQ4NDMxNDkwMTYzMDgwOTU0NTc3MjA3OTkxNjg3NzU0NjQ4MTYwNzM5MjQ2ODUyMjMxMjU2NTk0MTg4MTAxNDU2MjgzNjc3MTA4NTMwNjY1NDQwNTUwMDY0MjgxODI3NzUxNjA3NjM1ODE2MjczNDU3MTc4MzAyNjEyOTI5ODMyNDMzNDc3ODk0ODMzMDc2MDA5OTE4MTc1MzI4OTUzNjg1MjEwOTQ1ODg0MTQyMDg0Nzk4ODMzNzM2OTExNDcwMTkwOTYwMjI3MzAyMzI2NjQ2NjE2NjUwMjY4OTU3NDcyMTI3MTA2Mjk3NzQ0NDg3MDUzODY2NjI0NTk4Njg1MzA0MzA0OTMzNjAxNDczNjY4Njg1NDg5MzYyMzQ2NzE5ODA4MjgxNzYxNjc0ODQyMzE5NTY5Mzk1Nzk1NTQ5OTA4MjAyODI0MjgyOTc1MTI3MDA0MzM0NTkwNTYzMTI3NjU3NDM3MjQ2MDQ3OTUyOTk0OTIyODQ3MzcxMzY4NDM0OTE3MDM1Njc4ODA2NjM1OTQ0ODY4Njc5MDY1NDc5Njk1MDU5NzkyNzUyMzk5NDcwMzUzMDI3MjEyNTg2OTc1Mjk5MTk1NDcwNjY0NDMzMDIyNTQyODg4MzI4OTA0Mjg2NTIxMzM5Nzc2OTkxMDYzNzA1MTI2NjA4MDY4OTA0Mzg2MDc4NzA5NTE3NjU0OTE3MzI0NjExMzkzNTM4MDkyNTQ3NzQ0OTM2NTM1NDkwODcwNDU4NjQ3NjY2OTU3MjA5MDk4MDU2NzIwMjAzOTAxMjI3MjU2NDM5NTkwNTA0MzIwOTI3NTc1ODA2NjE0NzA4NTU3MjAxMTAxODczODc4NDg1NjM4MzQ2Nzk1NjE4NDQxOTQxMjQyODc5ODMyNjQ5ODEyIn0sInJfY3JlZGVudGlhbCI6bnVsbH0sInNpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZiI6eyJzZSI6IjIxOTkzMzcwNTI0MjIxNTM0MTM0MTc4MDM3MDIyMDEyMzE4NjQ2MDE4MTk0MDIwMjgxNzY4NTQ4OTUyNjM5ODg4MDE2NDQ1NTM2NTQ2MzczMzkwMDU5NjY1Nzg4OTc2MDE0OTUzMTU2MjA1MTA0ODU1NTM1NjEyODY5Mjg5NjgyOTQ1ODI4MDQxOTMxMzc1ODY4NjE4OTE0NjUwNTc5ODM2NzI0NDE0MDMxMjU3MjU2MzkxNjg2OTQ0NjQyMzg3NTIwNjExNDQ5ODM1NTgxNDMzMDMzOTQ4MTA4OTE0MzI2NzkyNDU5MjQ0Mzc0MTgyOTQ4MDQxODIzMTg3NjY3MjE2MDI5OTEwMTAzMTM1MjE4NjY5ODc5MDk5ODA0NTA0NjI1NTAzNDM2NTAxOTk5ODkwODIyNjcyMjYwNzc1NDIzMzIxNTQ0MDk0Mjk2NDI0Nzc2Njg2MDI1MzU2MjMwOTY0OTQyMzc3NTY0NDUwMDk4NTgyMzg2Nzg0ODc3OTQwNTI2ODg0MzgzOTcxOTE4OTE3ODIyOTkzMDIzMDU2NjU1NTg2NDI3MDAwMzQ2OTcwMTI1MjA2ODg0NTkyNzkwMDU4NTAyMzkxMzUwODIyNjg1NDYyMzY0MzE5NTMwOTI0MjM4Mzg2MjkwMDI5MTQ1ODAzNTgxODA2MDQ1MTYzNDMzNTQ2OTAwMzQzNDg0MTg2NTEyMjIzODYwODY3NTI3ODExOTkyOTQwMjMxMzgzOTM4MjI0NjEwMTk0NDc1NjczMDYyMDI3MDgwOTI5NDYzMjU0NjIxMjI1NTg3MDg0NTUyODEyMDgxMDE3IiwiYyI6Ijg2NzE3OTAzNTAxODI5MzU5NDk4MjE3NDU5NjE3NjgyNTM4NzIxNzQwMjE5MDM5MDUzNjQzNTE3NDU0Nzc2NTUzMzA3MzU1OTkxMjE5In0sInJldl9yZWciOm51bGwsIndpdG5lc3MiOm51bGx9", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, "~please_ack": Object {}, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, - "~timing": undefined, - "~transport": undefined, }, "id": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "metadata": Object { @@ -94,21 +84,12 @@ Object { "offers~attach": Array [ Object { "@id": "libindy-cred-offer-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiMTE4MTE3NTM4MDU1MjM2NjMxNjAwNjM1NyJ9", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, - "~thread": undefined, - "~timing": undefined, - "~transport": undefined, }, "requestMessage": Object { "@id": "1284ae78-f3d3-4fed-a5ff-0e2aba968c3c", @@ -116,26 +97,15 @@ Object { "requests~attach": Array [ Object { "@id": "libindy-cred-request-0", - "byte_count": undefined, "data": Object { "base64": "eyJwcm92ZXJfZGlkIjoiUVZveGd3d25WUGtBQlRMVmNtQ013TCIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiMTkwNzM1MzQyMjkwNjk4Mzk3NzgyNTUyMTM2NTA3NzAxMDA4NzYwMzcxMjg3NTQ1MzI0NDM2NDIyMTMwNzQ3MDI3NTk4NDM2NTM5Mzc0NzM0MjgxOTk4MDY5Mjg1OTg4MzAzMDE1MzAzMTYxNjExMTEzMTY2MzQxMzkyOTkzMTk0ODUxNzM5Njk4NzcxNDYzNzMzMDA4MjUxMzQ5NjM4OTkzMDE5NTk5MTY1NDUyOTk4OTA4OTY4MDE5NTUxODM2NDg1NzYxMzMxNTgxNTY0MzgxNTkxNjMwOTcyNTc5NTkyODMyNDk3MDI0NTMyMjQxNzk4MDMzMjI1NDg4NjA5NjEwNjEzNTU1NDMxODc3NDQzODk2ODUyMzA4NjIxNDA1NjI5NjA5MTg5Nzg2MjYzMTcyODU0MjA1MTI3ODMxNjc5NzM5NTkxODQyMTYwOTAyNjczMDE4Mzc0MzE5NjUyODA3Njc2MDQzNzc0ODcxMjQzMzkzNTIwNTkwODE5MDgxOTI4NzY3MjU5NDQ2OTIxNTM2MjU3MjQ2NjIxMjgzNjc2NDM1MDIwNzUwODI4NDI2NTM2MTU2ODA5NDgwMTU3MTQ0NDkxNTY2MTM0ODYzNjU4Mjg5ODIyMTE1NjI4MzMxNjMxMTQ3ODM1NzQ4MjAxMzkyNjY4NzQyMTQ5NTI1OTQ0OTc1NzY3NTYwMTQyNzQ5MTU3MTY2NzE0MDY0OTM2OTQ1MzEwMzEwMzU1NjgwNTcyNDgzNDgyNTYyMzk5Nzc0OTY5NTYwMTA1Njk2MzczMDU4MDMzODgyMTAwOTY2ODUwMTk5MjEzMzAiLCJ1ciI6bnVsbCwiaGlkZGVuX2F0dHJpYnV0ZXMiOlsibWFzdGVyX3NlY3JldCJdLCJjb21taXR0ZWRfYXR0cmlidXRlcyI6e319LCJibGluZGVkX21zX2NvcnJlY3RuZXNzX3Byb29mIjp7ImMiOiI1MDg3Mzk4NDExNzQ3Mzc5NjY5MDkyNzU5ODQ5OTEwMDI2OTYzMTk2NjExNjc5MzU5NDYwNDMxMjYyMDE4NzgyNzY4NTM2NTUzNzUwMiIsInZfZGFzaF9jYXAiOiIxODU0NzEwMDA5NDM4NTg5MTc4MzkzMjgzMDk1MzM5NDUzMDQ3OTkwOTYyMjE2NzEyNzk2ODkyMzcyMzA5NjU5NTU3MDY2MzQxMTMxOTY0Mjg5NjA3MTI5MDg4MjMxMDY0NTk5ODY4NDg4MTIzNDMwMzY5OTkxMjI1OTMxMTIyMjY4NjU5MDEwMDA0NDA1OTIzNTcyMzgyMzQzODczNjkxODg3NDQzMjQ5MTcwNTQwNDk4Nzk5MTkxOTIzMjc4NDU4MzcyMzk2NzIyMjM5NDE1NjY3ODIxMDQ4ODA0NDk1NDQ5ODQ3MjcwOTg1MDcwNzY3NjU2NDU4NDM0MTYzNTI3NDAyMzA1NTg5MTg4NzcyNDg4NzE1NjcwOTgxNTc0NzQxMTI1NzYxOTIxNTE3NzYyNzg1Nzk4MjU5MTQ0OTYzMDQyMjg0NzUwMDE5MjAwMjQ4NjgxNjkxOTE5Njg3MDA1MDA4MDUzMjYwODY5NzEyNTEwNzIwNDg5NDAwMjM0NDU3Njk2MDk4NjI0Nzk1MDUwMzQ2NzM2Mjg1MDE3MTU5Mjk1OTA0NTU1NTk0MzMxNzI4MTQ5MzgyODE5NTI2NTc3MDg3NjA0ODMwNDk3MTE0Mjc3MTkyMDU3ODk1MzYxNzI5NTE3NTgxNzg5ODEyMDY3MjcxNTU5MTMyNTI1MzEzNTc0MDEwNTM3NDMxMDY2NzUwNzAzMDgxMTQxNDIyODg5MzUxOTY0MDUyMDU0NjY0MTA0ODE0MzY1Njg3NTcxNjU5MTk3ODQxMjU5NjE3OTI4MDg4NjM0ODY1NTI2MjI4NTkyNTI2NTgwODgzMzIzNjEwNTc5NzU4ODgzMjgwNDcyNTA0OTQ0MjM2ODY5MTYyNzM3NzUwNTI0NTIyMjE5NTM4NjE4OTk2ODQzODU3MzU3MjUxODI5NTIyODgxOTA0NTAwMDU2MjU1NTMwNDgyIiwibV9jYXBzIjp7Im1hc3Rlcl9zZWNyZXQiOiIxMjM5OTQ3ODE4MDA1MTQ3MjQ5MjcyODIxNDI2OTgwNjQ2NTIwMjAwMTc0MzUwMzkzMzQ0NzU1NTU0NDAxNjg1NzQ2NzExMzkzMjEzMjA3NjI3ODI5MTczMjc2OTA2MDg4MDAxMDIxMTMxMzY4NzY4MjI0ODgxNTExMjEwNjY0NzA5OTAxOTQwODA0MzA0OTc1NTUyMzExNzAyMjU3MTYwOTAxNTE0MzIxNzYyNzM0ODUwMSJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiIzNzM5ODQyNzAxNTA3ODY4NjQ0MzMxNjMifQ==", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, - "~timing": undefined, - "~transport": undefined, }, "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", @@ -146,6 +116,7 @@ Object { "tags": Object { "connectionId": "54b61a2c-59ae-4e63-a441-7f1286350132", "credentialId": "a77114e1-c812-4bff-a53c-3d5003fcc278", + "credentialIds": Array [], "indyCredentialRevocationId": undefined, "indyRevocationRegistryId": undefined, "state": "done", @@ -180,26 +151,16 @@ Object { "credentials~attach": Array [ Object { "@id": "libindy-cred-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImRhdGVPZkJpcnRoIjp7InJhdyI6IjIwMjAtMDEtMDEiLCJlbmNvZGVkIjoiNDEwNjEyOTM3ODA0NjIwNzU1MTQyMDgyMjM4OTc1OTA0MDc4MDA3NDg1NDMwMTQ5OTE1ODg1MjI3MjExOTM4ODY4OTgxMTk3NTM2MjQifSwiYWdlIjp7InJhdyI6IjI1IiwiZW5jb2RlZCI6IjI1In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6Ijg5NjQzNDI5NjIzMzI5NjQ1MDM2NDU5MjU4NzU2Nzc5MDY3ODczOTc4MDg1ODc3MDM3MzU1NzMxMjk1MzA0NzY5ODAxNDM5MjI2MDI4IiwiYSI6IjUzOTQ3MDU1MTU0OTEyMTE2MzM0MzU2NTMyMjEyMTM5MjMxNjE2NTcwMzU3MjkwNzcyOTkxNjM4NTU1MjU2Mjc2NjgyODY5NDM2MTI5OTEzNzk2MzQwOTA1Nzk3Mzc2OTI5NDE0MTIwNzMwMjI5NjQzNjMwNTI2OTcyOTUxNDk1NjA5NDcwMDU0NzEzOTY1OTE5NTU3MzM3NDkwMjUyNTI1NTM5NzI4MjA0NTI2NTU0MDcyMDYyOTcxMTA0MTM3NTQ4NzEzMzIzNTUzMTYwOTEzODQ0MDk0MDczOTQ3MjM2OTQyNzgyODIzNDA2NDUyNzIzODY2NjMzMzc2MjI4Mzk2ODE5ODI0MTQ2MjgyNTI4NDIyOTIzMjM1NzEzNTI5MjQ3ODkxMTQzMDE4OTM3ODQ0ODM3NzQ2MDE4MTc5Mjk5ODI5ODQ1MTI4MTgxMDUxOTE4MjE0ODU5Mzg5MjIzOTEzNjUzMjE0MjIxMTk2NDI2OTA2NDM1NDYwNDQwNTgxNDkxNTg5MzMxMzIyMDU1MDU1NjE5NDY0OTEwNTc3OTcyODAyNDM1MDY3OTMxMDczOTI5OTgyOTQ2Njg4NDg5MTIwNTQ1MjA1MzQ0MjQ1NTIzNTExNDc5NjUzNjI5ODIxNTA4NTc3MjI2MzU5MjUyMDA5MjUyNTU2NDA5NTg0MDgwNDY5NDI4NzE1NDQ0MDkyOTA4NzAxNjMwMzE3NzI5MTE3NTYwMzg2NjAyNDA4OTE3Mzg2NjM4NzQ2MDY1NjU0NzQ2OTAxOTA1NjA4MjE5MzgzNzczNjg3NDcxODI3NjE2OTU5MDk3NDU4IiwiZSI6IjI1OTM0NDcyMzA1NTA2MjA1OTkwNzAyNTQ5MTQ4MDY5NzU3MTkzODI3Nzg4OTUxNTE1MjMwNjI0OTcyODU4MzEwNTY2NTgwMDcxMzMwNjc1OTE0OTk4MTY5MDU1OTE5Mzk4NzE0MzAxMjM2NzkxMzIwNjI5OTMyMzg5OTY5Njk0MjIxMzIzNTk1Njc0MjkzMDMwMzU1ODY2NDUxMDY0MDQ4Mzk4OTcyMjU0Nzk2ODY2NDA2OSIsInYiOiI4NzE4MTIwMTY2NjA3NTg4MjIxMDczMTE0NTgxNTcxMDA5NTEwNDUwMzkyNDk0MDM1MTg3MDk5MTQyNzA5NDcyMjYxMDAyMjg5MzI5MzcyMzk1MDUwMzgzOTA3ODUxODc1MjIxODU0NjI4ODc4OTcxNzQ0Njg3MDgxMDM4Mzk2Njc2MzgyMTI0NjEyNDY0NjQxMDQ4NDMxNDkwMTYzMDgwOTU0NTc3MjA3OTkxNjg3NzU0NjQ4MTYwNzM5MjQ2ODUyMjMxMjU2NTk0MTg4MTAxNDU2MjgzNjc3MTA4NTMwNjY1NDQwNTUwMDY0MjgxODI3NzUxNjA3NjM1ODE2MjczNDU3MTc4MzAyNjEyOTI5ODMyNDMzNDc3ODk0ODMzMDc2MDA5OTE4MTc1MzI4OTUzNjg1MjEwOTQ1ODg0MTQyMDg0Nzk4ODMzNzM2OTExNDcwMTkwOTYwMjI3MzAyMzI2NjQ2NjE2NjUwMjY4OTU3NDcyMTI3MTA2Mjk3NzQ0NDg3MDUzODY2NjI0NTk4Njg1MzA0MzA0OTMzNjAxNDczNjY4Njg1NDg5MzYyMzQ2NzE5ODA4MjgxNzYxNjc0ODQyMzE5NTY5Mzk1Nzk1NTQ5OTA4MjAyODI0MjgyOTc1MTI3MDA0MzM0NTkwNTYzMTI3NjU3NDM3MjQ2MDQ3OTUyOTk0OTIyODQ3MzcxMzY4NDM0OTE3MDM1Njc4ODA2NjM1OTQ0ODY4Njc5MDY1NDc5Njk1MDU5NzkyNzUyMzk5NDcwMzUzMDI3MjEyNTg2OTc1Mjk5MTk1NDcwNjY0NDMzMDIyNTQyODg4MzI4OTA0Mjg2NTIxMzM5Nzc2OTkxMDYzNzA1MTI2NjA4MDY4OTA0Mzg2MDc4NzA5NTE3NjU0OTE3MzI0NjExMzkzNTM4MDkyNTQ3NzQ0OTM2NTM1NDkwODcwNDU4NjQ3NjY2OTU3MjA5MDk4MDU2NzIwMjAzOTAxMjI3MjU2NDM5NTkwNTA0MzIwOTI3NTc1ODA2NjE0NzA4NTU3MjAxMTAxODczODc4NDg1NjM4MzQ2Nzk1NjE4NDQxOTQxMjQyODc5ODMyNjQ5ODEyIn0sInJfY3JlZGVudGlhbCI6bnVsbH0sInNpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZiI6eyJzZSI6IjIxOTkzMzcwNTI0MjIxNTM0MTM0MTc4MDM3MDIyMDEyMzE4NjQ2MDE4MTk0MDIwMjgxNzY4NTQ4OTUyNjM5ODg4MDE2NDQ1NTM2NTQ2MzczMzkwMDU5NjY1Nzg4OTc2MDE0OTUzMTU2MjA1MTA0ODU1NTM1NjEyODY5Mjg5NjgyOTQ1ODI4MDQxOTMxMzc1ODY4NjE4OTE0NjUwNTc5ODM2NzI0NDE0MDMxMjU3MjU2MzkxNjg2OTQ0NjQyMzg3NTIwNjExNDQ5ODM1NTgxNDMzMDMzOTQ4MTA4OTE0MzI2NzkyNDU5MjQ0Mzc0MTgyOTQ4MDQxODIzMTg3NjY3MjE2MDI5OTEwMTAzMTM1MjE4NjY5ODc5MDk5ODA0NTA0NjI1NTAzNDM2NTAxOTk5ODkwODIyNjcyMjYwNzc1NDIzMzIxNTQ0MDk0Mjk2NDI0Nzc2Njg2MDI1MzU2MjMwOTY0OTQyMzc3NTY0NDUwMDk4NTgyMzg2Nzg0ODc3OTQwNTI2ODg0MzgzOTcxOTE4OTE3ODIyOTkzMDIzMDU2NjU1NTg2NDI3MDAwMzQ2OTcwMTI1MjA2ODg0NTkyNzkwMDU4NTAyMzkxMzUwODIyNjg1NDYyMzY0MzE5NTMwOTI0MjM4Mzg2MjkwMDI5MTQ1ODAzNTgxODA2MDQ1MTYzNDMzNTQ2OTAwMzQzNDg0MTg2NTEyMjIzODYwODY3NTI3ODExOTkyOTQwMjMxMzgzOTM4MjI0NjEwMTk0NDc1NjczMDYyMDI3MDgwOTI5NDYzMjU0NjIxMjI1NTg3MDg0NTUyODEyMDgxMDE3IiwiYyI6Ijg2NzE3OTAzNTAxODI5MzU5NDk4MjE3NDU5NjE3NjgyNTM4NzIxNzQwMjE5MDM5MDUzNjQzNTE3NDU0Nzc2NTUzMzA3MzU1OTkxMjE5In0sInJldl9yZWciOm51bGwsIndpdG5lc3MiOm51bGx9", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, "~please_ack": Object {}, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, - "~timing": undefined, - "~transport": undefined, }, "id": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", "metadata": Object { @@ -242,21 +203,12 @@ Object { "offers~attach": Array [ Object { "@id": "libindy-cred-offer-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiMTE4MTE3NTM4MDU1MjM2NjMxNjAwNjM1NyJ9", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, - "~thread": undefined, - "~timing": undefined, - "~transport": undefined, }, "requestMessage": Object { "@id": "1284ae78-f3d3-4fed-a5ff-0e2aba968c3c", @@ -264,26 +216,15 @@ Object { "requests~attach": Array [ Object { "@id": "libindy-cred-request-0", - "byte_count": undefined, "data": Object { "base64": "eyJwcm92ZXJfZGlkIjoiUVZveGd3d25WUGtBQlRMVmNtQ013TCIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiMTkwNzM1MzQyMjkwNjk4Mzk3NzgyNTUyMTM2NTA3NzAxMDA4NzYwMzcxMjg3NTQ1MzI0NDM2NDIyMTMwNzQ3MDI3NTk4NDM2NTM5Mzc0NzM0MjgxOTk4MDY5Mjg1OTg4MzAzMDE1MzAzMTYxNjExMTEzMTY2MzQxMzkyOTkzMTk0ODUxNzM5Njk4NzcxNDYzNzMzMDA4MjUxMzQ5NjM4OTkzMDE5NTk5MTY1NDUyOTk4OTA4OTY4MDE5NTUxODM2NDg1NzYxMzMxNTgxNTY0MzgxNTkxNjMwOTcyNTc5NTkyODMyNDk3MDI0NTMyMjQxNzk4MDMzMjI1NDg4NjA5NjEwNjEzNTU1NDMxODc3NDQzODk2ODUyMzA4NjIxNDA1NjI5NjA5MTg5Nzg2MjYzMTcyODU0MjA1MTI3ODMxNjc5NzM5NTkxODQyMTYwOTAyNjczMDE4Mzc0MzE5NjUyODA3Njc2MDQzNzc0ODcxMjQzMzkzNTIwNTkwODE5MDgxOTI4NzY3MjU5NDQ2OTIxNTM2MjU3MjQ2NjIxMjgzNjc2NDM1MDIwNzUwODI4NDI2NTM2MTU2ODA5NDgwMTU3MTQ0NDkxNTY2MTM0ODYzNjU4Mjg5ODIyMTE1NjI4MzMxNjMxMTQ3ODM1NzQ4MjAxMzkyNjY4NzQyMTQ5NTI1OTQ0OTc1NzY3NTYwMTQyNzQ5MTU3MTY2NzE0MDY0OTM2OTQ1MzEwMzEwMzU1NjgwNTcyNDgzNDgyNTYyMzk5Nzc0OTY5NTYwMTA1Njk2MzczMDU4MDMzODgyMTAwOTY2ODUwMTk5MjEzMzAiLCJ1ciI6bnVsbCwiaGlkZGVuX2F0dHJpYnV0ZXMiOlsibWFzdGVyX3NlY3JldCJdLCJjb21taXR0ZWRfYXR0cmlidXRlcyI6e319LCJibGluZGVkX21zX2NvcnJlY3RuZXNzX3Byb29mIjp7ImMiOiI1MDg3Mzk4NDExNzQ3Mzc5NjY5MDkyNzU5ODQ5OTEwMDI2OTYzMTk2NjExNjc5MzU5NDYwNDMxMjYyMDE4NzgyNzY4NTM2NTUzNzUwMiIsInZfZGFzaF9jYXAiOiIxODU0NzEwMDA5NDM4NTg5MTc4MzkzMjgzMDk1MzM5NDUzMDQ3OTkwOTYyMjE2NzEyNzk2ODkyMzcyMzA5NjU5NTU3MDY2MzQxMTMxOTY0Mjg5NjA3MTI5MDg4MjMxMDY0NTk5ODY4NDg4MTIzNDMwMzY5OTkxMjI1OTMxMTIyMjY4NjU5MDEwMDA0NDA1OTIzNTcyMzgyMzQzODczNjkxODg3NDQzMjQ5MTcwNTQwNDk4Nzk5MTkxOTIzMjc4NDU4MzcyMzk2NzIyMjM5NDE1NjY3ODIxMDQ4ODA0NDk1NDQ5ODQ3MjcwOTg1MDcwNzY3NjU2NDU4NDM0MTYzNTI3NDAyMzA1NTg5MTg4NzcyNDg4NzE1NjcwOTgxNTc0NzQxMTI1NzYxOTIxNTE3NzYyNzg1Nzk4MjU5MTQ0OTYzMDQyMjg0NzUwMDE5MjAwMjQ4NjgxNjkxOTE5Njg3MDA1MDA4MDUzMjYwODY5NzEyNTEwNzIwNDg5NDAwMjM0NDU3Njk2MDk4NjI0Nzk1MDUwMzQ2NzM2Mjg1MDE3MTU5Mjk1OTA0NTU1NTk0MzMxNzI4MTQ5MzgyODE5NTI2NTc3MDg3NjA0ODMwNDk3MTE0Mjc3MTkyMDU3ODk1MzYxNzI5NTE3NTgxNzg5ODEyMDY3MjcxNTU5MTMyNTI1MzEzNTc0MDEwNTM3NDMxMDY2NzUwNzAzMDgxMTQxNDIyODg5MzUxOTY0MDUyMDU0NjY0MTA0ODE0MzY1Njg3NTcxNjU5MTk3ODQxMjU5NjE3OTI4MDg4NjM0ODY1NTI2MjI4NTkyNTI2NTgwODgzMzIzNjEwNTc5NzU4ODgzMjgwNDcyNTA0OTQ0MjM2ODY5MTYyNzM3NzUwNTI0NTIyMjE5NTM4NjE4OTk2ODQzODU3MzU3MjUxODI5NTIyODgxOTA0NTAwMDU2MjU1NTMwNDgyIiwibV9jYXBzIjp7Im1hc3Rlcl9zZWNyZXQiOiIxMjM5OTQ3ODE4MDA1MTQ3MjQ5MjcyODIxNDI2OTgwNjQ2NTIwMjAwMTc0MzUwMzkzMzQ0NzU1NTU0NDAxNjg1NzQ2NzExMzkzMjEzMjA3NjI3ODI5MTczMjc2OTA2MDg4MDAxMDIxMTMxMzY4NzY4MjI0ODgxNTExMjEwNjY0NzA5OTAxOTQwODA0MzA0OTc1NTUyMzExNzAyMjU3MTYwOTAxNTE0MzIxNzYyNzM0ODUwMSJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiIzNzM5ODQyNzAxNTA3ODY4NjQ0MzMxNjMifQ==", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, - "~timing": undefined, - "~transport": undefined, }, "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", @@ -304,7 +245,7 @@ Object { "id": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "tags": Object { "connectionId": "cd66cbf1-5721-449e-8724-f4d8dcef1bc4", - "credentialId": undefined, + "credentialIds": Array [], "indyCredentialRevocationId": undefined, "indyRevocationRegistryId": undefined, "state": "done", @@ -338,26 +279,16 @@ Object { "credentials~attach": Array [ Object { "@id": "libindy-cred-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFnZSI6eyJyYXciOiIyNSIsImVuY29kZWQiOiIyNSJ9LCJkYXRlT2ZCaXJ0aCI6eyJyYXciOiIyMDIwLTAxLTAxIiwiZW5jb2RlZCI6IjQxMDYxMjkzNzgwNDYyMDc1NTE0MjA4MjIzODk3NTkwNDA3ODAwNzQ4NTQzMDE0OTkxNTg4NTIyNzIxMTkzODg2ODk4MTE5NzUzNjI0In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6IjEwMzE4MTcwNjEzMDMzODg1ODkxNDkzMzk2MjU5NDYyNDIxMzM3MjY3ODcyNzkyNzczNjgwMDQwMTgyNTE4NDM1MzEzMDYyNjE3MTcxMCIsImEiOiI3MTM3NDExNDU3NjI3MDE5MDcyNjY0ODMzNTQ5MjAzMDQyMTg0MDQ0OTUxNTU5MzYwMDczMzk1MjM4NjkwMzkwNjgxNzA3ODAyNzY0MTE2MzQ4NjE4NjgxOTgzNTM2MTczNTE3MzgxODc5NzQ0MzQyODIzMDkzNzc4MTk1NzYxNzgzMjQ3NDcxOTQ2NDgwODc0OTY2OTQyNjY0NzU4NjIwNzEyNzExODExNTY5NTc1NzMwNTg4NTQwNDI1MjEzNjY0OTg0OTQyOTIzODU5NzQ3MjIzNjA5ODQ1NjIwMjE5NTY1NzYxODY2OTMwMzI3OTYzNTIwNzE5MTg2NjMyNTQ4NzkzNDI3MTQ0NTY0NTAxNjE1NTg1MTI2NzkxNzM5Njg3ODc2MzIxMTQ1NTAzNDU1OTM0MzUxODc0MjQ3NjA0NzAwODYxMTgxNjY4NjUxNzQ5NTExMjY0MzExMDU2NjI5MjM4NjQwNDY2NzM4ODA0NjI0NzU1MzEzODgxMjQzMjkwNDM5ODI4MDE0NzY0MTQ3NDM1MzE3NTY3MjY3MzQ2NTQxMTY2ODIwNTI2MDkyMDAyMDE0NzY5NjE1MzI3Mjg4MTYwMzM0MDg1MTQ1MzQ0Mzc5MzAxMDg1NDc1MDEyNzUzNDIzNzkxMjI4NzM5NDE3MzA0OTM2NDUzNDEyMDYxNjY0MTUzNTM4MjM5MDA0OTUxMDgyODQxMTE1MTIyNDQ1MzkzNzc5Mzc5NDI5NjQzMjMxNDE2NDQzNDM4NDQ1NzI1NTAzMDc0MTM5MzEwOTc2MjkwMTQ2MTIwMDI2MDI1NTczNzIyNTUyOCIsImUiOiIyNTkzNDQ3MjMwNTUwNjIwNTk5MDcwMjU0OTE0ODA2OTc1NzE5MzgyNzc4ODk1MTUxNTIzMDYyNDk3Mjg1ODMxMDU2NjU4MDA3MTMzMDY3NTkxNDk5ODE2OTA1NTkxOTM5ODcxNDMwMTIzNjc5MTMyMDYyOTkzMjM4OTk2OTY5NDIyMTMyMzU5NTY3NDI5MzAxNTQ3NTU5NjM2MjQ0MTcyOTU5NjM1MjY1ODc1MTkyNjIwNTEiLCJ2IjoiODMxMTU0NzI2ODU4Mzg3ODc5NTU5NDUzNzcwNjg3MzIyMzE1NTgyMjYzMTk3NDUzMjMxNTI1ODIzNDIzNjkxMDk5Mzc2NTExNzI0ODAxMzA1MTU0NzY2Mjc0NjQ1OTMzMTAwOTIzMjIxOTgwNDkyNzE0NDQxMTY0NTYxNTIwMDIzMjYzMDYzMzQ4NjQ4NzkzMjkxMzEwNDc0NTU5NDIwMTkzMTA1MjE5NzMxNTAwMTc0ODg0NzQ0MDk1MjU3MDYyMjczODA2OTYzNjg5MjY3NzA1NTg4NTQ4MzU4NzQ2NDc1Mzc1MzAzMjI1OTI3MDkxMzA0NzQ2NTg5MzA1MDEzNjc1ODk0MzIxMDkzNjE0NzIxMjQwNDAzNDE5OTM5OTk0Mjg5NTU2MzY0MDExMTg2ODQ2NjMxNTA2OTU0NDg0NjM4NjgwMzEyMDA2Njk0MjcwOTkwNDU3NTk3NjAyNzc5MjUzMDc3MTg3NDgwNTg5NDMyNzU4ODgwMjY3NzA1NzMyMjg3Nzc0ODczOTI3MDExMTQ1MDE1NzgyNjE5NzI4NTAxNjI5MTE4ODE1ODM2NjU2OTMzMzcwMzgwNDk4NDk5MDE0MDEzNDI1NDMwMjMwODQzODc0OTk3NTg0NTY3NTA0Mzg3OTE4MjQxMzMxNTM1NDk5MTQxMjU1NzQzNjQ0MzgwNTQ4ODAxNDUwNDEyMzQzMTAxODc4Nzg3NzIzOTIxMDU5NjQyNDg2MjE3NjE0MzQyMDc0MTQ3ODk1ODA2MzQ1NTQ0Njk3NjI2MzcwMDY1MjYxMjQ3OTM2NzMwMzMyNTkyMjM3NDY1MjIyNTQ1MjE4ODc4MDk0ODk0NTE0ODU2ODUyNTU1NjI4MjIwNDg2MTU5MjcyMjIxMjM3MDkwMjc4NjUyNDM2MzcyNTE4MDgzOTUxNjAwNzI1ODA1MDYwNjExMTkyNzEyOTI3MjQ2MTUxMDU4NzU5NDk2MzQ3NjA0NDQwNDcxODcwODEyNzMwODE3MTU4NzQxNDQ1MDA0OTg2MTgyNDk5NDA4OTQxMzk2NjcwMzEyOTU0NDQ1MTk1NTM5MTM5MzIwMDI4MTI3NzMyMDQ0MSJ9LCJyX2NyZWRlbnRpYWwiOm51bGx9LCJzaWduYXR1cmVfY29ycmVjdG5lc3NfcHJvb2YiOnsic2UiOiIyOTQ0ODU2NTEyNDk3MjM2MTc2OTQ3OTMxNzM1Mjk0NDc4MTU5NTE2ODM4OTExNDEyMDE3MTI1NTAyNjc1ODM2Nzk1NTQ4OTczNDMyMTg2NjI0MTc2NjQwNzAxNjczOTk4MjI3NTEzMTA0OTU5OTgzMjk1NjI0MTA0MjkwNjgzMjU0OTI4NTg1MDkwMTI2Mjk5ODczMjY4NDYyODA5NTY4NjMxNDg3MDk2ODgzMDE0OTcwMTcyMzM0MzIwMTM4OTg5ODkzMzcyODIyODU2MTQxMTM5NTQ0MzQyMzExNDEzNDgyMDU0OTYyNjE5NTgzNjU5NjMwMzYxNTc3Nzg0MTQ4NjY3NTgwNjMxODc4NDIwNTgzMDk5OTM1NzE0NDYyMzE5Njg4NDE5MTM4NjY3Nzk2Nzc2MzQwMDk2MDIxMTgwMTU0ODU0Njg1MDEzNzY1MTM0ODMwNjA0MzEyMjI0MjIwNzA5MzQwODExMTI4MDczMDk3NjEyMDA0MTE4NjA0MDI3MTczNjExNTE2ODA0NTQxMzUzODA2OTkxMTg0MDY3MzY1MDE5Nzk2NDM2MDA3NDI0NTM5NzQ4ODQxMjU4NjA1MTUxNTQxNzQzMDk4MTE5NzI1Mzc4MTQ4MTgyNDQ2Njg3NDQ0MjE0ODk1NTE2NDc3MTM4Mjk1OTI1Mzc4Nzg1MTA3OTc5MTYxNTIyNTYyNDk2Njg2MTAzMjg3MDUxNDUyMTE3NTQzMzc4Mzc0MDM5Mjg3NzgxMjAwNzgxMTkyNDQyOTY5MDU1NjIwNTcyODg1NDM5NjU2MTU3Njk2Mzc5MTAyNDc2MTQ2IiwiYyI6IjI4NjYxMjE3ODQ5OTg3ODI4MTA2Nzk5MTAxOTIxNDcyNzgxNDMzNzgzMDE5Mzg0NzAwMDUzMjU4MTU5NDUxNjc5MjMwODI0NzA5NjIifSwicmV2X3JlZyI6bnVsbCwid2l0bmVzcyI6bnVsbH0=", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, "~please_ack": Object {}, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, - "~timing": undefined, - "~transport": undefined, }, "id": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "metadata": Object { @@ -392,21 +323,12 @@ Object { "offers~attach": Array [ Object { "@id": "libindy-cred-offer-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiNTQzODYyOTczNzUxMjk1OTk2MTAzNjA3In0=", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, - "~thread": undefined, - "~timing": undefined, - "~transport": undefined, }, "requestMessage": Object { "@id": "edba1c87-51d3-4c70-aff2-ab8016e1060e", @@ -414,26 +336,15 @@ Object { "requests~attach": Array [ Object { "@id": "libindy-cred-request-0", - "byte_count": undefined, "data": Object { "base64": "eyJwcm92ZXJfZGlkIjoiRUg2OTVENjRRd2hWRmtyazFtcDQ5aiIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiOTcwNjA5MzQ1NDAxNzE0NDIxNjQzNDg0NDE0MTQwOTA0NzMzNTQ4NTA4NzY0OTk5MTgxNzY1ODI3MjM3ODg3NjQ2MzQzNDYyMTY0ODA3MTUxMjg1ODk2MDczODAyNDY1MDMwMTcxNTIyODE4ODY4Mjc2ODUwMzE0NzQzMjM3ODc3NDMyMDgxMTQwMzE5ODU5OTM5NjM0MTI4NzkzOTk4NDcwMTUzNTQ0NjgxNTM5NDg4NzEyNDE5MTk3NzcxODE1NjU5Nzg1NDE5NTA1ODEyODI4NzYxOTI4MzExODczNjA5NDYwMjExMzQ4OTAyNDk4NzYxNjc5OTIzNzA2NjUwMDIzODg4ODU4NzE1MTYxMTIyMzA5MTc1MTE3MTk0NDMwNTI1ODY5NjcwMDEzMTgxNTkzNjI4NDQzMjk2MDI0MDE5NTc4MzIzODQwNzk0OTQ0MjIyODE1MTQwNTM2Mjc3ODQwMjU2ODc2MDE1MzUwNDgzOTE2MjYzMDgyODM5NzI2NzAxNjg4NjcxNDY0MjA3MzQxMTgwMjg3Mjk0Njg4NDA3NzQ3MDk1NjA0NzQ3NzA3OTc2Nzk2MjU0MTQ2NDQ0NTY5NzQ4MTk4OTMwMjkyOTkzNjY1ODk2MTUyMDMyNzQwODY3OTgwMjczMTMxMDM3NjkwNDkzNDU1Mjc4NDc3MDc3NjE0OTU2NjgzNjgxNDc5NzY3Njg0MDI4MzU5NzE4NzM0ODEyNzcyMDIyOTIwODQ3NDIyNDYyOTAwMjczMTcwMTU2NzQyMzUyMDQ2NDYyODI4NzAxMTE2MzU0MTkwMDY5MDE0NTIwMSIsInVyIjpudWxsLCJoaWRkZW5fYXR0cmlidXRlcyI6WyJtYXN0ZXJfc2VjcmV0Il0sImNvbW1pdHRlZF9hdHRyaWJ1dGVzIjp7fX0sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6IjE4MzE5MTUyNjg0NDkyMTM0OTc4MzkzNDE3OTY5NjQ5MDIyNzMzNzQzMTQ5NTUwNzAwNzc5ODk0Nzg1MTg3MTA1OTkyNjk4Mzg5MjAzIiwidl9kYXNoX2NhcCI6IjQ0NzA4MzAwOTUyNzA3MjA3NjI3NjA3NzM4MTI2NDgxNzA3OTA1MDcwNjEyMzQ5OTIxNTAxNTYyOTA2MzgyMzE0MDE4MzQ4MTAxMTE4MDI4MDIyMjk5OTgyNTEwMjI5ODM1OTQxMzY1MTM4Njg0MTU1OTEyOTE3NzYwMjgwOTIyNDk1MjE1ODA2NTM3MzA5NDU5NDA3NjcxNDgyNDA0NDMwODU4MzU5ODU3MDgzNzg1Njk1MTYzMjkwMzEyNzMzNDAxNjY1NDk5MjUwMDQ0NzkwODk4OTA4NzIzNzE1OTc0MDYwNzgyNDEzODYzMTU0MTUxNjg2OTM3ODY4MDM5MDU1Nzc1MzA5MjQ0MTYzOTUxNzgwMTgxNDk5MDM5MDgyMjcxNzgzNTgzNTkxMDIyNjYwMDYyMDQ3MDQ2NTEyMTM0NDU5OTI1MTgyOTg2MTkxOTAwNTQwMDg4MjE3Mzc2NjM4MjEzNTI0MDUxNjcxNzg3ODY0ODQ2NzIxNjk5NjQzNDk0MTI2MjA3NTg2MjgwNjQ5OTc4ODE2ODEwMjM5OTAzMzU0NzIyOTI2NTUxODYyNTQwMjc4ODU2OTEyNDQ2MDUzMTg4MzI3Mjk4NDc0NjgzMjkwMjU4MjgwNjY0OTgyOTM2NTYyODcwNTIyODA2NzYwMjE1OTI0MDc3ODQ2NjA2NTM0NzI4MjkyODQ2MDQyOTk1NjgxMTQzOTQ5MTU0MTU0NTU2NzQzMDYzMzY1OTIzMzU2OTg1MjQ5ODI1Njk2NzE3MzE2MDk1NzE5MDU2MTE5NTE0NTYwODY0MzUyMDc4ODMyOTYzNjI3Mjk0Njk1ODQ0MTE5NDA1NTMzNTY5MTI2ODExODE3NDYyNjczMzM3OTg4MzA0MDcwMzk5ODYyNTk2ODMyNDk2OTU3NzA4Nzc0NzI5NzAyOTEyNjY3Njk0OTgwMjc3OTI5MDgzNiIsIm1fY2FwcyI6eyJtYXN0ZXJfc2VjcmV0IjoiMjIxNTAyNTExNTYzODg1MTM1NzIwNjg4MzE5Njk5NzE5ODAxNDI4NDgzMTI2MjY0NjI0NTE5OTA4MjM5NTM0MjQ3MDQ3NTIwODgyNDE4Mzk0ODMzNjUwODM2NTI0NDk2MzUzMDkwNzIzOTI1MDc2NDY2ODQ3NjIxNzE4MDA4MDAxNTQyNTMzOTk4NTU1MzA1MDYwNjUzMzkwMjc4MDc2MzE2Mjg5MzcwMzA2MDcyMDYxNCJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiI2OTgzNzA2MTYwMjM4ODM3MzA0OTgzNzUifQ==", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, - "~timing": undefined, - "~transport": undefined, }, "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", @@ -444,6 +355,7 @@ Object { "tags": Object { "connectionId": "d8f23338-9e99-469a-bd57-1c9a26c0080f", "credentialId": "19c1f29f-d2df-486c-b8c6-950c403fa7d9", + "credentialIds": Array [], "indyCredentialRevocationId": undefined, "indyRevocationRegistryId": undefined, "state": "done", @@ -478,26 +390,16 @@ Object { "credentials~attach": Array [ Object { "@id": "libindy-cred-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFnZSI6eyJyYXciOiIyNSIsImVuY29kZWQiOiIyNSJ9LCJkYXRlT2ZCaXJ0aCI6eyJyYXciOiIyMDIwLTAxLTAxIiwiZW5jb2RlZCI6IjQxMDYxMjkzNzgwNDYyMDc1NTE0MjA4MjIzODk3NTkwNDA3ODAwNzQ4NTQzMDE0OTkxNTg4NTIyNzIxMTkzODg2ODk4MTE5NzUzNjI0In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6IjEwMzE4MTcwNjEzMDMzODg1ODkxNDkzMzk2MjU5NDYyNDIxMzM3MjY3ODcyNzkyNzczNjgwMDQwMTgyNTE4NDM1MzEzMDYyNjE3MTcxMCIsImEiOiI3MTM3NDExNDU3NjI3MDE5MDcyNjY0ODMzNTQ5MjAzMDQyMTg0MDQ0OTUxNTU5MzYwMDczMzk1MjM4NjkwMzkwNjgxNzA3ODAyNzY0MTE2MzQ4NjE4NjgxOTgzNTM2MTczNTE3MzgxODc5NzQ0MzQyODIzMDkzNzc4MTk1NzYxNzgzMjQ3NDcxOTQ2NDgwODc0OTY2OTQyNjY0NzU4NjIwNzEyNzExODExNTY5NTc1NzMwNTg4NTQwNDI1MjEzNjY0OTg0OTQyOTIzODU5NzQ3MjIzNjA5ODQ1NjIwMjE5NTY1NzYxODY2OTMwMzI3OTYzNTIwNzE5MTg2NjMyNTQ4NzkzNDI3MTQ0NTY0NTAxNjE1NTg1MTI2NzkxNzM5Njg3ODc2MzIxMTQ1NTAzNDU1OTM0MzUxODc0MjQ3NjA0NzAwODYxMTgxNjY4NjUxNzQ5NTExMjY0MzExMDU2NjI5MjM4NjQwNDY2NzM4ODA0NjI0NzU1MzEzODgxMjQzMjkwNDM5ODI4MDE0NzY0MTQ3NDM1MzE3NTY3MjY3MzQ2NTQxMTY2ODIwNTI2MDkyMDAyMDE0NzY5NjE1MzI3Mjg4MTYwMzM0MDg1MTQ1MzQ0Mzc5MzAxMDg1NDc1MDEyNzUzNDIzNzkxMjI4NzM5NDE3MzA0OTM2NDUzNDEyMDYxNjY0MTUzNTM4MjM5MDA0OTUxMDgyODQxMTE1MTIyNDQ1MzkzNzc5Mzc5NDI5NjQzMjMxNDE2NDQzNDM4NDQ1NzI1NTAzMDc0MTM5MzEwOTc2MjkwMTQ2MTIwMDI2MDI1NTczNzIyNTUyOCIsImUiOiIyNTkzNDQ3MjMwNTUwNjIwNTk5MDcwMjU0OTE0ODA2OTc1NzE5MzgyNzc4ODk1MTUxNTIzMDYyNDk3Mjg1ODMxMDU2NjU4MDA3MTMzMDY3NTkxNDk5ODE2OTA1NTkxOTM5ODcxNDMwMTIzNjc5MTMyMDYyOTkzMjM4OTk2OTY5NDIyMTMyMzU5NTY3NDI5MzAxNTQ3NTU5NjM2MjQ0MTcyOTU5NjM1MjY1ODc1MTkyNjIwNTEiLCJ2IjoiODMxMTU0NzI2ODU4Mzg3ODc5NTU5NDUzNzcwNjg3MzIyMzE1NTgyMjYzMTk3NDUzMjMxNTI1ODIzNDIzNjkxMDk5Mzc2NTExNzI0ODAxMzA1MTU0NzY2Mjc0NjQ1OTMzMTAwOTIzMjIxOTgwNDkyNzE0NDQxMTY0NTYxNTIwMDIzMjYzMDYzMzQ4NjQ4NzkzMjkxMzEwNDc0NTU5NDIwMTkzMTA1MjE5NzMxNTAwMTc0ODg0NzQ0MDk1MjU3MDYyMjczODA2OTYzNjg5MjY3NzA1NTg4NTQ4MzU4NzQ2NDc1Mzc1MzAzMjI1OTI3MDkxMzA0NzQ2NTg5MzA1MDEzNjc1ODk0MzIxMDkzNjE0NzIxMjQwNDAzNDE5OTM5OTk0Mjg5NTU2MzY0MDExMTg2ODQ2NjMxNTA2OTU0NDg0NjM4NjgwMzEyMDA2Njk0MjcwOTkwNDU3NTk3NjAyNzc5MjUzMDc3MTg3NDgwNTg5NDMyNzU4ODgwMjY3NzA1NzMyMjg3Nzc0ODczOTI3MDExMTQ1MDE1NzgyNjE5NzI4NTAxNjI5MTE4ODE1ODM2NjU2OTMzMzcwMzgwNDk4NDk5MDE0MDEzNDI1NDMwMjMwODQzODc0OTk3NTg0NTY3NTA0Mzg3OTE4MjQxMzMxNTM1NDk5MTQxMjU1NzQzNjQ0MzgwNTQ4ODAxNDUwNDEyMzQzMTAxODc4Nzg3NzIzOTIxMDU5NjQyNDg2MjE3NjE0MzQyMDc0MTQ3ODk1ODA2MzQ1NTQ0Njk3NjI2MzcwMDY1MjYxMjQ3OTM2NzMwMzMyNTkyMjM3NDY1MjIyNTQ1MjE4ODc4MDk0ODk0NTE0ODU2ODUyNTU1NjI4MjIwNDg2MTU5MjcyMjIxMjM3MDkwMjc4NjUyNDM2MzcyNTE4MDgzOTUxNjAwNzI1ODA1MDYwNjExMTkyNzEyOTI3MjQ2MTUxMDU4NzU5NDk2MzQ3NjA0NDQwNDcxODcwODEyNzMwODE3MTU4NzQxNDQ1MDA0OTg2MTgyNDk5NDA4OTQxMzk2NjcwMzEyOTU0NDQ1MTk1NTM5MTM5MzIwMDI4MTI3NzMyMDQ0MSJ9LCJyX2NyZWRlbnRpYWwiOm51bGx9LCJzaWduYXR1cmVfY29ycmVjdG5lc3NfcHJvb2YiOnsic2UiOiIyOTQ0ODU2NTEyNDk3MjM2MTc2OTQ3OTMxNzM1Mjk0NDc4MTU5NTE2ODM4OTExNDEyMDE3MTI1NTAyNjc1ODM2Nzk1NTQ4OTczNDMyMTg2NjI0MTc2NjQwNzAxNjczOTk4MjI3NTEzMTA0OTU5OTgzMjk1NjI0MTA0MjkwNjgzMjU0OTI4NTg1MDkwMTI2Mjk5ODczMjY4NDYyODA5NTY4NjMxNDg3MDk2ODgzMDE0OTcwMTcyMzM0MzIwMTM4OTg5ODkzMzcyODIyODU2MTQxMTM5NTQ0MzQyMzExNDEzNDgyMDU0OTYyNjE5NTgzNjU5NjMwMzYxNTc3Nzg0MTQ4NjY3NTgwNjMxODc4NDIwNTgzMDk5OTM1NzE0NDYyMzE5Njg4NDE5MTM4NjY3Nzk2Nzc2MzQwMDk2MDIxMTgwMTU0ODU0Njg1MDEzNzY1MTM0ODMwNjA0MzEyMjI0MjIwNzA5MzQwODExMTI4MDczMDk3NjEyMDA0MTE4NjA0MDI3MTczNjExNTE2ODA0NTQxMzUzODA2OTkxMTg0MDY3MzY1MDE5Nzk2NDM2MDA3NDI0NTM5NzQ4ODQxMjU4NjA1MTUxNTQxNzQzMDk4MTE5NzI1Mzc4MTQ4MTgyNDQ2Njg3NDQ0MjE0ODk1NTE2NDc3MTM4Mjk1OTI1Mzc4Nzg1MTA3OTc5MTYxNTIyNTYyNDk2Njg2MTAzMjg3MDUxNDUyMTE3NTQzMzc4Mzc0MDM5Mjg3NzgxMjAwNzgxMTkyNDQyOTY5MDU1NjIwNTcyODg1NDM5NjU2MTU3Njk2Mzc5MTAyNDc2MTQ2IiwiYyI6IjI4NjYxMjE3ODQ5OTg3ODI4MTA2Nzk5MTAxOTIxNDcyNzgxNDMzNzgzMDE5Mzg0NzAwMDUzMjU4MTU5NDUxNjc5MjMwODI0NzA5NjIifSwicmV2X3JlZyI6bnVsbCwid2l0bmVzcyI6bnVsbH0=", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, "~please_ack": Object {}, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, - "~timing": undefined, - "~transport": undefined, }, "id": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", "metadata": Object { @@ -540,21 +442,12 @@ Object { "offers~attach": Array [ Object { "@id": "libindy-cred-offer-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiNTQzODYyOTczNzUxMjk1OTk2MTAzNjA3In0=", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, - "~thread": undefined, - "~timing": undefined, - "~transport": undefined, }, "requestMessage": Object { "@id": "edba1c87-51d3-4c70-aff2-ab8016e1060e", @@ -562,26 +455,15 @@ Object { "requests~attach": Array [ Object { "@id": "libindy-cred-request-0", - "byte_count": undefined, "data": Object { "base64": "eyJwcm92ZXJfZGlkIjoiRUg2OTVENjRRd2hWRmtyazFtcDQ5aiIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiOTcwNjA5MzQ1NDAxNzE0NDIxNjQzNDg0NDE0MTQwOTA0NzMzNTQ4NTA4NzY0OTk5MTgxNzY1ODI3MjM3ODg3NjQ2MzQzNDYyMTY0ODA3MTUxMjg1ODk2MDczODAyNDY1MDMwMTcxNTIyODE4ODY4Mjc2ODUwMzE0NzQzMjM3ODc3NDMyMDgxMTQwMzE5ODU5OTM5NjM0MTI4NzkzOTk4NDcwMTUzNTQ0NjgxNTM5NDg4NzEyNDE5MTk3NzcxODE1NjU5Nzg1NDE5NTA1ODEyODI4NzYxOTI4MzExODczNjA5NDYwMjExMzQ4OTAyNDk4NzYxNjc5OTIzNzA2NjUwMDIzODg4ODU4NzE1MTYxMTIyMzA5MTc1MTE3MTk0NDMwNTI1ODY5NjcwMDEzMTgxNTkzNjI4NDQzMjk2MDI0MDE5NTc4MzIzODQwNzk0OTQ0MjIyODE1MTQwNTM2Mjc3ODQwMjU2ODc2MDE1MzUwNDgzOTE2MjYzMDgyODM5NzI2NzAxNjg4NjcxNDY0MjA3MzQxMTgwMjg3Mjk0Njg4NDA3NzQ3MDk1NjA0NzQ3NzA3OTc2Nzk2MjU0MTQ2NDQ0NTY5NzQ4MTk4OTMwMjkyOTkzNjY1ODk2MTUyMDMyNzQwODY3OTgwMjczMTMxMDM3NjkwNDkzNDU1Mjc4NDc3MDc3NjE0OTU2NjgzNjgxNDc5NzY3Njg0MDI4MzU5NzE4NzM0ODEyNzcyMDIyOTIwODQ3NDIyNDYyOTAwMjczMTcwMTU2NzQyMzUyMDQ2NDYyODI4NzAxMTE2MzU0MTkwMDY5MDE0NTIwMSIsInVyIjpudWxsLCJoaWRkZW5fYXR0cmlidXRlcyI6WyJtYXN0ZXJfc2VjcmV0Il0sImNvbW1pdHRlZF9hdHRyaWJ1dGVzIjp7fX0sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6IjE4MzE5MTUyNjg0NDkyMTM0OTc4MzkzNDE3OTY5NjQ5MDIyNzMzNzQzMTQ5NTUwNzAwNzc5ODk0Nzg1MTg3MTA1OTkyNjk4Mzg5MjAzIiwidl9kYXNoX2NhcCI6IjQ0NzA4MzAwOTUyNzA3MjA3NjI3NjA3NzM4MTI2NDgxNzA3OTA1MDcwNjEyMzQ5OTIxNTAxNTYyOTA2MzgyMzE0MDE4MzQ4MTAxMTE4MDI4MDIyMjk5OTgyNTEwMjI5ODM1OTQxMzY1MTM4Njg0MTU1OTEyOTE3NzYwMjgwOTIyNDk1MjE1ODA2NTM3MzA5NDU5NDA3NjcxNDgyNDA0NDMwODU4MzU5ODU3MDgzNzg1Njk1MTYzMjkwMzEyNzMzNDAxNjY1NDk5MjUwMDQ0NzkwODk4OTA4NzIzNzE1OTc0MDYwNzgyNDEzODYzMTU0MTUxNjg2OTM3ODY4MDM5MDU1Nzc1MzA5MjQ0MTYzOTUxNzgwMTgxNDk5MDM5MDgyMjcxNzgzNTgzNTkxMDIyNjYwMDYyMDQ3MDQ2NTEyMTM0NDU5OTI1MTgyOTg2MTkxOTAwNTQwMDg4MjE3Mzc2NjM4MjEzNTI0MDUxNjcxNzg3ODY0ODQ2NzIxNjk5NjQzNDk0MTI2MjA3NTg2MjgwNjQ5OTc4ODE2ODEwMjM5OTAzMzU0NzIyOTI2NTUxODYyNTQwMjc4ODU2OTEyNDQ2MDUzMTg4MzI3Mjk4NDc0NjgzMjkwMjU4MjgwNjY0OTgyOTM2NTYyODcwNTIyODA2NzYwMjE1OTI0MDc3ODQ2NjA2NTM0NzI4MjkyODQ2MDQyOTk1NjgxMTQzOTQ5MTU0MTU0NTU2NzQzMDYzMzY1OTIzMzU2OTg1MjQ5ODI1Njk2NzE3MzE2MDk1NzE5MDU2MTE5NTE0NTYwODY0MzUyMDc4ODMyOTYzNjI3Mjk0Njk1ODQ0MTE5NDA1NTMzNTY5MTI2ODExODE3NDYyNjczMzM3OTg4MzA0MDcwMzk5ODYyNTk2ODMyNDk2OTU3NzA4Nzc0NzI5NzAyOTEyNjY3Njk0OTgwMjc3OTI5MDgzNiIsIm1fY2FwcyI6eyJtYXN0ZXJfc2VjcmV0IjoiMjIxNTAyNTExNTYzODg1MTM1NzIwNjg4MzE5Njk5NzE5ODAxNDI4NDgzMTI2MjY0NjI0NTE5OTA4MjM5NTM0MjQ3MDQ3NTIwODgyNDE4Mzk0ODMzNjUwODM2NTI0NDk2MzUzMDkwNzIzOTI1MDc2NDY2ODQ3NjIxNzE4MDA4MDAxNTQyNTMzOTk4NTU1MzA1MDYwNjUzMzkwMjc4MDc2MzE2Mjg5MzcwMzA2MDcyMDYxNCJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiI2OTgzNzA2MTYwMjM4ODM3MzA0OTgzNzUifQ==", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, - "~timing": undefined, - "~transport": undefined, }, "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", @@ -596,7 +478,7 @@ Object { "id": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "tags": Object { "connectionId": "0b6de73d-b376-430f-b2b4-f6e51407bb66", - "credentialId": undefined, + "credentialIds": Array [], "indyCredentialRevocationId": undefined, "indyRevocationRegistryId": undefined, "state": "done", @@ -630,26 +512,16 @@ Object { "credentials~attach": Array [ Object { "@id": "libindy-cred-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImRhdGVPZkJpcnRoIjp7InJhdyI6IjIwMjAtMDEtMDEiLCJlbmNvZGVkIjoiNDEwNjEyOTM3ODA0NjIwNzU1MTQyMDgyMjM4OTc1OTA0MDc4MDA3NDg1NDMwMTQ5OTE1ODg1MjI3MjExOTM4ODY4OTgxMTk3NTM2MjQifSwiYWdlIjp7InJhdyI6IjI1IiwiZW5jb2RlZCI6IjI1In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6Ijg5NjQzNDI5NjIzMzI5NjQ1MDM2NDU5MjU4NzU2Nzc5MDY3ODczOTc4MDg1ODc3MDM3MzU1NzMxMjk1MzA0NzY5ODAxNDM5MjI2MDI4IiwiYSI6IjUzOTQ3MDU1MTU0OTEyMTE2MzM0MzU2NTMyMjEyMTM5MjMxNjE2NTcwMzU3MjkwNzcyOTkxNjM4NTU1MjU2Mjc2NjgyODY5NDM2MTI5OTEzNzk2MzQwOTA1Nzk3Mzc2OTI5NDE0MTIwNzMwMjI5NjQzNjMwNTI2OTcyOTUxNDk1NjA5NDcwMDU0NzEzOTY1OTE5NTU3MzM3NDkwMjUyNTI1NTM5NzI4MjA0NTI2NTU0MDcyMDYyOTcxMTA0MTM3NTQ4NzEzMzIzNTUzMTYwOTEzODQ0MDk0MDczOTQ3MjM2OTQyNzgyODIzNDA2NDUyNzIzODY2NjMzMzc2MjI4Mzk2ODE5ODI0MTQ2MjgyNTI4NDIyOTIzMjM1NzEzNTI5MjQ3ODkxMTQzMDE4OTM3ODQ0ODM3NzQ2MDE4MTc5Mjk5ODI5ODQ1MTI4MTgxMDUxOTE4MjE0ODU5Mzg5MjIzOTEzNjUzMjE0MjIxMTk2NDI2OTA2NDM1NDYwNDQwNTgxNDkxNTg5MzMxMzIyMDU1MDU1NjE5NDY0OTEwNTc3OTcyODAyNDM1MDY3OTMxMDczOTI5OTgyOTQ2Njg4NDg5MTIwNTQ1MjA1MzQ0MjQ1NTIzNTExNDc5NjUzNjI5ODIxNTA4NTc3MjI2MzU5MjUyMDA5MjUyNTU2NDA5NTg0MDgwNDY5NDI4NzE1NDQ0MDkyOTA4NzAxNjMwMzE3NzI5MTE3NTYwMzg2NjAyNDA4OTE3Mzg2NjM4NzQ2MDY1NjU0NzQ2OTAxOTA1NjA4MjE5MzgzNzczNjg3NDcxODI3NjE2OTU5MDk3NDU4IiwiZSI6IjI1OTM0NDcyMzA1NTA2MjA1OTkwNzAyNTQ5MTQ4MDY5NzU3MTkzODI3Nzg4OTUxNTE1MjMwNjI0OTcyODU4MzEwNTY2NTgwMDcxMzMwNjc1OTE0OTk4MTY5MDU1OTE5Mzk4NzE0MzAxMjM2NzkxMzIwNjI5OTMyMzg5OTY5Njk0MjIxMzIzNTk1Njc0MjkzMDMwMzU1ODY2NDUxMDY0MDQ4Mzk4OTcyMjU0Nzk2ODY2NDA2OSIsInYiOiI4NzE4MTIwMTY2NjA3NTg4MjIxMDczMTE0NTgxNTcxMDA5NTEwNDUwMzkyNDk0MDM1MTg3MDk5MTQyNzA5NDcyMjYxMDAyMjg5MzI5MzcyMzk1MDUwMzgzOTA3ODUxODc1MjIxODU0NjI4ODc4OTcxNzQ0Njg3MDgxMDM4Mzk2Njc2MzgyMTI0NjEyNDY0NjQxMDQ4NDMxNDkwMTYzMDgwOTU0NTc3MjA3OTkxNjg3NzU0NjQ4MTYwNzM5MjQ2ODUyMjMxMjU2NTk0MTg4MTAxNDU2MjgzNjc3MTA4NTMwNjY1NDQwNTUwMDY0MjgxODI3NzUxNjA3NjM1ODE2MjczNDU3MTc4MzAyNjEyOTI5ODMyNDMzNDc3ODk0ODMzMDc2MDA5OTE4MTc1MzI4OTUzNjg1MjEwOTQ1ODg0MTQyMDg0Nzk4ODMzNzM2OTExNDcwMTkwOTYwMjI3MzAyMzI2NjQ2NjE2NjUwMjY4OTU3NDcyMTI3MTA2Mjk3NzQ0NDg3MDUzODY2NjI0NTk4Njg1MzA0MzA0OTMzNjAxNDczNjY4Njg1NDg5MzYyMzQ2NzE5ODA4MjgxNzYxNjc0ODQyMzE5NTY5Mzk1Nzk1NTQ5OTA4MjAyODI0MjgyOTc1MTI3MDA0MzM0NTkwNTYzMTI3NjU3NDM3MjQ2MDQ3OTUyOTk0OTIyODQ3MzcxMzY4NDM0OTE3MDM1Njc4ODA2NjM1OTQ0ODY4Njc5MDY1NDc5Njk1MDU5NzkyNzUyMzk5NDcwMzUzMDI3MjEyNTg2OTc1Mjk5MTk1NDcwNjY0NDMzMDIyNTQyODg4MzI4OTA0Mjg2NTIxMzM5Nzc2OTkxMDYzNzA1MTI2NjA4MDY4OTA0Mzg2MDc4NzA5NTE3NjU0OTE3MzI0NjExMzkzNTM4MDkyNTQ3NzQ0OTM2NTM1NDkwODcwNDU4NjQ3NjY2OTU3MjA5MDk4MDU2NzIwMjAzOTAxMjI3MjU2NDM5NTkwNTA0MzIwOTI3NTc1ODA2NjE0NzA4NTU3MjAxMTAxODczODc4NDg1NjM4MzQ2Nzk1NjE4NDQxOTQxMjQyODc5ODMyNjQ5ODEyIn0sInJfY3JlZGVudGlhbCI6bnVsbH0sInNpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZiI6eyJzZSI6IjIxOTkzMzcwNTI0MjIxNTM0MTM0MTc4MDM3MDIyMDEyMzE4NjQ2MDE4MTk0MDIwMjgxNzY4NTQ4OTUyNjM5ODg4MDE2NDQ1NTM2NTQ2MzczMzkwMDU5NjY1Nzg4OTc2MDE0OTUzMTU2MjA1MTA0ODU1NTM1NjEyODY5Mjg5NjgyOTQ1ODI4MDQxOTMxMzc1ODY4NjE4OTE0NjUwNTc5ODM2NzI0NDE0MDMxMjU3MjU2MzkxNjg2OTQ0NjQyMzg3NTIwNjExNDQ5ODM1NTgxNDMzMDMzOTQ4MTA4OTE0MzI2NzkyNDU5MjQ0Mzc0MTgyOTQ4MDQxODIzMTg3NjY3MjE2MDI5OTEwMTAzMTM1MjE4NjY5ODc5MDk5ODA0NTA0NjI1NTAzNDM2NTAxOTk5ODkwODIyNjcyMjYwNzc1NDIzMzIxNTQ0MDk0Mjk2NDI0Nzc2Njg2MDI1MzU2MjMwOTY0OTQyMzc3NTY0NDUwMDk4NTgyMzg2Nzg0ODc3OTQwNTI2ODg0MzgzOTcxOTE4OTE3ODIyOTkzMDIzMDU2NjU1NTg2NDI3MDAwMzQ2OTcwMTI1MjA2ODg0NTkyNzkwMDU4NTAyMzkxMzUwODIyNjg1NDYyMzY0MzE5NTMwOTI0MjM4Mzg2MjkwMDI5MTQ1ODAzNTgxODA2MDQ1MTYzNDMzNTQ2OTAwMzQzNDg0MTg2NTEyMjIzODYwODY3NTI3ODExOTkyOTQwMjMxMzgzOTM4MjI0NjEwMTk0NDc1NjczMDYyMDI3MDgwOTI5NDYzMjU0NjIxMjI1NTg3MDg0NTUyODEyMDgxMDE3IiwiYyI6Ijg2NzE3OTAzNTAxODI5MzU5NDk4MjE3NDU5NjE3NjgyNTM4NzIxNzQwMjE5MDM5MDUzNjQzNTE3NDU0Nzc2NTUzMzA3MzU1OTkxMjE5In0sInJldl9yZWciOm51bGwsIndpdG5lc3MiOm51bGx9", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, "~please_ack": Object {}, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, - "~timing": undefined, - "~transport": undefined, }, "id": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "metadata": Object { @@ -684,21 +556,12 @@ Object { "offers~attach": Array [ Object { "@id": "libindy-cred-offer-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiMTE4MTE3NTM4MDU1MjM2NjMxNjAwNjM1NyJ9", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, - "~thread": undefined, - "~timing": undefined, - "~transport": undefined, }, "requestMessage": Object { "@id": "1284ae78-f3d3-4fed-a5ff-0e2aba968c3c", @@ -706,26 +569,15 @@ Object { "requests~attach": Array [ Object { "@id": "libindy-cred-request-0", - "byte_count": undefined, "data": Object { "base64": "eyJwcm92ZXJfZGlkIjoiUVZveGd3d25WUGtBQlRMVmNtQ013TCIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiMTkwNzM1MzQyMjkwNjk4Mzk3NzgyNTUyMTM2NTA3NzAxMDA4NzYwMzcxMjg3NTQ1MzI0NDM2NDIyMTMwNzQ3MDI3NTk4NDM2NTM5Mzc0NzM0MjgxOTk4MDY5Mjg1OTg4MzAzMDE1MzAzMTYxNjExMTEzMTY2MzQxMzkyOTkzMTk0ODUxNzM5Njk4NzcxNDYzNzMzMDA4MjUxMzQ5NjM4OTkzMDE5NTk5MTY1NDUyOTk4OTA4OTY4MDE5NTUxODM2NDg1NzYxMzMxNTgxNTY0MzgxNTkxNjMwOTcyNTc5NTkyODMyNDk3MDI0NTMyMjQxNzk4MDMzMjI1NDg4NjA5NjEwNjEzNTU1NDMxODc3NDQzODk2ODUyMzA4NjIxNDA1NjI5NjA5MTg5Nzg2MjYzMTcyODU0MjA1MTI3ODMxNjc5NzM5NTkxODQyMTYwOTAyNjczMDE4Mzc0MzE5NjUyODA3Njc2MDQzNzc0ODcxMjQzMzkzNTIwNTkwODE5MDgxOTI4NzY3MjU5NDQ2OTIxNTM2MjU3MjQ2NjIxMjgzNjc2NDM1MDIwNzUwODI4NDI2NTM2MTU2ODA5NDgwMTU3MTQ0NDkxNTY2MTM0ODYzNjU4Mjg5ODIyMTE1NjI4MzMxNjMxMTQ3ODM1NzQ4MjAxMzkyNjY4NzQyMTQ5NTI1OTQ0OTc1NzY3NTYwMTQyNzQ5MTU3MTY2NzE0MDY0OTM2OTQ1MzEwMzEwMzU1NjgwNTcyNDgzNDgyNTYyMzk5Nzc0OTY5NTYwMTA1Njk2MzczMDU4MDMzODgyMTAwOTY2ODUwMTk5MjEzMzAiLCJ1ciI6bnVsbCwiaGlkZGVuX2F0dHJpYnV0ZXMiOlsibWFzdGVyX3NlY3JldCJdLCJjb21taXR0ZWRfYXR0cmlidXRlcyI6e319LCJibGluZGVkX21zX2NvcnJlY3RuZXNzX3Byb29mIjp7ImMiOiI1MDg3Mzk4NDExNzQ3Mzc5NjY5MDkyNzU5ODQ5OTEwMDI2OTYzMTk2NjExNjc5MzU5NDYwNDMxMjYyMDE4NzgyNzY4NTM2NTUzNzUwMiIsInZfZGFzaF9jYXAiOiIxODU0NzEwMDA5NDM4NTg5MTc4MzkzMjgzMDk1MzM5NDUzMDQ3OTkwOTYyMjE2NzEyNzk2ODkyMzcyMzA5NjU5NTU3MDY2MzQxMTMxOTY0Mjg5NjA3MTI5MDg4MjMxMDY0NTk5ODY4NDg4MTIzNDMwMzY5OTkxMjI1OTMxMTIyMjY4NjU5MDEwMDA0NDA1OTIzNTcyMzgyMzQzODczNjkxODg3NDQzMjQ5MTcwNTQwNDk4Nzk5MTkxOTIzMjc4NDU4MzcyMzk2NzIyMjM5NDE1NjY3ODIxMDQ4ODA0NDk1NDQ5ODQ3MjcwOTg1MDcwNzY3NjU2NDU4NDM0MTYzNTI3NDAyMzA1NTg5MTg4NzcyNDg4NzE1NjcwOTgxNTc0NzQxMTI1NzYxOTIxNTE3NzYyNzg1Nzk4MjU5MTQ0OTYzMDQyMjg0NzUwMDE5MjAwMjQ4NjgxNjkxOTE5Njg3MDA1MDA4MDUzMjYwODY5NzEyNTEwNzIwNDg5NDAwMjM0NDU3Njk2MDk4NjI0Nzk1MDUwMzQ2NzM2Mjg1MDE3MTU5Mjk1OTA0NTU1NTk0MzMxNzI4MTQ5MzgyODE5NTI2NTc3MDg3NjA0ODMwNDk3MTE0Mjc3MTkyMDU3ODk1MzYxNzI5NTE3NTgxNzg5ODEyMDY3MjcxNTU5MTMyNTI1MzEzNTc0MDEwNTM3NDMxMDY2NzUwNzAzMDgxMTQxNDIyODg5MzUxOTY0MDUyMDU0NjY0MTA0ODE0MzY1Njg3NTcxNjU5MTk3ODQxMjU5NjE3OTI4MDg4NjM0ODY1NTI2MjI4NTkyNTI2NTgwODgzMzIzNjEwNTc5NzU4ODgzMjgwNDcyNTA0OTQ0MjM2ODY5MTYyNzM3NzUwNTI0NTIyMjE5NTM4NjE4OTk2ODQzODU3MzU3MjUxODI5NTIyODgxOTA0NTAwMDU2MjU1NTMwNDgyIiwibV9jYXBzIjp7Im1hc3Rlcl9zZWNyZXQiOiIxMjM5OTQ3ODE4MDA1MTQ3MjQ5MjcyODIxNDI2OTgwNjQ2NTIwMjAwMTc0MzUwMzkzMzQ0NzU1NTU0NDAxNjg1NzQ2NzExMzkzMjEzMjA3NjI3ODI5MTczMjc2OTA2MDg4MDAxMDIxMTMxMzY4NzY4MjI0ODgxNTExMjEwNjY0NzA5OTAxOTQwODA0MzA0OTc1NTUyMzExNzAyMjU3MTYwOTAxNTE0MzIxNzYyNzM0ODUwMSJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiIzNzM5ODQyNzAxNTA3ODY4NjQ0MzMxNjMifQ==", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, - "~timing": undefined, - "~transport": undefined, }, "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", @@ -736,6 +588,7 @@ Object { "tags": Object { "connectionId": "54b61a2c-59ae-4e63-a441-7f1286350132", "credentialId": "a77114e1-c812-4bff-a53c-3d5003fcc278", + "credentialIds": Array [], "indyCredentialRevocationId": undefined, "indyRevocationRegistryId": undefined, "state": "done", @@ -770,26 +623,16 @@ Object { "credentials~attach": Array [ Object { "@id": "libindy-cred-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImRhdGVPZkJpcnRoIjp7InJhdyI6IjIwMjAtMDEtMDEiLCJlbmNvZGVkIjoiNDEwNjEyOTM3ODA0NjIwNzU1MTQyMDgyMjM4OTc1OTA0MDc4MDA3NDg1NDMwMTQ5OTE1ODg1MjI3MjExOTM4ODY4OTgxMTk3NTM2MjQifSwiYWdlIjp7InJhdyI6IjI1IiwiZW5jb2RlZCI6IjI1In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6Ijg5NjQzNDI5NjIzMzI5NjQ1MDM2NDU5MjU4NzU2Nzc5MDY3ODczOTc4MDg1ODc3MDM3MzU1NzMxMjk1MzA0NzY5ODAxNDM5MjI2MDI4IiwiYSI6IjUzOTQ3MDU1MTU0OTEyMTE2MzM0MzU2NTMyMjEyMTM5MjMxNjE2NTcwMzU3MjkwNzcyOTkxNjM4NTU1MjU2Mjc2NjgyODY5NDM2MTI5OTEzNzk2MzQwOTA1Nzk3Mzc2OTI5NDE0MTIwNzMwMjI5NjQzNjMwNTI2OTcyOTUxNDk1NjA5NDcwMDU0NzEzOTY1OTE5NTU3MzM3NDkwMjUyNTI1NTM5NzI4MjA0NTI2NTU0MDcyMDYyOTcxMTA0MTM3NTQ4NzEzMzIzNTUzMTYwOTEzODQ0MDk0MDczOTQ3MjM2OTQyNzgyODIzNDA2NDUyNzIzODY2NjMzMzc2MjI4Mzk2ODE5ODI0MTQ2MjgyNTI4NDIyOTIzMjM1NzEzNTI5MjQ3ODkxMTQzMDE4OTM3ODQ0ODM3NzQ2MDE4MTc5Mjk5ODI5ODQ1MTI4MTgxMDUxOTE4MjE0ODU5Mzg5MjIzOTEzNjUzMjE0MjIxMTk2NDI2OTA2NDM1NDYwNDQwNTgxNDkxNTg5MzMxMzIyMDU1MDU1NjE5NDY0OTEwNTc3OTcyODAyNDM1MDY3OTMxMDczOTI5OTgyOTQ2Njg4NDg5MTIwNTQ1MjA1MzQ0MjQ1NTIzNTExNDc5NjUzNjI5ODIxNTA4NTc3MjI2MzU5MjUyMDA5MjUyNTU2NDA5NTg0MDgwNDY5NDI4NzE1NDQ0MDkyOTA4NzAxNjMwMzE3NzI5MTE3NTYwMzg2NjAyNDA4OTE3Mzg2NjM4NzQ2MDY1NjU0NzQ2OTAxOTA1NjA4MjE5MzgzNzczNjg3NDcxODI3NjE2OTU5MDk3NDU4IiwiZSI6IjI1OTM0NDcyMzA1NTA2MjA1OTkwNzAyNTQ5MTQ4MDY5NzU3MTkzODI3Nzg4OTUxNTE1MjMwNjI0OTcyODU4MzEwNTY2NTgwMDcxMzMwNjc1OTE0OTk4MTY5MDU1OTE5Mzk4NzE0MzAxMjM2NzkxMzIwNjI5OTMyMzg5OTY5Njk0MjIxMzIzNTk1Njc0MjkzMDMwMzU1ODY2NDUxMDY0MDQ4Mzk4OTcyMjU0Nzk2ODY2NDA2OSIsInYiOiI4NzE4MTIwMTY2NjA3NTg4MjIxMDczMTE0NTgxNTcxMDA5NTEwNDUwMzkyNDk0MDM1MTg3MDk5MTQyNzA5NDcyMjYxMDAyMjg5MzI5MzcyMzk1MDUwMzgzOTA3ODUxODc1MjIxODU0NjI4ODc4OTcxNzQ0Njg3MDgxMDM4Mzk2Njc2MzgyMTI0NjEyNDY0NjQxMDQ4NDMxNDkwMTYzMDgwOTU0NTc3MjA3OTkxNjg3NzU0NjQ4MTYwNzM5MjQ2ODUyMjMxMjU2NTk0MTg4MTAxNDU2MjgzNjc3MTA4NTMwNjY1NDQwNTUwMDY0MjgxODI3NzUxNjA3NjM1ODE2MjczNDU3MTc4MzAyNjEyOTI5ODMyNDMzNDc3ODk0ODMzMDc2MDA5OTE4MTc1MzI4OTUzNjg1MjEwOTQ1ODg0MTQyMDg0Nzk4ODMzNzM2OTExNDcwMTkwOTYwMjI3MzAyMzI2NjQ2NjE2NjUwMjY4OTU3NDcyMTI3MTA2Mjk3NzQ0NDg3MDUzODY2NjI0NTk4Njg1MzA0MzA0OTMzNjAxNDczNjY4Njg1NDg5MzYyMzQ2NzE5ODA4MjgxNzYxNjc0ODQyMzE5NTY5Mzk1Nzk1NTQ5OTA4MjAyODI0MjgyOTc1MTI3MDA0MzM0NTkwNTYzMTI3NjU3NDM3MjQ2MDQ3OTUyOTk0OTIyODQ3MzcxMzY4NDM0OTE3MDM1Njc4ODA2NjM1OTQ0ODY4Njc5MDY1NDc5Njk1MDU5NzkyNzUyMzk5NDcwMzUzMDI3MjEyNTg2OTc1Mjk5MTk1NDcwNjY0NDMzMDIyNTQyODg4MzI4OTA0Mjg2NTIxMzM5Nzc2OTkxMDYzNzA1MTI2NjA4MDY4OTA0Mzg2MDc4NzA5NTE3NjU0OTE3MzI0NjExMzkzNTM4MDkyNTQ3NzQ0OTM2NTM1NDkwODcwNDU4NjQ3NjY2OTU3MjA5MDk4MDU2NzIwMjAzOTAxMjI3MjU2NDM5NTkwNTA0MzIwOTI3NTc1ODA2NjE0NzA4NTU3MjAxMTAxODczODc4NDg1NjM4MzQ2Nzk1NjE4NDQxOTQxMjQyODc5ODMyNjQ5ODEyIn0sInJfY3JlZGVudGlhbCI6bnVsbH0sInNpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZiI6eyJzZSI6IjIxOTkzMzcwNTI0MjIxNTM0MTM0MTc4MDM3MDIyMDEyMzE4NjQ2MDE4MTk0MDIwMjgxNzY4NTQ4OTUyNjM5ODg4MDE2NDQ1NTM2NTQ2MzczMzkwMDU5NjY1Nzg4OTc2MDE0OTUzMTU2MjA1MTA0ODU1NTM1NjEyODY5Mjg5NjgyOTQ1ODI4MDQxOTMxMzc1ODY4NjE4OTE0NjUwNTc5ODM2NzI0NDE0MDMxMjU3MjU2MzkxNjg2OTQ0NjQyMzg3NTIwNjExNDQ5ODM1NTgxNDMzMDMzOTQ4MTA4OTE0MzI2NzkyNDU5MjQ0Mzc0MTgyOTQ4MDQxODIzMTg3NjY3MjE2MDI5OTEwMTAzMTM1MjE4NjY5ODc5MDk5ODA0NTA0NjI1NTAzNDM2NTAxOTk5ODkwODIyNjcyMjYwNzc1NDIzMzIxNTQ0MDk0Mjk2NDI0Nzc2Njg2MDI1MzU2MjMwOTY0OTQyMzc3NTY0NDUwMDk4NTgyMzg2Nzg0ODc3OTQwNTI2ODg0MzgzOTcxOTE4OTE3ODIyOTkzMDIzMDU2NjU1NTg2NDI3MDAwMzQ2OTcwMTI1MjA2ODg0NTkyNzkwMDU4NTAyMzkxMzUwODIyNjg1NDYyMzY0MzE5NTMwOTI0MjM4Mzg2MjkwMDI5MTQ1ODAzNTgxODA2MDQ1MTYzNDMzNTQ2OTAwMzQzNDg0MTg2NTEyMjIzODYwODY3NTI3ODExOTkyOTQwMjMxMzgzOTM4MjI0NjEwMTk0NDc1NjczMDYyMDI3MDgwOTI5NDYzMjU0NjIxMjI1NTg3MDg0NTUyODEyMDgxMDE3IiwiYyI6Ijg2NzE3OTAzNTAxODI5MzU5NDk4MjE3NDU5NjE3NjgyNTM4NzIxNzQwMjE5MDM5MDUzNjQzNTE3NDU0Nzc2NTUzMzA3MzU1OTkxMjE5In0sInJldl9yZWciOm51bGwsIndpdG5lc3MiOm51bGx9", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, "~please_ack": Object {}, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, - "~timing": undefined, - "~transport": undefined, }, "id": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", "metadata": Object { @@ -832,21 +675,12 @@ Object { "offers~attach": Array [ Object { "@id": "libindy-cred-offer-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiMTE4MTE3NTM4MDU1MjM2NjMxNjAwNjM1NyJ9", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, - "~thread": undefined, - "~timing": undefined, - "~transport": undefined, }, "requestMessage": Object { "@id": "1284ae78-f3d3-4fed-a5ff-0e2aba968c3c", @@ -854,26 +688,15 @@ Object { "requests~attach": Array [ Object { "@id": "libindy-cred-request-0", - "byte_count": undefined, "data": Object { "base64": "eyJwcm92ZXJfZGlkIjoiUVZveGd3d25WUGtBQlRMVmNtQ013TCIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiMTkwNzM1MzQyMjkwNjk4Mzk3NzgyNTUyMTM2NTA3NzAxMDA4NzYwMzcxMjg3NTQ1MzI0NDM2NDIyMTMwNzQ3MDI3NTk4NDM2NTM5Mzc0NzM0MjgxOTk4MDY5Mjg1OTg4MzAzMDE1MzAzMTYxNjExMTEzMTY2MzQxMzkyOTkzMTk0ODUxNzM5Njk4NzcxNDYzNzMzMDA4MjUxMzQ5NjM4OTkzMDE5NTk5MTY1NDUyOTk4OTA4OTY4MDE5NTUxODM2NDg1NzYxMzMxNTgxNTY0MzgxNTkxNjMwOTcyNTc5NTkyODMyNDk3MDI0NTMyMjQxNzk4MDMzMjI1NDg4NjA5NjEwNjEzNTU1NDMxODc3NDQzODk2ODUyMzA4NjIxNDA1NjI5NjA5MTg5Nzg2MjYzMTcyODU0MjA1MTI3ODMxNjc5NzM5NTkxODQyMTYwOTAyNjczMDE4Mzc0MzE5NjUyODA3Njc2MDQzNzc0ODcxMjQzMzkzNTIwNTkwODE5MDgxOTI4NzY3MjU5NDQ2OTIxNTM2MjU3MjQ2NjIxMjgzNjc2NDM1MDIwNzUwODI4NDI2NTM2MTU2ODA5NDgwMTU3MTQ0NDkxNTY2MTM0ODYzNjU4Mjg5ODIyMTE1NjI4MzMxNjMxMTQ3ODM1NzQ4MjAxMzkyNjY4NzQyMTQ5NTI1OTQ0OTc1NzY3NTYwMTQyNzQ5MTU3MTY2NzE0MDY0OTM2OTQ1MzEwMzEwMzU1NjgwNTcyNDgzNDgyNTYyMzk5Nzc0OTY5NTYwMTA1Njk2MzczMDU4MDMzODgyMTAwOTY2ODUwMTk5MjEzMzAiLCJ1ciI6bnVsbCwiaGlkZGVuX2F0dHJpYnV0ZXMiOlsibWFzdGVyX3NlY3JldCJdLCJjb21taXR0ZWRfYXR0cmlidXRlcyI6e319LCJibGluZGVkX21zX2NvcnJlY3RuZXNzX3Byb29mIjp7ImMiOiI1MDg3Mzk4NDExNzQ3Mzc5NjY5MDkyNzU5ODQ5OTEwMDI2OTYzMTk2NjExNjc5MzU5NDYwNDMxMjYyMDE4NzgyNzY4NTM2NTUzNzUwMiIsInZfZGFzaF9jYXAiOiIxODU0NzEwMDA5NDM4NTg5MTc4MzkzMjgzMDk1MzM5NDUzMDQ3OTkwOTYyMjE2NzEyNzk2ODkyMzcyMzA5NjU5NTU3MDY2MzQxMTMxOTY0Mjg5NjA3MTI5MDg4MjMxMDY0NTk5ODY4NDg4MTIzNDMwMzY5OTkxMjI1OTMxMTIyMjY4NjU5MDEwMDA0NDA1OTIzNTcyMzgyMzQzODczNjkxODg3NDQzMjQ5MTcwNTQwNDk4Nzk5MTkxOTIzMjc4NDU4MzcyMzk2NzIyMjM5NDE1NjY3ODIxMDQ4ODA0NDk1NDQ5ODQ3MjcwOTg1MDcwNzY3NjU2NDU4NDM0MTYzNTI3NDAyMzA1NTg5MTg4NzcyNDg4NzE1NjcwOTgxNTc0NzQxMTI1NzYxOTIxNTE3NzYyNzg1Nzk4MjU5MTQ0OTYzMDQyMjg0NzUwMDE5MjAwMjQ4NjgxNjkxOTE5Njg3MDA1MDA4MDUzMjYwODY5NzEyNTEwNzIwNDg5NDAwMjM0NDU3Njk2MDk4NjI0Nzk1MDUwMzQ2NzM2Mjg1MDE3MTU5Mjk1OTA0NTU1NTk0MzMxNzI4MTQ5MzgyODE5NTI2NTc3MDg3NjA0ODMwNDk3MTE0Mjc3MTkyMDU3ODk1MzYxNzI5NTE3NTgxNzg5ODEyMDY3MjcxNTU5MTMyNTI1MzEzNTc0MDEwNTM3NDMxMDY2NzUwNzAzMDgxMTQxNDIyODg5MzUxOTY0MDUyMDU0NjY0MTA0ODE0MzY1Njg3NTcxNjU5MTk3ODQxMjU5NjE3OTI4MDg4NjM0ODY1NTI2MjI4NTkyNTI2NTgwODgzMzIzNjEwNTc5NzU4ODgzMjgwNDcyNTA0OTQ0MjM2ODY5MTYyNzM3NzUwNTI0NTIyMjE5NTM4NjE4OTk2ODQzODU3MzU3MjUxODI5NTIyODgxOTA0NTAwMDU2MjU1NTMwNDgyIiwibV9jYXBzIjp7Im1hc3Rlcl9zZWNyZXQiOiIxMjM5OTQ3ODE4MDA1MTQ3MjQ5MjcyODIxNDI2OTgwNjQ2NTIwMjAwMTc0MzUwMzkzMzQ0NzU1NTU0NDAxNjg1NzQ2NzExMzkzMjEzMjA3NjI3ODI5MTczMjc2OTA2MDg4MDAxMDIxMTMxMzY4NzY4MjI0ODgxNTExMjEwNjY0NzA5OTAxOTQwODA0MzA0OTc1NTUyMzExNzAyMjU3MTYwOTAxNTE0MzIxNzYyNzM0ODUwMSJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiIzNzM5ODQyNzAxNTA3ODY4NjQ0MzMxNjMifQ==", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, - "~timing": undefined, - "~transport": undefined, }, "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", @@ -894,7 +717,7 @@ Object { "id": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "tags": Object { "connectionId": "cd66cbf1-5721-449e-8724-f4d8dcef1bc4", - "credentialId": undefined, + "credentialIds": Array [], "indyCredentialRevocationId": undefined, "indyRevocationRegistryId": undefined, "state": "done", @@ -928,26 +751,16 @@ Object { "credentials~attach": Array [ Object { "@id": "libindy-cred-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFnZSI6eyJyYXciOiIyNSIsImVuY29kZWQiOiIyNSJ9LCJkYXRlT2ZCaXJ0aCI6eyJyYXciOiIyMDIwLTAxLTAxIiwiZW5jb2RlZCI6IjQxMDYxMjkzNzgwNDYyMDc1NTE0MjA4MjIzODk3NTkwNDA3ODAwNzQ4NTQzMDE0OTkxNTg4NTIyNzIxMTkzODg2ODk4MTE5NzUzNjI0In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6IjEwMzE4MTcwNjEzMDMzODg1ODkxNDkzMzk2MjU5NDYyNDIxMzM3MjY3ODcyNzkyNzczNjgwMDQwMTgyNTE4NDM1MzEzMDYyNjE3MTcxMCIsImEiOiI3MTM3NDExNDU3NjI3MDE5MDcyNjY0ODMzNTQ5MjAzMDQyMTg0MDQ0OTUxNTU5MzYwMDczMzk1MjM4NjkwMzkwNjgxNzA3ODAyNzY0MTE2MzQ4NjE4NjgxOTgzNTM2MTczNTE3MzgxODc5NzQ0MzQyODIzMDkzNzc4MTk1NzYxNzgzMjQ3NDcxOTQ2NDgwODc0OTY2OTQyNjY0NzU4NjIwNzEyNzExODExNTY5NTc1NzMwNTg4NTQwNDI1MjEzNjY0OTg0OTQyOTIzODU5NzQ3MjIzNjA5ODQ1NjIwMjE5NTY1NzYxODY2OTMwMzI3OTYzNTIwNzE5MTg2NjMyNTQ4NzkzNDI3MTQ0NTY0NTAxNjE1NTg1MTI2NzkxNzM5Njg3ODc2MzIxMTQ1NTAzNDU1OTM0MzUxODc0MjQ3NjA0NzAwODYxMTgxNjY4NjUxNzQ5NTExMjY0MzExMDU2NjI5MjM4NjQwNDY2NzM4ODA0NjI0NzU1MzEzODgxMjQzMjkwNDM5ODI4MDE0NzY0MTQ3NDM1MzE3NTY3MjY3MzQ2NTQxMTY2ODIwNTI2MDkyMDAyMDE0NzY5NjE1MzI3Mjg4MTYwMzM0MDg1MTQ1MzQ0Mzc5MzAxMDg1NDc1MDEyNzUzNDIzNzkxMjI4NzM5NDE3MzA0OTM2NDUzNDEyMDYxNjY0MTUzNTM4MjM5MDA0OTUxMDgyODQxMTE1MTIyNDQ1MzkzNzc5Mzc5NDI5NjQzMjMxNDE2NDQzNDM4NDQ1NzI1NTAzMDc0MTM5MzEwOTc2MjkwMTQ2MTIwMDI2MDI1NTczNzIyNTUyOCIsImUiOiIyNTkzNDQ3MjMwNTUwNjIwNTk5MDcwMjU0OTE0ODA2OTc1NzE5MzgyNzc4ODk1MTUxNTIzMDYyNDk3Mjg1ODMxMDU2NjU4MDA3MTMzMDY3NTkxNDk5ODE2OTA1NTkxOTM5ODcxNDMwMTIzNjc5MTMyMDYyOTkzMjM4OTk2OTY5NDIyMTMyMzU5NTY3NDI5MzAxNTQ3NTU5NjM2MjQ0MTcyOTU5NjM1MjY1ODc1MTkyNjIwNTEiLCJ2IjoiODMxMTU0NzI2ODU4Mzg3ODc5NTU5NDUzNzcwNjg3MzIyMzE1NTgyMjYzMTk3NDUzMjMxNTI1ODIzNDIzNjkxMDk5Mzc2NTExNzI0ODAxMzA1MTU0NzY2Mjc0NjQ1OTMzMTAwOTIzMjIxOTgwNDkyNzE0NDQxMTY0NTYxNTIwMDIzMjYzMDYzMzQ4NjQ4NzkzMjkxMzEwNDc0NTU5NDIwMTkzMTA1MjE5NzMxNTAwMTc0ODg0NzQ0MDk1MjU3MDYyMjczODA2OTYzNjg5MjY3NzA1NTg4NTQ4MzU4NzQ2NDc1Mzc1MzAzMjI1OTI3MDkxMzA0NzQ2NTg5MzA1MDEzNjc1ODk0MzIxMDkzNjE0NzIxMjQwNDAzNDE5OTM5OTk0Mjg5NTU2MzY0MDExMTg2ODQ2NjMxNTA2OTU0NDg0NjM4NjgwMzEyMDA2Njk0MjcwOTkwNDU3NTk3NjAyNzc5MjUzMDc3MTg3NDgwNTg5NDMyNzU4ODgwMjY3NzA1NzMyMjg3Nzc0ODczOTI3MDExMTQ1MDE1NzgyNjE5NzI4NTAxNjI5MTE4ODE1ODM2NjU2OTMzMzcwMzgwNDk4NDk5MDE0MDEzNDI1NDMwMjMwODQzODc0OTk3NTg0NTY3NTA0Mzg3OTE4MjQxMzMxNTM1NDk5MTQxMjU1NzQzNjQ0MzgwNTQ4ODAxNDUwNDEyMzQzMTAxODc4Nzg3NzIzOTIxMDU5NjQyNDg2MjE3NjE0MzQyMDc0MTQ3ODk1ODA2MzQ1NTQ0Njk3NjI2MzcwMDY1MjYxMjQ3OTM2NzMwMzMyNTkyMjM3NDY1MjIyNTQ1MjE4ODc4MDk0ODk0NTE0ODU2ODUyNTU1NjI4MjIwNDg2MTU5MjcyMjIxMjM3MDkwMjc4NjUyNDM2MzcyNTE4MDgzOTUxNjAwNzI1ODA1MDYwNjExMTkyNzEyOTI3MjQ2MTUxMDU4NzU5NDk2MzQ3NjA0NDQwNDcxODcwODEyNzMwODE3MTU4NzQxNDQ1MDA0OTg2MTgyNDk5NDA4OTQxMzk2NjcwMzEyOTU0NDQ1MTk1NTM5MTM5MzIwMDI4MTI3NzMyMDQ0MSJ9LCJyX2NyZWRlbnRpYWwiOm51bGx9LCJzaWduYXR1cmVfY29ycmVjdG5lc3NfcHJvb2YiOnsic2UiOiIyOTQ0ODU2NTEyNDk3MjM2MTc2OTQ3OTMxNzM1Mjk0NDc4MTU5NTE2ODM4OTExNDEyMDE3MTI1NTAyNjc1ODM2Nzk1NTQ4OTczNDMyMTg2NjI0MTc2NjQwNzAxNjczOTk4MjI3NTEzMTA0OTU5OTgzMjk1NjI0MTA0MjkwNjgzMjU0OTI4NTg1MDkwMTI2Mjk5ODczMjY4NDYyODA5NTY4NjMxNDg3MDk2ODgzMDE0OTcwMTcyMzM0MzIwMTM4OTg5ODkzMzcyODIyODU2MTQxMTM5NTQ0MzQyMzExNDEzNDgyMDU0OTYyNjE5NTgzNjU5NjMwMzYxNTc3Nzg0MTQ4NjY3NTgwNjMxODc4NDIwNTgzMDk5OTM1NzE0NDYyMzE5Njg4NDE5MTM4NjY3Nzk2Nzc2MzQwMDk2MDIxMTgwMTU0ODU0Njg1MDEzNzY1MTM0ODMwNjA0MzEyMjI0MjIwNzA5MzQwODExMTI4MDczMDk3NjEyMDA0MTE4NjA0MDI3MTczNjExNTE2ODA0NTQxMzUzODA2OTkxMTg0MDY3MzY1MDE5Nzk2NDM2MDA3NDI0NTM5NzQ4ODQxMjU4NjA1MTUxNTQxNzQzMDk4MTE5NzI1Mzc4MTQ4MTgyNDQ2Njg3NDQ0MjE0ODk1NTE2NDc3MTM4Mjk1OTI1Mzc4Nzg1MTA3OTc5MTYxNTIyNTYyNDk2Njg2MTAzMjg3MDUxNDUyMTE3NTQzMzc4Mzc0MDM5Mjg3NzgxMjAwNzgxMTkyNDQyOTY5MDU1NjIwNTcyODg1NDM5NjU2MTU3Njk2Mzc5MTAyNDc2MTQ2IiwiYyI6IjI4NjYxMjE3ODQ5OTg3ODI4MTA2Nzk5MTAxOTIxNDcyNzgxNDMzNzgzMDE5Mzg0NzAwMDUzMjU4MTU5NDUxNjc5MjMwODI0NzA5NjIifSwicmV2X3JlZyI6bnVsbCwid2l0bmVzcyI6bnVsbH0=", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, "~please_ack": Object {}, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, - "~timing": undefined, - "~transport": undefined, }, "id": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "metadata": Object { @@ -982,21 +795,12 @@ Object { "offers~attach": Array [ Object { "@id": "libindy-cred-offer-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiNTQzODYyOTczNzUxMjk1OTk2MTAzNjA3In0=", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, - "~thread": undefined, - "~timing": undefined, - "~transport": undefined, }, "requestMessage": Object { "@id": "edba1c87-51d3-4c70-aff2-ab8016e1060e", @@ -1004,26 +808,15 @@ Object { "requests~attach": Array [ Object { "@id": "libindy-cred-request-0", - "byte_count": undefined, "data": Object { "base64": "eyJwcm92ZXJfZGlkIjoiRUg2OTVENjRRd2hWRmtyazFtcDQ5aiIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiOTcwNjA5MzQ1NDAxNzE0NDIxNjQzNDg0NDE0MTQwOTA0NzMzNTQ4NTA4NzY0OTk5MTgxNzY1ODI3MjM3ODg3NjQ2MzQzNDYyMTY0ODA3MTUxMjg1ODk2MDczODAyNDY1MDMwMTcxNTIyODE4ODY4Mjc2ODUwMzE0NzQzMjM3ODc3NDMyMDgxMTQwMzE5ODU5OTM5NjM0MTI4NzkzOTk4NDcwMTUzNTQ0NjgxNTM5NDg4NzEyNDE5MTk3NzcxODE1NjU5Nzg1NDE5NTA1ODEyODI4NzYxOTI4MzExODczNjA5NDYwMjExMzQ4OTAyNDk4NzYxNjc5OTIzNzA2NjUwMDIzODg4ODU4NzE1MTYxMTIyMzA5MTc1MTE3MTk0NDMwNTI1ODY5NjcwMDEzMTgxNTkzNjI4NDQzMjk2MDI0MDE5NTc4MzIzODQwNzk0OTQ0MjIyODE1MTQwNTM2Mjc3ODQwMjU2ODc2MDE1MzUwNDgzOTE2MjYzMDgyODM5NzI2NzAxNjg4NjcxNDY0MjA3MzQxMTgwMjg3Mjk0Njg4NDA3NzQ3MDk1NjA0NzQ3NzA3OTc2Nzk2MjU0MTQ2NDQ0NTY5NzQ4MTk4OTMwMjkyOTkzNjY1ODk2MTUyMDMyNzQwODY3OTgwMjczMTMxMDM3NjkwNDkzNDU1Mjc4NDc3MDc3NjE0OTU2NjgzNjgxNDc5NzY3Njg0MDI4MzU5NzE4NzM0ODEyNzcyMDIyOTIwODQ3NDIyNDYyOTAwMjczMTcwMTU2NzQyMzUyMDQ2NDYyODI4NzAxMTE2MzU0MTkwMDY5MDE0NTIwMSIsInVyIjpudWxsLCJoaWRkZW5fYXR0cmlidXRlcyI6WyJtYXN0ZXJfc2VjcmV0Il0sImNvbW1pdHRlZF9hdHRyaWJ1dGVzIjp7fX0sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6IjE4MzE5MTUyNjg0NDkyMTM0OTc4MzkzNDE3OTY5NjQ5MDIyNzMzNzQzMTQ5NTUwNzAwNzc5ODk0Nzg1MTg3MTA1OTkyNjk4Mzg5MjAzIiwidl9kYXNoX2NhcCI6IjQ0NzA4MzAwOTUyNzA3MjA3NjI3NjA3NzM4MTI2NDgxNzA3OTA1MDcwNjEyMzQ5OTIxNTAxNTYyOTA2MzgyMzE0MDE4MzQ4MTAxMTE4MDI4MDIyMjk5OTgyNTEwMjI5ODM1OTQxMzY1MTM4Njg0MTU1OTEyOTE3NzYwMjgwOTIyNDk1MjE1ODA2NTM3MzA5NDU5NDA3NjcxNDgyNDA0NDMwODU4MzU5ODU3MDgzNzg1Njk1MTYzMjkwMzEyNzMzNDAxNjY1NDk5MjUwMDQ0NzkwODk4OTA4NzIzNzE1OTc0MDYwNzgyNDEzODYzMTU0MTUxNjg2OTM3ODY4MDM5MDU1Nzc1MzA5MjQ0MTYzOTUxNzgwMTgxNDk5MDM5MDgyMjcxNzgzNTgzNTkxMDIyNjYwMDYyMDQ3MDQ2NTEyMTM0NDU5OTI1MTgyOTg2MTkxOTAwNTQwMDg4MjE3Mzc2NjM4MjEzNTI0MDUxNjcxNzg3ODY0ODQ2NzIxNjk5NjQzNDk0MTI2MjA3NTg2MjgwNjQ5OTc4ODE2ODEwMjM5OTAzMzU0NzIyOTI2NTUxODYyNTQwMjc4ODU2OTEyNDQ2MDUzMTg4MzI3Mjk4NDc0NjgzMjkwMjU4MjgwNjY0OTgyOTM2NTYyODcwNTIyODA2NzYwMjE1OTI0MDc3ODQ2NjA2NTM0NzI4MjkyODQ2MDQyOTk1NjgxMTQzOTQ5MTU0MTU0NTU2NzQzMDYzMzY1OTIzMzU2OTg1MjQ5ODI1Njk2NzE3MzE2MDk1NzE5MDU2MTE5NTE0NTYwODY0MzUyMDc4ODMyOTYzNjI3Mjk0Njk1ODQ0MTE5NDA1NTMzNTY5MTI2ODExODE3NDYyNjczMzM3OTg4MzA0MDcwMzk5ODYyNTk2ODMyNDk2OTU3NzA4Nzc0NzI5NzAyOTEyNjY3Njk0OTgwMjc3OTI5MDgzNiIsIm1fY2FwcyI6eyJtYXN0ZXJfc2VjcmV0IjoiMjIxNTAyNTExNTYzODg1MTM1NzIwNjg4MzE5Njk5NzE5ODAxNDI4NDgzMTI2MjY0NjI0NTE5OTA4MjM5NTM0MjQ3MDQ3NTIwODgyNDE4Mzk0ODMzNjUwODM2NTI0NDk2MzUzMDkwNzIzOTI1MDc2NDY2ODQ3NjIxNzE4MDA4MDAxNTQyNTMzOTk4NTU1MzA1MDYwNjUzMzkwMjc4MDc2MzE2Mjg5MzcwMzA2MDcyMDYxNCJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiI2OTgzNzA2MTYwMjM4ODM3MzA0OTgzNzUifQ==", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, - "~timing": undefined, - "~transport": undefined, }, "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", @@ -1034,6 +827,7 @@ Object { "tags": Object { "connectionId": "d8f23338-9e99-469a-bd57-1c9a26c0080f", "credentialId": "19c1f29f-d2df-486c-b8c6-950c403fa7d9", + "credentialIds": Array [], "indyCredentialRevocationId": undefined, "indyRevocationRegistryId": undefined, "state": "done", @@ -1068,26 +862,16 @@ Object { "credentials~attach": Array [ Object { "@id": "libindy-cred-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFnZSI6eyJyYXciOiIyNSIsImVuY29kZWQiOiIyNSJ9LCJkYXRlT2ZCaXJ0aCI6eyJyYXciOiIyMDIwLTAxLTAxIiwiZW5jb2RlZCI6IjQxMDYxMjkzNzgwNDYyMDc1NTE0MjA4MjIzODk3NTkwNDA3ODAwNzQ4NTQzMDE0OTkxNTg4NTIyNzIxMTkzODg2ODk4MTE5NzUzNjI0In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6IjEwMzE4MTcwNjEzMDMzODg1ODkxNDkzMzk2MjU5NDYyNDIxMzM3MjY3ODcyNzkyNzczNjgwMDQwMTgyNTE4NDM1MzEzMDYyNjE3MTcxMCIsImEiOiI3MTM3NDExNDU3NjI3MDE5MDcyNjY0ODMzNTQ5MjAzMDQyMTg0MDQ0OTUxNTU5MzYwMDczMzk1MjM4NjkwMzkwNjgxNzA3ODAyNzY0MTE2MzQ4NjE4NjgxOTgzNTM2MTczNTE3MzgxODc5NzQ0MzQyODIzMDkzNzc4MTk1NzYxNzgzMjQ3NDcxOTQ2NDgwODc0OTY2OTQyNjY0NzU4NjIwNzEyNzExODExNTY5NTc1NzMwNTg4NTQwNDI1MjEzNjY0OTg0OTQyOTIzODU5NzQ3MjIzNjA5ODQ1NjIwMjE5NTY1NzYxODY2OTMwMzI3OTYzNTIwNzE5MTg2NjMyNTQ4NzkzNDI3MTQ0NTY0NTAxNjE1NTg1MTI2NzkxNzM5Njg3ODc2MzIxMTQ1NTAzNDU1OTM0MzUxODc0MjQ3NjA0NzAwODYxMTgxNjY4NjUxNzQ5NTExMjY0MzExMDU2NjI5MjM4NjQwNDY2NzM4ODA0NjI0NzU1MzEzODgxMjQzMjkwNDM5ODI4MDE0NzY0MTQ3NDM1MzE3NTY3MjY3MzQ2NTQxMTY2ODIwNTI2MDkyMDAyMDE0NzY5NjE1MzI3Mjg4MTYwMzM0MDg1MTQ1MzQ0Mzc5MzAxMDg1NDc1MDEyNzUzNDIzNzkxMjI4NzM5NDE3MzA0OTM2NDUzNDEyMDYxNjY0MTUzNTM4MjM5MDA0OTUxMDgyODQxMTE1MTIyNDQ1MzkzNzc5Mzc5NDI5NjQzMjMxNDE2NDQzNDM4NDQ1NzI1NTAzMDc0MTM5MzEwOTc2MjkwMTQ2MTIwMDI2MDI1NTczNzIyNTUyOCIsImUiOiIyNTkzNDQ3MjMwNTUwNjIwNTk5MDcwMjU0OTE0ODA2OTc1NzE5MzgyNzc4ODk1MTUxNTIzMDYyNDk3Mjg1ODMxMDU2NjU4MDA3MTMzMDY3NTkxNDk5ODE2OTA1NTkxOTM5ODcxNDMwMTIzNjc5MTMyMDYyOTkzMjM4OTk2OTY5NDIyMTMyMzU5NTY3NDI5MzAxNTQ3NTU5NjM2MjQ0MTcyOTU5NjM1MjY1ODc1MTkyNjIwNTEiLCJ2IjoiODMxMTU0NzI2ODU4Mzg3ODc5NTU5NDUzNzcwNjg3MzIyMzE1NTgyMjYzMTk3NDUzMjMxNTI1ODIzNDIzNjkxMDk5Mzc2NTExNzI0ODAxMzA1MTU0NzY2Mjc0NjQ1OTMzMTAwOTIzMjIxOTgwNDkyNzE0NDQxMTY0NTYxNTIwMDIzMjYzMDYzMzQ4NjQ4NzkzMjkxMzEwNDc0NTU5NDIwMTkzMTA1MjE5NzMxNTAwMTc0ODg0NzQ0MDk1MjU3MDYyMjczODA2OTYzNjg5MjY3NzA1NTg4NTQ4MzU4NzQ2NDc1Mzc1MzAzMjI1OTI3MDkxMzA0NzQ2NTg5MzA1MDEzNjc1ODk0MzIxMDkzNjE0NzIxMjQwNDAzNDE5OTM5OTk0Mjg5NTU2MzY0MDExMTg2ODQ2NjMxNTA2OTU0NDg0NjM4NjgwMzEyMDA2Njk0MjcwOTkwNDU3NTk3NjAyNzc5MjUzMDc3MTg3NDgwNTg5NDMyNzU4ODgwMjY3NzA1NzMyMjg3Nzc0ODczOTI3MDExMTQ1MDE1NzgyNjE5NzI4NTAxNjI5MTE4ODE1ODM2NjU2OTMzMzcwMzgwNDk4NDk5MDE0MDEzNDI1NDMwMjMwODQzODc0OTk3NTg0NTY3NTA0Mzg3OTE4MjQxMzMxNTM1NDk5MTQxMjU1NzQzNjQ0MzgwNTQ4ODAxNDUwNDEyMzQzMTAxODc4Nzg3NzIzOTIxMDU5NjQyNDg2MjE3NjE0MzQyMDc0MTQ3ODk1ODA2MzQ1NTQ0Njk3NjI2MzcwMDY1MjYxMjQ3OTM2NzMwMzMyNTkyMjM3NDY1MjIyNTQ1MjE4ODc4MDk0ODk0NTE0ODU2ODUyNTU1NjI4MjIwNDg2MTU5MjcyMjIxMjM3MDkwMjc4NjUyNDM2MzcyNTE4MDgzOTUxNjAwNzI1ODA1MDYwNjExMTkyNzEyOTI3MjQ2MTUxMDU4NzU5NDk2MzQ3NjA0NDQwNDcxODcwODEyNzMwODE3MTU4NzQxNDQ1MDA0OTg2MTgyNDk5NDA4OTQxMzk2NjcwMzEyOTU0NDQ1MTk1NTM5MTM5MzIwMDI4MTI3NzMyMDQ0MSJ9LCJyX2NyZWRlbnRpYWwiOm51bGx9LCJzaWduYXR1cmVfY29ycmVjdG5lc3NfcHJvb2YiOnsic2UiOiIyOTQ0ODU2NTEyNDk3MjM2MTc2OTQ3OTMxNzM1Mjk0NDc4MTU5NTE2ODM4OTExNDEyMDE3MTI1NTAyNjc1ODM2Nzk1NTQ4OTczNDMyMTg2NjI0MTc2NjQwNzAxNjczOTk4MjI3NTEzMTA0OTU5OTgzMjk1NjI0MTA0MjkwNjgzMjU0OTI4NTg1MDkwMTI2Mjk5ODczMjY4NDYyODA5NTY4NjMxNDg3MDk2ODgzMDE0OTcwMTcyMzM0MzIwMTM4OTg5ODkzMzcyODIyODU2MTQxMTM5NTQ0MzQyMzExNDEzNDgyMDU0OTYyNjE5NTgzNjU5NjMwMzYxNTc3Nzg0MTQ4NjY3NTgwNjMxODc4NDIwNTgzMDk5OTM1NzE0NDYyMzE5Njg4NDE5MTM4NjY3Nzk2Nzc2MzQwMDk2MDIxMTgwMTU0ODU0Njg1MDEzNzY1MTM0ODMwNjA0MzEyMjI0MjIwNzA5MzQwODExMTI4MDczMDk3NjEyMDA0MTE4NjA0MDI3MTczNjExNTE2ODA0NTQxMzUzODA2OTkxMTg0MDY3MzY1MDE5Nzk2NDM2MDA3NDI0NTM5NzQ4ODQxMjU4NjA1MTUxNTQxNzQzMDk4MTE5NzI1Mzc4MTQ4MTgyNDQ2Njg3NDQ0MjE0ODk1NTE2NDc3MTM4Mjk1OTI1Mzc4Nzg1MTA3OTc5MTYxNTIyNTYyNDk2Njg2MTAzMjg3MDUxNDUyMTE3NTQzMzc4Mzc0MDM5Mjg3NzgxMjAwNzgxMTkyNDQyOTY5MDU1NjIwNTcyODg1NDM5NjU2MTU3Njk2Mzc5MTAyNDc2MTQ2IiwiYyI6IjI4NjYxMjE3ODQ5OTg3ODI4MTA2Nzk5MTAxOTIxNDcyNzgxNDMzNzgzMDE5Mzg0NzAwMDUzMjU4MTU5NDUxNjc5MjMwODI0NzA5NjIifSwicmV2X3JlZyI6bnVsbCwid2l0bmVzcyI6bnVsbH0=", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, "~please_ack": Object {}, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, - "~timing": undefined, - "~transport": undefined, }, "id": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", "metadata": Object { @@ -1130,21 +914,12 @@ Object { "offers~attach": Array [ Object { "@id": "libindy-cred-offer-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiNTQzODYyOTczNzUxMjk1OTk2MTAzNjA3In0=", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, - "~thread": undefined, - "~timing": undefined, - "~transport": undefined, }, "requestMessage": Object { "@id": "edba1c87-51d3-4c70-aff2-ab8016e1060e", @@ -1152,26 +927,15 @@ Object { "requests~attach": Array [ Object { "@id": "libindy-cred-request-0", - "byte_count": undefined, "data": Object { "base64": "eyJwcm92ZXJfZGlkIjoiRUg2OTVENjRRd2hWRmtyazFtcDQ5aiIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiOTcwNjA5MzQ1NDAxNzE0NDIxNjQzNDg0NDE0MTQwOTA0NzMzNTQ4NTA4NzY0OTk5MTgxNzY1ODI3MjM3ODg3NjQ2MzQzNDYyMTY0ODA3MTUxMjg1ODk2MDczODAyNDY1MDMwMTcxNTIyODE4ODY4Mjc2ODUwMzE0NzQzMjM3ODc3NDMyMDgxMTQwMzE5ODU5OTM5NjM0MTI4NzkzOTk4NDcwMTUzNTQ0NjgxNTM5NDg4NzEyNDE5MTk3NzcxODE1NjU5Nzg1NDE5NTA1ODEyODI4NzYxOTI4MzExODczNjA5NDYwMjExMzQ4OTAyNDk4NzYxNjc5OTIzNzA2NjUwMDIzODg4ODU4NzE1MTYxMTIyMzA5MTc1MTE3MTk0NDMwNTI1ODY5NjcwMDEzMTgxNTkzNjI4NDQzMjk2MDI0MDE5NTc4MzIzODQwNzk0OTQ0MjIyODE1MTQwNTM2Mjc3ODQwMjU2ODc2MDE1MzUwNDgzOTE2MjYzMDgyODM5NzI2NzAxNjg4NjcxNDY0MjA3MzQxMTgwMjg3Mjk0Njg4NDA3NzQ3MDk1NjA0NzQ3NzA3OTc2Nzk2MjU0MTQ2NDQ0NTY5NzQ4MTk4OTMwMjkyOTkzNjY1ODk2MTUyMDMyNzQwODY3OTgwMjczMTMxMDM3NjkwNDkzNDU1Mjc4NDc3MDc3NjE0OTU2NjgzNjgxNDc5NzY3Njg0MDI4MzU5NzE4NzM0ODEyNzcyMDIyOTIwODQ3NDIyNDYyOTAwMjczMTcwMTU2NzQyMzUyMDQ2NDYyODI4NzAxMTE2MzU0MTkwMDY5MDE0NTIwMSIsInVyIjpudWxsLCJoaWRkZW5fYXR0cmlidXRlcyI6WyJtYXN0ZXJfc2VjcmV0Il0sImNvbW1pdHRlZF9hdHRyaWJ1dGVzIjp7fX0sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6IjE4MzE5MTUyNjg0NDkyMTM0OTc4MzkzNDE3OTY5NjQ5MDIyNzMzNzQzMTQ5NTUwNzAwNzc5ODk0Nzg1MTg3MTA1OTkyNjk4Mzg5MjAzIiwidl9kYXNoX2NhcCI6IjQ0NzA4MzAwOTUyNzA3MjA3NjI3NjA3NzM4MTI2NDgxNzA3OTA1MDcwNjEyMzQ5OTIxNTAxNTYyOTA2MzgyMzE0MDE4MzQ4MTAxMTE4MDI4MDIyMjk5OTgyNTEwMjI5ODM1OTQxMzY1MTM4Njg0MTU1OTEyOTE3NzYwMjgwOTIyNDk1MjE1ODA2NTM3MzA5NDU5NDA3NjcxNDgyNDA0NDMwODU4MzU5ODU3MDgzNzg1Njk1MTYzMjkwMzEyNzMzNDAxNjY1NDk5MjUwMDQ0NzkwODk4OTA4NzIzNzE1OTc0MDYwNzgyNDEzODYzMTU0MTUxNjg2OTM3ODY4MDM5MDU1Nzc1MzA5MjQ0MTYzOTUxNzgwMTgxNDk5MDM5MDgyMjcxNzgzNTgzNTkxMDIyNjYwMDYyMDQ3MDQ2NTEyMTM0NDU5OTI1MTgyOTg2MTkxOTAwNTQwMDg4MjE3Mzc2NjM4MjEzNTI0MDUxNjcxNzg3ODY0ODQ2NzIxNjk5NjQzNDk0MTI2MjA3NTg2MjgwNjQ5OTc4ODE2ODEwMjM5OTAzMzU0NzIyOTI2NTUxODYyNTQwMjc4ODU2OTEyNDQ2MDUzMTg4MzI3Mjk4NDc0NjgzMjkwMjU4MjgwNjY0OTgyOTM2NTYyODcwNTIyODA2NzYwMjE1OTI0MDc3ODQ2NjA2NTM0NzI4MjkyODQ2MDQyOTk1NjgxMTQzOTQ5MTU0MTU0NTU2NzQzMDYzMzY1OTIzMzU2OTg1MjQ5ODI1Njk2NzE3MzE2MDk1NzE5MDU2MTE5NTE0NTYwODY0MzUyMDc4ODMyOTYzNjI3Mjk0Njk1ODQ0MTE5NDA1NTMzNTY5MTI2ODExODE3NDYyNjczMzM3OTg4MzA0MDcwMzk5ODYyNTk2ODMyNDk2OTU3NzA4Nzc0NzI5NzAyOTEyNjY3Njk0OTgwMjc3OTI5MDgzNiIsIm1fY2FwcyI6eyJtYXN0ZXJfc2VjcmV0IjoiMjIxNTAyNTExNTYzODg1MTM1NzIwNjg4MzE5Njk5NzE5ODAxNDI4NDgzMTI2MjY0NjI0NTE5OTA4MjM5NTM0MjQ3MDQ3NTIwODgyNDE4Mzk0ODMzNjUwODM2NTI0NDk2MzUzMDkwNzIzOTI1MDc2NDY2ODQ3NjIxNzE4MDA4MDAxNTQyNTMzOTk4NTU1MzA1MDYwNjUzMzkwMjc4MDc2MzE2Mjg5MzcwMzA2MDcyMDYxNCJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiI2OTgzNzA2MTYwMjM4ODM3MzA0OTgzNzUifQ==", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, - "~timing": undefined, - "~transport": undefined, }, "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/backup.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/backup.test.ts.snap index ff5993a05e..d99afc4e98 100644 --- a/packages/core/src/storage/migration/__tests__/__snapshots__/backup.test.ts.snap +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/backup.test.ts.snap @@ -34,26 +34,16 @@ Array [ "credentials~attach": Array [ Object { "@id": "libindy-cred-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImRhdGVPZkJpcnRoIjp7InJhdyI6IjIwMjAtMDEtMDEiLCJlbmNvZGVkIjoiNDEwNjEyOTM3ODA0NjIwNzU1MTQyMDgyMjM4OTc1OTA0MDc4MDA3NDg1NDMwMTQ5OTE1ODg1MjI3MjExOTM4ODY4OTgxMTk3NTM2MjQifSwiYWdlIjp7InJhdyI6IjI1IiwiZW5jb2RlZCI6IjI1In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6Ijg5NjQzNDI5NjIzMzI5NjQ1MDM2NDU5MjU4NzU2Nzc5MDY3ODczOTc4MDg1ODc3MDM3MzU1NzMxMjk1MzA0NzY5ODAxNDM5MjI2MDI4IiwiYSI6IjUzOTQ3MDU1MTU0OTEyMTE2MzM0MzU2NTMyMjEyMTM5MjMxNjE2NTcwMzU3MjkwNzcyOTkxNjM4NTU1MjU2Mjc2NjgyODY5NDM2MTI5OTEzNzk2MzQwOTA1Nzk3Mzc2OTI5NDE0MTIwNzMwMjI5NjQzNjMwNTI2OTcyOTUxNDk1NjA5NDcwMDU0NzEzOTY1OTE5NTU3MzM3NDkwMjUyNTI1NTM5NzI4MjA0NTI2NTU0MDcyMDYyOTcxMTA0MTM3NTQ4NzEzMzIzNTUzMTYwOTEzODQ0MDk0MDczOTQ3MjM2OTQyNzgyODIzNDA2NDUyNzIzODY2NjMzMzc2MjI4Mzk2ODE5ODI0MTQ2MjgyNTI4NDIyOTIzMjM1NzEzNTI5MjQ3ODkxMTQzMDE4OTM3ODQ0ODM3NzQ2MDE4MTc5Mjk5ODI5ODQ1MTI4MTgxMDUxOTE4MjE0ODU5Mzg5MjIzOTEzNjUzMjE0MjIxMTk2NDI2OTA2NDM1NDYwNDQwNTgxNDkxNTg5MzMxMzIyMDU1MDU1NjE5NDY0OTEwNTc3OTcyODAyNDM1MDY3OTMxMDczOTI5OTgyOTQ2Njg4NDg5MTIwNTQ1MjA1MzQ0MjQ1NTIzNTExNDc5NjUzNjI5ODIxNTA4NTc3MjI2MzU5MjUyMDA5MjUyNTU2NDA5NTg0MDgwNDY5NDI4NzE1NDQ0MDkyOTA4NzAxNjMwMzE3NzI5MTE3NTYwMzg2NjAyNDA4OTE3Mzg2NjM4NzQ2MDY1NjU0NzQ2OTAxOTA1NjA4MjE5MzgzNzczNjg3NDcxODI3NjE2OTU5MDk3NDU4IiwiZSI6IjI1OTM0NDcyMzA1NTA2MjA1OTkwNzAyNTQ5MTQ4MDY5NzU3MTkzODI3Nzg4OTUxNTE1MjMwNjI0OTcyODU4MzEwNTY2NTgwMDcxMzMwNjc1OTE0OTk4MTY5MDU1OTE5Mzk4NzE0MzAxMjM2NzkxMzIwNjI5OTMyMzg5OTY5Njk0MjIxMzIzNTk1Njc0MjkzMDMwMzU1ODY2NDUxMDY0MDQ4Mzk4OTcyMjU0Nzk2ODY2NDA2OSIsInYiOiI4NzE4MTIwMTY2NjA3NTg4MjIxMDczMTE0NTgxNTcxMDA5NTEwNDUwMzkyNDk0MDM1MTg3MDk5MTQyNzA5NDcyMjYxMDAyMjg5MzI5MzcyMzk1MDUwMzgzOTA3ODUxODc1MjIxODU0NjI4ODc4OTcxNzQ0Njg3MDgxMDM4Mzk2Njc2MzgyMTI0NjEyNDY0NjQxMDQ4NDMxNDkwMTYzMDgwOTU0NTc3MjA3OTkxNjg3NzU0NjQ4MTYwNzM5MjQ2ODUyMjMxMjU2NTk0MTg4MTAxNDU2MjgzNjc3MTA4NTMwNjY1NDQwNTUwMDY0MjgxODI3NzUxNjA3NjM1ODE2MjczNDU3MTc4MzAyNjEyOTI5ODMyNDMzNDc3ODk0ODMzMDc2MDA5OTE4MTc1MzI4OTUzNjg1MjEwOTQ1ODg0MTQyMDg0Nzk4ODMzNzM2OTExNDcwMTkwOTYwMjI3MzAyMzI2NjQ2NjE2NjUwMjY4OTU3NDcyMTI3MTA2Mjk3NzQ0NDg3MDUzODY2NjI0NTk4Njg1MzA0MzA0OTMzNjAxNDczNjY4Njg1NDg5MzYyMzQ2NzE5ODA4MjgxNzYxNjc0ODQyMzE5NTY5Mzk1Nzk1NTQ5OTA4MjAyODI0MjgyOTc1MTI3MDA0MzM0NTkwNTYzMTI3NjU3NDM3MjQ2MDQ3OTUyOTk0OTIyODQ3MzcxMzY4NDM0OTE3MDM1Njc4ODA2NjM1OTQ0ODY4Njc5MDY1NDc5Njk1MDU5NzkyNzUyMzk5NDcwMzUzMDI3MjEyNTg2OTc1Mjk5MTk1NDcwNjY0NDMzMDIyNTQyODg4MzI4OTA0Mjg2NTIxMzM5Nzc2OTkxMDYzNzA1MTI2NjA4MDY4OTA0Mzg2MDc4NzA5NTE3NjU0OTE3MzI0NjExMzkzNTM4MDkyNTQ3NzQ0OTM2NTM1NDkwODcwNDU4NjQ3NjY2OTU3MjA5MDk4MDU2NzIwMjAzOTAxMjI3MjU2NDM5NTkwNTA0MzIwOTI3NTc1ODA2NjE0NzA4NTU3MjAxMTAxODczODc4NDg1NjM4MzQ2Nzk1NjE4NDQxOTQxMjQyODc5ODMyNjQ5ODEyIn0sInJfY3JlZGVudGlhbCI6bnVsbH0sInNpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZiI6eyJzZSI6IjIxOTkzMzcwNTI0MjIxNTM0MTM0MTc4MDM3MDIyMDEyMzE4NjQ2MDE4MTk0MDIwMjgxNzY4NTQ4OTUyNjM5ODg4MDE2NDQ1NTM2NTQ2MzczMzkwMDU5NjY1Nzg4OTc2MDE0OTUzMTU2MjA1MTA0ODU1NTM1NjEyODY5Mjg5NjgyOTQ1ODI4MDQxOTMxMzc1ODY4NjE4OTE0NjUwNTc5ODM2NzI0NDE0MDMxMjU3MjU2MzkxNjg2OTQ0NjQyMzg3NTIwNjExNDQ5ODM1NTgxNDMzMDMzOTQ4MTA4OTE0MzI2NzkyNDU5MjQ0Mzc0MTgyOTQ4MDQxODIzMTg3NjY3MjE2MDI5OTEwMTAzMTM1MjE4NjY5ODc5MDk5ODA0NTA0NjI1NTAzNDM2NTAxOTk5ODkwODIyNjcyMjYwNzc1NDIzMzIxNTQ0MDk0Mjk2NDI0Nzc2Njg2MDI1MzU2MjMwOTY0OTQyMzc3NTY0NDUwMDk4NTgyMzg2Nzg0ODc3OTQwNTI2ODg0MzgzOTcxOTE4OTE3ODIyOTkzMDIzMDU2NjU1NTg2NDI3MDAwMzQ2OTcwMTI1MjA2ODg0NTkyNzkwMDU4NTAyMzkxMzUwODIyNjg1NDYyMzY0MzE5NTMwOTI0MjM4Mzg2MjkwMDI5MTQ1ODAzNTgxODA2MDQ1MTYzNDMzNTQ2OTAwMzQzNDg0MTg2NTEyMjIzODYwODY3NTI3ODExOTkyOTQwMjMxMzgzOTM4MjI0NjEwMTk0NDc1NjczMDYyMDI3MDgwOTI5NDYzMjU0NjIxMjI1NTg3MDg0NTUyODEyMDgxMDE3IiwiYyI6Ijg2NzE3OTAzNTAxODI5MzU5NDk4MjE3NDU5NjE3NjgyNTM4NzIxNzQwMjE5MDM5MDUzNjQzNTE3NDU0Nzc2NTUzMzA3MzU1OTkxMjE5In0sInJldl9yZWciOm51bGwsIndpdG5lc3MiOm51bGx9", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, "~please_ack": Object {}, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, - "~timing": undefined, - "~transport": undefined, }, "id": "574b2a37-1db1-4af1-a3bf-35c6cb9e1d7a", "metadata": Object { @@ -88,21 +78,12 @@ Array [ "offers~attach": Array [ Object { "@id": "libindy-cred-offer-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiMTE4MTE3NTM4MDU1MjM2NjMxNjAwNjM1NyJ9", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, - "~thread": undefined, - "~timing": undefined, - "~transport": undefined, }, "requestMessage": Object { "@id": "1284ae78-f3d3-4fed-a5ff-0e2aba968c3c", @@ -110,26 +91,15 @@ Array [ "requests~attach": Array [ Object { "@id": "libindy-cred-request-0", - "byte_count": undefined, "data": Object { "base64": "eyJwcm92ZXJfZGlkIjoiUVZveGd3d25WUGtBQlRMVmNtQ013TCIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiMTkwNzM1MzQyMjkwNjk4Mzk3NzgyNTUyMTM2NTA3NzAxMDA4NzYwMzcxMjg3NTQ1MzI0NDM2NDIyMTMwNzQ3MDI3NTk4NDM2NTM5Mzc0NzM0MjgxOTk4MDY5Mjg1OTg4MzAzMDE1MzAzMTYxNjExMTEzMTY2MzQxMzkyOTkzMTk0ODUxNzM5Njk4NzcxNDYzNzMzMDA4MjUxMzQ5NjM4OTkzMDE5NTk5MTY1NDUyOTk4OTA4OTY4MDE5NTUxODM2NDg1NzYxMzMxNTgxNTY0MzgxNTkxNjMwOTcyNTc5NTkyODMyNDk3MDI0NTMyMjQxNzk4MDMzMjI1NDg4NjA5NjEwNjEzNTU1NDMxODc3NDQzODk2ODUyMzA4NjIxNDA1NjI5NjA5MTg5Nzg2MjYzMTcyODU0MjA1MTI3ODMxNjc5NzM5NTkxODQyMTYwOTAyNjczMDE4Mzc0MzE5NjUyODA3Njc2MDQzNzc0ODcxMjQzMzkzNTIwNTkwODE5MDgxOTI4NzY3MjU5NDQ2OTIxNTM2MjU3MjQ2NjIxMjgzNjc2NDM1MDIwNzUwODI4NDI2NTM2MTU2ODA5NDgwMTU3MTQ0NDkxNTY2MTM0ODYzNjU4Mjg5ODIyMTE1NjI4MzMxNjMxMTQ3ODM1NzQ4MjAxMzkyNjY4NzQyMTQ5NTI1OTQ0OTc1NzY3NTYwMTQyNzQ5MTU3MTY2NzE0MDY0OTM2OTQ1MzEwMzEwMzU1NjgwNTcyNDgzNDgyNTYyMzk5Nzc0OTY5NTYwMTA1Njk2MzczMDU4MDMzODgyMTAwOTY2ODUwMTk5MjEzMzAiLCJ1ciI6bnVsbCwiaGlkZGVuX2F0dHJpYnV0ZXMiOlsibWFzdGVyX3NlY3JldCJdLCJjb21taXR0ZWRfYXR0cmlidXRlcyI6e319LCJibGluZGVkX21zX2NvcnJlY3RuZXNzX3Byb29mIjp7ImMiOiI1MDg3Mzk4NDExNzQ3Mzc5NjY5MDkyNzU5ODQ5OTEwMDI2OTYzMTk2NjExNjc5MzU5NDYwNDMxMjYyMDE4NzgyNzY4NTM2NTUzNzUwMiIsInZfZGFzaF9jYXAiOiIxODU0NzEwMDA5NDM4NTg5MTc4MzkzMjgzMDk1MzM5NDUzMDQ3OTkwOTYyMjE2NzEyNzk2ODkyMzcyMzA5NjU5NTU3MDY2MzQxMTMxOTY0Mjg5NjA3MTI5MDg4MjMxMDY0NTk5ODY4NDg4MTIzNDMwMzY5OTkxMjI1OTMxMTIyMjY4NjU5MDEwMDA0NDA1OTIzNTcyMzgyMzQzODczNjkxODg3NDQzMjQ5MTcwNTQwNDk4Nzk5MTkxOTIzMjc4NDU4MzcyMzk2NzIyMjM5NDE1NjY3ODIxMDQ4ODA0NDk1NDQ5ODQ3MjcwOTg1MDcwNzY3NjU2NDU4NDM0MTYzNTI3NDAyMzA1NTg5MTg4NzcyNDg4NzE1NjcwOTgxNTc0NzQxMTI1NzYxOTIxNTE3NzYyNzg1Nzk4MjU5MTQ0OTYzMDQyMjg0NzUwMDE5MjAwMjQ4NjgxNjkxOTE5Njg3MDA1MDA4MDUzMjYwODY5NzEyNTEwNzIwNDg5NDAwMjM0NDU3Njk2MDk4NjI0Nzk1MDUwMzQ2NzM2Mjg1MDE3MTU5Mjk1OTA0NTU1NTk0MzMxNzI4MTQ5MzgyODE5NTI2NTc3MDg3NjA0ODMwNDk3MTE0Mjc3MTkyMDU3ODk1MzYxNzI5NTE3NTgxNzg5ODEyMDY3MjcxNTU5MTMyNTI1MzEzNTc0MDEwNTM3NDMxMDY2NzUwNzAzMDgxMTQxNDIyODg5MzUxOTY0MDUyMDU0NjY0MTA0ODE0MzY1Njg3NTcxNjU5MTk3ODQxMjU5NjE3OTI4MDg4NjM0ODY1NTI2MjI4NTkyNTI2NTgwODgzMzIzNjEwNTc5NzU4ODgzMjgwNDcyNTA0OTQ0MjM2ODY5MTYyNzM3NzUwNTI0NTIyMjE5NTM4NjE4OTk2ODQzODU3MzU3MjUxODI5NTIyODgxOTA0NTAwMDU2MjU1NTMwNDgyIiwibV9jYXBzIjp7Im1hc3Rlcl9zZWNyZXQiOiIxMjM5OTQ3ODE4MDA1MTQ3MjQ5MjcyODIxNDI2OTgwNjQ2NTIwMjAwMTc0MzUwMzkzMzQ0NzU1NTU0NDAxNjg1NzQ2NzExMzkzMjEzMjA3NjI3ODI5MTczMjc2OTA2MDg4MDAxMDIxMTMxMzY4NzY4MjI0ODgxNTExMjEwNjY0NzA5OTAxOTQwODA0MzA0OTc1NTUyMzExNzAyMjU3MTYwOTAxNTE0MzIxNzYyNzM0ODUwMSJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiIzNzM5ODQyNzAxNTA3ODY4NjQ0MzMxNjMifQ==", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, - "~timing": undefined, - "~transport": undefined, }, "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", @@ -168,26 +138,16 @@ Array [ "credentials~attach": Array [ Object { "@id": "libindy-cred-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImRhdGVPZkJpcnRoIjp7InJhdyI6IjIwMjAtMDEtMDEiLCJlbmNvZGVkIjoiNDEwNjEyOTM3ODA0NjIwNzU1MTQyMDgyMjM4OTc1OTA0MDc4MDA3NDg1NDMwMTQ5OTE1ODg1MjI3MjExOTM4ODY4OTgxMTk3NTM2MjQifSwiYWdlIjp7InJhdyI6IjI1IiwiZW5jb2RlZCI6IjI1In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6Ijg5NjQzNDI5NjIzMzI5NjQ1MDM2NDU5MjU4NzU2Nzc5MDY3ODczOTc4MDg1ODc3MDM3MzU1NzMxMjk1MzA0NzY5ODAxNDM5MjI2MDI4IiwiYSI6IjUzOTQ3MDU1MTU0OTEyMTE2MzM0MzU2NTMyMjEyMTM5MjMxNjE2NTcwMzU3MjkwNzcyOTkxNjM4NTU1MjU2Mjc2NjgyODY5NDM2MTI5OTEzNzk2MzQwOTA1Nzk3Mzc2OTI5NDE0MTIwNzMwMjI5NjQzNjMwNTI2OTcyOTUxNDk1NjA5NDcwMDU0NzEzOTY1OTE5NTU3MzM3NDkwMjUyNTI1NTM5NzI4MjA0NTI2NTU0MDcyMDYyOTcxMTA0MTM3NTQ4NzEzMzIzNTUzMTYwOTEzODQ0MDk0MDczOTQ3MjM2OTQyNzgyODIzNDA2NDUyNzIzODY2NjMzMzc2MjI4Mzk2ODE5ODI0MTQ2MjgyNTI4NDIyOTIzMjM1NzEzNTI5MjQ3ODkxMTQzMDE4OTM3ODQ0ODM3NzQ2MDE4MTc5Mjk5ODI5ODQ1MTI4MTgxMDUxOTE4MjE0ODU5Mzg5MjIzOTEzNjUzMjE0MjIxMTk2NDI2OTA2NDM1NDYwNDQwNTgxNDkxNTg5MzMxMzIyMDU1MDU1NjE5NDY0OTEwNTc3OTcyODAyNDM1MDY3OTMxMDczOTI5OTgyOTQ2Njg4NDg5MTIwNTQ1MjA1MzQ0MjQ1NTIzNTExNDc5NjUzNjI5ODIxNTA4NTc3MjI2MzU5MjUyMDA5MjUyNTU2NDA5NTg0MDgwNDY5NDI4NzE1NDQ0MDkyOTA4NzAxNjMwMzE3NzI5MTE3NTYwMzg2NjAyNDA4OTE3Mzg2NjM4NzQ2MDY1NjU0NzQ2OTAxOTA1NjA4MjE5MzgzNzczNjg3NDcxODI3NjE2OTU5MDk3NDU4IiwiZSI6IjI1OTM0NDcyMzA1NTA2MjA1OTkwNzAyNTQ5MTQ4MDY5NzU3MTkzODI3Nzg4OTUxNTE1MjMwNjI0OTcyODU4MzEwNTY2NTgwMDcxMzMwNjc1OTE0OTk4MTY5MDU1OTE5Mzk4NzE0MzAxMjM2NzkxMzIwNjI5OTMyMzg5OTY5Njk0MjIxMzIzNTk1Njc0MjkzMDMwMzU1ODY2NDUxMDY0MDQ4Mzk4OTcyMjU0Nzk2ODY2NDA2OSIsInYiOiI4NzE4MTIwMTY2NjA3NTg4MjIxMDczMTE0NTgxNTcxMDA5NTEwNDUwMzkyNDk0MDM1MTg3MDk5MTQyNzA5NDcyMjYxMDAyMjg5MzI5MzcyMzk1MDUwMzgzOTA3ODUxODc1MjIxODU0NjI4ODc4OTcxNzQ0Njg3MDgxMDM4Mzk2Njc2MzgyMTI0NjEyNDY0NjQxMDQ4NDMxNDkwMTYzMDgwOTU0NTc3MjA3OTkxNjg3NzU0NjQ4MTYwNzM5MjQ2ODUyMjMxMjU2NTk0MTg4MTAxNDU2MjgzNjc3MTA4NTMwNjY1NDQwNTUwMDY0MjgxODI3NzUxNjA3NjM1ODE2MjczNDU3MTc4MzAyNjEyOTI5ODMyNDMzNDc3ODk0ODMzMDc2MDA5OTE4MTc1MzI4OTUzNjg1MjEwOTQ1ODg0MTQyMDg0Nzk4ODMzNzM2OTExNDcwMTkwOTYwMjI3MzAyMzI2NjQ2NjE2NjUwMjY4OTU3NDcyMTI3MTA2Mjk3NzQ0NDg3MDUzODY2NjI0NTk4Njg1MzA0MzA0OTMzNjAxNDczNjY4Njg1NDg5MzYyMzQ2NzE5ODA4MjgxNzYxNjc0ODQyMzE5NTY5Mzk1Nzk1NTQ5OTA4MjAyODI0MjgyOTc1MTI3MDA0MzM0NTkwNTYzMTI3NjU3NDM3MjQ2MDQ3OTUyOTk0OTIyODQ3MzcxMzY4NDM0OTE3MDM1Njc4ODA2NjM1OTQ0ODY4Njc5MDY1NDc5Njk1MDU5NzkyNzUyMzk5NDcwMzUzMDI3MjEyNTg2OTc1Mjk5MTk1NDcwNjY0NDMzMDIyNTQyODg4MzI4OTA0Mjg2NTIxMzM5Nzc2OTkxMDYzNzA1MTI2NjA4MDY4OTA0Mzg2MDc4NzA5NTE3NjU0OTE3MzI0NjExMzkzNTM4MDkyNTQ3NzQ0OTM2NTM1NDkwODcwNDU4NjQ3NjY2OTU3MjA5MDk4MDU2NzIwMjAzOTAxMjI3MjU2NDM5NTkwNTA0MzIwOTI3NTc1ODA2NjE0NzA4NTU3MjAxMTAxODczODc4NDg1NjM4MzQ2Nzk1NjE4NDQxOTQxMjQyODc5ODMyNjQ5ODEyIn0sInJfY3JlZGVudGlhbCI6bnVsbH0sInNpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZiI6eyJzZSI6IjIxOTkzMzcwNTI0MjIxNTM0MTM0MTc4MDM3MDIyMDEyMzE4NjQ2MDE4MTk0MDIwMjgxNzY4NTQ4OTUyNjM5ODg4MDE2NDQ1NTM2NTQ2MzczMzkwMDU5NjY1Nzg4OTc2MDE0OTUzMTU2MjA1MTA0ODU1NTM1NjEyODY5Mjg5NjgyOTQ1ODI4MDQxOTMxMzc1ODY4NjE4OTE0NjUwNTc5ODM2NzI0NDE0MDMxMjU3MjU2MzkxNjg2OTQ0NjQyMzg3NTIwNjExNDQ5ODM1NTgxNDMzMDMzOTQ4MTA4OTE0MzI2NzkyNDU5MjQ0Mzc0MTgyOTQ4MDQxODIzMTg3NjY3MjE2MDI5OTEwMTAzMTM1MjE4NjY5ODc5MDk5ODA0NTA0NjI1NTAzNDM2NTAxOTk5ODkwODIyNjcyMjYwNzc1NDIzMzIxNTQ0MDk0Mjk2NDI0Nzc2Njg2MDI1MzU2MjMwOTY0OTQyMzc3NTY0NDUwMDk4NTgyMzg2Nzg0ODc3OTQwNTI2ODg0MzgzOTcxOTE4OTE3ODIyOTkzMDIzMDU2NjU1NTg2NDI3MDAwMzQ2OTcwMTI1MjA2ODg0NTkyNzkwMDU4NTAyMzkxMzUwODIyNjg1NDYyMzY0MzE5NTMwOTI0MjM4Mzg2MjkwMDI5MTQ1ODAzNTgxODA2MDQ1MTYzNDMzNTQ2OTAwMzQzNDg0MTg2NTEyMjIzODYwODY3NTI3ODExOTkyOTQwMjMxMzgzOTM4MjI0NjEwMTk0NDc1NjczMDYyMDI3MDgwOTI5NDYzMjU0NjIxMjI1NTg3MDg0NTUyODEyMDgxMDE3IiwiYyI6Ijg2NzE3OTAzNTAxODI5MzU5NDk4MjE3NDU5NjE3NjgyNTM4NzIxNzQwMjE5MDM5MDUzNjQzNTE3NDU0Nzc2NTUzMzA3MzU1OTkxMjE5In0sInJldl9yZWciOm51bGwsIndpdG5lc3MiOm51bGx9", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, "~please_ack": Object {}, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, - "~timing": undefined, - "~transport": undefined, }, "id": "5f2b7bc7-edfd-47e7-a1d4-aae050df2c4a", "metadata": Object { @@ -230,21 +190,12 @@ Array [ "offers~attach": Array [ Object { "@id": "libindy-cred-offer-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiMTE4MTE3NTM4MDU1MjM2NjMxNjAwNjM1NyJ9", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, - "~thread": undefined, - "~timing": undefined, - "~transport": undefined, }, "requestMessage": Object { "@id": "1284ae78-f3d3-4fed-a5ff-0e2aba968c3c", @@ -252,26 +203,15 @@ Array [ "requests~attach": Array [ Object { "@id": "libindy-cred-request-0", - "byte_count": undefined, "data": Object { "base64": "eyJwcm92ZXJfZGlkIjoiUVZveGd3d25WUGtBQlRMVmNtQ013TCIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiMTkwNzM1MzQyMjkwNjk4Mzk3NzgyNTUyMTM2NTA3NzAxMDA4NzYwMzcxMjg3NTQ1MzI0NDM2NDIyMTMwNzQ3MDI3NTk4NDM2NTM5Mzc0NzM0MjgxOTk4MDY5Mjg1OTg4MzAzMDE1MzAzMTYxNjExMTEzMTY2MzQxMzkyOTkzMTk0ODUxNzM5Njk4NzcxNDYzNzMzMDA4MjUxMzQ5NjM4OTkzMDE5NTk5MTY1NDUyOTk4OTA4OTY4MDE5NTUxODM2NDg1NzYxMzMxNTgxNTY0MzgxNTkxNjMwOTcyNTc5NTkyODMyNDk3MDI0NTMyMjQxNzk4MDMzMjI1NDg4NjA5NjEwNjEzNTU1NDMxODc3NDQzODk2ODUyMzA4NjIxNDA1NjI5NjA5MTg5Nzg2MjYzMTcyODU0MjA1MTI3ODMxNjc5NzM5NTkxODQyMTYwOTAyNjczMDE4Mzc0MzE5NjUyODA3Njc2MDQzNzc0ODcxMjQzMzkzNTIwNTkwODE5MDgxOTI4NzY3MjU5NDQ2OTIxNTM2MjU3MjQ2NjIxMjgzNjc2NDM1MDIwNzUwODI4NDI2NTM2MTU2ODA5NDgwMTU3MTQ0NDkxNTY2MTM0ODYzNjU4Mjg5ODIyMTE1NjI4MzMxNjMxMTQ3ODM1NzQ4MjAxMzkyNjY4NzQyMTQ5NTI1OTQ0OTc1NzY3NTYwMTQyNzQ5MTU3MTY2NzE0MDY0OTM2OTQ1MzEwMzEwMzU1NjgwNTcyNDgzNDgyNTYyMzk5Nzc0OTY5NTYwMTA1Njk2MzczMDU4MDMzODgyMTAwOTY2ODUwMTk5MjEzMzAiLCJ1ciI6bnVsbCwiaGlkZGVuX2F0dHJpYnV0ZXMiOlsibWFzdGVyX3NlY3JldCJdLCJjb21taXR0ZWRfYXR0cmlidXRlcyI6e319LCJibGluZGVkX21zX2NvcnJlY3RuZXNzX3Byb29mIjp7ImMiOiI1MDg3Mzk4NDExNzQ3Mzc5NjY5MDkyNzU5ODQ5OTEwMDI2OTYzMTk2NjExNjc5MzU5NDYwNDMxMjYyMDE4NzgyNzY4NTM2NTUzNzUwMiIsInZfZGFzaF9jYXAiOiIxODU0NzEwMDA5NDM4NTg5MTc4MzkzMjgzMDk1MzM5NDUzMDQ3OTkwOTYyMjE2NzEyNzk2ODkyMzcyMzA5NjU5NTU3MDY2MzQxMTMxOTY0Mjg5NjA3MTI5MDg4MjMxMDY0NTk5ODY4NDg4MTIzNDMwMzY5OTkxMjI1OTMxMTIyMjY4NjU5MDEwMDA0NDA1OTIzNTcyMzgyMzQzODczNjkxODg3NDQzMjQ5MTcwNTQwNDk4Nzk5MTkxOTIzMjc4NDU4MzcyMzk2NzIyMjM5NDE1NjY3ODIxMDQ4ODA0NDk1NDQ5ODQ3MjcwOTg1MDcwNzY3NjU2NDU4NDM0MTYzNTI3NDAyMzA1NTg5MTg4NzcyNDg4NzE1NjcwOTgxNTc0NzQxMTI1NzYxOTIxNTE3NzYyNzg1Nzk4MjU5MTQ0OTYzMDQyMjg0NzUwMDE5MjAwMjQ4NjgxNjkxOTE5Njg3MDA1MDA4MDUzMjYwODY5NzEyNTEwNzIwNDg5NDAwMjM0NDU3Njk2MDk4NjI0Nzk1MDUwMzQ2NzM2Mjg1MDE3MTU5Mjk1OTA0NTU1NTk0MzMxNzI4MTQ5MzgyODE5NTI2NTc3MDg3NjA0ODMwNDk3MTE0Mjc3MTkyMDU3ODk1MzYxNzI5NTE3NTgxNzg5ODEyMDY3MjcxNTU5MTMyNTI1MzEzNTc0MDEwNTM3NDMxMDY2NzUwNzAzMDgxMTQxNDIyODg5MzUxOTY0MDUyMDU0NjY0MTA0ODE0MzY1Njg3NTcxNjU5MTk3ODQxMjU5NjE3OTI4MDg4NjM0ODY1NTI2MjI4NTkyNTI2NTgwODgzMzIzNjEwNTc5NzU4ODgzMjgwNDcyNTA0OTQ0MjM2ODY5MTYyNzM3NzUwNTI0NTIyMjE5NTM4NjE4OTk2ODQzODU3MzU3MjUxODI5NTIyODgxOTA0NTAwMDU2MjU1NTMwNDgyIiwibV9jYXBzIjp7Im1hc3Rlcl9zZWNyZXQiOiIxMjM5OTQ3ODE4MDA1MTQ3MjQ5MjcyODIxNDI2OTgwNjQ2NTIwMjAwMTc0MzUwMzkzMzQ0NzU1NTU0NDAxNjg1NzQ2NzExMzkzMjEzMjA3NjI3ODI5MTczMjc2OTA2MDg4MDAxMDIxMTMxMzY4NzY4MjI0ODgxNTExMjEwNjY0NzA5OTAxOTQwODA0MzA0OTc1NTUyMzExNzAyMjU3MTYwOTAxNTE0MzIxNzYyNzM0ODUwMSJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiIzNzM5ODQyNzAxNTA3ODY4NjQ0MzMxNjMifQ==", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, - "~timing": undefined, - "~transport": undefined, }, "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", @@ -308,26 +248,16 @@ Array [ "credentials~attach": Array [ Object { "@id": "libindy-cred-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFnZSI6eyJyYXciOiIyNSIsImVuY29kZWQiOiIyNSJ9LCJkYXRlT2ZCaXJ0aCI6eyJyYXciOiIyMDIwLTAxLTAxIiwiZW5jb2RlZCI6IjQxMDYxMjkzNzgwNDYyMDc1NTE0MjA4MjIzODk3NTkwNDA3ODAwNzQ4NTQzMDE0OTkxNTg4NTIyNzIxMTkzODg2ODk4MTE5NzUzNjI0In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6IjEwMzE4MTcwNjEzMDMzODg1ODkxNDkzMzk2MjU5NDYyNDIxMzM3MjY3ODcyNzkyNzczNjgwMDQwMTgyNTE4NDM1MzEzMDYyNjE3MTcxMCIsImEiOiI3MTM3NDExNDU3NjI3MDE5MDcyNjY0ODMzNTQ5MjAzMDQyMTg0MDQ0OTUxNTU5MzYwMDczMzk1MjM4NjkwMzkwNjgxNzA3ODAyNzY0MTE2MzQ4NjE4NjgxOTgzNTM2MTczNTE3MzgxODc5NzQ0MzQyODIzMDkzNzc4MTk1NzYxNzgzMjQ3NDcxOTQ2NDgwODc0OTY2OTQyNjY0NzU4NjIwNzEyNzExODExNTY5NTc1NzMwNTg4NTQwNDI1MjEzNjY0OTg0OTQyOTIzODU5NzQ3MjIzNjA5ODQ1NjIwMjE5NTY1NzYxODY2OTMwMzI3OTYzNTIwNzE5MTg2NjMyNTQ4NzkzNDI3MTQ0NTY0NTAxNjE1NTg1MTI2NzkxNzM5Njg3ODc2MzIxMTQ1NTAzNDU1OTM0MzUxODc0MjQ3NjA0NzAwODYxMTgxNjY4NjUxNzQ5NTExMjY0MzExMDU2NjI5MjM4NjQwNDY2NzM4ODA0NjI0NzU1MzEzODgxMjQzMjkwNDM5ODI4MDE0NzY0MTQ3NDM1MzE3NTY3MjY3MzQ2NTQxMTY2ODIwNTI2MDkyMDAyMDE0NzY5NjE1MzI3Mjg4MTYwMzM0MDg1MTQ1MzQ0Mzc5MzAxMDg1NDc1MDEyNzUzNDIzNzkxMjI4NzM5NDE3MzA0OTM2NDUzNDEyMDYxNjY0MTUzNTM4MjM5MDA0OTUxMDgyODQxMTE1MTIyNDQ1MzkzNzc5Mzc5NDI5NjQzMjMxNDE2NDQzNDM4NDQ1NzI1NTAzMDc0MTM5MzEwOTc2MjkwMTQ2MTIwMDI2MDI1NTczNzIyNTUyOCIsImUiOiIyNTkzNDQ3MjMwNTUwNjIwNTk5MDcwMjU0OTE0ODA2OTc1NzE5MzgyNzc4ODk1MTUxNTIzMDYyNDk3Mjg1ODMxMDU2NjU4MDA3MTMzMDY3NTkxNDk5ODE2OTA1NTkxOTM5ODcxNDMwMTIzNjc5MTMyMDYyOTkzMjM4OTk2OTY5NDIyMTMyMzU5NTY3NDI5MzAxNTQ3NTU5NjM2MjQ0MTcyOTU5NjM1MjY1ODc1MTkyNjIwNTEiLCJ2IjoiODMxMTU0NzI2ODU4Mzg3ODc5NTU5NDUzNzcwNjg3MzIyMzE1NTgyMjYzMTk3NDUzMjMxNTI1ODIzNDIzNjkxMDk5Mzc2NTExNzI0ODAxMzA1MTU0NzY2Mjc0NjQ1OTMzMTAwOTIzMjIxOTgwNDkyNzE0NDQxMTY0NTYxNTIwMDIzMjYzMDYzMzQ4NjQ4NzkzMjkxMzEwNDc0NTU5NDIwMTkzMTA1MjE5NzMxNTAwMTc0ODg0NzQ0MDk1MjU3MDYyMjczODA2OTYzNjg5MjY3NzA1NTg4NTQ4MzU4NzQ2NDc1Mzc1MzAzMjI1OTI3MDkxMzA0NzQ2NTg5MzA1MDEzNjc1ODk0MzIxMDkzNjE0NzIxMjQwNDAzNDE5OTM5OTk0Mjg5NTU2MzY0MDExMTg2ODQ2NjMxNTA2OTU0NDg0NjM4NjgwMzEyMDA2Njk0MjcwOTkwNDU3NTk3NjAyNzc5MjUzMDc3MTg3NDgwNTg5NDMyNzU4ODgwMjY3NzA1NzMyMjg3Nzc0ODczOTI3MDExMTQ1MDE1NzgyNjE5NzI4NTAxNjI5MTE4ODE1ODM2NjU2OTMzMzcwMzgwNDk4NDk5MDE0MDEzNDI1NDMwMjMwODQzODc0OTk3NTg0NTY3NTA0Mzg3OTE4MjQxMzMxNTM1NDk5MTQxMjU1NzQzNjQ0MzgwNTQ4ODAxNDUwNDEyMzQzMTAxODc4Nzg3NzIzOTIxMDU5NjQyNDg2MjE3NjE0MzQyMDc0MTQ3ODk1ODA2MzQ1NTQ0Njk3NjI2MzcwMDY1MjYxMjQ3OTM2NzMwMzMyNTkyMjM3NDY1MjIyNTQ1MjE4ODc4MDk0ODk0NTE0ODU2ODUyNTU1NjI4MjIwNDg2MTU5MjcyMjIxMjM3MDkwMjc4NjUyNDM2MzcyNTE4MDgzOTUxNjAwNzI1ODA1MDYwNjExMTkyNzEyOTI3MjQ2MTUxMDU4NzU5NDk2MzQ3NjA0NDQwNDcxODcwODEyNzMwODE3MTU4NzQxNDQ1MDA0OTg2MTgyNDk5NDA4OTQxMzk2NjcwMzEyOTU0NDQ1MTk1NTM5MTM5MzIwMDI4MTI3NzMyMDQ0MSJ9LCJyX2NyZWRlbnRpYWwiOm51bGx9LCJzaWduYXR1cmVfY29ycmVjdG5lc3NfcHJvb2YiOnsic2UiOiIyOTQ0ODU2NTEyNDk3MjM2MTc2OTQ3OTMxNzM1Mjk0NDc4MTU5NTE2ODM4OTExNDEyMDE3MTI1NTAyNjc1ODM2Nzk1NTQ4OTczNDMyMTg2NjI0MTc2NjQwNzAxNjczOTk4MjI3NTEzMTA0OTU5OTgzMjk1NjI0MTA0MjkwNjgzMjU0OTI4NTg1MDkwMTI2Mjk5ODczMjY4NDYyODA5NTY4NjMxNDg3MDk2ODgzMDE0OTcwMTcyMzM0MzIwMTM4OTg5ODkzMzcyODIyODU2MTQxMTM5NTQ0MzQyMzExNDEzNDgyMDU0OTYyNjE5NTgzNjU5NjMwMzYxNTc3Nzg0MTQ4NjY3NTgwNjMxODc4NDIwNTgzMDk5OTM1NzE0NDYyMzE5Njg4NDE5MTM4NjY3Nzk2Nzc2MzQwMDk2MDIxMTgwMTU0ODU0Njg1MDEzNzY1MTM0ODMwNjA0MzEyMjI0MjIwNzA5MzQwODExMTI4MDczMDk3NjEyMDA0MTE4NjA0MDI3MTczNjExNTE2ODA0NTQxMzUzODA2OTkxMTg0MDY3MzY1MDE5Nzk2NDM2MDA3NDI0NTM5NzQ4ODQxMjU4NjA1MTUxNTQxNzQzMDk4MTE5NzI1Mzc4MTQ4MTgyNDQ2Njg3NDQ0MjE0ODk1NTE2NDc3MTM4Mjk1OTI1Mzc4Nzg1MTA3OTc5MTYxNTIyNTYyNDk2Njg2MTAzMjg3MDUxNDUyMTE3NTQzMzc4Mzc0MDM5Mjg3NzgxMjAwNzgxMTkyNDQyOTY5MDU1NjIwNTcyODg1NDM5NjU2MTU3Njk2Mzc5MTAyNDc2MTQ2IiwiYyI6IjI4NjYxMjE3ODQ5OTg3ODI4MTA2Nzk5MTAxOTIxNDcyNzgxNDMzNzgzMDE5Mzg0NzAwMDUzMjU4MTU5NDUxNjc5MjMwODI0NzA5NjIifSwicmV2X3JlZyI6bnVsbCwid2l0bmVzcyI6bnVsbH0=", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, "~please_ack": Object {}, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, - "~timing": undefined, - "~transport": undefined, }, "id": "ad644d8a-48a2-4c55-b46d-7a7f1a9278c7", "metadata": Object { @@ -362,21 +292,12 @@ Array [ "offers~attach": Array [ Object { "@id": "libindy-cred-offer-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiNTQzODYyOTczNzUxMjk1OTk2MTAzNjA3In0=", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, - "~thread": undefined, - "~timing": undefined, - "~transport": undefined, }, "requestMessage": Object { "@id": "edba1c87-51d3-4c70-aff2-ab8016e1060e", @@ -384,26 +305,15 @@ Array [ "requests~attach": Array [ Object { "@id": "libindy-cred-request-0", - "byte_count": undefined, "data": Object { "base64": "eyJwcm92ZXJfZGlkIjoiRUg2OTVENjRRd2hWRmtyazFtcDQ5aiIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiOTcwNjA5MzQ1NDAxNzE0NDIxNjQzNDg0NDE0MTQwOTA0NzMzNTQ4NTA4NzY0OTk5MTgxNzY1ODI3MjM3ODg3NjQ2MzQzNDYyMTY0ODA3MTUxMjg1ODk2MDczODAyNDY1MDMwMTcxNTIyODE4ODY4Mjc2ODUwMzE0NzQzMjM3ODc3NDMyMDgxMTQwMzE5ODU5OTM5NjM0MTI4NzkzOTk4NDcwMTUzNTQ0NjgxNTM5NDg4NzEyNDE5MTk3NzcxODE1NjU5Nzg1NDE5NTA1ODEyODI4NzYxOTI4MzExODczNjA5NDYwMjExMzQ4OTAyNDk4NzYxNjc5OTIzNzA2NjUwMDIzODg4ODU4NzE1MTYxMTIyMzA5MTc1MTE3MTk0NDMwNTI1ODY5NjcwMDEzMTgxNTkzNjI4NDQzMjk2MDI0MDE5NTc4MzIzODQwNzk0OTQ0MjIyODE1MTQwNTM2Mjc3ODQwMjU2ODc2MDE1MzUwNDgzOTE2MjYzMDgyODM5NzI2NzAxNjg4NjcxNDY0MjA3MzQxMTgwMjg3Mjk0Njg4NDA3NzQ3MDk1NjA0NzQ3NzA3OTc2Nzk2MjU0MTQ2NDQ0NTY5NzQ4MTk4OTMwMjkyOTkzNjY1ODk2MTUyMDMyNzQwODY3OTgwMjczMTMxMDM3NjkwNDkzNDU1Mjc4NDc3MDc3NjE0OTU2NjgzNjgxNDc5NzY3Njg0MDI4MzU5NzE4NzM0ODEyNzcyMDIyOTIwODQ3NDIyNDYyOTAwMjczMTcwMTU2NzQyMzUyMDQ2NDYyODI4NzAxMTE2MzU0MTkwMDY5MDE0NTIwMSIsInVyIjpudWxsLCJoaWRkZW5fYXR0cmlidXRlcyI6WyJtYXN0ZXJfc2VjcmV0Il0sImNvbW1pdHRlZF9hdHRyaWJ1dGVzIjp7fX0sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6IjE4MzE5MTUyNjg0NDkyMTM0OTc4MzkzNDE3OTY5NjQ5MDIyNzMzNzQzMTQ5NTUwNzAwNzc5ODk0Nzg1MTg3MTA1OTkyNjk4Mzg5MjAzIiwidl9kYXNoX2NhcCI6IjQ0NzA4MzAwOTUyNzA3MjA3NjI3NjA3NzM4MTI2NDgxNzA3OTA1MDcwNjEyMzQ5OTIxNTAxNTYyOTA2MzgyMzE0MDE4MzQ4MTAxMTE4MDI4MDIyMjk5OTgyNTEwMjI5ODM1OTQxMzY1MTM4Njg0MTU1OTEyOTE3NzYwMjgwOTIyNDk1MjE1ODA2NTM3MzA5NDU5NDA3NjcxNDgyNDA0NDMwODU4MzU5ODU3MDgzNzg1Njk1MTYzMjkwMzEyNzMzNDAxNjY1NDk5MjUwMDQ0NzkwODk4OTA4NzIzNzE1OTc0MDYwNzgyNDEzODYzMTU0MTUxNjg2OTM3ODY4MDM5MDU1Nzc1MzA5MjQ0MTYzOTUxNzgwMTgxNDk5MDM5MDgyMjcxNzgzNTgzNTkxMDIyNjYwMDYyMDQ3MDQ2NTEyMTM0NDU5OTI1MTgyOTg2MTkxOTAwNTQwMDg4MjE3Mzc2NjM4MjEzNTI0MDUxNjcxNzg3ODY0ODQ2NzIxNjk5NjQzNDk0MTI2MjA3NTg2MjgwNjQ5OTc4ODE2ODEwMjM5OTAzMzU0NzIyOTI2NTUxODYyNTQwMjc4ODU2OTEyNDQ2MDUzMTg4MzI3Mjk4NDc0NjgzMjkwMjU4MjgwNjY0OTgyOTM2NTYyODcwNTIyODA2NzYwMjE1OTI0MDc3ODQ2NjA2NTM0NzI4MjkyODQ2MDQyOTk1NjgxMTQzOTQ5MTU0MTU0NTU2NzQzMDYzMzY1OTIzMzU2OTg1MjQ5ODI1Njk2NzE3MzE2MDk1NzE5MDU2MTE5NTE0NTYwODY0MzUyMDc4ODMyOTYzNjI3Mjk0Njk1ODQ0MTE5NDA1NTMzNTY5MTI2ODExODE3NDYyNjczMzM3OTg4MzA0MDcwMzk5ODYyNTk2ODMyNDk2OTU3NzA4Nzc0NzI5NzAyOTEyNjY3Njk0OTgwMjc3OTI5MDgzNiIsIm1fY2FwcyI6eyJtYXN0ZXJfc2VjcmV0IjoiMjIxNTAyNTExNTYzODg1MTM1NzIwNjg4MzE5Njk5NzE5ODAxNDI4NDgzMTI2MjY0NjI0NTE5OTA4MjM5NTM0MjQ3MDQ3NTIwODgyNDE4Mzk0ODMzNjUwODM2NTI0NDk2MzUzMDkwNzIzOTI1MDc2NDY2ODQ3NjIxNzE4MDA4MDAxNTQyNTMzOTk4NTU1MzA1MDYwNjUzMzkwMjc4MDc2MzE2Mjg5MzcwMzA2MDcyMDYxNCJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiI2OTgzNzA2MTYwMjM4ODM3MzA0OTgzNzUifQ==", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, - "~timing": undefined, - "~transport": undefined, }, "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", @@ -442,26 +352,16 @@ Array [ "credentials~attach": Array [ Object { "@id": "libindy-cred-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsInJldl9yZWdfaWQiOm51bGwsInZhbHVlcyI6eyJuYW1lIjp7InJhdyI6IkFsaWNlIiwiZW5jb2RlZCI6IjI3MDM0NjQwMDI0MTE3MzMxMDMzMDYzMTI4MDQ0MDA0MzE4MjE4NDg2ODE2OTMxNTIwODg2NDA1NTM1NjU5OTM0NDE3NDM4NzgxNTA3In0sImFnZSI6eyJyYXciOiIyNSIsImVuY29kZWQiOiIyNSJ9LCJkYXRlT2ZCaXJ0aCI6eyJyYXciOiIyMDIwLTAxLTAxIiwiZW5jb2RlZCI6IjQxMDYxMjkzNzgwNDYyMDc1NTE0MjA4MjIzODk3NTkwNDA3ODAwNzQ4NTQzMDE0OTkxNTg4NTIyNzIxMTkzODg2ODk4MTE5NzUzNjI0In19LCJzaWduYXR1cmUiOnsicF9jcmVkZW50aWFsIjp7Im1fMiI6IjEwMzE4MTcwNjEzMDMzODg1ODkxNDkzMzk2MjU5NDYyNDIxMzM3MjY3ODcyNzkyNzczNjgwMDQwMTgyNTE4NDM1MzEzMDYyNjE3MTcxMCIsImEiOiI3MTM3NDExNDU3NjI3MDE5MDcyNjY0ODMzNTQ5MjAzMDQyMTg0MDQ0OTUxNTU5MzYwMDczMzk1MjM4NjkwMzkwNjgxNzA3ODAyNzY0MTE2MzQ4NjE4NjgxOTgzNTM2MTczNTE3MzgxODc5NzQ0MzQyODIzMDkzNzc4MTk1NzYxNzgzMjQ3NDcxOTQ2NDgwODc0OTY2OTQyNjY0NzU4NjIwNzEyNzExODExNTY5NTc1NzMwNTg4NTQwNDI1MjEzNjY0OTg0OTQyOTIzODU5NzQ3MjIzNjA5ODQ1NjIwMjE5NTY1NzYxODY2OTMwMzI3OTYzNTIwNzE5MTg2NjMyNTQ4NzkzNDI3MTQ0NTY0NTAxNjE1NTg1MTI2NzkxNzM5Njg3ODc2MzIxMTQ1NTAzNDU1OTM0MzUxODc0MjQ3NjA0NzAwODYxMTgxNjY4NjUxNzQ5NTExMjY0MzExMDU2NjI5MjM4NjQwNDY2NzM4ODA0NjI0NzU1MzEzODgxMjQzMjkwNDM5ODI4MDE0NzY0MTQ3NDM1MzE3NTY3MjY3MzQ2NTQxMTY2ODIwNTI2MDkyMDAyMDE0NzY5NjE1MzI3Mjg4MTYwMzM0MDg1MTQ1MzQ0Mzc5MzAxMDg1NDc1MDEyNzUzNDIzNzkxMjI4NzM5NDE3MzA0OTM2NDUzNDEyMDYxNjY0MTUzNTM4MjM5MDA0OTUxMDgyODQxMTE1MTIyNDQ1MzkzNzc5Mzc5NDI5NjQzMjMxNDE2NDQzNDM4NDQ1NzI1NTAzMDc0MTM5MzEwOTc2MjkwMTQ2MTIwMDI2MDI1NTczNzIyNTUyOCIsImUiOiIyNTkzNDQ3MjMwNTUwNjIwNTk5MDcwMjU0OTE0ODA2OTc1NzE5MzgyNzc4ODk1MTUxNTIzMDYyNDk3Mjg1ODMxMDU2NjU4MDA3MTMzMDY3NTkxNDk5ODE2OTA1NTkxOTM5ODcxNDMwMTIzNjc5MTMyMDYyOTkzMjM4OTk2OTY5NDIyMTMyMzU5NTY3NDI5MzAxNTQ3NTU5NjM2MjQ0MTcyOTU5NjM1MjY1ODc1MTkyNjIwNTEiLCJ2IjoiODMxMTU0NzI2ODU4Mzg3ODc5NTU5NDUzNzcwNjg3MzIyMzE1NTgyMjYzMTk3NDUzMjMxNTI1ODIzNDIzNjkxMDk5Mzc2NTExNzI0ODAxMzA1MTU0NzY2Mjc0NjQ1OTMzMTAwOTIzMjIxOTgwNDkyNzE0NDQxMTY0NTYxNTIwMDIzMjYzMDYzMzQ4NjQ4NzkzMjkxMzEwNDc0NTU5NDIwMTkzMTA1MjE5NzMxNTAwMTc0ODg0NzQ0MDk1MjU3MDYyMjczODA2OTYzNjg5MjY3NzA1NTg4NTQ4MzU4NzQ2NDc1Mzc1MzAzMjI1OTI3MDkxMzA0NzQ2NTg5MzA1MDEzNjc1ODk0MzIxMDkzNjE0NzIxMjQwNDAzNDE5OTM5OTk0Mjg5NTU2MzY0MDExMTg2ODQ2NjMxNTA2OTU0NDg0NjM4NjgwMzEyMDA2Njk0MjcwOTkwNDU3NTk3NjAyNzc5MjUzMDc3MTg3NDgwNTg5NDMyNzU4ODgwMjY3NzA1NzMyMjg3Nzc0ODczOTI3MDExMTQ1MDE1NzgyNjE5NzI4NTAxNjI5MTE4ODE1ODM2NjU2OTMzMzcwMzgwNDk4NDk5MDE0MDEzNDI1NDMwMjMwODQzODc0OTk3NTg0NTY3NTA0Mzg3OTE4MjQxMzMxNTM1NDk5MTQxMjU1NzQzNjQ0MzgwNTQ4ODAxNDUwNDEyMzQzMTAxODc4Nzg3NzIzOTIxMDU5NjQyNDg2MjE3NjE0MzQyMDc0MTQ3ODk1ODA2MzQ1NTQ0Njk3NjI2MzcwMDY1MjYxMjQ3OTM2NzMwMzMyNTkyMjM3NDY1MjIyNTQ1MjE4ODc4MDk0ODk0NTE0ODU2ODUyNTU1NjI4MjIwNDg2MTU5MjcyMjIxMjM3MDkwMjc4NjUyNDM2MzcyNTE4MDgzOTUxNjAwNzI1ODA1MDYwNjExMTkyNzEyOTI3MjQ2MTUxMDU4NzU5NDk2MzQ3NjA0NDQwNDcxODcwODEyNzMwODE3MTU4NzQxNDQ1MDA0OTg2MTgyNDk5NDA4OTQxMzk2NjcwMzEyOTU0NDQ1MTk1NTM5MTM5MzIwMDI4MTI3NzMyMDQ0MSJ9LCJyX2NyZWRlbnRpYWwiOm51bGx9LCJzaWduYXR1cmVfY29ycmVjdG5lc3NfcHJvb2YiOnsic2UiOiIyOTQ0ODU2NTEyNDk3MjM2MTc2OTQ3OTMxNzM1Mjk0NDc4MTU5NTE2ODM4OTExNDEyMDE3MTI1NTAyNjc1ODM2Nzk1NTQ4OTczNDMyMTg2NjI0MTc2NjQwNzAxNjczOTk4MjI3NTEzMTA0OTU5OTgzMjk1NjI0MTA0MjkwNjgzMjU0OTI4NTg1MDkwMTI2Mjk5ODczMjY4NDYyODA5NTY4NjMxNDg3MDk2ODgzMDE0OTcwMTcyMzM0MzIwMTM4OTg5ODkzMzcyODIyODU2MTQxMTM5NTQ0MzQyMzExNDEzNDgyMDU0OTYyNjE5NTgzNjU5NjMwMzYxNTc3Nzg0MTQ4NjY3NTgwNjMxODc4NDIwNTgzMDk5OTM1NzE0NDYyMzE5Njg4NDE5MTM4NjY3Nzk2Nzc2MzQwMDk2MDIxMTgwMTU0ODU0Njg1MDEzNzY1MTM0ODMwNjA0MzEyMjI0MjIwNzA5MzQwODExMTI4MDczMDk3NjEyMDA0MTE4NjA0MDI3MTczNjExNTE2ODA0NTQxMzUzODA2OTkxMTg0MDY3MzY1MDE5Nzk2NDM2MDA3NDI0NTM5NzQ4ODQxMjU4NjA1MTUxNTQxNzQzMDk4MTE5NzI1Mzc4MTQ4MTgyNDQ2Njg3NDQ0MjE0ODk1NTE2NDc3MTM4Mjk1OTI1Mzc4Nzg1MTA3OTc5MTYxNTIyNTYyNDk2Njg2MTAzMjg3MDUxNDUyMTE3NTQzMzc4Mzc0MDM5Mjg3NzgxMjAwNzgxMTkyNDQyOTY5MDU1NjIwNTcyODg1NDM5NjU2MTU3Njk2Mzc5MTAyNDc2MTQ2IiwiYyI6IjI4NjYxMjE3ODQ5OTg3ODI4MTA2Nzk5MTAxOTIxNDcyNzgxNDMzNzgzMDE5Mzg0NzAwMDUzMjU4MTU5NDUxNjc5MjMwODI0NzA5NjIifSwicmV2X3JlZyI6bnVsbCwid2l0bmVzcyI6bnVsbH0=", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, "~please_ack": Object {}, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, - "~timing": undefined, - "~transport": undefined, }, "id": "c7e0a752-7f1c-41c0-b0ae-a68c2d97ca8c", "metadata": Object { @@ -504,21 +404,12 @@ Array [ "offers~attach": Array [ Object { "@id": "libindy-cred-offer-0", - "byte_count": undefined, "data": Object { "base64": "eyJzY2hlbWFfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjI6c2NoZW1hLTgwZjdlZWM1LThlNWEtNDNjYS1hZDRkLTMyNzRmYjkzNjFiODoxLjAiLCJjcmVkX2RlZl9pZCI6IlRMMUVhUEZDWjhTaTVhVXJxU2NCRHQ6MzpDTDo2ODE6ZGVmYXVsdCIsImtleV9jb3JyZWN0bmVzc19wcm9vZiI6eyJjIjoiMTEzOTY4MTg4OTM2OTQ5MzcyNzU3NjU2NzI1MjU1MTQ3NDk1OTI5NTM0MjQ5NjU1MzY4NTMzMTY4OTIzMjU4NTA2OTUzOTk3MTI2MDEyIiwieHpfY2FwIjoiMjM5NDkwMjQ4MjE4MTExOTQ1MjIxOTQ1ODcyMTE4MjQzNzA3NjE5OTQ4MzQ0MjU1ODM5ODI4NTU3NjkyNTE3NDExNDMwNDgwNDgwMTkxMTMwMjM0OTg5ODk0NzIyNDE2Nzk1MzUzODAwMDk3NDUxMjI5NDE4MzQ0MjEyOTI3NDk1NjI2NTc4MTk2ODUxMTcwMTI0MDI1NDk1MDExMjc0NjU1NjQzNjkzNTE1ODczMjA5OTczMzgwNjA3MzQxNzQzMzIwNTY0NjkwMzcxOTgyNDIxNTQyMzQzMTMzNTMxOTcxMTk4NTA4NDk5MjYyNzUxNDMyODg0NzgzMzc1MTAzODI0OTE3NzEwODAxOTE3OTc1OTM2OTg4OTIwMzYyMzA5NjE4NTgxMzY0ODE5NTA5ODYxNTE3NjI2ODc3OTUzMzMzMTkzMDExMjA2NDA5NDQ1MzA0MzEwMjUzMTU5OTE1NzYxOTI1MDY1MTg3Mjk1OTg1MzU0NDY1NjY5MTMxNjgwMzc5MzY0ODk0Mzc3NTYxODcyMjcxNDY5Mjk1NzY4MTc4NjQ2NDMxODI3NjI1MTQ3Mzk4MDg1ODI3NTUzMDAzNjIyMDM1ODM1MDg2NzE1NjgyMDA5MzgyNjgxNDc3NDc4ODQ0MDEyNDQ5NTE2NjYwODMwNDMwODQ5ODMxNjAxNDk3MTk3MjczMTIzNjg1NTE0NDMwMjY5OTkxMzMzNDI1Nzk0NjAwMzc3Mzk3NDMwMjg0MjIyMjQ1OTgyMjI1NTE3MjQ4NzA5NTczMzEwNjM5NzQyNzc2NjMyMzM3MDM0Nzk4NDY3MTAwNDczNDUxNTMzMTg1NDg5NDU0NjUyNTgwMjcxNjgyMDQzOTc4MDY4NDc4MjM1MjM5NjMzMTk0MzE4NDcxNDM2MjMwOTg1NTQ1MzAyMDQwNiIsInhyX2NhcCI6W1siZGF0ZW9mYmlydGgiLCIyNjMyMjI0MDEyMDg0NjM3ODA0NjA4MDE5MTM2MzIyNDkzMTE1MzA1MTA2ODQ1MTA0NTE4MDE4MjY2MjU1NjMyNDM0MTMzODY0MTIwNzUxNDkyMDAyMTI4MzU3NTcwMTY4MjE0OTU3OTgzNTQ3Mjk0NTczOTExMzk3MTQwNDAxNDk4MzQ0NzE0NTA5MjEyNDA2NTYzNzgyOTc4ODUzNDgzMTM4NTA1NTA3OTcxNTY3MTgyOTQ1ODQ5ODI3MDU0Nzg4NjA3ODgwOTU5NDE1MTYwMjU0OTE2MDExODkyNTIzNjUzODA1NTk2MDQ3NjA5Mjg3ODA4ODg2MzcwOTM5NDA3Njc5NTE3MjUzOTUxOTUzNDgxNDkzOTMzNDI3NTA5OTUwNTY5NjUwMTc4MjQwNTg0ODI4NzgzOTAxOTA0MjI3MzE1OTEyNzQ0NDYyODc3NDI3Njg3MDEzMDkyNDY1NTc5NzY0OTk2MTc2NTU1NDg1Nzc2Njc0NTcxMjcwNjU4NjAyMTk3OTExNTYzMjY0MzEyNTgzNzMyNTE3NjI3MjY3MzQ1MzEwODcwMjk2NTk4NTEyMDc0NzczNDQyMTExNjY4NjM0MzYzNjkzMDkxNzQ2MjE1MDQwODg1NTcyMzAzMDU4MDUwNzc3Nzg4Mzc5NDY3MzMyNDUwOTY5NTg1NDE2NzU2NzA2MzcxMzMyOTk5MDg1NjM5MDU4Nzk4ODE4MjkwNzEyNDk2NDk0NTY1MzgxMjI0NzUyMTA2OTQyMTg2OTc3MzUwNzE2NjI0MTYxMjIwNjExMDY3MzcxNTM0NjYyNTc0MzY1NTM0MzEzNjAwMTQyODk2MDkwMDQ3MTI5NjQ3OTEzOTgzMjk0Mjc2MDI2OTA2MTA3NzM4MDM2MjI2MTA4NzU0MTE3OTIwMTg2NDM1MDkwOTU2Il0sWyJuYW1lIiwiMzE2Nzc0MzUwOTgzMjI4Nzg1MDcxNDg1NjYzOTM0MTYyNjQ1MzE2NjQzMTc5Njg2NTg2MTU2MjgwMjg3Njg5Nzg0NTI1Mjg5MzY0NDg5NDMxNjEwMTc1MzUwMDgzMzUzMTg3MTIwMTE1MTU1NjUxOTYzMjcyODM4MjcyMTgyNTI2MzUyNjMyMzI5MDY2NDIxMjM3OTM3NTc4MjY4MjkwNzU4MDgwNjI0MjE3MDM0NDU5MDUyMjY2Njk5NjQzMTg2MjkyNjcxNDk1ODEwMDU4Mjg5MzA1MjI4OTQ2MTQ2MTkzMDAzNDk0NDgwNzY0NDY5Nzc1NzM5Njg0NzcxNjMyNDIzNTM5MjE1MzIxMjc0NTQ4NDU5NzE4NTM3NTMzMzE0MjYwMjcwNzE0MTkzNjc4MTEzMDg5MDUxODI1MjA2MTAyMjQ0MTc3NzAwNDk3MTYxMDM2NjgwNDYzMDUxNDcxNDk3MTgzNzc3Nzc5Nzc2Nzc0MTUzNzQ1NjEwNzc3NzgyMDYyODA3NjY5MjE2ODA2NDgxNzAzNTU5NDk5MTAyNTc5ODAwNjgxNTQxMjg3MTk0MTAwNzg4MDMxNDE3Nzg5MzQyNjk2NTQyNTA3NjE5MTgzNDIyMTc0OTk5NzQ2ODM3NjY4NTg0Mzk0NzQxNzI5MDY2MjgwNjYyMzAyMDI3NTczMDgwMDY5NTE5MDgxOTA5OTA3MzQzODAxNzg3MjgyMTEyODY2NzkwNTI2MDIwMjk4NjM2ODY3Njg2NTE5MjQ0NTg2MTg1NzMxMTE0ODk1ODU3NjkzNTQxMTIwOTc2NTQ5MzYwNDE1MTkxMjI4ODA4MzE5NTcxOTU4NTkxNDc4NDYwNzMwMTg2NDQ4MTU3NjU3OTkyOTI4NTM2MjgxODU2NjAzNjU5NjM3OTE2NzE0OTk0NTU0NSJdLFsiYWdlIiwiMTY5MjgwMDQ2OTQyNDI3NDY1ODE5OTE3MzEyNTkzMzEyMzkyNDYzMDQxODc5MzIwMjM0NDU4NjQ4Mzg2MzI4MTE5MDQ5OTIyMzc1NjkxNzI0ODYzMDM3NDMyODU4OTQ4MTIyNzI2ODQ5NDU5NDcxODA2NTU4NzYyMzU4MTgzMzU3OTYwNDk3NTMyNjUzNzE4ODE0NzQ1NzY2ODc3OTI2NzU2NTQ3NjQwMjUzMTczMDYzMjY0Mzc0OTAzNTgwNjEwNDMxMTM2NTA2NjQ1NjE5NzYzNTE1Nzc3MTkyNjU4ODk0OTc1MDMyODAzNzM0MTE5MjM5NjcxMjgwOTQyOTkxMTg2MjYyNjYzNTM5NDU3ODc1NjY1NDcwMTAxMTEzOTUyOTY2MDQyMzU4NDQ4ODE1NTk5MDgzNTU4NTIyMDQ1OTI3NDI0NjI5Njk4MTgzMTUzNzUyMDA4MzM5NTI1NTYxMDI0ODg2MzUzOTc3NzA1ODE5Mjc1MzQzOTg3MzMzODMxNjU0NzA4ODI3NDI0NzMzNzcyNjI3MTA2OTgxNjE5NDY0MzUwNDU3NzE4NzM2MDA0NjEyNzQ0OTAyNDA5NjA0Njk4NzkzNzI0MTc1MDA4OTUzMDMyMDgxMTQ2OTE3MTM4ODc4NzQ4MDM0Mzg1NDQxNTIxMTU5ODM2NDIwMDEzNTQ1NTQyMDk2NDIwODA1MDYxMzI5MTkwNzczNzIzMDYxMjE2NDIzNjczNTM1MzU1OTc5MzY1Njg0MzM2NjEyOTU2NjkxNDA4NDQ5MjE4MjcwOTYyODUyMzQwMTQ2MDAwMDYzNzA3NzU5MzUxODM0MTQ2MDI4MTYyMTEyMzU4MzAzNDQ1OTcwMTg3MTk3OTQ5MDcwNzE4NzQ4OTI4NjM5MDkyMzY1MjExMjgyODY0NDE3OTcwOTg5OCJdLFsibWFzdGVyX3NlY3JldCIsIjk4NjY2MDAzNjA2Njc3MjkxODM1MjEwMzA1NDczNTA3NjU0NTM1OTgxNDYxODkyNjY5NjI5OTE1MzQ5NjU3NjY5MTI5NzAxNTUwNzUyNTU2NjMxMzU4MzU3NjEzNjg3OTgyNTQzNTcyNTc5ODEzOTgxMzI4MDM4NzY5OTcxMzAwNTE0NDI2NzAxNTM2ODE1ODI3MDgzNDY5MzEzOTQ0ODAzMzIzMDUzMzUyNDkxMTIwMDUwNzkyNzA4NTQzMTE2NTM1NjA2NTQyNDY3OTcxNTUxODA4MzQyOTk1OTM4NzQ2NDQ4NjMyNTY4NjU0NjA4MzI1NDk2NjM3Mzc5OTQ0NjA5MDU3Mjc2OTE0OTQxNzE4MzU2MTYzODYyMzI2MDc3MDUzNjIyMDI3OTE2MzIzNzAzMDE2MTc4NDQ3MDEwMTc1MjI1MTM0NjE3NTcxMTgzMjcyNzMwNjQxNzI3Mzk2MzM4ODk2NTUyNzM4NzUwMDA4MTQ3MDExNjkyNzIwNzI4NDY2MjcwNDQ2MDg4NjEyMDg2MDExMzg2NzQxOTMzODM4ODQ1NjkzMTM1NzcyODk0MDIwNTM4NTU3ODI1MjA3OTkxNDAwODIyNjg4OTgwNTg4MjgzMTY0MzAxNTY1NjAyNDAzNTI4MDE2MTczNTk5MTM4NzI5ODEyOTE2MTYxNDEwNjgyODU4MzU4MDE3ODI1ODUyMzY2OTgzMDQzMzM4ODY2MzI3MzM3MTE0NzcyMzM5NTUyNTYzNzU0NzQ0NTA5MTYzNzYzNjAyNTI0NjgxNTUyNjIyOTYwNjM4MzE2OTc0NjI4MjQyNjQ5NjYyMjQyMTkzODMxOTUyMDE0MTAxNTA3Njk2MjIxMDU5NDE5MTcyNzMwNjE2NDE2NzEwNTMzMzc2NjI4MjQ4NzkxMTUwNjMxMDMxOCJdXX0sIm5vbmNlIjoiNTQzODYyOTczNzUxMjk1OTk2MTAzNjA3In0=", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, - "~thread": undefined, - "~timing": undefined, - "~transport": undefined, }, "requestMessage": Object { "@id": "edba1c87-51d3-4c70-aff2-ab8016e1060e", @@ -526,26 +417,15 @@ Array [ "requests~attach": Array [ Object { "@id": "libindy-cred-request-0", - "byte_count": undefined, "data": Object { "base64": "eyJwcm92ZXJfZGlkIjoiRUg2OTVENjRRd2hWRmtyazFtcDQ5aiIsImNyZWRfZGVmX2lkIjoiVEwxRWFQRkNaOFNpNWFVcnFTY0JEdDozOkNMOjY4MTpkZWZhdWx0IiwiYmxpbmRlZF9tcyI6eyJ1IjoiOTcwNjA5MzQ1NDAxNzE0NDIxNjQzNDg0NDE0MTQwOTA0NzMzNTQ4NTA4NzY0OTk5MTgxNzY1ODI3MjM3ODg3NjQ2MzQzNDYyMTY0ODA3MTUxMjg1ODk2MDczODAyNDY1MDMwMTcxNTIyODE4ODY4Mjc2ODUwMzE0NzQzMjM3ODc3NDMyMDgxMTQwMzE5ODU5OTM5NjM0MTI4NzkzOTk4NDcwMTUzNTQ0NjgxNTM5NDg4NzEyNDE5MTk3NzcxODE1NjU5Nzg1NDE5NTA1ODEyODI4NzYxOTI4MzExODczNjA5NDYwMjExMzQ4OTAyNDk4NzYxNjc5OTIzNzA2NjUwMDIzODg4ODU4NzE1MTYxMTIyMzA5MTc1MTE3MTk0NDMwNTI1ODY5NjcwMDEzMTgxNTkzNjI4NDQzMjk2MDI0MDE5NTc4MzIzODQwNzk0OTQ0MjIyODE1MTQwNTM2Mjc3ODQwMjU2ODc2MDE1MzUwNDgzOTE2MjYzMDgyODM5NzI2NzAxNjg4NjcxNDY0MjA3MzQxMTgwMjg3Mjk0Njg4NDA3NzQ3MDk1NjA0NzQ3NzA3OTc2Nzk2MjU0MTQ2NDQ0NTY5NzQ4MTk4OTMwMjkyOTkzNjY1ODk2MTUyMDMyNzQwODY3OTgwMjczMTMxMDM3NjkwNDkzNDU1Mjc4NDc3MDc3NjE0OTU2NjgzNjgxNDc5NzY3Njg0MDI4MzU5NzE4NzM0ODEyNzcyMDIyOTIwODQ3NDIyNDYyOTAwMjczMTcwMTU2NzQyMzUyMDQ2NDYyODI4NzAxMTE2MzU0MTkwMDY5MDE0NTIwMSIsInVyIjpudWxsLCJoaWRkZW5fYXR0cmlidXRlcyI6WyJtYXN0ZXJfc2VjcmV0Il0sImNvbW1pdHRlZF9hdHRyaWJ1dGVzIjp7fX0sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnsiYyI6IjE4MzE5MTUyNjg0NDkyMTM0OTc4MzkzNDE3OTY5NjQ5MDIyNzMzNzQzMTQ5NTUwNzAwNzc5ODk0Nzg1MTg3MTA1OTkyNjk4Mzg5MjAzIiwidl9kYXNoX2NhcCI6IjQ0NzA4MzAwOTUyNzA3MjA3NjI3NjA3NzM4MTI2NDgxNzA3OTA1MDcwNjEyMzQ5OTIxNTAxNTYyOTA2MzgyMzE0MDE4MzQ4MTAxMTE4MDI4MDIyMjk5OTgyNTEwMjI5ODM1OTQxMzY1MTM4Njg0MTU1OTEyOTE3NzYwMjgwOTIyNDk1MjE1ODA2NTM3MzA5NDU5NDA3NjcxNDgyNDA0NDMwODU4MzU5ODU3MDgzNzg1Njk1MTYzMjkwMzEyNzMzNDAxNjY1NDk5MjUwMDQ0NzkwODk4OTA4NzIzNzE1OTc0MDYwNzgyNDEzODYzMTU0MTUxNjg2OTM3ODY4MDM5MDU1Nzc1MzA5MjQ0MTYzOTUxNzgwMTgxNDk5MDM5MDgyMjcxNzgzNTgzNTkxMDIyNjYwMDYyMDQ3MDQ2NTEyMTM0NDU5OTI1MTgyOTg2MTkxOTAwNTQwMDg4MjE3Mzc2NjM4MjEzNTI0MDUxNjcxNzg3ODY0ODQ2NzIxNjk5NjQzNDk0MTI2MjA3NTg2MjgwNjQ5OTc4ODE2ODEwMjM5OTAzMzU0NzIyOTI2NTUxODYyNTQwMjc4ODU2OTEyNDQ2MDUzMTg4MzI3Mjk4NDc0NjgzMjkwMjU4MjgwNjY0OTgyOTM2NTYyODcwNTIyODA2NzYwMjE1OTI0MDc3ODQ2NjA2NTM0NzI4MjkyODQ2MDQyOTk1NjgxMTQzOTQ5MTU0MTU0NTU2NzQzMDYzMzY1OTIzMzU2OTg1MjQ5ODI1Njk2NzE3MzE2MDk1NzE5MDU2MTE5NTE0NTYwODY0MzUyMDc4ODMyOTYzNjI3Mjk0Njk1ODQ0MTE5NDA1NTMzNTY5MTI2ODExODE3NDYyNjczMzM3OTg4MzA0MDcwMzk5ODYyNTk2ODMyNDk2OTU3NzA4Nzc0NzI5NzAyOTEyNjY3Njk0OTgwMjc3OTI5MDgzNiIsIm1fY2FwcyI6eyJtYXN0ZXJfc2VjcmV0IjoiMjIxNTAyNTExNTYzODg1MTM1NzIwNjg4MzE5Njk5NzE5ODAxNDI4NDgzMTI2MjY0NjI0NTE5OTA4MjM5NTM0MjQ3MDQ3NTIwODgyNDE4Mzk0ODMzNjUwODM2NTI0NDk2MzUzMDkwNzIzOTI1MDc2NDY2ODQ3NjIxNzE4MDA4MDAxNTQyNTMzOTk4NTU1MzA1MDYwNjUzMzkwMjc4MDc2MzE2Mjg5MzcwMzA2MDcyMDYxNCJ9LCJyX2NhcHMiOnt9fSwibm9uY2UiOiI2OTgzNzA2MTYwMjM4ODM3MzA0OTgzNzUifQ==", }, - "lastmod_time": undefined, "mime-type": "application/json", }, ], - "~attach": undefined, - "~l10n": undefined, - "~please_ack": undefined, - "~service": undefined, "~thread": Object { - "pthid": undefined, - "received_orders": undefined, - "sender_order": undefined, "thid": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, - "~timing": undefined, - "~transport": undefined, }, "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", diff --git a/packages/core/src/storage/migration/__tests__/backup.test.ts b/packages/core/src/storage/migration/__tests__/backup.test.ts index 79eb17d31e..92ebd421d9 100644 --- a/packages/core/src/storage/migration/__tests__/backup.test.ts +++ b/packages/core/src/storage/migration/__tests__/backup.test.ts @@ -7,7 +7,7 @@ import { container } from 'tsyringe' import { getBaseConfig } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { AriesFrameworkError } from '../../../error' -import { CredentialRecord, CredentialRepository } from '../../../modules/credentials' +import { CredentialExchangeRecord, CredentialRepository } from '../../../modules/credentials' import { JsonTransformer } from '../../../utils' import { StorageUpdateService } from '../StorageUpdateService' import { UpdateAssistant } from '../UpdateAssistant' @@ -54,7 +54,7 @@ describe('UpdateAssistant | Backup', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const aliceCredentialRecords = Object.values(aliceCredentialRecordsJson).map((data: any) => { - const record = JsonTransformer.fromJSON(data.value, CredentialRecord) + const record = JsonTransformer.fromJSON(data.value, CredentialExchangeRecord) record.setTags(data.tags) return record @@ -90,7 +90,7 @@ describe('UpdateAssistant | Backup', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const aliceCredentialRecords = Object.values(aliceCredentialRecordsJson).map((data: any) => { - const record = JsonTransformer.fromJSON(data.value, CredentialRecord) + const record = JsonTransformer.fromJSON(data.value, CredentialExchangeRecord) record.setTags(data.tags) return record diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/credential.test.ts b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/credential.test.ts index 65175edaad..56d9098826 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/credential.test.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/__tests__/credential.test.ts @@ -1,6 +1,6 @@ +import { CredentialExchangeRecord } from '../../../../../../src/modules/credentials' import { getAgentConfig, mockFunction } from '../../../../../../tests/helpers' import { Agent } from '../../../../../agent/Agent' -import { CredentialRecord } from '../../../../../modules/credentials' import { CredentialRepository } from '../../../../../modules/credentials/repository/CredentialRepository' import { JsonTransformer } from '../../../../../utils' import * as testModule from '../credential' @@ -34,7 +34,7 @@ describe('0.1-0.2 | Credential', () => { describe('migrateCredentialRecordToV0_2()', () => { it('should fetch all records and apply the needed updates ', async () => { - const records: CredentialRecord[] = [ + const records: CredentialExchangeRecord[] = [ getCredentialWithMetadata({ schemaId: 'schemaId', credentialDefinitionId: 'schemaId', @@ -174,6 +174,6 @@ function getCredentialWithMetadata(metadata: Record) { { metadata, }, - CredentialRecord + CredentialExchangeRecord ) } diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts b/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts index 122e8f9b9b..838b1271f3 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts @@ -1,7 +1,7 @@ import type { Agent } from '../../../../agent/Agent' -import type { CredentialMetadata, CredentialRecord } from '../../../../modules/credentials' +import type { CredentialMetadata, CredentialExchangeRecord } from '../../../../modules/credentials' -import { CredentialMetadataKeys } from '../../../../modules/credentials' +import { CredentialMetadataKeys } from '../../../../modules/credentials/repository/CredentialMetadataTypes' import { CredentialRepository } from '../../../../modules/credentials/repository/CredentialRepository' import { Metadata } from '../../../Metadata' @@ -62,7 +62,7 @@ export async function migrateCredentialRecordToV0_2(agent: Agent) { * } * ``` */ -export async function updateIndyMetadata(agent: Agent, credentialRecord: CredentialRecord) { +export async function updateIndyMetadata(agent: Agent, credentialRecord: CredentialExchangeRecord) { agent.config.logger.debug(`Updating indy metadata to use the generic metadata api available to records.`) const { requestMetadata, schemaId, credentialDefinitionId, ...rest } = credentialRecord.metadata.data diff --git a/packages/core/src/utils/transformers.ts b/packages/core/src/utils/transformers.ts index 6f5a993c48..622772a385 100644 --- a/packages/core/src/utils/transformers.ts +++ b/packages/core/src/utils/transformers.ts @@ -1,7 +1,7 @@ import type { ValidationOptions } from 'class-validator' import { Transform, TransformationType } from 'class-transformer' -import { ValidateBy, buildMessage } from 'class-validator' +import { isString, ValidateBy, buildMessage } from 'class-validator' import { DateTime } from 'luxon' import { Metadata } from '../storage/Metadata' @@ -96,3 +96,22 @@ export function IsMap(validationOptions?: ValidationOptions): PropertyDecorator validationOptions ) } + +/** + * Checks if a given value is a string or string array. + */ +export function IsStringOrStringArray(validationOptions?: Omit): PropertyDecorator { + return ValidateBy( + { + name: 'isStringOrStringArray', + validator: { + validate: (value): boolean => isString(value) || (Array.isArray(value) && value.every((v) => isString(v))), + defaultMessage: buildMessage( + (eachPrefix) => eachPrefix + '$property must be a string or string array', + validationOptions + ), + }, + }, + validationOptions + ) +} diff --git a/packages/core/tests/connectionless-proofs.test.ts b/packages/core/tests/connectionless-proofs.test.ts index ebddd35ea7..81fbd52090 100644 --- a/packages/core/tests/connectionless-proofs.test.ts +++ b/packages/core/tests/connectionless-proofs.test.ts @@ -7,7 +7,7 @@ import { SubjectInboundTransport } from '../../../tests/transport/SubjectInbound import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' import { Agent } from '../src/agent/Agent' import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachment' -import { CredentialPreview } from '../src/modules/credentials' +import { V1CredentialPreview } from '../src/modules/credentials' import { PredicateType, ProofState, @@ -178,7 +178,7 @@ describe('Present Proof', () => { test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and both agents having a mediator', async () => { testLogger.test('Faber sends presentation request to Alice') - const credentialPreview = CredentialPreview.fromRecord({ + const credentialPreview = V1CredentialPreview.fromRecord({ name: 'John', age: '99', }) diff --git a/packages/core/tests/credentials-auto-accept.test.ts b/packages/core/tests/credentials-auto-accept.test.ts deleted file mode 100644 index 6ea51e3435..0000000000 --- a/packages/core/tests/credentials-auto-accept.test.ts +++ /dev/null @@ -1,522 +0,0 @@ -import type { Agent } from '../src/agent/Agent' -import type { ConnectionRecord } from '../src/modules/connections' - -import { AutoAcceptCredential, CredentialPreview, CredentialRecord, CredentialState } from '../src/modules/credentials' -import { JsonTransformer } from '../src/utils/JsonTransformer' -import { sleep } from '../src/utils/sleep' - -import { setupCredentialTests, waitForCredentialRecord } from './helpers' -import testLogger from './logger' - -const credentialPreview = CredentialPreview.fromRecord({ - name: 'John', - age: '99', - 'x-ray': 'some x-ray', - profile_picture: 'profile picture', -}) - -const newCredentialPreview = CredentialPreview.fromRecord({ - name: 'John', - age: '99', - 'x-ray': 'another x-ray value', - profile_picture: 'another profile picture', -}) - -describe('auto accept credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let schemaId: string - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - let faberCredentialRecord: CredentialRecord - let aliceCredentialRecord: CredentialRecord - - describe('Auto accept on `always`', () => { - beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, schemaId, faberConnection, aliceConnection } = await setupCredentialTests( - 'faber agent: always', - 'alice agent: always', - AutoAcceptCredential.Always - )) - }) - - afterAll(async () => { - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test('Alice starts with credential proposal to Faber, both with autoAcceptCredential on `always`', async () => { - testLogger.test('Alice sends credential proposal to Faber') - let aliceCredentialRecord = await aliceAgent.credentials.proposeCredential(aliceConnection.id, { - credentialProposal: credentialPreview, - credentialDefinitionId: credDefId, - }) - - testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.Done, - }) - - expect(aliceCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), - metadata: { - data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { - schemaId, - credentialDefinitionId: credDefId, - }, - }, - }, - credentialId: expect.any(String), - state: CredentialState.Done, - }) - - expect(faberCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - metadata: { - data: { - '_internal/indyCredential': { - schemaId, - credentialDefinitionId: credDefId, - }, - }, - }, - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), - state: CredentialState.Done, - }) - }) - - test('Faber starts with credential offer to Alice, both with autoAcceptCredential on `always`', async () => { - testLogger.test('Faber sends credential offer to Alice') - faberCredentialRecord = await faberAgent.credentials.offerCredential(faberConnection.id, { - preview: credentialPreview, - credentialDefinitionId: credDefId, - comment: 'some comment about credential', - }) - - testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.Done, - }) - - expect(aliceCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), - metadata: { - data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { - schemaId, - credentialDefinitionId: credDefId, - }, - }, - }, - credentialId: expect.any(String), - state: CredentialState.Done, - }) - - expect(faberCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), - state: CredentialState.Done, - }) - }) - }) - - describe('Auto accept on `contentApproved`', () => { - beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, schemaId, faberConnection, aliceConnection } = await setupCredentialTests( - 'faber agent: contentApproved', - 'alice agent: contentApproved', - AutoAcceptCredential.ContentApproved - )) - }) - - afterAll(async () => { - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test('Alice starts with credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { - testLogger.test('Alice sends credential proposal to Faber') - let aliceCredentialRecord = await aliceAgent.credentials.proposeCredential(aliceConnection.id, { - credentialProposal: credentialPreview, - credentialDefinitionId: credDefId, - }) - - testLogger.test('Faber waits for credential proposal from Alice') - let faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.ProposalReceived, - }) - - testLogger.test('Faber sends credential offer to Alice') - faberCredentialRecord = await faberAgent.credentials.acceptProposal(faberCredentialRecord.id) - - testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.Done, - }) - - expect(aliceCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), - metadata: { - data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { - schemaId, - credentialDefinitionId: credDefId, - }, - }, - }, - credentialId: expect.any(String), - state: CredentialState.Done, - }) - - expect(faberCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - metadata: { - data: { - '_internal/indyCredential': { - schemaId, - credentialDefinitionId: credDefId, - }, - }, - }, - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), - state: CredentialState.Done, - }) - }) - - test('Faber starts with credential offer to Alice, both with autoAcceptCredential on `contentApproved`', async () => { - testLogger.test('Faber sends credential offer to Alice') - faberCredentialRecord = await faberAgent.credentials.offerCredential(faberConnection.id, { - preview: credentialPreview, - credentialDefinitionId: credDefId, - }) - - testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.OfferReceived, - }) - - expect(JsonTransformer.toJSON(aliceCredentialRecord)).toMatchObject({ - createdAt: expect.any(String), - offerMessage: { - '@id': expect.any(String), - '@type': 'https://didcomm.org/issue-credential/1.0/offer-credential', - credential_preview: { - '@type': 'https://didcomm.org/issue-credential/1.0/credential-preview', - attributes: [ - { - name: 'name', - value: 'John', - }, - { - name: 'age', - value: '99', - }, - { - name: 'x-ray', - 'mime-type': 'text/plain', - value: 'some x-ray', - }, - { - name: 'profile_picture', - 'mime-type': 'text/plain', - value: 'profile picture', - }, - ], - }, - 'offers~attach': expect.any(Array), - }, - state: CredentialState.OfferReceived, - }) - - // below values are not in json object - expect(aliceCredentialRecord.id).not.toBeNull() - expect(aliceCredentialRecord.getTags()).toEqual({ - threadId: aliceCredentialRecord.threadId, - state: aliceCredentialRecord.state, - connectionId: aliceConnection.id, - }) - expect(aliceCredentialRecord.type).toBe(CredentialRecord.name) - - testLogger.test('alice sends credential request to faber') - aliceCredentialRecord = await aliceAgent.credentials.acceptOffer(aliceCredentialRecord.id) - - testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.Done, - }) - - expect(aliceCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), - metadata: { - data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { - schemaId, - credentialDefinitionId: credDefId, - }, - }, - }, - credentialId: expect.any(String), - state: CredentialState.Done, - }) - - expect(faberCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), - state: CredentialState.Done, - }) - }) - - test('Alice starts with credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { - testLogger.test('Alice sends credential proposal to Faber') - let aliceCredentialRecord = await aliceAgent.credentials.proposeCredential(aliceConnection.id, { - credentialProposal: credentialPreview, - credentialDefinitionId: credDefId, - }) - - testLogger.test('Faber waits for credential proposal from Alice') - let faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.ProposalReceived, - }) - - faberCredentialRecord = await faberAgent.credentials.negotiateProposal( - faberCredentialRecord.id, - newCredentialPreview - ) - - testLogger.test('Alice waits for credential offer from Faber') - - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.OfferReceived, - }) - - expect(JsonTransformer.toJSON(aliceCredentialRecord)).toMatchObject({ - createdAt: expect.any(String), - offerMessage: { - '@id': expect.any(String), - '@type': 'https://didcomm.org/issue-credential/1.0/offer-credential', - credential_preview: { - '@type': 'https://didcomm.org/issue-credential/1.0/credential-preview', - attributes: [ - { - name: 'name', - value: 'John', - }, - { - name: 'age', - value: '99', - }, - { - name: 'x-ray', - 'mime-type': 'text/plain', - value: 'another x-ray value', - }, - { - name: 'profile_picture', - 'mime-type': 'text/plain', - value: 'another profile picture', - }, - ], - }, - 'offers~attach': expect.any(Array), - }, - state: CredentialState.OfferReceived, - }) - - // below values are not in json object - expect(aliceCredentialRecord.id).not.toBeNull() - expect(aliceCredentialRecord.getTags()).toEqual({ - threadId: aliceCredentialRecord.threadId, - state: aliceCredentialRecord.state, - connectionId: aliceConnection.id, - }) - expect(aliceCredentialRecord.type).toBe(CredentialRecord.name) - - // Wait for ten seconds - await sleep(5000) - - // Check if the state of the credential records did not change - faberCredentialRecord = await faberAgent.credentials.getById(faberCredentialRecord.id) - faberCredentialRecord.assertState(CredentialState.OfferSent) - - aliceCredentialRecord = await aliceAgent.credentials.getById(aliceCredentialRecord.id) - aliceCredentialRecord.assertState(CredentialState.OfferReceived) - }) - - test('Faber starts with credential offer to Alice, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { - testLogger.test('Faber sends credential offer to Alice') - faberCredentialRecord = await faberAgent.credentials.offerCredential(faberConnection.id, { - preview: credentialPreview, - credentialDefinitionId: credDefId, - }) - - testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.OfferReceived, - }) - - expect(JsonTransformer.toJSON(aliceCredentialRecord)).toMatchObject({ - createdAt: expect.any(String), - offerMessage: { - '@id': expect.any(String), - '@type': 'https://didcomm.org/issue-credential/1.0/offer-credential', - credential_preview: { - '@type': 'https://didcomm.org/issue-credential/1.0/credential-preview', - attributes: [ - { - name: 'name', - value: 'John', - }, - { - name: 'age', - value: '99', - }, - { - name: 'x-ray', - 'mime-type': 'text/plain', - value: 'some x-ray', - }, - { - name: 'profile_picture', - 'mime-type': 'text/plain', - value: 'profile picture', - }, - ], - }, - 'offers~attach': expect.any(Array), - }, - state: CredentialState.OfferReceived, - }) - - // below values are not in json object - expect(aliceCredentialRecord.id).not.toBeNull() - expect(aliceCredentialRecord.getTags()).toEqual({ - threadId: aliceCredentialRecord.threadId, - state: aliceCredentialRecord.state, - connectionId: aliceConnection.id, - }) - expect(aliceCredentialRecord.type).toBe(CredentialRecord.name) - - testLogger.test('Alice sends credential request to Faber') - aliceCredentialRecord = await aliceAgent.credentials.negotiateOffer( - aliceCredentialRecord.id, - newCredentialPreview - ) - - expect(JsonTransformer.toJSON(aliceCredentialRecord)).toMatchObject({ - createdAt: expect.any(String), - proposalMessage: { - '@type': 'https://didcomm.org/issue-credential/1.0/propose-credential', - '@id': expect.any(String), - credential_proposal: { - '@type': 'https://didcomm.org/issue-credential/1.0/credential-preview', - attributes: [ - { - 'mime-type': 'text/plain', - name: 'name', - value: 'John', - }, - { - 'mime-type': 'text/plain', - name: 'age', - value: '99', - }, - { - name: 'x-ray', - 'mime-type': 'text/plain', - value: 'another x-ray value', - }, - { - name: 'profile_picture', - 'mime-type': 'text/plain', - value: 'another profile picture', - }, - ], - }, - '~thread': { thid: expect.any(String) }, - }, - state: CredentialState.ProposalSent, - }) - - // Wait for ten seconds - await sleep(5000) - - // Check if the state of fabers credential record did not change - faberCredentialRecord = await faberAgent.credentials.getById(faberCredentialRecord.id) - faberCredentialRecord.assertState(CredentialState.ProposalReceived) - - aliceCredentialRecord = await aliceAgent.credentials.getById(aliceCredentialRecord.id) - aliceCredentialRecord.assertState(CredentialState.ProposalSent) - }) - }) -}) diff --git a/packages/core/tests/credentials.test.ts b/packages/core/tests/credentials.test.ts deleted file mode 100644 index 74ed5f1c3f..0000000000 --- a/packages/core/tests/credentials.test.ts +++ /dev/null @@ -1,526 +0,0 @@ -import type { Agent } from '../src/agent/Agent' -import type { ConnectionRecord } from '../src/modules/connections' - -import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachment' -import { CredentialPreview, CredentialRecord, CredentialState } from '../src/modules/credentials' -import { JsonTransformer } from '../src/utils/JsonTransformer' -import { LinkedAttachment } from '../src/utils/LinkedAttachment' - -import { setupCredentialTests, waitForCredentialRecord } from './helpers' -import testLogger from './logger' - -const credentialPreview = CredentialPreview.fromRecord({ - name: 'John', - age: '99', - 'x-ray': 'some x-ray', - profile_picture: 'profile picture', -}) - -const credentialPreviewWithoutProfilePicture = CredentialPreview.fromRecord({ - name: 'John', - age: '99', - 'x-ray': 'some x-ray', -}) - -const credentialPreviewWithoutXray = CredentialPreview.fromRecord({ - name: 'John', - age: '99', - profile_picture: 'profile picture', -}) - -describe('credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - let faberCredentialRecord: CredentialRecord - let aliceCredentialRecord: CredentialRecord - - beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection } = await setupCredentialTests( - 'Faber Agent Credentials', - 'Alice Agent Credential' - )) - }) - - afterAll(async () => { - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test('Alice starts with credential proposal to Faber', async () => { - testLogger.test('Alice sends credential proposal to Faber') - let aliceCredentialRecord = await aliceAgent.credentials.proposeCredential(aliceConnection.id, { - credentialProposal: credentialPreview, - credentialDefinitionId: credDefId, - }) - - testLogger.test('Faber waits for credential proposal from Alice') - let faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.ProposalReceived, - }) - - testLogger.test('Faber sends credential offer to Alice') - faberCredentialRecord = await faberAgent.credentials.acceptProposal(faberCredentialRecord.id, { - comment: 'some comment about credential', - }) - - testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.OfferReceived, - }) - - expect(JsonTransformer.toJSON(aliceCredentialRecord)).toMatchObject({ - createdAt: expect.any(String), - offerMessage: { - '@id': expect.any(String), - '@type': 'https://didcomm.org/issue-credential/1.0/offer-credential', - comment: 'some comment about credential', - credential_preview: { - '@type': 'https://didcomm.org/issue-credential/1.0/credential-preview', - attributes: [ - { - name: 'name', - 'mime-type': 'text/plain', - value: 'John', - }, - { - name: 'age', - 'mime-type': 'text/plain', - value: '99', - }, - { - name: 'x-ray', - 'mime-type': 'text/plain', - value: 'some x-ray', - }, - { - name: 'profile_picture', - 'mime-type': 'text/plain', - value: 'profile picture', - }, - ], - }, - 'offers~attach': expect.any(Array), - }, - state: CredentialState.OfferReceived, - }) - - // below values are not in json object - expect(aliceCredentialRecord.id).not.toBeNull() - expect(aliceCredentialRecord.getTags()).toEqual({ - threadId: faberCredentialRecord.threadId, - connectionId: aliceCredentialRecord.connectionId, - state: aliceCredentialRecord.state, - }) - expect(aliceCredentialRecord.type).toBe(CredentialRecord.name) - - testLogger.test('Alice sends credential request to Faber') - aliceCredentialRecord = await aliceAgent.credentials.acceptOffer(aliceCredentialRecord.id) - - testLogger.test('Faber waits for credential request from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.RequestReceived, - }) - - testLogger.test('Faber sends credential to Alice') - faberCredentialRecord = await faberAgent.credentials.acceptRequest(faberCredentialRecord.id) - - testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Alice sends credential ack to Faber') - aliceCredentialRecord = await aliceAgent.credentials.acceptCredential(aliceCredentialRecord.id) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.Done, - }) - - expect(aliceCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - threadId: expect.any(String), - connectionId: expect.any(String), - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), - credentialId: expect.any(String), - state: CredentialState.Done, - }) - - expect(faberCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - threadId: expect.any(String), - connectionId: expect.any(String), - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), - state: CredentialState.Done, - }) - }) - - test('Faber starts with credential offer to Alice', async () => { - testLogger.test('Faber sends credential offer to Alice') - faberCredentialRecord = await faberAgent.credentials.offerCredential(faberConnection.id, { - preview: credentialPreview, - credentialDefinitionId: credDefId, - comment: 'some comment about credential', - }) - - testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.OfferReceived, - }) - - expect(JsonTransformer.toJSON(aliceCredentialRecord)).toMatchObject({ - createdAt: expect.any(String), - offerMessage: { - '@id': expect.any(String), - '@type': 'https://didcomm.org/issue-credential/1.0/offer-credential', - comment: 'some comment about credential', - credential_preview: { - '@type': 'https://didcomm.org/issue-credential/1.0/credential-preview', - attributes: [ - { - name: 'name', - 'mime-type': 'text/plain', - value: 'John', - }, - { - name: 'age', - 'mime-type': 'text/plain', - value: '99', - }, - { - name: 'x-ray', - 'mime-type': 'text/plain', - value: 'some x-ray', - }, - { - name: 'profile_picture', - 'mime-type': 'text/plain', - value: 'profile picture', - }, - ], - }, - 'offers~attach': expect.any(Array), - }, - state: CredentialState.OfferReceived, - }) - - // below values are not in json object - expect(aliceCredentialRecord.id).not.toBeNull() - expect(aliceCredentialRecord.getTags()).toEqual({ - threadId: faberCredentialRecord.threadId, - connectionId: aliceConnection.id, - state: aliceCredentialRecord.state, - }) - expect(aliceCredentialRecord.type).toBe(CredentialRecord.name) - - testLogger.test('Alice sends credential request to Faber') - aliceCredentialRecord = await aliceAgent.credentials.acceptOffer(aliceCredentialRecord.id) - - testLogger.test('Faber waits for credential request from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.RequestReceived, - }) - - testLogger.test('Faber sends credential to Alice') - faberCredentialRecord = await faberAgent.credentials.acceptRequest(faberCredentialRecord.id) - - testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Alice sends credential ack to Faber') - aliceCredentialRecord = await aliceAgent.credentials.acceptCredential(aliceCredentialRecord.id) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.Done, - }) - - expect(aliceCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), - metadata: expect.any(Object), - credentialId: expect.any(String), - state: CredentialState.Done, - threadId: expect.any(String), - }) - - expect(faberCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - offerMessage: expect.any(Object), - metadata: expect.any(Object), - requestMessage: expect.any(Object), - state: CredentialState.Done, - threadId: expect.any(String), - connectionId: expect.any(String), - }) - }) - - test('Alice starts with credential proposal, with attachments, to Faber', async () => { - testLogger.test('Alice sends credential proposal to Faber') - let aliceCredentialRecord = await aliceAgent.credentials.proposeCredential(aliceConnection.id, { - credentialProposal: credentialPreviewWithoutProfilePicture, - credentialDefinitionId: credDefId, - linkedAttachments: [ - new LinkedAttachment({ - name: 'profile_picture', - attachment: new Attachment({ - mimeType: 'image/png', - data: new AttachmentData({ base64: 'base64encodedpic' }), - }), - }), - ], - }) - - testLogger.test('Faber waits for credential proposal from Alice') - let faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.ProposalReceived, - }) - - testLogger.test('Faber sends credential offer to Alice') - faberCredentialRecord = await faberAgent.credentials.acceptProposal(faberCredentialRecord.id, { - comment: 'some comment about credential', - }) - - testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.OfferReceived, - }) - - expect(JsonTransformer.toJSON(aliceCredentialRecord)).toMatchObject({ - createdAt: expect.any(String), - offerMessage: { - '@id': expect.any(String), - '@type': 'https://didcomm.org/issue-credential/1.0/offer-credential', - comment: 'some comment about credential', - credential_preview: { - '@type': 'https://didcomm.org/issue-credential/1.0/credential-preview', - attributes: [ - { - name: 'name', - 'mime-type': 'text/plain', - value: 'John', - }, - { - name: 'age', - 'mime-type': 'text/plain', - value: '99', - }, - { - name: 'x-ray', - 'mime-type': 'text/plain', - value: 'some x-ray', - }, - { - name: 'profile_picture', - 'mime-type': 'image/png', - value: 'hl:zQmcKEWE6eZWpVqGKhbmhd8SxWBa9fgLX7aYW8RJzeHQMZg', - }, - ], - }, - '~attach': [{ '@id': 'zQmcKEWE6eZWpVqGKhbmhd8SxWBa9fgLX7aYW8RJzeHQMZg' }], - 'offers~attach': expect.any(Array), - }, - state: CredentialState.OfferReceived, - }) - - // below values are not in json object - expect(aliceCredentialRecord.id).not.toBeNull() - expect(aliceCredentialRecord.getTags()).toEqual({ - state: aliceCredentialRecord.state, - threadId: faberCredentialRecord.threadId, - connectionId: aliceCredentialRecord.connectionId, - }) - expect(aliceCredentialRecord.type).toBe(CredentialRecord.name) - - testLogger.test('Alice sends credential request to Faber') - aliceCredentialRecord = await aliceAgent.credentials.acceptOffer(aliceCredentialRecord.id) - - testLogger.test('Faber waits for credential request from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.RequestReceived, - }) - - testLogger.test('Faber sends credential to Alice') - faberCredentialRecord = await faberAgent.credentials.acceptRequest(faberCredentialRecord.id) - - testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Alice sends credential ack to Faber') - aliceCredentialRecord = await aliceAgent.credentials.acceptCredential(aliceCredentialRecord.id) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.Done, - }) - - expect(aliceCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - metadata: expect.any(Object), - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), - credentialId: expect.any(String), - state: CredentialState.Done, - }) - - expect(faberCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - metadata: expect.any(Object), - offerMessage: expect.any(Object), - requestMessage: expect.any(Object), - state: CredentialState.Done, - }) - }) - - test('Faber starts with credential, with attachments, offer to Alice', async () => { - testLogger.test('Faber sends credential offer to Alice') - faberCredentialRecord = await faberAgent.credentials.offerCredential(faberConnection.id, { - preview: credentialPreviewWithoutXray, - credentialDefinitionId: credDefId, - comment: 'some comment about credential', - linkedAttachments: [ - new LinkedAttachment({ - name: 'x-ray', - attachment: new Attachment({ - data: new AttachmentData({ - base64: 'c2Vjb25kYmFzZTY0ZW5jb2RlZHBpYw==', - }), - }), - }), - ], - }) - - testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.OfferReceived, - }) - - expect(JsonTransformer.toJSON(aliceCredentialRecord)).toMatchObject({ - createdAt: expect.any(String), - offerMessage: { - '@id': expect.any(String), - '@type': 'https://didcomm.org/issue-credential/1.0/offer-credential', - comment: 'some comment about credential', - credential_preview: { - '@type': 'https://didcomm.org/issue-credential/1.0/credential-preview', - attributes: [ - { - name: 'name', - 'mime-type': 'text/plain', - value: 'John', - }, - { - name: 'age', - 'mime-type': 'text/plain', - value: '99', - }, - { - name: 'profile_picture', - 'mime-type': 'text/plain', - value: 'profile picture', - }, - { - name: 'x-ray', - value: 'hl:zQmdsy1SSKztP7CGRiP2SuMV41Xxy9g69QswhUiSeo3d4pH', - }, - ], - }, - '~attach': [{ '@id': 'zQmdsy1SSKztP7CGRiP2SuMV41Xxy9g69QswhUiSeo3d4pH' }], - 'offers~attach': expect.any(Array), - }, - state: CredentialState.OfferReceived, - }) - - // below values are not in json object - expect(aliceCredentialRecord.id).not.toBeNull() - expect(aliceCredentialRecord.getTags()).toEqual({ - state: aliceCredentialRecord.state, - threadId: faberCredentialRecord.threadId, - connectionId: aliceCredentialRecord.connectionId, - }) - expect(aliceCredentialRecord.type).toBe(CredentialRecord.name) - - testLogger.test('Alice sends credential request to Faber') - aliceCredentialRecord = await aliceAgent.credentials.acceptOffer(aliceCredentialRecord.id) - - testLogger.test('Faber waits for credential request from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.RequestReceived, - }) - - testLogger.test('Faber sends credential to Alice') - faberCredentialRecord = await faberAgent.credentials.acceptRequest(faberCredentialRecord.id) - - testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Alice sends credential ack to Faber') - aliceCredentialRecord = await aliceAgent.credentials.acceptCredential(aliceCredentialRecord.id) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.Done, - }) - - expect(aliceCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - requestMessage: expect.any(Object), - credentialId: expect.any(String), - state: CredentialState.Done, - }) - - expect(faberCredentialRecord).toMatchObject({ - type: CredentialRecord.name, - id: expect.any(String), - createdAt: expect.any(Date), - requestMessage: expect.any(Object), - state: CredentialState.Done, - }) - }) -}) diff --git a/packages/core/tests/dids.test.ts b/packages/core/tests/dids.test.ts index dfb0920ec2..50c90c704d 100644 --- a/packages/core/tests/dids.test.ts +++ b/packages/core/tests/dids.test.ts @@ -1,9 +1,8 @@ import { Agent } from '../src/agent/Agent' +import { JsonTransformer } from '../src/utils/JsonTransformer' import { getBaseConfig } from './helpers' -import { JsonTransformer } from '@aries-framework/core' - const { config, agentDependencies } = getBaseConfig('Faber Dids', {}) describe('dids', () => { @@ -30,8 +29,8 @@ describe('dids', () => { 'https://w3id.org/security/suites/x25519-2019/v1', ], id: 'did:sov:TL1EaPFCZ8Si5aUrqScBDt', - alsoKnownAs: [], - controller: [], + alsoKnownAs: undefined, + controller: undefined, verificationMethod: [ { type: 'Ed25519VerificationKey2018', @@ -46,12 +45,12 @@ describe('dids', () => { publicKeyBase58: '6oKfyWDYRpbutQWDUu8ots6GoqAZJ9HYRzPuuEiqfyM', }, ], - capabilityDelegation: [], - capabilityInvocation: [], + capabilityDelegation: undefined, + capabilityInvocation: undefined, authentication: ['did:sov:TL1EaPFCZ8Si5aUrqScBDt#key-1'], assertionMethod: ['did:sov:TL1EaPFCZ8Si5aUrqScBDt#key-1'], keyAgreement: ['did:sov:TL1EaPFCZ8Si5aUrqScBDt#key-agreement-1'], - service: [], + service: undefined, }, didDocumentMetadata: {}, didResolutionMetadata: { @@ -71,8 +70,8 @@ describe('dids', () => { 'https://w3id.org/security/suites/x25519-2019/v1', ], id: 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', - alsoKnownAs: [], - controller: [], + alsoKnownAs: undefined, + controller: undefined, verificationMethod: [ { id: 'did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', @@ -101,7 +100,7 @@ describe('dids', () => { publicKeyBase58: 'FxfdY3DCQxVZddKGAtSjZdFW9bCCW7oRwZn1NFJ2Tbg2', }, ], - service: [], + service: undefined, }, didDocumentMetadata: {}, didResolutionMetadata: { @@ -121,8 +120,8 @@ describe('dids', () => { 'https://w3id.org/security/suites/x25519-2019/v1', ], id: 'did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', - alsoKnownAs: [], - controller: [], + alsoKnownAs: undefined, + controller: undefined, verificationMethod: [ { id: 'did:peer:0z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL#z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL', @@ -151,7 +150,7 @@ describe('dids', () => { publicKeyBase58: 'FxfdY3DCQxVZddKGAtSjZdFW9bCCW7oRwZn1NFJ2Tbg2', }, ], - service: [], + service: undefined, }, didDocumentMetadata: {}, didResolutionMetadata: { diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 39ff276e64..271d9125db 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -5,7 +5,6 @@ import type { BasicMessageStateChangedEvent, ConnectionRecordProps, CredentialDefinitionTemplate, - CredentialOfferTemplate, CredentialStateChangedEvent, InitConfig, ProofAttributeInfo, @@ -13,6 +12,8 @@ import type { ProofStateChangedEvent, SchemaTemplate, } from '../src' +import type { AcceptOfferOptions, OfferCredentialOptions } from '../src/modules/credentials/CredentialsModuleOptions' +import type { CredentialOfferTemplate } from '../src/modules/credentials/protocol' import type { Schema, CredDef } from 'indy-sdk' import type { Observable } from 'rxjs' @@ -24,6 +25,9 @@ import { SubjectInboundTransport } from '../../../tests/transport/SubjectInbound import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' import { agentDependencies, WalletScheme } from '../../node/src' import { + PresentationPreview, + PresentationPreviewAttribute, + PresentationPreviewPredicate, LogLevel, AgentConfig, AriesFrameworkError, @@ -33,19 +37,17 @@ import { ConnectionRole, ConnectionState, CredentialEventTypes, - CredentialPreview, CredentialState, DidDoc, PredicateType, - PresentationPreview, - PresentationPreviewAttribute, - PresentationPreviewPredicate, ProofEventTypes, ProofState, Agent, } from '../src' import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachment' import { AutoAcceptCredential } from '../src/modules/credentials/CredentialAutoAcceptType' +import { CredentialProtocolVersion } from '../src/modules/credentials/CredentialProtocolVersion' +import { V1CredentialPreview } from '../src/modules/credentials/protocol/v1/V1CredentialPreview' import { DidCommService } from '../src/modules/dids' import { LinkedAttachment } from '../src/utils/LinkedAttachment' import { uuid } from '../src/utils/uuid' @@ -187,6 +189,7 @@ export function waitForCredentialRecordSubject( } ) { const observable = subject instanceof ReplaySubject ? subject.asObservable() : subject + return firstValueFrom( observable.pipe( filter((e) => previousState === undefined || e.payload.previousState === previousState), @@ -215,7 +218,6 @@ export async function waitForCredentialRecord( } ) { const observable = agent.events.observable(CredentialEventTypes.CredentialStateChanged) - return waitForCredentialRecordSubject(observable, options) } @@ -360,7 +362,8 @@ export async function ensurePublicDidIsOnLedger(agent: Agent, publicDid: string) try { testLogger.test(`Ensure test DID ${publicDid} is written to ledger`) await agent.ledger.getPublicDid(publicDid) - } catch (error) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { // Unfortunately, this won't prevent from the test suite running because of Jest runner runs all tests // regardless of thrown errors. We're more explicit about the problem with this error handling. throw new Error(`Test DID ${publicDid} is not written on ledger or ledger is not available: ${error.message}`) @@ -391,19 +394,32 @@ export async function issueCredential({ .observable(CredentialEventTypes.CredentialStateChanged) .subscribe(holderReplay) - let issuerCredentialRecord = await issuerAgent.credentials.offerCredential(issuerConnectionId, { - ...credentialTemplate, + const offerOptions: OfferCredentialOptions = { + comment: 'some comment about credential', + connectionId: issuerConnectionId, + protocolVersion: CredentialProtocolVersion.V1, + credentialFormats: { + indy: { + attributes: credentialTemplate.preview.attributes, + credentialDefinitionId: credentialTemplate.credentialDefinitionId, + linkedAttachments: credentialTemplate.linkedAttachments, + }, + }, autoAcceptCredential: AutoAcceptCredential.ContentApproved, - }) + } + let issuerCredentialRecord = await issuerAgent.credentials.offerCredential(offerOptions) let holderCredentialRecord = await waitForCredentialRecordSubject(holderReplay, { threadId: issuerCredentialRecord.threadId, state: CredentialState.OfferReceived, }) - await holderAgent.credentials.acceptOffer(holderCredentialRecord.id, { + const acceptOfferOptions: AcceptOfferOptions = { + credentialRecordId: holderCredentialRecord.id, autoAcceptCredential: AutoAcceptCredential.ContentApproved, - }) + } + + await holderAgent.credentials.acceptOffer(acceptOfferOptions) // Because we use auto-accept it can take a while to have the whole credential flow finished // Both parties need to interact with the ledger and sign/verify the credential @@ -411,7 +427,6 @@ export async function issueCredential({ threadId: issuerCredentialRecord.threadId, state: CredentialState.Done, }) - issuerCredentialRecord = await waitForCredentialRecordSubject(issuerReplay, { threadId: issuerCredentialRecord.threadId, state: CredentialState.Done, @@ -442,22 +457,35 @@ export async function issueConnectionLessCredential({ .observable(CredentialEventTypes.CredentialStateChanged) .subscribe(holderReplay) - // eslint-disable-next-line prefer-const - let { credentialRecord: issuerCredentialRecord, offerMessage } = await issuerAgent.credentials.createOutOfBandOffer({ - ...credentialTemplate, + const offerOptions: OfferCredentialOptions = { + comment: 'V1 Out of Band offer', + protocolVersion: CredentialProtocolVersion.V1, + credentialFormats: { + indy: { + attributes: credentialTemplate.preview.attributes, + credentialDefinitionId: credentialTemplate.credentialDefinitionId, + }, + }, autoAcceptCredential: AutoAcceptCredential.ContentApproved, - }) + connectionId: '', + } + // eslint-disable-next-line prefer-const + let { credentialRecord: issuerCredentialRecord, message } = await issuerAgent.credentials.createOutOfBandOffer( + offerOptions + ) - await holderAgent.receiveMessage(offerMessage.toJSON()) + await holderAgent.receiveMessage(message.toJSON()) let holderCredentialRecord = await waitForCredentialRecordSubject(holderReplay, { threadId: issuerCredentialRecord.threadId, state: CredentialState.OfferReceived, }) - - holderCredentialRecord = await holderAgent.credentials.acceptOffer(holderCredentialRecord.id, { + const acceptOfferOptions: AcceptOfferOptions = { + credentialRecordId: holderCredentialRecord.id, autoAcceptCredential: AutoAcceptCredential.ContentApproved, - }) + } + + await holderAgent.credentials.acceptOffer(acceptOfferOptions) holderCredentialRecord = await waitForCredentialRecordSubject(holderReplay, { threadId: issuerCredentialRecord.threadId, @@ -581,17 +609,17 @@ export async function setupCredentialTests( await aliceAgent.initialize() const { - schema: { id: schemaId }, + schema, definition: { id: credDefId }, } = await prepareForIssuance(faberAgent, ['name', 'age', 'profile_picture', 'x-ray']) const [faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) - return { faberAgent, aliceAgent, credDefId, schemaId, faberConnection, aliceConnection } + return { faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection } } export async function setupProofsTest(faberName: string, aliceName: string, autoAcceptProofs?: AutoAcceptProof) { - const credentialPreview = CredentialPreview.fromRecord({ + const credentialPreview = V1CredentialPreview.fromRecord({ name: 'John', age: '99', }) @@ -683,7 +711,6 @@ export async function setupProofsTest(faberName: string, aliceName: string, auto ], }, }) - const faberReplay = new ReplaySubject() const aliceReplay = new ReplaySubject() diff --git a/packages/core/tests/proofs.test.ts b/packages/core/tests/proofs.test.ts index c9f8a65169..38c96c03ce 100644 --- a/packages/core/tests/proofs.test.ts +++ b/packages/core/tests/proofs.test.ts @@ -106,7 +106,6 @@ describe('Present Proof', () => { threadId: aliceProofRecord.threadId, state: ProofState.PresentationReceived, }) - expect(JsonTransformer.toJSON(faberProofRecord)).toMatchObject({ createdAt: expect.any(String), state: ProofState.PresentationReceived, @@ -279,7 +278,7 @@ describe('Present Proof', () => { mimeType: 'application/json', }, ], - attachments: [ + appendedAttachments: [ { id: 'zQmfDXo7T3J43j3CTkEZaz7qdHuABhWktksZ7JEBueZ5zUS', filename: 'picture-of-a-cat.png', diff --git a/packages/core/tests/wallet.test.ts b/packages/core/tests/wallet.test.ts index 581f6b51ff..aae1ea9660 100644 --- a/packages/core/tests/wallet.test.ts +++ b/packages/core/tests/wallet.test.ts @@ -2,20 +2,15 @@ import { tmpdir } from 'os' import path from 'path' import { Agent } from '../src/agent/Agent' +import { BasicMessageRepository, BasicMessageRecord, BasicMessageRole } from '../src/modules/basic-messages' import { KeyDerivationMethod } from '../src/types' import { uuid } from '../src/utils/uuid' +import { WalletInvalidKeyError } from '../src/wallet/error' +import { WalletDuplicateError } from '../src/wallet/error/WalletDuplicateError' +import { WalletNotFoundError } from '../src/wallet/error/WalletNotFoundError' import { getBaseConfig } from './helpers' -import { - BasicMessageRecord, - BasicMessageRepository, - BasicMessageRole, - WalletDuplicateError, - WalletInvalidKeyError, - WalletNotFoundError, -} from '@aries-framework/core' - const aliceConfig = getBaseConfig('wallet-tests-Alice') const bobConfig = getBaseConfig('wallet-tests-Bob') diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 962ff655df..9678d68e4a 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -31,7 +31,7 @@ "devDependencies": { "@types/indy-sdk-react-native": "npm:@types/indy-sdk@^1.16.16", "@types/react-native": "^0.64.10", - "indy-sdk-react-native": "^0.1.21", + "indy-sdk-react-native": "^0.2.0", "react": "17.0.1", "react-native": "0.64.2", "react-native-fs": "^2.18.0", @@ -40,7 +40,7 @@ "typescript": "~4.3.0" }, "peerDependencies": { - "indy-sdk-react-native": "^0.1.21", + "indy-sdk-react-native": "^0.2.0", "react-native-fs": "^2.18.0", "react-native-get-random-values": "^1.7.0" } diff --git a/tests/e2e-test.ts b/tests/e2e-test.ts index c6e5172743..a4f4e5f755 100644 --- a/tests/e2e-test.ts +++ b/tests/e2e-test.ts @@ -3,7 +3,7 @@ import type { Agent } from '@aries-framework/core' import { issueCredential, makeConnection, prepareForIssuance, presentProof } from '../packages/core/tests/helpers' import { - CredentialPreview, + V1CredentialPreview, AttributeFilter, CredentialState, MediationState, @@ -48,7 +48,7 @@ export async function e2eTest({ issuerConnectionId: senderRecipientConnection.id, credentialTemplate: { credentialDefinitionId: definition.id, - preview: CredentialPreview.fromRecord({ + preview: V1CredentialPreview.fromRecord({ name: 'John', age: '25', // year month day diff --git a/yarn.lock b/yarn.lock index 9e40296ae2..f1ea05bf10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,11 +3,12 @@ "@ampproject/remapping@^2.1.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" - integrity sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg== + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== dependencies: - "@jridgewell/trace-mapping" "^0.3.0" + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" "@azure/core-asynciterator-polyfill@^1.0.0": version "1.0.2" @@ -28,40 +29,40 @@ dependencies: "@babel/highlight" "^7.16.7" -"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.17.0", "@babel/compat-data@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.7.tgz#078d8b833fbbcc95286613be8c716cef2b519fa2" - integrity sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ== +"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.17.0", "@babel/compat-data@^7.17.10": + version "7.17.10" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.10.tgz#711dc726a492dfc8be8220028b1b92482362baab" + integrity sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw== "@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": - version "7.17.8" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.8.tgz#3dac27c190ebc3a4381110d46c80e77efe172e1a" - integrity sha512-OdQDV/7cRBtJHLSOBqqbYNkOcydOgnX59TZx4puf41fzcVtN3e/4yqY8lMQsK+5X2lJtAdmA+6OHqsj1hBJ4IQ== + version "7.17.10" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.10.tgz#74ef0fbf56b7dfc3f198fc2d927f4f03e12f4b05" + integrity sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.7" - "@babel/helper-compilation-targets" "^7.17.7" + "@babel/generator" "^7.17.10" + "@babel/helper-compilation-targets" "^7.17.10" "@babel/helper-module-transforms" "^7.17.7" - "@babel/helpers" "^7.17.8" - "@babel/parser" "^7.17.8" + "@babel/helpers" "^7.17.9" + "@babel/parser" "^7.17.10" "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.3" - "@babel/types" "^7.17.0" + "@babel/traverse" "^7.17.10" + "@babel/types" "^7.17.10" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.1.2" + json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.17.3", "@babel/generator@^7.17.7", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.7.tgz#8da2599beb4a86194a3b24df6c085931d9ee45ad" - integrity sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w== +"@babel/generator@^7.17.10", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": + version "7.17.10" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.10.tgz#c281fa35b0c349bbe9d02916f4ae08fc85ed7189" + integrity sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg== dependencies: - "@babel/types" "^7.17.0" + "@babel/types" "^7.17.10" + "@jridgewell/gen-mapping" "^0.1.0" jsesc "^2.5.1" - source-map "^0.5.0" "@babel/helper-annotate-as-pure@^7.16.7": version "7.16.7" @@ -78,25 +79,25 @@ "@babel/helper-explode-assignable-expression" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz#a3c2924f5e5f0379b356d4cfb313d1414dc30e46" - integrity sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w== +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.17.10": + version "7.17.10" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz#09c63106d47af93cf31803db6bc49fef354e2ebe" + integrity sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ== dependencies: - "@babel/compat-data" "^7.17.7" + "@babel/compat-data" "^7.17.10" "@babel/helper-validator-option" "^7.16.7" - browserslist "^4.17.5" + browserslist "^4.20.2" semver "^6.3.0" "@babel/helper-create-class-features-plugin@^7.16.7": - version "7.17.6" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz#3778c1ed09a7f3e65e6d6e0f6fbfcc53809d92c9" - integrity sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg== + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.9.tgz#71835d7fb9f38bd9f1378e40a4c0902fdc2ea49d" + integrity sha512-kUjip3gruz6AJKOq5i3nC6CoCEEF/oHH3cp6tOZhB+IyyyPyW0g1Gfsxn3mkk6S08pIA2y8GQh609v9G/5sHVQ== dependencies: "@babel/helper-annotate-as-pure" "^7.16.7" "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.16.7" - "@babel/helper-member-expression-to-functions" "^7.16.7" + "@babel/helper-function-name" "^7.17.9" + "@babel/helper-member-expression-to-functions" "^7.17.7" "@babel/helper-optimise-call-expression" "^7.16.7" "@babel/helper-replace-supers" "^7.16.7" "@babel/helper-split-export-declaration" "^7.16.7" @@ -137,21 +138,13 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-function-name@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz#f1ec51551fb1c8956bc8dd95f38523b6cf375f8f" - integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA== +"@babel/helper-function-name@^7.16.7", "@babel/helper-function-name@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" + integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== dependencies: - "@babel/helper-get-function-arity" "^7.16.7" "@babel/template" "^7.16.7" - "@babel/types" "^7.16.7" - -"@babel/helper-get-function-arity@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" - integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw== - dependencies: - "@babel/types" "^7.16.7" + "@babel/types" "^7.17.0" "@babel/helper-hoist-variables@^7.16.7": version "7.16.7" @@ -160,7 +153,7 @@ dependencies: "@babel/types" "^7.16.7" -"@babel/helper-member-expression-to-functions@^7.16.7": +"@babel/helper-member-expression-to-functions@^7.16.7", "@babel/helper-member-expression-to-functions@^7.17.7": version "7.17.7" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz#a34013b57d8542a8c4ff8ba3f747c02452a4d8c4" integrity sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw== @@ -242,28 +235,28 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== -"@babel/helpers@^7.17.8": - version "7.17.8" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.8.tgz#288450be8c6ac7e4e44df37bcc53d345e07bc106" - integrity sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw== +"@babel/helpers@^7.17.9": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.9.tgz#b2af120821bfbe44f9907b1826e168e819375a1a" + integrity sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q== dependencies: "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.3" + "@babel/traverse" "^7.17.9" "@babel/types" "^7.17.0" "@babel/highlight@^7.10.4", "@babel/highlight@^7.16.7": - version "7.16.10" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" - integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.9.tgz#61b2ee7f32ea0454612def4fccdae0de232b73e3" + integrity sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg== dependencies: "@babel/helper-validator-identifier" "^7.16.7" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.3", "@babel/parser@^7.17.8": - version "7.17.8" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.8.tgz#2817fb9d885dd8132ea0f8eb615a6388cca1c240" - integrity sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.10": + version "7.17.10" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.10.tgz#873b16db82a8909e0fbd7f115772f4b739f6ce78" + integrity sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ== "@babel/plugin-proposal-class-properties@^7.0.0", "@babel/plugin-proposal-class-properties@^7.1.0": version "7.16.7" @@ -430,9 +423,9 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.16.7", "@babel/plugin-syntax-typescript@^7.7.2": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz#39c9b55ee153151990fb038651d58d3fd03f98f8" - integrity sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A== + version "7.17.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.10.tgz#80031e6042cad6a95ed753f672ebd23c30933195" + integrity sha512-xJefea1DWXW09pW4Tm9bjwVlPDyYA2it3fWlmEjpYz6alPvTUjL0EOzNzI/FEOyI3r4/J7uVH5UqKgl1TQ5hqQ== dependencies: "@babel/helper-plugin-utils" "^7.16.7" @@ -532,9 +525,9 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.1.0": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.7.tgz#d86b217c8e45bb5f2dbc11eefc8eab62cf980d19" - integrity sha512-ITPmR2V7MqioMJyrxUo2onHNC3e+MvfFiFIR0RP21d3PtlVb6sfzoxNKiphSZUOM9hEIdzCcZe83ieX3yoqjUA== + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.9.tgz#274be1a2087beec0254d4abd4d86e52442e1e5b6" + integrity sha512-2TBFd/r2I6VlYn0YRTz2JdazS+FoUuQ2rIFHoAxtyP/0G3D82SBLaRq9rnUkpqlLg03Byfl/+M32mpxjO6KaPw== dependencies: "@babel/helper-module-transforms" "^7.17.7" "@babel/helper-plugin-utils" "^7.16.7" @@ -603,16 +596,16 @@ "@babel/types" "^7.17.0" "@babel/plugin-transform-regenerator@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz#9e7576dc476cb89ccc5096fff7af659243b4adeb" - integrity sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q== + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.17.9.tgz#0a33c3a61cf47f45ed3232903683a0afd2d3460c" + integrity sha512-Lc2TfbxR1HOyn/c6b4Y/b6NHoTb67n/IoWLxTu4kC7h4KQnWlhCq2S8Tx0t2SVvv5Uu87Hs+6JEJ5kt2tYGylQ== dependencies: - regenerator-transform "^0.14.2" + regenerator-transform "^0.15.0" "@babel/plugin-transform-runtime@^7.0.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz#0a2e08b5e2b2d95c4b1d3b3371a2180617455b70" - integrity sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A== + version "7.17.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.10.tgz#b89d821c55d61b5e3d3c3d1d636d8d5a81040ae1" + integrity sha512-6jrMilUAJhktTr56kACL8LnWC5hx3Lf27BS0R0DSyW/OoJfb/iTHeE96V3b1dgKG3FSFdd/0culnYWMkjcKCig== dependencies: "@babel/helper-module-imports" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" @@ -697,9 +690,9 @@ source-map-support "^0.5.16" "@babel/runtime@^7.8.4": - version "7.17.8" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" - integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA== + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72" + integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg== dependencies: regenerator-runtime "^0.13.4" @@ -712,26 +705,26 @@ "@babel/parser" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.17.3", "@babel/traverse@^7.7.2": - version "7.17.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57" - integrity sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.17.10", "@babel/traverse@^7.17.3", "@babel/traverse@^7.17.9", "@babel/traverse@^7.7.2": + version "7.17.10" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.10.tgz#1ee1a5ac39f4eac844e6cf855b35520e5eb6f8b5" + integrity sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw== dependencies: "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.3" + "@babel/generator" "^7.17.10" "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.16.7" + "@babel/helper-function-name" "^7.17.9" "@babel/helper-hoist-variables" "^7.16.7" "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/parser" "^7.17.3" - "@babel/types" "^7.17.0" + "@babel/parser" "^7.17.10" + "@babel/types" "^7.17.10" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" - integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== +"@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.17.10", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.17.10" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.10.tgz#d35d7b4467e439fcf06d195f8100e0fea7fc82c4" + integrity sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A== dependencies: "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" @@ -782,9 +775,9 @@ integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== "@hapi/hoek@^9.0.0": - version "9.2.1" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17" - integrity sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw== + version "9.3.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== "@hapi/topo@^5.0.0": version "5.1.0" @@ -1015,20 +1008,33 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/resolve-uri@^3.0.3": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c" - integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew== + version "3.0.7" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" + integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== + +"@jridgewell/set-array@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" + integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== "@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.11" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" - integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== + version "1.4.13" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" + integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== -"@jridgewell/trace-mapping@^0.3.0": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3" - integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ== +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.10" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.10.tgz#db436f0917d655393851bc258918c00226c9b183" + integrity sha512-Q0YbBd6OTsXm8Y21+YUSDXupHnodNC2M4O18jtd3iwJ3+vMZNdKGols0a9G6JOK0dcJ3IdUUHoh908ZI6qhk8Q== dependencies: "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" @@ -2041,9 +2047,9 @@ integrity sha512-0jbp4RxjYopTsIdLl+/Fy2TiwVYHy4mgeu07DG4b/LyM0OS/+lPP5c9sbnt/AMlnF6qz2JRZpPpGw1eMNS6A4w== "@sideway/address@^4.1.3": - version "4.1.3" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.3.tgz#d93cce5d45c5daec92ad76db492cc2ee3c64ab27" - integrity sha512-8ncEUtmnTsMmL7z1YPB47kPUq7LpKWJNFPsRzHiIajGC5uXlWGn+AmkYPcHNl8S4tcEGx+cnORnNYaw2wvL+LQ== + version "4.1.4" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" + integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== dependencies: "@hapi/hoek" "^9.0.0" @@ -2185,9 +2191,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": - version "7.14.2" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" - integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== + version "7.17.1" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.17.1.tgz#1a0e73e8c28c7e832656db372b779bfd2ef37314" + integrity sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA== dependencies: "@babel/types" "^7.3.0" @@ -2284,9 +2290,9 @@ buffer "^6.0.0" "@types/inquirer@^8.1.3": - version "8.2.0" - resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.0.tgz#b9566d048f5ff65159f2ed97aff45fe0f00b35ec" - integrity sha512-BNoMetRf3gmkpAlV5we+kxyZTle7YibdOntIZbU5pyIfMdcwy784KfeZDAcuyMznkh5OLa17RVXZOGA5LTlkgQ== + version "8.2.1" + resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.1.tgz#28a139be3105a1175e205537e8ac10830e38dbf4" + integrity sha512-wKW3SKIUMmltbykg4I5JzCVzUhkuD9trD6efAmYgN2MrSntY0SMRQzEnD3mkyJ/rv9NLbTC7g3hKKE86YwEDLw== dependencies: "@types/through" "*" rxjs "^7.2.0" @@ -2319,9 +2325,9 @@ pretty-format "^26.0.0" "@types/json-schema@*", "@types/json-schema@^7.0.7": - version "7.0.10" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.10.tgz#9b05b7896166cd00e9cbd59864853abf65d9ac23" - integrity sha512-BLO9bBq59vW3fxCpD4o0N4U+DXsvwvIcl+jofw0frQo/GrBFC+/jRZj1E7kgp6dvTyNmA4y6JCV5Id/r3mNP5A== + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/json5@^0.0.29": version "0.0.29" @@ -2377,14 +2383,14 @@ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/prettier@^2.1.5": - version "2.4.4" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.4.tgz#5d9b63132df54d8909fce1c3f8ca260fdd693e17" - integrity sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA== + version "2.6.0" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.0.tgz#efcbd41937f9ae7434c714ab698604822d890759" + integrity sha512-G/AdOadiZhnJp0jXCaBQU449W2h716OW/EoXeYkCytxKL06X1WCXB4DZpp8TpZ8eyIJVS1cw4lrlkkSYU21cDw== "@types/prop-types@*": - version "15.7.4" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" - integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== + version "15.7.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== "@types/qs@*": version "6.9.7" @@ -2404,9 +2410,9 @@ "@types/react" "*" "@types/react@*": - version "17.0.41" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.41.tgz#6e179590d276394de1e357b3f89d05d7d3da8b85" - integrity sha512-chYZ9ogWUodyC7VUTRBfblysKLjnohhFY9bGLwvnUFFy48+vB9DikmB3lW0qTFmBcKSzmdglcvkHK71IioOlDA== + version "18.0.8" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.8.tgz#a051eb380a9fbcaa404550543c58e1cf5ce4ab87" + integrity sha512-+j2hk9BzCOrrOSJASi5XiOyBbERk9jG5O73Ya4M0env5Ixi6vUNli4qy994AINcEF+1IEHISYFfIT4zwr++LKw== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -2457,9 +2463,9 @@ integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/validator@^13.1.3": - version "13.7.1" - resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.1.tgz#cdab1b4779f6b1718a08de89d92d2603b71950cb" - integrity sha512-I6OUIZ5cYRk5lp14xSOAiXjWrfVoMZVjDuevBYgQDYzZIjsf2CAISpEcXOkFAtpAHbmWIDLcZObejqny/9xq5Q== + version "13.7.2" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.2.tgz#a2114225d9be743fb154b06c29b8257aaca42922" + integrity sha512-KFcchQ3h0OPQgFirBRPZr5F/sVjxZsOrQHedj3zi8AH3Zv/hOLx2OLR4hxR5HcfoU+33n69ZuOfzthKVdMoTiw== "@types/varint@^6.0.0": version "6.0.0" @@ -2573,9 +2579,9 @@ JSONStream@^1.0.4: through ">=2.2.7 <3" abab@^2.0.3, abab@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" - integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== abbrev@1: version "1.1.1" @@ -2631,9 +2637,9 @@ acorn@^7.1.1, acorn@^7.4.0: integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.2.4, acorn@^8.4.1: - version "8.7.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" - integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== add-stream@^1.0.0: version "1.0.0" @@ -2675,9 +2681,9 @@ ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: uri-js "^4.2.2" ajv@^8.0.1: - version "8.10.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.10.0.tgz#e573f719bd3af069017e3b66538ab968d040e54d" - integrity sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw== + version "8.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" + integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" @@ -2839,13 +2845,13 @@ array-ify@^1.0.0: integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= array-includes@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9" - integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw== + version "3.1.5" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" + integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" + define-properties "^1.1.4" + es-abstract "^1.19.5" get-intrinsic "^1.1.1" is-string "^1.0.7" @@ -2870,13 +2876,14 @@ array-unique@^0.3.2: integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= array.prototype.flat@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13" - integrity sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg== + version "1.3.0" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz#0b0c1567bf57b38b56b4c97b8aa72ab45e4adc7b" + integrity sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.19.0" + es-abstract "^1.19.2" + es-shim-unscopables "^1.0.0" arrify@^1.0.1: version "1.0.1" @@ -3161,21 +3168,23 @@ bn.js@^5.2.0: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== -body-parser@1.19.2: - version "1.19.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e" - integrity sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw== +body-parser@1.20.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" + integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== dependencies: bytes "3.1.2" content-type "~1.0.4" debug "2.6.9" - depd "~1.1.2" - http-errors "1.8.1" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.9.7" - raw-body "2.4.3" + on-finished "2.4.1" + qs "6.10.3" + raw-body "2.5.1" type-is "~1.6.18" + unpipe "1.0.0" borc@^3.0.0: version "3.0.0" @@ -3197,10 +3206,10 @@ bplist-creator@0.1.0: dependencies: stream-buffers "2.2.x" -bplist-parser@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.3.0.tgz#ba50666370f61bbf94881636cd9f7d23c5286090" - integrity sha512-zgmaRvT6AN1JpPPV+S0a1/FAtoxSreYDccZGIqEMSvZl9DMe70mJ7MFzpxa1X+gHVdkToE2haRUHHMiW1OdejA== +bplist-parser@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.3.1.tgz#e1c90b2ca2a9f9474cc72f6862bbf3fee8341fd1" + integrity sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA== dependencies: big-integer "1.6.x" @@ -3228,7 +3237,7 @@ braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1: +braces@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -3240,15 +3249,15 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.17.5, browserslist@^4.19.1: - version "4.20.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88" - integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA== +browserslist@^4.20.2, browserslist@^4.20.3: + version "4.20.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf" + integrity sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg== dependencies: - caniuse-lite "^1.0.30001317" - electron-to-chromium "^1.4.84" + caniuse-lite "^1.0.30001332" + electron-to-chromium "^1.4.118" escalade "^3.1.1" - node-releases "^2.0.2" + node-releases "^2.0.3" picocolors "^1.0.0" bs-logger@0.x: @@ -3393,10 +3402,10 @@ camelcase@^6.0.0, camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001317: - version "1.0.30001319" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001319.tgz#eb4da4eb3ecdd409f7ba1907820061d56096e88f" - integrity sha512-xjlIAFHucBRSMUo1kb5D4LYgcN1M45qdKP++lhqowDpwJwGkpIRTt5qQqnhxjj1vHcI7nrJxWhCC1ATrCEBTcw== +caniuse-lite@^1.0.30001332: + version "1.0.30001338" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001338.tgz#b5dd7a7941a51a16480bdf6ff82bded1628eec0d" + integrity sha512-1gLHWyfVoRDsHieO+CaeYe7jSo/MT7D7lhaXUiwwbuR5BwQxORs0f1tAwUSQr3YbxRXJvxHM/PA5FfPQRnsPeQ== capture-exit@^2.0.0: version "2.0.0" @@ -3846,10 +3855,10 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== copy-descriptor@^0.1.0: version "0.1.1" @@ -3857,11 +3866,11 @@ copy-descriptor@^0.1.0: integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= core-js-compat@^3.21.0: - version "3.21.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.21.1.tgz#cac369f67c8d134ff8f9bd1623e3bc2c42068c82" - integrity sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g== + version "3.22.4" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.22.4.tgz#d700f451e50f1d7672dcad0ac85d910e6691e579" + integrity sha512-dIWcsszDezkFZrfm1cnB4f/J85gyhiCpxbgBdohWCDtSVuAaChTSpPV7ldOQf/Xds2U5xCIJZOK82G4ZPAIswA== dependencies: - browserslist "^4.19.1" + browserslist "^4.20.3" semver "7.0.0" core-util-is@1.0.2: @@ -3984,9 +3993,9 @@ dateformat@^3.0.0: integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== dayjs@^1.8.15: - version "1.11.0" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.0.tgz#009bf7ef2e2ea2d5db2e6583d2d39a4b5061e805" - integrity sha512-JLC809s6Y948/FuCZPm5IX8rRhQwOiyMb2TfVVQEixG7P8Lm/gt5S7yoQZmC8x1UehI9Pb7sksEt4xx14m+7Ug== + version "1.11.1" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.1.tgz#90b33a3dda3417258d48ad2771b415def6545eb0" + integrity sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA== debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" @@ -3995,7 +4004,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -4064,12 +4073,13 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" -define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== +define-properties@^1.1.3, define-properties@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" + integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== dependencies: - object-keys "^1.0.12" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" define-property@^0.2.5: version "0.2.5" @@ -4108,7 +4118,12 @@ denodeify@^1.2.1: resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631" integrity sha1-OjYof1A05pnnV3kBBSwubJQlFjE= -depd@^1.1.2, depd@~1.1.2: +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +depd@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= @@ -4118,10 +4133,10 @@ deprecation@^2.0.0, deprecation@^2.3.1: resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== detect-indent@^5.0.0: version "5.0.0" @@ -4139,17 +4154,17 @@ detect-newline@^3.0.0: integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== dezalgo@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" - integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY= + version "1.0.4" + resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" + integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== dependencies: asap "^2.0.0" wrappy "1" did-resolver@^3.1.3, did-resolver@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-3.1.5.tgz#1a82a00fa96d64085676183bff40ebc13c88cd6a" - integrity sha512-/4lM1vK5osnWVZ2oN9QhlWV5xOwssuLSL1MvueBc8LQWotbD5kM9XQMe7h4GydYpbh3JaWMFkOWwc9jvSZ+qgg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-3.2.0.tgz#b89edd0dd70ad6f1c65ca1285472e021c2239707" + integrity sha512-8YiTRitfGt9hJYDIzjc254gXgJptO4zq6Q2BMZMNqkbCf9EFkV6BD4QIh5BUF4YjBglBgJY+duQRzO3UZAlZsw== diff-sequences@^26.6.2: version "26.6.2" @@ -4231,10 +4246,10 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.4.84: - version "1.4.89" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.89.tgz#33c06592812a17a7131873f4596579084ce33ff8" - integrity sha512-z1Axg0Fu54fse8wN4fd+GAINdU5mJmLtcl6bqIcYyzNVGONcfHAeeJi88KYMQVKalhXlYuVPzKkFIU5VD0raUw== +electron-to-chromium@^1.4.118: + version "1.4.136" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.136.tgz#b6a3595a9c29d6d8f60e092d40ac24f997e4e7ef" + integrity sha512-GnITX8rHnUrIVnTxU9UlsTnSemHUA2iF+6QrRqxFbp/mf0vfuSc/goEyyQhUX3TUUCE3mv/4BNuXOtaJ4ur0eA== emittery@^0.8.1: version "0.8.1" @@ -4309,31 +4324,41 @@ errorhandler@^1.5.0: accepts "~1.3.7" escape-html "~1.0.3" -es-abstract@^1.19.0, es-abstract@^1.19.1: - version "1.19.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" - integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w== +es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5: + version "1.20.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.0.tgz#b2d526489cceca004588296334726329e0a6bfb6" + integrity sha512-URbD8tgRthKD3YcC39vbvSDrX23upXnPcnGAjQfgxXF5ID75YcENawc9ZX/9iTP9ptUyfCLIxTTuMYoRfiOVKA== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + function.prototype.name "^1.1.5" get-intrinsic "^1.1.1" get-symbol-description "^1.0.0" has "^1.0.3" - has-symbols "^1.0.2" + has-property-descriptors "^1.0.0" + has-symbols "^1.0.3" internal-slot "^1.0.3" is-callable "^1.2.4" - is-negative-zero "^2.0.1" + is-negative-zero "^2.0.2" is-regex "^1.1.4" - is-shared-array-buffer "^1.0.1" + is-shared-array-buffer "^1.0.2" is-string "^1.0.7" - is-weakref "^1.0.1" - object-inspect "^1.11.0" + is-weakref "^1.0.2" + object-inspect "^1.12.0" object-keys "^1.1.1" object.assign "^4.1.2" - string.prototype.trimend "^1.0.4" - string.prototype.trimstart "^1.0.4" - unbox-primitive "^1.0.1" + regexp.prototype.flags "^1.4.1" + string.prototype.trimend "^1.0.5" + string.prototype.trimstart "^1.0.5" + unbox-primitive "^1.0.2" + +es-shim-unscopables@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" + integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== + dependencies: + has "^1.0.3" es-to-primitive@^1.2.1: version "1.2.1" @@ -4395,17 +4420,17 @@ eslint-import-resolver-node@^0.3.6: resolve "^1.20.0" eslint-import-resolver-typescript@^2.4.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.5.0.tgz#07661966b272d14ba97f597b51e1a588f9722f0a" - integrity sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ== + version "2.7.1" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.7.1.tgz#a90a4a1c80da8d632df25994c4c5fdcdd02b8751" + integrity sha512-00UbgGwV8bSgUv34igBDbTOtKhqoRMy9bFjNehT40bXg6585PNIct8HhXZ0SybqB9rWtXj9crcku8ndDn/gIqQ== dependencies: - debug "^4.3.1" - glob "^7.1.7" - is-glob "^4.0.1" - resolve "^1.20.0" - tsconfig-paths "^3.9.0" + debug "^4.3.4" + glob "^7.2.0" + is-glob "^4.0.3" + resolve "^1.22.0" + tsconfig-paths "^3.14.1" -eslint-module-utils@^2.7.2: +eslint-module-utils@^2.7.3: version "2.7.3" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee" integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ== @@ -4414,23 +4439,23 @@ eslint-module-utils@^2.7.2: find-up "^2.1.0" eslint-plugin-import@^2.23.4: - version "2.25.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" - integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== + version "2.26.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" + integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== dependencies: array-includes "^3.1.4" array.prototype.flat "^1.2.5" debug "^2.6.9" doctrine "^2.1.0" eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.2" + eslint-module-utils "^2.7.3" has "^1.0.3" - is-core-module "^2.8.0" + is-core-module "^2.8.1" is-glob "^4.0.3" - minimatch "^3.0.4" + minimatch "^3.1.2" object.values "^1.1.5" - resolve "^1.20.0" - tsconfig-paths "^3.12.0" + resolve "^1.22.0" + tsconfig-paths "^3.14.1" eslint-plugin-prettier@^3.4.0: version "3.4.1" @@ -4642,37 +4667,38 @@ expect@^27.5.1: jest-message-util "^27.5.1" express@^4.17.1: - version "4.17.3" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" - integrity sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg== + version "4.18.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" + integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.19.2" + body-parser "1.20.0" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.4.2" + cookie "0.5.0" cookie-signature "1.0.6" debug "2.6.9" - depd "~1.1.2" + depd "2.0.0" encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "~1.1.2" + finalhandler "1.2.0" fresh "0.5.2" + http-errors "2.0.0" merge-descriptors "1.0.1" methods "~1.1.2" - on-finished "~2.3.0" + on-finished "2.4.1" parseurl "~1.3.3" path-to-regexp "0.1.7" proxy-addr "~2.0.7" - qs "6.9.7" + qs "6.10.3" range-parser "~1.2.1" safe-buffer "5.2.1" - send "0.17.2" - serve-static "1.14.2" + send "0.18.0" + serve-static "1.15.0" setprototypeof "1.2.0" - statuses "~1.5.0" + statuses "2.0.1" type-is "~1.6.18" utils-merge "1.0.1" vary "~1.1.2" @@ -4838,7 +4864,7 @@ filter-obj@^1.1.0: resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs= -finalhandler@1.1.2, finalhandler@~1.1.2: +finalhandler@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== @@ -4851,6 +4877,19 @@ finalhandler@1.1.2, finalhandler@~1.1.2: statuses "~1.5.0" unpipe "~1.0.0" +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + find-cache-dir@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" @@ -4896,9 +4935,9 @@ flatted@^3.1.0: integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== flow-parser@0.*: - version "0.174.1" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.174.1.tgz#bb81e17fe45a1e64d9752090e819a6744a539fa0" - integrity sha512-nDMOvlFR+4doLpB3OJpseHZ7uEr3ENptlF6qMas/kzQmNcLzMwfQeFX0gGJ/+em7UdldB/nGsk55tDTOvjbCuw== + version "0.177.0" + resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.177.0.tgz#653092470c8e79ed737cb38e3be1d1de0d25feac" + integrity sha512-Ac1OwHjSoUALrcnHTTD6oaEPITaxYmP34iiEEcuCxeeD+tOKR7/Toaw4RpJKcDmYxLX79ZP9E7z+Q8ze9pESbQ== flow-parser@^0.121.0: version "0.121.0" @@ -5007,15 +5046,30 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function.prototype.name@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" + integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + functions-have-names "^1.2.2" + functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -gauge@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.3.tgz#286cf105c1962c659f0963058fb05116c1b82d3f" - integrity sha512-ICw1DhAwMtb22rYFwEHgJcx1JCwJGv3x6G0OQUq56Nge+H4Q8JEwr8iveS0XFlsUNSI67F5ffMGK25bK4Pmskw== +functions-have-names@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== dependencies: aproba "^1.0.3 || ^2.0.0" color-support "^1.1.3" @@ -5179,7 +5233,7 @@ glob-parent@^5.1.1, glob-parent@^5.1.2: dependencies: is-glob "^4.0.1" -glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7: +glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== @@ -5216,9 +5270,9 @@ globby@^11.0.2, globby@^11.0.3: slash "^3.0.0" graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: - version "4.2.9" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" - integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== handlebars@^4.7.7: version "4.7.7" @@ -5250,10 +5304,10 @@ hard-rejection@^2.1.0: resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== -has-bigints@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" - integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== has-flag@^3.0.0: version "3.0.0" @@ -5265,7 +5319,14 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.1, has-symbols@^1.0.2: +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== @@ -5361,15 +5422,15 @@ http-cache-semantics@^4.1.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== -http-errors@1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" - integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== dependencies: - depd "~1.1.2" + depd "2.0.0" inherits "2.0.4" setprototypeof "1.2.0" - statuses ">= 1.5.0 < 2" + statuses "2.0.1" toidentifier "1.0.1" http-proxy-agent@^4.0.1: @@ -5391,9 +5452,9 @@ http-signature@~1.2.0: sshpk "^1.7.0" https-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" - integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== dependencies: agent-base "6" debug "4" @@ -5490,17 +5551,17 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indy-sdk-react-native@^0.1.21: - version "0.1.21" - resolved "https://registry.yarnpkg.com/indy-sdk-react-native/-/indy-sdk-react-native-0.1.21.tgz#19f8cfd2afbe10775cef4f48682499dd963b9e02" - integrity sha512-AvOle3xfY3xWfwQe4fit2Pxmx0dKAuamm1L+nOoLRMVzNQNvdMdH8oxXs8eSUWh/7F6raL+ghAG3B4OEEeH3DA== +indy-sdk-react-native@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/indy-sdk-react-native/-/indy-sdk-react-native-0.2.0.tgz#9f02dc29726b22b5f90aa602b6a0d15cbd26b48b" + integrity sha512-eipyH5GzQFjTf89sMCSMy5axbl3uVDln79LOH2rpqN2cb+80Pzb3tMFYWb9TaU4jMKYzlaEE0RsuQoS151g2jQ== dependencies: buffer "^6.0.2" indy-sdk@^1.16.0-dev-1636: - version "1.16.0-dev-1649" - resolved "https://registry.yarnpkg.com/indy-sdk/-/indy-sdk-1.16.0-dev-1649.tgz#e2c781d11356b60c4497b0ac515435ed9a5be4be" - integrity sha512-bbByZ/JUqR5LBi9yoiExXjmBEQqrNsb6hm0ts2lcVshBdN6DPYayVgmwjS7E7jMbBG5wFy/zk04HZVuVCYPlMw== + version "1.16.0-dev-1655" + resolved "https://registry.yarnpkg.com/indy-sdk/-/indy-sdk-1.16.0-dev-1655.tgz#098c38df4a6eb4e13f89c0b86ebe9636944b71e0" + integrity sha512-MSWRY8rdnGAegs4v4AnzE6CT9O/3JBMUiE45I0Ihj2DMuH+XS1EJZUQEJsyis6aOQzRavv/xVtaBC8o+6azKuw== dependencies: bindings "^1.3.1" nan "^2.11.1" @@ -5643,10 +5704,10 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-core-module@^2.5.0, is-core-module@^2.8.0, is-core-module@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" - integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== +is-core-module@^2.5.0, is-core-module@^2.8.1: + version "2.9.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" + integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== dependencies: has "^1.0.3" @@ -5745,15 +5806,15 @@ is-lambda@^1.0.1: resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU= -is-negative-zero@^2.0.1: +is-negative-zero@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== is-number-object@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" - integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== dependencies: has-tostringtag "^1.0.0" @@ -5809,10 +5870,12 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-shared-array-buffer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" - integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA== +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" is-ssh@^1.3.0: version "1.3.3" @@ -5857,7 +5920,7 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -is-weakref@^1.0.1: +is-weakref@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== @@ -5912,9 +5975,9 @@ istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" - integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== + version "5.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" + integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== dependencies: "@babel/core" "^7.12.3" "@babel/parser" "^7.14.7" @@ -6584,7 +6647,7 @@ json-text-sequence@~0.3.0: dependencies: "@sovpro/delimited-stream" "^1.1.0" -json5@2.x, json5@^2.1.2: +json5@2.x, json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== @@ -6742,9 +6805,9 @@ libnpmpublish@^4.0.0: ssri "^8.0.1" libphonenumber-js@^1.9.7: - version "1.9.50" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.50.tgz#f5028a2c4cc47a69d69a0de3629afad97a613712" - integrity sha512-cCzQPChw2XbordcO2LKiw5Htx5leHVfFk/EXkxNHqJfFo7Fndcb1kF5wPJpc316vCJhhikedYnVysMh3Sc7Ocw== + version "1.9.52" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.52.tgz#662ea92dcb6761ceb2dc9a8cd036aadd355bc999" + integrity sha512-8k83chc+zMj+J/RkaBxi0PpSTAdzHmpqzCMqquSJVRfbZFr8DCp6vPC7ms2PIPGxeqajZLI6CBLW5nLCJCJrYg== lines-and-columns@^1.1.6: version "1.2.4" @@ -7305,12 +7368,12 @@ micromatch@^3.1.10, micromatch@^3.1.4: to-regex "^3.0.2" micromatch@^4.0.2, micromatch@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: - braces "^3.0.1" - picomatch "^2.2.3" + braces "^3.0.2" + picomatch "^2.3.1" mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": version "1.52.0" @@ -7349,7 +7412,7 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -minimatch@^3.0.2, minimatch@^3.0.4: +minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -7365,7 +7428,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: +minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== @@ -7465,11 +7528,11 @@ mkdirp-infer-owner@^2.0.0: mkdirp "^1.0.3" mkdirp@^0.5.1, mkdirp@^0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== dependencies: - minimist "^1.2.5" + minimist "^1.2.6" mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" @@ -7637,10 +7700,10 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= -node-releases@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" - integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== +node-releases@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.4.tgz#f38252370c43854dc48aa431c766c6c398f40476" + integrity sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ== node-stream-zip@^1.9.1: version "1.15.0" @@ -7812,13 +7875,13 @@ npmlog@^4.1.2: set-blocking "~2.0.0" npmlog@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.1.tgz#06f1344a174c06e8de9c6c70834cfba2964bba17" - integrity sha512-BTHDvY6nrRHuRfyjt1MAufLxYdVXZfd099H4+i1f0lPywNQyI4foeNXJRObB/uy+TYqUW0vAD9gbdSOXPst7Eg== + version "6.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== dependencies: are-we-there-yet "^3.0.0" console-control-strings "^1.1.0" - gauge "^4.0.0" + gauge "^4.0.3" set-blocking "^2.0.0" nullthrows@^1.1.1: @@ -7860,12 +7923,12 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@^1.10.3, object-inspect@^1.11.0, object-inspect@^1.9.0: +object-inspect@^1.10.3, object-inspect@^1.12.0, object-inspect@^1.9.0: version "1.12.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== -object-keys@^1.0.12, object-keys@^1.1.1: +object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== @@ -7912,6 +7975,13 @@ object.values@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -8247,7 +8317,7 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.3: +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -8291,7 +8361,7 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -plist@^3.0.1, plist@^3.0.4: +plist@^3.0.1, plist@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.5.tgz#2cbeb52d10e3cdccccf0c11a63a85d830970a987" integrity sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA== @@ -8322,9 +8392,9 @@ prettier-linter-helpers@^1.0.0: fast-diff "^1.1.2" prettier@^2.3.1: - version "2.6.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.0.tgz#12f8f504c4d8ddb76475f441337542fa799207d4" - integrity sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A== + version "2.6.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032" + integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew== pretty-format@^26.0.0, pretty-format@^26.5.2, pretty-format@^26.6.2: version "26.6.2" @@ -8440,12 +8510,7 @@ q@^1.5.1: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= -qs@6.9.7: - version "6.9.7" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" - integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw== - -qs@^6.9.4: +qs@6.10.3, qs@^6.9.4: version "6.10.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== @@ -8492,20 +8557,20 @@ range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c" - integrity sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g== +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== dependencies: bytes "3.1.2" - http-errors "1.8.1" + http-errors "2.0.0" iconv-lite "0.4.24" unpipe "1.0.0" react-devtools-core@^4.6.0: - version "4.24.1" - resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.24.1.tgz#d274390db86898d779b6202a318fe6422eb046bb" - integrity sha512-skar+cqSg5Oz89n4lQ/aBQS8RGj93FMufg2TrMJqE+RSUTO9nLEYawRMXXCs8PnDVRSfG5pPVU5Nt1OegNflyA== + version "4.24.5" + resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.24.5.tgz#99b611ecb6dbe96d5de362ca2d1b0a1b9da06e0a" + integrity sha512-zr1LbHB5N5XjSNSCbxbfDNW8vryQdt2zYs8qnk5YacvFcwrMXGGXYoQ4gdu0rKe4mB/5MQYwgFse6IlyQiZwVw== dependencies: shell-quote "^1.6.1" ws "^7" @@ -8530,17 +8595,17 @@ react-native-codegen@^0.0.6: nullthrows "^1.1.1" react-native-fs@^2.18.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/react-native-fs/-/react-native-fs-2.19.0.tgz#5747eb52a5a3d2b31c8fb76f5f8044d0a855122c" - integrity sha512-Yl09IbETkV5UJcBtVtBLttyTmiAhJIHpGA/LvredI5dYiw3MXMMVu42bzELiuH2Bwj7F+qd0fMNvgfBDiDxd2A== + version "2.20.0" + resolved "https://registry.yarnpkg.com/react-native-fs/-/react-native-fs-2.20.0.tgz#05a9362b473bfc0910772c0acbb73a78dbc810f6" + integrity sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ== dependencies: base-64 "^0.1.0" utf8 "^3.0.0" react-native-get-random-values@^1.7.0: - version "1.7.2" - resolved "https://registry.yarnpkg.com/react-native-get-random-values/-/react-native-get-random-values-1.7.2.tgz#60a9b6497d22e713779b71139f016a5fcec7ac04" - integrity sha512-28KRYGpIG/upV8+k/qFA+TwGW+yGjmtOHaCduJHpOQK1QUTyhiA6E2IgL4UvvU2dybeCTYFmUi9wcEQ0GiWe5g== + version "1.8.0" + resolved "https://registry.yarnpkg.com/react-native-get-random-values/-/react-native-get-random-values-1.8.0.tgz#1cb4bd4bd3966a356e59697b8f372999fe97cb16" + integrity sha512-H/zghhun0T+UIJLmig3+ZuBCvF66rdbiWUfRSNS6kv5oDSpa1ZiVyvRWtuPesQpT8dXj+Bv7WJRQOUP+5TB1sA== dependencies: fast-base64-decode "^1.0.0" @@ -8786,10 +8851,10 @@ regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== -regenerator-transform@^0.14.2: - version "0.14.5" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" - integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== +regenerator-transform@^0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537" + integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg== dependencies: "@babel/runtime" "^7.8.4" @@ -8801,6 +8866,15 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +regexp.prototype.flags@^1.4.1: + version "1.4.3" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" + integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + functions-have-names "^1.2.2" + regexpp@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" @@ -8918,7 +8992,7 @@ resolve.exports@^1.1.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0: +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== @@ -9083,9 +9157,9 @@ semver@7.0.0: integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== semver@7.x, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" @@ -9094,39 +9168,39 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -send@0.17.2: - version "0.17.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" - integrity sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww== +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== dependencies: debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" + depd "2.0.0" + destroy "1.2.0" encodeurl "~1.0.2" escape-html "~1.0.3" etag "~1.8.1" fresh "0.5.2" - http-errors "1.8.1" + http-errors "2.0.0" mime "1.6.0" ms "2.1.3" - on-finished "~2.3.0" + on-finished "2.4.1" range-parser "~1.2.1" - statuses "~1.5.0" + statuses "2.0.1" serialize-error@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a" integrity sha1-ULZ51WNc34Rme9yOWa9OW4HV9go= -serve-static@1.14.2, serve-static@^1.13.1: - version "1.14.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.2.tgz#722d6294b1d62626d41b43a013ece4598d292bfa" - integrity sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ== +serve-static@1.15.0, serve-static@^1.13.1: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== dependencies: encodeurl "~1.0.2" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.17.2" + send "0.18.0" set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" @@ -9218,13 +9292,13 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== simple-plist@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/simple-plist/-/simple-plist-1.3.0.tgz#f451997663eafd8ea6bad353a01caf49ef186d43" - integrity sha512-uYWpeGFtZtVt2NhG4AHgpwx323zxD85x42heMJBan1qAiqqozIlaGrwrEt6kRjXWRWIXsuV1VLCvVmZan2B5dg== + version "1.3.1" + resolved "https://registry.yarnpkg.com/simple-plist/-/simple-plist-1.3.1.tgz#16e1d8f62c6c9b691b8383127663d834112fb017" + integrity sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw== dependencies: bplist-creator "0.1.0" - bplist-parser "0.3.0" - plist "^3.0.4" + bplist-parser "0.3.1" + plist "^3.0.5" sisteransi@^1.0.5: version "1.0.5" @@ -9304,15 +9378,15 @@ socks-proxy-agent@^5.0.0: socks "^2.3.3" socks-proxy-agent@^6.0.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz#e664e8f1aaf4e1fb3df945f09e3d94f911137f87" - integrity sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew== + version "6.2.0" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.0.tgz#f6b5229cc0cbd6f2f202d9695f09d871e951c85e" + integrity sha512-wWqJhjb32Q6GsrUqzuFkukxb/zzide5quXYcMVpIjxalDBBYy2nqKCFQ/9+Ie4dvOYSQdOk3hUlZSdzZOd3zMQ== dependencies: agent-base "^6.0.2" - debug "^4.3.1" - socks "^2.6.1" + debug "^4.3.3" + socks "^2.6.2" -socks@^2.3.3, socks@^2.6.1: +socks@^2.3.3, socks@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.2.tgz#ec042d7960073d40d94268ff3bb727dc685f111a" integrity sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA== @@ -9358,7 +9432,7 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== -source-map@^0.5.0, source-map@^0.5.6: +source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -9479,7 +9553,12 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.5.0 < 2", statuses@~1.5.0: +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= @@ -9520,21 +9599,23 @@ string-width@^1.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string.prototype.trimend@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" - integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== +string.prototype.trimend@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" + integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" + define-properties "^1.1.4" + es-abstract "^1.19.5" -string.prototype.trimstart@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" - integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== +string.prototype.trimstart@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" + integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" + define-properties "^1.1.4" + es-abstract "^1.19.5" string_decoder@^1.1.1: version "1.3.0" @@ -9869,9 +9950,9 @@ trim-newlines@^3.0.0: integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== ts-jest@^27.0.3: - version "27.1.3" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.3.tgz#1f723e7e74027c4da92c0ffbd73287e8af2b2957" - integrity sha512-6Nlura7s6uM9BVUAoqLH7JHyMXjz8gluryjpPXxr3IxZdAXnU6FhjvVLHFtfd1vsE1p8zD1OJfskkc0jhTSnkA== + version "27.1.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.4.tgz#84d42cf0f4e7157a52e7c64b1492c46330943e00" + integrity sha512-qjkZlVPWVctAezwsOD1OPzbZ+k7zA5z3oxII4dGdZo5ggX/PL7kvwTM0pXTr10fAtbiVpJaL3bWd502zAhpgSQ== dependencies: bs-logger "0.x" fast-json-stable-stringify "2.x" @@ -9901,14 +9982,14 @@ ts-node@^10.0.0, ts-node@^10.4.0: v8-compile-cache-lib "^3.0.0" yn "3.1.1" -tsconfig-paths@^3.12.0, tsconfig-paths@^3.9.0: - version "3.14.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.0.tgz#4fcc48f9ccea8826c41b9ca093479de7f5018976" - integrity sha512-cg/1jAZoL57R39+wiw4u/SCC6Ic9Q5NqjBOb+9xISedOYurfog9ZNmKJSxAnb2m/5Bq4lE9lhUcau33Ml8DM0g== +tsconfig-paths@^3.14.1, tsconfig-paths@^3.9.0: + version "3.14.1" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" + integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== dependencies: "@types/json5" "^0.0.29" json5 "^1.0.1" - minimist "^1.2.0" + minimist "^1.2.6" strip-bom "^3.0.0" tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: @@ -9917,9 +9998,9 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== tslib@^2.0.1, tslib@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== tslog@^3.2.0: version "3.3.3" @@ -10042,9 +10123,9 @@ uglify-es@^3.1.9: source-map "~0.6.1" uglify-js@^3.1.4: - version "3.15.3" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.15.3.tgz#9aa82ca22419ba4c0137642ba0df800cb06e0471" - integrity sha512-6iCVm2omGJbsu3JWac+p6kUiOpg3wFO2f8lIXjfEb8RrmLjzog1wTPMmwKB7swfzzqxj9YM+sGUM++u1qN4qJg== + version "3.15.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.15.4.tgz#fa95c257e88f85614915b906204b9623d4fa340d" + integrity sha512-vMOPGDuvXecPs34V74qDKk4iJ/SN4vL3Ow/23ixafENYvtrNvtbcgUeugTcUGRGsOF/5fU8/NYSL5Hyb3l1OJA== uid-number@0.0.6: version "0.0.6" @@ -10061,14 +10142,14 @@ umask@^1.1.0: resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= -unbox-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" - integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== dependencies: - function-bind "^1.1.1" - has-bigints "^1.0.1" - has-symbols "^1.0.2" + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" unicode-canonical-property-names-ecmascript@^2.0.0: @@ -10164,11 +10245,16 @@ urix@^0.1.0: integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= use-subscription@^1.0.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1" - integrity sha512-Xv2a1P/yReAjAbhylMfFplFKj9GssgTwN7RlcTxBujFQcloStWNDQdc4g4NRWH9xS4i/FDk04vQBptAXoF3VcA== + version "1.7.0" + resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.7.0.tgz#c7505263315deac9fd2581cdf4ab1e3ff2585d0f" + integrity sha512-87x6MjiIVE/BWqtxfiRvM6jfvGudN+UeVOnWi7qKYp2c0YJn5+Z5Jt0kZw6Tt+8hs7kw/BWo2WBhizJSAZsQJA== dependencies: - object-assign "^4.1.1" + use-sync-external-store "^1.1.0" + +use-sync-external-store@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz#3343c3fe7f7e404db70f8c687adf5c1652d34e82" + integrity sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ== use@^3.1.0: version "3.1.1" @@ -10208,9 +10294,9 @@ uuid@^8.3.2: integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== v8-compile-cache-lib@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz#0582bcb1c74f3a2ee46487ceecf372e46bce53e8" - integrity sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA== + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== v8-compile-cache@^2.0.3: version "2.3.0" @@ -10299,9 +10385,9 @@ wcwidth@^1.0.0, wcwidth@^1.0.1: defaults "^1.0.3" web-did-resolver@^2.0.8: - version "2.0.12" - resolved "https://registry.yarnpkg.com/web-did-resolver/-/web-did-resolver-2.0.12.tgz#3413b988c2ab9d52378be7aa22ef457d70c48e21" - integrity sha512-bidL5bPn8CYFM33sfh465iLcgTbkNpfAlmpWkSC69D24fXnAY36tbMfhnehqIut+VCKZqIqeeZZl5ACanF5/+A== + version "2.0.16" + resolved "https://registry.yarnpkg.com/web-did-resolver/-/web-did-resolver-2.0.16.tgz#23e6607a6a068218ff8403d967b8a70af2e0cc25" + integrity sha512-PNGO9nP8H1mTxBRzg/AdzB40HXHhQ99BMCMEQYLK1fatohdmEDetJglgTFwavKQEbBexDG3xknCIzryWD7iS0A== dependencies: cross-fetch "^3.1.2" did-resolver "^3.1.5"