@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,62 @@
1
+ type Scheme = 'bip322' | 'legacy';
2
+ type Network = 'mainnet' | 'testnet' | 'signet';
3
+ type IdentityProtocol = 'nostr' | 'dns' | 'twitter' | 'github' | string;
4
+ interface IdentityBinding {
5
+ protocol: string;
6
+ identifier: string;
7
+ proof?: string;
8
+ }
9
+ type StatusCode = 'sig_ok_bip322' | 'sig_ok_legacy' | 'sig_invalid' | 'sig_unsupported_script' | 'bond_confirmed' | 'bond_zero' | 'bond_pending' | 'bond_insufficient' | 'aud_mismatch' | 'expired' | 'network_testmode' | 'bad_request' | 'decode_error' | 'invalid_scheme' | 'invalid_attestation_id';
10
+ interface VerifyOptions {
11
+ testMode?: boolean;
12
+ demoMode?: boolean;
13
+ expectedAud?: string;
14
+ esploraMainnetBase?: string;
15
+ esploraSignetBase?: string;
16
+ esploraTestnetBase?: string;
17
+ }
18
+ interface VerifyInput {
19
+ addr?: string;
20
+ msg?: string;
21
+ sig?: string;
22
+ scheme?: Scheme;
23
+ attestation_id?: string;
24
+ }
25
+ interface Metrics {
26
+ sats_bonded: number;
27
+ days_unspent: number;
28
+ score: number;
29
+ }
30
+ interface VerifyOutcome {
31
+ ok: boolean;
32
+ codes: StatusCode[];
33
+ network: Network;
34
+ attestation_id?: string;
35
+ identities?: IdentityBinding[];
36
+ metrics?: Metrics;
37
+ }
38
+ interface AttestationEnvelope {
39
+ attestation_id: string;
40
+ scheme: Scheme;
41
+ address: string;
42
+ identities: IdentityBinding[];
43
+ message: string;
44
+ message_b64url: string;
45
+ signature: string;
46
+ issued_at: string;
47
+ expires_at?: string;
48
+ verification_url: string;
49
+ publish_targets?: string[];
50
+ relay_hints?: string[];
51
+ }
52
+ interface NostrEvent {
53
+ kind: number;
54
+ tags: string[][];
55
+ content: string;
56
+ created_at: number;
57
+ pubkey: string;
58
+ id?: string;
59
+ sig?: string;
60
+ }
61
+
62
+ export type { AttestationEnvelope as A, IdentityBinding as I, Metrics as M, NostrEvent as N, Scheme as S, VerifyInput as V, VerifyOptions as a, VerifyOutcome as b, Network as c, StatusCode as d, IdentityProtocol as e };
@@ -0,0 +1,62 @@
1
+ type Scheme = 'bip322' | 'legacy';
2
+ type Network = 'mainnet' | 'testnet' | 'signet';
3
+ type IdentityProtocol = 'nostr' | 'dns' | 'twitter' | 'github' | string;
4
+ interface IdentityBinding {
5
+ protocol: string;
6
+ identifier: string;
7
+ proof?: string;
8
+ }
9
+ type StatusCode = 'sig_ok_bip322' | 'sig_ok_legacy' | 'sig_invalid' | 'sig_unsupported_script' | 'bond_confirmed' | 'bond_zero' | 'bond_pending' | 'bond_insufficient' | 'aud_mismatch' | 'expired' | 'network_testmode' | 'bad_request' | 'decode_error' | 'invalid_scheme' | 'invalid_attestation_id';
10
+ interface VerifyOptions {
11
+ testMode?: boolean;
12
+ demoMode?: boolean;
13
+ expectedAud?: string;
14
+ esploraMainnetBase?: string;
15
+ esploraSignetBase?: string;
16
+ esploraTestnetBase?: string;
17
+ }
18
+ interface VerifyInput {
19
+ addr?: string;
20
+ msg?: string;
21
+ sig?: string;
22
+ scheme?: Scheme;
23
+ attestation_id?: string;
24
+ }
25
+ interface Metrics {
26
+ sats_bonded: number;
27
+ days_unspent: number;
28
+ score: number;
29
+ }
30
+ interface VerifyOutcome {
31
+ ok: boolean;
32
+ codes: StatusCode[];
33
+ network: Network;
34
+ attestation_id?: string;
35
+ identities?: IdentityBinding[];
36
+ metrics?: Metrics;
37
+ }
38
+ interface AttestationEnvelope {
39
+ attestation_id: string;
40
+ scheme: Scheme;
41
+ address: string;
42
+ identities: IdentityBinding[];
43
+ message: string;
44
+ message_b64url: string;
45
+ signature: string;
46
+ issued_at: string;
47
+ expires_at?: string;
48
+ verification_url: string;
49
+ publish_targets?: string[];
50
+ relay_hints?: string[];
51
+ }
52
+ interface NostrEvent {
53
+ kind: number;
54
+ tags: string[][];
55
+ content: string;
56
+ created_at: number;
57
+ pubkey: string;
58
+ id?: string;
59
+ sig?: string;
60
+ }
61
+
62
+ export type { AttestationEnvelope as A, IdentityBinding as I, Metrics as M, NostrEvent as N, Scheme as S, VerifyInput as V, VerifyOptions as a, VerifyOutcome as b, Network as c, StatusCode as d, IdentityProtocol as e };
@@ -0,0 +1,6 @@
1
+ export { ScoringAlgorithm, computeAllScores, computeScore } from './scoring.mjs';
2
+ import { V as VerifyInput, a as VerifyOptions, b as VerifyOutcome } from './types-BJKDroFv.mjs';
3
+
4
+ declare function verify(input: VerifyInput, opts?: VerifyOptions): Promise<VerifyOutcome>;
5
+
6
+ export { verify };
@@ -0,0 +1,6 @@
1
+ export { ScoringAlgorithm, computeAllScores, computeScore } from './scoring.js';
2
+ import { V as VerifyInput, a as VerifyOptions, b as VerifyOutcome } from './types-BJKDroFv.js';
3
+
4
+ declare function verify(input: VerifyInput, opts?: VerifyOptions): Promise<VerifyOutcome>;
5
+
6
+ export { verify };
package/dist/verify.js ADDED
@@ -0,0 +1,509 @@
1
+ 'use strict';
2
+
3
+ var buffer = require('buffer');
4
+ var bip322Js = require('bip322-js');
5
+ var bitcoinMessage = require('bitcoinjs-message');
6
+
7
+ function _interopNamespace(e) {
8
+ if (e && e.__esModule) return e;
9
+ var n = Object.create(null);
10
+ if (e) {
11
+ Object.keys(e).forEach(function (k) {
12
+ if (k !== 'default') {
13
+ var d = Object.getOwnPropertyDescriptor(e, k);
14
+ Object.defineProperty(n, k, d.get ? d : {
15
+ enumerable: true,
16
+ get: function () { return e[k]; }
17
+ });
18
+ }
19
+ });
20
+ }
21
+ n.default = e;
22
+ return Object.freeze(n);
23
+ }
24
+
25
+ var bitcoinMessage__namespace = /*#__PURE__*/_interopNamespace(bitcoinMessage);
26
+
27
+ // src/verify.ts
28
+
29
+ // src/canonical.ts
30
+ function toLowerHex(bytes) {
31
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
32
+ }
33
+ function parseIdentities(identitiesStr) {
34
+ if (!identitiesStr || identitiesStr.trim() === "") {
35
+ return [];
36
+ }
37
+ return identitiesStr.split(",").map((binding) => {
38
+ const colonIndex = binding.indexOf(":");
39
+ if (colonIndex === -1) {
40
+ throw new Error(`Invalid identity binding format: ${binding}`);
41
+ }
42
+ return {
43
+ protocol: binding.substring(0, colonIndex).trim(),
44
+ identifier: binding.substring(colonIndex + 1).trim()
45
+ };
46
+ }).filter((binding) => binding.protocol && binding.identifier);
47
+ }
48
+ async function generateAttestationId(message) {
49
+ const encoder = new TextEncoder();
50
+ const data = encoder.encode(message);
51
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
52
+ return toLowerHex(new Uint8Array(hashBuffer));
53
+ }
54
+
55
+ // src/scoring.ts
56
+ function scoreV0(sats, days) {
57
+ if (sats <= 0 || days <= 0) return 0;
58
+ return Math.round(Math.log(1 + sats) * (1 + days / 30) * 100) / 100;
59
+ }
60
+ function scoreTier(sats, days) {
61
+ if (sats >= 1e7 && days >= 365) return "platinum";
62
+ if (sats >= 1e6 && days >= 180) return "gold";
63
+ if (sats >= 1e5 && days >= 90) return "silver";
64
+ if (sats >= 1e4 && days >= 30) return "bronze";
65
+ return "none";
66
+ }
67
+ function computeScore(sats, days, config) {
68
+ switch (config.algorithm) {
69
+ case "v0":
70
+ return scoreV0(sats, days);
71
+ case "tier":
72
+ return scoreTier(sats, days);
73
+ case "none":
74
+ return null;
75
+ }
76
+ }
77
+ function computeAllScores(sats, days) {
78
+ return { v0: scoreV0(sats, days), tier: scoreTier(sats, days) };
79
+ }
80
+ function createLogger(namespace) {
81
+ const shouldLog = (level) => {
82
+ return false;
83
+ };
84
+ const formatMessage = (msg) => `[${namespace}] ${msg}`;
85
+ return {
86
+ debug: (contextOrMessage, message) => {
87
+ if (shouldLog()) {
88
+ if (typeof contextOrMessage === "string") {
89
+ console.debug(formatMessage(contextOrMessage));
90
+ } else {
91
+ console.debug(formatMessage(message ?? "Debug"), contextOrMessage);
92
+ }
93
+ }
94
+ },
95
+ info: (contextOrMessage, message) => {
96
+ if (shouldLog()) {
97
+ if (typeof contextOrMessage === "string") {
98
+ console.info(formatMessage(contextOrMessage));
99
+ } else {
100
+ console.info(formatMessage(message ?? "Info"), contextOrMessage);
101
+ }
102
+ }
103
+ },
104
+ warn: (contextOrMessage, message) => {
105
+ if (shouldLog()) {
106
+ if (typeof contextOrMessage === "string") {
107
+ console.warn(formatMessage(contextOrMessage));
108
+ } else {
109
+ console.warn(formatMessage(message ?? "Warning"), contextOrMessage);
110
+ }
111
+ }
112
+ },
113
+ error: (contextOrMessage, message) => {
114
+ if (shouldLog()) {
115
+ if (typeof contextOrMessage === "string") {
116
+ console.error(formatMessage(contextOrMessage));
117
+ } else {
118
+ console.error(formatMessage(message ?? "Error"), contextOrMessage);
119
+ }
120
+ }
121
+ }
122
+ };
123
+ }
124
+
125
+ // src/verify.ts
126
+ var log = createLogger("ocp/verify");
127
+ function scoreV02(sats, days) {
128
+ return computeScore(sats, days, { algorithm: "v0" });
129
+ }
130
+ function looksLikeHex(s) {
131
+ return /^[0-9a-fA-F]+$/.test(s) && s.length % 2 === 0;
132
+ }
133
+ function toBase64Maybe(sig) {
134
+ if (looksLikeHex(sig)) {
135
+ return buffer.Buffer.from(sig, "hex").toString("base64");
136
+ }
137
+ return sig;
138
+ }
139
+ function detectSignatureScheme(addr, sig) {
140
+ const isSegWit = addr.startsWith("bc1") || addr.startsWith("tb1");
141
+ if (isSegWit) {
142
+ return "bip322";
143
+ }
144
+ const sigBase64 = toBase64Maybe(sig);
145
+ const looksLikeLegacy = sigBase64.length === 88 && /^[IHG]/.test(sigBase64);
146
+ if (looksLikeLegacy) {
147
+ return "legacy";
148
+ }
149
+ return "bip322";
150
+ }
151
+ async function verifySignature({
152
+ addr,
153
+ msg,
154
+ sig,
155
+ scheme
156
+ }) {
157
+ try {
158
+ if (sig === "demo_signature") {
159
+ log.warn("Demo signature detected in verifySignature - should be handled by demo mode");
160
+ return "sig_invalid";
161
+ }
162
+ const detectedScheme = scheme || detectSignatureScheme(addr, sig);
163
+ log.debug(
164
+ { addr, msgLength: msg.length, sigLength: sig.length, scheme: detectedScheme },
165
+ "Verifying signature"
166
+ );
167
+ if (detectedScheme === "bip322") {
168
+ const sigBase64 = toBase64Maybe(sig);
169
+ log.debug(
170
+ { sigBase64: sigBase64.substring(0, 50) + "..." },
171
+ "Signature converted to base64"
172
+ );
173
+ log.debug(
174
+ {
175
+ msgFull: msg,
176
+ msgLines: msg.split("\n").length,
177
+ msgFirstLine: msg.split("\n")[0],
178
+ msgLastLine: msg.split("\n")[msg.split("\n").length - 1]
179
+ },
180
+ "Message being verified"
181
+ );
182
+ const ok = bip322Js.Verifier.verifySignature(addr, msg, sigBase64);
183
+ log.info({ addr, ok }, "BIP-322 verification result");
184
+ if (!ok) {
185
+ const isSegWit = addr.startsWith("bc1") || addr.startsWith("tb1");
186
+ if (isSegWit && sigBase64.length === 88) {
187
+ log.warn(
188
+ { addr, wallet: "possibly Electrum", sigLength: sigBase64.length },
189
+ "BIP-322 verification failed - likely Electrum wallet (does not support BIP-322 for SegWit)"
190
+ );
191
+ } else {
192
+ log.error(
193
+ {
194
+ addr,
195
+ sigLength: sigBase64.length,
196
+ msgLength: msg.length,
197
+ msgPreview: msg.substring(0, 200)
198
+ },
199
+ "BIP-322 verification failed - signature does not match message"
200
+ );
201
+ }
202
+ }
203
+ return ok ? "sig_ok_bip322" : "sig_invalid";
204
+ }
205
+ if (detectedScheme === "legacy") {
206
+ const sigBase64 = toBase64Maybe(sig);
207
+ if (!addr.startsWith("1") && !addr.startsWith("m") && !addr.startsWith("n")) {
208
+ log.warn({ addr }, "Legacy signature attempted on non-P2PKH address");
209
+ return "sig_unsupported_script";
210
+ }
211
+ log.debug(
212
+ {
213
+ addr,
214
+ sigBase64: sigBase64.substring(0, 50) + "...",
215
+ msgPreview: msg.substring(0, 100) + "...",
216
+ msgLength: msg.length
217
+ },
218
+ "Verifying legacy signature"
219
+ );
220
+ const ok = bitcoinMessage__namespace.verify(msg, addr, sigBase64);
221
+ log.info({ addr, ok, sigBase64Length: sigBase64.length }, "Legacy verification result");
222
+ if (!ok) {
223
+ log.error(
224
+ {
225
+ addr,
226
+ msgFirstLine: msg.split("\n")[0],
227
+ sigFormat: sigBase64.substring(0, 10)
228
+ },
229
+ "Legacy signature verification failed - check message format and signature"
230
+ );
231
+ }
232
+ return ok ? "sig_ok_legacy" : "sig_invalid";
233
+ }
234
+ return "invalid_scheme";
235
+ } catch (err) {
236
+ log.error(
237
+ { addr, error: err instanceof Error ? err.message : String(err) },
238
+ "Signature verification error"
239
+ );
240
+ return "sig_invalid";
241
+ }
242
+ }
243
+ function parseCanonicalMessage(msg) {
244
+ const lines = msg.split("\n").map((l) => l.trim());
245
+ const getVal = (prefix) => lines.find((l) => l.toLowerCase().startsWith(prefix + ":"))?.split(":", 2)[1]?.trim();
246
+ const address = getVal("address") || "";
247
+ const identitiesStr = getVal("identities") || "";
248
+ let identities = [];
249
+ try {
250
+ identities = parseIdentities(identitiesStr);
251
+ } catch (err) {
252
+ log.warn({ identitiesStr, error: err }, "Failed to parse identities field");
253
+ }
254
+ const coreKeys = /* @__PURE__ */ new Set([
255
+ "orangecheck",
256
+ "identities",
257
+ "address",
258
+ "purpose",
259
+ "nonce",
260
+ "issued_at",
261
+ "ack"
262
+ ]);
263
+ const extensions = {};
264
+ for (const line of lines) {
265
+ const idx = line.indexOf(":");
266
+ if (idx === -1) continue;
267
+ const key = line.slice(0, idx).trim().toLowerCase();
268
+ const val = line.slice(idx + 1).trim();
269
+ if (!coreKeys.has(key) && val) extensions[key] = val;
270
+ }
271
+ const network = extensions["network"] || "mainnet";
272
+ const bondStr = extensions["bond"];
273
+ const bond = bondStr ? parseInt(bondStr, 10) : void 0;
274
+ return {
275
+ core: { address, identities },
276
+ network,
277
+ extensions,
278
+ bond: bond !== void 0 && !isNaN(bond) && bond > 0 ? bond : void 0
279
+ };
280
+ }
281
+ async function getAddressUtxos(address, network, opts) {
282
+ const endpoints = network === "signet" ? [
283
+ opts?.esploraSignetBase || "https://mempool.space/signet/api",
284
+ "https://blockstream.info/signet/api"
285
+ ] : network === "testnet" ? [
286
+ opts?.esploraTestnetBase || "https://mempool.space/testnet/api",
287
+ "https://blockstream.info/testnet/api"
288
+ ] : [
289
+ opts?.esploraMainnetBase || "https://mempool.space/api",
290
+ "https://blockstream.info/api"
291
+ ];
292
+ let lastError = null;
293
+ for (const base of endpoints) {
294
+ try {
295
+ const url = `${base}/address/${address}/utxo`;
296
+ log.debug({ url, network }, "Fetching UTXOs");
297
+ const resp = await fetch(url, {
298
+ method: "GET",
299
+ headers: { Accept: "application/json" },
300
+ signal: AbortSignal.timeout(1e4)
301
+ // 10s timeout
302
+ });
303
+ if (!resp.ok) {
304
+ throw new Error(`HTTP ${resp.status}`);
305
+ }
306
+ const data = await resp.json();
307
+ if (!Array.isArray(data)) {
308
+ throw new Error("Invalid response: expected array");
309
+ }
310
+ for (const utxo of data) {
311
+ if (typeof utxo.value !== "number" || typeof utxo.status !== "object" || typeof utxo.status.confirmed !== "boolean" || typeof utxo.txid !== "string" || typeof utxo.vout !== "number") {
312
+ throw new Error("Invalid UTXO structure");
313
+ }
314
+ }
315
+ log.info({ base, count: data.length, network }, "UTXOs fetched successfully");
316
+ return data;
317
+ } catch (err) {
318
+ lastError = err instanceof Error ? err : new Error(String(err));
319
+ log.warn(
320
+ { base, error: lastError.message, network },
321
+ "Esplora endpoint failed, trying next"
322
+ );
323
+ }
324
+ }
325
+ log.error({ endpoints, error: lastError?.message, network }, "All Esplora endpoints failed");
326
+ throw new Error(`esplora_all_failed: ${lastError?.message || "unknown"}`);
327
+ }
328
+ function computeMetrics(utxos, bondAmount) {
329
+ const confirmedUtxos = utxos.filter((u) => u.status.confirmed);
330
+ if (confirmedUtxos.length === 0) {
331
+ return { sats_bonded: 0, days_unspent: 0, confirmed_balance: 0 };
332
+ }
333
+ const confirmed_balance = confirmedUtxos.reduce((sum, u) => sum + u.value, 0);
334
+ if (bondAmount !== void 0 && bondAmount > 0) {
335
+ const sorted = [...confirmedUtxos].sort((a, b) => {
336
+ const heightA = a.status.block_height ?? Infinity;
337
+ const heightB = b.status.block_height ?? Infinity;
338
+ if (heightA !== heightB) return heightA - heightB;
339
+ if (a.txid !== b.txid) return a.txid.localeCompare(b.txid);
340
+ return a.vout - b.vout;
341
+ });
342
+ let sum = 0;
343
+ let youngestTime = 0;
344
+ for (const utxo of sorted) {
345
+ sum += utxo.value;
346
+ youngestTime = Math.max(youngestTime, utxo.status.block_time || 0);
347
+ if (sum >= bondAmount) break;
348
+ }
349
+ const days_unspent2 = Math.max(
350
+ 0,
351
+ Math.floor((Date.now() / 1e3 - youngestTime) / (24 * 60 * 60))
352
+ );
353
+ return {
354
+ sats_bonded: bondAmount,
355
+ // Use exactly bond amount (ignore surplus)
356
+ days_unspent: days_unspent2,
357
+ confirmed_balance
358
+ };
359
+ }
360
+ const sats_bonded = confirmed_balance;
361
+ const oldestTime = Math.min(
362
+ ...confirmedUtxos.map((u) => u.status.block_time || Date.now() / 1e3)
363
+ );
364
+ const days_unspent = Math.max(0, Math.floor((Date.now() / 1e3 - oldestTime) / (24 * 60 * 60)));
365
+ return { sats_bonded, days_unspent, confirmed_balance };
366
+ }
367
+ async function verify2(input, opts = {}) {
368
+ const codes = [];
369
+ let network = "mainnet";
370
+ try {
371
+ if (!input.msg || !input.addr || !input.sig) {
372
+ codes.push("bad_request");
373
+ return { ok: false, codes, network };
374
+ }
375
+ const parsed = parseCanonicalMessage(input.msg);
376
+ network = parsed.network || "mainnet";
377
+ const attestation_id = await generateAttestationId(input.msg);
378
+ if (parsed.core.address !== input.addr) {
379
+ codes.push("bad_request");
380
+ return { ok: false, codes, network, attestation_id };
381
+ }
382
+ if (opts.demoMode) {
383
+ const sats_bonded = 1e6;
384
+ const days_unspent = 90;
385
+ const metrics = {
386
+ sats_bonded,
387
+ days_unspent,
388
+ score: scoreV02(sats_bonded, days_unspent)
389
+ // ≈ 55.26
390
+ };
391
+ codes.push("sig_ok_bip322");
392
+ codes.push("bond_confirmed");
393
+ return {
394
+ ok: true,
395
+ codes,
396
+ network,
397
+ attestation_id,
398
+ identities: parsed.core.identities,
399
+ metrics
400
+ };
401
+ }
402
+ if ((network === "testnet" || network === "signet") && !opts.testMode) {
403
+ codes.push("network_testmode");
404
+ return { ok: false, codes, network, attestation_id };
405
+ }
406
+ if (opts.expectedAud && parsed.extensions["aud"] && parsed.extensions["aud"] !== opts.expectedAud) {
407
+ codes.push("aud_mismatch");
408
+ }
409
+ if (parsed.extensions["expires"]) {
410
+ const exp = Date.parse(parsed.extensions["expires"]);
411
+ if (!Number.isNaN(exp) && exp < Date.now()) {
412
+ codes.push("expired");
413
+ }
414
+ }
415
+ const sigCode = await verifySignature({
416
+ addr: input.addr,
417
+ msg: input.msg,
418
+ sig: input.sig,
419
+ ...input.scheme && { scheme: input.scheme }
420
+ });
421
+ codes.push(sigCode);
422
+ const sigOk = sigCode === "sig_ok_bip322" || sigCode === "sig_ok_legacy";
423
+ if (!sigOk) {
424
+ return {
425
+ ok: false,
426
+ codes,
427
+ network,
428
+ attestation_id,
429
+ identities: parsed.core.identities
430
+ };
431
+ }
432
+ try {
433
+ const utxos = await getAddressUtxos(input.addr, network, {
434
+ esploraMainnetBase: opts.esploraMainnetBase,
435
+ esploraSignetBase: opts.esploraSignetBase
436
+ });
437
+ const { sats_bonded, days_unspent, confirmed_balance } = computeMetrics(
438
+ utxos,
439
+ parsed.bond
440
+ );
441
+ if (parsed.bond !== void 0 && parsed.bond > 0) {
442
+ if (confirmed_balance < parsed.bond) {
443
+ codes.push("bond_insufficient");
444
+ log.warn(
445
+ {
446
+ bond: parsed.bond,
447
+ confirmed_balance,
448
+ address: input.addr
449
+ },
450
+ "Bond extension: insufficient balance"
451
+ );
452
+ return {
453
+ ok: false,
454
+ codes,
455
+ network,
456
+ attestation_id,
457
+ identities: parsed.core.identities
458
+ };
459
+ }
460
+ }
461
+ if (sats_bonded <= 0) {
462
+ codes.push("bond_zero");
463
+ if (utxos.some((u) => !u.status.confirmed)) {
464
+ codes.push("bond_pending");
465
+ }
466
+ return {
467
+ ok: false,
468
+ codes,
469
+ network,
470
+ attestation_id,
471
+ identities: parsed.core.identities
472
+ };
473
+ } else {
474
+ codes.push("bond_confirmed");
475
+ }
476
+ const metrics = {
477
+ sats_bonded,
478
+ days_unspent,
479
+ score: scoreV02(sats_bonded, days_unspent)
480
+ };
481
+ return {
482
+ ok: true,
483
+ codes,
484
+ network,
485
+ attestation_id,
486
+ identities: parsed.core.identities,
487
+ metrics
488
+ };
489
+ } catch (utxoError) {
490
+ console.error("UTXO fetch failed:", utxoError);
491
+ codes.push("bad_request");
492
+ return { ok: false, codes, network, attestation_id };
493
+ }
494
+ } catch (e) {
495
+ const msg = e instanceof Error ? e.message : String(e ?? "decode_error");
496
+ if (msg === "decode_error") {
497
+ codes.push("decode_error");
498
+ } else {
499
+ codes.push("bad_request");
500
+ }
501
+ return { ok: false, codes, network };
502
+ }
503
+ }
504
+
505
+ exports.computeAllScores = computeAllScores;
506
+ exports.computeScore = computeScore;
507
+ exports.verify = verify2;
508
+ //# sourceMappingURL=verify.js.map
509
+ //# sourceMappingURL=verify.js.map