@orangecheck/sdk 1.16.1 → 1.17.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 +1 -0
- package/dist/attestation.js +86 -170
- package/dist/attestation.js.map +1 -1
- package/dist/attestation.mjs +86 -170
- package/dist/attestation.mjs.map +1 -1
- package/dist/identity.js.map +1 -1
- package/dist/identity.mjs.map +1 -1
- package/dist/index.js +131 -172
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +131 -172
- package/dist/index.mjs.map +1 -1
- package/dist/nostr.js +86 -170
- package/dist/nostr.js.map +1 -1
- package/dist/nostr.mjs +86 -170
- package/dist/nostr.mjs.map +1 -1
- package/dist/verify.d.mts +1 -0
- package/dist/verify.d.ts +1 -0
- package/dist/verify.js +35 -0
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +35 -0
- package/dist/verify.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -223,6 +223,44 @@ function rfc3339Ms(value) {
|
|
|
223
223
|
return Number.isNaN(ms) ? null : ms;
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
// src/options.ts
|
|
227
|
+
var CHECK_OPTIONS = /* @__PURE__ */ new Set([
|
|
228
|
+
"addr",
|
|
229
|
+
"id",
|
|
230
|
+
"identity",
|
|
231
|
+
"minSats",
|
|
232
|
+
"minDays",
|
|
233
|
+
"relays",
|
|
234
|
+
"verifyOptions"
|
|
235
|
+
]);
|
|
236
|
+
var VERIFY_OPTIONS = /* @__PURE__ */ new Set([
|
|
237
|
+
"testMode",
|
|
238
|
+
"demoMode",
|
|
239
|
+
"expectedAud",
|
|
240
|
+
"esploraMainnetBase",
|
|
241
|
+
"esploraSignetBase",
|
|
242
|
+
"esploraTestnetBase",
|
|
243
|
+
"coverSats"
|
|
244
|
+
]);
|
|
245
|
+
function refuseUnknown(where, opts, known) {
|
|
246
|
+
if (opts === null || typeof opts !== "object") {
|
|
247
|
+
throw new TypeError(`@orangecheck/sdk: ${where} must be an object`);
|
|
248
|
+
}
|
|
249
|
+
const unknown = Object.keys(opts).filter((k) => !known.has(k));
|
|
250
|
+
if (unknown.length > 0) {
|
|
251
|
+
throw new TypeError(
|
|
252
|
+
`@orangecheck/sdk: ${where}: unknown option ${unknown.join(", ")}. It reads only ${[...known].join(", ")}.`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function checkCheckParams(params) {
|
|
257
|
+
refuseUnknown("check()", params, CHECK_OPTIONS);
|
|
258
|
+
if (params.verifyOptions !== void 0) checkVerifyOptions(params.verifyOptions, "verifyOptions");
|
|
259
|
+
}
|
|
260
|
+
function checkVerifyOptions(opts, where = "verify()") {
|
|
261
|
+
refuseUnknown(where, opts, VERIFY_OPTIONS);
|
|
262
|
+
}
|
|
263
|
+
|
|
226
264
|
// src/scoring.ts
|
|
227
265
|
function scoreV0(sats, days) {
|
|
228
266
|
if (sats <= 0 || days <= 0) return 0;
|
|
@@ -308,11 +346,15 @@ function parseCanonicalMessage(msg) {
|
|
|
308
346
|
"ack"
|
|
309
347
|
]);
|
|
310
348
|
const extensions = {};
|
|
349
|
+
const keys = /* @__PURE__ */ new Set();
|
|
350
|
+
const repeated = [];
|
|
311
351
|
for (const line of lines) {
|
|
312
352
|
const idx = line.indexOf(":");
|
|
313
353
|
if (idx === -1) continue;
|
|
314
354
|
const key = line.slice(0, idx).trim().toLowerCase();
|
|
315
355
|
const val = line.slice(idx + 1).trim();
|
|
356
|
+
if (keys.has(key)) repeated.push(key);
|
|
357
|
+
keys.add(key);
|
|
316
358
|
if (!coreKeys.has(key) && val) extensions[key] = val;
|
|
317
359
|
}
|
|
318
360
|
const network = extensions["network"] || "mainnet";
|
|
@@ -321,6 +363,7 @@ function parseCanonicalMessage(msg) {
|
|
|
321
363
|
return {
|
|
322
364
|
core: { address, identities, issued_at },
|
|
323
365
|
expires,
|
|
366
|
+
repeated,
|
|
324
367
|
network,
|
|
325
368
|
extensions,
|
|
326
369
|
bond: bond !== void 0 && !isNaN(bond) && bond > 0 ? bond : void 0
|
|
@@ -429,6 +472,7 @@ function computeMetrics(utxos, bondAmount, coverSats) {
|
|
|
429
472
|
return { sats_bonded, days_unspent, confirmed_balance };
|
|
430
473
|
}
|
|
431
474
|
async function verify(input, opts = {}) {
|
|
475
|
+
checkVerifyOptions(opts);
|
|
432
476
|
const codes = [];
|
|
433
477
|
let network = "mainnet";
|
|
434
478
|
try {
|
|
@@ -439,6 +483,10 @@ async function verify(input, opts = {}) {
|
|
|
439
483
|
const parsed = parseCanonicalMessage(input.msg);
|
|
440
484
|
network = parsed.network || "mainnet";
|
|
441
485
|
const attestation_id = await generateAttestationId(input.msg);
|
|
486
|
+
if (parsed.repeated.length > 0) {
|
|
487
|
+
codes.push("decode_error");
|
|
488
|
+
return { ok: false, codes, network, attestation_id };
|
|
489
|
+
}
|
|
442
490
|
if (parsed.core.address !== input.addr) {
|
|
443
491
|
codes.push("bad_request");
|
|
444
492
|
return { ok: false, codes, network, attestation_id };
|
|
@@ -696,188 +744,98 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
|
|
|
696
744
|
log2.info({ successCount: success.length, failedCount: failed.length }, "Publishing complete");
|
|
697
745
|
return { success, failed };
|
|
698
746
|
}
|
|
699
|
-
|
|
747
|
+
var PAGE_LIMIT = 500;
|
|
748
|
+
var MAX_PAGES = 4;
|
|
749
|
+
async function querySubject(filter, relays, label) {
|
|
700
750
|
const events = [];
|
|
701
|
-
log2.info({ attestationId, relayCount: relays.length }, "Querying relays for attestation");
|
|
702
751
|
const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
752
|
+
await Promise.allSettled(
|
|
753
|
+
relays.map(async (relayUrl) => {
|
|
754
|
+
let ws = null;
|
|
755
|
+
try {
|
|
756
|
+
ws = new WebSocket(relayUrl);
|
|
757
|
+
const wsRef = ws;
|
|
758
|
+
await new Promise((resolve, reject) => {
|
|
759
|
+
const timeout = setTimeout(() => {
|
|
760
|
+
wsRef.close();
|
|
761
|
+
reject(new Error("Query timeout"));
|
|
762
|
+
}, budgetFor(deadlineAt));
|
|
763
|
+
const seen = /* @__PURE__ */ new Set();
|
|
764
|
+
let page = 0;
|
|
765
|
+
let inPage = 0;
|
|
766
|
+
let newInPage = 0;
|
|
767
|
+
let oldest = Infinity;
|
|
768
|
+
const ask = () => {
|
|
769
|
+
page++;
|
|
770
|
+
inPage = 0;
|
|
771
|
+
newInPage = 0;
|
|
772
|
+
const until = Number.isFinite(oldest) ? { until: oldest } : {};
|
|
773
|
+
wsRef.send(
|
|
774
|
+
JSON.stringify([
|
|
775
|
+
"REQ",
|
|
776
|
+
`ochk_${label}_${Date.now()}_${page}`,
|
|
777
|
+
{ ...filter, ...until, limit: PAGE_LIMIT }
|
|
778
|
+
])
|
|
779
|
+
);
|
|
780
|
+
};
|
|
781
|
+
wsRef.onopen = ask;
|
|
782
|
+
wsRef.onmessage = (msg) => {
|
|
783
|
+
try {
|
|
784
|
+
const data = JSON.parse(msg.data);
|
|
785
|
+
if (data[0] === "EVENT") {
|
|
786
|
+
const event = data[2];
|
|
787
|
+
const key = event.id ?? JSON.stringify(event);
|
|
788
|
+
inPage++;
|
|
789
|
+
if (!seen.has(key)) {
|
|
790
|
+
seen.add(key);
|
|
791
|
+
newInPage++;
|
|
792
|
+
events.push(event);
|
|
793
|
+
}
|
|
794
|
+
if (typeof event.created_at === "number") {
|
|
795
|
+
oldest = Math.min(oldest, event.created_at);
|
|
796
|
+
}
|
|
797
|
+
} else if (data[0] === "EOSE") {
|
|
798
|
+
wsRef.send(JSON.stringify(["CLOSE", data[1]]));
|
|
799
|
+
if (inPage >= PAGE_LIMIT && newInPage > 0 && page < MAX_PAGES) {
|
|
800
|
+
ask();
|
|
801
|
+
} else {
|
|
802
|
+
clearTimeout(timeout);
|
|
803
|
+
wsRef.close();
|
|
804
|
+
resolve();
|
|
805
|
+
}
|
|
727
806
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
};
|
|
731
|
-
wsRef.onmessage = (msg) => {
|
|
732
|
-
try {
|
|
733
|
-
const data = JSON.parse(msg.data);
|
|
734
|
-
if (data[0] === "EVENT") {
|
|
735
|
-
events.push(data[2]);
|
|
736
|
-
} else if (data[0] === "EOSE") {
|
|
737
|
-
cleanup();
|
|
738
|
-
resolve();
|
|
807
|
+
} catch (err) {
|
|
808
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
|
|
739
809
|
}
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
};
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
};
|
|
752
|
-
});
|
|
753
|
-
} catch (err) {
|
|
754
|
-
log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
|
|
755
|
-
} finally {
|
|
756
|
-
if (ws && ws.readyState !== WebSocket.CLOSED) {
|
|
757
|
-
ws.close();
|
|
810
|
+
};
|
|
811
|
+
wsRef.onerror = (err) => {
|
|
812
|
+
clearTimeout(timeout);
|
|
813
|
+
log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
|
|
814
|
+
reject(err);
|
|
815
|
+
};
|
|
816
|
+
});
|
|
817
|
+
} catch (err) {
|
|
818
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
|
|
819
|
+
} finally {
|
|
820
|
+
if (ws && ws.readyState !== WebSocket.CLOSED) ws.close();
|
|
758
821
|
}
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
log2.info({ eventCount: events.length }, "Query complete");
|
|
822
|
+
})
|
|
823
|
+
);
|
|
824
|
+
log2.info({ label, eventCount: events.length }, "Query complete");
|
|
763
825
|
return events;
|
|
764
826
|
}
|
|
827
|
+
async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
|
|
828
|
+
log2.info({ attestationId, relayCount: relays.length }, "Querying relays for attestation");
|
|
829
|
+
return querySubject({ kinds: [30078], "#d": [attestationId] }, relays, "id");
|
|
830
|
+
}
|
|
765
831
|
async function queryByAddress(address, relays = DEFAULT_RELAYS) {
|
|
766
|
-
const events = [];
|
|
767
832
|
log2.info({ address, relayCount: relays.length }, "Querying relays for address");
|
|
768
|
-
|
|
769
|
-
const queryPromises = relays.map(async (relayUrl) => {
|
|
770
|
-
try {
|
|
771
|
-
const ws = new WebSocket(relayUrl);
|
|
772
|
-
await new Promise((resolve, reject) => {
|
|
773
|
-
const timeout = setTimeout(() => {
|
|
774
|
-
ws.close();
|
|
775
|
-
reject(new Error("Query timeout"));
|
|
776
|
-
}, budgetFor(deadlineAt));
|
|
777
|
-
ws.onopen = () => {
|
|
778
|
-
const subscriptionId = `ochk_addr_${Date.now()}`;
|
|
779
|
-
ws.send(
|
|
780
|
-
JSON.stringify([
|
|
781
|
-
"REQ",
|
|
782
|
-
subscriptionId,
|
|
783
|
-
{
|
|
784
|
-
kinds: [30078],
|
|
785
|
-
// `#t`, NOT `#address`: NIP-12 relays index
|
|
786
|
-
// single-letter tags only, so `#address`
|
|
787
|
-
// matched nothing on any relay and this
|
|
788
|
-
// function returned empty for every real
|
|
789
|
-
// attestation. attest.ochk.io's verifier has
|
|
790
|
-
// queried `#t` since the same fix landed
|
|
791
|
-
// there; this is the back-port.
|
|
792
|
-
"#t": [address],
|
|
793
|
-
limit: 50
|
|
794
|
-
}
|
|
795
|
-
])
|
|
796
|
-
);
|
|
797
|
-
};
|
|
798
|
-
ws.onmessage = (msg) => {
|
|
799
|
-
try {
|
|
800
|
-
const data = JSON.parse(msg.data);
|
|
801
|
-
if (data[0] === "EVENT") {
|
|
802
|
-
events.push(data[2]);
|
|
803
|
-
} else if (data[0] === "EOSE") {
|
|
804
|
-
clearTimeout(timeout);
|
|
805
|
-
ws.close();
|
|
806
|
-
resolve();
|
|
807
|
-
}
|
|
808
|
-
} catch (err) {
|
|
809
|
-
log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
|
|
810
|
-
}
|
|
811
|
-
};
|
|
812
|
-
ws.onerror = (err) => {
|
|
813
|
-
clearTimeout(timeout);
|
|
814
|
-
log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
|
|
815
|
-
reject(err);
|
|
816
|
-
};
|
|
817
|
-
});
|
|
818
|
-
} catch (err) {
|
|
819
|
-
log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
|
|
820
|
-
}
|
|
821
|
-
});
|
|
822
|
-
await Promise.allSettled(queryPromises);
|
|
823
|
-
log2.info({ eventCount: events.length }, "Query complete");
|
|
824
|
-
return events;
|
|
833
|
+
return querySubject({ kinds: [30078], "#t": [address] }, relays, "addr");
|
|
825
834
|
}
|
|
826
835
|
async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
|
|
827
|
-
const
|
|
828
|
-
const identityTag = `${protocol}:${identifier}`;
|
|
829
|
-
const identityTags = protocol === "nostr" ? nostrIdentifierForms(identifier).map((f) => `nostr:${f}`) : [identityTag];
|
|
836
|
+
const identityTags = protocol === "nostr" ? nostrIdentifierForms(identifier).map((f) => `nostr:${f}`) : [`${protocol}:${identifier}`];
|
|
830
837
|
log2.info({ protocol, identifier, relayCount: relays.length }, "Querying relays for identity");
|
|
831
|
-
|
|
832
|
-
const queryPromises = relays.map(async (relayUrl) => {
|
|
833
|
-
try {
|
|
834
|
-
const ws = new WebSocket(relayUrl);
|
|
835
|
-
await new Promise((resolve, reject) => {
|
|
836
|
-
const timeout = setTimeout(() => {
|
|
837
|
-
ws.close();
|
|
838
|
-
reject(new Error("Query timeout"));
|
|
839
|
-
}, budgetFor(deadlineAt));
|
|
840
|
-
ws.onopen = () => {
|
|
841
|
-
const subscriptionId = `ochk_identity_${Date.now()}`;
|
|
842
|
-
ws.send(
|
|
843
|
-
JSON.stringify([
|
|
844
|
-
"REQ",
|
|
845
|
-
subscriptionId,
|
|
846
|
-
{
|
|
847
|
-
kinds: [30078],
|
|
848
|
-
"#i": identityTags,
|
|
849
|
-
limit: 10
|
|
850
|
-
}
|
|
851
|
-
])
|
|
852
|
-
);
|
|
853
|
-
};
|
|
854
|
-
ws.onmessage = (msg) => {
|
|
855
|
-
try {
|
|
856
|
-
const data = JSON.parse(msg.data);
|
|
857
|
-
if (data[0] === "EVENT") {
|
|
858
|
-
events.push(data[2]);
|
|
859
|
-
} else if (data[0] === "EOSE") {
|
|
860
|
-
clearTimeout(timeout);
|
|
861
|
-
ws.close();
|
|
862
|
-
resolve();
|
|
863
|
-
}
|
|
864
|
-
} catch (err) {
|
|
865
|
-
log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
|
|
866
|
-
}
|
|
867
|
-
};
|
|
868
|
-
ws.onerror = (err) => {
|
|
869
|
-
clearTimeout(timeout);
|
|
870
|
-
log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
|
|
871
|
-
reject(err);
|
|
872
|
-
};
|
|
873
|
-
});
|
|
874
|
-
} catch (err) {
|
|
875
|
-
log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
|
|
876
|
-
}
|
|
877
|
-
});
|
|
878
|
-
await Promise.allSettled(queryPromises);
|
|
879
|
-
log2.info({ eventCount: events.length }, "Query complete");
|
|
880
|
-
return events;
|
|
838
|
+
return querySubject({ kinds: [30078], "#i": identityTags }, relays, "identity");
|
|
881
839
|
}
|
|
882
840
|
async function queryRecent(relays = DEFAULT_RELAYS, limit = 200) {
|
|
883
841
|
const events = [];
|
|
@@ -960,6 +918,7 @@ function signedEnvelope(envelope) {
|
|
|
960
918
|
return null;
|
|
961
919
|
}
|
|
962
920
|
if (!parsed.core.address || rfc3339Ms(parsed.core.issued_at) === null) return null;
|
|
921
|
+
if (parsed.repeated.length > 0) return null;
|
|
963
922
|
const { expires_at: _copy, ...rest } = envelope;
|
|
964
923
|
return {
|
|
965
924
|
...rest,
|
|
@@ -1191,8 +1150,8 @@ function extractAttestationIdFromUrl(url) {
|
|
|
1191
1150
|
}
|
|
1192
1151
|
|
|
1193
1152
|
// src/check.ts
|
|
1194
|
-
var MAX_CANDIDATES = 20;
|
|
1195
1153
|
async function check(params) {
|
|
1154
|
+
checkCheckParams(params);
|
|
1196
1155
|
const {
|
|
1197
1156
|
addr,
|
|
1198
1157
|
id,
|
|
@@ -1219,7 +1178,7 @@ async function check(params) {
|
|
|
1219
1178
|
return { ok: false, sats: 0, days: 0, score: 0, reasons: ["not_found"] };
|
|
1220
1179
|
}
|
|
1221
1180
|
let env = envelopes[0];
|
|
1222
|
-
for (const candidate of envelopes
|
|
1181
|
+
for (const candidate of envelopes) {
|
|
1223
1182
|
if (await signedBy(candidate)) {
|
|
1224
1183
|
env = candidate;
|
|
1225
1184
|
break;
|