@orangecheck/sdk 0.1.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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +277 -0
  3. package/dist/attestation.d.mts +44 -0
  4. package/dist/attestation.d.ts +44 -0
  5. package/dist/attestation.js +579 -0
  6. package/dist/attestation.js.map +1 -0
  7. package/dist/attestation.mjs +567 -0
  8. package/dist/attestation.mjs.map +1 -0
  9. package/dist/canonical.d.mts +15 -0
  10. package/dist/canonical.d.ts +15 -0
  11. package/dist/canonical.js +109 -0
  12. package/dist/canonical.js.map +1 -0
  13. package/dist/canonical.mjs +102 -0
  14. package/dist/canonical.mjs.map +1 -0
  15. package/dist/challenge.d.mts +40 -0
  16. package/dist/challenge.d.ts +40 -0
  17. package/dist/challenge.js +169 -0
  18. package/dist/challenge.js.map +1 -0
  19. package/dist/challenge.mjs +146 -0
  20. package/dist/challenge.mjs.map +1 -0
  21. package/dist/identity.d.mts +88 -0
  22. package/dist/identity.d.ts +88 -0
  23. package/dist/identity.js +730 -0
  24. package/dist/identity.js.map +1 -0
  25. package/dist/identity.mjs +702 -0
  26. package/dist/identity.mjs.map +1 -0
  27. package/dist/index.d.mts +47 -0
  28. package/dist/index.d.ts +47 -0
  29. package/dist/index.js +2004 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/index.mjs +1930 -0
  32. package/dist/index.mjs.map +1 -0
  33. package/dist/nostr.d.mts +19 -0
  34. package/dist/nostr.d.ts +19 -0
  35. package/dist/nostr.js +395 -0
  36. package/dist/nostr.js.map +1 -0
  37. package/dist/nostr.mjs +382 -0
  38. package/dist/nostr.mjs.map +1 -0
  39. package/dist/scoring.d.mts +16 -0
  40. package/dist/scoring.d.ts +16 -0
  41. package/dist/scoring.js +75 -0
  42. package/dist/scoring.js.map +1 -0
  43. package/dist/scoring.mjs +69 -0
  44. package/dist/scoring.mjs.map +1 -0
  45. package/dist/types-BJKDroFv.d.mts +62 -0
  46. package/dist/types-BJKDroFv.d.ts +62 -0
  47. package/dist/verify.d.mts +6 -0
  48. package/dist/verify.d.ts +6 -0
  49. package/dist/verify.js +509 -0
  50. package/dist/verify.js.map +1 -0
  51. package/dist/verify.mjs +485 -0
  52. package/dist/verify.mjs.map +1 -0
  53. package/package.json +106 -0
