@verana-labs/vs-agent-sdk 2.0.0-dev.7 → 2.0.0-dev.70

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 (166) hide show
  1. package/README.md +0 -2
  2. package/build/adminApi/AdminApiError.d.ts +27 -0
  3. package/build/adminApi/AdminApiError.js +59 -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 +17 -34
  30. package/build/agent/VsAgent.js +75 -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 +25 -14
  45. package/build/blockchain/VeranaChainService.js +123 -113
  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/handlers/authorizationHandlers.js +2 -2
  51. package/build/blockchain/handlers/authorizationHandlers.js.map +1 -1
  52. package/build/blockchain/handlers/defaultHandlers.js +2 -0
  53. package/build/blockchain/handlers/defaultHandlers.js.map +1 -1
  54. package/build/blockchain/handlers/index.d.ts +1 -0
  55. package/build/blockchain/handlers/index.js +1 -0
  56. package/build/blockchain/handlers/index.js.map +1 -1
  57. package/build/blockchain/handlers/selfIssuanceHandlers.d.ts +13 -0
  58. package/build/blockchain/handlers/selfIssuanceHandlers.js +71 -0
  59. package/build/blockchain/handlers/selfIssuanceHandlers.js.map +1 -0
  60. package/build/blockchain/handlers/stateMutations.d.ts +21 -2
  61. package/build/blockchain/handlers/stateMutations.js +285 -37
  62. package/build/blockchain/handlers/stateMutations.js.map +1 -1
  63. package/build/blockchain/index.d.ts +2 -0
  64. package/build/blockchain/index.js +2 -0
  65. package/build/blockchain/index.js.map +1 -1
  66. package/build/blockchain/indexerMappers.d.ts +3 -0
  67. package/build/blockchain/indexerMappers.js +53 -0
  68. package/build/blockchain/indexerMappers.js.map +1 -0
  69. package/build/blockchain/types.d.ts +13 -58
  70. package/build/blockchain/types.js.map +1 -1
  71. package/build/bootstrap/EcsBootstrapService.d.ts +14 -3
  72. package/build/bootstrap/EcsBootstrapService.js +166 -76
  73. package/build/bootstrap/EcsBootstrapService.js.map +1 -1
  74. package/build/connections/ParentConnectionModule.d.ts +20 -0
  75. package/build/connections/ParentConnectionModule.js +56 -0
  76. package/build/connections/ParentConnectionModule.js.map +1 -0
  77. package/build/credentials/FullTailsFileService.d.ts +0 -1
  78. package/build/credentials/FullTailsFileService.js +0 -49
  79. package/build/credentials/FullTailsFileService.js.map +1 -1
  80. package/build/did/CachedDocumentLoader.d.ts +3 -0
  81. package/build/did/CachedDocumentLoader.js +7 -1
  82. package/build/did/CachedDocumentLoader.js.map +1 -1
  83. package/build/did/CachedWebDidResolver.d.ts +0 -4
  84. package/build/did/CachedWebDidResolver.js +1 -5
  85. package/build/did/CachedWebDidResolver.js.map +1 -1
  86. package/build/did/artifactServices.d.ts +12 -0
  87. package/build/did/artifactServices.js +48 -0
  88. package/build/did/artifactServices.js.map +1 -0
  89. package/build/did/legacyDidWeb.d.ts +6 -4
  90. package/build/did/legacyDidWeb.js +17 -14
  91. package/build/did/legacyDidWeb.js.map +1 -1
  92. package/build/did/publicDid.d.ts +4 -0
  93. package/build/did/publicDid.js +19 -0
  94. package/build/did/publicDid.js.map +1 -0
  95. package/build/events/BaseMessageEvents.js +139 -5
  96. package/build/events/BaseMessageEvents.js.map +1 -1
  97. package/build/events/ConnectionEvents.js +129 -82
  98. package/build/events/ConnectionEvents.js.map +1 -1
  99. package/build/events/VsAgentEvents.d.ts +11 -9
  100. package/build/events/VsAgentEvents.js +8 -1
  101. package/build/events/VsAgentEvents.js.map +1 -1
  102. package/build/events/VtFlowEvents.d.ts +1 -1
  103. package/build/events/VtFlowEvents.js +27 -18
  104. package/build/events/VtFlowEvents.js.map +1 -1
  105. package/build/index.d.ts +7 -1
  106. package/build/index.js +15 -2
  107. package/build/index.js.map +1 -1
  108. package/build/plugins/setupBaseDidComm.js +9 -12
  109. package/build/plugins/setupBaseDidComm.js.map +1 -1
  110. package/build/transports/VsAgentWsInboundTransport.d.ts +1 -1
  111. package/build/transports/VsAgentWsInboundTransport.js +20 -14
  112. package/build/transports/VsAgentWsInboundTransport.js.map +1 -1
  113. package/build/types.d.ts +7 -0
  114. package/build/types.js.map +1 -1
  115. package/build/utils/agent.d.ts +29 -4
  116. package/build/utils/agent.js +91 -22
  117. package/build/utils/agent.js.map +1 -1
  118. package/build/utils/didReadiness.d.ts +9 -0
  119. package/build/utils/didReadiness.js +42 -0
  120. package/build/utils/didReadiness.js.map +1 -0
  121. package/build/utils/ecsClaims.d.ts +47 -0
  122. package/build/utils/ecsClaims.js +120 -0
  123. package/build/utils/ecsClaims.js.map +1 -0
  124. package/build/utils/ecsService.d.ts +8 -0
  125. package/build/utils/ecsService.js +23 -0
  126. package/build/utils/ecsService.js.map +1 -0
  127. package/build/utils/index.d.ts +4 -0
  128. package/build/utils/index.js +4 -0
  129. package/build/utils/index.js.map +1 -1
  130. package/build/utils/selfIssuedEcsCredential.d.ts +8 -0
  131. package/build/utils/selfIssuedEcsCredential.js +151 -0
  132. package/build/utils/selfIssuedEcsCredential.js.map +1 -0
  133. package/build/utils/setupSelfTr.d.ts +34 -66
  134. package/build/utils/setupSelfTr.js +89 -295
  135. package/build/utils/setupSelfTr.js.map +1 -1
  136. package/build/utils/trustCredentialStore.d.ts +67 -9
  137. package/build/utils/trustCredentialStore.js +296 -111
  138. package/build/utils/trustCredentialStore.js.map +1 -1
  139. package/build/utils/util.d.ts +10 -0
  140. package/build/utils/util.js +16 -0
  141. package/build/utils/util.js.map +1 -1
  142. package/build/utils/vcdm2.d.ts +34 -0
  143. package/build/utils/vcdm2.js +62 -0
  144. package/build/utils/vcdm2.js.map +1 -0
  145. package/build/utils/vtjscResolver.d.ts +18 -0
  146. package/build/utils/vtjscResolver.js +54 -0
  147. package/build/utils/vtjscResolver.js.map +1 -0
  148. package/build/vtFlow/VtFlowOrchestrator.d.ts +84 -13
  149. package/build/vtFlow/VtFlowOrchestrator.js +505 -107
  150. package/build/vtFlow/VtFlowOrchestrator.js.map +1 -1
  151. package/build/vtFlow/assertVerifiableService.d.ts +1 -0
  152. package/build/vtFlow/assertVerifiableService.js +45 -5
  153. package/build/vtFlow/assertVerifiableService.js.map +1 -1
  154. package/build/vtFlow/ecsIssuanceExemption.d.ts +12 -0
  155. package/build/vtFlow/ecsIssuanceExemption.js +84 -0
  156. package/build/vtFlow/ecsIssuanceExemption.js.map +1 -0
  157. package/build/vtFlow/index.d.ts +1 -0
  158. package/build/vtFlow/index.js +1 -0
  159. package/build/vtFlow/index.js.map +1 -1
  160. package/package.json +17 -12
  161. package/build/did/migrateWebVhLog.d.ts +0 -34
  162. package/build/did/migrateWebVhLog.js +0 -230
  163. package/build/did/migrateWebVhLog.js.map +0 -1
  164. package/build/did/migrateWebVhVersionTime.d.ts +0 -9
  165. package/build/did/migrateWebVhVersionTime.js +0 -191
  166. package/build/did/migrateWebVhVersionTime.js.map +0 -1
