@bwalletx/connect 0.4.1 → 0.5.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 (47) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/assets/protocol/401.jpg +0 -0
  3. package/assets/protocol/401.mp4 +0 -0
  4. package/assets/protocol/402.jpg +0 -0
  5. package/assets/protocol/402.mp4 +0 -0
  6. package/assets/protocol/403.jpg +0 -0
  7. package/assets/protocol/403.mp4 +0 -0
  8. package/dist/access-Z6xQwKVF.d.cts +118 -0
  9. package/dist/access-Z6xQwKVF.d.ts +118 -0
  10. package/dist/bwalletx-connect.global.js +17 -17
  11. package/dist/bwalletx-connect.global.js.map +1 -1
  12. package/dist/{chunk-NNMUSWPG.js → chunk-CVLXFNAG.js} +123 -1
  13. package/dist/chunk-CVLXFNAG.js.map +1 -0
  14. package/dist/{chunk-FUQ46RJV.js → chunk-GF6GUDZL.js} +2 -2
  15. package/dist/chunk-MWD4BCO5.js +182 -0
  16. package/dist/chunk-MWD4BCO5.js.map +1 -0
  17. package/dist/{chunk-7NAKOJGZ.js → chunk-QRXFL66W.js} +220 -10
  18. package/dist/chunk-QRXFL66W.js.map +1 -0
  19. package/dist/element.cjs +209 -0
  20. package/dist/element.cjs.map +1 -1
  21. package/dist/element.js +2 -1
  22. package/dist/element.js.map +1 -1
  23. package/dist/index.cjs +292 -11
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +44 -1
  26. package/dist/index.d.ts +44 -1
  27. package/dist/index.js +30 -4
  28. package/dist/index.js.map +1 -1
  29. package/dist/next.cjs +377 -9
  30. package/dist/next.cjs.map +1 -1
  31. package/dist/next.d.cts +52 -2
  32. package/dist/next.d.ts +52 -2
  33. package/dist/next.js +92 -1
  34. package/dist/next.js.map +1 -1
  35. package/dist/react.cjs +209 -0
  36. package/dist/react.cjs.map +1 -1
  37. package/dist/react.js +3 -2
  38. package/dist/react.js.map +1 -1
  39. package/dist/server.cjs +406 -9
  40. package/dist/server.cjs.map +1 -1
  41. package/dist/server.d.cts +111 -1
  42. package/dist/server.d.ts +111 -1
  43. package/dist/server.js +47 -1
  44. package/package.json +10 -9
  45. package/dist/chunk-7NAKOJGZ.js.map +0 -1
  46. package/dist/chunk-NNMUSWPG.js.map +0 -1
  47. /package/dist/{chunk-FUQ46RJV.js.map → chunk-GF6GUDZL.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/nonce-stores.ts","../src/session.ts","../src/verify-payment.ts","../src/link.ts"],"sourcesContent":["import { ProtoWallet, PublicKey, type WalletProtocol } from '@bsv/sdk';\n\n/** Security level 2, a protocol name used for nothing else. Keep it constant. */\nexport const LOGIN_PROTOCOL: WalletProtocol = [2, 'bwallet sign in'];\nexport const CHALLENGE_LIFETIME_MS = 2 * 60 * 1000;\n\nexport interface PendingChallenge {\n identityKey: string;\n origin: string;\n message: string;\n expiresAt: number;\n}\n\n/**\n * Where challenges wait between the two routes. Use Redis or a database table in production.\n * `take` must read and delete in one step, so each nonce can be tried once.\n */\nexport interface NonceStore {\n put(nonce: string, c: PendingChallenge): Promise<void> | void;\n take(nonce: string): Promise<PendingChallenge | null> | PendingChallenge | null;\n}\n\nlet warned = false;\n\n/**\n * In-memory store. For development and tests only: it is lost on restart and not shared between\n * server processes or serverless instances. Expired entries are pruned on each put.\n */\nexport function memoryStore(opts: { quiet?: boolean } = {}): NonceStore {\n const prod = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production';\n if ((prod || !opts.quiet) && !warned) {\n warned = true;\n console.warn(\n prod\n ? '[@bwalletx/connect] WARNING: the in-memory nonce store is running in production. It is not shared between instances and breaks on serverless. Use redisStore or pgStore.'\n : '[@bwalletx/connect] Using the in-memory nonce store. It is for development only; pass a shared store (redisStore, pgStore) in production.',\n );\n }\n const m = new Map<string, PendingChallenge>();\n return {\n put(nonce, c) {\n const now = Date.now();\n for (const [k, v] of m) if (v.expiresAt < now) m.delete(k);\n m.set(nonce, c);\n },\n take(nonce) {\n const c = m.get(nonce) ?? null;\n m.delete(nonce);\n return c;\n },\n };\n}\n\nlet defaultStore: NonceStore | null = null;\nconst storeOrDefault = (s?: NonceStore) => s ?? (defaultStore ??= memoryStore());\n\nexport function isIdentityKey(x: unknown): x is string {\n if (typeof x !== 'string' || !/^0[23][0-9a-fA-F]{64}$/.test(x)) return false;\n try {\n PublicKey.fromString(x);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction normalOrigin(origin: string): string {\n const u = new URL(origin);\n if (u.origin === 'null' || u.origin !== origin.replace(/\\/$/, '')) {\n throw new Error(`origin must be scheme://host[:port], got ${origin}`);\n }\n return u.origin;\n}\n\n/** The text the user signs. Lines joined with \\n, no trailing newline. */\nexport function loginMessage(origin: string, nonce: string, expiresAt: number): string {\n return [\n `Sign in to ${new URL(origin).host} with bWallet`,\n '',\n 'This proves you hold this wallet. It does not spend anything.',\n '',\n `Origin: ${origin}`,\n `Nonce: ${nonce}`,\n `Expires: ${new Date(expiresAt).toISOString()}`,\n ].join('\\n');\n}\n\nfunction randomNonce(): string {\n const b = new Uint8Array(16);\n globalThis.crypto.getRandomValues(b);\n let s = '';\n for (const x of b) s += String.fromCharCode(x);\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nexport interface CreateChallengeOptions {\n /** YOUR site's origin from config, e.g. \"https://example.com\". Never take it from the request. */\n origin: string;\n /** The identity key the browser claims. Checked again at verify. */\n identityKey: unknown;\n /** Default: a process-wide in-memory store (dev only). */\n store?: NonceStore;\n}\n\nexport interface ChallengeResponse {\n nonce: string;\n message: string;\n protocolID: WalletProtocol;\n keyID: string;\n expiresAt: number;\n}\n\n/** Step 1. Throws if identityKey is not a valid compressed public key. Send the result to the browser. */\nexport async function createChallenge(opts: CreateChallengeOptions): Promise<ChallengeResponse> {\n if (!isIdentityKey(opts.identityKey)) throw new Error('identityKey must be a compressed public key (66 hex chars)');\n const origin = normalOrigin(opts.origin);\n const nonce = randomNonce();\n const expiresAt = Date.now() + CHALLENGE_LIFETIME_MS;\n const message = loginMessage(origin, nonce, expiresAt);\n await storeOrDefault(opts.store).put(nonce, { identityKey: opts.identityKey.toLowerCase(), origin, message, expiresAt });\n return { nonce, message, protocolID: LOGIN_PROTOCOL, keyID: nonce, expiresAt };\n}\n\nexport interface VerifySignInOptions {\n /** YOUR site's origin from config. Must equal the origin the challenge was made for. */\n origin: string;\n /** From the request body. */\n identityKey: unknown;\n nonce: unknown;\n signature: unknown;\n store?: NonceStore;\n}\n\n/**\n * Step 2. Returns the proven identity key (lowercase hex), or null.\n * The nonce is deleted before anything is checked, so each challenge can be tried once.\n */\nexport async function verifySignIn(opts: VerifySignInOptions): Promise<string | null> {\n if (typeof opts.nonce !== 'string' || opts.nonce.length > 64) return null;\n const c = await storeOrDefault(opts.store).take(opts.nonce);\n if (!c || Date.now() > c.expiresAt) return null;\n let origin: string;\n try {\n origin = normalOrigin(opts.origin);\n } catch {\n return null;\n }\n if (c.origin !== origin) return null;\n if (!isIdentityKey(opts.identityKey)) return null;\n const identityKey = opts.identityKey.toLowerCase();\n if (c.identityKey !== identityKey) return null;\n\n const sig = opts.signature;\n if (!Array.isArray(sig) || sig.length < 8 || sig.length > 80 || !sig.every((b) => Number.isInteger(b) && b >= 0 && b <= 255)) {\n return null;\n }\n try {\n const { valid } = await new ProtoWallet('anyone').verifySignature({\n protocolID: LOGIN_PROTOCOL,\n keyID: opts.nonce,\n counterparty: identityKey,\n data: Array.from(new TextEncoder().encode(c.message)),\n signature: sig as number[],\n });\n return valid ? identityKey : null;\n } catch {\n return null; // @bsv/sdk throws on a bad signature instead of returning valid: false\n }\n}\n\n/** True when a request's Origin header is exactly your configured origin. Check it on both routes. */\nexport function originAllowed(requestOrigin: string | null | undefined, origin: string): boolean {\n try {\n return !!requestOrigin && requestOrigin === normalOrigin(origin);\n } catch {\n return false;\n }\n}\n\nexport { redisStore, pgStore, pgNonceTableSql } from './nonce-stores.js';\nexport type { RedisLike, RedisStoreOptions, PgQuery, PgStoreOptions } from './nonce-stores.js';\nexport { signSession, verifySession, serializeCookie, readCookie, MIN_SECRET_LENGTH } from './session.js';\nexport type { SessionPayload, SignSessionOptions, CookieOptions } from './session.js';\nexport { verifyPayment, PaymentVerificationError } from './verify-payment.js';\nexport type { VerifyPaymentOptions, VerifyPaymentResult, ExpectedOutput, TxStatus } from './verify-payment.js';\nexport { linkIdentity, signInOrLink, IdentityLinkConflictError } from './link.js';\nexport type { LinkCallbacks, LinkResult, SignInOrLinkOptions, SignInOrLinkResult } from './link.js';\n","/**\n * Shared NonceStore adapters. No database driver is imported: you pass your own client or query\n * function. Every adapter makes `take` read-and-delete in one step, so each nonce works once.\n */\nimport type { NonceStore, PendingChallenge } from './server.js';\n\nconst ttlSeconds = (c: PendingChallenge) => Math.max(1, Math.ceil((c.expiresAt - Date.now()) / 1000));\n\nfunction parse(v: unknown): PendingChallenge | null {\n if (v == null) return null;\n try {\n const c = (typeof v === 'string' ? JSON.parse(v) : v) as PendingChallenge;\n if (!c || typeof c.identityKey !== 'string' || typeof c.message !== 'string' || typeof c.expiresAt !== 'number') return null;\n return c.expiresAt < Date.now() ? null : c;\n } catch {\n return null;\n }\n}\n\n/**\n * The Redis commands used. Fits @upstash/redis and node-redis v4+ as they are\n * (`set(key, value, { ex | EX })`, `getdel` / `getDel`). For ioredis pass `flavor: 'ioredis'`.\n */\nexport interface RedisLike {\n set(key: string, value: string, ...args: unknown[]): Promise<unknown>;\n getdel?(key: string): Promise<unknown>;\n getDel?(key: string): Promise<unknown>;\n}\n\nexport interface RedisStoreOptions {\n client: RedisLike;\n /** Key prefix. Default 'bwalletx:nonce:'. */\n prefix?: string;\n /** 'ioredis' uses positional `set(key, value, 'EX', s)`. Default 'object' (Upstash, node-redis). */\n flavor?: 'object' | 'ioredis';\n}\n\n/** Redis / Upstash / Vercel KV. Needs Redis 6.2+ for GETDEL. */\nexport function redisStore(opts: RedisStoreOptions): NonceStore {\n const { client } = opts;\n const prefix = opts.prefix ?? 'bwalletx:nonce:';\n const getdel = client.getdel?.bind(client) ?? client.getDel?.bind(client);\n if (!getdel) throw new Error('redisStore: the client needs getdel (or getDel). Redis 6.2+.');\n return {\n async put(nonce, c) {\n const s = ttlSeconds(c);\n if (opts.flavor === 'ioredis') await client.set(prefix + nonce, JSON.stringify(c), 'EX', s);\n else await client.set(prefix + nonce, JSON.stringify(c), { ex: s, EX: s });\n },\n async take(nonce) {\n return parse(await getdel(prefix + nonce));\n },\n };\n}\n\n/** `query(sql, params)` resolving to `{ rows }` (node-postgres, @vercel/postgres, Neon) or a bare array (postgres.js `sql.unsafe`). */\nexport type PgQuery = (sql: string, params: unknown[]) => Promise<{ rows: unknown[] } | unknown[]>;\n\nexport interface PgStoreOptions {\n query: PgQuery;\n /** Table name (letters, digits, _ and one optional schema dot). Default 'bwalletx_nonces'. */\n table?: string;\n}\n\n/** Run this once (a migration) before using pgStore. */\nexport function pgNonceTableSql(table = 'bwalletx_nonces'): string {\n const t = safeTable(table);\n return `create table if not exists ${t} (\n nonce text primary key,\n challenge jsonb not null,\n expires_at timestamptz not null\n);\ncreate index if not exists ${t.replace('.', '_')}_expires_idx on ${t} (expires_at);`;\n}\n\nfunction safeTable(t: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)?$/.test(t)) throw new Error(`pgStore: bad table name ${t}`);\n return t;\n}\n\n/** Postgres (e.g. self-hosted Supabase). Single-use via DELETE ... RETURNING. */\nexport function pgStore(opts: PgStoreOptions): NonceStore {\n const t = safeTable(opts.table ?? 'bwalletx_nonces');\n const rows = (r: { rows: unknown[] } | unknown[]) => (Array.isArray(r) ? r : r.rows);\n return {\n async put(nonce, c) {\n await opts.query(`delete from ${t} where expires_at < now()`, []);\n await opts.query(`insert into ${t} (nonce, challenge, expires_at) values ($1, $2, to_timestamp($3 / 1000.0))`, [\n nonce,\n JSON.stringify(c),\n c.expiresAt,\n ]);\n },\n async take(nonce) {\n const r = rows(await opts.query(`delete from ${t} where nonce = $1 returning challenge`, [nonce]));\n const row = r[0] as { challenge?: unknown } | undefined;\n return parse(row?.challenge);\n },\n };\n}\n","/**\n * Signed session tokens: base64url(JSON payload) + \".\" + base64url(HMAC-SHA256).\n * WebCrypto only, so it runs in Node 20+, Vercel Edge and Cloudflare Workers.\n * There is no default secret: pass one (32+ chars) or every call throws.\n */\n\nexport interface SessionPayload {\n /** The proven identity key (lowercase hex). */\n sub: string;\n /** Issued at, seconds since the epoch. */\n iat: number;\n /** Expires at, seconds since the epoch. */\n exp: number;\n /** Your app's user id, if the identity key is linked to one. */\n uid?: string;\n handle?: string;\n paymail?: string;\n [claim: string]: unknown;\n}\n\nexport const MIN_SECRET_LENGTH = 32;\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\nfunction b64u(bytes: Uint8Array): string {\n let s = '';\n for (const b of bytes) s += String.fromCharCode(b);\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction unb64u(s: string): Uint8Array<ArrayBuffer> {\n if (!/^[A-Za-z0-9_-]*$/.test(s)) throw new Error('bad base64url');\n const b = atob(s.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (s.length % 4)) % 4));\n return Uint8Array.from(b, (c) => c.charCodeAt(0));\n}\n\nexport function assertSecret(secret: unknown): asserts secret is string {\n if (typeof secret !== 'string' || secret.length < MIN_SECRET_LENGTH) {\n throw new Error(\n `[@bwalletx/connect] A session secret of at least ${MIN_SECRET_LENGTH} characters is required. ` +\n 'Set it in an environment variable (e.g. BWALLETX_SESSION_SECRET). There is no default.',\n );\n }\n}\n\nconst keys = new Map<string, Promise<CryptoKey>>();\nfunction hmacKey(secret: string): Promise<CryptoKey> {\n let k = keys.get(secret);\n if (!k) {\n k = globalThis.crypto.subtle.importKey('raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']);\n keys.set(secret, k);\n }\n return k;\n}\n\nexport interface SignSessionOptions {\n secret: string;\n /** Lifetime in seconds. Default 7 days. */\n maxAge?: number;\n /** For tests. */\n now?: number;\n}\n\n/** Sign a session. `claims.sub` (identity key) is required. */\nexport async function signSession(\n claims: Omit<SessionPayload, 'iat' | 'exp'> & Partial<Pick<SessionPayload, 'iat' | 'exp'>>,\n opts: SignSessionOptions,\n): Promise<string> {\n assertSecret(opts.secret);\n if (typeof claims.sub !== 'string' || !claims.sub) throw new Error('session needs a sub (identity key)');\n const iat = Math.floor((opts.now ?? Date.now()) / 1000);\n const payload: SessionPayload = { ...claims, sub: claims.sub, iat, exp: iat + (opts.maxAge ?? 7 * 24 * 3600) };\n const body = b64u(enc.encode(JSON.stringify(payload)));\n const sig = new Uint8Array(await globalThis.crypto.subtle.sign('HMAC', await hmacKey(opts.secret), enc.encode(body)));\n return `${body}.${b64u(sig)}`;\n}\n\n/** The payload if the token is authentic and unexpired, else null. Never throws on bad input (only on a missing secret). */\nexport async function verifySession(token: unknown, opts: { secret: string; now?: number }): Promise<SessionPayload | null> {\n assertSecret(opts.secret);\n if (typeof token !== 'string' || token.length > 8192) return null;\n const dot = token.indexOf('.');\n if (dot < 1 || dot !== token.lastIndexOf('.')) return null;\n const body = token.slice(0, dot);\n try {\n const sig = unb64u(token.slice(dot + 1));\n // crypto.subtle.verify compares in constant time.\n const ok = await globalThis.crypto.subtle.verify('HMAC', await hmacKey(opts.secret), sig, enc.encode(body));\n if (!ok) return null;\n const p = JSON.parse(dec.decode(unb64u(body))) as SessionPayload;\n if (!p || typeof p.sub !== 'string' || typeof p.exp !== 'number') return null;\n if (Math.floor((opts.now ?? Date.now()) / 1000) >= p.exp) return null;\n return p;\n } catch {\n return null;\n }\n}\n\nexport interface CookieOptions {\n name: string;\n maxAge: number;\n secure?: boolean;\n sameSite?: 'Lax' | 'Strict' | 'None';\n path?: string;\n domain?: string;\n}\n\n/** A Set-Cookie header value. Always HttpOnly. */\nexport function serializeCookie(value: string, o: CookieOptions): string {\n const parts = [`${o.name}=${value}`, `Path=${o.path ?? '/'}`, `Max-Age=${o.maxAge}`, 'HttpOnly', `SameSite=${o.sameSite ?? 'Lax'}`];\n if (o.secure !== false) parts.push('Secure');\n if (o.domain) parts.push(`Domain=${o.domain}`);\n return parts.join('; ');\n}\n\n/** Read one cookie from a Cookie header. */\nexport function readCookie(header: string | null | undefined, name: string): string | null {\n if (!header) return null;\n for (const part of header.split(';')) {\n const i = part.indexOf('=');\n if (i > 0 && part.slice(0, i).trim() === name) return part.slice(i + 1).trim();\n }\n return null;\n}\n","/**\n * Server side: check that a transaction really pays what you expected before you deliver anything.\n * Read-only: it never broadcasts, signs or retries a spend.\n */\nimport { P2PKH, Transaction, Utils, type ChainTracker } from '@bsv/sdk';\n\nexport interface ExpectedOutput {\n /** Locking script hex. Give this or `address`. */\n script?: string;\n /** A P2PKH address. */\n address?: string;\n /** Minimum satoshis this output must carry. */\n satoshis: number;\n}\n\nexport type TxStatus =\n | { state: 'confirmed'; confirmations: number; source: 'woc' | 'arc' }\n | { state: 'mempool'; source: 'woc' | 'arc'; detail?: string }\n | { state: 'not-found' | 'rejected'; source: 'woc' | 'arc'; detail?: string }\n | { state: 'unchecked' };\n\nexport interface VerifyPaymentOptions {\n /** A txid (fetched from WhatsOnChain), raw tx / BEEF / Atomic BEEF as hex, or BEEF bytes. */\n tx: string | number[] | Uint8Array;\n /** Every one must be matched by a different output. */\n expected: ExpectedOutput[];\n network?: 'main' | 'test';\n /** Where to ask for status. Default 'woc'. 'none' skips the network (BEEF only). */\n status?: 'woc' | 'arc' | 'none';\n /** ARC base URL, e.g. https://arc.taal.com. Needed when status is 'arc'. */\n arcUrl?: string;\n /** Sent as Bearer. Read it from env; never hardcode. */\n arcApiKey?: string;\n /** Fail unless the network has seen the tx (mempool or confirmed). Default false. */\n requireSeen?: boolean;\n /** Minimum confirmations. Default 0. */\n minConfirmations?: number;\n /** Also run SPV (merkle proofs + scripts) on a BEEF with this chain tracker. */\n spv?: ChainTracker;\n /** Injected fetch (tests, custom agents). Default globalThis.fetch. */\n fetch?: typeof fetch;\n}\n\nexport interface VerifyPaymentResult {\n ok: true;\n txid: string;\n /** For each expected output, the index of the output that paid it. */\n matched: { vout: number; satoshis: number; script: string }[];\n status: TxStatus;\n}\n\nexport type PaymentErrorCode = 'BAD_TX' | 'TX_NOT_FOUND' | 'OUTPUT_MISSING' | 'NOT_SEEN' | 'REJECTED' | 'SPV_FAILED' | 'NETWORK' | 'BAD_EXPECTED';\n\nexport class PaymentVerificationError extends Error {\n constructor(\n readonly code: PaymentErrorCode,\n message: string,\n readonly txid?: string,\n ) {\n super(message);\n this.name = 'PaymentVerificationError';\n }\n}\n\nconst isHex = (s: string) => /^([0-9a-fA-F]{2})+$/.test(s);\nconst woc = (n: 'main' | 'test') => `https://api.whatsonchain.com/v1/bsv/${n}`;\n\nfunction parseTx(input: string | number[] | Uint8Array): Transaction {\n const bytes = typeof input === 'string' ? Utils.toArray(input, 'hex') : Array.from(input);\n const tries: (() => Transaction)[] = [\n () => Transaction.fromAtomicBEEF(bytes),\n () => Transaction.fromBEEF(bytes),\n () => Transaction.fromBinary(bytes),\n ];\n for (const t of tries) {\n try {\n return t();\n } catch {\n /* next format */\n }\n }\n throw new PaymentVerificationError('BAD_TX', 'Could not parse the transaction (expected a txid, raw tx, BEEF or Atomic BEEF).');\n}\n\nfunction expectedScript(e: ExpectedOutput): string {\n if (!Number.isSafeInteger(e.satoshis) || e.satoshis < 0) throw new PaymentVerificationError('BAD_EXPECTED', 'expected.satoshis must be a non-negative integer');\n if (e.script) {\n if (!isHex(e.script)) throw new PaymentVerificationError('BAD_EXPECTED', 'expected.script must be hex');\n return e.script.toLowerCase();\n }\n if (e.address) {\n try {\n return new P2PKH().lock(e.address).toHex();\n } catch {\n throw new PaymentVerificationError('BAD_EXPECTED', `Not a valid address: ${e.address}`);\n }\n }\n throw new PaymentVerificationError('BAD_EXPECTED', 'Each expected output needs a script or an address');\n}\n\nasync function getJson(f: typeof fetch, url: string, headers: Record<string, string> = {}): Promise<{ status: number; body: unknown }> {\n let r: Response;\n try {\n r = await f(url, { headers });\n } catch (e) {\n throw new PaymentVerificationError('NETWORK', `Could not reach ${new URL(url).host}: ${(e as Error).message}`);\n }\n const text = await r.text();\n let body: unknown = text;\n try {\n body = JSON.parse(text);\n } catch {\n /* plain text */\n }\n return { status: r.status, body };\n}\n\nasync function checkStatus(o: VerifyPaymentOptions, f: typeof fetch, txid: string): Promise<TxStatus> {\n const mode = o.status ?? 'woc';\n if (mode === 'none') return { state: 'unchecked' };\n if (mode === 'arc') {\n if (!o.arcUrl) throw new PaymentVerificationError('NETWORK', \"status 'arc' needs arcUrl\");\n const { status, body } = await getJson(f, `${o.arcUrl.replace(/\\/$/, '')}/v1/tx/${txid}`, o.arcApiKey ? { Authorization: `Bearer ${o.arcApiKey}` } : {});\n if (status === 404) return { state: 'not-found', source: 'arc' };\n if (status >= 400) throw new PaymentVerificationError('NETWORK', `ARC answered ${status}`, txid);\n const b = body as { txStatus?: string; blockHeight?: number; extraInfo?: string };\n const s = String(b.txStatus ?? '').toUpperCase();\n if (s === 'MINED' || s === 'IMMUTABLE') return { state: 'confirmed', confirmations: 1, source: 'arc' };\n if (s === 'REJECTED' || s === 'DOUBLE_SPEND_ATTEMPTED') return { state: 'rejected', source: 'arc', detail: b.extraInfo ?? s };\n if (s === 'UNKNOWN' || s === '') return { state: 'not-found', source: 'arc', detail: s };\n return { state: 'mempool', source: 'arc', detail: s };\n }\n const { status, body } = await getJson(f, `${woc(o.network ?? 'main')}/tx/hash/${txid}`);\n if (status === 404) return { state: 'not-found', source: 'woc' };\n if (status >= 400) throw new PaymentVerificationError('NETWORK', `WhatsOnChain answered ${status}`, txid);\n const c = Number((body as { confirmations?: number }).confirmations ?? 0);\n return c > 0 ? { state: 'confirmed', confirmations: c, source: 'woc' } : { state: 'mempool', source: 'woc' };\n}\n\n/**\n * Throws PaymentVerificationError (with a `code`) unless every expected output is paid by a\n * distinct output of the transaction with at least the expected satoshis.\n */\nexport async function verifyPayment(o: VerifyPaymentOptions): Promise<VerifyPaymentResult> {\n const f = o.fetch ?? globalThis.fetch;\n if (!o.expected?.length) throw new PaymentVerificationError('BAD_EXPECTED', 'Pass at least one expected output');\n const want = o.expected.map((e) => ({ script: expectedScript(e), satoshis: e.satoshis }));\n\n let tx: Transaction;\n if (typeof o.tx === 'string' && /^[0-9a-fA-F]{64}$/.test(o.tx)) {\n const txid = o.tx.toLowerCase();\n let r: Response;\n try {\n r = await f(`${woc(o.network ?? 'main')}/tx/${txid}/hex`);\n } catch (e) {\n throw new PaymentVerificationError('NETWORK', `Could not reach WhatsOnChain: ${(e as Error).message}`, txid);\n }\n if (r.status === 404) throw new PaymentVerificationError('TX_NOT_FOUND', `Transaction ${txid} was not found`, txid);\n if (!r.ok) throw new PaymentVerificationError('NETWORK', `WhatsOnChain answered ${r.status}`, txid);\n tx = parseTx((await r.text()).trim());\n if (tx.id('hex') !== txid) throw new PaymentVerificationError('BAD_TX', 'The fetched transaction does not match the txid', txid);\n } else {\n if (typeof o.tx === 'string' && !isHex(o.tx)) throw new PaymentVerificationError('BAD_TX', 'tx must be a txid or hex');\n tx = parseTx(o.tx);\n }\n const txid = tx.id('hex');\n\n if (o.spv) {\n let ok = false;\n try {\n ok = await tx.verify(o.spv);\n } catch (e) {\n throw new PaymentVerificationError('SPV_FAILED', `SPV check failed: ${(e as Error).message}`, txid);\n }\n if (!ok) throw new PaymentVerificationError('SPV_FAILED', 'SPV check failed', txid);\n }\n\n const used = new Set<number>();\n const matched: VerifyPaymentResult['matched'] = [];\n for (const w of want) {\n const vout = tx.outputs.findIndex(\n (out, i) => !used.has(i) && out.lockingScript.toHex() === w.script && (out.satoshis ?? 0) >= w.satoshis,\n );\n if (vout < 0) {\n throw new PaymentVerificationError('OUTPUT_MISSING', `No output pays ${w.satoshis} sats to script ${w.script.slice(0, 16)}…`, txid);\n }\n used.add(vout);\n matched.push({ vout, satoshis: tx.outputs[vout].satoshis ?? 0, script: w.script });\n }\n\n const status = await checkStatus(o, f, txid);\n if (status.state === 'rejected') throw new PaymentVerificationError('REJECTED', `The network rejected ${txid}: ${status.detail ?? ''}`.trim(), txid);\n if ((o.requireSeen || (o.minConfirmations ?? 0) > 0) && status.state !== 'mempool' && status.state !== 'confirmed') {\n throw new PaymentVerificationError('NOT_SEEN', `The network has not seen ${txid} yet`, txid);\n }\n const conf = status.state === 'confirmed' ? status.confirmations : 0;\n if ((o.minConfirmations ?? 0) > conf) {\n throw new PaymentVerificationError('NOT_SEEN', `${txid} has ${conf} confirmations, needs ${o.minConfirmations}`, txid);\n }\n return { ok: true, txid, matched, status };\n}\n","/**\n * Link a proven identity key to a user in YOUR auth system (Supabase, next-auth, your own table).\n * You supply the storage callbacks; this module only holds the rules.\n */\n\nexport interface LinkCallbacks {\n /** The user id this identity key is linked to, or null. */\n findUserByIdentityKey(identityKey: string): Promise<string | null> | string | null;\n /** Save the link. Make (identity_key) unique in your table so a race cannot double-link. */\n attach(userId: string, identityKey: string): Promise<void> | void;\n}\n\nexport type LinkResult = { status: 'linked' | 'already-linked'; userId: string; identityKey: string };\n\nexport class IdentityLinkConflictError extends Error {\n readonly code = 'IDENTITY_LINKED_TO_OTHER_USER';\n constructor(\n readonly identityKey: string,\n readonly otherUserId: string,\n ) {\n super('This bWalletX identity is already linked to a different account. Sign in with that account, or unlink it there first.');\n this.name = 'IdentityLinkConflictError';\n }\n}\n\nconst norm = (k: string) => {\n if (typeof k !== 'string' || !/^0[23][0-9a-fA-F]{64}$/.test(k)) throw new Error('identityKey must be a compressed public key (66 hex chars)');\n return k.toLowerCase();\n};\n\n/**\n * Link `identityKey` (already verified with verifySignIn) to the signed-in `userId`.\n * Throws IdentityLinkConflictError if it belongs to someone else. Idempotent for the same user.\n */\nexport async function linkIdentity(userId: string, identityKey: string, cb: LinkCallbacks): Promise<LinkResult> {\n if (!userId) throw new Error('linkIdentity needs the current user id');\n const key = norm(identityKey);\n const owner = await cb.findUserByIdentityKey(key);\n if (owner && owner !== userId) throw new IdentityLinkConflictError(key, owner);\n if (owner === userId) return { status: 'already-linked', userId, identityKey: key };\n await cb.attach(userId, key);\n return { status: 'linked', userId, identityKey: key };\n}\n\nexport interface SignInOrLinkOptions extends LinkCallbacks {\n /** The user already signed in some other way (e.g. HandCash, email), if any. */\n currentUserId?: string | null;\n /** Make a new user for an unknown identity key. Without it, unknown keys return status 'unknown'. */\n createUser?(identityKey: string): Promise<string> | string;\n}\n\nexport type SignInOrLinkResult =\n | { status: 'signed-in' | 'linked' | 'already-linked' | 'created'; userId: string; identityKey: string }\n | { status: 'unknown'; userId: null; identityKey: string };\n\n/**\n * The usual verify-route logic: signed in already → link; known key → that user; else create (or report unknown).\n * Throws IdentityLinkConflictError when a signed-in user tries to link a key owned by another user.\n */\nexport async function signInOrLink(identityKey: string, o: SignInOrLinkOptions): Promise<SignInOrLinkResult> {\n const key = norm(identityKey);\n if (o.currentUserId) return linkIdentity(o.currentUserId, key, o);\n const owner = await o.findUserByIdentityKey(key);\n if (owner) return { status: 'signed-in', userId: owner, identityKey: key };\n if (!o.createUser) return { status: 'unknown', userId: null, identityKey: key };\n const userId = await o.createUser(key);\n await o.attach(userId, key);\n return { status: 'created', userId, identityKey: key };\n}\n"],"mappings":"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","names":["import_sdk","status","body","txid"]}
