@verana-labs/vs-agent-sdk 2.0.0-dev.8 → 2.0.0-dev.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +0 -2
  2. package/build/adminApi/AdminApiError.d.ts +28 -0
  3. package/build/adminApi/AdminApiError.js +60 -0
  4. package/build/adminApi/AdminApiError.js.map +1 -0
  5. package/build/adminApi/didcommModules.d.ts +2 -0
  6. package/build/adminApi/didcommModules.js +8 -0
  7. package/build/adminApi/didcommModules.js.map +1 -0
  8. package/build/adminApi/index.d.ts +4 -0
  9. package/build/adminApi/index.js +21 -0
  10. package/build/adminApi/index.js.map +1 -0
  11. package/build/adminApi/moduleEndpoint.d.ts +8 -0
  12. package/build/adminApi/moduleEndpoint.js +28 -0
  13. package/build/adminApi/moduleEndpoint.js.map +1 -0
  14. package/build/adminApi/pagination/PageDto.d.ts +7 -0
  15. package/build/adminApi/pagination/PageDto.js +50 -0
  16. package/build/adminApi/pagination/PageDto.js.map +1 -0
  17. package/build/adminApi/pagination/PaginationQueryDto.d.ts +11 -0
  18. package/build/adminApi/pagination/PaginationQueryDto.js +46 -0
  19. package/build/adminApi/pagination/PaginationQueryDto.js.map +1 -0
  20. package/build/adminApi/pagination/cursor.d.ts +28 -0
  21. package/build/adminApi/pagination/cursor.js +64 -0
  22. package/build/adminApi/pagination/cursor.js.map +1 -0
  23. package/build/adminApi/pagination/index.d.ts +4 -0
  24. package/build/adminApi/pagination/index.js +21 -0
  25. package/build/adminApi/pagination/index.js.map +1 -0
  26. package/build/adminApi/pagination/paginate.d.ts +27 -0
  27. package/build/adminApi/pagination/paginate.js +77 -0
  28. package/build/adminApi/pagination/paginate.js.map +1 -0
  29. package/build/agent/VsAgent.d.ts +22 -34
  30. package/build/agent/VsAgent.js +84 -205
  31. package/build/agent/VsAgent.js.map +1 -1
  32. package/build/agent/createVsAgent.d.ts +5 -6
  33. package/build/agent/createVsAgent.js +3 -5
  34. package/build/agent/createVsAgent.js.map +1 -1
  35. package/build/blockchain/AnonCredsTrustService.d.ts +62 -0
  36. package/build/blockchain/AnonCredsTrustService.js +256 -0
  37. package/build/blockchain/AnonCredsTrustService.js.map +1 -0
  38. package/build/blockchain/AuthorizationService.d.ts +5 -3
  39. package/build/blockchain/AuthorizationService.js +17 -29
  40. package/build/blockchain/AuthorizationService.js.map +1 -1
  41. package/build/blockchain/IndexerWebSocketService.d.ts +7 -1
  42. package/build/blockchain/IndexerWebSocketService.js +68 -36
  43. package/build/blockchain/IndexerWebSocketService.js.map +1 -1
  44. package/build/blockchain/VeranaChainService.d.ts +38 -27
  45. package/build/blockchain/VeranaChainService.js +169 -177
  46. package/build/blockchain/VeranaChainService.js.map +1 -1
  47. package/build/blockchain/VeranaIndexerService.d.ts +14 -1
  48. package/build/blockchain/VeranaIndexerService.js +122 -8
  49. package/build/blockchain/VeranaIndexerService.js.map +1 -1
  50. package/build/blockchain/feePreflight.d.ts +32 -0
  51. package/build/blockchain/feePreflight.js +89 -0
  52. package/build/blockchain/feePreflight.js.map +1 -0
  53. package/build/blockchain/handlers/authorizationHandlers.js +2 -2
  54. package/build/blockchain/handlers/authorizationHandlers.js.map +1 -1
  55. package/build/blockchain/handlers/defaultHandlers.js +6 -1
  56. package/build/blockchain/handlers/defaultHandlers.js.map +1 -1
  57. package/build/blockchain/handlers/index.d.ts +1 -0
  58. package/build/blockchain/handlers/index.js +1 -0
  59. package/build/blockchain/handlers/index.js.map +1 -1
  60. package/build/blockchain/handlers/selfIssuanceHandlers.d.ts +13 -0
  61. package/build/blockchain/handlers/selfIssuanceHandlers.js +76 -0
  62. package/build/blockchain/handlers/selfIssuanceHandlers.js.map +1 -0
  63. package/build/blockchain/handlers/stateMutations.d.ts +26 -3
  64. package/build/blockchain/handlers/stateMutations.js +317 -41
  65. package/build/blockchain/handlers/stateMutations.js.map +1 -1
  66. package/build/blockchain/index.d.ts +4 -0
  67. package/build/blockchain/index.js +4 -0
  68. package/build/blockchain/index.js.map +1 -1
  69. package/build/blockchain/indexerMappers.d.ts +3 -0
  70. package/build/blockchain/indexerMappers.js +53 -0
  71. package/build/blockchain/indexerMappers.js.map +1 -0
  72. package/build/blockchain/triggerResolver.d.ts +26 -0
  73. package/build/blockchain/triggerResolver.js +120 -0
  74. package/build/blockchain/triggerResolver.js.map +1 -0
  75. package/build/blockchain/types.d.ts +13 -70
  76. package/build/blockchain/types.js.map +1 -1
  77. package/build/bootstrap/EcsBootstrapService.d.ts +13 -3
  78. package/build/bootstrap/EcsBootstrapService.js +172 -94
  79. package/build/bootstrap/EcsBootstrapService.js.map +1 -1
  80. package/build/connections/ParentConnectionModule.d.ts +20 -0
  81. package/build/connections/ParentConnectionModule.js +56 -0
  82. package/build/connections/ParentConnectionModule.js.map +1 -0
  83. package/build/credentials/FullTailsFileService.d.ts +0 -1
  84. package/build/credentials/FullTailsFileService.js +0 -49
  85. package/build/credentials/FullTailsFileService.js.map +1 -1
  86. package/build/did/CachedDocumentLoader.d.ts +3 -0
  87. package/build/did/CachedDocumentLoader.js +7 -1
  88. package/build/did/CachedDocumentLoader.js.map +1 -1
  89. package/build/did/CachedWebDidResolver.d.ts +0 -4
  90. package/build/did/CachedWebDidResolver.js +1 -5
  91. package/build/did/CachedWebDidResolver.js.map +1 -1
  92. package/build/did/artifactServices.d.ts +12 -0
  93. package/build/did/artifactServices.js +48 -0
  94. package/build/did/artifactServices.js.map +1 -0
  95. package/build/did/legacyDidWeb.d.ts +6 -4
  96. package/build/did/legacyDidWeb.js +17 -14
  97. package/build/did/legacyDidWeb.js.map +1 -1
  98. package/build/did/publicDid.d.ts +4 -0
  99. package/build/did/publicDid.js +19 -0
  100. package/build/did/publicDid.js.map +1 -0
  101. package/build/events/BaseMessageEvents.js +139 -5
  102. package/build/events/BaseMessageEvents.js.map +1 -1
  103. package/build/events/ConnectionEvents.js +150 -86
  104. package/build/events/ConnectionEvents.js.map +1 -1
  105. package/build/events/VsAgentEvents.d.ts +11 -9
  106. package/build/events/VsAgentEvents.js +8 -1
  107. package/build/events/VsAgentEvents.js.map +1 -1
  108. package/build/events/VtFlowEvents.d.ts +1 -1
  109. package/build/events/VtFlowEvents.js +29 -17
  110. package/build/events/VtFlowEvents.js.map +1 -1
  111. package/build/index.d.ts +7 -1
  112. package/build/index.js +15 -2
  113. package/build/index.js.map +1 -1
  114. package/build/plugins/setupBaseDidComm.js +9 -12
  115. package/build/plugins/setupBaseDidComm.js.map +1 -1
  116. package/build/transports/VsAgentWsInboundTransport.d.ts +1 -1
  117. package/build/transports/VsAgentWsInboundTransport.js +20 -14
  118. package/build/transports/VsAgentWsInboundTransport.js.map +1 -1
  119. package/build/types.d.ts +14 -13
  120. package/build/types.js +1 -2
  121. package/build/types.js.map +1 -1
  122. package/build/utils/agent.d.ts +29 -4
  123. package/build/utils/agent.js +91 -22
  124. package/build/utils/agent.js.map +1 -1
  125. package/build/utils/boundedFetch.d.ts +13 -0
  126. package/build/utils/boundedFetch.js +47 -0
  127. package/build/utils/boundedFetch.js.map +1 -0
  128. package/build/utils/didReadiness.d.ts +9 -0
  129. package/build/utils/didReadiness.js +42 -0
  130. package/build/utils/didReadiness.js.map +1 -0
  131. package/build/utils/ecsClaims.d.ts +46 -0
  132. package/build/utils/ecsClaims.js +115 -0
  133. package/build/utils/ecsClaims.js.map +1 -0
  134. package/build/utils/ecsService.d.ts +8 -0
  135. package/build/utils/ecsService.js +23 -0
  136. package/build/utils/ecsService.js.map +1 -0
  137. package/build/utils/index.d.ts +7 -0
  138. package/build/utils/index.js +7 -0
  139. package/build/utils/index.js.map +1 -1
  140. package/build/utils/publishedDidRecord.d.ts +15 -0
  141. package/build/utils/publishedDidRecord.js +83 -0
  142. package/build/utils/publishedDidRecord.js.map +1 -0
  143. package/build/utils/selfIssuedEcsCredential.d.ts +8 -0
  144. package/build/utils/selfIssuedEcsCredential.js +153 -0
  145. package/build/utils/selfIssuedEcsCredential.js.map +1 -0
  146. package/build/utils/setupSelfTr.d.ts +35 -74
  147. package/build/utils/setupSelfTr.js +93 -312
  148. package/build/utils/setupSelfTr.js.map +1 -1
  149. package/build/utils/trustCredentialStore.d.ts +78 -9
  150. package/build/utils/trustCredentialStore.js +324 -136
  151. package/build/utils/trustCredentialStore.js.map +1 -1
  152. package/build/utils/typeMetadata.d.ts +43 -0
  153. package/build/utils/typeMetadata.js +77 -0
  154. package/build/utils/typeMetadata.js.map +1 -0
  155. package/build/utils/util.d.ts +19 -0
  156. package/build/utils/util.js +24 -0
  157. package/build/utils/util.js.map +1 -1
  158. package/build/utils/vcdm2.d.ts +34 -0
  159. package/build/utils/vcdm2.js +60 -0
  160. package/build/utils/vcdm2.js.map +1 -0
  161. package/build/utils/vtjscResolver.d.ts +23 -0
  162. package/build/utils/vtjscResolver.js +53 -0
  163. package/build/utils/vtjscResolver.js.map +1 -0
  164. package/build/vtFlow/VtFlowOrchestrator.d.ts +107 -15
  165. package/build/vtFlow/VtFlowOrchestrator.js +678 -116
  166. package/build/vtFlow/VtFlowOrchestrator.js.map +1 -1
  167. package/build/vtFlow/assertVerifiableService.d.ts +1 -0
  168. package/build/vtFlow/assertVerifiableService.js +45 -5
  169. package/build/vtFlow/assertVerifiableService.js.map +1 -1
  170. package/build/vtFlow/ecsIssuanceExemption.d.ts +12 -0
  171. package/build/vtFlow/ecsIssuanceExemption.js +84 -0
  172. package/build/vtFlow/ecsIssuanceExemption.js.map +1 -0
  173. package/build/vtFlow/index.d.ts +1 -0
  174. package/build/vtFlow/index.js +1 -0
  175. package/build/vtFlow/index.js.map +1 -1
  176. package/package.json +18 -12
  177. package/build/did/migrateWebVhLog.d.ts +0 -34
  178. package/build/did/migrateWebVhLog.js +0 -230
  179. package/build/did/migrateWebVhLog.js.map +0 -1
  180. package/build/did/migrateWebVhVersionTime.d.ts +0 -9
  181. package/build/did/migrateWebVhVersionTime.js +0 -191
  182. package/build/did/migrateWebVhVersionTime.js.map +0 -1
