@orangecheck/sdk 1.16.0 → 1.16.2

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/dist/index.js CHANGED
@@ -215,6 +215,7 @@ function logVerificationError(protocol, identifier, error, context) {
215
215
  }
216
216
 
217
217
  // src/utils/time.ts
218
+ var ISSUED_AHEAD_MS = 12e4;
218
219
  var RFC3339 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
219
220
  function rfc3339Ms(value) {
220
221
  if (!value || !RFC3339.test(value)) return null;
@@ -307,11 +308,15 @@ function parseCanonicalMessage(msg) {
307
308
  "ack"
308
309
  ]);
309
310
  const extensions = {};
311
+ const keys = /* @__PURE__ */ new Set();
312
+ const repeated = [];
310
313
  for (const line of lines) {
311
314
  const idx = line.indexOf(":");
312
315
  if (idx === -1) continue;
313
316
  const key = line.slice(0, idx).trim().toLowerCase();
314
317
  const val = line.slice(idx + 1).trim();
318
+ if (keys.has(key)) repeated.push(key);
319
+ keys.add(key);
315
320
  if (!coreKeys.has(key) && val) extensions[key] = val;
316
321
  }
317
322
  const network = extensions["network"] || "mainnet";
@@ -320,6 +325,7 @@ function parseCanonicalMessage(msg) {
320
325
  return {
321
326
  core: { address, identities, issued_at },
322
327
  expires,
328
+ repeated,
323
329
  network,
324
330
  extensions,
325
331
  bond: bond !== void 0 && !isNaN(bond) && bond > 0 ? bond : void 0
@@ -438,6 +444,10 @@ async function verify(input, opts = {}) {
438
444
  const parsed = parseCanonicalMessage(input.msg);
439
445
  network = parsed.network || "mainnet";
440
446
  const attestation_id = await generateAttestationId(input.msg);
447
+ if (parsed.repeated.length > 0) {
448
+ codes.push("decode_error");
449
+ return { ok: false, codes, network, attestation_id };
450
+ }
441
451
  if (parsed.core.address !== input.addr) {
442
452
  codes.push("bad_request");
443
453
  return { ok: false, codes, network, attestation_id };
@@ -453,6 +463,15 @@ async function verify(input, opts = {}) {
453
463
  return { ok: false, codes, network, attestation_id };
454
464
  }
455
465
  }
466
+ const issued = rfc3339Ms(parsed.core.issued_at);
467
+ if (issued === null) {
468
+ codes.push("decode_error");
469
+ return { ok: false, codes, network, attestation_id };
470
+ }
471
+ if (issued > Date.now() + ISSUED_AHEAD_MS) {
472
+ codes.push("not_yet_valid");
473
+ return { ok: false, codes, network, attestation_id };
474
+ }
456
475
  if (parsed.expires !== void 0) {
457
476
  const exp = rfc3339Ms(parsed.expires);
458
477
  if (exp === null || exp < Date.now()) {
@@ -686,188 +705,98 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
686
705
  log2.info({ successCount: success.length, failedCount: failed.length }, "Publishing complete");
687
706
  return { success, failed };
688
707
  }
689
- async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
708
+ var PAGE_LIMIT = 500;
709
+ var MAX_PAGES = 4;
710
+ async function querySubject(filter, relays, label) {
690
711
  const events = [];
691
- log2.info({ attestationId, relayCount: relays.length }, "Querying relays for attestation");
692
712
  const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
693
- const queryPromises = relays.map(async (relayUrl) => {
694
- let ws = null;
695
- try {
696
- ws = new WebSocket(relayUrl);
697
- const wsRef = ws;
698
- await new Promise((resolve, reject) => {
699
- const timeout = setTimeout(() => {
700
- wsRef.close();
701
- reject(new Error("Query timeout"));
702
- }, budgetFor(deadlineAt));
703
- const cleanup = () => {
704
- clearTimeout(timeout);
705
- wsRef.close();
706
- };
707
- wsRef.onopen = () => {
708
- const subscriptionId = `ochk_${Date.now()}`;
709
- wsRef.send(
710
- JSON.stringify([
711
- "REQ",
712
- subscriptionId,
713
- {
714
- kinds: [30078],
715
- "#d": [attestationId],
716
- limit: 1
713
+ await Promise.allSettled(
714
+ relays.map(async (relayUrl) => {
715
+ let ws = null;
716
+ try {
717
+ ws = new WebSocket(relayUrl);
718
+ const wsRef = ws;
719
+ await new Promise((resolve, reject) => {
720
+ const timeout = setTimeout(() => {
721
+ wsRef.close();
722
+ reject(new Error("Query timeout"));
723
+ }, budgetFor(deadlineAt));
724
+ const seen = /* @__PURE__ */ new Set();
725
+ let page = 0;
726
+ let inPage = 0;
727
+ let newInPage = 0;
728
+ let oldest = Infinity;
729
+ const ask = () => {
730
+ page++;
731
+ inPage = 0;
732
+ newInPage = 0;
733
+ const until = Number.isFinite(oldest) ? { until: oldest } : {};
734
+ wsRef.send(
735
+ JSON.stringify([
736
+ "REQ",
737
+ `ochk_${label}_${Date.now()}_${page}`,
738
+ { ...filter, ...until, limit: PAGE_LIMIT }
739
+ ])
740
+ );
741
+ };
742
+ wsRef.onopen = ask;
743
+ wsRef.onmessage = (msg) => {
744
+ try {
745
+ const data = JSON.parse(msg.data);
746
+ if (data[0] === "EVENT") {
747
+ const event = data[2];
748
+ const key = event.id ?? JSON.stringify(event);
749
+ inPage++;
750
+ if (!seen.has(key)) {
751
+ seen.add(key);
752
+ newInPage++;
753
+ events.push(event);
754
+ }
755
+ if (typeof event.created_at === "number") {
756
+ oldest = Math.min(oldest, event.created_at);
757
+ }
758
+ } else if (data[0] === "EOSE") {
759
+ wsRef.send(JSON.stringify(["CLOSE", data[1]]));
760
+ if (inPage >= PAGE_LIMIT && newInPage > 0 && page < MAX_PAGES) {
761
+ ask();
762
+ } else {
763
+ clearTimeout(timeout);
764
+ wsRef.close();
765
+ resolve();
766
+ }
717
767
  }
718
- ])
719
- );
720
- };
721
- wsRef.onmessage = (msg) => {
722
- try {
723
- const data = JSON.parse(msg.data);
724
- if (data[0] === "EVENT") {
725
- events.push(data[2]);
726
- } else if (data[0] === "EOSE") {
727
- cleanup();
728
- resolve();
768
+ } catch (err) {
769
+ log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
729
770
  }
730
- } catch (err) {
731
- log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
732
- }
733
- };
734
- wsRef.onerror = (err) => {
735
- log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
736
- cleanup();
737
- reject(err);
738
- };
739
- wsRef.onclose = () => {
740
- clearTimeout(timeout);
741
- };
742
- });
743
- } catch (err) {
744
- log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
745
- } finally {
746
- if (ws && ws.readyState !== WebSocket.CLOSED) {
747
- ws.close();
771
+ };
772
+ wsRef.onerror = (err) => {
773
+ clearTimeout(timeout);
774
+ log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
775
+ reject(err);
776
+ };
777
+ });
778
+ } catch (err) {
779
+ log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
780
+ } finally {
781
+ if (ws && ws.readyState !== WebSocket.CLOSED) ws.close();
748
782
  }
749
- }
750
- });
751
- await Promise.allSettled(queryPromises);
752
- log2.info({ eventCount: events.length }, "Query complete");
783
+ })
784
+ );
785
+ log2.info({ label, eventCount: events.length }, "Query complete");
753
786
  return events;
754
787
  }
788
+ async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
789
+ log2.info({ attestationId, relayCount: relays.length }, "Querying relays for attestation");
790
+ return querySubject({ kinds: [30078], "#d": [attestationId] }, relays, "id");
791
+ }
755
792
  async function queryByAddress(address, relays = DEFAULT_RELAYS) {
756
- const events = [];
757
793
  log2.info({ address, relayCount: relays.length }, "Querying relays for address");
758
- const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
759
- const queryPromises = relays.map(async (relayUrl) => {
760
- try {
761
- const ws = new WebSocket(relayUrl);
762
- await new Promise((resolve, reject) => {
763
- const timeout = setTimeout(() => {
764
- ws.close();
765
- reject(new Error("Query timeout"));
766
- }, budgetFor(deadlineAt));
767
- ws.onopen = () => {
768
- const subscriptionId = `ochk_addr_${Date.now()}`;
769
- ws.send(
770
- JSON.stringify([
771
- "REQ",
772
- subscriptionId,
773
- {
774
- kinds: [30078],
775
- // `#t`, NOT `#address`: NIP-12 relays index
776
- // single-letter tags only, so `#address`
777
- // matched nothing on any relay and this
778
- // function returned empty for every real
779
- // attestation. attest.ochk.io's verifier has
780
- // queried `#t` since the same fix landed
781
- // there; this is the back-port.
782
- "#t": [address],
783
- limit: 50
784
- }
785
- ])
786
- );
787
- };
788
- ws.onmessage = (msg) => {
789
- try {
790
- const data = JSON.parse(msg.data);
791
- if (data[0] === "EVENT") {
792
- events.push(data[2]);
793
- } else if (data[0] === "EOSE") {
794
- clearTimeout(timeout);
795
- ws.close();
796
- resolve();
797
- }
798
- } catch (err) {
799
- log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
800
- }
801
- };
802
- ws.onerror = (err) => {
803
- clearTimeout(timeout);
804
- log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
805
- reject(err);
806
- };
807
- });
808
- } catch (err) {
809
- log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
810
- }
811
- });
812
- await Promise.allSettled(queryPromises);
813
- log2.info({ eventCount: events.length }, "Query complete");
814
- return events;
794
+ return querySubject({ kinds: [30078], "#t": [address] }, relays, "addr");
815
795
  }
816
796
  async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
817
- const events = [];
818
- const identityTag = `${protocol}:${identifier}`;
819
- const identityTags = protocol === "nostr" ? nostrIdentifierForms(identifier).map((f) => `nostr:${f}`) : [identityTag];
797
+ const identityTags = protocol === "nostr" ? nostrIdentifierForms(identifier).map((f) => `nostr:${f}`) : [`${protocol}:${identifier}`];
820
798
  log2.info({ protocol, identifier, relayCount: relays.length }, "Querying relays for identity");
821
- const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
822
- const queryPromises = relays.map(async (relayUrl) => {
823
- try {
824
- const ws = new WebSocket(relayUrl);
825
- await new Promise((resolve, reject) => {
826
- const timeout = setTimeout(() => {
827
- ws.close();
828
- reject(new Error("Query timeout"));
829
- }, budgetFor(deadlineAt));
830
- ws.onopen = () => {
831
- const subscriptionId = `ochk_identity_${Date.now()}`;
832
- ws.send(
833
- JSON.stringify([
834
- "REQ",
835
- subscriptionId,
836
- {
837
- kinds: [30078],
838
- "#i": identityTags,
839
- limit: 10
840
- }
841
- ])
842
- );
843
- };
844
- ws.onmessage = (msg) => {
845
- try {
846
- const data = JSON.parse(msg.data);
847
- if (data[0] === "EVENT") {
848
- events.push(data[2]);
849
- } else if (data[0] === "EOSE") {
850
- clearTimeout(timeout);
851
- ws.close();
852
- resolve();
853
- }
854
- } catch (err) {
855
- log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
856
- }
857
- };
858
- ws.onerror = (err) => {
859
- clearTimeout(timeout);
860
- log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
861
- reject(err);
862
- };
863
- });
864
- } catch (err) {
865
- log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
866
- }
867
- });
868
- await Promise.allSettled(queryPromises);
869
- log2.info({ eventCount: events.length }, "Query complete");
870
- return events;
799
+ return querySubject({ kinds: [30078], "#i": identityTags }, relays, "identity");
871
800
  }
872
801
  async function queryRecent(relays = DEFAULT_RELAYS, limit = 200) {
873
802
  const events = [];
@@ -950,6 +879,7 @@ function signedEnvelope(envelope) {
950
879
  return null;
951
880
  }
952
881
  if (!parsed.core.address || rfc3339Ms(parsed.core.issued_at) === null) return null;
882
+ if (parsed.repeated.length > 0) return null;
953
883
  const { expires_at: _copy, ...rest } = envelope;
954
884
  return {
955
885
  ...rest,
@@ -1181,8 +1111,6 @@ function extractAttestationIdFromUrl(url) {
1181
1111
  }
1182
1112
 
1183
1113
  // src/check.ts
1184
- var ISSUED_AHEAD_MS = 12e4;
1185
- var MAX_CANDIDATES = 20;
1186
1114
  async function check(params) {
1187
1115
  const {
1188
1116
  addr,
@@ -1210,7 +1138,7 @@ async function check(params) {
1210
1138
  return { ok: false, sats: 0, days: 0, score: 0, reasons: ["not_found"] };
1211
1139
  }
1212
1140
  let env = envelopes[0];
1213
- for (const candidate of envelopes.slice(0, MAX_CANDIDATES)) {
1141
+ for (const candidate of envelopes) {
1214
1142
  if (await signedBy(candidate)) {
1215
1143
  env = candidate;
1216
1144
  break;
@@ -2361,6 +2289,11 @@ var STATUS_META = {
2361
2289
  detail: "This signature has passed its expiration date. Generate a new signature with a fresh message.",
2362
2290
  severity: "error"
2363
2291
  },
2292
+ not_yet_valid: {
2293
+ label: "Not valid yet",
2294
+ detail: "The message is dated ahead of this clock. It counts once that time arrives, or once a clock that is wrong is set right.",
2295
+ severity: "error"
2296
+ },
2364
2297
  network_testmode: {
2365
2298
  label: "Test network detected",
2366
2299
  detail: "This signature uses testnet or signet. Test networks are for development only and cannot be verified in production mode.",