1
+ {"version":3,"sources":["../src/server.ts","../src/nonce-stores.ts","../src/session.ts","../src/verify-payment.ts","../src/link.ts","../src/access.ts","../src/access-server.ts"],"sourcesContent":["import { ProtoWallet, PublicKey, type WalletProtocol } from '@bsv/sdk';\n\n/** Security level 2, a protocol name used for nothing else. Keep it constant. */\nexport const LOGIN_PROTOCOL: WalletProtocol = [2, 'bwallet sign in'];\nexport const CHALLENGE_LIFETIME_MS = 2 * 60 * 1000;\n\nexport interface PendingChallenge {\n identityKey: string;\n origin: string;\n message: string;\n expiresAt: number;\n}\n\n/**\n * Where challenges wait between the two routes. Use Redis or a database table in production.\n * `take` must read and delete in one step, so each nonce can be tried once.\n */\nexport interface NonceStore {\n put(nonce: string, c: PendingChallenge): Promise<void> | void;\n take(nonce: string): Promise<PendingChallenge | null> | PendingChallenge | null;\n}\n\nlet warned = false;\n\n/**\n * In-memory store. For development and tests only: it is lost on restart and not shared between\n * server processes or serverless instances. Expired entries are pruned on each put.\n */\nexport function memoryStore(opts: { quiet?: boolean } = {}): NonceStore {\n const prod = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production';\n if ((prod || !opts.quiet) && !warned) {\n warned = true;\n console.warn(\n prod\n ? '[@bwalletx/connect] WARNING: the in-memory nonce store is running in production. It is not shared between instances and breaks on serverless. Use redisStore or pgStore.'\n : '[@bwalletx/connect] Using the in-memory nonce store. It is for development only; pass a shared store (redisStore, pgStore) in production.',\n );\n }\n const m = new Map<string, PendingChallenge>();\n return {\n put(nonce, c) {\n const now = Date.now();\n for (const [k, v] of m) if (v.expiresAt < now) m.delete(k);\n m.set(nonce, c);\n },\n take(nonce) {\n const c = m.get(nonce) ?? null;\n m.delete(nonce);\n return c;\n },\n };\n}\n\nlet defaultStore: NonceStore | null = null;\nconst storeOrDefault = (s?: NonceStore) => s ?? (defaultStore ??= memoryStore());\n\nexport function isIdentityKey(x: unknown): x is string {\n if (typeof x !== 'string' || !/^0[23][0-9a-fA-F]{64}$/.test(x)) return false;\n try {\n PublicKey.fromString(x);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction normalOrigin(origin: string): string {\n const u = new URL(origin);\n if (u.origin === 'null' || u.origin !== origin.replace(/\\/$/, '')) {\n throw new Error(`origin must be scheme://host[:port], got ${origin}`);\n }\n return u.origin;\n}\n\n/** The text the user signs. Lines joined with \\n, no trailing newline. */\nexport function loginMessage(origin: string, nonce: string, expiresAt: number): string {\n return [\n `Sign in to ${new URL(origin).host} with bWallet`,\n '',\n 'This proves you hold this wallet. It does not spend anything.',\n '',\n `Origin: ${origin}`,\n `Nonce: ${nonce}`,\n `Expires: ${new Date(expiresAt).toISOString()}`,\n ].join('\\n');\n}\n\nfunction randomNonce(): string {\n const b = new Uint8Array(16);\n globalThis.crypto.getRandomValues(b);\n let s = '';\n for (const x of b) s += String.fromCharCode(x);\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nexport interface CreateChallengeOptions {\n /** YOUR site's origin from config, e.g. \"https://example.com\". Never take it from the request. */\n origin: string;\n /** The identity key the browser claims. Checked again at verify. */\n identityKey: unknown;\n /** Default: a process-wide in-memory store (dev only). */\n store?: NonceStore;\n}\n\nexport interface ChallengeResponse {\n nonce: string;\n message: string;\n protocolID: WalletProtocol;\n keyID: string;\n expiresAt: number;\n}\n\n/** Step 1. Throws if identityKey is not a valid compressed public key. Send the result to the browser. */\nexport async function createChallenge(opts: CreateChallengeOptions): Promise<ChallengeResponse> {\n if (!isIdentityKey(opts.identityKey)) throw new Error('identityKey must be a compressed public key (66 hex chars)');\n const origin = normalOrigin(opts.origin);\n const nonce = randomNonce();\n const expiresAt = Date.now() + CHALLENGE_LIFETIME_MS;\n const message = loginMessage(origin, nonce, expiresAt);\n await storeOrDefault(opts.store).put(nonce, { identityKey: opts.identityKey.toLowerCase(), origin, message, expiresAt });\n return { nonce, message, protocolID: LOGIN_PROTOCOL, keyID: nonce, expiresAt };\n}\n\nexport interface VerifySignInOptions {\n /** YOUR site's origin from config. Must equal the origin the challenge was made for. */\n origin: string;\n /** From the request body. */\n identityKey: unknown;\n nonce: unknown;\n signature: unknown;\n store?: NonceStore;\n}\n\n/**\n * Step 2. Returns the proven identity key (lowercase hex), or null.\n * The nonce is deleted before anything is checked, so each challenge can be tried once.\n */\nexport async function verifySignIn(opts: VerifySignInOptions): Promise<string | null> {\n if (typeof opts.nonce !== 'string' || opts.nonce.length > 64) return null;\n const c = await storeOrDefault(opts.store).take(opts.nonce);\n if (!c || Date.now() > c.expiresAt) return null;\n let origin: string;\n try {\n origin = normalOrigin(opts.origin);\n } catch {\n return null;\n }\n if (c.origin !== origin) return null;\n if (!isIdentityKey(opts.identityKey)) return null;\n const identityKey = opts.identityKey.toLowerCase();\n if (c.identityKey !== identityKey) return null;\n\n const sig = opts.signature;\n if (!Array.isArray(sig) || sig.length < 8 || sig.length > 80 || !sig.every((b) => Number.isInteger(b) && b >= 0 && b <= 255)) {\n return null;\n }\n try {\n const { valid } = await new ProtoWallet('anyone').verifySignature({\n protocolID: LOGIN_PROTOCOL,\n keyID: opts.nonce,\n counterparty: identityKey,\n data: Array.from(new TextEncoder().encode(c.message)),\n signature: sig as number[],\n });\n return valid ? identityKey : null;\n } catch {\n return null; // @bsv/sdk throws on a bad signature instead of returning valid: false\n }\n}\n\n/** True when a request's Origin header is exactly your configured origin. Check it on both routes. */\nexport function originAllowed(requestOrigin: string | null | undefined, origin: string): boolean {\n try {\n return !!requestOrigin && requestOrigin === normalOrigin(origin);\n } catch {\n return false;\n }\n}\n\nexport { redisStore, pgStore, pgNonceTableSql } from './nonce-stores.js';\nexport type { RedisLike, RedisStoreOptions, PgQuery, PgStoreOptions } from './nonce-stores.js';\nexport { signSession, verifySession, serializeCookie, readCookie, MIN_SECRET_LENGTH } from './session.js';\nexport type { SessionPayload, SignSessionOptions, CookieOptions } from './session.js';\nexport { verifyPayment, PaymentVerificationError } from './verify-payment.js';\nexport type { VerifyPaymentOptions, VerifyPaymentResult, ExpectedOutput, TxStatus } from './verify-payment.js';\nexport { linkIdentity, signInOrLink, IdentityLinkConflictError } from './link.js';\nexport type { LinkCallbacks, LinkResult, SignInOrLinkOptions, SignInOrLinkResult } from './link.js';\nexport * from './access.js';\nexport { createAccessChallenge, verifyAccess, verifyHoldingProof, checkTokenHolding, oneSatResolver, bitSignResolver } from './access-server.js';\nexport type { AccessResolver, IdentityStatus, OneSatResolverOptions, BitSignResolverOptions, CreateAccessChallengeOptions, AccessChallengeResponse, VerifyAccessOptions, AccessGrant } from './access-server.js';\n","/**\n * Shared NonceStore adapters. No database driver is imported: you pass your own client or query\n * function. Every adapter makes `take` read-and-delete in one step, so each nonce works once.\n */\nimport type { NonceStore, PendingChallenge } from './server.js';\n\nconst ttlSeconds = (c: PendingChallenge) => Math.max(1, Math.ceil((c.expiresAt - Date.now()) / 1000));\n\nfunction parse(v: unknown): PendingChallenge | null {\n if (v == null) return null;\n try {\n const c = (typeof v === 'string' ? JSON.parse(v) : v) as PendingChallenge;\n if (!c || typeof c.identityKey !== 'string' || typeof c.message !== 'string' || typeof c.expiresAt !== 'number') return null;\n return c.expiresAt < Date.now() ? null : c;\n } catch {\n return null;\n }\n}\n\n/**\n * The Redis commands used. Fits @upstash/redis and node-redis v4+ as they are\n * (`set(key, value, { ex | EX })`, `getdel` / `getDel`). For ioredis pass `flavor: 'ioredis'`.\n */\nexport interface RedisLike {\n set(key: string, value: string, ...args: unknown[]): Promise<unknown>;\n getdel?(key: string): Promise<unknown>;\n getDel?(key: string): Promise<unknown>;\n}\n\nexport interface RedisStoreOptions {\n client: RedisLike;\n /** Key prefix. Default 'bwalletx:nonce:'. */\n prefix?: string;\n /** 'ioredis' uses positional `set(key, value, 'EX', s)`. Default 'object' (Upstash, node-redis). */\n flavor?: 'object' | 'ioredis';\n}\n\n/** Redis / Upstash / Vercel KV. Needs Redis 6.2+ for GETDEL. */\nexport function redisStore(opts: RedisStoreOptions): NonceStore {\n const { client } = opts;\n const prefix = opts.prefix ?? 'bwalletx:nonce:';\n const getdel = client.getdel?.bind(client) ?? client.getDel?.bind(client);\n if (!getdel) throw new Error('redisStore: the client needs getdel (or getDel). Redis 6.2+.');\n return {\n async put(nonce, c) {\n const s = ttlSeconds(c);\n if (opts.flavor === 'ioredis') await client.set(prefix + nonce, JSON.stringify(c), 'EX', s);\n else await client.set(prefix + nonce, JSON.stringify(c), { ex: s, EX: s });\n },\n async take(nonce) {\n return parse(await getdel(prefix + nonce));\n },\n };\n}\n\n/** `query(sql, params)` resolving to `{ rows }` (node-postgres, @vercel/postgres, Neon) or a bare array (postgres.js `sql.unsafe`). */\nexport type PgQuery = (sql: string, params: unknown[]) => Promise<{ rows: unknown[] } | unknown[]>;\n\nexport interface PgStoreOptions {\n query: PgQuery;\n /** Table name (letters, digits, _ and one optional schema dot). Default 'bwalletx_nonces'. */\n table?: string;\n}\n\n/** Run this once (a migration) before using pgStore. */\nexport function pgNonceTableSql(table = 'bwalletx_nonces'): string {\n const t = safeTable(table);\n return `create table if not exists ${t} (\n nonce text primary key,\n challenge jsonb not null,\n expires_at timestamptz not null\n);\ncreate index if not exists ${t.replace('.', '_')}_expires_idx on ${t} (expires_at);`;\n}\n\nfunction safeTable(t: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)?$/.test(t)) throw new Error(`pgStore: bad table name ${t}`);\n return t;\n}\n\n/** Postgres (e.g. self-hosted Supabase). Single-use via DELETE ... RETURNING. */\nexport function pgStore(opts: PgStoreOptions): NonceStore {\n const t = safeTable(opts.table ?? 'bwalletx_nonces');\n const rows = (r: { rows: unknown[] } | unknown[]) => (Array.isArray(r) ? r : r.rows);\n return {\n async put(nonce, c) {\n await opts.query(`delete from ${t} where expires_at < now()`, []);\n await opts.query(`insert into ${t} (nonce, challenge, expires_at) values ($1, $2, to_timestamp($3 / 1000.0))`, [\n nonce,\n JSON.stringify(c),\n c.expiresAt,\n ]);\n },\n async take(nonce) {\n const r = rows(await opts.query(`delete from ${t} where nonce = $1 returning challenge`, [nonce]));\n const row = r[0] as { challenge?: unknown } | undefined;\n return parse(row?.challenge);\n },\n };\n}\n","/**\n * Signed session tokens: base64url(JSON payload) + \".\" + base64url(HMAC-SHA256).\n * WebCrypto only, so it runs in Node 20+, Vercel Edge and Cloudflare Workers.\n * There is no default secret: pass one (32+ chars) or every call throws.\n */\n\nexport interface SessionPayload {\n /** The proven identity key (lowercase hex). */\n sub: string;\n /** Issued at, seconds since the epoch. */\n iat: number;\n /** Expires at, seconds since the epoch. */\n exp: number;\n /** Your app's user id, if the identity key is linked to one. */\n uid?: string;\n handle?: string;\n paymail?: string;\n [claim: string]: unknown;\n}\n\nexport const MIN_SECRET_LENGTH = 32;\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\nfunction b64u(bytes: Uint8Array): string {\n let s = '';\n for (const b of bytes) s += String.fromCharCode(b);\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction unb64u(s: string): Uint8Array<ArrayBuffer> {\n if (!/^[A-Za-z0-9_-]*$/.test(s)) throw new Error('bad base64url');\n const b = atob(s.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (s.length % 4)) % 4));\n return Uint8Array.from(b, (c) => c.charCodeAt(0));\n}\n\nexport function assertSecret(secret: unknown): asserts secret is string {\n if (typeof secret !== 'string' || secret.length < MIN_SECRET_LENGTH) {\n throw new Error(\n `[@bwalletx/connect] A session secret of at least ${MIN_SECRET_LENGTH} characters is required. ` +\n 'Set it in an environment variable (e.g. BWALLETX_SESSION_SECRET). There is no default.',\n );\n }\n}\n\nconst keys = new Map<string, Promise<CryptoKey>>();\nfunction hmacKey(secret: string): Promise<CryptoKey> {\n let k = keys.get(secret);\n if (!k) {\n k = globalThis.crypto.subtle.importKey('raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']);\n keys.set(secret, k);\n }\n return k;\n}\n\nexport interface SignSessionOptions {\n secret: string;\n /** Lifetime in seconds. Default 7 days. */\n maxAge?: number;\n /** For tests. */\n now?: number;\n}\n\n/** Sign a session. `claims.sub` (identity key) is required. */\nexport async function signSession(\n claims: Omit<SessionPayload, 'iat' | 'exp'> & Partial<Pick<SessionPayload, 'iat' | 'exp'>>,\n opts: SignSessionOptions,\n): Promise<string> {\n assertSecret(opts.secret);\n if (typeof claims.sub !== 'string' || !claims.sub) throw new Error('session needs a sub (identity key)');\n const iat = Math.floor((opts.now ?? Date.now()) / 1000);\n const payload: SessionPayload = { ...claims, sub: claims.sub, iat, exp: iat + (opts.maxAge ?? 7 * 24 * 3600) };\n const body = b64u(enc.encode(JSON.stringify(payload)));\n const sig = new Uint8Array(await globalThis.crypto.subtle.sign('HMAC', await hmacKey(opts.secret), enc.encode(body)));\n return `${body}.${b64u(sig)}`;\n}\n\n/** The payload if the token is authentic and unexpired, else null. Never throws on bad input (only on a missing secret). */\nexport async function verifySession(token: unknown, opts: { secret: string; now?: number }): Promise<SessionPayload | null> {\n assertSecret(opts.secret);\n if (typeof token !== 'string' || token.length > 8192) return null;\n const dot = token.indexOf('.');\n if (dot < 1 || dot !== token.lastIndexOf('.')) return null;\n const body = token.slice(0, dot);\n try {\n const sig = unb64u(token.slice(dot + 1));\n // crypto.subtle.verify compares in constant time.\n const ok = await globalThis.crypto.subtle.verify('HMAC', await hmacKey(opts.secret), sig, enc.encode(body));\n if (!ok) return null;\n const p = JSON.parse(dec.decode(unb64u(body))) as SessionPayload;\n if (!p || typeof p.sub !== 'string' || typeof p.exp !== 'number') return null;\n if (Math.floor((opts.now ?? Date.now()) / 1000) >= p.exp) return null;\n return p;\n } catch {\n return null;\n }\n}\n\nexport interface CookieOptions {\n name: string;\n maxAge: number;\n secure?: boolean;\n sameSite?: 'Lax' | 'Strict' | 'None';\n path?: string;\n domain?: string;\n}\n\n/** A Set-Cookie header value. Always HttpOnly. */\nexport function serializeCookie(value: string, o: CookieOptions): string {\n const parts = [`${o.name}=${value}`, `Path=${o.path ?? '/'}`, `Max-Age=${o.maxAge}`, 'HttpOnly', `SameSite=${o.sameSite ?? 'Lax'}`];\n if (o.secure !== false) parts.push('Secure');\n if (o.domain) parts.push(`Domain=${o.domain}`);\n return parts.join('; ');\n}\n\n/** Read one cookie from a Cookie header. */\nexport function readCookie(header: string | null | undefined, name: string): string | null {\n if (!header) return null;\n for (const part of header.split(';')) {\n const i = part.indexOf('=');\n if (i > 0 && part.slice(0, i).trim() === name) return part.slice(i + 1).trim();\n }\n return null;\n}\n","/**\n * Server side: check that a transaction really pays what you expected before you deliver anything.\n * Read-only: it never broadcasts, signs or retries a spend.\n */\nimport { P2PKH, Transaction, Utils, type ChainTracker } from '@bsv/sdk';\n\nexport interface ExpectedOutput {\n /** Locking script hex. Give this or `address`. */\n script?: string;\n /** A P2PKH address. */\n address?: string;\n /** Minimum satoshis this output must carry. */\n satoshis: number;\n}\n\nexport type TxStatus =\n | { state: 'confirmed'; confirmations: number; source: 'woc' | 'arc' }\n | { state: 'mempool'; source: 'woc' | 'arc'; detail?: string }\n | { state: 'not-found' | 'rejected'; source: 'woc' | 'arc'; detail?: string }\n | { state: 'unchecked' };\n\nexport interface VerifyPaymentOptions {\n /** A txid (fetched from WhatsOnChain), raw tx / BEEF / Atomic BEEF as hex, or BEEF bytes. */\n tx: string | number[] | Uint8Array;\n /** Every one must be matched by a different output. */\n expected: ExpectedOutput[];\n network?: 'main' | 'test';\n /** Where to ask for status. Default 'woc'. 'none' skips the network (BEEF only). */\n status?: 'woc' | 'arc' | 'none';\n /** ARC base URL, e.g. https://arc.taal.com. Needed when status is 'arc'. */\n arcUrl?: string;\n /** Sent as Bearer. Read it from env; never hardcode. */\n arcApiKey?: string;\n /** Fail unless the network has seen the tx (mempool or confirmed). Default false. */\n requireSeen?: boolean;\n /** Minimum confirmations. Default 0. */\n minConfirmations?: number;\n /** Also run SPV (merkle proofs + scripts) on a BEEF with this chain tracker. */\n spv?: ChainTracker;\n /** Injected fetch (tests, custom agents). Default globalThis.fetch. */\n fetch?: typeof fetch;\n}\n\nexport interface VerifyPaymentResult {\n ok: true;\n txid: string;\n /** For each expected output, the index of the output that paid it. */\n matched: { vout: number; satoshis: number; script: string }[];\n status: TxStatus;\n}\n\nexport type PaymentErrorCode = 'BAD_TX' | 'TX_NOT_FOUND' | 'OUTPUT_MISSING' | 'NOT_SEEN' | 'REJECTED' | 'SPV_FAILED' | 'NETWORK' | 'BAD_EXPECTED';\n\nexport class PaymentVerificationError extends Error {\n constructor(\n readonly code: PaymentErrorCode,\n message: string,\n readonly txid?: string,\n ) {\n super(message);\n this.name = 'PaymentVerificationError';\n }\n}\n\nconst isHex = (s: string) => /^([0-9a-fA-F]{2})+$/.test(s);\nconst woc = (n: 'main' | 'test') => `https://api.whatsonchain.com/v1/bsv/${n}`;\n\nfunction parseTx(input: string | number[] | Uint8Array): Transaction {\n const bytes = typeof input === 'string' ? Utils.toArray(input, 'hex') : Array.from(input);\n const tries: (() => Transaction)[] = [\n () => Transaction.fromAtomicBEEF(bytes),\n () => Transaction.fromBEEF(bytes),\n () => Transaction.fromBinary(bytes),\n ];\n for (const t of tries) {\n try {\n return t();\n } catch {\n /* next format */\n }\n }\n throw new PaymentVerificationError('BAD_TX', 'Could not parse the transaction (expected a txid, raw tx, BEEF or Atomic BEEF).');\n}\n\nfunction expectedScript(e: ExpectedOutput): string {\n if (!Number.isSafeInteger(e.satoshis) || e.satoshis < 0) throw new PaymentVerificationError('BAD_EXPECTED', 'expected.satoshis must be a non-negative integer');\n if (e.script) {\n if (!isHex(e.script)) throw new PaymentVerificationError('BAD_EXPECTED', 'expected.script must be hex');\n return e.script.toLowerCase();\n }\n if (e.address) {\n try {\n return new P2PKH().lock(e.address).toHex();\n } catch {\n throw new PaymentVerificationError('BAD_EXPECTED', `Not a valid address: ${e.address}`);\n }\n }\n throw new PaymentVerificationError('BAD_EXPECTED', 'Each expected output needs a script or an address');\n}\n\nasync function getJson(f: typeof fetch, url: string, headers: Record<string, string> = {}): Promise<{ status: number; body: unknown }> {\n let r: Response;\n try {\n r = await f(url, { headers });\n } catch (e) {\n throw new PaymentVerificationError('NETWORK', `Could not reach ${new URL(url).host}: ${(e as Error).message}`);\n }\n const text = await r.text();\n let body: unknown = text;\n try {\n body = JSON.parse(text);\n } catch {\n /* plain text */\n }\n return { status: r.status, body };\n}\n\nasync function checkStatus(o: VerifyPaymentOptions, f: typeof fetch, txid: string): Promise<TxStatus> {\n const mode = o.status ?? 'woc';\n if (mode === 'none') return { state: 'unchecked' };\n if (mode === 'arc') {\n if (!o.arcUrl) throw new PaymentVerificationError('NETWORK', \"status 'arc' needs arcUrl\");\n const { status, body } = await getJson(f, `${o.arcUrl.replace(/\\/$/, '')}/v1/tx/${txid}`, o.arcApiKey ? { Authorization: `Bearer ${o.arcApiKey}` } : {});\n if (status === 404) return { state: 'not-found', source: 'arc' };\n if (status >= 400) throw new PaymentVerificationError('NETWORK', `ARC answered ${status}`, txid);\n const b = body as { txStatus?: string; blockHeight?: number; extraInfo?: string };\n const s = String(b.txStatus ?? '').toUpperCase();\n if (s === 'MINED' || s === 'IMMUTABLE') return { state: 'confirmed', confirmations: 1, source: 'arc' };\n if (s === 'REJECTED' || s === 'DOUBLE_SPEND_ATTEMPTED') return { state: 'rejected', source: 'arc', detail: b.extraInfo ?? s };\n if (s === 'UNKNOWN' || s === '') return { state: 'not-found', source: 'arc', detail: s };\n return { state: 'mempool', source: 'arc', detail: s };\n }\n const { status, body } = await getJson(f, `${woc(o.network ?? 'main')}/tx/hash/${txid}`);\n if (status === 404) return { state: 'not-found', source: 'woc' };\n if (status >= 400) throw new PaymentVerificationError('NETWORK', `WhatsOnChain answered ${status}`, txid);\n const c = Number((body as { confirmations?: number }).confirmations ?? 0);\n return c > 0 ? { state: 'confirmed', confirmations: c, source: 'woc' } : { state: 'mempool', source: 'woc' };\n}\n\n/**\n * Throws PaymentVerificationError (with a `code`) unless every expected output is paid by a\n * distinct output of the transaction with at least the expected satoshis.\n */\nexport async function verifyPayment(o: VerifyPaymentOptions): Promise<VerifyPaymentResult> {\n const f = o.fetch ?? globalThis.fetch;\n if (!o.expected?.length) throw new PaymentVerificationError('BAD_EXPECTED', 'Pass at least one expected output');\n const want = o.expected.map((e) => ({ script: expectedScript(e), satoshis: e.satoshis }));\n\n let tx: Transaction;\n if (typeof o.tx === 'string' && /^[0-9a-fA-F]{64}$/.test(o.tx)) {\n const txid = o.tx.toLowerCase();\n let r: Response;\n try {\n r = await f(`${woc(o.network ?? 'main')}/tx/${txid}/hex`);\n } catch (e) {\n throw new PaymentVerificationError('NETWORK', `Could not reach WhatsOnChain: ${(e as Error).message}`, txid);\n }\n if (r.status === 404) throw new PaymentVerificationError('TX_NOT_FOUND', `Transaction ${txid} was not found`, txid);\n if (!r.ok) throw new PaymentVerificationError('NETWORK', `WhatsOnChain answered ${r.status}`, txid);\n tx = parseTx((await r.text()).trim());\n if (tx.id('hex') !== txid) throw new PaymentVerificationError('BAD_TX', 'The fetched transaction does not match the txid', txid);\n } else {\n if (typeof o.tx === 'string' && !isHex(o.tx)) throw new PaymentVerificationError('BAD_TX', 'tx must be a txid or hex');\n tx = parseTx(o.tx);\n }\n const txid = tx.id('hex');\n\n if (o.spv) {\n let ok = false;\n try {\n ok = await tx.verify(o.spv);\n } catch (e) {\n throw new PaymentVerificationError('SPV_FAILED', `SPV check failed: ${(e as Error).message}`, txid);\n }\n if (!ok) throw new PaymentVerificationError('SPV_FAILED', 'SPV check failed', txid);\n }\n\n const used = new Set<number>();\n const matched: VerifyPaymentResult['matched'] = [];\n for (const w of want) {\n const vout = tx.outputs.findIndex(\n (out, i) => !used.has(i) && out.lockingScript.toHex() === w.script && (out.satoshis ?? 0) >= w.satoshis,\n );\n if (vout < 0) {\n throw new PaymentVerificationError('OUTPUT_MISSING', `No output pays ${w.satoshis} sats to script ${w.script.slice(0, 16)}…`, txid);\n }\n used.add(vout);\n matched.push({ vout, satoshis: tx.outputs[vout].satoshis ?? 0, script: w.script });\n }\n\n const status = await checkStatus(o, f, txid);\n if (status.state === 'rejected') throw new PaymentVerificationError('REJECTED', `The network rejected ${txid}: ${status.detail ?? ''}`.trim(), txid);\n if ((o.requireSeen || (o.minConfirmations ?? 0) > 0) && status.state !== 'mempool' && status.state !== 'confirmed') {\n throw new PaymentVerificationError('NOT_SEEN', `The network has not seen ${txid} yet`, txid);\n }\n const conf = status.state === 'confirmed' ? status.confirmations : 0;\n if ((o.minConfirmations ?? 0) > conf) {\n throw new PaymentVerificationError('NOT_SEEN', `${txid} has ${conf} confirmations, needs ${o.minConfirmations}`, txid);\n }\n return { ok: true, txid, matched, status };\n}\n","/**\n * Link a proven identity key to a user in YOUR auth system (Supabase, next-auth, your own table).\n * You supply the storage callbacks; this module only holds the rules.\n */\n\nexport interface LinkCallbacks {\n /** The user id this identity key is linked to, or null. */\n findUserByIdentityKey(identityKey: string): Promise<string | null> | string | null;\n /** Save the link. Make (identity_key) unique in your table so a race cannot double-link. */\n attach(userId: string, identityKey: string): Promise<void> | void;\n}\n\nexport type LinkResult = { status: 'linked' | 'already-linked'; userId: string; identityKey: string };\n\nexport class IdentityLinkConflictError extends Error {\n readonly code = 'IDENTITY_LINKED_TO_OTHER_USER';\n constructor(\n readonly identityKey: string,\n readonly otherUserId: string,\n ) {\n super('This bWalletX identity is already linked to a different account. Sign in with that account, or unlink it there first.');\n this.name = 'IdentityLinkConflictError';\n }\n}\n\nconst norm = (k: string) => {\n if (typeof k !== 'string' || !/^0[23][0-9a-fA-F]{64}$/.test(k)) throw new Error('identityKey must be a compressed public key (66 hex chars)');\n return k.toLowerCase();\n};\n\n/**\n * Link `identityKey` (already verified with verifySignIn) to the signed-in `userId`.\n * Throws IdentityLinkConflictError if it belongs to someone else. Idempotent for the same user.\n */\nexport async function linkIdentity(userId: string, identityKey: string, cb: LinkCallbacks): Promise<LinkResult> {\n if (!userId) throw new Error('linkIdentity needs the current user id');\n const key = norm(identityKey);\n const owner = await cb.findUserByIdentityKey(key);\n if (owner && owner !== userId) throw new IdentityLinkConflictError(key, owner);\n if (owner === userId) return { status: 'already-linked', userId, identityKey: key };\n await cb.attach(userId, key);\n return { status: 'linked', userId, identityKey: key };\n}\n\nexport interface SignInOrLinkOptions extends LinkCallbacks {\n /** The user already signed in some other way (e.g. HandCash, email), if any. */\n currentUserId?: string | null;\n /** Make a new user for an unknown identity key. Without it, unknown keys return status 'unknown'. */\n createUser?(identityKey: string): Promise<string> | string;\n}\n\nexport type SignInOrLinkResult =\n | { status: 'signed-in' | 'linked' | 'already-linked' | 'created'; userId: string; identityKey: string }\n | { status: 'unknown'; userId: null; identityKey: string };\n\n/**\n * The usual verify-route logic: signed in already → link; known key → that user; else create (or report unknown).\n * Throws IdentityLinkConflictError when a signed-in user tries to link a key owned by another user.\n */\nexport async function signInOrLink(identityKey: string, o: SignInOrLinkOptions): Promise<SignInOrLinkResult> {\n const key = norm(identityKey);\n if (o.currentUserId) return linkIdentity(o.currentUserId, key, o);\n const owner = await o.findUserByIdentityKey(key);\n if (owner) return { status: 'signed-in', userId: owner, identityKey: key };\n if (!o.createUser) return { status: 'unknown', userId: null, identityKey: key };\n const userId = await o.createUser(key);\n await o.attach(userId, key);\n return { status: 'created', userId, identityKey: key };\n}\n","/**\n * $401 / $402 / $403 access protocol, version 1. Shared by sites (client + server) and wallets.\n * The full spec is docs/ACCESS-PROTOCOL.md. Pure: no I/O, @bsv/sdk types only.\n *\n * 401 identity \"prove who you are (at identity level N, or KYC'd)\"\n * 402 payment \"pay for this\" (the challenge format is reserved; payment itself uses pay())\n * 403 access \"prove you hold N of token X (or N basis points of its supply)\"\n */\nimport type { WalletProtocol } from '@bsv/sdk';\n\nexport const ACCESS_VERSION = 1 as const;\n/** Security level 2, a protocol name used for nothing else. Wallets recognise it and show the kind's video. */\nexport const ACCESS_PROTOCOL: WalletProtocol = [2, 'bwalletx access'];\n/** How long a challenge is valid. */\nexport const ACCESS_CHALLENGE_LIFETIME_S = 120;\n/** At most this many holding proofs are read from one response. */\nexport const MAX_HOLDING_PROOFS = 50;\n\nexport type AccessKind = '401' | '402' | '403';\n\nexport interface TokenRequirement {\n /** 'bsv21:<txid>_<vout>' */\n id: string;\n /** Minimum raw (unscaled, integer string) amount. */\n min?: string;\n /** Minimum in basis points of total supply (1 = 0.01%, 10000 = 100%). */\n minBps?: number;\n}\n\nexport interface AccessRequirement {\n token?: TokenRequirement;\n /** $401 identity level (bit-sign strands): 1 Basic, 2 Verified, 3 Strong, 4 Sovereign (Veriff). */\n identityLevel?: number;\n /** A live KYC credential is required. */\n kyc?: boolean;\n}\n\n/** What the wallet signs, as canonical JSON. Times are seconds since the epoch. */\nexport interface AccessChallenge {\n v: 1;\n kind: AccessKind;\n origin: string;\n nonce: string;\n iat: number;\n exp: number;\n /** The identity key the site expects. A different wallet account is `wrong_account`. */\n sub?: string;\n require: AccessRequirement;\n}\n\n/** A key that locks some of the required token, proving control by signing the same challenge bytes. */\nexport interface HoldingProof {\n /** Compressed public key hex of the key locking the outputs. */\n pubkey: string;\n /** DER signature by that key over SHA-256(challenge bytes), as BRC-100 createSignature returns. */\n signature: number[];\n /** Optional hint: outpoints ('txid_vout') this key holds. Servers must not trust it. */\n outpoints?: string[];\n}\n\n/** What the wallet (via requestAccess) posts back to the site. */\nexport interface AccessResponse {\n v: 1;\n /** The challenge string exactly as issued. */\n challenge: string;\n identityKey: string;\n /** DER signature: createSignature({ protocolID: ACCESS_PROTOCOL, keyID: nonce, counterparty: 'anyone', data: challenge bytes }). */\n signature: number[];\n holdings?: HoldingProof[];\n}\n\nexport type AccessErrorCode =\n | 'not_installed'\n | 'user_rejected'\n | 'wrong_account'\n | 'insufficient_tokens'\n | 'identity_level_too_low'\n | 'kyc_required'\n | 'indexer_unavailable'\n | 'expired'\n | 'replay'\n | 'invalid_signature'\n | 'bad_request';\n\n/** The HTTP status a site should answer each code with. */\nexport const ACCESS_ERROR_STATUS: Record<AccessErrorCode, number> = {\n not_installed: 424,\n user_rejected: 403,\n wrong_account: 409,\n insufficient_tokens: 403,\n identity_level_too_low: 403,\n kyc_required: 403,\n indexer_unavailable: 503,\n expired: 401,\n replay: 401,\n invalid_signature: 401,\n bad_request: 400,\n};\n\nexport interface AccessErrorBody {\n error: AccessErrorCode;\n message: string;\n /** insufficient_tokens: raw amounts; identity_level_too_low: levels. */\n have?: string | number;\n need?: string | number;\n}\n\nexport class AccessError extends Error {\n readonly code: AccessErrorCode;\n readonly have?: string | number;\n readonly need?: string | number;\n constructor(code: AccessErrorCode, message?: string, extra: { have?: string | number; need?: string | number } = {}) {\n super(message ?? code);\n this.name = 'AccessError';\n this.code = code;\n this.have = extra.have;\n this.need = extra.need;\n }\n toJSON(): AccessErrorBody {\n const b: AccessErrorBody = { error: this.code, message: this.message };\n if (this.have !== undefined) b.have = this.have;\n if (this.need !== undefined) b.need = this.need;\n return b;\n }\n}\n\n/** One video + poster + colour per kind, so wallets and sites show the same thing. Paths are inside the package. */\nexport const PROTOCOL_MEDIA: Record<AccessKind, { name: string; color: string; video: string; poster: string }> = {\n '401': { name: 'Identity', color: '#22c55e', video: 'assets/protocol/401.mp4', poster: 'assets/protocol/401.jpg' },\n '402': { name: 'Payment', color: '#3b82f6', video: 'assets/protocol/402.mp4', poster: 'assets/protocol/402.jpg' },\n '403': { name: 'Access', color: '#ef4444', video: 'assets/protocol/403.mp4', poster: 'assets/protocol/403.jpg' },\n};\n\n/** Media URLs on a CDN (unpkg) for a given package version. */\nexport function protocolMediaUrl(kind: AccessKind, which: 'video' | 'poster', version = 'latest'): string {\n return `https://unpkg.com/@bwalletx/connect@${version}/${PROTOCOL_MEDIA[kind][which]}`;\n}\n\n/**\n * Canonical JSON: object keys sorted by code unit, no whitespace, undefined members dropped.\n * Only strings, finite numbers, booleans, null, arrays and plain objects are allowed.\n */\nexport function canonicalJson(v: unknown): string {\n if (v === null) return 'null';\n if (typeof v === 'string' || typeof v === 'boolean') return JSON.stringify(v);\n if (typeof v === 'number') {\n if (!Number.isFinite(v)) throw new Error('canonicalJson: non-finite number');\n return JSON.stringify(v);\n }\n if (Array.isArray(v)) return `[${v.map((x) => canonicalJson(x)).join(',')}]`;\n if (typeof v === 'object') {\n const o = v as Record<string, unknown>;\n const keys = Object.keys(o).filter((k) => o[k] !== undefined).sort();\n return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(o[k])}`).join(',')}}`;\n }\n throw new Error(`canonicalJson: unsupported ${typeof v}`);\n}\n\n/** The bytes every signature in the protocol covers: UTF-8 of the challenge string. */\nexport function challengeBytes(challenge: string): number[] {\n return Array.from(new TextEncoder().encode(challenge));\n}\n\nconst TOKEN_ID = /^bsv21:[0-9a-f]{64}_\\d{1,6}$/;\nconst RAW = /^[1-9]\\d{0,38}$/;\n\n/** Throws AccessError('bad_request') if the requirement is malformed. Returns a normalised copy. */\nexport function normaliseRequirement(r: unknown): AccessRequirement {\n if (!r || typeof r !== 'object') throw new AccessError('bad_request', 'require must be an object');\n const req = r as Record<string, unknown>;\n const out: AccessRequirement = {};\n if (req.token !== undefined) {\n const t = req.token as Record<string, unknown>;\n if (!t || typeof t !== 'object' || typeof t.id !== 'string') throw new AccessError('bad_request', 'require.token.id is required');\n const id = t.id.trim().toLowerCase();\n if (!TOKEN_ID.test(id)) throw new AccessError('bad_request', \"require.token.id must be 'bsv21:<txid>_<vout>'\");\n const hasMin = t.min !== undefined;\n const hasBps = t.minBps !== undefined;\n if (hasMin === hasBps) throw new AccessError('bad_request', 'require.token needs exactly one of min or minBps');\n out.token = { id };\n if (hasMin) {\n if (typeof t.min !== 'string' || !RAW.test(t.min)) throw new AccessError('bad_request', 'require.token.min must be a positive integer string (raw units)');\n out.token.min = t.min;\n } else {\n if (!Number.isInteger(t.minBps) || (t.minBps as number) < 1 || (t.minBps as number) > 10000) {\n throw new AccessError('bad_request', 'require.token.minBps must be an integer 1..10000');\n }\n out.token.minBps = t.minBps as number;\n }\n }\n if (req.identityLevel !== undefined) {\n if (!Number.isInteger(req.identityLevel) || (req.identityLevel as number) < 1 || (req.identityLevel as number) > 10) {\n throw new AccessError('bad_request', 'require.identityLevel must be an integer 1..10');\n }\n out.identityLevel = req.identityLevel as number;\n }\n if (req.kyc !== undefined) {\n if (typeof req.kyc !== 'boolean') throw new AccessError('bad_request', 'require.kyc must be a boolean');\n if (req.kyc) out.kyc = true;\n }\n if (!out.token && !out.identityLevel && !out.kyc) throw new AccessError('bad_request', 'require is empty');\n return out;\n}\n\n/** The kind a requirement implies: any token → 403; otherwise 401. */\nexport function kindFor(r: AccessRequirement): AccessKind {\n return r.token ? '403' : '401';\n}\n\n/** Parse and validate a challenge string. Throws AccessError('bad_request'). Does not check expiry. */\nexport function parseAccessChallenge(s: unknown): AccessChallenge {\n if (typeof s !== 'string' || s.length > 4096) throw new AccessError('bad_request', 'challenge must be a string');\n let c: AccessChallenge;\n try {\n c = JSON.parse(s) as AccessChallenge;\n } catch {\n throw new AccessError('bad_request', 'challenge is not JSON');\n }\n if (!c || typeof c !== 'object' || c.v !== 1) throw new AccessError('bad_request', 'unsupported challenge version');\n if (c.kind !== '401' && c.kind !== '402' && c.kind !== '403') throw new AccessError('bad_request', 'bad kind');\n if (typeof c.origin !== 'string' || typeof c.nonce !== 'string' || !/^[A-Za-z0-9_-]{16,64}$/.test(c.nonce)) {\n throw new AccessError('bad_request', 'bad origin or nonce');\n }\n if (!Number.isInteger(c.iat) || !Number.isInteger(c.exp) || c.exp <= c.iat) throw new AccessError('bad_request', 'bad iat/exp');\n if (c.sub !== undefined && (typeof c.sub !== 'string' || !/^0[23][0-9a-f]{64}$/.test(c.sub))) throw new AccessError('bad_request', 'bad sub');\n c.require = normaliseRequirement(c.require);\n if (canonicalJson(c) !== s) throw new AccessError('bad_request', 'challenge is not canonical');\n return c;\n}\n\n/** Raw amount needed for a token requirement, given total supply (needed only for minBps). Rounds up. */\nexport function tokenNeedRaw(t: TokenRequirement, supplyRaw?: string): string {\n if (t.min) return t.min;\n if (supplyRaw === undefined || !/^\\d+$/.test(supplyRaw)) throw new AccessError('indexer_unavailable', 'token supply unknown');\n const n = BigInt(supplyRaw) * BigInt(t.minBps!);\n const need = (n + 9999n) / 10000n;\n return (need < 1n ? 1n : need).toString();\n}\n\n/** Compare unscaled integer strings. Fails closed on malformed input. */\nexport function atLeastRaw(have: string, need: string): boolean {\n try {\n return BigInt(have) >= BigInt(need);\n } catch {\n return false;\n }\n}\n\n/** A user-readable line for an error code. */\nexport function accessErrorMessage(code: AccessErrorCode, e: { have?: string | number; need?: string | number } = {}): string {\n switch (code) {\n case 'not_installed': return 'No bWalletX (or other BRC-100 wallet) was found.';\n case 'user_rejected': return 'The request was declined in the wallet.';\n case 'wrong_account': return 'The wallet is signed in to a different account than this site expects.';\n case 'insufficient_tokens': return `Not enough tokens: holding ${e.have ?? 0}, need ${e.need ?? '?'}.`;\n case 'identity_level_too_low': return `Identity level ${e.have ?? 0} is below the required level ${e.need ?? '?'}.`;\n case 'kyc_required': return 'A verified identity (KYC) is required.';\n case 'indexer_unavailable': return 'Holdings could not be checked right now. Try again shortly.';\n case 'expired': return 'The request expired. Try again.';\n case 'replay': return 'That request was already used. Try again.';\n case 'invalid_signature': return 'The wallet signature did not verify.';\n case 'bad_request': return 'The request was malformed.';\n }\n}\n","/**\n * Server side of the access protocol (docs/ACCESS-PROTOCOL.md): issue a challenge, verify the\n * wallet's response against an indexer, and re-check a granted session later.\n *\n * Fails closed: anything that cannot be confirmed is an AccessError, never a grant.\n */\nimport { ProtoWallet, PublicKey, Signature } from '@bsv/sdk';\nimport {\n ACCESS_CHALLENGE_LIFETIME_S,\n ACCESS_PROTOCOL,\n AccessError,\n MAX_HOLDING_PROOFS,\n atLeastRaw,\n canonicalJson,\n challengeBytes,\n kindFor,\n normaliseRequirement,\n parseAccessChallenge,\n tokenNeedRaw,\n type AccessChallenge,\n type AccessKind,\n type AccessRequirement,\n type AccessResponse,\n} from './access.js';\nimport { isIdentityKey, type NonceStore } from './server.js';\n\n// ── Resolvers ─────────────────────────────────────────────────────────────────────────────\n\nexport interface IdentityStatus {\n /** bit-sign identity level: 0 none, 1 Basic … 4 Sovereign (Veriff). */\n level: number;\n /** A live KYC credential for this identity key. */\n kyc: boolean;\n}\n\n/**\n * Where verifyAccess reads the chain and identity data. Every method THROWS when it cannot answer;\n * a throw becomes `indexer_unavailable`, never \"holds nothing\".\n */\nexport interface AccessResolver {\n /** Confirmed + unspent raw balance of `tokenId` ('bsv21:…') over these P2PKH addresses. */\n tokenBalance(tokenId: string, addresses: string[]): Promise<string>;\n /** Total supply, raw. Needed only for minBps requirements. */\n tokenSupply?(tokenId: string): Promise<string>;\n /** Identity level and KYC for a key. Needed only for identityLevel / kyc requirements. */\n identity?(identityKey: string, response: AccessResponse): Promise<IdentityStatus>;\n}\n\ntype FetchLike = typeof fetch;\n\nexport interface OneSatResolverOptions {\n /** Default 'https://api.1sat.app/1sat'. */\n base?: string;\n fetch?: FetchLike;\n timeoutMs?: number;\n}\n\nconst bare = (tokenId: string) => tokenId.replace(/^bsv21:/, '');\n\n/** BSV-21 balances and supply from the 1Sat indexer (the one bit-sign's token rooms use). */\nexport function oneSatResolver(o: OneSatResolverOptions = {}): AccessResolver {\n const base = (o.base ?? 'https://api.1sat.app/1sat').replace(/\\/$/, '');\n const f = o.fetch ?? globalThis.fetch;\n const t = o.timeoutMs ?? 12_000;\n const call = async (path: string, init?: RequestInit) => {\n const res = await f(`${base}${path}`, { ...init, signal: AbortSignal.timeout(t), cache: 'no-store' });\n if (!res.ok) throw new Error(`1sat ${res.status} for ${path}`);\n return res.json() as Promise<unknown>;\n };\n return {\n async tokenBalance(tokenId, addresses) {\n const sorted = [...new Set(addresses)].sort();\n let total = 0n;\n for (let i = 0; i < sorted.length; i += 100) {\n const j = (await call(`/bsv21/${bare(tokenId)}/p2pkh/balance`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(sorted.slice(i, i + 100)),\n })) as { balance?: number | string } | null;\n const b = j?.balance ?? 0;\n const s = typeof b === 'number' ? String(Math.trunc(b)) : String(b).split('.')[0] || '0';\n if (!/^\\d+$/.test(s)) throw new Error('1sat returned a bad balance');\n total += BigInt(s);\n }\n return total.toString();\n },\n async tokenSupply(tokenId) {\n const j = (await call(`/bsv21/${bare(tokenId)}`)) as Record<string, unknown> | null;\n const tok = ((j?.token as Record<string, unknown> | undefined) ?? j) || {};\n const v = tok.amt ?? tok.max ?? tok.supply;\n const s = v === undefined || v === null ? '' : String(v).split('.')[0];\n if (!/^\\d+$/.test(s) || s === '0') throw new Error('1sat did not report a supply');\n return s;\n },\n };\n}\n\nexport interface BitSignResolverOptions {\n /** Default 'https://bit-sign.online'. */\n base?: string;\n fetch?: FetchLike;\n timeoutMs?: number;\n /** Token reads. Default: oneSatResolver(). */\n tokens?: AccessResolver;\n}\n\n/**\n * Identity level / KYC from bit-sign's POST /api/access/verify, which checks the signed response\n * itself (the user's signature is their consent to disclose those facts to this origin).\n * Token reads go to `tokens` (default the 1Sat indexer).\n */\nexport function bitSignResolver(o: BitSignResolverOptions = {}): AccessResolver {\n const base = (o.base ?? 'https://bit-sign.online').replace(/\\/$/, '');\n const f = o.fetch ?? globalThis.fetch;\n const tokens = o.tokens ?? oneSatResolver({ fetch: f });\n return {\n tokenBalance: (id, a) => tokens.tokenBalance(id, a),\n tokenSupply: tokens.tokenSupply ? (id) => tokens.tokenSupply!(id) : undefined,\n async identity(identityKey, response) {\n const res = await f(`${base}/api/access/verify`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ response, only: 'identity' }),\n signal: AbortSignal.timeout(o.timeoutMs ?? 12_000),\n cache: 'no-store',\n });\n const j = (await res.json().catch(() => null)) as { identityKey?: string; identity?: { level?: unknown; kyc?: unknown } } | null;\n if (!j?.identity || j.identityKey !== identityKey) throw new Error(`bit-sign ${res.status}`);\n return { level: Number(j.identity.level) || 0, kyc: j.identity.kyc === true };\n },\n };\n}\n\n// ── Challenge ─────────────────────────────────────────────────────────────────────────────\n\nfunction normalOrigin(origin: string): string {\n const u = new URL(origin);\n if (u.origin === 'null' || u.origin !== origin.replace(/\\/$/, '')) throw new Error(`origin must be scheme://host[:port], got ${origin}`);\n return u.origin;\n}\n\nfunction randomNonce(): string {\n const b = new Uint8Array(18);\n globalThis.crypto.getRandomValues(b);\n let s = '';\n for (const x of b) s += String.fromCharCode(x);\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nexport interface CreateAccessChallengeOptions {\n /** YOUR origin from config. Never from the request. */\n origin: string;\n /** The requirement, from YOUR config. Never from the request. */\n require: AccessRequirement;\n /** A shared store (redisStore / pgStore). The same stores sign-in uses. */\n store: NonceStore;\n /** The identity key the browser says it is (optional). If given, a different signer is wrong_account. */\n identityKey?: unknown;\n /** Default: '403' when a token is required, else '401'. */\n kind?: AccessKind;\n lifetimeS?: number;\n now?: number;\n}\n\nexport interface AccessChallengeResponse {\n /** The canonical JSON the wallet signs. Pass it to the wallet unchanged. */\n challenge: string;\n nonce: string;\n kind: AccessKind;\n protocolID: typeof ACCESS_PROTOCOL;\n keyID: string;\n exp: number;\n}\n\nexport async function createAccessChallenge(o: CreateAccessChallengeOptions): Promise<AccessChallengeResponse> {\n const origin = normalOrigin(o.origin);\n const require = normaliseRequirement(o.require);\n let sub: string | undefined;\n if (o.identityKey !== undefined && o.identityKey !== null && o.identityKey !== '') {\n if (!isIdentityKey(o.identityKey)) throw new AccessError('bad_request', 'identityKey must be a compressed public key');\n sub = o.identityKey.toLowerCase();\n }\n const iat = Math.floor((o.now ?? Date.now()) / 1000);\n const exp = iat + (o.lifetimeS ?? ACCESS_CHALLENGE_LIFETIME_S);\n const nonce = randomNonce();\n const c: AccessChallenge = { v: 1, kind: o.kind ?? kindFor(require), origin, nonce, iat, exp, sub, require };\n const challenge = canonicalJson(c);\n await o.store.put(nonce, { identityKey: sub ?? '', origin, message: challenge, expiresAt: exp * 1000 });\n return { challenge, nonce, kind: c.kind, protocolID: ACCESS_PROTOCOL, keyID: nonce, exp };\n}\n\n// ── Verify ────────────────────────────────────────────────────────────────────────────────\n\nconst isByteArray = (s: unknown): s is number[] =>\n Array.isArray(s) && s.length >= 8 && s.length <= 80 && s.every((b) => Number.isInteger(b) && b >= 0 && b <= 255);\n\n/** The P2PKH address a holding proof establishes, or null if the proof does not verify. */\nexport function verifyHoldingProof(challenge: string, p: unknown): string | null {\n const proof = p as { pubkey?: unknown; signature?: unknown } | null;\n if (!proof || typeof proof.pubkey !== 'string' || !/^0[23][0-9a-fA-F]{64}$/.test(proof.pubkey) || !isByteArray(proof.signature)) return null;\n try {\n const pub = PublicKey.fromString(proof.pubkey);\n const sig = Signature.fromDER(proof.signature);\n return pub.verify(challengeBytes(challenge), sig) ? pub.toAddress() : null;\n } catch {\n return null;\n }\n}\n\nexport interface VerifyAccessOptions {\n origin: string;\n /** The body the browser posted (AccessResponse). Untrusted. */\n response: unknown;\n store: NonceStore;\n resolver: AccessResolver;\n /**\n * The requirement YOUR config says this gate needs. If given, the challenge must carry exactly it\n * (defence in depth: the challenge came from your store, but configs change).\n */\n require?: AccessRequirement;\n now?: number;\n}\n\nexport interface AccessGrant {\n ok: true;\n identityKey: string;\n kind: AccessKind;\n require: AccessRequirement;\n /** Proven addresses holding the token (store them to re-check later). */\n addresses: string[];\n have: { tokenRaw?: string; needRaw?: string; identityLevel?: number; kyc?: boolean };\n}\n\n/**\n * Verify a wallet's response. Returns the grant, or throws AccessError with a protocol code.\n * The nonce is consumed first, so each challenge can be tried once.\n */\nexport async function verifyAccess(o: VerifyAccessOptions): Promise<AccessGrant> {\n const r = o.response as Partial<AccessResponse> | null;\n if (!r || typeof r !== 'object' || r.v !== 1) throw new AccessError('bad_request', 'response must be { v: 1, challenge, identityKey, signature }');\n const c = parseAccessChallenge(r.challenge);\n const nowS = Math.floor((o.now ?? Date.now()) / 1000);\n\n const pending = await o.store.take(c.nonce);\n if (!pending) throw new AccessError(nowS >= c.exp ? 'expired' : 'replay');\n if (nowS >= c.exp || (o.now ?? Date.now()) > pending.expiresAt) throw new AccessError('expired');\n if (pending.message !== r.challenge) throw new AccessError('replay', 'challenge does not match the one issued');\n let origin: string;\n try {\n origin = normalOrigin(o.origin);\n } catch {\n throw new AccessError('bad_request', 'server origin is misconfigured');\n }\n if (c.origin !== origin || pending.origin !== origin) throw new AccessError('bad_request', 'origin mismatch');\n if (o.require && canonicalJson(normaliseRequirement(o.require)) !== canonicalJson(c.require)) {\n throw new AccessError('bad_request', 'challenge requirement does not match this gate');\n }\n\n if (!isIdentityKey(r.identityKey)) throw new AccessError('bad_request', 'identityKey must be a compressed public key');\n const identityKey = r.identityKey.toLowerCase();\n if (c.sub && c.sub !== identityKey) throw new AccessError('wrong_account');\n if (!isByteArray(r.signature)) throw new AccessError('invalid_signature');\n let valid = false;\n try {\n ({ valid } = await new ProtoWallet('anyone').verifySignature({\n protocolID: ACCESS_PROTOCOL,\n keyID: c.nonce,\n counterparty: identityKey,\n data: challengeBytes(r.challenge as string),\n signature: r.signature,\n }));\n } catch {\n valid = false; // @bsv/sdk throws on a bad signature\n }\n if (!valid) throw new AccessError('invalid_signature');\n\n const grant: AccessGrant = { ok: true, identityKey, kind: c.kind, require: c.require, addresses: [], have: {} };\n\n if (c.require.identityLevel || c.require.kyc) {\n if (!o.resolver.identity) throw new AccessError('indexer_unavailable', 'no identity resolver configured');\n let id: IdentityStatus;\n try {\n id = await o.resolver.identity(identityKey, r as AccessResponse);\n } catch (e) {\n throw new AccessError('indexer_unavailable', `identity lookup failed: ${(e as Error).message}`);\n }\n grant.have.identityLevel = id.level;\n grant.have.kyc = id.kyc;\n if (c.require.kyc && !id.kyc) throw new AccessError('kyc_required');\n if (c.require.identityLevel && id.level < c.require.identityLevel) {\n throw new AccessError('identity_level_too_low', undefined, { have: id.level, need: c.require.identityLevel });\n }\n }\n\n if (c.require.token) {\n const proofs = Array.isArray(r.holdings) ? r.holdings.slice(0, MAX_HOLDING_PROOFS) : [];\n const addresses = new Set<string>();\n for (const p of proofs) {\n const a = verifyHoldingProof(r.challenge as string, p);\n if (a) addresses.add(a);\n }\n grant.addresses = [...addresses].sort();\n const check = await checkTokenHolding(c.require, grant.addresses, o.resolver);\n grant.have.tokenRaw = check.have;\n grant.have.needRaw = check.need;\n }\n return grant;\n}\n\n/**\n * Re-check a token requirement over already-proven addresses (for periodic re-verification).\n * Returns { have, need } or throws AccessError('insufficient_tokens' | 'indexer_unavailable').\n */\nexport async function checkTokenHolding(\n require: AccessRequirement,\n addresses: string[],\n resolver: AccessResolver,\n): Promise<{ have: string; need: string }> {\n const t = require.token;\n if (!t) return { have: '0', need: '0' };\n let supply: string | undefined;\n if (t.minBps) {\n if (!resolver.tokenSupply) throw new AccessError('indexer_unavailable', 'no supply resolver configured');\n try {\n supply = await resolver.tokenSupply(t.id);\n } catch (e) {\n throw new AccessError('indexer_unavailable', `supply lookup failed: ${(e as Error).message}`);\n }\n }\n const need = tokenNeedRaw(t, supply);\n if (!addresses.length) throw new AccessError('insufficient_tokens', undefined, { have: '0', need });\n let have: string;\n try {\n have = await resolver.tokenBalance(t.id, addresses);\n } catch (e) {\n throw new AccessError('indexer_unavailable', `balance lookup failed: ${(e as Error).message}`);\n }\n if (!atLeastRaw(have, need)) throw new AccessError('insufficient_tokens', undefined, { have, need });\n return { have, need };\n}\n"],"mappings":"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","names":["import_sdk","status","body","txid","keys","import_sdk","require","normalOrigin","randomNonce"]}
package/dist/server.d.cts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { ChainTracker, WalletProtocol } from '@bsv/sdk';
2
+ import { b as AccessResponse, i as AccessKind, d as ACCESS_PROTOCOL, a as AccessRequirement } from './access-Z6xQwKVF.cjs';
3
+ export { l as ACCESS_CHALLENGE_LIFETIME_S, c as ACCESS_ERROR_STATUS, e as ACCESS_VERSION, f as AccessChallenge, A as AccessError, g as AccessErrorBody, h as AccessErrorCode, H as HoldingProof, M as MAX_HOLDING_PROOFS, P as PROTOCOL_MEDIA, T as TokenRequirement, j as accessErrorMessage, m as atLeastRaw, k as canonicalJson, n as challengeBytes, o as kindFor, q as normaliseRequirement, r as parseAccessChallenge, p as protocolMediaUrl, t as tokenNeedRaw } from './access-Z6xQwKVF.cjs';
2
4
 
