@orangecheck/sdk 1.14.1 → 1.16.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.
package/README.md CHANGED
@@ -61,7 +61,6 @@ const outcome = await verify({
61
61
  addr: 'bc1q...',
62
62
  msg: canonicalMessage, // the exact signed text
63
63
  sig: signature,
64
- scheme: 'bip322',
65
64
  });
66
65
 
67
66
  if (outcome.ok) {
@@ -69,6 +68,8 @@ if (outcome.ok) {
69
68
  }
70
69
  ```
71
70
 
71
+ The signature's bytes decide how it is checked: 65 bytes is a legacy (BIP-137) signature, on any address it can stand for, and anything else is BIP-322 simple, with or without the `smp` prefix. `outcome.codes` names the one that verified, `sig_ok_bip322` or `sig_ok_legacy`. A BIP-322 full or proof-of-funds signature gets `sig_unsupported_script`. The `scheme` input is ignored.
72
+
72
73
  Use this when you already hold the `(addr, msg, sig)` tuple — e.g., a user pasted it into your UI, or you're verifying an offline proof.
73
74
 
74
75
  ---
@@ -1,11 +1,52 @@
1
1
  'use strict';
2
2
 
3
3
  var base = require('@scure/base');
4
+ var bip322 = require('@orangecheck/bip322');
5
+ var buffer = require('buffer');
4
6
 
5
7
  // src/canonical.ts
6
8
  function toLowerHex(bytes) {
7
9
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
8
10
  }
11
+ var IDENTITY_PROTOCOL_RE = /^[a-z][a-z0-9_-]*$/;
12
+ var IDENTITY_FORBIDDEN_CHARS = /[\r\n,]/;
13
+ function assertSafeIdentity({ protocol, identifier }) {
14
+ if (!IDENTITY_PROTOCOL_RE.test(protocol)) {
15
+ throw new Error(
16
+ `invalid identity protocol: ${JSON.stringify(protocol)} \u2014 must match /^[a-z][a-z0-9_-]*$/`
17
+ );
18
+ }
19
+ if (!identifier || IDENTITY_FORBIDDEN_CHARS.test(identifier)) {
20
+ throw new Error(
21
+ `invalid identity identifier: ${JSON.stringify(identifier)} \u2014 must be non-empty and contain no newlines or commas`
22
+ );
23
+ }
24
+ }
25
+ function parseIdentities(identitiesStr) {
26
+ if (!identitiesStr || identitiesStr.trim() === "") {
27
+ return [];
28
+ }
29
+ if (IDENTITY_FORBIDDEN_CHARS.test(identitiesStr.replace(/,/g, ""))) {
30
+ throw new Error("identities field contains forbidden characters (newline)");
31
+ }
32
+ const bindings = identitiesStr.split(",").map((binding) => {
33
+ const colonIndex = binding.indexOf(":");
34
+ if (colonIndex === -1) {
35
+ throw new Error(`Invalid identity binding format: ${binding}`);
36
+ }
37
+ return {
38
+ protocol: binding.substring(0, colonIndex).trim(),
39
+ identifier: binding.substring(colonIndex + 1).trim()
40
+ };
41
+ });
42
+ for (const b of bindings) {
43
+ if (!b.protocol || !b.identifier) {
44
+ throw new Error(`Invalid identity binding: ${b.protocol}:${b.identifier}`);
45
+ }
46
+ assertSafeIdentity(b);
47
+ }
48
+ return bindings;
49
+ }
9
50
  async function generateAttestationId(message) {
10
51
  const encoder = new TextEncoder();
11
52
  const data = encoder.encode(message);
@@ -109,8 +150,94 @@ function createLogger(namespace) {
109
150
  };
110
151
  }
111
152
 
153
+ // src/utils/time.ts
154
+ var RFC3339 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
155
+ function rfc3339Ms(value) {
156
+ if (!value || !RFC3339.test(value)) return null;
157
+ const ms = Date.parse(value);
158
+ return Number.isNaN(ms) ? null : ms;
159
+ }
160
+ function toBase64Maybe(sig) {
161
+ if (/^[0-9a-fA-F]+$/.test(sig) && sig.length % 2 === 0) {
162
+ return buffer.Buffer.from(sig, "hex").toString("base64");
163
+ }
164
+ return sig;
165
+ }
166
+ function signatureScheme(sigBase64) {
167
+ if (/^(ful|pof)/.test(sigBase64)) return null;
168
+ if (sigBase64.startsWith("smp")) return "bip322";
169
+ return buffer.Buffer.from(sigBase64, "base64").length === 65 ? "legacy" : "bip322";
170
+ }
171
+
172
+ // src/verify.ts
173
+ var log = createLogger("ocp/verify");
174
+ async function verifySignature({ addr, msg, sig }) {
175
+ const sigBase64 = toBase64Maybe(sig);
176
+ const scheme = signatureScheme(sigBase64);
177
+ if (!scheme) return "sig_unsupported_script";
178
+ const ok = bip322.verifySignature(addr, msg, sigBase64);
179
+ log.info({ addr, ok, scheme, msgLength: msg.length }, "Signature verification result");
180
+ if (!ok) return "sig_invalid";
181
+ return scheme === "legacy" ? "sig_ok_legacy" : "sig_ok_bip322";
182
+ }
183
+ async function signatureIsValid(input) {
184
+ try {
185
+ const code = await verifySignature(input);
186
+ return code === "sig_ok_bip322" || code === "sig_ok_legacy";
187
+ } catch {
188
+ return false;
189
+ }
190
+ }
191
+ function parseCanonicalMessage(msg) {
192
+ const lines = msg.split("\n").map((l) => l.trim());
193
+ const getVal = (prefix) => {
194
+ const needle = prefix + ":";
195
+ const hit = lines.find((l) => l.toLowerCase().startsWith(needle));
196
+ if (!hit) return void 0;
197
+ const idx = hit.indexOf(":");
198
+ return hit.slice(idx + 1).trim();
199
+ };
200
+ const address = getVal("address") || "";
201
+ const identitiesStr = getVal("identities") || "";
202
+ const issued_at = getVal("issued_at") || "";
203
+ const expires = getVal("expires");
204
+ let identities = [];
205
+ try {
206
+ identities = parseIdentities(identitiesStr);
207
+ } catch (err) {
208
+ log.warn({ identitiesStr, error: err }, "Failed to parse identities field");
209
+ }
210
+ const coreKeys = /* @__PURE__ */ new Set([
211
+ "orangecheck",
212
+ "identities",
213
+ "address",
214
+ "purpose",
215
+ "nonce",
216
+ "issued_at",
217
+ "ack"
218
+ ]);
219
+ const extensions = {};
220
+ for (const line of lines) {
221
+ const idx = line.indexOf(":");
222
+ if (idx === -1) continue;
223
+ const key = line.slice(0, idx).trim().toLowerCase();
224
+ const val = line.slice(idx + 1).trim();
225
+ if (!coreKeys.has(key) && val) extensions[key] = val;
226
+ }
227
+ const network = extensions["network"] || "mainnet";
228
+ const bondStr = extensions["bond"];
229
+ const bond = bondStr ? parseInt(bondStr, 10) : void 0;
230
+ return {
231
+ core: { address, identities, issued_at },
232
+ expires,
233
+ network,
234
+ extensions,
235
+ bond: bond !== void 0 && !isNaN(bond) && bond > 0 ? bond : void 0
236
+ };
237
+ }
238
+
112
239
  // src/nostr.ts
113
- var log = createLogger("ocp/nostr");
240
+ var log2 = createLogger("ocp/nostr");
114
241
  var ATTESTATION_EVENT_KIND = 30078;
115
242
  var DEFAULT_RELAYS = [
116
243
  "wss://relay.damus.io",
@@ -163,7 +290,7 @@ function createAttestationEvent(envelope, pubkey) {
163
290
  async function publishToRelays(event, relays = DEFAULT_RELAYS) {
164
291
  const success = [];
165
292
  const failed = [];
166
- log.info({ eventId: event.id, relayCount: relays.length }, "Publishing event to relays");
293
+ log2.info({ eventId: event.id, relayCount: relays.length }, "Publishing event to relays");
167
294
  const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
168
295
  const publishPromises = relays.map(async (relayUrl) => {
169
296
  let ws = null;
@@ -202,7 +329,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
202
329
  cleanup();
203
330
  resolve();
204
331
  } else {
205
- log.warn(
332
+ log2.warn(
206
333
  { relay: relayUrl, reason: data[3] },
207
334
  "Relay rejected event"
208
335
  );
@@ -212,7 +339,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
212
339
  }
213
340
  }
214
341
  } catch (err) {
215
- log.error(
342
+ log2.error(
216
343
  { relay: relayUrl, error: err },
217
344
  "Failed to parse relay response"
218
345
  );
@@ -222,7 +349,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
222
349
  }
223
350
  };
224
351
  wsRef.onerror = (err) => {
225
- log.error({ relay: relayUrl, error: err }, "WebSocket error");
352
+ log2.error({ relay: relayUrl, error: err }, "WebSocket error");
226
353
  recordFailure();
227
354
  cleanup();
228
355
  reject(err);
@@ -232,7 +359,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
232
359
  };
233
360
  });
234
361
  } catch (err) {
235
- log.error({ relay: relayUrl, error: err }, "Failed to publish to relay");
362
+ log2.error({ relay: relayUrl, error: err }, "Failed to publish to relay");
236
363
  recordFailure();
237
364
  } finally {
238
365
  if (ws && ws.readyState !== WebSocket.CLOSED) {
@@ -241,12 +368,12 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
241
368
  }
242
369
  });
243
370
  await Promise.allSettled(publishPromises);
244
- log.info({ successCount: success.length, failedCount: failed.length }, "Publishing complete");
371
+ log2.info({ successCount: success.length, failedCount: failed.length }, "Publishing complete");
245
372
  return { success, failed };
246
373
  }
247
374
  async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
248
375
  const events = [];
249
- log.info({ attestationId, relayCount: relays.length }, "Querying relays for attestation");
376
+ log2.info({ attestationId, relayCount: relays.length }, "Querying relays for attestation");
250
377
  const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
251
378
  const queryPromises = relays.map(async (relayUrl) => {
252
379
  let ws = null;
@@ -286,11 +413,11 @@ async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
286
413
  resolve();
287
414
  }
288
415
  } catch (err) {
289
- log.error({ relay: relayUrl, error: err }, "Failed to parse event");
416
+ log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
290
417
  }
291
418
  };
292
419
  wsRef.onerror = (err) => {
293
- log.error({ relay: relayUrl, error: err }, "WebSocket error during query");
420
+ log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
294
421
  cleanup();
295
422
  reject(err);
296
423
  };
@@ -299,7 +426,7 @@ async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
299
426
  };
300
427
  });
301
428
  } catch (err) {
302
- log.error({ relay: relayUrl, error: err }, "Failed to query relay");
429
+ log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
303
430
  } finally {
304
431
  if (ws && ws.readyState !== WebSocket.CLOSED) {
305
432
  ws.close();
@@ -307,12 +434,12 @@ async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
307
434
  }
308
435
  });
309
436
  await Promise.allSettled(queryPromises);
310
- log.info({ eventCount: events.length }, "Query complete");
437
+ log2.info({ eventCount: events.length }, "Query complete");
311
438
  return events;
312
439
  }
313
440
  async function queryByAddress(address, relays = DEFAULT_RELAYS) {
314
441
  const events = [];
315
- log.info({ address, relayCount: relays.length }, "Querying relays for address");
442
+ log2.info({ address, relayCount: relays.length }, "Querying relays for address");
316
443
  const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
317
444
  const queryPromises = relays.map(async (relayUrl) => {
318
445
  try {
@@ -354,28 +481,28 @@ async function queryByAddress(address, relays = DEFAULT_RELAYS) {
354
481
  resolve();
355
482
  }
356
483
  } catch (err) {
357
- log.error({ relay: relayUrl, error: err }, "Failed to parse event");
484
+ log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
358
485
  }
359
486
  };
360
487
  ws.onerror = (err) => {
361
488
  clearTimeout(timeout);
362
- log.error({ relay: relayUrl, error: err }, "WebSocket error during query");
489
+ log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
363
490
  reject(err);
364
491
  };
365
492
  });
366
493
  } catch (err) {
367
- log.error({ relay: relayUrl, error: err }, "Failed to query relay");
494
+ log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
368
495
  }
369
496
  });
370
497
  await Promise.allSettled(queryPromises);
371
- log.info({ eventCount: events.length }, "Query complete");
498
+ log2.info({ eventCount: events.length }, "Query complete");
372
499
  return events;
373
500
  }
374
501
  async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
375
502
  const events = [];
376
503
  const identityTag = `${protocol}:${identifier}`;
377
504
  const identityTags = protocol === "nostr" ? nostrIdentifierForms(identifier).map((f) => `nostr:${f}`) : [identityTag];
378
- log.info({ protocol, identifier, relayCount: relays.length }, "Querying relays for identity");
505
+ log2.info({ protocol, identifier, relayCount: relays.length }, "Querying relays for identity");
379
506
  const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
380
507
  const queryPromises = relays.map(async (relayUrl) => {
381
508
  try {
@@ -410,21 +537,21 @@ async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
410
537
  resolve();
411
538
  }
412
539
  } catch (err) {
413
- log.error({ relay: relayUrl, error: err }, "Failed to parse event");
540
+ log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
414
541
  }
415
542
  };
416
543
  ws.onerror = (err) => {
417
544
  clearTimeout(timeout);
418
- log.error({ relay: relayUrl, error: err }, "WebSocket error during query");
545
+ log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
419
546
  reject(err);
420
547
  };
421
548
  });
422
549
  } catch (err) {
423
- log.error({ relay: relayUrl, error: err }, "Failed to query relay");
550
+ log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
424
551
  }
425
552
  });
426
553
  await Promise.allSettled(queryPromises);
427
- log.info({ eventCount: events.length }, "Query complete");
554
+ log2.info({ eventCount: events.length }, "Query complete");
428
555
  return events;
429
556
  }
430
557
  async function verifyAttestationEnvelope(envelope) {
@@ -436,22 +563,74 @@ async function verifyAttestationEnvelope(envelope) {
436
563
  const computedId = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
437
564
  return computedId === envelope.attestation_id;
438
565
  } catch (err) {
439
- log.error({ error: err }, "Failed to verify attestation envelope");
566
+ log2.error({ error: err }, "Failed to verify attestation envelope");
440
567
  return false;
441
568
  }
442
569
  }
570
+ function base64Url(text) {
571
+ const base64 = btoa(String.fromCharCode(...new TextEncoder().encode(text)));
572
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
573
+ }
574
+ function signedEnvelope(envelope) {
575
+ let parsed;
576
+ try {
577
+ parsed = parseCanonicalMessage(envelope.message);
578
+ } catch {
579
+ return null;
580
+ }
581
+ if (!parsed.core.address || rfc3339Ms(parsed.core.issued_at) === null) return null;
582
+ const { expires_at: _copy, ...rest } = envelope;
583
+ return {
584
+ ...rest,
585
+ address: parsed.core.address,
586
+ identities: parsed.core.identities,
587
+ issued_at: parsed.core.issued_at,
588
+ ...parsed.expires !== void 0 ? { expires_at: parsed.expires } : {},
589
+ message_b64url: base64Url(envelope.message)
590
+ };
591
+ }
592
+ async function collectSignedEnvelopes(events, accept = () => true) {
593
+ const copies = /* @__PURE__ */ new Map();
594
+ for (const event of events) {
595
+ const raw = parseAttestationFromEvent(event);
596
+ if (!raw || !await verifyAttestationEnvelope(raw)) continue;
597
+ const envelope = signedEnvelope(raw);
598
+ if (!envelope || !accept(event, envelope)) continue;
599
+ const same = copies.get(envelope.attestation_id) ?? [];
600
+ if (!same.some((e) => e.signature === envelope.signature && e.scheme === envelope.scheme)) {
601
+ same.push(envelope);
602
+ }
603
+ copies.set(envelope.attestation_id, same);
604
+ }
605
+ const kept = [];
606
+ for (const same of copies.values()) {
607
+ if (same.length === 1) {
608
+ kept.push(same[0]);
609
+ continue;
610
+ }
611
+ let valid;
612
+ for (const e of same) {
613
+ if (await signatureIsValid({ addr: e.address, msg: e.message, sig: e.signature, scheme: e.scheme })) {
614
+ valid = e;
615
+ break;
616
+ }
617
+ }
618
+ kept.push(valid ?? same[0]);
619
+ }
620
+ return kept;
621
+ }
443
622
  function parseAttestationFromEvent(event) {
444
623
  try {
445
624
  const envelope = JSON.parse(event.content);
446
625
  return envelope;
447
626
  } catch (err) {
448
- log.error({ eventId: event.id, error: err }, "Failed to parse attestation from event");
627
+ log2.error({ eventId: event.id, error: err }, "Failed to parse attestation from event");
449
628
  return null;
450
629
  }
451
630
  }
452
631
 
453
632
  // src/nostr-crypto.ts
454
- var log2 = createLogger("ocp/nostr-crypto");
633
+ var log3 = createLogger("ocp/nostr-crypto");
455
634
  function isNip07Available() {
456
635
  if (typeof window === "undefined") {
457
636
  return false;
@@ -460,22 +639,22 @@ function isNip07Available() {
460
639
  }
461
640
  async function signNostrEvent(event) {
462
641
  if (!isNip07Available()) {
463
- log2.warn("NIP-07 extension not available");
642
+ log3.warn("NIP-07 extension not available");
464
643
  return null;
465
644
  }
466
645
  try {
467
646
  const nostr = window.nostr;
468
647
  const signedEvent = await nostr.signEvent(event);
469
- log2.info({ eventId: signedEvent.id }, "Signed Nostr event with NIP-07");
648
+ log3.info({ eventId: signedEvent.id }, "Signed Nostr event with NIP-07");
470
649
  return signedEvent;
471
650
  } catch (err) {
472
- log2.error({ error: err }, "Failed to sign Nostr event");
651
+ log3.error({ error: err }, "Failed to sign Nostr event");
473
652
  return null;
474
653
  }
475
654
  }
476
655
 
477
656
  // src/attestation.ts
478
- var log3 = createLogger("ocp/attestation");
657
+ var log4 = createLogger("ocp/attestation");
479
658
  var AttestationError = class extends Error {
480
659
  constructor(message, code) {
481
660
  super(message);
@@ -485,7 +664,7 @@ var AttestationError = class extends Error {
485
664
  };
486
665
  async function createAttestation(options) {
487
666
  const { message, signature, scheme, address, identities = [], extensions = {} } = options;
488
- log3.info({ address, identityCount: identities.length }, "Creating attestation");
667
+ log4.info({ address, identityCount: identities.length }, "Creating attestation");
489
668
  const envelope = await createAttestationEnvelope(
490
669
  message,
491
670
  signature,
@@ -501,29 +680,29 @@ async function createAttestation(options) {
501
680
  "INTEGRITY_CHECK_FAILED"
502
681
  );
503
682
  }
504
- log3.info({ attestationId: envelope.attestation_id }, "Attestation created successfully");
683
+ log4.info({ attestationId: envelope.attestation_id }, "Attestation created successfully");
505
684
  return envelope;
506
685
  }
507
686
  async function publishAttestation(options) {
508
687
  const { envelope, npub, relays = DEFAULT_RELAYS } = options;
509
- log3.info(
688
+ log4.info(
510
689
  { attestationId: envelope.attestation_id, relayCount: relays.length },
511
690
  "Publishing attestation"
512
691
  );
513
692
  const unsignedEvent = createAttestationEvent(envelope, npub);
514
693
  if (!isNip07Available()) {
515
- log3.warn("NIP-07 unavailable \u2014 skipping Nostr publish");
694
+ log4.warn("NIP-07 unavailable \u2014 skipping Nostr publish");
516
695
  throw new Error(
517
696
  "nostr_signer_unavailable: install a NIP-07 extension (e.g. nos2x, Alby) to publish"
518
697
  );
519
698
  }
520
- log3.info("Using NIP-07 to sign event");
699
+ log4.info("Using NIP-07 to sign event");
521
700
  const signedEvent = await signNostrEvent(unsignedEvent);
522
701
  if (!signedEvent) {
523
702
  throw new Error("nostr_signing_failed: NIP-07 extension rejected or errored");
524
703
  }
525
704
  const result = await publishToRelays(signedEvent, relays);
526
- log3.info(
705
+ log4.info(
527
706
  {
528
707
  attestationId: envelope.attestation_id,
529
708
  successCount: result.success.length,
@@ -543,49 +722,34 @@ async function discoverAttestations(options) {
543
722
  const { attestationId, address, identity, relays = DEFAULT_RELAYS } = options;
544
723
  let events = [];
545
724
  if (attestationId) {
546
- log3.info({ attestationId }, "Discovering by attestation ID");
725
+ log4.info({ attestationId }, "Discovering by attestation ID");
547
726
  events = await queryByAttestationId(attestationId, relays);
548
727
  } else if (address) {
549
- log3.info({ address }, "Discovering by Bitcoin address");
728
+ log4.info({ address }, "Discovering by Bitcoin address");
550
729
  events = await queryByAddress(address, relays);
551
730
  } else if (identity) {
552
- log3.info({ identity }, "Discovering by identity");
731
+ log4.info({ identity }, "Discovering by identity");
553
732
  events = await queryByIdentity(identity.protocol, identity.identifier, relays);
554
733
  } else {
555
734
  throw new Error("Must provide attestationId, address, or identity");
556
735
  }
557
- const envelopes = [];
558
- const kept = /* @__PURE__ */ new Set();
559
- for (const event of events) {
560
- const envelope = parseAttestationFromEvent(event);
561
- if (envelope && kept.has(envelope.attestation_id)) continue;
562
- if (envelope && !eventIndexesEnvelope(event, envelope)) {
563
- log3.warn({ eventId: event.id }, "Skipping event whose index tags disagree with its envelope");
564
- } else if (envelope) {
565
- const isValid = await verifyAttestationEnvelope(envelope);
566
- if (isValid) {
567
- kept.add(envelope.attestation_id);
568
- envelopes.push(envelope);
569
- } else {
570
- log3.warn(
571
- { attestationId: envelope.attestation_id },
572
- "Skipping invalid attestation envelope"
573
- );
574
- }
575
- }
576
- }
577
- log3.info({ eventCount: events.length, validCount: envelopes.length }, "Discovery complete");
736
+ const envelopes = await collectSignedEnvelopes(events, (event, envelope) => {
737
+ if (eventIndexesEnvelope(event, envelope)) return true;
738
+ log4.warn({ eventId: event.id }, "Skipping event whose index tags disagree with its envelope");
739
+ return false;
740
+ });
741
+ log4.info({ eventCount: events.length, validCount: envelopes.length }, "Discovery complete");
578
742
  return envelopes;
579
743
  }
580
744
  async function verifyAttestationById(attestationId, relays = DEFAULT_RELAYS) {
581
- log3.info({ attestationId }, "Verifying attestation by ID");
745
+ log4.info({ attestationId }, "Verifying attestation by ID");
582
746
  const envelopes = await discoverAttestations({ attestationId, relays });
583
747
  if (envelopes.length === 0) {
584
- log3.warn({ attestationId }, "Attestation not found on any relay");
748
+ log4.warn({ attestationId }, "Attestation not found on any relay");
585
749
  return null;
586
750
  }
587
751
  if (envelopes.length > 1) {
588
- log3.warn(
752
+ log4.warn(
589
753
  { attestationId, count: envelopes.length },
590
754
  "Multiple attestations found with same ID - using first"
591
755
  );
@@ -593,11 +757,11 @@ async function verifyAttestationById(attestationId, relays = DEFAULT_RELAYS) {
593
757
  return envelopes[0] || null;
594
758
  }
595
759
  async function getAttestationsForAddress(address, relays = DEFAULT_RELAYS) {
596
- log3.info({ address }, "Getting all attestations for address");
760
+ log4.info({ address }, "Getting all attestations for address");
597
761
  return discoverAttestations({ address, relays });
598
762
  }
599
763
  async function getAttestationsForIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
600
- log3.info({ protocol, identifier }, "Getting all attestations for identity");
764
+ log4.info({ protocol, identifier }, "Getting all attestations for identity");
601
765
  return discoverAttestations({ identity: { protocol, identifier }, relays });
602
766
  }
603
767
  function formatIdentitiesForDisplay(identities) {