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