3
5
  /**
4
6
  * Shared NonceStore adapters. No database driver is imported: you pass your own client or query
@@ -202,6 +204,114 @@ type SignInOrLinkResult = {
202
204
  */
203
205
  declare function signInOrLink(identityKey: string, o: SignInOrLinkOptions): Promise<SignInOrLinkResult>;
204
206
 
207
+ interface IdentityStatus {
208
+ /** bit-sign identity level: 0 none, 1 Basic … 4 Sovereign (Veriff). */
209
+ level: number;
210
+ /** A live KYC credential for this identity key. */
211
+ kyc: boolean;
212
+ }
213
+ /**
214
+ * Where verifyAccess reads the chain and identity data. Every method THROWS when it cannot answer;
215
+ * a throw becomes `indexer_unavailable`, never "holds nothing".
216
+ */
217
+ interface AccessResolver {
218
+ /** Confirmed + unspent raw balance of `tokenId` ('bsv21:…') over these P2PKH addresses. */
219
+ tokenBalance(tokenId: string, addresses: string[]): Promise<string>;
220
+ /** Total supply, raw. Needed only for minBps requirements. */
221
+ tokenSupply?(tokenId: string): Promise<string>;
222
+ /** Identity level and KYC for a key. Needed only for identityLevel / kyc requirements. */
223
+ identity?(identityKey: string, response: AccessResponse): Promise<IdentityStatus>;
224
+ }
225
+ type FetchLike = typeof fetch;
226
+ interface OneSatResolverOptions {
227
+ /** Default 'https://api.1sat.app/1sat'. */
228
+ base?: string;
229
+ fetch?: FetchLike;
230
+ timeoutMs?: number;
231
+ }
232
+ /** BSV-21 balances and supply from the 1Sat indexer (the one bit-sign's token rooms use). */
233
+ declare function oneSatResolver(o?: OneSatResolverOptions): AccessResolver;
234
+ interface BitSignResolverOptions {
235
+ /** Default 'https://bit-sign.online'. */
236
+ base?: string;
237
+ fetch?: FetchLike;
238
+ timeoutMs?: number;
239
+ /** Token reads. Default: oneSatResolver(). */
240
+ tokens?: AccessResolver;
241
+ }
242
+ /**
243
+ * Identity level / KYC from bit-sign's POST /api/access/verify, which checks the signed response
244
+ * itself (the user's signature is their consent to disclose those facts to this origin).
245
+ * Token reads go to `tokens` (default the 1Sat indexer).
246
+ */
247
+ declare function bitSignResolver(o?: BitSignResolverOptions): AccessResolver;
248
+ interface CreateAccessChallengeOptions {
249
+ /** YOUR origin from config. Never from the request. */
250
+ origin: string;
251
+ /** The requirement, from YOUR config. Never from the request. */
252
+ require: AccessRequirement;
253
+ /** A shared store (redisStore / pgStore). The same stores sign-in uses. */
254
+ store: NonceStore;
255
+ /** The identity key the browser says it is (optional). If given, a different signer is wrong_account. */
256
+ identityKey?: unknown;
257
+ /** Default: '403' when a token is required, else '401'. */
258
+ kind?: AccessKind;
259
+ lifetimeS?: number;
260
+ now?: number;
261
+ }
262
+ interface AccessChallengeResponse {
263
+ /** The canonical JSON the wallet signs. Pass it to the wallet unchanged. */
264
+ challenge: string;
265
+ nonce: string;
266
+ kind: AccessKind;
267
+ protocolID: typeof ACCESS_PROTOCOL;
268
+ keyID: string;
269
+ exp: number;
270
+ }
271
+ declare function createAccessChallenge(o: CreateAccessChallengeOptions): Promise<AccessChallengeResponse>;
272
+ /** The P2PKH address a holding proof establishes, or null if the proof does not verify. */
273
+ declare function verifyHoldingProof(challenge: string, p: unknown): string | null;
274
+ interface VerifyAccessOptions {
275
+ origin: string;
276
+ /** The body the browser posted (AccessResponse). Untrusted. */
277
+ response: unknown;
278
+ store: NonceStore;
279
+ resolver: AccessResolver;
280
+ /**
281
+ * The requirement YOUR config says this gate needs. If given, the challenge must carry exactly it
282
+ * (defence in depth: the challenge came from your store, but configs change).
283
+ */
284
+ require?: AccessRequirement;
285
+ now?: number;
286
+ }
287
+ interface AccessGrant {
288
+ ok: true;
289
+ identityKey: string;
290
+ kind: AccessKind;
291
+ require: AccessRequirement;
292
+ /** Proven addresses holding the token (store them to re-check later). */
293
+ addresses: string[];
294
+ have: {
295
+ tokenRaw?: string;
296
+ needRaw?: string;
297
+ identityLevel?: number;
298
+ kyc?: boolean;
299
+ };
300
+ }
301
+ /**
302
+ * Verify a wallet's response. Returns the grant, or throws AccessError with a protocol code.
303
+ * The nonce is consumed first, so each challenge can be tried once.
304
+ */
305
+ declare function verifyAccess(o: VerifyAccessOptions): Promise<AccessGrant>;
306
+ /**
307
+ * Re-check a token requirement over already-proven addresses (for periodic re-verification).
308
+ * Returns { have, need } or throws AccessError('insufficient_tokens' | 'indexer_unavailable').
309
+ */
310
+ declare function checkTokenHolding(require: AccessRequirement, addresses: string[], resolver: AccessResolver): Promise<{
311
+ have: string;
312
+ need: string;
313
+ }>;
314
+
205
315
  /** Security level 2, a protocol name used for nothing else. Keep it constant. */