@@ -3,10 +3,44 @@ 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");
7
11
  const types_1 = require("../blockchain/types");
8
12
  const types_2 = require("../types");
9
13
  const utils_1 = require("../utils");
14
+ // The chain carries a fee discount as an integer from 0 to 10000. The API and the indexer carry it
15
+ // as a decimal from 0 to 1, so only the message is scaled.
16
+ const DISCOUNT_SCALE = 10000;
17
+ const TX_LOOKUP_TIMEOUT_MS = 60000;
18
+ const TX_LOOKUP_INTERVAL_MS = 3000;
19
+ const FEE_DENOM = 'uvna';
20
+ const FEE_KEYS = ['validationFees', 'issuanceFees', 'verificationFees'];
21
+ const DISCOUNT_KEYS = ['issuanceFeeDiscount', 'verificationFeeDiscount'];
22
+ function invalidInput(message) {
23
+ return new adminApi_1.AdminApiError(adminApi_1.AdminApiErrorCode.InvalidInput, 400, message);
24
+ }
25
+ function invalidState(message) {
26
+ return new adminApi_1.AdminApiError(adminApi_1.AdminApiErrorCode.InvalidState, 409, message);
27
+ }
28
+ // AUTHZ-CHECK-3 step 1. A record's expiration is only its budget clock, not a validity window.
29
+ function isActiveParticipant(participant) {
30
+ const now = Date.now();
31
+ if (!participant.effective_from || Date.parse(participant.effective_from) > now)
32
+ return false;
33
+ if (participant.effective_until && Date.parse(participant.effective_until) <= now)
34
+ return false;
35
+ return !participant.revoked && !participant.slashed;
36
+ }
37
+ function sameTerm(key, given, onEntry) {
38
+ const scale = DISCOUNT_KEYS.includes(key) ? DISCOUNT_SCALE : 1;
39
+ return Math.round(given * scale) === Math.round(onEntry * scale);
40
+ }
41
+ function errorMessage(error) {
42
+ return error instanceof Error ? error.message : String(error);
43
+ }
10
44
  class VtFlowOrchestrator {
11
45
  constructor(agent, options = {}) {
12
46
  this.agent = agent;
@@ -14,10 +48,10 @@ class VtFlowOrchestrator {
14
48
  }
15
49
  async startOnboardingProcess(input) {
16
50
  var _a, _b, _c, _d;
17
- const chain = this.requireChain();
51
+ this.requireChain();
18
52
  if (!this.agent.did)
19
53
  throw new Error('Agent has no public DID');
20
- const holderParticipant = await chain.getParticipant(input.applicantParticipantId);
54
+ const holderParticipant = await this.agent.indexer.findParticipant(input.applicantParticipantId);
21
55
  if (!holderParticipant) {
22
56
  throw new Error(`Applicant participant ${input.applicantParticipantId} not found on chain`);
23
57
  }
@@ -37,7 +71,7 @@ class VtFlowOrchestrator {
37
71
  if (!holderParticipant.validatorParticipantId) {
38
72
  throw new Error(`Applicant participant ${input.applicantParticipantId} has no validator_participant_id`);
39
73
  }
40
- const validatorParticipant = await chain.getParticipant(Number(holderParticipant.validatorParticipantId));
74
+ const validatorParticipant = await this.agent.indexer.findParticipant(Number(holderParticipant.validatorParticipantId));
41
75
  if (!(validatorParticipant === null || validatorParticipant === void 0 ? void 0 : validatorParticipant.did)) {
42
76
  throw new Error(`Validator participant ${holderParticipant.validatorParticipantId} not resolvable`);
43
77
  }
@@ -47,46 +81,60 @@ class VtFlowOrchestrator {
47
81
  // Renewals resend the OR with the same session id so the validator re-attaches the finished flow.
48
82
  const vtFlowApi = this.resolveVtFlowApi();
49
83
  const [latest] = (await vtFlowApi.findAllByQuery({
50
- participantId: String(holderParticipant.id),
84
+ applicantParticipantId: String(holderParticipant.id),
51
85
  role: credo_ts_didcomm_vt_flow_1.VtFlowRole.Applicant,
52
86
  flowVariant: credo_ts_didcomm_vt_flow_1.VtFlowVariant.OnboardingProcess,
53
87
  }))
54
88
  .filter(record => !(0, credo_ts_didcomm_vt_flow_1.isVtFlowTerminalState)(record.state))
55
89
  .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;
90
+ const running = latest !== undefined &&
91
+ latest.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.Completed &&
92
+ latest.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.CredRevoked;
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,387 @@ 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;
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
+ }));
204
+ // W3C Data Integrity attachment format (RFC 0809). The applicant DID is the credential subject
205
+ // and the exchange runs over its authenticated connection, so no extra binding is required. The
206
+ // cryptosuite is the issuer's choice and is applied when the credential is issued.
127
207
  const { record: offered } = await vtFlowApi.offerCredentialForSession({
128
208
  vtFlowRecordId: record.id,
129
- issuerParticipantId: Number(holderParticipant.validatorParticipantId),
209
+ issuerParticipantId: Number(participant.validatorParticipantId),
130
210
  credentialFormats: {
131
- jsonld: {
132
- credential: unsignedCredentialJson,
133
- options: { proofType: 'Ed25519Signature2020', proofPurpose: 'assertionMethod' },
134
- },
211
+ dataIntegrity: { credential: unsignedCredentialJson, bindingRequired: false },
135
212
  },
136
213
  });
137
214
  return offered;
138
215
  }
216
+ /**
217
+ * Close an onboarding process that carries no credential exchange, on the validator side. The
218
+ * applicant reaches the same state from the SetParticipantOPToValidated chain event.
219
+ */
220
+ async completeOnboardingProcess(vtFlowRecordId) {
221
+ return this.resolveVtFlowApi().markCompleted(vtFlowRecordId);
222
+ }
223
+ /** [VSA-ADM-VT-FL-VALIDATE] */
224
+ async validateFlow(input) {
225
+ this.requireChain();
226
+ const vtFlowApi = this.resolveVtFlowApi();
227
+ const record = await vtFlowApi.findById(input.vtFlowRecordId);
228
+ if (!record) {
229
+ throw new adminApi_1.AdminApiError(adminApi_1.AdminApiErrorCode.UnknownId, 404, `no flow with id "${input.vtFlowRecordId}"`);
230
+ }
231
+ if (record.role !== credo_ts_didcomm_vt_flow_1.VtFlowRole.Validator)
232
+ throw invalidState('validate is a validator action');
233
+ if (record.variant === credo_ts_didcomm_vt_flow_1.VtFlowVariant.DirectIssuance)
234
+ return this.validateDirectIssuance(record);
235
+ if (!record.applicantParticipantId)
236
+ throw invalidState('the flow names no applicant participant');
237
+ const applicant = await this.agent.indexer.getParticipant(record.applicantParticipantId);
238
+ const entryValidated = applicant.op_state === 'VALIDATED';
239
+ this.assertValidateState(record, entryValidated);
240
+ const terms = this.validationTerms(input, applicant, record);
241
+ await this.assertClaimsAndTerm(record, applicant, terms);
242
+ if (record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingOr)
243
+ await vtFlowApi.acceptOnboardingRequest(record.id);
244
+ if (record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.OobPending)
245
+ await this.sendValidating(record);
246
+ if (record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.ValidatedPendingClaims)
247
+ return this.continueAfterValidated(record.id);
248
+ if (entryValidated) {
249
+ if (record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.Validated)
250
+ await vtFlowApi.markValidated(record.id);
251
+ return this.continueAfterValidated(record.id);
252
+ }
253
+ const validation = Object.assign({ decidedAt: new Date().toISOString(), submission: await this.submissionPath(applicant) }, terms);
254
+ if (validation.submission === credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Operator) {
255
+ return vtFlowApi.recordValidation(record.id, validation, credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingValidationTx);
256
+ }
257
+ return this.submitValidation(record.id, applicant, validation);
258
+ }
259
+ // A Direct Issuance flow has no entry to validate, so no transaction: check the claims, then offer.
260
+ async validateDirectIssuance(record) {
261
+ if (record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.Validating && record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.OobPending) {
262
+ throw invalidState(`the flow is '${record.state}', which validate does not accept`);
263
+ }
264
+ if (!record.schemaId)
265
+ throw invalidState('the flow names no credential schema');
266
+ const connection = await this.agent.didcomm.connections.findById(record.connectionId);
267
+ if (!(connection === null || connection === void 0 ? void 0 : connection.theirDid))
268
+ throw invalidState('the flow connection has no peer DID');
269
+ const schema = await this.agent.indexer.getCredentialSchema(record.schemaId);
270
+ this.assertClaims(schema.json_schema, connection.theirDid, record.claims);
271
+ const vtFlowApi = this.resolveVtFlowApi();
272
+ if (record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.OobPending)
273
+ await this.sendValidating(record);
274
+ const offer = await this.buildDirectIssuanceOffer(record.id);
275
+ if (!offer)
276
+ throw invalidState('the agent holds no active ISSUER participant for the schema of the flow');
277
+ const { record: offered } = await vtFlowApi.offerCredentialForSession(Object.assign({ vtFlowRecordId: record.id }, offer));
278
+ return offered;
279
+ }
280
+ // [VSA-ADM-VT-FL-START]: checked before the flow moves, as a failed send would leave it in VALIDATING
281
+ async sendValidating(record) {
282
+ const connection = await this.agent.didcomm.connections.findById(record.connectionId);
283
+ if (!(connection === null || connection === void 0 ? void 0 : connection.isReady))
284
+ throw invalidState('the flow connection is not ESTABLISHED');
285
+ await this.resolveVtFlowApi().sendValidating(record.id);
286
+ }
287
+ assertClaims(jsonSchema, subjectDid, claims) {
288
+ const violations = (0, utils_1.schemaViolations)(JSON.parse(jsonSchema), Object.assign({ id: subjectDid }, (claims !== null && claims !== void 0 ? claims : {})));
289
+ if (violations.length > 0) {
290
+ throw new adminApi_1.AdminApiError(adminApi_1.AdminApiErrorCode.InvalidClaims, 422, 'the claim set does not satisfy the json_schema', { violations });
291
+ }
292
+ }
293
+ assertValidateState(record, entryValidated) {
294
+ const accepted = [
295
+ credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingOr,
296
+ credo_ts_didcomm_vt_flow_1.VtFlowState.Validating,
297
+ credo_ts_didcomm_vt_flow_1.VtFlowState.OobPending,
298
+ credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxFailed,
299
+ credo_ts_didcomm_vt_flow_1.VtFlowState.ValidatedPendingClaims,
300
+ ];
301
+ // Once the entry is VALIDATED on chain any validator-side state resumes into issuance, which is
302
+ // how a backend that disabled the default notification handler drives the flow ([VSA-ADM-VT-FL-VALIDATE-11]).
303
+ const acceptedOnceValidated = [
304
+ credo_ts_didcomm_vt_flow_1.VtFlowState.AwaitingValidationTx,
305
+ credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxSubmitted,
306
+ credo_ts_didcomm_vt_flow_1.VtFlowState.Validated,
307
+ ];
308
+ if (accepted.includes(record.state))
309
+ return;
310
+ if (entryValidated && acceptedOnceValidated.includes(record.state))
311
+ return;
312
+ throw invalidState(`the flow is '${record.state}', which validate does not accept`);
313
+ }
314
+ validationTerms(input, applicant, record) {
315
+ var _a, _b, _c, _d, _e, _f, _g, _h;
316
+ for (const key of FEE_KEYS) {
317
+ const fee = input[key];
318
+ if (fee !== undefined && !(Number.isInteger(fee) && fee >= 0)) {
319
+ throw invalidInput(`${key} must be a non-negative integer`);
320
+ }
321
+ }
322
+ for (const key of DISCOUNT_KEYS) {
323
+ const discount = input[key];
324
+ if (discount !== undefined && !(discount >= 0 && discount <= 1)) {
325
+ throw invalidInput(`${key} must be between 0 and 1`);
326
+ }
327
+ }
328
+ if (input.effectiveUntil !== undefined && Number.isNaN(Date.parse(input.effectiveUntil))) {
329
+ throw invalidInput('effectiveUntil must be an ISO 8601 datetime');
330
+ }
331
+ const onEntry = {
332
+ validationFees: (_a = applicant.validation_fees) !== null && _a !== void 0 ? _a : 0,
333
+ issuanceFees: (_b = applicant.issuance_fees) !== null && _b !== void 0 ? _b : 0,
334
+ verificationFees: (_c = applicant.verification_fees) !== null && _c !== void 0 ? _c : 0,
335
+ issuanceFeeDiscount: (_d = applicant.issuance_fee_discount) !== null && _d !== void 0 ? _d : 0,
336
+ verificationFeeDiscount: (_e = applicant.verification_fee_discount) !== null && _e !== void 0 ? _e : 0,
337
+ };
338
+ const renewal = !!applicant.effective_from;
339
+ const recorded = record.state === credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxFailed ? record.validation : undefined;
340
+ const terms = {
341
+ effectiveUntil: (_f = input.effectiveUntil) !== null && _f !== void 0 ? _f : recorded === null || recorded === void 0 ? void 0 : recorded.effectiveUntil,
342
+ opSummaryDigest: (_g = input.opSummaryDigest) !== null && _g !== void 0 ? _g : recorded === null || recorded === void 0 ? void 0 : recorded.opSummaryDigest,
343
+ };
344
+ for (const key of [...FEE_KEYS, ...DISCOUNT_KEYS]) {
345
+ const given = input[key];
346
+ if (renewal) {
347
+ if (given !== undefined && !sameTerm(key, given, onEntry[key])) {
348
+ throw invalidInput(`${key} differs from the entry, and a renewal keeps the agreed value`);
349
+ }
350
+ terms[key] = onEntry[key];
351
+ }
352
+ else {
353
+ terms[key] = (_h = given !== null && given !== void 0 ? given : recorded === null || recorded === void 0 ? void 0 : recorded[key]) !== null && _h !== void 0 ? _h : 0;
354
+ }
355
+ }
356
+ return terms;
357
+ }
358
+ async assertClaimsAndTerm(record, applicant, terms) {
359
+ var _a;
360
+ if (applicant.role !== types_1.ParticipantRole.Holder)
361
+ return;
362
+ if (!applicant.did)
363
+ throw invalidState('the applicant entry has no DID to issue to');
364
+ const schema = await this.agent.indexer.getCredentialSchema(applicant.schema_id);
365
+ this.assertClaims(schema.json_schema, applicant.did, record.claims);
366
+ // An ECS Organization or Persona credential needs a validUntil. With a validity period of 0 the
367
+ // VPR sets no effective_until, so the call has to carry one.
368
+ const ecsKey = await (0, vs_agent_model_1.classifyEcsSchema)(schema.json_schema);
369
+ const needsValidUntil = ecsKey === vs_agent_model_1.ECS.ORG || ecsKey === vs_agent_model_1.ECS.PERSONA;
370
+ if (needsValidUntil && ((_a = schema.holder_validation_validity_period) !== null && _a !== void 0 ? _a : 0) === 0 && !terms.effectiveUntil) {
371
+ throw invalidInput('this schema has no validity period, so effectiveUntil is required');
372
+ }
373
+ }
374
+ async submissionPath(applicant) {
375
+ const authorization = this.agent.authorizationService;
376
+ if (!authorization || applicant.validator_participant_id == null)
377
+ return credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Operator;
378
+ const validator = await this.agent.indexer
379
+ .getParticipant(applicant.validator_participant_id)
380
+ .catch(() => undefined);
381
+ if (!validator || !isActiveParticipant(validator))
382
+ return credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Operator;
383
+ await authorization.refreshForOperator().catch(() => undefined);
384
+ const grant = authorization.getVsOperatorAuthorizationRecord(validator.id);
385
+ return (grant === null || grant === void 0 ? void 0 : grant.msgTypes.includes(verana_types_1.veranaTypeUrls.MsgSetParticipantOPToValidated))
386
+ ? credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Agent
387
+ : credo_ts_didcomm_vt_flow_1.VtFlowSubmission.Operator;
388
+ }
389
+ async submitValidation(recordId, applicant, validation) {
390
+ var _a, _b, _c, _d, _e;
391
+ const chain = this.requireChain();
392
+ const vtFlowApi = this.resolveVtFlowApi();
393
+ 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);
394
+ const grant = applicant.validator_participant_id == null
395
+ ? undefined
396
+ : (_a = this.agent.authorizationService) === null || _a === void 0 ? void 0 : _a.getVsOperatorAuthorizationRecord(applicant.validator_participant_id);
397
+ const granter = (grant === null || grant === void 0 ? void 0 : grant.withFeegrant) ? chain.corporation : undefined;
398
+ const message = chain.setParticipantOPToValidatedMsg({
399
+ id: applicant.id,
400
+ effectiveUntil: validation.effectiveUntil ? new Date(validation.effectiveUntil) : undefined,
401
+ validationFees: validation.validationFees,
402
+ issuanceFees: validation.issuanceFees,
403
+ verificationFees: validation.verificationFees,
404
+ issuanceFeeDiscount: Math.round(((_b = validation.issuanceFeeDiscount) !== null && _b !== void 0 ? _b : 0) * DISCOUNT_SCALE),
405
+ verificationFeeDiscount: Math.round(((_c = validation.verificationFeeDiscount) !== null && _c !== void 0 ? _c : 0) * DISCOUNT_SCALE),
406
+ opSummaryDigest: validation.opSummaryDigest,
407
+ });
408
+ let allowance;
409
+ try {
410
+ allowance = granter ? await chain.feeAllowance(granter, FEE_DENOM) : undefined;
411
+ }
412
+ catch (error) {
413
+ return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.PreflightError, errorMessage(error));
414
+ }
415
+ if (granter && !allowance) {
416
+ return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.FeegrantExpired, 'the Corporation grants the agent no active fee allowance');
417
+ }
418
+ let fee;
419
+ try {
420
+ fee = await chain.estimateFee([message], granter);
421
+ }
422
+ catch (error) {
423
+ return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.PreflightError, errorMessage(error));
424
+ }
425
+ const amount = BigInt((_e = (_d = fee.amount.find(coin => coin.denom === FEE_DENOM)) === null || _d === void 0 ? void 0 : _d.amount) !== null && _e !== void 0 ? _e : '0');
426
+ if (granter && allowance) {
427
+ if (!allowance.unlimited && allowance.remaining < amount) {
428
+ return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.FeegrantExhausted, `the fee allowance has ${allowance.remaining}${FEE_DENOM} left`);
429
+ }
430
+ let corporation;
431
+ try {
432
+ corporation = BigInt((await chain.getAccountBalance(granter, FEE_DENOM)).amount);
433
+ }
434
+ catch (error) {
435
+ return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.PreflightError, errorMessage(error));
436
+ }
437
+ if (corporation < amount) {
438
+ return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.InsufficientFundsCorporation, `the Corporation holds ${corporation}${FEE_DENOM}`);
439
+ }
440
+ }
441
+ else {
442
+ let own;
443
+ try {
444
+ own = BigInt((await chain.getBalance(FEE_DENOM)).amount);
445
+ }
446
+ catch (error) {
447
+ return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.PreflightError, errorMessage(error));
448
+ }
449
+ if (own < amount) {
450
+ return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.InsufficientFundsAgent, `the agent account holds ${own}${FEE_DENOM}`);
451
+ }
452
+ }
453
+ let hash;
454
+ try {
455
+ hash = await chain.broadcastWithoutWaiting([message], fee);
456
+ }
457
+ catch (error) {
458
+ return fail(credo_ts_didcomm_vt_flow_1.VtFlowTxReason.BroadcastError, errorMessage(error));
459
+ }
460
+ 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);
461
+ void this.resolveValidationTx(recordId).catch(error => this.agent.config.logger.error(`[vt-flow] resolving the validation of ${recordId} failed`, {
462
+ error: errorMessage(error),
463
+ }));
464
+ return submitted;
465
+ }
466
+ // [VSA-ADM-VT-FL-VALIDATE-8]. Leaving VALIDATION_TX_SUBMITTED ends the loop, so the notification
467
+ // handler moving the flow first wins the race.
468
+ async resolveValidationTx(recordId) {
469
+ var _a, _b, _c;
470
+ const chain = this.requireChain();
471
+ const vtFlowApi = this.resolveVtFlowApi();
472
+ for (;;) {
473
+ const record = await vtFlowApi.findById(recordId);
474
+ const validation = record === null || record === void 0 ? void 0 : record.validation;
475
+ const hash = (_a = validation === null || validation === void 0 ? void 0 : validation.tx) === null || _a === void 0 ? void 0 : _a.hash;
476
+ if (!record || !validation || !hash || record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxSubmitted)
477
+ return;
478
+ const tx = await chain.findTx(hash).catch(() => undefined);
479
+ if (tx && tx.code === 0) {
480
+ await vtFlowApi.recordValidation(recordId, Object.assign(Object.assign({}, validation), { tx: Object.assign(Object.assign({}, validation.tx), { height: tx.height, status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Succeeded }) }));
481
+ await vtFlowApi.markValidated(recordId);
482
+ await this.continueAfterValidated(recordId);
483
+ return;
484
+ }
485
+ if (tx)
486
+ return this.failUnlessValidated(record, validation, credo_ts_didcomm_vt_flow_1.VtFlowTxReason.TxFailed, tx.rawLog, tx.height);
487
+ const submittedAt = (_c = (_b = validation.tx) === null || _b === void 0 ? void 0 : _b.submittedAt) !== null && _c !== void 0 ? _c : validation.decidedAt;
488
+ if (Date.now() - Date.parse(submittedAt) >= TX_LOOKUP_TIMEOUT_MS) {
489
+ return this.failUnlessValidated(record, validation, credo_ts_didcomm_vt_flow_1.VtFlowTxReason.TxNotFound, 'not found 60 seconds after the broadcast');
490
+ }
491
+ await new Promise(resolve => setTimeout(resolve, TX_LOOKUP_INTERVAL_MS));
492
+ }
493
+ }
494
+ // The VPR has no distinct error for a second SetParticipantOPtoValidated, so the entry tells a
495
+ // rejected duplicate apart from a real failure.
496
+ async failUnlessValidated(record, validation, reason, error, height) {
497
+ const vtFlowApi = this.resolveVtFlowApi();
498
+ const applicant = record.applicantParticipantId
499
+ ? await this.agent.indexer.getParticipant(record.applicantParticipantId)
500
+ : undefined;
501
+ if ((applicant === null || applicant === void 0 ? void 0 : applicant.op_state) === 'VALIDATED') {
502
+ await vtFlowApi.markValidated(record.id);
503
+ await this.continueAfterValidated(record.id);
504
+ return;
505
+ }
506
+ await vtFlowApi.recordValidation(record.id, Object.assign(Object.assign({}, validation), { tx: Object.assign(Object.assign({}, validation.tx), { height, status: credo_ts_didcomm_vt_flow_1.VtFlowTxStatus.Failed, reason, error }) }), credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxFailed);
507
+ }
508
+ async continueAfterValidated(recordId) {
509
+ const vtFlowApi = this.resolveVtFlowApi();
510
+ const record = await vtFlowApi.findById(recordId);
511
+ if (!record)
512
+ throw new Error(`vt-flow record ${recordId} not found`);
513
+ if (record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.Validated && record.state !== credo_ts_didcomm_vt_flow_1.VtFlowState.ValidatedPendingClaims) {
514
+ return record;
515
+ }
516
+ const participant = await this.agent.indexer.findParticipant(Number(record.applicantParticipantId));
517
+ if (!participant)
518
+ throw new Error(`Applicant participant ${record.applicantParticipantId} not found`);
519
+ if (Number(participant.role) !== types_2.HOLDER_PARTICIPANT_TYPE)
520
+ return this.completeOnboardingProcess(recordId);
521
+ return this.offerOnboardingCredential({ vtFlowRecordId: recordId, participant });
522
+ }
523
+ /** [VSA-ADM-VT-FL-VALIDATE-8]: at startup, resume every flow left in VALIDATION_TX_SUBMITTED. */
524
+ async resumeValidationSubmissions() {
525
+ const pending = await this.resolveVtFlowApi().findAllByQuery({
526
+ role: credo_ts_didcomm_vt_flow_1.VtFlowRole.Validator,
527
+ flowState: credo_ts_didcomm_vt_flow_1.VtFlowState.ValidationTxSubmitted,
528
+ });
529
+ await Promise.all(pending.map(record => this.resolveValidationTx(record.id)));
530
+ }
139
531
  async acceptCredential(input) {
140
532
  const vtFlowApi = this.resolveVtFlowApi();
141
533
  const record = await vtFlowApi.findById(input.vtFlowRecordId);
@@ -154,7 +546,7 @@ class VtFlowOrchestrator {
154
546
  const chain = this.requireChain();
155
547
  if (!this.agent.did)
156
548
  throw new Error('Agent has no public DID');
157
- const indexer = this.requireIndexer();
549
+ const indexer = this.agent.indexer;
158
550
  const vtFlowApi = this.resolveVtFlowApi();
159
551
  const record = await vtFlowApi.findById(vtFlowRecordId);
160
552
  if (!record)
@@ -185,47 +577,48 @@ class VtFlowOrchestrator {
185
577
  });
186
578
  return {
187
579
  credentialFormats: {
188
- jsonld: {
189
- credential: unsignedCredentialJson,
190
- options: { proofType: 'Ed25519Signature2020', proofPurpose: 'assertionMethod' },
191
- },
580
+ dataIntegrity: { credential: unsignedCredentialJson, bindingRequired: false },
192
581
  },
193
582
  issuerParticipantId: issuer.id,
194
583
  };
195
584
  }
