@bwalletx/connect 0.5.4 → 0.5.6

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 (48) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +76 -8
  3. package/dist/bwalletx-connect.global.js +92 -25
  4. package/dist/bwalletx-connect.global.js.map +1 -1
  5. package/dist/{wallets-CbzMtxrf.d.ts → choice-DCh-tgpT.d.cts} +112 -4
  6. package/dist/{wallets-CbzMtxrf.d.cts → choice-DCh-tgpT.d.ts} +112 -4
  7. package/dist/chunk-34DKBQNN.js +222 -0
  8. package/dist/chunk-34DKBQNN.js.map +1 -0
  9. package/dist/{chunk-QRXFL66W.js → chunk-A2WLGS25.js} +6 -4
  10. package/dist/chunk-A2WLGS25.js.map +1 -0
  11. package/dist/chunk-IFGOAROM.js +255 -0
  12. package/dist/chunk-IFGOAROM.js.map +1 -0
  13. package/dist/chunk-LS6AYEBO.js +1886 -0
  14. package/dist/chunk-LS6AYEBO.js.map +1 -0
  15. package/dist/element-D9Zl-x12.d.cts +46 -0
  16. package/dist/element-D9Zl-x12.d.ts +46 -0
  17. package/dist/element.cjs +1147 -61
  18. package/dist/element.cjs.map +1 -1
  19. package/dist/element.d.cts +23 -28
  20. package/dist/element.d.ts +23 -28
  21. package/dist/element.js +29 -2
  22. package/dist/element.js.map +1 -1
  23. package/dist/index.cjs +1469 -453
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +163 -14
  26. package/dist/index.d.ts +163 -14
  27. package/dist/index.js +100 -12
  28. package/dist/index.js.map +1 -1
  29. package/dist/next.cjs +5 -3
  30. package/dist/next.cjs.map +1 -1
  31. package/dist/next.js +1 -1
  32. package/dist/react.cjs +994 -183
  33. package/dist/react.cjs.map +1 -1
  34. package/dist/react.d.cts +75 -8
  35. package/dist/react.d.ts +75 -8
  36. package/dist/react.js +121 -40
  37. package/dist/react.js.map +1 -1
  38. package/dist/server.cjs +5 -3
  39. package/dist/server.cjs.map +1 -1
  40. package/dist/server.d.cts +8 -3
  41. package/dist/server.d.ts +8 -3
  42. package/dist/server.js +1 -1
  43. package/package.json +10 -9
  44. package/dist/chunk-EXRQJTXF.js +0 -910
  45. package/dist/chunk-EXRQJTXF.js.map +0 -1
  46. package/dist/chunk-QRXFL66W.js.map +0 -1
  47. package/dist/chunk-RIOHG5JH.js +0 -459
  48. package/dist/chunk-RIOHG5JH.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/nonce-stores.ts","../src/session.ts","../src/verify-payment.ts","../src/link.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 /** The key the challenge was made for, or '' when none was given (verifySignIn then binds the claimed key). */\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 /**\n * The identity key the browser claims, checked again at verify. Optional since 0.5.5: leave it out so the browser\n * can ask the wallet for the key and the signature in ONE sheet; verifySignIn then binds the key the browser claims,\n * and the signature only verifies for that key. A challenge made WITH a key still only verifies for that key.\n */\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 an identityKey is given and is not a valid compressed public key. Send the result to the browser. */\nexport async function createChallenge(opts: CreateChallengeOptions): Promise<ChallengeResponse> {\n const given = opts.identityKey !== undefined && opts.identityKey !== null && opts.identityKey !== '';\n if (given && !isIdentityKey(opts.identityKey)) throw new Error('identityKey must be a compressed public key (66 hex chars)');\n const boundKey = given ? (opts.identityKey as string).toLowerCase() : '';\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: boundKey, 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 // A challenge made for a key only verifies for that key. One made without a key binds the key claimed now: the\n // signature below is checked against exactly this key, so a wrong claim fails.\n if (c.identityKey && 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 * 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":["ProtoWallet","PublicKey","status","body","txid","require","PublicKey","normalOrigin","randomNonce","ProtoWallet"]}
@@ -0,0 +1,255 @@
1
+ import {
2
+ attemptFor,
3
+ decideWallet,
4
+ outcomeOf,
5
+ rememberIdentity,
6
+ rememberedIdentity,
7
+ reportAttempt,
8
+ signInAndVerify,
9
+ substrateOf,
10
+ walletTypeOf
11
+ } from "./chunk-LS6AYEBO.js";
12
+
13
+ // src/pay.ts
14
+ import { P2PKH, PublicKey, Transaction, Utils } from "@bsv/sdk";
15
+ var BRC29_PROTOCOL = [2, "3241645161d8"];
16
+ var PaymentError = class extends Error {
17
+ constructor(code, message, maybeSent = false, cause) {
18
+ super(message);
19
+ this.code = code;
20
+ this.maybeSent = maybeSent;
21
+ this.cause = cause;
22
+ this.name = "PaymentError";
23
+ }
24
+ code;
25
+ maybeSent;
26
+ cause;
27
+ };
28
+ var isLegacy = (s) => typeof s.pay === "function" && typeof s.createAction !== "function";
29
+ var isIdentityKeyHex = (s) => /^0[23][0-9a-fA-F]{64}$/.test(s);
30
+ var isPaymail = (s) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
31
+ function randomB64(n) {
32
+ const b = new Uint8Array(n);
33
+ globalThis.crypto.getRandomValues(b);
34
+ return Utils.toBase64(Array.from(b));
35
+ }
36
+ function opReturn(text) {
37
+ const d = Utils.toArray(text, "utf8");
38
+ const len = d.length;
39
+ const push = len < 76 ? [len] : len <= 255 ? [76, len] : [77, len & 255, len >> 8];
40
+ return Utils.toHex([0, 106, ...push, ...d]);
41
+ }
42
+ var clip = (s, n) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
43
+ var describe = (s) => clip(s.length < 5 ? `${s} payment`.trim() : s, 50);
44
+ async function usdRate(o) {
45
+ if (typeof o.usdPerBsv === "number") return o.usdPerBsv;
46
+ try {
47
+ if (typeof o.usdPerBsv === "function") return await o.usdPerBsv();
48
+ const r = await (o.fetch ?? fetch)("https://api.whatsonchain.com/v1/bsv/main/exchangerate");
49
+ const j = await r.json();
50
+ if (!r.ok || !(Number(j.rate) > 0)) throw new Error(`status ${r.status}`);
51
+ return Number(j.rate);
52
+ } catch (e) {
53
+ throw new PaymentError("RATE_FAILED", `Could not get the BSV/USD rate: ${e.message}`, false, e);
54
+ }
55
+ }
56
+ async function amountInSats(req, o = {}) {
57
+ if (req.sats != null) {
58
+ if (!Number.isSafeInteger(req.sats) || req.sats <= 0) throw new PaymentError("BAD_REQUEST", "sats must be a positive integer");
59
+ return req.sats;
60
+ }
61
+ if (req.usd != null) {
62
+ if (!(req.usd > 0)) throw new PaymentError("BAD_REQUEST", "usd must be positive");
63
+ return Math.max(1, Math.ceil(req.usd / await usdRate(o) * 1e8));
64
+ }
65
+ throw new PaymentError("BAD_REQUEST", "Give sats or usd");
66
+ }
67
+ async function resolvePaymailP2P(paymail, sats, f = fetch) {
68
+ const [alias, domain] = paymail.split("@");
69
+ const fail = (m, e) => new PaymentError("RESOLVE_FAILED", `Could not resolve ${paymail}: ${m}`, false, e);
70
+ let caps;
71
+ try {
72
+ const r = await f(`https://${domain}/.well-known/bsvalias`);
73
+ caps = (await r.json()).capabilities;
74
+ } catch (e) {
75
+ throw fail("no bsvalias document (or CORS blocked it; resolve on your server and pass resolvePaymail)", e);
76
+ }
77
+ const fill = (u) => u.replace("{alias}", encodeURIComponent(alias)).replace("{domain.tld}", domain);
78
+ const dest = caps?.["2a40af698840"];
79
+ if (!dest) throw fail("it does not offer P2P payment destinations");
80
+ let body;
81
+ try {
82
+ const r = await f(fill(dest), { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ satoshis: sats }) });
83
+ if (!r.ok) throw new Error(`status ${r.status}`);
84
+ body = await r.json();
85
+ } catch (e) {
86
+ throw fail(e.message, e);
87
+ }
88
+ if (!body.outputs?.length) throw fail("no outputs returned");
89
+ const receive = caps["5f1323cddf31"];
90
+ return {
91
+ outputs: body.outputs.map((o) => ({ script: o.script, satoshis: o.satoshis })),
92
+ reference: body.reference,
93
+ submit: receive ? async (hex) => {
94
+ await f(fill(receive), {
95
+ method: "POST",
96
+ headers: { "content-type": "application/json" },
97
+ body: JSON.stringify({ hex, reference: body.reference, metadata: {} })
98
+ });
99
+ } : void 0
100
+ };
101
+ }
102
+ async function runAction(wallet, description, outputs, labels) {
103
+ try {
104
+ return await wallet.createAction({
105
+ description: describe(description),
106
+ outputs: outputs.map((o) => ({ lockingScript: o.script, satoshis: o.satoshis, outputDescription: describe(o.description ?? "payment") })),
107
+ labels: labels?.length ? labels : void 0,
108
+ options: { randomizeOutputs: false }
109
+ });
110
+ } catch (e) {
111
+ const msg = e?.message ?? String(e);
112
+ const rejected = /denied|reject|cancel|abort|user/i.test(msg) || e?.code === 4001;
113
+ throw new PaymentError(rejected ? "WALLET_REJECTED" : "WALLET_ERROR", rejected ? "The payment was declined in the wallet." : `The wallet could not make the payment: ${msg}`, !rejected, e);
114
+ }
115
+ }
116
+ async function pay(source, req, o = {}) {
117
+ const to = (req.to ?? "").trim();
118
+ if (!to) throw new PaymentError("BAD_REQUEST", "Give a recipient (to)");
119
+ if (req.memo && req.memo.length > 1e3) throw new PaymentError("BAD_REQUEST", "memo is too long");
120
+ const sats = await amountInSats(req, o);
121
+ if (isLegacy(source)) return source.pay({ ...req, to, sats });
122
+ let outputs;
123
+ let remittance;
124
+ let submit;
125
+ if (isIdentityKeyHex(to)) {
126
+ const derivationPrefix = randomB64(8);
127
+ const derivationSuffix = randomB64(8);
128
+ let pk;
129
+ let me;
130
+ try {
131
+ ({ publicKey: pk } = await source.getPublicKey({ protocolID: BRC29_PROTOCOL, keyID: `${derivationPrefix} ${derivationSuffix}`, counterparty: to.toLowerCase() }));
132
+ ({ publicKey: me } = await source.getPublicKey({ identityKey: true }));
133
+ } catch (e) {
134
+ throw new PaymentError("WALLET_ERROR", `The wallet could not derive the payment key: ${e.message}`, false, e);
135
+ }
136
+ outputs = [{ script: new P2PKH().lock(PublicKey.fromString(pk).toHash()).toHex(), satoshis: sats, description: "payment" }];
137
+ remittance = { senderIdentityKey: me, derivationPrefix, derivationSuffix, outputIndex: 0 };
138
+ } else if (isPaymail(to)) {
139
+ const r2 = await (o.resolvePaymail ?? ((p, s) => resolvePaymailP2P(p, s, o.fetch ?? fetch)))(to, sats);
140
+ outputs = r2.outputs;
141
+ submit = r2.submit;
142
+ } else {
143
+ try {
144
+ outputs = [{ script: new P2PKH().lock(to).toHex(), satoshis: sats, description: "payment" }];
145
+ } catch {
146
+ throw new PaymentError("BAD_REQUEST", `Not a paymail, address or identity key: ${to}`);
147
+ }
148
+ }
149
+ if (req.memo && req.memoOnChain) outputs = [...outputs, { script: opReturn(req.memo), satoshis: 0, description: "memo" }];
150
+ const r = await runAction(source, req.memo || `Pay ${clip(to, 30)}`, outputs, req.labels);
151
+ if (!r.txid) throw new PaymentError("WALLET_ERROR", "The wallet did not return a txid", true);
152
+ const tx = r.tx ? Array.from(r.tx) : void 0;
153
+ if (submit && tx) {
154
+ await submit(Transaction.fromAtomicBEEF(tx).toHex()).catch(() => {
155
+ });
156
+ }
157
+ return { txid: r.txid, tx, sats, outputs, remittance };
158
+ }
159
+ function tip(source, req, o = {}) {
160
+ return pay(source, { memo: "Tip", ...req, labels: [...req.labels ?? [], "tip"] }, o);
161
+ }
162
+ async function buyToken(source, req) {
163
+ if (isLegacy(source)) throw new PaymentError("NOT_SUPPORTED", "This wallet cannot buy tokens. Use a BRC-100 wallet such as bWalletX.");
164
+ const q = await req.quote({ token: req.token, amount: req.amount });
165
+ if (!q?.outputs?.length) throw new PaymentError("BAD_REQUEST", "The quote returned no outputs");
166
+ const sats = q.outputs.reduce((n, x) => n + x.satoshis, 0);
167
+ const r = await runAction(source, q.description ?? `Buy ${clip(req.token, 30)}`, q.outputs, [...req.labels ?? [], "token-buy"]);
168
+ if (!r.txid) throw new PaymentError("WALLET_ERROR", "The wallet did not return a txid", true);
169
+ const res = { txid: r.txid, tx: r.tx ? Array.from(r.tx) : void 0, sats, outputs: q.outputs };
170
+ await q.onPaid?.(res);
171
+ return res;
172
+ }
173
+
174
+ // src/theme.ts
175
+ var WALLET_THEME_META = "bwalletx-theme";
176
+ var ID = /^[a-z0-9-]{1,32}$/;
177
+ function declareWalletTheme(theme) {
178
+ if (typeof document === "undefined") return;
179
+ const id = (theme ?? "").trim().toLowerCase();
180
+ let meta = document.head.querySelector(`meta[name="${WALLET_THEME_META}"]`);
181
+ if (!id || !ID.test(id)) {
182
+ meta?.remove();
183
+ return;
184
+ }
185
+ if (!meta) {
186
+ meta = document.createElement("meta");
187
+ meta.name = WALLET_THEME_META;
188
+ document.head.appendChild(meta);
189
+ }
190
+ if (meta.content !== id) meta.content = id;
191
+ }
192
+
193
+ // src/connect-flow.ts
194
+ async function runConnect(o) {
195
+ const started = Date.now();
196
+ const { list } = o;
197
+ let choice = null;
198
+ let reason = "none";
199
+ try {
200
+ const d = decideWallet(list, { walletId: o.walletId, chooser: o.chooser, picker: o.picker, fastPath: o.fastPath });
201
+ choice = d.option;
202
+ reason = d.reason;
203
+ if (!choice && d.reason === "chooser") choice = await o.pick(list);
204
+ if (!choice) {
205
+ if (d.reason === "none" || !list.length) throw Object.assign(new Error("No wallet found. Get bWalletX at bwalletx.com."), { code: "NOT_FOUND" });
206
+ reportAttempt(o.onAttempt, attemptFor(null, "cancelled", started));
207
+ return null;
208
+ }
209
+ const signIn = !!(o.challengeUrl && o.verifyUrl) && choice.kind !== "handcash";
210
+ const cw = await choice.connect(signIn ? { identity: false } : void 0);
211
+ let handle = cw.handle ?? null;
212
+ let paymail = cw.paymail ?? null;
213
+ let identityKey = cw.identityKey;
214
+ let signedIn = false;
215
+ if (cw.wallet && signIn) {
216
+ const { result, verified } = await signInAndVerify({
217
+ wallet: cw.wallet,
218
+ identityKey: cw.identityKey ?? void 0,
219
+ // The key that signed in here last time: a repeat sign-in is then the signature alone.
220
+ identityKeyHint: rememberedIdentity(),
221
+ challengeNeedsIdentityKey: o.challengeNeedsIdentityKey,
222
+ challenge: (key) => o.post(o.challengeUrl, key ? { identityKey: key } : {}),
223
+ verify: (r) => o.post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
224
+ });
225
+ rememberIdentity(result.identityKey);
226
+ identityKey = result.identityKey;
227
+ handle = verified.handle ?? handle;
228
+ paymail = verified.paymail ?? paymail;
229
+ signedIn = true;
230
+ } else if (cw.wallet && !identityKey) {
231
+ identityKey = (await cw.wallet.getPublicKey({ identityKey: true })).publicKey;
232
+ }
233
+ reportAttempt(o.onAttempt, attemptFor(choice, signedIn ? "signed-in" : "connected", started));
234
+ return { connected: cw, identityKey, handle, paymail, signedIn, walletType: walletTypeOf(choice), substrate: substrateOf(choice), reason };
235
+ } catch (e) {
236
+ reportAttempt(o.onAttempt, attemptFor(choice, outcomeOf(e), started));
237
+ throw e;
238
+ }
239
+ }
240
+
241
+ export {
242
+ BRC29_PROTOCOL,
243
+ PaymentError,
244
+ isIdentityKeyHex,
245
+ isPaymail,
246
+ amountInSats,
247
+ resolvePaymailP2P,
248
+ pay,
249
+ tip,
250
+ buyToken,
251
+ WALLET_THEME_META,
252
+ declareWalletTheme,
253
+ runConnect
254
+ };
255
+ //# sourceMappingURL=chunk-IFGOAROM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/pay.ts","../src/theme.ts","../src/connect-flow.ts"],"sourcesContent":["/**\n * Browser-side payment helpers on top of the BRC-100 wallet's createAction. Works with any\n * BRC-100 wallet. Each helper asks the wallet once; it never retries a spend. If a call fails\n * the error says whether the wallet may have sent anything (`maybeSent`).\n */\nimport { P2PKH, PublicKey, Transaction, Utils, type WalletInterface } from '@bsv/sdk';\n\n/** BRC-29 payment protocol id. */\nexport const BRC29_PROTOCOL: [2, string] = [2, '3241645161d8'];\n\nexport type PayTo = string; // paymail (name@domain), P2PKH address, or 66-hex identity key\n\nexport interface PayOutput {\n /** Locking script hex. */\n script: string;\n satoshis: number;\n description?: string;\n}\n\nexport interface PayRequest {\n to: PayTo;\n /** Amount in satoshis. Give this or `usd`. */\n sats?: number;\n /** Amount in US dollars, converted at `usdPerBsv` (or the live WhatsOnChain rate). */\n usd?: number;\n /** Shown in the wallet and, with `memoOnChain`, written in an OP_RETURN output. */\n memo?: string;\n memoOnChain?: boolean;\n /** Labels for the wallet's history. */\n labels?: string[];\n}\n\nexport interface PayOptions {\n /** Fixed rate for `usd`. Otherwise fetched from WhatsOnChain. */\n usdPerBsv?: number | (() => Promise<number>);\n /** Resolve a paymail to outputs. Default: bsvalias P2P payment destination (needs CORS on the paymail host). */\n resolvePaymail?: (paymail: string, sats: number) => Promise<{ outputs: PayOutput[]; reference?: string; submit?: (rawTxHex: string) => Promise<void> }>;\n fetch?: typeof fetch;\n}\n\nexport interface Brc29Remittance {\n senderIdentityKey: string;\n derivationPrefix: string;\n derivationSuffix: string;\n outputIndex: number;\n}\n\nexport interface PayResult {\n txid: string;\n /** Atomic BEEF (BRC-95) if the wallet returned it. Send it to your server's verifyPayment. */\n tx?: number[];\n sats: number;\n outputs: PayOutput[];\n /** For an identity-key payment: what the recipient needs to internalize the output (BRC-29). Deliver it to them. */\n remittance?: Brc29Remittance;\n}\n\nexport type PaymentErrorCode = 'BAD_REQUEST' | 'RESOLVE_FAILED' | 'RATE_FAILED' | 'WALLET_REJECTED' | 'WALLET_ERROR' | 'NOT_SUPPORTED';\n\nexport class PaymentError extends Error {\n constructor(\n readonly code: PaymentErrorCode,\n message: string,\n /** true when the wallet was asked to spend and the outcome is unknown. Check before trying again. */\n readonly maybeSent = false,\n readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'PaymentError';\n }\n}\n\n/** A wallet that is not BRC-100 (the HandCash transitional adapter) can still pay through this. */\nexport interface LegacyPayer {\n pay(req: PayRequest & { sats: number }): Promise<PayResult>;\n}\nexport type PaySource = WalletInterface | LegacyPayer;\n\nconst isLegacy = (s: PaySource): s is LegacyPayer => typeof (s as LegacyPayer).pay === 'function' && typeof (s as WalletInterface).createAction !== 'function';\n\nexport const isIdentityKeyHex = (s: string) => /^0[23][0-9a-fA-F]{64}$/.test(s);\nexport const isPaymail = (s: string) => /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(s);\n\nfunction randomB64(n: number) {\n const b = new Uint8Array(n);\n globalThis.crypto.getRandomValues(b);\n return Utils.toBase64(Array.from(b));\n}\n\nfunction opReturn(text: string): string {\n const d = Utils.toArray(text, 'utf8');\n const len = d.length;\n const push = len < 0x4c ? [len] : len <= 0xff ? [0x4c, len] : [0x4d, len & 0xff, len >> 8];\n return Utils.toHex([0x00, 0x6a, ...push, ...d]);\n}\n\nconst clip = (s: string, n: number) => (s.length > n ? s.slice(0, n - 1) + '…' : s);\n/** createAction descriptions must be 5–50 bytes. */\nconst describe = (s: string) => clip(s.length < 5 ? `${s} payment`.trim() : s, 50);\n\nasync function usdRate(o: PayOptions): Promise<number> {\n if (typeof o.usdPerBsv === 'number') return o.usdPerBsv;\n try {\n if (typeof o.usdPerBsv === 'function') return await o.usdPerBsv();\n const r = await (o.fetch ?? fetch)('https://api.whatsonchain.com/v1/bsv/main/exchangerate');\n const j = (await r.json()) as { rate?: number };\n if (!r.ok || !(Number(j.rate) > 0)) throw new Error(`status ${r.status}`);\n return Number(j.rate);\n } catch (e) {\n throw new PaymentError('RATE_FAILED', `Could not get the BSV/USD rate: ${(e as Error).message}`, false, e);\n }\n}\n\n/** Satoshis for a request (sats as given, or usd converted, rounded up). */\nexport async function amountInSats(req: Pick<PayRequest, 'sats' | 'usd'>, o: PayOptions = {}): Promise<number> {\n if (req.sats != null) {\n if (!Number.isSafeInteger(req.sats) || req.sats <= 0) throw new PaymentError('BAD_REQUEST', 'sats must be a positive integer');\n return req.sats;\n }\n if (req.usd != null) {\n if (!(req.usd > 0)) throw new PaymentError('BAD_REQUEST', 'usd must be positive');\n return Math.max(1, Math.ceil((req.usd / (await usdRate(o))) * 1e8));\n }\n throw new PaymentError('BAD_REQUEST', 'Give sats or usd');\n}\n\n/** Default paymail resolver: bsvalias P2P Payment Destination (BRC-? / capability 2a40af698840). */\nexport async function resolvePaymailP2P(paymail: string, sats: number, f: typeof fetch = fetch) {\n const [alias, domain] = paymail.split('@');\n const fail = (m: string, e?: unknown) => new PaymentError('RESOLVE_FAILED', `Could not resolve ${paymail}: ${m}`, false, e);\n let caps: Record<string, string>;\n try {\n const r = await f(`https://${domain}/.well-known/bsvalias`);\n caps = ((await r.json()) as { capabilities: Record<string, string> }).capabilities;\n } catch (e) {\n throw fail('no bsvalias document (or CORS blocked it; resolve on your server and pass resolvePaymail)', e);\n }\n const fill = (u: string) => u.replace('{alias}', encodeURIComponent(alias)).replace('{domain.tld}', domain);\n const dest = caps?.['2a40af698840'];\n if (!dest) throw fail('it does not offer P2P payment destinations');\n let body: { outputs?: { script: string; satoshis: number }[]; reference?: string };\n try {\n const r = await f(fill(dest), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ satoshis: sats }) });\n if (!r.ok) throw new Error(`status ${r.status}`);\n body = await r.json();\n } catch (e) {\n throw fail((e as Error).message, e);\n }\n if (!body.outputs?.length) throw fail('no outputs returned');\n const receive = caps['5f1323cddf31'];\n return {\n outputs: body.outputs.map((o) => ({ script: o.script, satoshis: o.satoshis })),\n reference: body.reference,\n submit: receive\n ? async (hex: string) => {\n await f(fill(receive), {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ hex, reference: body.reference, metadata: {} }),\n });\n }\n : undefined,\n };\n}\n\nasync function runAction(wallet: WalletInterface, description: string, outputs: PayOutput[], labels?: string[]) {\n try {\n return await wallet.createAction({\n description: describe(description),\n outputs: outputs.map((o) => ({ lockingScript: o.script, satoshis: o.satoshis, outputDescription: describe(o.description ?? 'payment') })),\n labels: labels?.length ? labels : undefined,\n options: { randomizeOutputs: false },\n });\n } catch (e) {\n const msg = (e as Error)?.message ?? String(e);\n const rejected = /denied|reject|cancel|abort|user/i.test(msg) || (e as { code?: unknown })?.code === 4001;\n // Not retried: a lost reply could mean the wallet already broadcast it.\n throw new PaymentError(rejected ? 'WALLET_REJECTED' : 'WALLET_ERROR', rejected ? 'The payment was declined in the wallet.' : `The wallet could not make the payment: ${msg}`, !rejected, e);\n }\n}\n\n/** Pay a paymail, address or identity key. One wallet prompt; never retried. */\nexport async function pay(source: PaySource, req: PayRequest, o: PayOptions = {}): Promise<PayResult> {\n const to = (req.to ?? '').trim();\n if (!to) throw new PaymentError('BAD_REQUEST', 'Give a recipient (to)');\n if (req.memo && req.memo.length > 1000) throw new PaymentError('BAD_REQUEST', 'memo is too long');\n const sats = await amountInSats(req, o);\n if (isLegacy(source)) return source.pay({ ...req, to, sats });\n\n let outputs: PayOutput[];\n let remittance: Brc29Remittance | undefined;\n let submit: ((hex: string) => Promise<void>) | undefined;\n if (isIdentityKeyHex(to)) {\n const derivationPrefix = randomB64(8);\n const derivationSuffix = randomB64(8);\n let pk: string;\n let me: string;\n try {\n ({ publicKey: pk } = await source.getPublicKey({ protocolID: BRC29_PROTOCOL, keyID: `${derivationPrefix} ${derivationSuffix}`, counterparty: to.toLowerCase() }));\n ({ publicKey: me } = await source.getPublicKey({ identityKey: true }));\n } catch (e) {\n throw new PaymentError('WALLET_ERROR', `The wallet could not derive the payment key: ${(e as Error).message}`, false, e);\n }\n outputs = [{ script: new P2PKH().lock(PublicKey.fromString(pk).toHash() as number[]).toHex(), satoshis: sats, description: 'payment' }];\n remittance = { senderIdentityKey: me, derivationPrefix, derivationSuffix, outputIndex: 0 };\n } else if (isPaymail(to)) {\n const r = await (o.resolvePaymail ?? ((p, s) => resolvePaymailP2P(p, s, o.fetch ?? fetch)))(to, sats);\n outputs = r.outputs;\n submit = r.submit;\n } else {\n try {\n outputs = [{ script: new P2PKH().lock(to).toHex(), satoshis: sats, description: 'payment' }];\n } catch {\n throw new PaymentError('BAD_REQUEST', `Not a paymail, address or identity key: ${to}`);\n }\n }\n if (req.memo && req.memoOnChain) outputs = [...outputs, { script: opReturn(req.memo), satoshis: 0, description: 'memo' }];\n\n const r = await runAction(source, req.memo || `Pay ${clip(to, 30)}`, outputs, req.labels);\n if (!r.txid) throw new PaymentError('WALLET_ERROR', 'The wallet did not return a txid', true);\n const tx = r.tx ? Array.from(r.tx) : undefined;\n if (submit && tx) {\n // Tell the paymail host about the tx. Best effort: the payment itself is already made.\n await submit(Transaction.fromAtomicBEEF(tx).toHex()).catch(() => {});\n }\n return { txid: r.txid, tx, sats, outputs, remittance };\n}\n\n/** pay() labelled as a tip. */\nexport function tip(source: PaySource, req: PayRequest, o: PayOptions = {}): Promise<PayResult> {\n return pay(source, { memo: 'Tip', ...req, labels: [...(req.labels ?? []), 'tip'] }, o);\n}\n\nexport interface TokenQuote {\n /** Outputs to fund, as your server priced them (e.g. payment to the seller + fee). */\n outputs: PayOutput[];\n description?: string;\n /** Called with the result after the wallet signs; e.g. POST txid + tx to your server for verifyPayment. */\n onPaid?: (r: PayResult) => Promise<void> | void;\n}\n\nexport interface BuyTokenRequest {\n token: string;\n amount: number | string;\n /** The hook point: return the outputs for this purchase (usually from your server). */\n quote: (req: { token: string; amount: number | string }) => Promise<TokenQuote>;\n labels?: string[];\n}\n\n/** Buy a token: your `quote` hook prices it, the wallet pays once, `onPaid` delivers it. */\nexport async function buyToken(source: PaySource, req: BuyTokenRequest): Promise<PayResult> {\n if (isLegacy(source)) throw new PaymentError('NOT_SUPPORTED', 'This wallet cannot buy tokens. Use a BRC-100 wallet such as bWalletX.');\n const q = await req.quote({ token: req.token, amount: req.amount });\n if (!q?.outputs?.length) throw new PaymentError('BAD_REQUEST', 'The quote returned no outputs');\n const sats = q.outputs.reduce((n, x) => n + x.satoshis, 0);\n const r = await runAction(source, q.description ?? `Buy ${clip(req.token, 30)}`, q.outputs, [...(req.labels ?? []), 'token-buy']);\n if (!r.txid) throw new PaymentError('WALLET_ERROR', 'The wallet did not return a txid', true);\n const res: PayResult = { txid: r.txid, tx: r.tx ? Array.from(r.tx) : undefined, sats, outputs: q.outputs };\n await q.onPaid?.(res);\n return res;\n}\n","/**\n * Site theme for the wallet. A site may ask bWalletX to wear one of the themes the wallet already bundles\n * (e.g. 'npgx') while the site is connected and is the active tab; the wallet puts the user's own theme back on\n * any other site or when this declaration is removed. Only an id is declared: the wallet decides whether this\n * origin may wear it (brand editions are locked to their own sites) and never loads colours, CSS or URLs from here.\n */\nexport const WALLET_THEME_META = 'bwalletx-theme';\n\nconst ID = /^[a-z0-9-]{1,32}$/;\n\n/** Declare (or with null, withdraw) the theme the wallet should wear for this page. No-op outside a browser. */\nexport function declareWalletTheme(theme: string | null | undefined): void {\n if (typeof document === 'undefined') return;\n const id = (theme ?? '').trim().toLowerCase();\n let meta = document.head.querySelector<HTMLMetaElement>(`meta[name=\"${WALLET_THEME_META}\"]`);\n if (!id || !ID.test(id)) {\n meta?.remove();\n return;\n }\n if (!meta) {\n meta = document.createElement('meta');\n meta.name = WALLET_THEME_META;\n document.head.appendChild(meta);\n }\n if (meta.content !== id) meta.content = id;\n}\n","/**\n * What BwalletXProvider.connect() does, without React: choose a wallet (bWalletX straight away when it is here; the\n * chooser otherwise, or when the user asked for \"Other wallets\"), open it, and sign in with ONE wallet sheet.\n */\nimport { rememberIdentity, rememberedIdentity } from './account.js';\nimport { signInAndVerify, type Challenge } from './signin.js';\nimport type { ConnectedWallet, Substrate, WalletOption } from './wallets.js';\nimport { attemptFor, decideWallet, outcomeOf, reportAttempt, substrateOf, walletTypeOf, type AttemptHook, type ChoiceReason } from './choice.js';\n\nexport interface ConnectFlowOptions {\n /** The wallets found (discoverWallets). */\n list: WalletOption[];\n /** Connect this option without asking. */\n walletId?: string;\n /** false: never show the chooser (bWalletX, else the phone; never another wallet unasked). Default true. */\n picker?: boolean;\n /** \"Other wallets\": show the chooser even when bWalletX is here. */\n chooser?: boolean;\n /**\n * bWalletX first (0.5.6, default true): bWalletX in this browser is connected with no chooser; without it the\n * chooser opens (\"Get bWalletX\" first) and no other wallet is connected unless picked. false: the 0.5.5 rule.\n */\n fastPath?: boolean;\n /** Show the chooser; resolves null when closed. chooseWallet (modal.ts) or your own UI. */\n pick: (list: WalletOption[]) => Promise<WalletOption | null>;\n /** With verifyUrl: sign in after connecting. */\n challengeUrl?: string;\n verifyUrl?: string;\n challengeNeedsIdentityKey?: boolean;\n /** POST JSON; throws with `status` on a non-2xx answer. */\n post: (url: string, body: unknown) => Promise<Record<string, unknown>>;\n /** Called once per attempt with { walletType, substrate, outcome, ms }. No keys, addresses or identity data. */\n onAttempt?: AttemptHook;\n}\n\nexport interface ConnectFlowResult {\n connected: ConnectedWallet;\n identityKey: string | null;\n handle: string | null;\n paymail: string | null;\n signedIn: boolean;\n /** 'bwalletx' for bWalletX, else the wallet's rdns / option id. */\n walletType: string;\n substrate: Substrate;\n /** Why this wallet: 'bwalletx' (fast path), 'chooser' (picked), 'requested' (walletId), … */\n reason: ChoiceReason;\n}\n\n/** Resolves null when the user closed the chooser. Throws NOT_FOUND when there is nothing to connect. */\nexport async function runConnect(o: ConnectFlowOptions): Promise<ConnectFlowResult | null> {\n const started = Date.now();\n const { list } = o;\n let choice: WalletOption | null = null;\n let reason: ChoiceReason = 'none';\n try {\n const d = decideWallet(list, { walletId: o.walletId, chooser: o.chooser, picker: o.picker, fastPath: o.fastPath });\n choice = d.option;\n reason = d.reason;\n if (!choice && d.reason === 'chooser') choice = await o.pick(list);\n if (!choice) {\n if (d.reason === 'none' || !list.length) throw Object.assign(new Error('No wallet found. Get bWalletX at bwalletx.com.'), { code: 'NOT_FOUND' });\n reportAttempt(o.onAttempt, attemptFor(null, 'cancelled', started));\n return null;\n }\n const signIn = !!(o.challengeUrl && o.verifyUrl) && choice.kind !== 'handcash';\n // Signing in: open the wallet WITHOUT asking for the key; the key comes with the signature, in one sheet.\n const cw = await choice.connect(signIn ? { identity: false } : undefined);\n let handle = cw.handle ?? null;\n let paymail = cw.paymail ?? null;\n let identityKey = cw.identityKey;\n let signedIn = false;\n if (cw.wallet && signIn) {\n const { result, verified } = await signInAndVerify({\n wallet: cw.wallet,\n identityKey: cw.identityKey ?? undefined,\n // The key that signed in here last time: a repeat sign-in is then the signature alone.\n identityKeyHint: rememberedIdentity(),\n challengeNeedsIdentityKey: o.challengeNeedsIdentityKey,\n challenge: (key) => o.post(o.challengeUrl!, key ? { identityKey: key } : {}) as unknown as Promise<Challenge>,\n verify: (r) => o.post(o.verifyUrl!, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature }),\n });\n rememberIdentity(result.identityKey);\n identityKey = result.identityKey;\n handle = (verified.handle as string | undefined) ?? handle;\n paymail = (verified.paymail as string | undefined) ?? paymail;\n signedIn = true;\n } else if (cw.wallet && !identityKey) {\n identityKey = (await cw.wallet.getPublicKey({ identityKey: true })).publicKey;\n }\n reportAttempt(o.onAttempt, attemptFor(choice, signedIn ? 'signed-in' : 'connected', started));\n return { connected: cw, identityKey, handle, paymail, signedIn, walletType: walletTypeOf(choice), substrate: substrateOf(choice), reason };\n } catch (e) {\n reportAttempt(o.onAttempt, attemptFor(choice, outcomeOf(e), started));\n throw e;\n }\n}\n"],"mappings":"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","names":["r"]}