206
316
  declare const LOGIN_PROTOCOL: WalletProtocol;
207
317
  declare const CHALLENGE_LIFETIME_MS: number;
@@ -263,4 +373,4 @@ declare function verifySignIn(opts: VerifySignInOptions): Promise<string | null>
263
373
  /** True when a request's Origin header is exactly your configured origin. Check it on both routes. */
264
374
  declare function originAllowed(requestOrigin: string | null | undefined, origin: string): boolean;
265
375
 
266
- export { CHALLENGE_LIFETIME_MS, type ChallengeResponse, type CookieOptions, type CreateChallengeOptions, type ExpectedOutput, IdentityLinkConflictError, LOGIN_PROTOCOL, type LinkCallbacks, type LinkResult, MIN_SECRET_LENGTH, type NonceStore, PaymentVerificationError, type PendingChallenge, type PgQuery, type PgStoreOptions, type RedisLike, type RedisStoreOptions, type SessionPayload, type SignInOrLinkOptions, type SignInOrLinkResult, type SignSessionOptions, type TxStatus, type VerifyPaymentOptions, type VerifyPaymentResult, type VerifySignInOptions, createChallenge, isIdentityKey, linkIdentity, loginMessage, memoryStore, originAllowed, pgNonceTableSql, pgStore, readCookie, redisStore, serializeCookie, signInOrLink, signSession, verifyPayment, verifySession, verifySignIn };
376
+ export { ACCESS_PROTOCOL, type AccessChallengeResponse, type AccessGrant, AccessKind, AccessRequirement, type AccessResolver, AccessResponse, type BitSignResolverOptions, CHALLENGE_LIFETIME_MS, type ChallengeResponse, type CookieOptions, type CreateAccessChallengeOptions, type CreateChallengeOptions, type ExpectedOutput, IdentityLinkConflictError, type IdentityStatus, LOGIN_PROTOCOL, type LinkCallbacks, type LinkResult, MIN_SECRET_LENGTH, type NonceStore, type OneSatResolverOptions, PaymentVerificationError, type PendingChallenge, type PgQuery, type PgStoreOptions, type RedisLike, type RedisStoreOptions, type SessionPayload, type SignInOrLinkOptions, type SignInOrLinkResult, type SignSessionOptions, type TxStatus, type VerifyAccessOptions, type VerifyPaymentOptions, type VerifyPaymentResult, type VerifySignInOptions, bitSignResolver, checkTokenHolding, createAccessChallenge, createChallenge, isIdentityKey, linkIdentity, loginMessage, memoryStore, oneSatResolver, originAllowed, pgNonceTableSql, pgStore, readCookie, redisStore, serializeCookie, signInOrLink, signSession, verifyAccess, verifyHoldingProof, verifyPayment, verifySession, verifySignIn };
package/dist/server.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { ChainTracker, WalletProtocol } from '@bsv/sdk';
2
+ import { b as AccessResponse, i as AccessKind, d as ACCESS_PROTOCOL, a as AccessRequirement } from './access-Z6xQwKVF.js';
3
+ export { l as ACCESS_CHALLENGE_LIFETIME_S, c as ACCESS_ERROR_STATUS, e as ACCESS_VERSION, f as AccessChallenge, A as AccessError, g as AccessErrorBody, h as AccessErrorCode, H as HoldingProof, M as MAX_HOLDING_PROOFS, P as PROTOCOL_MEDIA, T as TokenRequirement, j as accessErrorMessage, m as atLeastRaw, k as canonicalJson, n as challengeBytes, o as kindFor, q as normaliseRequirement, r as parseAccessChallenge, p as protocolMediaUrl, t as tokenNeedRaw } from './access-Z6xQwKVF.js';
2
4
 
