@verana-labs/vs-agent-plugin-openid4vc 1.11.0

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 (51) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +193 -0
  3. package/build/config.d.ts +5 -0
  4. package/build/config.js +279 -0
  5. package/build/config.js.map +1 -0
  6. package/build/index.d.ts +12 -0
  7. package/build/index.js +28 -0
  8. package/build/index.js.map +1 -0
  9. package/build/nestjs/IssuerController.d.ts +8 -0
  10. package/build/nestjs/IssuerController.js +66 -0
  11. package/build/nestjs/IssuerController.js.map +1 -0
  12. package/build/nestjs/OpenId4VcPlugin.d.ts +3 -0
  13. package/build/nestjs/OpenId4VcPlugin.js +61 -0
  14. package/build/nestjs/OpenId4VcPlugin.js.map +1 -0
  15. package/build/nestjs/VerifierController.d.ts +8 -0
  16. package/build/nestjs/VerifierController.js +66 -0
  17. package/build/nestjs/VerifierController.js.map +1 -0
  18. package/build/nestjs/dto.d.ts +7 -0
  19. package/build/nestjs/dto.js +34 -0
  20. package/build/nestjs/dto.js.map +1 -0
  21. package/build/sdk/setupOpenId4Vc.d.ts +18 -0
  22. package/build/sdk/setupOpenId4Vc.js +111 -0
  23. package/build/sdk/setupOpenId4Vc.js.map +1 -0
  24. package/build/services/CertificateService.d.ts +22 -0
  25. package/build/services/CertificateService.js +249 -0
  26. package/build/services/CertificateService.js.map +1 -0
  27. package/build/services/IssuerService.d.ts +47 -0
  28. package/build/services/IssuerService.js +185 -0
  29. package/build/services/IssuerService.js.map +1 -0
  30. package/build/services/VerifierService.d.ts +57 -0
  31. package/build/services/VerifierService.js +289 -0
  32. package/build/services/VerifierService.js.map +1 -0
  33. package/build/trust/CertificateTrust.d.ts +7 -0
  34. package/build/trust/CertificateTrust.js +29 -0
  35. package/build/trust/CertificateTrust.js.map +1 -0
  36. package/build/trust/TrustClient.d.ts +12 -0
  37. package/build/trust/TrustClient.js +120 -0
  38. package/build/trust/TrustClient.js.map +1 -0
  39. package/build/trust/keyBinding.d.ts +14 -0
  40. package/build/trust/keyBinding.js +171 -0
  41. package/build/trust/keyBinding.js.map +1 -0
  42. package/build/trust/types.d.ts +28 -0
  43. package/build/trust/types.js +3 -0
  44. package/build/trust/types.js.map +1 -0
  45. package/build/trust/verdict.d.ts +2 -0
  46. package/build/trust/verdict.js +13 -0
  47. package/build/trust/verdict.js.map +1 -0
  48. package/build/types.d.ts +55 -0
  49. package/build/types.js +3 -0
  50. package/build/types.js.map +1 -0
  51. package/package.json +47 -0
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TrustClient = void 0;
4
+ const verdict_1 = require("./verdict");
5
+ const TRUST_STATUSES = new Set(['TRUSTED', 'PARTIAL', 'UNTRUSTED']);
6
+ class TrustClient {
7
+ constructor(options, fetchImplementation = globalThis.fetch) {
8
+ this.options = options;
9
+ this.fetchImplementation = fetchImplementation;
10
+ const resolverUrl = new URL(options.resolverUrl);
11
+ if (resolverUrl.username || resolverUrl.password) {
12
+ throw new Error('resolver URL must not contain credentials');
13
+ }
14
+ this.resolverUrl = resolverUrl;
15
+ }
16
+ async resolve(did) {
17
+ const url = this.endpoint('resolve', { did });
18
+ try {
19
+ const { response, body } = await this.request(url);
20
+ if (response.status === 404)
21
+ return { status: 'not_found' };
22
+ if (!response.ok)
23
+ return { status: 'unreachable' };
24
+ if (!isRecord(body) || !isVeranaTrustStatus(body.trustStatus)) {
25
+ return { status: 'unreachable' };
26
+ }
27
+ return { status: 'ok', trustStatus: body.trustStatus };
28
+ }
29
+ catch (_a) {
30
+ return { status: 'unreachable' };
31
+ }
32
+ }
33
+ async checkAuthorization(role, did, vtjscId) {
34
+ const url = this.endpoint(`${role}-authorization`, { did, vtjscId });
35
+ try {
36
+ const { response, body } = await this.request(url);
37
+ if (response.status === 404)
38
+ return false;
39
+ if (!response.ok)
40
+ return null;
41
+ return isRecord(body) && typeof body.authorized === 'boolean' ? body.authorized : null;
42
+ }
43
+ catch (_a) {
44
+ return null;
45
+ }
46
+ }
47
+ async verdictFor(role, did, vtjscId) {
48
+ if (!did) {
49
+ return {
50
+ verdict: 'UNTRUSTED',
51
+ evidence: {
52
+ did,
53
+ trustStatus: null,
54
+ vtjscId,
55
+ authorized: null,
56
+ queries: [],
57
+ note: 'no DID was available for trust resolution',
58
+ },
59
+ };
60
+ }
61
+ const resolutionUrl = this.endpoint('resolve', { did });
62
+ const resolution = await this.resolve(did);
63
+ const evidence = {
64
+ did,
65
+ trustStatus: resolution.status === 'ok' ? resolution.trustStatus : null,
66
+ vtjscId,
67
+ authorized: null,
68
+ queries: [resolutionUrl.toString()],
69
+ };
70
+ if (resolution.status !== 'ok' || resolution.trustStatus !== 'TRUSTED') {
71
+ return { verdict: (0, verdict_1.computeVerdict)(resolution, null), evidence };
72
+ }
73
+ if (!vtjscId) {
74
+ return {
75
+ verdict: 'TRUSTED_NOT_AUTHORIZED',
76
+ evidence: Object.assign(Object.assign({}, evidence), { note: 'no VTJSC identifier was available for authorization' }),
77
+ };
78
+ }
79
+ const authorizationUrl = this.endpoint(`${role}-authorization`, { did, vtjscId });
80
+ const authorized = await this.checkAuthorization(role, did, vtjscId);
81
+ evidence.authorized = authorized;
82
+ evidence.queries.push(authorizationUrl.toString());
83
+ return { verdict: (0, verdict_1.computeVerdict)(resolution, authorized), evidence };
84
+ }
85
+ endpoint(pathSegment, query) {
86
+ const url = new URL(this.resolverUrl);
87
+ const basePathSegments = url.pathname.split('/').filter(Boolean);
88
+ url.pathname = `/${[...basePathSegments, pathSegment].join('/')}`;
89
+ url.search = '';
90
+ url.hash = '';
91
+ const searchParams = new URLSearchParams();
92
+ for (const [key, value] of Object.entries(query))
93
+ searchParams.set(key, value);
94
+ url.search = searchParams.toString();
95
+ return url;
96
+ }
97
+ async request(url) {
98
+ const controller = new AbortController();
99
+ const timeout = setTimeout(() => controller.abort(), this.options.timeoutMs);
100
+ try {
101
+ const response = await this.fetchImplementation(url, {
102
+ headers: { accept: 'application/json' },
103
+ signal: controller.signal,
104
+ });
105
+ const body = response.ok ? await response.json() : undefined;
106
+ return { response, body };
107
+ }
108
+ finally {
109
+ clearTimeout(timeout);
110
+ }
111
+ }
112
+ }
113
+ exports.TrustClient = TrustClient;
114
+ function isRecord(value) {
115
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
116
+ }
117
+ function isVeranaTrustStatus(value) {
118
+ return typeof value === 'string' && TRUST_STATUSES.has(value);
119
+ }
120
+ //# sourceMappingURL=TrustClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TrustClient.js","sourceRoot":"","sources":["../../src/trust/TrustClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { KeyBindingResult, TrustVerdict } from './types';
2
+ import type { BaseAgent, DidPurpose } from '@credo-ts/core';
3
+ type BindingPurpose = Extract<DidPurpose, 'assertionMethod' | 'authentication'>;
4
+ type DidResolverAgent = Pick<BaseAgent, 'dids'>;
5
+ export declare const DEFAULT_DID_RESOLUTION_TIMEOUT_MS = 5000;
6
+ export declare const MAX_DID_RESOLUTION_TIMEOUT_MS = 30000;
7
+ export interface DidResolutionPolicy {
8
+ allowedWebHosts: string[];
9
+ timeoutMs: number;
10
+ }
11
+ export declare function ownDidResolutionPolicy(did: string, timeoutMs?: number): DidResolutionPolicy;
12
+ export declare function verifyKeyBoundToDid(agent: DidResolverAgent, did: string | null, certificatePublicJwk: unknown, purposes: BindingPurpose[], resolutionPolicy: DidResolutionPolicy): Promise<KeyBindingResult>;
13
+ export declare function blockingBindingVerdict(did: string | null, vtjscId: string | null, binding: Exclude<KeyBindingResult, 'bound'>): TrustVerdict;
14
+ export {};
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_DID_RESOLUTION_TIMEOUT_MS = exports.DEFAULT_DID_RESOLUTION_TIMEOUT_MS = void 0;
4
+ exports.ownDidResolutionPolicy = ownDidResolutionPolicy;
5
+ exports.verifyKeyBoundToDid = verifyKeyBoundToDid;
6
+ exports.blockingBindingVerdict = blockingBindingVerdict;
7
+ const core_1 = require("@credo-ts/core");
8
+ const node_net_1 = require("node:net");
9
+ exports.DEFAULT_DID_RESOLUTION_TIMEOUT_MS = 5000;
10
+ exports.MAX_DID_RESOLUTION_TIMEOUT_MS = 30000;
11
+ const NON_PUBLIC_IPS = createNonPublicIpBlockList();
12
+ function ownDidResolutionPolicy(did, timeoutMs = exports.DEFAULT_DID_RESOLUTION_TIMEOUT_MS) {
13
+ const host = didWebHost(did);
14
+ return { allowedWebHosts: host ? [host] : [], timeoutMs };
15
+ }
16
+ async function verifyKeyBoundToDid(agent, did, certificatePublicJwk, purposes, resolutionPolicy) {
17
+ var _a, _b;
18
+ if (!did)
19
+ return 'unbound';
20
+ let certificateKey;
21
+ try {
22
+ certificateKey = core_1.Kms.PublicJwk.fromUnknown(certificatePublicJwk);
23
+ }
24
+ catch (_c) {
25
+ return 'unbound';
26
+ }
27
+ if (!isResolutionAllowed(did, resolutionPolicy))
28
+ return 'unresolvable';
29
+ let didDocument;
30
+ try {
31
+ const resolution = await withTimeout(agent.dids.resolve(did, { useCache: false, persistInCache: false }), resolutionPolicy.timeoutMs);
32
+ if (((_a = resolution.didResolutionMetadata) === null || _a === void 0 ? void 0 : _a.error) || !resolution.didDocument)
33
+ return 'unresolvable';
34
+ if (resolution.didDocument.id !== did)
35
+ return 'unresolvable';
36
+ didDocument = resolution.didDocument;
37
+ }
38
+ catch (_d) {
39
+ return 'unresolvable';
40
+ }
41
+ for (const purpose of purposes) {
42
+ for (const entry of (_b = didDocument[purpose]) !== null && _b !== void 0 ? _b : []) {
43
+ let verificationMethod;
44
+ try {
45
+ verificationMethod =
46
+ typeof entry === 'string' ? didDocument.dereferenceVerificationMethod(entry) : entry;
47
+ }
48
+ catch (_e) {
49
+ continue;
50
+ }
51
+ try {
52
+ const methodKey = (0, core_1.getPublicJwkFromVerificationMethod)(verificationMethod);
53
+ if (certificateKey.equals(methodKey))
54
+ return 'bound';
55
+ }
56
+ catch (_f) {
57
+ continue;
58
+ }
59
+ }
60
+ }
61
+ return 'unbound';
62
+ }
63
+ function isResolutionAllowed(did, policy) {
64
+ if (!Number.isInteger(policy.timeoutMs) ||
65
+ policy.timeoutMs <= 0 ||
66
+ policy.timeoutMs > exports.MAX_DID_RESOLUTION_TIMEOUT_MS ||
67
+ !Array.isArray(policy.allowedWebHosts)) {
68
+ return false;
69
+ }
70
+ const requestedHost = didWebHost(did);
71
+ if (!requestedHost || isNonPublicHost(new URL(`https://${requestedHost}`).hostname))
72
+ return false;
73
+ return policy.allowedWebHosts.some(allowedHost => canonicalHost(allowedHost) === requestedHost);
74
+ }
75
+ function didWebHost(did) {
76
+ const parsed = (0, core_1.tryParseDid)(did);
77
+ if (!parsed || parsed.did !== did)
78
+ return undefined;
79
+ const components = parsed.id.split(':');
80
+ const encodedHost = parsed.method === 'web'
81
+ ? components[0]
82
+ : parsed.method === 'webvh' && components.length >= 2 && components[0]
83
+ ? components[1]
84
+ : undefined;
85
+ if (!encodedHost)
86
+ return undefined;
87
+ try {
88
+ return canonicalHost(decodeURIComponent(encodedHost));
89
+ }
90
+ catch (_a) {
91
+ return undefined;
92
+ }
93
+ }
94
+ function canonicalHost(value) {
95
+ try {
96
+ const url = new URL(`https://${value}`);
97
+ if (!url.hostname || url.username || url.password || url.pathname !== '/' || url.search || url.hash) {
98
+ return undefined;
99
+ }
100
+ return url.host.toLowerCase();
101
+ }
102
+ catch (_a) {
103
+ return undefined;
104
+ }
105
+ }
106
+ function isNonPublicHost(hostname) {
107
+ const normalized = hostname.replace(/^\[|\]$/g, '').toLowerCase();
108
+ if (normalized === 'localhost' ||
109
+ normalized.endsWith('.localhost') ||
110
+ normalized.endsWith('.local') ||
111
+ normalized.endsWith('.internal') ||
112
+ normalized === 'home.arpa' ||
113
+ normalized.endsWith('.home.arpa')) {
114
+ return true;
115
+ }
116
+ const ipVersion = (0, node_net_1.isIP)(normalized);
117
+ return ipVersion === 4
118
+ ? NON_PUBLIC_IPS.check(normalized, 'ipv4')
119
+ : ipVersion === 6
120
+ ? NON_PUBLIC_IPS.check(normalized, 'ipv6')
121
+ : false;
122
+ }
123
+ function createNonPublicIpBlockList() {
124
+ const blockList = new node_net_1.BlockList();
125
+ blockList.addSubnet('0.0.0.0', 8, 'ipv4');
126
+ blockList.addSubnet('10.0.0.0', 8, 'ipv4');
127
+ blockList.addSubnet('100.64.0.0', 10, 'ipv4');
128
+ blockList.addSubnet('127.0.0.0', 8, 'ipv4');
129
+ blockList.addSubnet('169.254.0.0', 16, 'ipv4');
130
+ blockList.addSubnet('172.16.0.0', 12, 'ipv4');
131
+ blockList.addSubnet('192.0.0.0', 24, 'ipv4');
132
+ blockList.addSubnet('192.168.0.0', 16, 'ipv4');
133
+ blockList.addSubnet('198.18.0.0', 15, 'ipv4');
134
+ blockList.addSubnet('224.0.0.0', 3, 'ipv4');
135
+ blockList.addSubnet('::', 128, 'ipv6');
136
+ blockList.addSubnet('::1', 128, 'ipv6');
137
+ blockList.addSubnet('::ffff:0:0', 96, 'ipv6');
138
+ blockList.addSubnet('fc00::', 7, 'ipv6');
139
+ blockList.addSubnet('fe80::', 10, 'ipv6');
140
+ blockList.addSubnet('ff00::', 8, 'ipv6');
141
+ return blockList;
142
+ }
143
+ async function withTimeout(operation, timeoutMs) {
144
+ let timeout;
145
+ const timeoutPromise = new Promise((_resolve, reject) => {
146
+ timeout = setTimeout(() => reject(new Error('DID resolution timed out')), timeoutMs);
147
+ });
148
+ try {
149
+ return await Promise.race([operation, timeoutPromise]);
150
+ }
151
+ finally {
152
+ if (timeout)
153
+ clearTimeout(timeout);
154
+ }
155
+ }
156
+ function blockingBindingVerdict(did, vtjscId, binding) {
157
+ return {
158
+ verdict: binding === 'unresolvable' ? 'RESOLVER_UNAVAILABLE' : 'UNTRUSTED',
159
+ evidence: {
160
+ did,
161
+ trustStatus: null,
162
+ vtjscId,
163
+ authorized: null,
164
+ queries: [],
165
+ note: binding === 'unresolvable'
166
+ ? 'the asserted DID could not be resolved for key binding'
167
+ : 'the certificate public key is not authorized by the asserted DID document',
168
+ },
169
+ };
170
+ }
171
+ //# sourceMappingURL=keyBinding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyBinding.js","sourceRoot":"","sources":["../../src/trust/keyBinding.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ export type TrustVerdictName = 'TRUSTED_AUTHORIZED' | 'TRUSTED_NOT_AUTHORIZED' | 'UNTRUSTED' | 'RESOLVER_UNAVAILABLE';
2
+ export type VeranaTrustStatus = 'TRUSTED' | 'PARTIAL' | 'UNTRUSTED';
3
+ export type TrustResolution = {
4
+ status: 'ok';
5
+ trustStatus: VeranaTrustStatus;
6
+ } | {
7
+ status: 'not_found';
8
+ } | {
9
+ status: 'unreachable';
10
+ };
11
+ export type TrustRole = 'issuer' | 'verifier';
12
+ export type KeyBindingResult = 'bound' | 'unbound' | 'unresolvable';
13
+ export interface TrustEvidence {
14
+ did: string | null;
15
+ trustStatus: VeranaTrustStatus | null;
16
+ vtjscId: string | null;
17
+ authorized: boolean | null;
18
+ queries: string[];
19
+ note?: string;
20
+ }
21
+ export interface TrustVerdict {
22
+ verdict: TrustVerdictName;
23
+ evidence: TrustEvidence;
24
+ }
25
+ export interface TrustClientOptions {
26
+ resolverUrl: string;
27
+ timeoutMs: number;
28
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/trust/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ import type { TrustResolution, TrustVerdictName } from './types';
2
+ export declare function computeVerdict(resolution: TrustResolution, authorized: boolean | null): TrustVerdictName;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.computeVerdict = computeVerdict;
4
+ function computeVerdict(resolution, authorized) {
5
+ if (resolution.status === 'unreachable')
6
+ return 'RESOLVER_UNAVAILABLE';
7
+ if (resolution.status === 'not_found' || resolution.trustStatus !== 'TRUSTED')
8
+ return 'UNTRUSTED';
9
+ if (authorized === null)
10
+ return 'RESOLVER_UNAVAILABLE';
11
+ return authorized ? 'TRUSTED_AUTHORIZED' : 'TRUSTED_NOT_AUTHORIZED';
12
+ }
13
+ //# sourceMappingURL=verdict.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verdict.js","sourceRoot":"","sources":["../../src/trust/verdict.ts"],"names":[],"mappings":";;AAEA,wCAMC;AAND,SAAgB,cAAc,CAAC,UAA2B,EAAE,UAA0B;IACpF,IAAI,UAAU,CAAC,MAAM,KAAK,aAAa;QAAE,OAAO,sBAAsB,CAAA;IACtE,IAAI,UAAU,CAAC,MAAM,KAAK,WAAW,IAAI,UAAU,CAAC,WAAW,KAAK,SAAS;QAAE,OAAO,WAAW,CAAA;IACjG,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,sBAAsB,CAAA;IAEtD,OAAO,UAAU,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,wBAAwB,CAAA;AACrE,CAAC"}
@@ -0,0 +1,55 @@
1
+ import type { Kms } from '@credo-ts/core';
2
+ export interface OpenId4VcConfiguredSigningMaterial {
3
+ certificateChain: string[];
4
+ privateJwk: Kms.KmsJwkPrivateEc;
5
+ }
6
+ export type OpenId4VcSigningOptions = {
7
+ configured: OpenId4VcConfiguredSigningMaterial;
8
+ development?: never;
9
+ } | {
10
+ configured?: never;
11
+ development: {
12
+ enabled: true;
13
+ commonName: string;
14
+ };
15
+ };
16
+ export interface OpenId4VcCredentialConfiguration {
17
+ id: string;
18
+ format: 'dc+sd-jwt';
19
+ vct: string;
20
+ name: string;
21
+ description?: string;
22
+ vtjscId: string;
23
+ claims: string[];
24
+ disclosureFrame: string[];
25
+ ttlSeconds: number;
26
+ }
27
+ export interface OpenId4VcVerifierPolicy {
28
+ id: string;
29
+ credentialConfigurationId: string;
30
+ requestedClaims: string[];
31
+ }
32
+ export interface OpenId4VcPluginOptions {
33
+ publicApiBaseUrl: string;
34
+ issuer?: {
35
+ id: string;
36
+ displayName: string;
37
+ signing: OpenId4VcSigningOptions;
38
+ requireWalletAttestation?: boolean;
39
+ walletAttestationCertificates?: string[];
40
+ };
41
+ verifier?: {
42
+ id: string;
43
+ displayName: string;
44
+ signing: OpenId4VcSigningOptions;
45
+ };
46
+ trust?: {
47
+ resolverUrl: string;
48
+ timeoutMs: number;
49
+ allowedDidWebHosts: string[];
50
+ credentialIssuerCertificates: string[];
51
+ developmentCertificateFingerprints?: string[];
52
+ };
53
+ credentialConfigurations: OpenId4VcCredentialConfiguration[];
54
+ verifierPolicies: OpenId4VcVerifierPolicy[];
55
+ }
package/build/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@verana-labs/vs-agent-plugin-openid4vc",
3
+ "main": "build/index",
4
+ "types": "build/index",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./build/index.d.ts",
8
+ "require": "./build/index.js",
9
+ "default": "./build/index.js"
10
+ }
11
+ },
12
+ "version": "1.11.0",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/verana-labs/vs-agent"
16
+ },
17
+ "files": [
18
+ "build"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "license": "Apache-2.0",
24
+ "description": "OpenID4VC issuer and verifier plugin for VS Agent",
25
+ "dependencies": {
26
+ "@credo-ts/openid4vc": "0.7.1-pr-2704-20260630143332",
27
+ "@nestjs/common": "^10.0.0",
28
+ "class-validator": "0.14.1",
29
+ "express": "^4.18.1",
30
+ "@verana-labs/vs-agent-sdk": "1.11.0"
31
+ },
32
+ "peerDependencies": {
33
+ "@credo-ts/core": "0.7.1-pr-2704-20260630143332"
34
+ },
35
+ "devDependencies": {
36
+ "@credo-ts/askar": "0.7.1-pr-2704-20260630143332",
37
+ "@credo-ts/node": "0.7.1-pr-2704-20260630143332",
38
+ "@openwallet-foundation/askar-nodejs": "0.6.0"
39
+ },
40
+ "scripts": {
41
+ "build": "pnpm run clean && pnpm run compile",
42
+ "clean": "rimraf -rf ./build",
43
+ "compile": "tsc -p tsconfig.build.json",
44
+ "version": "pnpm version --allow-same-version",
45
+ "test": "vitest"
46
+ }
47
+ }