@@ -3,10 +3,46 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VtFlowOrchestrator = void 0;
4
4
  const core_1 = require("@credo-ts/core");
5
5
  const credo_ts_didcomm_vt_flow_1 = require("@verana-labs/credo-ts-didcomm-vt-flow");
6
+ const verana_types_1 = require("@verana-labs/verana-types");
6
7
  const verre_1 = require("@verana-labs/verre");
8
+ const vs_agent_model_1 = require("@verana-labs/vs-agent-model");
9
+ const adminApi_1 = require("../adminApi");
10
+ const ecsIssuanceExemption_1 = require("./ecsIssuanceExemption");
11
+ const feePreflight_1 = require("../blockchain/feePreflight");
7
12
  const types_1 = require("../blockchain/types");
8
13
  const types_2 = require("../types");
9
14
  const utils_1 = require("../utils");
15
+ // The chain carries a fee discount as an integer from 0 to 10000. The API and the indexer carry it
16
+ // as a decimal from 0 to 1, so only the message is scaled.
17
+ const DISCOUNT_SCALE = 10000;
18
+ const TX_LOOKUP_TIMEOUT_MS = 60000;
19
+ const TX_LOOKUP_INTERVAL_MS = 3000;
20
+ // the notification handler and the tx lookup can both reach markValidated for one flow at the same time
21
+ const markingValidated = new Set();
22
+ const FEE_KEYS = ['validationFees', 'issuanceFees', 'verificationFees'];
23
+ const DISCOUNT_KEYS = ['issuanceFeeDiscount', 'verificationFeeDiscount'];
24
+ function invalidInput(message) {
25
+ return new adminApi_1.AdminApiError(adminApi_1.AdminApiErrorCode.InvalidInput, 400, message);
26
+ }
27
+ function invalidState(message) {
28
+ return new adminApi_1.AdminApiError(adminApi_1.AdminApiErrorCode.InvalidState, 409, message);
29
+ }
30
+ // AUTHZ-CHECK-3 step 1. A record's expiration is only its budget clock, not a validity window.
31
+ function isActiveParticipant(participant) {
32
+ const now = Date.now();
33
+ if (!participant.effective_from || Date.parse(participant.effective_from) > now)
34
+ return false;
35
+ if (participant.effective_until && Date.parse(participant.effective_until) <= now)
36
+ return false;
37
+ return !participant.revoked && !participant.slashed;
38
+ }
39
+ function sameTerm(key, given, onEntry) {
40
+ const scale = DISCOUNT_KEYS.includes(key) ? DISCOUNT_SCALE : 1;
41
+ return Math.round(given * scale) === Math.round(onEntry * scale);
42
+ }
43
+ function errorMessage(error) {
44
+ return error instanceof Error ? error.message : String(error);
45
+ }
10
46
  class VtFlowOrchestrator {
11
47
  constructor(agent, options = {}) {
12
48
  this.agent = agent;
@@ -14,10 +50,10 @@ class VtFlowOrchestrator {
14
50
  }
15
51
  async startOnboardingProcess(input) {
16
52
  var _a, _b, _c, _d;
17
- const chain = this.requireChain();
53
+ this.requireChain();
18
54
  if (!this.agent.did)
19
55
  throw new Error('Agent has no public DID');
20
- const holderParticipant = await chain.getParticipant(input.applicantParticipantId);
56
+ const holderParticipant = await this.agent.indexer.findParticipant(input.applicantParticipantId);
21
57
  if (!holderParticipant) {
22
58
  throw new Error(`Applicant participant ${input.applicantParticipantId} not found on chain`);
23
59
  }
@@ -37,7 +73,7 @@ class VtFlowOrchestrator {
37
73
  if (!holderParticipant.validatorParticipantId) {
38
74
  throw new Error(`Applicant participant ${input.applicantParticipantId} has no validator_participant_id`);
39
75
  }
40
- const validatorParticipant = await chain.getParticipant(Number(holderParticipant.validatorParticipantId));
76
+ const validatorParticipant = await this.agent.indexer.findParticipant(Number(holderParticipant.validatorParticipantId));
41
77
  if (!(validatorParticipant === null || validatorParticipant === void 0 ? void 0 : validatorParticipant.did)) {
42
78
  throw new Error(`Validator participant ${holderParticipant.validatorParticipantId} not resolvable`);
43
79
  }
@@ -47,46 +83,58 @@ class VtFlowOrchestrator {
47
83
  // Renewals resend the OR with the same session id so the validator re-attaches the finished flow.
48
84
  const vtFlowApi = this.resolveVtFlowApi();
49
85
  const [latest] = (await vtFlowApi.findAllByQuery({
50
- participantId: String(holderParticipant.id),
86
+ applicantParticipantId: String(holderParticipant.id),
51
87
  role: credo_ts_didcomm_vt_flow_1.VtFlowRole.Applicant,
52
88
  flowVariant: credo_ts_didcomm_vt_flow_1.VtFlowVariant.OnboardingProcess,
53
89
  }))
54
90
  .filter(record => !(0, credo_ts_didcomm_vt_flow_1.isVtFlowTerminalState)(record.state))
55
91
  .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
56
- if (latest && latest.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.Completed && latest.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.CredRevoked) {
57
- this.agent.config.logger.info(`[vt-flow] onboarding flow ${latest.id} for participant ${holderParticipant.id} is already in progress (state ${latest.state}); not resending`);
58
- return latest;
59
- }
60
- const existing = latest;
92
+ const running = latest !== undefined && !(0, credo_ts_didcomm_vt_flow_1.isVtFlowRenewable)(latest);
61
93
  let connectionId;
62
- if (existing) {
63
- const connection = await this.agent.didcomm.connections.findById(existing.connectionId);
64
- if (connection === null || connection === void 0 ? void 0 : connection.isReady)
94
+ if (latest) {
95
+ const connection = await this.agent.didcomm.connections.findById(latest.connectionId);
96
+ if (connection && (0, utils_1.isUsableConnectionTo)(connection, validatorParticipant.did))
65
97
  connectionId = connection.id;
66
98
  }
99
+ const undelivered = running && latest.state === credo_ts_didcomm_vt_flow_1.VtFlowState.OrSent && holderParticipant.opState === types_1.ValidationState.PENDING;
100
+ if (running && connectionId && !undelivered) {
101
+ this.agent.config.logger.info(`[vt-flow] onboarding flow ${latest.id} for participant ${holderParticipant.id} is in progress (state ${latest.state}) on an open connection, not resending`);
102
+ return latest;
103
+ }
67
104
  if (!connectionId) {
68
- const { connectionRecord } = await this.agent.didcomm.oob.receiveImplicitInvitation({
69
- did: validatorParticipant.did,
70
- ourDid: this.agent.did,
71
- label: this.agent.label,
72
- didCommVersion: 'v2',
73
- });
74
- if (!connectionRecord)
75
- throw new Error('Failed to establish DIDComm connection to validator');
76
- const ready = await this.agent.didcomm.connections.returnWhenIsConnected(connectionRecord.id);
77
- connectionId = ready.id;
105
+ connectionId = await (0, utils_1.connectToPublicDid)(this.agent, validatorParticipant.did);
106
+ }
107
+ if (running) {
108
+ this.agent.config.logger.info(`[vt-flow] resending the onboarding request of flow ${latest.id} (state ${latest.state}) on ${connectionId === latest.connectionId ? 'its' : 'a new'} connection`);
109
+ return vtFlowApi.resendOnboardingRequest({ vtFlowRecordId: latest.id, connectionId });
78
110
  }
79
111
  return vtFlowApi.sendOnboardingRequest({
80
112
  connectionId,
81
- participantSessionId: (_b = (_a = input.participantSessionId) !== null && _a !== void 0 ? _a : existing === null || existing === void 0 ? void 0 : existing.participantSessionId) !== null && _b !== void 0 ? _b : core_1.utils.uuid(),
82
- participantId: String(holderParticipant.id),
113
+ participantSessionId: (_b = (_a = input.participantSessionId) !== null && _a !== void 0 ? _a : latest === null || latest === void 0 ? void 0 : latest.participantSessionId) !== null && _b !== void 0 ? _b : core_1.utils.uuid(),
114
+ applicantParticipantId: String(holderParticipant.id),
115
+ applicantParticipantRole: Number(holderParticipant.role),
116
+ validatorParticipantId: String(holderParticipant.validatorParticipantId),
117
+ schemaId: String(holderParticipant.schemaId),
83
118
  agentParticipantId: String((_c = this.options.agentParticipantId) !== null && _c !== void 0 ? _c : 0),
84
119
  walletAgentParticipantId: String((_d = this.options.walletAgentParticipantId) !== null && _d !== void 0 ? _d : 0),
85
120
  claims: input.claims,
86
121
  });
87
122
  }
88
- async validateAndOfferCredential(input) {
89
- var _a;
123
+ /**
124
+ * Validate an onboarding process: record the outcome on-chain, accept the onboarding request and
125
+ * move the record to VALIDATED. It sends no credential. Only a HOLDER takes part in a credential
126
+ * exchange; an ISSUER, a VERIFIER or a grantor joins the Ecosystem, and SetParticipantOPToValidated
127
+ * is the whole of it. The caller decides what follows, from the role of the returned participant.
128
+ *
129
+ * For a HOLDER it builds the credential before the chain write, and returns it for
130
+ * offerOnboardingCredential. Claims that do not satisfy the schema, or a schema fetch that fails,
131
+ * must stop the process while the chain holds no outcome yet and the caller can repeat the call.
132
+ *
133
+ * A record that is already VALIDATED and has no credential exchange keeps the chain outcome and
134
+ * only builds the credential again. A repeat call thus re-drives an offer that did not complete.
135
+ */
136
+ async validateOnboardingProcess(input) {
137
+ var _a, _b;
90
138
  const chain = this.requireChain();
91
139
  if (!this.agent.did)
92
140
  throw new Error('Agent has no public DID');
@@ -99,43 +147,495 @@ class VtFlowOrchestrator {
99
147
  if (record.variant !== credo_ts_didcomm_vt_flow_1.VtFlowVariant.OnboardingProcess) {
100
148
  throw new Error(`Record variant is '${record.variant}', expected OnboardingProcess`);
101
149
  }
102
- if (record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingOr) {
103
- throw new Error(`Record state is '${record.state}', expected '${credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingOr}'`);
104
- }
105
- if (!record.participantId)
106
- throw new Error('Record has no participantId');
107
- const holderParticipantId = Number(record.participantId);
108
- const holderParticipant = await chain.getParticipant(holderParticipantId);
109
- if (!holderParticipant)
110
- throw new Error(`Holder participant ${holderParticipantId} not found on chain`);
111
- if (!holderParticipant.did)
112
- throw new Error('Holder participant has no DID');
113
- const unsignedCredentialJson = await this.buildCredential({
114
- credentialSchemaId: input.credentialSchemaId,
115
- subjectDid: holderParticipant.did,
116
- claims: ((_a = record.claims) !== null && _a !== void 0 ? _a : {}),
117
- credentialType: input.credentialType,
118
- credentialContext: input.credentialContext,
119
- });
150
+ const resumeOffer = record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.Validated && !record.credentialExchangeRecordId;
151
+ if (record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingOr && !resumeOffer) {
152
+ throw new Error(`Record state is '${record.state}', expected '${credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingOr}', or ` +
153
+ `'${credo_ts_didcomm_vt_flow_1.VtFlowState.Validated}' with no credential exchange`);
154
+ }
155
+ if (!record.applicantParticipantId)
156
+ throw new Error('Record has no applicantParticipantId');
157
+ const participantId = Number(record.applicantParticipantId);
158
+ const participant = await this.agent.indexer.findParticipant(participantId);
159
+ if (!participant)
160
+ throw new Error(`Applicant participant ${participantId} not found on chain`);
161
+ if (!participant.did)
162
+ throw new Error('Applicant participant has no DID');
163
+ const credential = Number(participant.role) === types_2.HOLDER_PARTICIPANT_TYPE
164
+ ? await this.buildCredential({
165
+ credentialSchemaId: (_a = input.credentialSchemaId) !== null && _a !== void 0 ? _a : String(participant.schemaId),
166
+ subjectDid: participant.did,
167
+ claims: ((_b = record.claims) !== null && _b !== void 0 ? _b : {}),
168
+ credentialType: input.credentialType,
169
+ credentialContext: input.credentialContext,
170
+ })
171
+ : undefined;
172
+ if (resumeOffer)
173
+ return { record, participant, credential };
120
174
  // op_summary_digest (MOD-PP-MSG-3) digests the applicant's submission, not the credential
121
- await chain.setParticipantOPToValidated({
122
- id: holderParticipantId,
123
- corporation: holderParticipant.corporation,
124
- });
175
+ await chain.setParticipantOPToValidated({ id: participantId, corporation: participant.corporation });
125
176
  await vtFlowApi.acceptOnboardingRequest(record.id);
126
- await vtFlowApi.markValidated(record.id);
177
+ const validated = await vtFlowApi.markValidated(record.id);
178
+ return { record: validated, participant, credential };
179
+ }
180
+ /**
181
+ * Offer the credential of an onboarding process to its applicant. Call it only for a HOLDER, and
182
+ * only after validateOnboardingProcess has moved the record to VALIDATED. Give it the credential
183
+ * that validateOnboardingProcess built, so that the process does not build the credential twice.
184
+ */
185
+ async offerOnboardingCredential(input) {
186
+ var _a, _b, _c, _d, _e;
187
+ this.requireChain();
188
+ const vtFlowApi = this.resolveVtFlowApi();
189
+ const record = await vtFlowApi.findById(input.vtFlowRecordId);
190
+ if (!record)
191
+ throw new Error(`vt-flow record ${input.vtFlowRecordId} not found`);
192
+ if (!record.applicantParticipantId)
193
+ throw new Error('Record has no applicantParticipantId');
194
+ const participant = (_a = input.participant) !== null && _a !== void 0 ? _a : (await this.agent.indexer.findParticipant(Number(record.applicantParticipantId)));
195
+ if (!(participant === null || participant === void 0 ? void 0 : participant.did))
196
+ throw new Error('Applicant participant has no DID');
197
+ const unsignedCredentialJson = (_b = input.credential) !== null && _b !== void 0 ? _b : (await this.buildCredential({
198
+ credentialSchemaId: (_c = input.credentialSchemaId) !== null && _c !== void 0 ? _c : String(participant.schemaId),
199
+ subjectDid: participant.did,
200
+ claims: ((_d = record.claims) !== null && _d !== void 0 ? _d : {}),
201
+ credentialType: input.credentialType,
202
+ credentialContext: input.credentialContext,
203
+ // [VSA-VTI-FLOW-FMT-1]: markValidated sets it from the entry on every pass, a renewal included
204
+ validUntil: (_e = record.validation) === null || _e === void 0 ? void 0 : _e.effectiveUntil,
205
+ }));
206
+ // W3C Data Integrity attachment format (RFC 0809). The applicant DID is the credential subject
207
+ // and the exchange runs over its authenticated connection, so no extra binding is required. The
208
+ // cryptosuite is the issuer's choice and is applied when the credential is issued.
127
209
  const { record: offered } = await vtFlowApi.offerCredentialForSession({
128
210
  vtFlowRecordId: record.id,
129
- issuerParticipantId: Number(holderParticipant.validatorParticipantId),
211
+ issuerParticipantId: Number(participant.validatorParticipantId),
130
212
  credentialFormats: {
131
- jsonld: {
132
- credential: unsignedCredentialJson,
133
- options: { proofType: 'Ed25519Signature2020', proofPurpose: 'assertionMethod' },
134
- },
213
+ dataIntegrity: { credential: unsignedCredentialJson, bindingRequired: false },
135
214
  },
136
215
  });
137
216
  return offered;
138
217
  }
218
+ /**
219
+ * Close an onboarding process that carries no credential exchange, on the validator side. The
220
+ * applicant reaches the same state from the SetParticipantOPToValidated chain event.
221
+ */
222
+ async completeOnboardingProcess(vtFlowRecordId) {
223
+ return this.resolveVtFlowApi().markCompleted(vtFlowRecordId);
224
+ }
225
+ /** [VSA-ADM-VT-FL-VALIDATE] */
226
+ async validateFlow(input) {
227
+ var _a, _b;
228
+ this.requireChain();
229
+ const vtFlowApi = this.resolveVtFlowApi();
230
+ const record = await vtFlowApi.findById(input.vtFlowRecordId);
231
+ if (!record) {
232
+ throw new adminApi_1.AdminApiError(adminApi_1.AdminApiErrorCode.UnknownId, 404, `no flow with id "${input.vtFlowRecordId}"`);
233
+ }
234
+ if (record.role !== credo_ts_didcomm_vt_flow_1.VtFlowRole.Validator)
235
+ throw invalidState('validate is a validator action');
236
+ const reissue = record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.CredOffered && ((_b = (_a = record.issuance) === null || _a === void 0 ? void 0 : _a.tx) === null || _b === void 0 ? void 0 : _b.status) === credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Failed;
237
+ if (reissue && record.credentialExchangeRecordId) {
238
+ const { record: issued } = await vtFlowApi.issueCredentialForSession({
239
+ vtFlowRecordId: record.id,
240
+ credentialExchangeRecordId: record.credentialExchangeRecordId,
241
+ });
242
+ return issued;
243
+ }
244
+ if (record.variant === credo_ts_didcomm_vt_flow_1.VtFlowVariant.DirectIssuance)
245
+ return this.validateDirectIssuance(record);
246
+ if (!record.applicantParticipantId)
247
+ throw invalidState('the flow names no applicant participant');
248
+ const applicant = await this.agent.indexer.getParticipant(record.applicantParticipantId);
249
+ const entryValidated = applicant.op_state === 'VALIDATED';
250
+ const rejectedInFlight = entryValidated && (await this.isRejectedInFlight(record));
251
+ if (!rejectedInFlight) {
252
+ this.assertValidateState(record, entryValidated);
253
+ if (record.connectionTerminated)
254
+ throw invalidState('the flow connection is TERMINATED until the applicant reconnects');
255
+ }
256
+ const terms = this.validationTerms(input, applicant, record);
257
+ await this.assertClaimsAndTerm(record, applicant, terms);
258
+ // [VSA-VTI-FLOW-OP-ISSUE-6]: the connection stays TERMINATED, so issuance waits for the applicant to reconnect
259
+ if (rejectedInFlight)
260
+ return this.markValidated(record.id, applicant);
261
+ if (record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.OobPending)
262
+ await this.sendValidating(record);
263
+ if (record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.ValidatedPendingClaims)
264
+ return this.continueAfterValidated(record.id);
265
+ if (entryValidated) {
266
+ if (record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.Validated)
267
+ await this.markValidated(record.id, applicant);
268
+ return this.continueAfterValidated(record.id);
269
+ }
270
+ const validation = Object.assign({ decidedAt: new Date().toISOString(), submission: await this.submissionPath(applicant) }, terms);
271
+ if (validation.submission === credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Operator) {
272
+ return vtFlowApi.recordValidation(record.id, validation, credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingValidationTx);
273
+ }
274
+ return this.submitValidation(record.id, applicant, validation);
275
+ }
276
+ // A Direct Issuance flow has no entry to validate, so no transaction: check the claims, then offer.
277
+ async validateDirectIssuance(record) {
278
+ if (record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.Validating && record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.OobPending) {
279
+ throw invalidState(`the flow is '${record.state}', which validate does not accept`);
280
+ }
281
+ if (!record.schemaId)
282
+ throw invalidState('the flow names no credential schema');
283
+ const connection = await this.agent.didcomm.connections.findById(record.connectionId);
284
+ if (!(connection === null || connection === void 0 ? void 0 : connection.theirDid))
285
+ throw invalidState('the flow connection has no peer DID');
286
+ const schema = await this.agent.indexer.getCredentialSchema(record.schemaId);
287
+ this.assertClaims(schema.json_schema, connection.theirDid, record.claims);
288
+ const vtFlowApi = this.resolveVtFlowApi();
289
+ if (record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.OobPending)
290
+ await this.sendValidating(record);
291
+ const offer = await this.buildDirectIssuanceOffer(record.id);
292
+ if (!offer)
293
+ throw invalidState('the agent holds no active ISSUER participant for the schema of the flow');
294
+ const { record: offered } = await vtFlowApi.offerCredentialForSession(Object.assign({ vtFlowRecordId: record.id }, offer));
295
+ return offered;
296
+ }
297
+ // [VSA-ADM-VT-FL-START]: checked before the flow moves, as a failed send would leave it in VALIDATING
298
+ async sendValidating(record) {
299
+ const connection = await this.agent.didcomm.connections.findById(record.connectionId);
300
+ if (!(connection === null || connection === void 0 ? void 0 : connection.isReady))
301
+ throw invalidState('the flow connection is not ESTABLISHED');
302
+ await this.resolveVtFlowApi().sendValidating(record.id);
303
+ }
304
+ assertClaims(jsonSchema, subjectDid, claims) {
305
+ const violations = (0, utils_1.schemaViolations)(JSON.parse(jsonSchema), Object.assign({ id: subjectDid }, (claims !== null && claims !== void 0 ? claims : {})));
306
+ if (violations.length > 0) {
307
+ throw new adminApi_1.AdminApiError(adminApi_1.AdminApiErrorCode.InvalidClaims, 422, 'the claim set does not satisfy the json_schema', { violations });
308
+ }
309
+ }
310
+ assertValidateState(record, entryValidated) {
311
+ const accepted = [
312
+ credo_ts_didcomm_vt_flow_1.VtFlowState.Validating,
313
+ credo_ts_didcomm_vt_flow_1.VtFlowState.OobPending,
314
+ credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxFailed,
315
+ credo_ts_didcomm_vt_flow_1.VtFlowState.ValidatedPendingClaims,
316
+ ];
317
+ // Once the entry is VALIDATED on chain any validator-side state resumes into issuance, which is
318
+ // how a backend that disabled the default notification handler drives the flow ([VSA-ADM-VT-FL-VALIDATE-11]).
319
+ const acceptedOnceValidated = [
320
+ credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingOr,
321
+ credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingValidationTx,
322
+ credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxSubmitted,
323
+ credo_ts_didcomm_vt_flow_1.VtFlowState.Validated,
324
+ ];
325
+ if (accepted.includes(record.state))
326
+ return;
327
+ if (entryValidated && acceptedOnceValidated.includes(record.state))
328
+ return;
329
+ throw invalidState(`the flow is '${record.state}', which validate does not accept`);
330
+ }
331
+ validationTerms(input, applicant, record) {
332
+ var _a, _b, _c, _d, _e, _f, _g, _h;
333
+ for (const key of FEE_KEYS) {
334
+ const fee = input[key];
335
+ if (fee !== undefined && !(Number.isInteger(fee) && fee >= 0)) {
336
+ throw invalidInput(`${key} must be a non-negative integer`);
337
+ }
338
+ }
339
+ for (const key of DISCOUNT_KEYS) {
340
+ const discount = input[key];
341
+ if (discount !== undefined && !(discount >= 0 && discount <= 1)) {
342
+ throw invalidInput(`${key} must be between 0 and 1`);
343
+ }
344
+ }
345
+ if (input.effectiveUntil !== undefined && Number.isNaN(Date.parse(input.effectiveUntil))) {
346
+ throw invalidInput('effectiveUntil must be an ISO 8601 datetime');
347
+ }
348
+ const onEntry = {
349
+ validationFees: (_a = applicant.validation_fees) !== null && _a !== void 0 ? _a : 0,
350
+ issuanceFees: (_b = applicant.issuance_fees) !== null && _b !== void 0 ? _b : 0,
351
+ verificationFees: (_c = applicant.verification_fees) !== null && _c !== void 0 ? _c : 0,
352
+ issuanceFeeDiscount: (_d = applicant.issuance_fee_discount) !== null && _d !== void 0 ? _d : 0,
353
+ verificationFeeDiscount: (_e = applicant.verification_fee_discount) !== null && _e !== void 0 ? _e : 0,
354
+ };
355
+ const renewal = !!applicant.effective_from;
356
+ const recorded = record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxFailed ? record.validation : undefined;
357
+ const terms = {
358
+ effectiveUntil: (_f = input.effectiveUntil) !== null && _f !== void 0 ? _f : recorded === null || recorded === void 0 ? void 0 : recorded.effectiveUntil,
359
+ opSummaryDigest: (_g = input.opSummaryDigest) !== null && _g !== void 0 ? _g : recorded === null || recorded === void 0 ? void 0 : recorded.opSummaryDigest,
360
+ };
361
+ for (const key of [...FEE_KEYS, ...DISCOUNT_KEYS]) {
362
+ const given = input[key];
363
+ if (renewal) {
364
+ if (given !== undefined && !sameTerm(key, given, onEntry[key])) {
365
+ throw invalidInput(`${key} differs from the entry, and a renewal keeps the agreed value`);
366
+ }
367
+ terms[key] = onEntry[key];
368
+ }
369
+ else {
370
+ terms[key] = (_h = given !== null && given !== void 0 ? given : recorded === null || recorded === void 0 ? void 0 : recorded[key]) !== null && _h !== void 0 ? _h : 0;
371
+ }
372
+ }
373
+ return terms;
374
+ }
375
+ async assertClaimsAndTerm(record, applicant, terms) {
376
+ var _a;
377
+ if (applicant.role !== types_1.ParticipantRole.Holder)
378
+ return;
379
+ if (!applicant.did)
380
+ throw invalidState('the applicant entry has no DID to issue to');
381
+ const schema = await this.agent.indexer.getCredentialSchema(applicant.schema_id);
382
+ this.assertClaims(schema.json_schema, applicant.did, record.claims);
383
+ // An ECS Organization or Persona credential needs a validUntil. With a validity period of 0 the
384
+ // VPR sets no effective_until, so the call has to carry one.
385
+ const needsValidUntil = (0, vs_agent_model_1.ecsRequiresValidUntil)(await (0, vs_agent_model_1.classifyEcsSchema)(schema.json_schema));
386
+ if (needsValidUntil && ((_a = schema.holder_validation_validity_period) !== null && _a !== void 0 ? _a : 0) === 0 && !terms.effectiveUntil) {
387
+ throw invalidInput('this schema has no validity period, so effectiveUntil is required');
388
+ }
389
+ }
390
+ async submissionPath(applicant) {
391
+ const authorization = this.agent.authorizationService;
392
+ if (!authorization || applicant.validator_participant_id == null)
393
+ return credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Operator;
394
+ const validator = await this.agent.indexer
395
+ .getParticipant(applicant.validator_participant_id)
396
+ .catch(() => undefined);
397
+ if (!validator || !isActiveParticipant(validator))
398
+ return credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Operator;
399
+ await authorization.refreshForOperator().catch(() => undefined);
400
+ const grant = authorization.getVsOperatorAuthorizationRecord(validator.id);
401
+ return (grant === null || grant === void 0 ? void 0 : grant.msgTypes.includes(verana_types_1.veranaTypeUrls.MsgSetParticipantOPToValidated))
402
+ ? credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Agent
403
+ : credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Operator;
404
+ }
405
+ async submitValidation(recordId, applicant, validation) {
406
+ var _a, _b;
407
+ const chain = this.requireChain();
408
+ const vtFlowApi = this.resolveVtFlowApi();
409
+ const fail = (reason, error) => vtFlowApi.recordValidation(recordId, Object.assign(Object.assign({}, validation), { tx: { status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Failed, reason, error } }), credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxFailed);
410
+ const message = chain.setParticipantOPToValidatedMsg({
411
+ id: applicant.id,
412
+ effectiveUntil: validation.effectiveUntil ? new Date(validation.effectiveUntil) : undefined,
413
+ validationFees: validation.validationFees,
414
+ issuanceFees: validation.issuanceFees,
415
+ verificationFees: validation.verificationFees,
416
+ issuanceFeeDiscount: Math.round(((_a = validation.issuanceFeeDiscount) !== null && _a !== void 0 ? _a : 0) * DISCOUNT_SCALE),
417
+ verificationFeeDiscount: Math.round(((_b = validation.verificationFeeDiscount) !== null && _b !== void 0 ? _b : 0) * DISCOUNT_SCALE),
418
+ opSummaryDigest: validation.opSummaryDigest,
419
+ });
420
+ const checked = await this.preflight(message, applicant.validator_participant_id);
421
+ if ('reason' in checked)
422
+ return fail(checked.reason, checked.error);
423
+ let hash;
424
+ try {
425
+ hash = await chain.broadcastWithoutWaiting([message], checked.fee);
426
+ }
427
+ catch (error) {
428
+ return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.BroadcastError, errorMessage(error));
429
+ }
430
+ const submitted = await vtFlowApi.recordValidation(recordId, Object.assign(Object.assign({}, validation), { tx: { hash, submittedAt: new Date().toISOString(), status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Submitted } }), credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxSubmitted);
431
+ void this.resolveValidationTx(recordId).catch(error => this.agent.config.logger.error(`[vt-flow] resolving the validation of ${recordId} failed`, {
432
+ error: errorMessage(error),
433
+ }));
434
+ return submitted;
435
+ }
436
+ // [VSA-ADM-VT-FL-VALIDATE-6]: the fee payer is the Corporation when the grant of the entry has with_feegrant
437
+ preflight(message, participantId) {
438
+ return (0, feePreflight_1.preflightFee)(this.requireChain(), message, (0, feePreflight_1.feeGranterFor)(this.agent, participantId));
439
+ }
440
+ // [VSA-VTI-FLOW-ISSUE-1]: [VSA-ADM-VT-FL-VALIDATE-6] to -9 applied to the anchoring, which the delivery waits for.
441
+ // A transaction left SUBMITTED by a restart is looked up again, not broadcast twice.
442
+ async anchor(record, message, issuerParticipantId) {
443
+ var _a, _b, _c;
444
+ const chain = this.requireChain();
445
+ const pending = ((_b = (_a = record.issuance) === null || _a === void 0 ? void 0 : _a.tx) === null || _b === void 0 ? void 0 : _b.status) === credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Submitted ? record.issuance.tx : undefined;
446
+ let hash = pending === null || pending === void 0 ? void 0 : pending.hash;
447
+ let submittedAt = (_c = pending === null || pending === void 0 ? void 0 : pending.submittedAt) !== null && _c !== void 0 ? _c : new Date().toISOString();
448
+ if (!hash) {
449
+ const checked = await this.preflight(message, issuerParticipantId);
450
+ if ('reason' in checked)
451
+ return Object.assign({ status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Failed }, checked);
452
+ try {
453
+ hash = await chain.broadcastWithoutWaiting([message], checked.fee);
454
+ }
455
+ catch (error) {
456
+ return {
457
+ status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Failed,
458
+ reason: credo_ts_didcomm_vt_flow_1.VtFlowTxReason.BroadcastError,
459
+ error: errorMessage(error),
460
+ };
461
+ }
462
+ submittedAt = new Date().toISOString();
463
+ await this.resolveVtFlowApi().recordIssuance(record.id, {
464
+ tx: { hash, submittedAt, status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Submitted },
465
+ });
466
+ }
467
+ for (;;) {
468
+ const tx = await chain.findTx(hash).catch(() => undefined);
469
+ if ((tx === null || tx === void 0 ? void 0 : tx.code) === 0)
470
+ return { hash, submittedAt, height: tx.height, status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Succeeded };
471
+ if (tx) {
472
+ const failure = { status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Failed, reason: credo_ts_didcomm_vt_flow_1.VtFlowTxReason.TxFailed, error: tx.rawLog };
473
+ return Object.assign({ hash, submittedAt, height: tx.height }, failure);
474
+ }
475
+ if (Date.now() - Date.parse(submittedAt) >= TX_LOOKUP_TIMEOUT_MS) {
476
+ const error = 'not found 60 seconds after the broadcast';
477
+ return { hash, submittedAt, status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Failed, reason: credo_ts_didcomm_vt_flow_1.VtFlowTxReason.TxNotFound, error };
478
+ }
479
+ await new Promise(resolve => setTimeout(resolve, TX_LOOKUP_INTERVAL_MS));
480
+ }
481
+ }
482
+ // [VSA-ADM-VT-FL-VALIDATE-8]. Leaving VALIDATION_TX_SUBMITTED ends the loop, so the notification
483
+ // handler moving the flow first wins the race.
484
+ async resolveValidationTx(recordId) {
485
+ var _a, _b, _c;
486
+ const chain = this.requireChain();
487
+ const vtFlowApi = this.resolveVtFlowApi();
488
+ let landed;
489
+ for (;;) {
490
+ const record = await vtFlowApi.findById(recordId);
491
+ const validation = record === null || record === void 0 ? void 0 : record.validation;
492
+ const hash = (_a = validation === null || validation === void 0 ? void 0 : validation.tx) === null || _a === void 0 ? void 0 : _a.hash;
493
+ if (!(record === null || record === void 0 ? void 0 : record.applicantParticipantId) || !validation || !hash)
494
+ return;
495
+ if (record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxSubmitted)
496
+ return;
497
+ const tx = landed !== null && landed !== void 0 ? landed : (await chain.findTx(hash).catch(() => undefined));
498
+ if (tx && tx.code === 0) {
499
+ landed = tx;
500
+ // the indexer can lag the block of the transaction, and the fill needs the validated entry
501
+ const entry = await this.agent.indexer.getParticipant(record.applicantParticipantId);
502
+ if (entry.op_state === 'VALIDATED') {
503
+ await this.markValidated(recordId, entry, { hash, height: tx.height });
504
+ await this.continueAfterValidated(recordId);
505
+ return;
506
+ }
507
+ }
508
+ else if (tx) {
509
+ return this.failUnlessValidated(record, validation, credo_ts_didcomm_vt_flow_1.VtFlowTxReason.TxFailed, tx.rawLog, tx.height);
510
+ }
511
+ const submittedAt = (_c = (_b = validation.tx) === null || _b === void 0 ? void 0 : _b.submittedAt) !== null && _c !== void 0 ? _c : validation.decidedAt;
512
+ if (Date.now() - Date.parse(submittedAt) >= TX_LOOKUP_TIMEOUT_MS) {
513
+ if (tx) {
514
+ this.agent.config.logger.warn(`[vt-flow] the transaction of flow ${recordId} landed, but the indexer does not show the entry VALIDATED yet: its notification completes the flow`);
515
+ return;
516
+ }
517
+ return this.failUnlessValidated(record, validation, credo_ts_didcomm_vt_flow_1.VtFlowTxReason.TxNotFound, 'not found 60 seconds after the broadcast');
518
+ }
519
+ await new Promise(resolve => setTimeout(resolve, TX_LOOKUP_INTERVAL_MS));
520
+ }
521
+ }
522
+ // The VPR has no distinct error for a second SetParticipantOPtoValidated, so the entry tells a
523
+ // rejected duplicate apart from a real failure.
524
+ async failUnlessValidated(record, validation, reason, error, height) {
525
+ var _a;
526
+ const vtFlowApi = this.resolveVtFlowApi();
527
+ const applicant = record.applicantParticipantId
528
+ ? await this.agent.indexer.getParticipant(record.applicantParticipantId)
529
+ : undefined;
530
+ const failed = Object.assign(Object.assign({}, validation), { tx: Object.assign(Object.assign({}, validation.tx), { height, status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Failed, reason, error }) });
531
+ if ((applicant === null || applicant === void 0 ? void 0 : applicant.op_state) === 'VALIDATED') {
532
+ // the notification handler may have moved the flow while the tx and the entry were read
533
+ if (((_a = (await vtFlowApi.findById(record.id))) === null || _a === void 0 ? void 0 : _a.state) !== credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxSubmitted)
534
+ return;
535
+ if (reason === credo_ts_didcomm_vt_flow_1.VtFlowTxReason.TxFailed)
536
+ await vtFlowApi.recordValidation(record.id, failed);
537
+ await this.markValidated(record.id, applicant);
538
+ await this.continueAfterValidated(record.id);
539
+ return;
540
+ }
541
+ await vtFlowApi.recordValidation(record.id, failed, credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxFailed);
542
+ }
543
+ /**
544
+ * [VSA-VTI-FLOW-OP-ISSUE]: moves the flow to VALIDATED and fills `validation` from the validated
545
+ * entry. Without the landed transaction, a recorded broadcast keeps its tx, and its submission
546
+ * unless that tx failed.
547
+ */
548
+ async markValidated(recordId, entry, landed) {
549
+ var _a, _b, _c, _d, _e;
550
+ const vtFlowApi = this.resolveVtFlowApi();
551
+ if (markingValidated.has(recordId))
552
+ throw invalidState('the flow is already moving to VALIDATED');
553
+ markingValidated.add(recordId);
554
+ try {
555
+ const record = await vtFlowApi.findById(recordId);
556
+ if (!record)
557
+ throw new Error(`vt-flow record ${recordId} not found`);
558
+ if (!credo_ts_didcomm_vt_flow_1.VtFlowValidatedFromStates.has(record.state) &&
559
+ record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.TerminatedByValidator) {
560
+ throw invalidState(`the flow is '${record.state}', which does not precede VALIDATED`);
561
+ }
562
+ const recorded = record.validation;
563
+ const agentTx = landed && ((_a = recorded === null || recorded === void 0 ? void 0 : recorded.tx) === null || _a === void 0 ? void 0 : _a.hash) === landed.hash ? recorded.tx : undefined;
564
+ const validation = Object.assign(Object.assign(Object.assign({}, recorded), { decidedAt: (_c = (_b = recorded === null || recorded === void 0 ? void 0 : recorded.decidedAt) !== null && _b !== void 0 ? _b : landed === null || landed === void 0 ? void 0 : landed.timestamp) !== null && _c !== void 0 ? _c : entry.modified, submission: !landed && ((_d = recorded === null || recorded === void 0 ? void 0 : recorded.tx) === null || _d === void 0 ? void 0 : _d.hash) && recorded.tx.reason !== credo_ts_didcomm_vt_flow_1.VtFlowTxReason.TxFailed
565
+ ? recorded.submission
566
+ : agentTx
567
+ ? credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Agent
568
+ : credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Operator, validationFees: entry.validation_fees, issuanceFees: entry.issuance_fees, verificationFees: entry.verification_fees, issuanceFeeDiscount: entry.issuance_fee_discount, verificationFeeDiscount: entry.verification_fee_discount, effectiveUntil: (_e = entry.effective_until) !== null && _e !== void 0 ? _e : undefined }), (agentTx && {
569
+ tx: {
570
+ hash: agentTx.hash,
571
+ submittedAt: agentTx.submittedAt,
572
+ height: landed === null || landed === void 0 ? void 0 : landed.height,
573
+ status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Succeeded,
574
+ },
575
+ }));
576
+ return await vtFlowApi.recordValidation(recordId, validation, credo_ts_didcomm_vt_flow_1.VtFlowState.Validated);
577
+ }
578
+ finally {
579
+ markingValidated.delete(recordId);
580
+ }
581
+ }
582
+ /** rejectFlow ended the flow with its transaction in flight, and no newer flow of the applicant replaced it ([VSA-ADM-VT-FL-REJECT-2]). */
583
+ async isRejectedInFlight(record) {
584
+ if (record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.TerminatedByValidator || !record.validation)
585
+ return false;
586
+ if (!record.applicantParticipantId)
587
+ return false;
588
+ const [latest] = (await this.resolveVtFlowApi().findAllByQuery({
589
+ role: credo_ts_didcomm_vt_flow_1.VtFlowRole.Validator,
590
+ applicantParticipantId: record.applicantParticipantId,
591
+ })).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
592
+ return (latest === null || latest === void 0 ? void 0 : latest.id) === record.id;
593
+ }
594
+ async continueAfterValidated(recordId) {
595
+ var _a;
596
+ const vtFlowApi = this.resolveVtFlowApi();
597
+ const record = await vtFlowApi.findById(recordId);
598
+ if (!record)
599
+ throw new Error(`vt-flow record ${recordId} not found`);
600
+ if (record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.Validated && record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.ValidatedPendingClaims) {
601
+ return record;
602
+ }
603
+ const participant = await this.agent.indexer.findParticipant(Number(record.applicantParticipantId));
604
+ if (!participant)
605
+ throw new Error(`Applicant participant ${record.applicantParticipantId} not found`);
606
+ if (Number(participant.role) !== types_2.HOLDER_PARTICIPANT_TYPE)
607
+ return record;
608
+ const schema = await this.agent.indexer.getCredentialSchema(participant.schemaId);
609
+ const violations = (0, utils_1.schemaViolations)(JSON.parse(schema.json_schema), Object.assign({ id: participant.did }, ((_a = record.claims) !== null && _a !== void 0 ? _a : {})));
610
+ if (violations.length > 0) {
611
+ this.agent.config.logger.error(`[vt-flow] not offering the credential of flow ${recordId}: its claims do not satisfy the ` +
612
+ `json_schema of schema ${participant.schemaId}: ${violations.map(v => `${v.path} ${v.message}`.trim()).join(', ')}`);
613
+ return record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.Validated ? vtFlowApi.markPendingClaims(recordId) : record;
614
+ }
615
+ return this.offerOnboardingCredential({ vtFlowRecordId: recordId, participant });
616
+ }
617
+ /** [VSA-ADM-VT-FL-VALIDATE-8]: at startup, resume every flow left in VALIDATION_TX_SUBMITTED. */
618
+ async resumeValidationSubmissions() {
619
+ const pending = await this.resolveVtFlowApi().findAllByQuery({
620
+ role: credo_ts_didcomm_vt_flow_1.VtFlowRole.Validator,
621
+ flowState: credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxSubmitted,
622
+ });
623
+ await Promise.all(pending.map(record => this.resolveValidationTx(record.id)));
624
+ }
625
+ /** [VSA-VTI-FLOW-ISSUE-1]: at startup, resume every anchoring left SUBMITTED through the stored issued credential. */
626
+ async resumeIssuanceSubmissions() {
627
+ const vtFlowApi = this.resolveVtFlowApi();
628
+ const offered = await vtFlowApi.findAllByQuery({
629
+ role: credo_ts_didcomm_vt_flow_1.VtFlowRole.Validator,
630
+ flowState: credo_ts_didcomm_vt_flow_1.VtFlowState.CredOffered,
631
+ });
632
+ await Promise.all(offered.map(({ id, issuance, credentialExchangeRecordId }) => {
633
+ var _a;
634
+ return ((_a = issuance === null || issuance === void 0 ? void 0 : issuance.tx) === null || _a === void 0 ? void 0 : _a.status) === credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Submitted && credentialExchangeRecordId
635
+ ? vtFlowApi.issueCredentialForSession({ vtFlowRecordId: id, credentialExchangeRecordId })
636
+ : undefined;
637
+ }));
638
+ }
139
639
  async acceptCredential(input) {
140
640
  const vtFlowApi = this.resolveVtFlowApi();
141
641
  const record = await vtFlowApi.findById(input.vtFlowRecordId);
@@ -154,7 +654,7 @@ class VtFlowOrchestrator {
154
654
  const chain = this.requireChain();
155
655
  if (!this.agent.did)
156
656
  throw new Error('Agent has no public DID');
157
- const indexer = this.requireIndexer();
657
+ const indexer = this.agent.indexer;
158
658
  const vtFlowApi = this.resolveVtFlowApi();
159
659
  const record = await vtFlowApi.findById(vtFlowRecordId);
160
660
  if (!record)
@@ -185,49 +685,51 @@ class VtFlowOrchestrator {
185
685
  });
186
686
  return {
187
687
  credentialFormats: {
188
- jsonld: {
189
- credential: unsignedCredentialJson,
190
- options: { proofType: 'Ed25519Signature2020', proofPurpose: 'assertionMethod' },
191
- },
688
+ dataIntegrity: { credential: unsignedCredentialJson, bindingRequired: false },
192
689
  },
193
690
  issuerParticipantId: issuer.id,
194
691
  };
195
692
  }
693
+ /**
694
+ * Builds the unsigned VC Data Model 2.0 credential that goes into the offer. RFC 0809 derives the
695
+ * advertised data model version from its context, and the issuer secures it with a
696
+ * DataIntegrityProof once the applicant requests it.
697
+ */
196
698
  async buildCredential(input) {
197
- var _a, _b, _c;
198
- const chain = this.requireChain();
199
- const didRecords = await this.agent.dids.getCreatedDids({ did: this.agent.did });
200
- const didRecord = didRecords[0];
201
- if (!didRecord)
202
- throw new Error('Agent DID record not found');
203
- const schemaRef = `vpr:verana:${chain.getChainId}:cs:${input.credentialSchemaId}`;
204
- const entry = await (0, utils_1.findMetadataEntry)(didRecord, '_vt/jsc', '', schemaRef);
205
- if (!entry)
206
- throw new Error(`No stored VTJSC found for ${schemaRef}`);
207
- const { data } = entry;
208
- const unsignedCredential = (0, utils_1.createCredential)({
699
+ var _a;
700
+ const jsonSchemaCredentialId = await this.resolveJsonSchemaCredentialId(input.credentialSchemaId);
701
+ // A credential whose claims don't satisfy the schema's required fields is not a valid
702
+ // instance of that credential type — reject it here rather than issue an empty shell.
703
+ const schema = await this.agent.indexer.getCredentialSchema(input.credentialSchemaId);
704
+ (0, utils_1.validateSchema)(JSON.parse(schema.json_schema), Object.assign({ id: input.subjectDid }, input.claims));
705
+ // Data model 2.0 holds the claims on the credential subject itself; the applicant DID wins over
706
+ // any `id` claim so the credential can never name a different subject
707
+ const unsignedCredential = (0, utils_1.createW3cV2Credential)({
209
708
  id: `${this.agent.did}#${core_1.utils.uuid()}`,
210
709
  type: (_a = input.credentialType) !== null && _a !== void 0 ? _a : ['VerifiableCredential', 'VerifiableTrustCredential'],
211
710
  issuer: this.agent.did,
212
- credentialSubject: { id: input.subjectDid, claims: input.claims },
711
+ context: input.credentialContext,
712
+ credentialSubject: Object.assign(Object.assign({}, input.claims), { id: input.subjectDid }),
713
+ credentialSchema: { id: jsonSchemaCredentialId, type: 'JsonSchemaCredential' },
714
+ validUntil: input.validUntil,
213
715
  });
214
- if (input.credentialContext)
215
- unsignedCredential.context = input.credentialContext;
216
- unsignedCredential.credentialSchema = {
217
- id: (_c = (_b = data.verifiableCredential) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.id,
218
- type: 'JsonSchemaCredential',
219
- };
220
- return core_1.JsonTransformer.toJSON(unsignedCredential);
716
+ return (0, utils_1.toOfferedCredentialJson)(unsignedCredential);
717
+ }
718
+ async resolveJsonSchemaCredentialId(credentialSchemaId) {
719
+ return (0, utils_1.resolveJsonSchemaCredentialId)(this.agent, this.agent.indexer, credentialSchemaId, this.requireChain().getChainId);
221
720
  }
222
721
  /** Per the spec the algorithm comes from the schema, never from the digest value. */
223
722
  async digestAlgorithmForSchema(schemaId) {
224
- const schema = await this.requireIndexer().getCredentialSchema(schemaId);
723
+ return (await this.credentialSchema(schemaId)).digestAlgorithm;
724
+ }
725
+ async credentialSchema(schemaId) {
726
+ const schema = await this.agent.indexer.getCredentialSchema(schemaId);
225
727
  if (!schema.digest_algorithm) {
226
728
  throw new Error(`Credential schema ${schemaId} has no digest_algorithm`);
227
729
  }
228
- return schema.digest_algorithm;
730
+ return { digestAlgorithm: schema.digest_algorithm, ecsKey: await (0, vs_agent_model_1.classifyEcsSchema)(schema.json_schema) };
229
731
  }
230
- /** Fired after signing and before delivery, so a failure here must abort the issuance. */
732
+ /** Fired after signing and before delivery. A failed anchoring comes back in `issuance`, and the credential stays undelivered. */
231
733
  async onCredentialIssued(vtFlowRecordId, signedCredential) {
232
734
  var _a, _b;
233
735
  const chain = this.requireChain();
@@ -238,17 +740,20 @@ class VtFlowOrchestrator {
238
740
  if (record.issuerParticipantId == null) {
239
741
  throw new Error(`vt-flow record ${vtFlowRecordId} has no issuerParticipantId to anchor against`);
240
742
  }
241
- const issuer = await this.requireIndexer().getParticipant(record.issuerParticipantId);
743
+ const issuer = await this.agent.indexer.getParticipant(record.issuerParticipantId);
242
744
  const algorithm = await this.digestAlgorithmForSchema(issuer.schema_id);
243
745
  const digest = (0, verre_1.computeCredentialDigestJCS)(signedCredential, algorithm);
244
- await chain.createOrUpdateParticipantSession({
746
+ const message = chain.createOrUpdateParticipantSessionMsg({
245
747
  id: record.participantSessionId,
246
748
  issuerParticipantId: record.issuerParticipantId,
247
749
  agentParticipantId: Number((_a = record.agentParticipantId) !== null && _a !== void 0 ? _a : 0) || 0,
248
750
  walletAgentParticipantId: Number((_b = record.walletAgentParticipantId) !== null && _b !== void 0 ? _b : 0) || 0,
249
751
  digest,
250
752
  });
251
- return digest;
753
+ return {
754
+ credentialDigest: digest,
755
+ issuance: { tx: await this.anchor(record, message, record.issuerParticipantId) },
756
+ };
252
757
  }
253
758
  async verifyOfferedCredential(vtFlowRecordId) {
254
759
  var _a, _b;
@@ -261,7 +766,7 @@ class VtFlowOrchestrator {
261
766
  if (!record.credentialExchangeRecordId) {
262
767
  throw new Error('Record has no credentialExchangeRecordId; nothing to verify');
263
768
  }
264
- const indexer = this.requireIndexer();
769
+ const indexer = this.agent.indexer;
265
770
  const session = await indexer.getParticipantSession(record.participantSessionId);
266
771
  if (!session) {
267
772
  throw new Error(`ParticipantSession ${record.participantSessionId} not found on indexer`);
@@ -282,13 +787,36 @@ class VtFlowOrchestrator {
282
787
  if (record.schemaId != null && Number(issuer.schema_id) !== Number(record.schemaId)) {
283
788
  throw new Error(`Issuer participant schema ${issuer.schema_id} does not match credential schema ${record.schemaId}`);
284
789
  }
285
- const credentialJson = await this.getOfferedCredentialJson(record.credentialExchangeRecordId);
286
- const algorithm = await this.digestAlgorithmForSchema(issuer.schema_id);
287
- const digest = (0, verre_1.computeCredentialDigestJCS)(credentialJson, algorithm);
790
+ const credentialJson = await this.getReceivedCredentialJson(record.credentialExchangeRecordId);
791
+ await this.assertFlowCredentialFormat(credentialJson, issuer);
792
+ const { digestAlgorithm, ecsKey } = await this.credentialSchema(issuer.schema_id);
793
+ const digest = (0, verre_1.computeCredentialDigestJCS)(credentialJson, digestAlgorithm);
288
794
  const anchored = await indexer.getDigest(digest);
289
795
  if (!anchored) {
290
796
  throw new Error(`Credential digest ${digest} is not anchored on-chain`);
291
797
  }
798
+ if (ecsKey)
799
+ await vtFlowApi.setEcsSchemaKey(record.id, ecsKey);
800
+ }
801
+ /** [VSA-VTI-FLOW-FMT-1]. Credo already verified the proof and its assertionMethod key, for data model 2.0 only. */
802
+ async assertFlowCredentialFormat(credential, issuer) {
803
+ var _a, _b, _c;
804
+ if (!(0, utils_1.isVcdm2Credential)(credential)) {
805
+ throw new Error('Received credential is not a VC Data Model 2.0 credential');
806
+ }
807
+ const verificationMethod = (_a = credential.proof) === null || _a === void 0 ? void 0 : _a.verificationMethod;
808
+ if (!issuer.did || (verificationMethod === null || verificationMethod === void 0 ? void 0 : verificationMethod.split('#')[0]) !== issuer.did) {
809
+ throw new Error(`Proof verification method ${verificationMethod} is not one of the validator ${issuer.did}`);
810
+ }
811
+ const subjectId = (_b = credential.credentialSubject) === null || _b === void 0 ? void 0 : _b.id;
812
+ if (subjectId !== this.agent.did) {
813
+ throw new Error(`Credential subject ${subjectId} is not the applicant ${this.agent.did}`);
814
+ }
815
+ const schemaRef = (_c = credential.credentialSchema) === null || _c === void 0 ? void 0 : _c.id;
816
+ const jsonSchemaCredentialId = await this.resolveJsonSchemaCredentialId(String(issuer.schema_id));
817
+ if (schemaRef !== jsonSchemaCredentialId) {
818
+ throw new Error(`Credential schema ${schemaRef} is not the VTJSC ${jsonSchemaCredentialId} of the flow`);
819
+ }
292
820
  }
293
821
  async onCredentialRevoked(vtFlowRecordId) {
294
822
  const vtFlowApi = this.resolveVtFlowApi();
@@ -297,7 +825,7 @@ class VtFlowOrchestrator {
297
825
  return;
298
826
  if (!this.options.publicApiBaseUrl || !record.credentialExchangeRecordId)
299
827
  return;
300
- const credentialId = await (0, utils_1.removeStoredTrustCredential)(this.agent, this.options.publicApiBaseUrl, record.credentialExchangeRecordId);
828
+ const credentialId = await (0, utils_1.removeStoredTrustCredential)(this.agent, record.credentialExchangeRecordId);
301
829
  if (credentialId) {
302
830
  this.agent.config.logger.info(`[vt-flow] removed revoked credential and its linked VP (${credentialId})`);
303
831
  }
@@ -311,33 +839,67 @@ class VtFlowOrchestrator {
311
839
  return;
312
840
  if (!this.options.publicApiBaseUrl)
313
841
  return;
842
+ // [VSA-VT-LVP-5]: the publication writes the DID record, and that write sends TriggerResolver
314
843
  await this.publishCredentialAsLinkedVp(vtFlowRecordId);
315
- await this.triggerResolver(record);
316
- }
317
- async triggerResolver(record) {
318
- const chain = this.agent.veranaChain;
319
- if (!chain || !chain.autoTriggerResolverEnabled)
320
- return;
321
- if (record.participantId == null)
322
- return;
323
- await chain.triggerResolver(Number(record.participantId));
324
844
  }
325
- async getOfferedCredentialJson(credentialExchangeRecordId) {
326
- var _a;
845
+ /** The signed credential as the issuer attached it, which is the exact JSON its digest covers. */
846
+ async getReceivedCredentialJson(credentialExchangeRecordId) {
847
+ var _a, _b;
327
848
  const formatData = await this.agent.didcomm.credentials.getFormatData(credentialExchangeRecordId);
328
- const credentialJson = (_a = formatData.credential) === null || _a === void 0 ? void 0 : _a.jsonld;
849
+ const credentialJson = (_b = (_a = formatData.credential) === null || _a === void 0 ? void 0 : _a.dataIntegrity) === null || _b === void 0 ? void 0 : _b.credential;
329
850
  if (!credentialJson)
330
- throw new Error('Offered credential has no JSON-LD body to verify');
851
+ throw new Error('Received credential has no data integrity credential body');
331
852
  return credentialJson;
332
853
  }
333
- requireIndexer() {
334
- if (!this.options.indexer) {
335
- throw new Error('Agent has no indexer configured (set VERANA_INDEXER_BASE_URL)');
854
+ // VS-CONN-VS gate: consulted only after trust resolution rejected the peer.
855
+ async checkEcsIssuanceExemption(context) {
856
+ return (0, ecsIssuanceExemption_1.isEcsIssuanceExempt)({
857
+ indexer: this.agent.indexer,
858
+ agent: this.agent,
859
+ trustedEcosystemDids: this.agent.trustedEcosystemDids,
860
+ }, context);
861
+ }
862
+ // the indexer can still lag the applicant's own transaction, so wait for it as a tx lookup does
863
+ async checkParticipantId({ record }) {
864
+ if (!record.applicantParticipantId || !this.agent.did)
865
+ return false;
866
+ const deadline = Date.now() + TX_LOOKUP_TIMEOUT_MS;
867
+ let validatedAtFirstRead;
868
+ for (;;) {
869
+ const entry = await this.agent.indexer
870
+ .getParticipant(record.applicantParticipantId)
871
+ .catch(() => undefined);
872
+ validatedAtFirstRead !== null && validatedAtFirstRead !== void 0 ? validatedAtFirstRead : (validatedAtFirstRead = (entry === null || entry === void 0 ? void 0 : entry.op_state) === 'VALIDATED');
873
+ if (entry) {
874
+ if (entry.revoked || entry.slashed || entry.validator_participant_id == null)
875
+ return false;
876
+ const validator = await this.agent.indexer
877
+ .getParticipant(entry.validator_participant_id)
878
+ .catch(() => undefined);
879
+ if (validator && validator.did !== this.agent.did)
880
+ return false;
881
+ if (validator && entry.op_state === 'PENDING')
882
+ return true;
883
+ if (validator && entry.op_state === 'VALIDATED') {
884
+ const flow = await this.resolveVtFlowApi().findById(record.id);
885
+ // the flow has moved on, or another path is moving it to VALIDATED and continues it itself
886
+ if ((flow === null || flow === void 0 ? void 0 : flow.state) !== credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingOr || markingValidated.has(record.id))
887
+ return 'validated';
888
+ // VALIDATED at the first read can be the round before this request, which it does not settle
889
+ if (!validatedAtFirstRead) {
890
+ await this.markValidated(record.id, entry);
891
+ await this.continueAfterValidated(record.id);
892
+ return 'validated';
893
+ }
894
+ }
895
+ }
896
+ if (Date.now() >= deadline)
897
+ return false;
898
+ await new Promise(resolve => setTimeout(resolve, TX_LOOKUP_INTERVAL_MS));
336
899
  }
337
- return this.options.indexer;
338
900
  }
339
901
  async publishCredentialAsLinkedVp(vtFlowRecordId) {
340
- var _a, _b;
902
+ var _a;
341
903
  if (!this.options.publicApiBaseUrl) {
342
904
  throw new Error('publicApiBaseUrl is required to publish a LinkedVerifiablePresentation');
343
905
  }
@@ -350,18 +912,18 @@ class VtFlowOrchestrator {
350
912
  if (!record.credentialExchangeRecordId) {
351
913
  throw new Error(`vt-flow record ${vtFlowRecordId} has no credentialExchangeRecordId`);
352
914
  }
353
- const formatData = await this.agent.didcomm.credentials.getFormatData(record.credentialExchangeRecordId);
354
- const jsonld = (_a = formatData.credential) === null || _a === void 0 ? void 0 : _a.jsonld;
355
- if (!jsonld) {
356
- throw new Error(`No jsonld credential in exchange ${record.credentialExchangeRecordId}`);
357
- }
358
- const schemaRef = (_b = jsonld.credentialSchema) === null || _b === void 0 ? void 0 : _b.id;
915
+ const credentialJson = await this.getReceivedCredentialJson(record.credentialExchangeRecordId);
916
+ const schemaRef = (_a = credentialJson.credentialSchema) === null || _a === void 0 ? void 0 : _a.id;
359
917
  const schemaBaseId = schemaRef ? this.extractSchemaBaseId(schemaRef) : undefined;
360
918
  if (!schemaBaseId) {
361
919
  throw new Error(`Cannot publish Linked VP: credential has no extractable schema base id (credentialSchema.id=${schemaRef !== null && schemaRef !== void 0 ? schemaRef : 'undefined'})`);
362
920
  }
363
- const w3cCredential = core_1.JsonTransformer.fromJSON(jsonld, core_1.W3cJsonLdVerifiableCredential);
364
- await (0, utils_1.createVtc)(this.agent, this.options.publicApiBaseUrl, schemaBaseId, w3cCredential);
921
+ // A data model 2.0 credential arrives secured with a DataIntegrityProof; a validator still on
922
+ // data model 1.1 sends a linked data proof, and both are published the same way
923
+ const credential = (0, utils_1.isVcdm2Credential)(credentialJson)
924
+ ? core_1.W3cV2DataIntegrityVerifiableCredential.fromObject(credentialJson)
925
+ : core_1.JsonTransformer.fromJSON(credentialJson, core_1.W3cJsonLdVerifiableCredential);
926
+ await (0, utils_1.createVtc)(this.agent, this.options.publicApiBaseUrl, record.ecsSchemaKey ? (0, utils_1.linkedVpSchemaId)(record.ecsSchemaKey) : schemaBaseId, credential);
365
927
  }
366
928
  extractSchemaBaseId(jscUrl) {
367
929
  var _a;