3
5
  /**
4
6
  * Shared NonceStore adapters. No database driver is imported: you pass your own client or query
@@ -202,6 +204,114 @@ type SignInOrLinkResult = {
202
204
  */
203
205
  declare function signInOrLink(identityKey: string, o: SignInOrLinkOptions): Promise<SignInOrLinkResult>;
204
206
 
207
+ interface IdentityStatus {
208
+ /** bit-sign identity level: 0 none, 1 Basic … 4 Sovereign (Veriff). */
209
+ level: number;
210
+ /** A live KYC credential for this identity key. */
211
+ kyc: boolean;
212
+ }
213
+ /**
214
+ * Where verifyAccess reads the chain and identity data. Every method THROWS when it cannot answer;
215
+ * a throw becomes `indexer_unavailable`, never "holds nothing".
216
+ */
217
+ interface AccessResolver {
218
+ /** Confirmed + unspent raw balance of `tokenId` ('bsv21:…') over these P2PKH addresses. */
219
+ tokenBalance(tokenId: string, addresses: string[]): Promise<string>;
220
+ /** Total supply, raw. Needed only for minBps requirements. */
221
+ tokenSupply?(tokenId: string): Promise<string>;
222
+ /** Identity level and KYC for a key. Needed only for identityLevel / kyc requirements. */
223
+ identity?(identityKey: string, response: AccessResponse): Promise<IdentityStatus>;
224
+ }
225
+ type FetchLike = typeof fetch;
226
+ interface OneSatResolverOptions {
227
+ /** Default 'https://api.1sat.app/1sat'. */
228
+ base?: string;
229
+ fetch?: FetchLike;
230
+ timeoutMs?: number;
231
+ }
232
+ /** BSV-21 balances and supply from the 1Sat indexer (the one bit-sign's token rooms use). */
233
+ declare function oneSatResolver(o?: OneSatResolverOptions): AccessResolver;
234
+ interface BitSignResolverOptions {
235
+ /** Default 'https://bit-sign.online'. */
236
+ base?: string;
237
+ fetch?: FetchLike;
238
+ timeoutMs?: number;
239
+ /** Token reads. Default: oneSatResolver(). */
240
+ tokens?: AccessResolver;
241
+ }
242
+ /**
243
+ * Identity level / KYC from bit-sign's POST /api/access/verify, which checks the signed response
244
+ * itself (the user's signature is their consent to disclose those facts to this origin).
245
+ * Token reads go to `tokens` (default the 1Sat indexer).
246
+ */
247
+ declare function bitSignResolver(o?: BitSignResolverOptions): AccessResolver;
248
+ interface CreateAccessChallengeOptions {
249
+ /** YOUR origin from config. Never from the request. */
250
+ origin: string;
251
+ /** The requirement, from YOUR config. Never from the request. */
252
+ require: AccessRequirement;
253
+ /** A shared store (redisStore / pgStore). The same stores sign-in uses. */
254
+ store: NonceStore;
255
+ /** The identity key the browser says it is (optional). If given, a different signer is wrong_account. */
256
+ identityKey?: unknown;
257
+ /** Default: '403' when a token is required, else '401'. */
258
+ kind?: AccessKind;
259
+ lifetimeS?: number;
260
+ now?: number;
261
+ }
262
+ interface AccessChallengeResponse {
263
+ /** The canonical JSON the wallet signs. Pass it to the wallet unchanged. */
264
+ challenge: string;
265
+ nonce: string;
266
+ kind: AccessKind;
267
+ protocolID: typeof ACCESS_PROTOCOL;
268
+ keyID: string;
269
+ exp: number;
270
+ }
271
+ declare function createAccessChallenge(o: CreateAccessChallengeOptions): Promise<AccessChallengeResponse>;
272
+ /** The P2PKH address a holding proof establishes, or null if the proof does not verify. */
273
+ declare function verifyHoldingProof(challenge: string, p: unknown): string | null;
274
+ interface VerifyAccessOptions {
275
+ origin: string;
276
+ /** The body the browser posted (AccessResponse). Untrusted. */
277
+ response: unknown;
278
+ store: NonceStore;
279
+ resolver: AccessResolver;
280
+ /**
281
+ * The requirement YOUR config says this gate needs. If given, the challenge must carry exactly it
282
+ * (defence in depth: the challenge came from your store, but configs change).
283
+ */
284
+ require?: AccessRequirement;
285
+ now?: number;
286
+ }
287
+ interface AccessGrant {
288
+ ok: true;
289
+ identityKey: string;
290
+ kind: AccessKind;
291
+ require: AccessRequirement;
292
+ /** Proven addresses holding the token (store them to re-check later). */
293
+ addresses: string[];
294
+ have: {
295
+ tokenRaw?: string;
296
+ needRaw?: string;
297
+ identityLevel?: number;
298
+ kyc?: boolean;
299
+ };
300
+ }
301
+ /**
302
+ * Verify a wallet's response. Returns the grant, or throws AccessError with a protocol code.
303
+ * The nonce is consumed first, so each challenge can be tried once.
304
+ */
305
+ declare function verifyAccess(o: VerifyAccessOptions): Promise<AccessGrant>;
306
+ /**
307
+ * Re-check a token requirement over already-proven addresses (for periodic re-verification).
308
+ * Returns { have, need } or throws AccessError('insufficient_tokens' | 'indexer_unavailable').
309
+ */
310
+ declare function checkTokenHolding(require: AccessRequirement, addresses: string[], resolver: AccessResolver): Promise<{
311
+ have: string;
312
+ need: string;
313
+ }>;
314
+
205
315
  /** Security level 2, a protocol name used for nothing else. Keep it constant. */