585
+ /**
586
+ * Builds the unsigned VC Data Model 2.0 credential that goes into the offer. RFC 0809 derives the
587
+ * advertised data model version from its context, and the issuer secures it with a
588
+ * DataIntegrityProof once the applicant requests it.
589
+ */
196
590
  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)({
591
+ var _a;
592
+ const jsonSchemaCredentialId = await this.resolveJsonSchemaCredentialId(input.credentialSchemaId);
593
+ // A credential whose claims don't satisfy the schema's required fields is not a valid
594
+ // instance of that credential type — reject it here rather than issue an empty shell.
595
+ const schema = await this.agent.indexer.getCredentialSchema(input.credentialSchemaId);
596
+ (0, utils_1.validateSchema)(JSON.parse(schema.json_schema), Object.assign({ id: input.subjectDid }, input.claims));
597
+ // Data model 2.0 holds the claims on the credential subject itself; the applicant DID wins over
598
+ // any `id` claim so the credential can never name a different subject
599
+ const unsignedCredential = (0, utils_1.createW3cV2Credential)({
209
600
  id: `${this.agent.did}#${core_1.utils.uuid()}`,
210
601
  type: (_a = input.credentialType) !== null && _a !== void 0 ? _a : ['VerifiableCredential', 'VerifiableTrustCredential'],
211
602
  issuer: this.agent.did,
212
- credentialSubject: { id: input.subjectDid, claims: input.claims },
603
+ context: input.credentialContext,
604
+ credentialSubject: Object.assign(Object.assign({}, input.claims), { id: input.subjectDid }),
605
+ credentialSchema: { id: jsonSchemaCredentialId, type: 'JsonSchemaCredential' },
213
606
  });
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);
607
+ return (0, utils_1.toOfferedCredentialJson)(unsignedCredential);
608
+ }
609
+ async resolveJsonSchemaCredentialId(credentialSchemaId) {
610
+ return (0, utils_1.resolveJsonSchemaCredentialId)(this.agent, this.agent.indexer, credentialSchemaId, this.requireChain().getChainId);
221
611
  }