@@ -0,0 +1,567 @@
1
+ // src/canonical.ts
2
+ function toLowerHex(bytes) {
3
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
4
+ }
5
+ function parseIdentities(identitiesStr) {
6
+ if (!identitiesStr || identitiesStr.trim() === "") {
7
+ return [];
8
+ }
9
+ return identitiesStr.split(",").map((binding) => {
10
+ const colonIndex = binding.indexOf(":");
11
+ if (colonIndex === -1) {
12
+ throw new Error(`Invalid identity binding format: ${binding}`);
13
+ }
14
+ return {
15
+ protocol: binding.substring(0, colonIndex).trim(),
16
+ identifier: binding.substring(colonIndex + 1).trim()
17
+ };
18
+ }).filter((binding) => binding.protocol && binding.identifier);
19
+ }
20
+ async function generateAttestationId(message) {
21
+ const encoder = new TextEncoder();
22
+ const data = encoder.encode(message);
23
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
24
+ return toLowerHex(new Uint8Array(hashBuffer));
25
+ }
26
+ async function createAttestationEnvelope(message, signature, scheme, address, identities, extensions = {}) {
27
+ const attestation_id = await generateAttestationId(message);
28
+ const issuedAtMatch = message.match(/issued_at: ([^\n]+)/);
29
+ const issued_at = issuedAtMatch?.[1] ?? (/* @__PURE__ */ new Date()).toISOString();
30
+ const expires_at = extensions.expires;
31
+ const relay_hints = extensions.relay_hints?.split(",").map((r) => r.trim());
32
+ const publish_targets = extensions.publish?.split(",").map((t) => t.trim());
33
+ const encoder = new TextEncoder();
34
+ const messageBytes = encoder.encode(message);
35
+ const base64 = btoa(String.fromCharCode(...messageBytes));
36
+ const message_b64url = base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
37
+ const verification_url = `https://ochk.io/attest/${attestation_id}`;
38
+ return {
39
+ attestation_id,
40
+ scheme,
41
+ address,
42
+ identities,
43
+ message,
44
+ message_b64url,
45
+ signature,
46
+ issued_at,
47
+ expires_at,
48
+ verification_url,
49
+ publish_targets,
50
+ relay_hints
51
+ };
52
+ }
53
+ function createLogger(namespace) {
54
+ const shouldLog = (level) => {
55
+ return false;
56
+ };
57
+ const formatMessage = (msg) => `[${namespace}] ${msg}`;
58
+ return {
59
+ debug: (contextOrMessage, message) => {
60
+ if (shouldLog()) {
61
+ if (typeof contextOrMessage === "string") {
62
+ console.debug(formatMessage(contextOrMessage));
63
+ } else {
64
+ console.debug(formatMessage(message ?? "Debug"), contextOrMessage);
65
+ }
66
+ }
67
+ },
68
+ info: (contextOrMessage, message) => {
69
+ if (shouldLog()) {
70
+ if (typeof contextOrMessage === "string") {
71
+ console.info(formatMessage(contextOrMessage));
72
+ } else {
73
+ console.info(formatMessage(message ?? "Info"), contextOrMessage);
74
+ }
75
+ }
76
+ },
77
+ warn: (contextOrMessage, message) => {
78
+ if (shouldLog()) {
79
+ if (typeof contextOrMessage === "string") {
80
+ console.warn(formatMessage(contextOrMessage));
81
+ } else {
82
+ console.warn(formatMessage(message ?? "Warning"), contextOrMessage);
83
+ }
84
+ }
85
+ },
86
+ error: (contextOrMessage, message) => {
87
+ if (shouldLog()) {
88
+ if (typeof contextOrMessage === "string") {
89
+ console.error(formatMessage(contextOrMessage));
90
+ } else {
91
+ console.error(formatMessage(message ?? "Error"), contextOrMessage);
92
+ }
93
+ }
94
+ }
95
+ };
96
+ }
97
+
98
+ // src/nostr.ts
99
+ var log = createLogger("ocp/nostr");
100
+ var ATTESTATION_EVENT_KIND = 30078;
101
+ var DEFAULT_RELAYS = [
102
+ "wss://relay.damus.io",
103
+ "wss://relay.nostr.band",
104
+ "wss://nos.lol",
105
+ "wss://relay.snort.social"
106
+ ];
107
+ function createAttestationEvent(envelope, pubkey) {
108
+ const tags = [
109
+ ["d", envelope.attestation_id],
110
+ // Parameterized replaceable event identifier
111
+ ["address", envelope.address],
112
+ // Bitcoin address
113
+ ["scheme", envelope.scheme],
114
+ // Signature scheme (bip322 or legacy)
115
+ ["issued_at", envelope.issued_at]
116
+ // ISO timestamp
117
+ ];
118
+ for (const identity of envelope.identities) {
119
+ tags.push(["i", `${identity.protocol}:${identity.identifier}`]);
120
+ }
121
+ if (envelope.expires_at) {
122
+ tags.push(["expires", envelope.expires_at]);
123
+ }
124
+ if (envelope.relay_hints?.length) {
125
+ for (const relay of envelope.relay_hints) {
126
+ tags.push(["relay", relay]);
127
+ }
128
+ }
129
+ const content = JSON.stringify(envelope, null, 2);
130
+ return {
131
+ kind: ATTESTATION_EVENT_KIND,
132
+ tags,
133
+ content,
134
+ created_at: Math.floor(Date.parse(envelope.issued_at) / 1e3),
135
+ pubkey
136
+ };
137
+ }
138
+ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
139
+ const success = [];
140
+ const failed = [];
141
+ log.info({ eventId: event.id, relayCount: relays.length }, "Publishing event to relays");
142
+ const publishPromises = relays.map(async (relayUrl) => {
143
+ let ws = null;
144
+ try {
145
+ ws = new WebSocket(relayUrl);
146
+ const wsRef = ws;
147
+ await new Promise((resolve, reject) => {
148
+ const timeout = setTimeout(() => {
149
+ wsRef.close();
150
+ reject(new Error("Connection timeout"));
151
+ }, 5e3);
152
+ const cleanup = () => {
153
+ clearTimeout(timeout);
154
+ wsRef.close();
155
+ };
156
+ wsRef.onopen = () => {
157
+ clearTimeout(timeout);
158
+ wsRef.send(JSON.stringify(["EVENT", event]));
159
+ };
160
+ wsRef.onmessage = (msg) => {
161
+ try {
162
+ const data = JSON.parse(msg.data);
163
+ if (data[0] === "OK" && data[1] === event.id) {
164
+ if (data[2] === true) {
165
+ success.push(relayUrl);
166
+ cleanup();
167
+ resolve();
168
+ } else {
169
+ log.warn(
170
+ { relay: relayUrl, reason: data[3] },
171
+ "Relay rejected event"
172
+ );
173
+ failed.push(relayUrl);
174
+ cleanup();
175
+ reject(new Error(data[3] || "Relay rejected event"));
176
+ }
177
+ }
178
+ } catch (err) {
179
+ log.error(
180
+ { relay: relayUrl, error: err },
181
+ "Failed to parse relay response"
182
+ );
183
+ failed.push(relayUrl);
184
+ cleanup();
185
+ reject(err);
186
+ }
187
+ };
188
+ wsRef.onerror = (err) => {
189
+ log.error({ relay: relayUrl, error: err }, "WebSocket error");
190
+ failed.push(relayUrl);
191
+ cleanup();
192
+ reject(err);
193
+ };
194
+ wsRef.onclose = () => {
195
+ clearTimeout(timeout);
196
+ };
197
+ });
198
+ } catch (err) {
199
+ log.error({ relay: relayUrl, error: err }, "Failed to publish to relay");
200
+ if (!failed.includes(relayUrl)) {
201
+ failed.push(relayUrl);
202
+ }
203
+ } finally {
204
+ if (ws && ws.readyState !== WebSocket.CLOSED) {
205
+ ws.close();
206
+ }
207
+ }
208
+ });
209
+ await Promise.allSettled(publishPromises);
210
+ log.info({ successCount: success.length, failedCount: failed.length }, "Publishing complete");
211
+ return { success, failed };
212
+ }
213
+ async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
214
+ const events = [];
215
+ log.info({ attestationId, relayCount: relays.length }, "Querying relays for attestation");
216
+ const queryPromises = relays.map(async (relayUrl) => {
217
+ let ws = null;
218
+ try {
219
+ ws = new WebSocket(relayUrl);
220
+ const wsRef = ws;
221
+ await new Promise((resolve, reject) => {
222
+ const timeout = setTimeout(() => {
223
+ wsRef.close();
224
+ reject(new Error("Query timeout"));
225
+ }, 1e4);
226
+ const cleanup = () => {
227
+ clearTimeout(timeout);
228
+ wsRef.close();
229
+ };
230
+ wsRef.onopen = () => {
231
+ const subscriptionId = `ochk_${Date.now()}`;
232
+ wsRef.send(
233
+ JSON.stringify([
234
+ "REQ",
235
+ subscriptionId,
236
+ {
237
+ kinds: [30078],
238
+ "#d": [attestationId],
239
+ limit: 1
240
+ }
241
+ ])
242
+ );
243
+ };
244
+ wsRef.onmessage = (msg) => {
245
+ try {
246
+ const data = JSON.parse(msg.data);
247
+ if (data[0] === "EVENT") {
248
+ events.push(data[2]);
249
+ } else if (data[0] === "EOSE") {
250
+ cleanup();
251
+ resolve();
252
+ }
253
+ } catch (err) {
254
+ log.error({ relay: relayUrl, error: err }, "Failed to parse event");
255
+ }
256
+ };
257
+ wsRef.onerror = (err) => {
258
+ log.error({ relay: relayUrl, error: err }, "WebSocket error during query");
259
+ cleanup();
260
+ reject(err);
261
+ };
262
+ wsRef.onclose = () => {
263
+ clearTimeout(timeout);
264
+ };
265
+ });
266
+ } catch (err) {
267
+ log.error({ relay: relayUrl, error: err }, "Failed to query relay");
268
+ } finally {
269
+ if (ws && ws.readyState !== WebSocket.CLOSED) {
270
+ ws.close();
271
+ }
272
+ }
273
+ });
274
+ await Promise.allSettled(queryPromises);
275
+ log.info({ eventCount: events.length }, "Query complete");
276
+ return events;
277
+ }
278
+ async function queryByAddress(address, relays = DEFAULT_RELAYS) {
279
+ const events = [];
280
+ log.info({ address, relayCount: relays.length }, "Querying relays for address");
281
+ const queryPromises = relays.map(async (relayUrl) => {
282
+ try {
283
+ const ws = new WebSocket(relayUrl);
284
+ await new Promise((resolve, reject) => {
285
+ const timeout = setTimeout(() => {
286
+ ws.close();
287
+ reject(new Error("Query timeout"));
288
+ }, 1e4);
289
+ ws.onopen = () => {
290
+ const subscriptionId = `ochk_addr_${Date.now()}`;
291
+ ws.send(
292
+ JSON.stringify([
293
+ "REQ",
294
+ subscriptionId,
295
+ {
296
+ kinds: [30078],
297
+ "#address": [address],
298
+ limit: 10
299
+ }
300
+ ])
301
+ );
302
+ };
303
+ ws.onmessage = (msg) => {
304
+ try {
305
+ const data = JSON.parse(msg.data);
306
+ if (data[0] === "EVENT") {
307
+ events.push(data[2]);
308
+ } else if (data[0] === "EOSE") {
309
+ clearTimeout(timeout);
310
+ ws.close();
311
+ resolve();
312
+ }
313
+ } catch (err) {
314
+ log.error({ relay: relayUrl, error: err }, "Failed to parse event");
315
+ }
316
+ };
317
+ ws.onerror = (err) => {
318
+ clearTimeout(timeout);
319
+ log.error({ relay: relayUrl, error: err }, "WebSocket error during query");
320
+ reject(err);
321
+ };
322
+ });
323
+ } catch (err) {
324
+ log.error({ relay: relayUrl, error: err }, "Failed to query relay");
325
+ }
326
+ });
327
+ await Promise.allSettled(queryPromises);
328
+ log.info({ eventCount: events.length }, "Query complete");
329
+ return events;
330
+ }
331
+ async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
332
+ const events = [];
333
+ const identityTag = `${protocol}:${identifier}`;
334
+ log.info({ protocol, identifier, relayCount: relays.length }, "Querying relays for identity");
335
+ const queryPromises = relays.map(async (relayUrl) => {
336
+ try {
337
+ const ws = new WebSocket(relayUrl);
338
+ await new Promise((resolve, reject) => {
339
+ const timeout = setTimeout(() => {
340
+ ws.close();
341
+ reject(new Error("Query timeout"));
342
+ }, 1e4);
343
+ ws.onopen = () => {
344
+ const subscriptionId = `ochk_identity_${Date.now()}`;
345
+ ws.send(
346
+ JSON.stringify([
347
+ "REQ",
348
+ subscriptionId,
349
+ {
350
+ kinds: [30078],
351
+ "#i": [identityTag],
352
+ limit: 10
353
+ }
354
+ ])
355
+ );
356
+ };
357
+ ws.onmessage = (msg) => {
358
+ try {
359
+ const data = JSON.parse(msg.data);
360
+ if (data[0] === "EVENT") {
361
+ events.push(data[2]);
362
+ } else if (data[0] === "EOSE") {
363
+ clearTimeout(timeout);
364
+ ws.close();
365
+ resolve();
366
+ }
367
+ } catch (err) {
368
+ log.error({ relay: relayUrl, error: err }, "Failed to parse event");
369
+ }
370
+ };
371
+ ws.onerror = (err) => {
372
+ clearTimeout(timeout);
373
+ log.error({ relay: relayUrl, error: err }, "WebSocket error during query");
374
+ reject(err);
375
+ };
376
+ });
377
+ } catch (err) {
378
+ log.error({ relay: relayUrl, error: err }, "Failed to query relay");
379
+ }
380
+ });
381
+ await Promise.allSettled(queryPromises);
382
+ log.info({ eventCount: events.length }, "Query complete");
383
+ return events;
384
+ }
385
+ async function verifyAttestationEnvelope(envelope) {
386
+ try {
387
+ const encoder = new TextEncoder();
388
+ const data = encoder.encode(envelope.message);
389
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
390
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
391
+ const computedId = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
392
+ return computedId === envelope.attestation_id;
393
+ } catch (err) {
394
+ log.error({ error: err }, "Failed to verify attestation envelope");
395
+ return false;
396
+ }
397
+ }
398
+ function parseAttestationFromEvent(event) {
399
+ try {
400
+ const envelope = JSON.parse(event.content);
401
+ return envelope;
402
+ } catch (err) {
403
+ log.error({ eventId: event.id, error: err }, "Failed to parse attestation from event");
404
+ return null;
405
+ }
406
+ }
407
+
408
+ // src/nostr-crypto.ts
409
+ var log2 = createLogger("ocp/nostr-crypto");
410
+ function isNip07Available() {
411
+ if (typeof window === "undefined") {
412
+ return false;
413
+ }
414
+ return !!window.nostr;
415
+ }
416
+ async function signNostrEvent(event) {
417
+ if (!isNip07Available()) {
418
+ log2.warn("NIP-07 extension not available");
419
+ return null;
420
+ }
421
+ try {
422
+ const nostr = window.nostr;
423
+ const signedEvent = await nostr.signEvent(event);
424
+ log2.info({ eventId: signedEvent.id }, "Signed Nostr event with NIP-07");
425
+ return signedEvent;
426
+ } catch (err) {
427
+ log2.error({ error: err }, "Failed to sign Nostr event");
428
+ return null;
429
+ }
430
+ }
431
+
432
+ // src/attestation.ts
433
+ var log3 = createLogger("ocp/attestation");
434
+ var AttestationError = class extends Error {
435
+ constructor(message, code) {
436
+ super(message);
437
+ this.code = code;
438
+ this.name = "AttestationError";
439
+ }
440
+ };
441
+ async function createAttestation(options) {
442
+ const { message, signature, scheme, address, identities = [], extensions = {} } = options;
443
+ log3.info({ address, identityCount: identities.length }, "Creating attestation");
444
+ const envelope = await createAttestationEnvelope(
445
+ message,
446
+ signature,
447
+ scheme,
448
+ address,
449
+ identities,
450
+ extensions
451
+ );
452
+ const isValid = await verifyAttestationEnvelope(envelope);
453
+ if (!isValid) {
454
+ throw new AttestationError(
455
+ "Attestation envelope integrity check failed",
456
+ "INTEGRITY_CHECK_FAILED"
457
+ );
458
+ }
459
+ log3.info({ attestationId: envelope.attestation_id }, "Attestation created successfully");
460
+ return envelope;
461
+ }
462
+ async function publishAttestation(options) {
463
+ const { envelope, npub, relays = DEFAULT_RELAYS } = options;
464
+ log3.info(
465
+ { attestationId: envelope.attestation_id, relayCount: relays.length },
466
+ "Publishing attestation"
467
+ );
468
+ const unsignedEvent = createAttestationEvent(envelope, npub);
469
+ if (!isNip07Available()) {
470
+ log3.warn("NIP-07 unavailable \u2014 skipping Nostr publish");
471
+ throw new Error(
472
+ "nostr_signer_unavailable: install a NIP-07 extension (e.g. nos2x, Alby) to publish"
473
+ );
474
+ }
475
+ log3.info("Using NIP-07 to sign event");
476
+ const signedEvent = await signNostrEvent(unsignedEvent);
477
+ if (!signedEvent) {
478
+ throw new Error("nostr_signing_failed: NIP-07 extension rejected or errored");
479
+ }
480
+ const result = await publishToRelays(signedEvent, relays);
481
+ log3.info(
482
+ {
483
+ attestationId: envelope.attestation_id,
484
+ successCount: result.success.length,
485
+ failedCount: result.failed.length
486
+ },
487
+ "Publishing complete"
488
+ );
489
+ return result;
490
+ }
491
+ async function discoverAttestations(options) {
492
+ const { attestationId, address, identity, relays = DEFAULT_RELAYS } = options;
493
+ let events = [];
494
+ if (attestationId) {
495
+ log3.info({ attestationId }, "Discovering by attestation ID");
496
+ events = await queryByAttestationId(attestationId, relays);
497
+ } else if (address) {
498
+ log3.info({ address }, "Discovering by Bitcoin address");
499
+ events = await queryByAddress(address, relays);
500
+ } else if (identity) {
501
+ log3.info({ identity }, "Discovering by identity");
502
+ events = await queryByIdentity(identity.protocol, identity.identifier, relays);
503
+ } else {
504
+ throw new Error("Must provide attestationId, address, or identity");
505
+ }
506
+ const envelopes = [];
507
+ for (const event of events) {
508
+ const envelope = parseAttestationFromEvent(event);
509
+ if (envelope) {
510
+ const isValid = await verifyAttestationEnvelope(envelope);
511
+ if (isValid) {
512
+ envelopes.push(envelope);
513
+ } else {
514
+ log3.warn(
515
+ { attestationId: envelope.attestation_id },
516
+ "Skipping invalid attestation envelope"
517
+ );
518
+ }
519
+ }
520
+ }
521
+ log3.info({ eventCount: events.length, validCount: envelopes.length }, "Discovery complete");
522
+ return envelopes;
523
+ }
524
+ async function verifyAttestationById(attestationId, relays = DEFAULT_RELAYS) {
525
+ log3.info({ attestationId }, "Verifying attestation by ID");
526
+ const envelopes = await discoverAttestations({ attestationId, relays });
527
+ if (envelopes.length === 0) {
528
+ log3.warn({ attestationId }, "Attestation not found on any relay");
529
+ return null;
530
+ }
531
+ if (envelopes.length > 1) {
532
+ log3.warn(
533
+ { attestationId, count: envelopes.length },
534
+ "Multiple attestations found with same ID - using first"
535
+ );
536
+ }
537
+ return envelopes[0] || null;
538
+ }
539
+ async function getAttestationsForAddress(address, relays = DEFAULT_RELAYS) {
540
+ log3.info({ address }, "Getting all attestations for address");
541
+ return discoverAttestations({ address, relays });
542
+ }
543
+ async function getAttestationsForIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
544
+ log3.info({ protocol, identifier }, "Getting all attestations for identity");
545
+ return discoverAttestations({ identity: { protocol, identifier }, relays });
546
+ }
547
+ function parseIdentitiesFromString(identitiesStr) {
548
+ return parseIdentities(identitiesStr);
549
+ }
550
+ function formatIdentitiesForDisplay(identities) {
551
+ return identities.map((id) => `${id.protocol}:${id.identifier}`).join(", ");
552
+ }
553
+ function getVerificationUrl(attestationId, baseUrl = "https://ochk.io") {
554
+ return `${baseUrl}/verify?id=${attestationId}`;
555
+ }
556
+ function extractAttestationIdFromUrl(url) {
557
+ try {
558
+ const urlObj = new URL(url);
559
+ return urlObj.searchParams.get("id");
560
+ } catch {
561
+ return null;
562
+ }
563
+ }
564
+
565
+ export { AttestationError, createAttestation, discoverAttestations, extractAttestationIdFromUrl, formatIdentitiesForDisplay, getAttestationsForAddress, getAttestationsForIdentity, getVerificationUrl, parseIdentitiesFromString, publishAttestation, verifyAttestationById };
566
+ //# sourceMappingURL=attestation.mjs.map
567
+ //# sourceMappingURL=attestation.mjs.map