206
316
  declare const LOGIN_PROTOCOL: WalletProtocol;
207
317
  declare const CHALLENGE_LIFETIME_MS: number;
@@ -263,4 +373,4 @@ declare function verifySignIn(opts: VerifySignInOptions): Promise<string | null>
263
373
  /** True when a request's Origin header is exactly your configured origin. Check it on both routes. */
264
374
  declare function originAllowed(requestOrigin: string | null | undefined, origin: string): boolean;
265
375
 
266
- export { CHALLENGE_LIFETIME_MS, type ChallengeResponse, type CookieOptions, type CreateChallengeOptions, type ExpectedOutput, IdentityLinkConflictError, LOGIN_PROTOCOL, type LinkCallbacks, type LinkResult, MIN_SECRET_LENGTH, type NonceStore, PaymentVerificationError, type PendingChallenge, type PgQuery, type PgStoreOptions, type RedisLike, type RedisStoreOptions, type SessionPayload, type SignInOrLinkOptions, type SignInOrLinkResult, type SignSessionOptions, type TxStatus, type VerifyPaymentOptions, type VerifyPaymentResult, type VerifySignInOptions, createChallenge, isIdentityKey, linkIdentity, loginMessage, memoryStore, originAllowed, pgNonceTableSql, pgStore, readCookie, redisStore, serializeCookie, signInOrLink, signSession, verifyPayment, verifySession, verifySignIn };
376
+ export { ACCESS_PROTOCOL, type AccessChallengeResponse, type AccessGrant, AccessKind, AccessRequirement, type AccessResolver, AccessResponse, type BitSignResolverOptions, CHALLENGE_LIFETIME_MS, type ChallengeResponse, type CookieOptions, type CreateAccessChallengeOptions, type CreateChallengeOptions, type ExpectedOutput, IdentityLinkConflictError, type IdentityStatus, LOGIN_PROTOCOL, type LinkCallbacks, type LinkResult, MIN_SECRET_LENGTH, type NonceStore, type OneSatResolverOptions, PaymentVerificationError, type PendingChallenge, type PgQuery, type PgStoreOptions, type RedisLike, type RedisStoreOptions, type SessionPayload, type SignInOrLinkOptions, type SignInOrLinkResult, type SignSessionOptions, type TxStatus, type VerifyAccessOptions, type VerifyPaymentOptions, type VerifyPaymentResult, type VerifySignInOptions, bitSignResolver, checkTokenHolding, createAccessChallenge, createChallenge, isIdentityKey, linkIdentity, loginMessage, memoryStore, oneSatResolver, originAllowed, pgNonceTableSql, pgStore, readCookie, redisStore, serializeCookie, signInOrLink, signSession, verifyAccess, verifyHoldingProof, verifyPayment, verifySession, verifySignIn };