222
612
  /** Per the spec the algorithm comes from the schema, never from the digest value. */
223
613
  async digestAlgorithmForSchema(schemaId) {
224
- const schema = await this.requireIndexer().getCredentialSchema(schemaId);
614
+ return (await this.credentialSchema(schemaId)).digestAlgorithm;
615
+ }
616
+ async credentialSchema(schemaId) {
617
+ const schema = await this.agent.indexer.getCredentialSchema(schemaId);
225
618
  if (!schema.digest_algorithm) {
226
619
  throw new Error(`Credential schema ${schemaId} has no digest_algorithm`);
227
620
  }
228
- return schema.digest_algorithm;
621
+ return { digestAlgorithm: schema.digest_algorithm, ecsKey: await (0, vs_agent_model_1.classifyEcsSchema)(schema.json_schema) };
229
622
  }
230
623
  /** Fired after signing and before delivery, so a failure here must abort the issuance. */
231
624
  async onCredentialIssued(vtFlowRecordId, signedCredential) {
@@ -238,7 +631,7 @@ class VtFlowOrchestrator {
238
631
  if (record.issuerParticipantId == null) {
239
632
  throw new Error(`vt-flow record ${vtFlowRecordId} has no issuerParticipantId to anchor against`);
240
633
  }
241
- const issuer = await this.requireIndexer().getParticipant(record.issuerParticipantId);
634
+ const issuer = await this.agent.indexer.getParticipant(record.issuerParticipantId);
242
635
  const algorithm = await this.digestAlgorithmForSchema(issuer.schema_id);
243
636
  const digest = (0, verre_1.computeCredentialDigestJCS)(signedCredential, algorithm);
244
637
  await chain.createOrUpdateParticipantSession({
@@ -261,7 +654,7 @@ class VtFlowOrchestrator {
261
654
  if (!record.credentialExchangeRecordId) {
262
655
  throw new Error('Record has no credentialExchangeRecordId; nothing to verify');
263
656
  }
264
- const indexer = this.requireIndexer();
657
+ const indexer = this.agent.indexer;
265
658
  const session = await indexer.getParticipantSession(record.participantSessionId);
266
659
  if (!session) {
267
660
  throw new Error(`ParticipantSession ${record.participantSessionId} not found on indexer`);
@@ -282,13 +675,15 @@ class VtFlowOrchestrator {
282
675
  if (record.schemaId != null && Number(issuer.schema_id) !== Number(record.schemaId)) {
283
676
  throw new Error(`Issuer participant schema ${issuer.schema_id} does not match credential schema ${record.schemaId}`);
284
677
  }
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);
678
+ const credentialJson = await this.getReceivedCredentialJson(record.credentialExchangeRecordId);
679
+ const { digestAlgorithm, ecsKey } = await this.credentialSchema(issuer.schema_id);
680
+ const digest = (0, verre_1.computeCredentialDigestJCS)(credentialJson, digestAlgorithm);
288
681
  const anchored = await indexer.getDigest(digest);
289
682
  if (!anchored) {
290
683
  throw new Error(`Credential digest ${digest} is not anchored on-chain`);
291
684
  }
685
+ if (ecsKey)
686
+ await vtFlowApi.setEcsSchemaKey(record.id, ecsKey);
292
687
  }
293
688
  async onCredentialRevoked(vtFlowRecordId) {
294
689
  const vtFlowApi = this.resolveVtFlowApi();
@@ -297,7 +692,7 @@ class VtFlowOrchestrator {
297
692
  return;
298
693
  if (!this.options.publicApiBaseUrl || !record.credentialExchangeRecordId)
299
694
  return;
300
- const credentialId = await (0, utils_1.removeStoredTrustCredential)(this.agent, this.options.publicApiBaseUrl, record.credentialExchangeRecordId);
695
+ const credentialId = await (0, utils_1.removeStoredTrustCredential)(this.agent, record.credentialExchangeRecordId);
301
696
  if (credentialId) {
302
697
  this.agent.config.logger.info(`[vt-flow] removed revoked credential and its linked VP (${credentialId})`);
303
698
  }
@@ -318,26 +713,29 @@ class VtFlowOrchestrator {
318
713
  const chain = this.agent.veranaChain;
319
714
  if (!chain || !chain.autoTriggerResolverEnabled)
320
715
  return;
321
- if (record.participantId == null)
716
+ if (record.applicantParticipantId == null)
322
717
  return;
323
- await chain.triggerResolver(Number(record.participantId));
718
+ await chain.triggerResolver(Number(record.applicantParticipantId));
324
719
  }
325
- async getOfferedCredentialJson(credentialExchangeRecordId) {
326
- var _a;
720
+ /** The signed credential as the issuer attached it, which is the exact JSON its digest covers. */
721
+ async getReceivedCredentialJson(credentialExchangeRecordId) {
722
+ var _a, _b;
327
723
  const formatData = await this.agent.didcomm.credentials.getFormatData(credentialExchangeRecordId);
328
- const credentialJson = (_a = formatData.credential) === null || _a === void 0 ? void 0 : _a.jsonld;
724
+ const credentialJson = (_b = (_a = formatData.credential) === null || _a === void 0 ? void 0 : _a.dataIntegrity) === null || _b === void 0 ? void 0 : _b.credential;
329
725
  if (!credentialJson)
330
- throw new Error('Offered credential has no JSON-LD body to verify');
726
+ throw new Error('Received credential has no data integrity credential body');
331
727
  return credentialJson;
332
728
  }
333
- requireIndexer() {
334
- if (!this.options.indexer) {
335
- throw new Error('Agent has no indexer configured (set VERANA_INDEXER_BASE_URL)');
336
- }
337
- return this.options.indexer;
729
+ // VS-CONN-VS gate: consulted only after trust resolution rejected the peer.
730
+ async checkEcsIssuanceExemption(context) {
731
+ return (0, ecsIssuanceExemption_1.isEcsIssuanceExempt)({
732
+ indexer: this.agent.indexer,
733
+ agent: this.agent,
734
+ trustedEcosystemDids: this.agent.trustedEcosystemDids,
735
+ }, context);
338
736
  }
339
737
  async publishCredentialAsLinkedVp(vtFlowRecordId) {
340
- var _a, _b;
738
+ var _a;
341
739
  if (!this.options.publicApiBaseUrl) {
342
740
  throw new Error('publicApiBaseUrl is required to publish a LinkedVerifiablePresentation');
343
741
  }
@@ -350,18 +748,18 @@ class VtFlowOrchestrator {
350
748
  if (!record.credentialExchangeRecordId) {
351
749
  throw new Error(`vt-flow record ${vtFlowRecordId} has no credentialExchangeRecordId`);
352
750
  }
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;
751
+ const credentialJson = await this.getReceivedCredentialJson(record.credentialExchangeRecordId);
752
+ const schemaRef = (_a = credentialJson.credentialSchema) === null || _a === void 0 ? void 0 : _a.id;
359
753
  const schemaBaseId = schemaRef ? this.extractSchemaBaseId(schemaRef) : undefined;
360
754
  if (!schemaBaseId) {
361
755
  throw new Error(`Cannot publish Linked VP: credential has no extractable schema base id (credentialSchema.id=${schemaRef !== null && schemaRef !== void 0 ? schemaRef : 'undefined'})`);
362
756
  }
363
- const w3cCredential = core_1.JsonTransformer.fromJSON(jsonld, core_1.W3cJsonLdVerifiableCredential);
364
- await (0, utils_1.createVtc)(this.agent, this.options.publicApiBaseUrl, schemaBaseId, w3cCredential);
757
+ // A data model 2.0 credential arrives secured with a DataIntegrityProof; a validator still on
758
+ // data model 1.1 sends a linked data proof, and both are published the same way
759
+ const credential = (0, utils_1.isVcdm2Credential)(credentialJson)
760
+ ? core_1.W3cV2DataIntegrityVerifiableCredential.fromObject(credentialJson)
761
+ : core_1.JsonTransformer.fromJSON(credentialJson, core_1.W3cJsonLdVerifiableCredential);
762
+ await (0, utils_1.createVtc)(this.agent, this.options.publicApiBaseUrl, record.ecsSchemaKey ? (0, utils_1.linkedVpSchemaId)(record.ecsSchemaKey) : schemaBaseId, credential);
365
763
  }
366
764
  extractSchemaBaseId(jscUrl) {
367
765
  var _a;