@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
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.ts"],"sourcesContent":["/**\n * React wrapper for <bwalletx-signin>. React is an optional peer dependency; the button itself is\n * the web component, so markup and behaviour are identical everywhere.\n *\n * import { BwalletxSignin } from '@bwalletx/connect/react';\n * <BwalletxSignin challengeUrl=\"/auth/bwalletx/challenge\" verifyUrl=\"/auth/bwalletx/verify\"\n * onSignedIn={(d) => router.refresh()} />\n */\nimport {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport type { WalletInterface } from '@bsv/sdk';\nimport { clearPairing } from './pair.js';\nimport { declareWalletTheme } from './theme.js';\nimport { atStage, signInWithBwalletX, type Challenge } from './signin.js';\nimport { pairingModal } from './modal.js';\nimport { pickWallet } from './modal.js';\nimport { discoverWallets, type ConnectedWallet, type WalletOption } from './wallets.js';\nimport { PaymentError, buyToken, pay, tip, type BuyTokenRequest, type PayOptions, type PayRequest, type PayResult } from './pay.js';\nimport { defineBwalletxSignin, type SigninBrand, type SigninLabel, type SigninSize, type SigninTheme } from './element.js';\n\nexport interface SignedInDetail {\n identityKey: string;\n method: string;\n nonce: string;\n signature: number[];\n response: unknown;\n}\n\nexport interface BwalletxSigninProps {\n size?: SigninSize;\n theme?: SigninTheme;\n label?: SigninLabel;\n brand?: SigninBrand;\n /** true/undefined = default, false = hidden, string = custom text. */\n subtitle?: boolean | string;\n fullWidth?: boolean;\n loading?: boolean;\n disabled?: boolean;\n challengeUrl?: string;\n verifyUrl?: string;\n getUrl?: string;\n webUrl?: string;\n href?: string;\n authorizeUrl?: string;\n clientId?: string;\n redirectUri?: string;\n scope?: string;\n discoverMs?: number;\n 'aria-label'?: string;\n className?: string;\n style?: CSSProperties;\n id?: string;\n /** Every click. Call e.preventDefault() to skip the built-in flow. */\n onClick?: (e: CustomEvent<{ element: HTMLElement }>) => void;\n onSignedIn?: (d: SignedInDetail) => void;\n onNotFound?: () => void;\n onError?: (error: unknown) => void;\n}\n\nexport function BwalletxSignin(props: BwalletxSigninProps): ReactElement {\n const ref = useRef<HTMLElement>(null);\n const cb = useRef(props);\n cb.current = props;\n\n useEffect(() => {\n defineBwalletxSignin();\n const el = ref.current;\n if (!el) return;\n const on: [string, EventListener][] = [\n ['bwalletx-signin', (e) => cb.current.onClick?.(e as CustomEvent<{ element: HTMLElement }>)],\n ['bwalletx-signed-in', (e) => cb.current.onSignedIn?.((e as CustomEvent<SignedInDetail>).detail)],\n ['bwalletx-not-found', () => cb.current.onNotFound?.()],\n ['bwalletx-error', (e) => cb.current.onError?.((e as CustomEvent<{ error: unknown }>).detail.error)],\n ];\n for (const [t, f] of on) el.addEventListener(t, f);\n return () => { for (const [t, f] of on) el.removeEventListener(t, f); };\n }, []);\n\n const p = props;\n const flag = (v: boolean | undefined) => (v ? '' : undefined);\n return createElement('bwalletx-signin', {\n ref,\n id: p.id,\n class: p.className,\n style: p.style,\n size: p.size,\n theme: p.theme,\n label: p.label,\n brand: p.brand,\n subtitle: p.subtitle === false ? 'off' : typeof p.subtitle === 'string' ? p.subtitle : undefined,\n 'full-width': flag(p.fullWidth),\n loading: flag(p.loading),\n disabled: flag(p.disabled),\n 'challenge-url': p.challengeUrl,\n 'verify-url': p.verifyUrl,\n 'get-url': p.getUrl,\n 'web-url': p.webUrl,\n href: p.href,\n 'authorize-url': p.authorizeUrl,\n 'client-id': p.clientId,\n 'redirect-uri': p.redirectUri,\n scope: p.scope,\n 'discover-ms': p.discoverMs,\n 'aria-label': p['aria-label'],\n });\n}\n\nexport default BwalletxSignin;\n\nexport type BwalletXStatus = 'idle' | 'restoring' | 'connecting' | 'connected' | 'error';\n\nexport interface BwalletXState {\n status: BwalletXStatus;\n identityKey: string | null;\n handle: string | null;\n paymail: string | null;\n /** The BRC-100 wallet. null until connect() (a session restored from the cookie has no wallet yet). */\n wallet: WalletInterface | null;\n /** Which option connected (e.g. 'com.bwalletx.extension', 'bwalletx-pairing', 'handcash'). */\n walletId: string | null;\n /** True while the transitional HandCash adapter is in use. */\n transitional: boolean;\n /** The server session (verifyUrl/sessionUrl) is in place. */\n signedIn: boolean;\n error: Error | null;\n}\n\nexport interface BwalletXContextValue extends BwalletXState {\n /** Find wallets, show the picker if there is a choice, connect, and sign in if verifyUrl is set. */\n connect(opts?: { walletId?: string }): Promise<ConnectedWallet | null>;\n disconnect(): Promise<void>;\n /** The wallets found on the last connect() (or listWallets()). */\n wallets: WalletOption[];\n listWallets(): Promise<WalletOption[]>;\n pay(req: PayRequest): Promise<PayResult>;\n tip(req: PayRequest): Promise<PayResult>;\n buyToken(req: BuyTokenRequest): Promise<PayResult>;\n}\n\nexport interface BwalletXProviderProps {\n children?: ReactNode;\n /** POST { identityKey } → challenge. With verifyUrl, connect() also signs in. */\n challengeUrl?: string;\n /** POST { identityKey, nonce, signature } → { identityKey, handle?, paymail? } and sets the cookie. */\n verifyUrl?: string;\n /** POST to clear the cookie on disconnect(). */\n logoutUrl?: string;\n /** GET → { identityKey, handle?, paymail? } to restore a session on load. */\n sessionUrl?: string;\n /** Offer phone pairing with the built-in QR modal. Default true. */\n pairing?: boolean;\n /** Show the built-in picker when more than one wallet is available. Default true. */\n picker?: boolean;\n /** Extra options, e.g. [handcashAdapter({ login })]. */\n extraWallets?: WalletOption[];\n /** Probe desktop wallets on localhost. Default false. */\n probeLocal?: boolean;\n /**\n * A theme the wallet already bundles (e.g. 'npgx') for bWalletX to wear while this site is connected and is the\n * active tab. Declared only while connecting or connected; withdrawn on disconnect. See declareWalletTheme.\n */\n theme?: string;\n discoverMs?: number;\n payOptions?: PayOptions;\n onError?: (e: Error) => void;\n}\n\nconst empty: BwalletXState = {\n status: 'idle', identityKey: null, handle: null, paymail: null, wallet: null, walletId: null, transitional: false, signedIn: false, error: null,\n};\n\nconst Ctx = createContext<BwalletXContextValue | null>(null);\n\nasync function postJson(url: string, body: unknown): Promise<Record<string, unknown>> {\n const r = await fetch(url, { method: 'POST', credentials: 'same-origin', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });\n const j = (await r.json().catch(() => ({}))) as Record<string, unknown>;\n if (!r.ok) throw Object.assign(new Error(String(j.error ?? `${url} answered ${r.status}`)), { status: r.status, code: j.code });\n return j;\n}\n\n/** Holds the connected wallet and session for the tree. Renders nothing itself; SSR-safe. */\nexport function BwalletXProvider(p: BwalletXProviderProps): ReactElement {\n const [state, setState] = useState<BwalletXState>(empty);\n const [wallets, setWallets] = useState<WalletOption[]>([]);\n const props = useRef(p);\n props.current = p;\n const payer = useRef<ConnectedWallet['payer']>(null);\n\n const fail = useCallback((e: unknown) => {\n const err = e instanceof Error ? e : new Error(String(e));\n setState((s) => ({ ...s, status: 'error', error: err }));\n props.current.onError?.(err);\n return err;\n }, []);\n\n const themeOn = !!p.theme && (state.status === 'connecting' || state.status === 'connected');\n useEffect(() => {\n if (!themeOn) return;\n declareWalletTheme(p.theme);\n return () => declareWalletTheme(null);\n }, [themeOn, p.theme]);\n\n useEffect(() => {\n const url = p.sessionUrl;\n if (!url) return;\n let live = true;\n setState((s) => ({ ...s, status: 'restoring' }));\n fetch(url, { credentials: 'same-origin' })\n .then((r) => r.json())\n .then((j: { identityKey?: string | null; handle?: string; paymail?: string }) => {\n if (!live) return;\n setState((s) =>\n j.identityKey\n ? { ...s, status: 'connected', identityKey: j.identityKey, handle: j.handle ?? null, paymail: j.paymail ?? null, signedIn: true }\n : { ...s, status: s.status === 'restoring' ? 'idle' : s.status },\n );\n })\n .catch(() => live && setState((s) => ({ ...s, status: s.status === 'restoring' ? 'idle' : s.status })));\n return () => {\n live = false;\n };\n }, [p.sessionUrl]);\n\n const listWallets = useCallback(async () => {\n const c = props.current;\n const list = await discoverWallets({\n ms: c.discoverMs,\n probeLocal: c.probeLocal,\n extra: c.extraWallets,\n pairing: c.pairing === false ? undefined : () => pairingModal(),\n });\n setWallets(list);\n return list;\n }, []);\n\n const connect = useCallback(\n async (opts: { walletId?: string } = {}) => {\n const c = props.current;\n setState((s) => ({ ...s, status: 'connecting', error: null }));\n try {\n const list = await listWallets();\n let choice: WalletOption | null | undefined = opts.walletId ? list.find((w) => w.id === opts.walletId) : undefined;\n if (!choice) {\n const direct = list.filter((w) => w.kind !== 'pairing');\n choice = c.picker !== false && list.length > 1 ? await pickWallet(list) : (direct[0] ?? list[0] ?? null);\n }\n if (!choice) {\n if (!list.length) throw Object.assign(new Error('No wallet found. Get bWalletX at bwalletx.com.'), { code: 'NOT_FOUND' });\n setState((s) => ({ ...s, status: s.identityKey ? 'connected' : 'idle' }));\n return null;\n }\n const cw = await choice.connect();\n let { handle = null, paymail = null } = cw as { handle?: string | null; paymail?: string | null };\n let signedIn = false;\n if (cw.wallet && c.challengeUrl && c.verifyUrl) {\n const r = await signInWithBwalletX({\n wallet: cw.wallet,\n challenge: (identityKey) => postJson(c.challengeUrl!, { identityKey }) as unknown as Promise<Challenge>,\n });\n const v = await atStage('verify', () => postJson(c.verifyUrl!, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature }));\n handle = (v.handle as string | undefined) ?? handle;\n paymail = (v.paymail as string | undefined) ?? paymail;\n signedIn = true;\n }\n payer.current = cw.payer;\n setState({\n status: 'connected',\n identityKey: cw.identityKey,\n handle,\n paymail,\n wallet: cw.wallet,\n walletId: cw.optionId,\n transitional: !!cw.transitional,\n signedIn,\n error: null,\n });\n return cw;\n } catch (e) {\n if ((e as Error)?.message === 'cancelled') {\n setState((s) => ({ ...s, status: s.identityKey ? 'connected' : 'idle' }));\n return null;\n }\n throw fail(e);\n }\n },\n [fail, listWallets],\n );\n\n const disconnect = useCallback(async () => {\n const c = props.current;\n if (c.logoutUrl) await postJson(c.logoutUrl, {}).catch(() => {});\n if (state.walletId === 'bwalletx-pairing') await clearPairing();\n payer.current = null;\n setState(empty);\n }, [state.walletId]);\n\n const need = () => {\n if (!payer.current) throw new PaymentError('NOT_SUPPORTED', 'Connect a wallet first (connect()).');\n return payer.current;\n };\n\n const value = useMemo<BwalletXContextValue>(\n () => ({\n ...state,\n wallets,\n connect,\n disconnect,\n listWallets,\n pay: (req) => pay(need(), req, props.current.payOptions),\n tip: (req) => tip(need(), req, props.current.payOptions),\n buyToken: (req) => buyToken(need(), req),\n }),\n [state, wallets, connect, disconnect, listWallets],\n );\n\n return createElement(Ctx.Provider, { value }, p.children);\n}\n\n/** The wallet + session state. Must be inside <BwalletXProvider>. */\nexport function useBwalletX(): BwalletXContextValue {\n const v = useContext(Ctx);\n if (!v) throw new Error('useBwalletX() must be used inside <BwalletXProvider>');\n return v;\n}\n\nexport { handcashAdapter, brc100Option, pairingOption, discoverWallets } from './wallets.js';\nexport type { WalletOption, ConnectedWallet, HandCashAdapterOptions } from './wallets.js';\nexport { PaymentError } from './pay.js';\nexport type { PayRequest, PayResult, BuyTokenRequest, PayOptions } from './pay.js';\n/** Alias with the casing used by <BwalletxSignin>. */\nexport const BwalletxProvider = BwalletXProvider;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/react.ts"],"sourcesContent":["/**\n * React wrapper for <bwalletx-signin>. React is an optional peer dependency; the button itself is\n * the web component, so markup and behaviour are identical everywhere.\n *\n * import { BwalletxSignin } from '@bwalletx/connect/react';\n * <BwalletxSignin challengeUrl=\"/auth/bwalletx/challenge\" verifyUrl=\"/auth/bwalletx/verify\"\n * onSignedIn={(d) => router.refresh()} />\n */\nimport {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport type { WalletInterface } from '@bsv/sdk';\nimport { clearPairing } from './pair.js';\nimport { declareWalletTheme } from './theme.js';\nimport { runConnect } from './connect-flow.js';\nimport { chooseWallet, pairingModal, pickWallet } from './modal.js';\nimport type { AttemptHook } from './choice.js';\nimport { discoverWallets, type ConnectedWallet, type WalletOption } from './wallets.js';\nimport { accountLabel, avatarUrlFor, lookupPaymail, signInErrorText } from './account.js';\nimport { PaymentError, buyToken, pay, tip, type BuyTokenRequest, type PayOptions, type PayRequest, type PayResult } from './pay.js';\nimport { defineBwalletxSignin, type SigninBrand, type SigninLabel, type SigninMode, type SigninSize, type SigninState, type SigninTheme } from './element.js';\n\nexport interface SignedInDetail {\n identityKey: string;\n method: string;\n nonce: string;\n signature: number[];\n response: unknown;\n}\n\nexport interface BwalletxSigninProps {\n size?: SigninSize;\n theme?: SigninTheme;\n label?: SigninLabel;\n brand?: SigninBrand;\n /** true/undefined = default, false = hidden, string = custom text. */\n subtitle?: boolean | string;\n fullWidth?: boolean;\n loading?: boolean;\n disabled?: boolean;\n challengeUrl?: string;\n verifyUrl?: string;\n getUrl?: string;\n webUrl?: string;\n href?: string;\n authorizeUrl?: string;\n clientId?: string;\n redirectUri?: string;\n scope?: string;\n discoverMs?: number;\n /** \"Other wallets\" link under the button: 'auto' (default: lg/md), 'on' or 'off'. */\n otherWallets?: 'auto' | 'on' | 'off';\n /** Without a provider: GET → { identityKey, handle?, paymail? } restores the signed-in badge on load. */\n sessionUrl?: string;\n /** Without a provider: POST on Disconnect. */\n logoutUrl?: string;\n /** Paymail lookup for the badge; 'off' to skip. */\n lookupUrl?: string;\n /**\n * Show the signed-in badge and the pending / error states (default true). Inside <BwalletXProvider> the button\n * follows the provider: click → connect() (one wallet sheet), badge when signed in, Disconnect → disconnect()\n * (logoutUrl). Set false for the pre-0.5.5 plain button.\n */\n stateful?: boolean;\n 'aria-label'?: string;\n className?: string;\n style?: CSSProperties;\n id?: string;\n /** Every click. Call e.preventDefault() to skip the built-in flow. */\n onClick?: (e: CustomEvent<{ element: HTMLElement }>) => void;\n onSignedIn?: (d: SignedInDetail) => void;\n onNotFound?: () => void;\n onError?: (error: unknown) => void;\n /** After Disconnect in the badge menu. */\n onSignedOut?: () => void;\n}\n\n/** The element state for the provider's state. */\nexport function signinStateFor(v: Pick<BwalletXState, 'status' | 'identityKey'>): SigninState {\n if (v.status === 'restoring') return 'restoring';\n if (v.status === 'connecting') return 'pending';\n if (v.status === 'error') return 'error';\n if (v.status === 'connected' && v.identityKey) return 'signed-in';\n return 'signed-out';\n}\n\nexport function BwalletxSignin(props: BwalletxSigninProps): ReactElement {\n const ref = useRef<HTMLElement>(null);\n const cb = useRef(props);\n cb.current = props;\n const ctx = useContext(Ctx);\n const ctxRef = useRef(ctx);\n ctxRef.current = ctx;\n const driven = !!ctx && props.stateful !== false;\n\n useEffect(() => {\n defineBwalletxSignin();\n const el = ref.current;\n if (!el) return;\n const on: [string, EventListener][] = [\n [\n 'bwalletx-signin',\n (e) => {\n cb.current.onClick?.(e as CustomEvent<{ element: HTMLElement }>);\n // Inside a provider the click is the provider's connect(): one wallet sheet, and this button follows it.\n // \"Other wallets\" arrives here too, as mode 'chooser'.\n const c = ctxRef.current;\n if (!e.defaultPrevented && c && cb.current.stateful !== false) {\n e.preventDefault();\n const mode = (e as CustomEvent<{ mode?: SigninMode }>).detail?.mode;\n c.connect(mode === 'chooser' ? { chooser: true } : {}).catch(() => undefined);\n }\n },\n ],\n ['bwalletx-signed-in', (e) => cb.current.onSignedIn?.((e as CustomEvent<SignedInDetail>).detail)],\n ['bwalletx-not-found', () => cb.current.onNotFound?.()],\n ['bwalletx-error', (e) => cb.current.onError?.((e as CustomEvent<{ error: unknown }>).detail.error)],\n [\n 'bwalletx-disconnect',\n (e) => {\n const c = ctxRef.current;\n if (c && cb.current.stateful !== false) {\n e.preventDefault();\n void c.disconnect().then(() => cb.current.onSignedOut?.());\n }\n },\n ],\n ['bwalletx-signed-out', () => cb.current.onSignedOut?.()],\n ];\n for (const [t, f] of on) el.addEventListener(t, f);\n return () => { for (const [t, f] of on) el.removeEventListener(t, f); };\n }, []);\n\n const p = props;\n const flag = (v: boolean | undefined) => (v ? '' : undefined);\n const state = driven ? signinStateFor(ctx) : undefined;\n return createElement('bwalletx-signin', {\n ref,\n id: p.id,\n class: p.className,\n style: p.style,\n size: p.size,\n theme: p.theme,\n label: p.label,\n brand: p.brand,\n subtitle: p.subtitle === false ? 'off' : typeof p.subtitle === 'string' ? p.subtitle : undefined,\n 'full-width': flag(p.fullWidth),\n loading: flag(p.loading),\n disabled: flag(p.disabled),\n 'challenge-url': p.challengeUrl,\n 'verify-url': p.verifyUrl,\n 'session-url': driven ? undefined : p.sessionUrl,\n 'logout-url': driven ? undefined : p.logoutUrl,\n 'lookup-url': p.lookupUrl,\n stateless: p.stateful === false ? '' : undefined,\n state,\n 'identity-key': driven ? (ctx.identityKey ?? undefined) : undefined,\n paymail: driven ? (ctx.paymail ?? undefined) : undefined,\n handle: driven ? (ctx.handle ?? undefined) : undefined,\n 'avatar-url': driven ? (avatarUrlFor(ctx.identityKey) ?? undefined) : undefined,\n error: driven && ctx.status === 'error' ? signInErrorText(ctx.error) : undefined,\n 'get-url': p.getUrl,\n 'web-url': p.webUrl,\n href: p.href,\n 'authorize-url': p.authorizeUrl,\n 'client-id': p.clientId,\n 'redirect-uri': p.redirectUri,\n scope: p.scope,\n 'discover-ms': p.discoverMs,\n 'other-wallets': p.otherWallets,\n 'aria-label': p['aria-label'],\n });\n}\n\nexport default BwalletxSignin;\n\nexport type BwalletXStatus = 'idle' | 'restoring' | 'connecting' | 'connected' | 'error';\n\nexport interface BwalletXState {\n status: BwalletXStatus;\n identityKey: string | null;\n handle: string | null;\n paymail: string | null;\n /** The BRC-100 wallet. null until connect() (a session restored from the cookie has no wallet yet). */\n wallet: WalletInterface | null;\n /** Which option connected (e.g. 'com.bwalletx.extension', 'bwalletx-pairing', 'handcash'). */\n walletId: string | null;\n /** 'bwalletx' for any bWalletX, else the wallet's rdns / option id (0.5.6). null until connect(). */\n walletType: string | null;\n /** True while the transitional HandCash adapter is in use. */\n transitional: boolean;\n /** The server session (verifyUrl/sessionUrl) is in place. */\n signedIn: boolean;\n error: Error | null;\n}\n\nexport interface BwalletXContextValue extends BwalletXState {\n /**\n * Find wallets, connect, and sign in if verifyUrl is set. bWalletX in this browser is used straight away (0.5.6);\n * without it the chooser opens with \"Get bWalletX\" first. `chooser: true` is the \"Other wallets\" link: the chooser\n * even when bWalletX is here.\n */\n connect(opts?: { walletId?: string; chooser?: boolean }): Promise<ConnectedWallet | null>;\n disconnect(): Promise<void>;\n /** The wallets found on the last connect() (or listWallets()). */\n wallets: WalletOption[];\n listWallets(): Promise<WalletOption[]>;\n pay(req: PayRequest): Promise<PayResult>;\n tip(req: PayRequest): Promise<PayResult>;\n buyToken(req: BuyTokenRequest): Promise<PayResult>;\n}\n\nexport interface BwalletXProviderProps {\n children?: ReactNode;\n /** POST {} (or { identityKey }) → challenge. With verifyUrl, connect() also signs in, in ONE wallet sheet. */\n challengeUrl?: string;\n /** POST { identityKey, nonce, signature } → { identityKey, handle?, paymail? } and sets the cookie. */\n verifyUrl?: string;\n /** POST to clear the cookie on disconnect(). */\n logoutUrl?: string;\n /** GET → { identityKey, handle?, paymail? } to restore a session on load. */\n sessionUrl?: string;\n /** Offer phone pairing with the built-in QR modal. Default true. */\n pairing?: boolean;\n /**\n * Show the built-in picker when there is a real choice. Default true. One wallet found in this browser (and no\n * app-supplied option such as HandCash) is used straight away, with no picker; phone pairing is then not offered.\n */\n picker?: boolean;\n /**\n * Your challenge route is older than 0.5.5 and needs the identity key first (two wallet sheets). Normally not\n * needed: such a server is detected and retried. Upgrade the server to @bwalletx/connect 0.5.5 for one sheet.\n */\n challengeNeedsIdentityKey?: boolean;\n /** Paymail lookup by identity key for the signed-in badge (best effort). Default pay.bwallet.space; 'off' to skip. */\n lookupUrl?: string;\n /** Extra options, e.g. [handcashAdapter({ login })]. */\n extraWallets?: WalletOption[];\n /**\n * Probe desktop wallets on localhost while listing. Default false, and best left off (Chrome's Local Network Access\n * prompt): the chooser offers \"Desktop wallet\", which probes only when tapped.\n */\n probeLocal?: boolean;\n /**\n * bWalletX first (0.5.6, default true): with bWalletX in this browser connect() goes straight to its sheet, no\n * chooser; without it the chooser opens, \"Get bWalletX\" first, and another wallet is connected only when picked.\n * false: the 0.5.5 picker rule (one detected wallet of any brand was used without asking).\n */\n fastPath?: boolean;\n /** Called once per connect attempt with { walletType, substrate, outcome, ms }. No keys, addresses or identity data. */\n onAttempt?: AttemptHook;\n /**\n * A theme the wallet already bundles (e.g. 'npgx') for bWalletX to wear while this site is connected and is the\n * active tab. Declared only while connecting or connected; withdrawn on disconnect. See declareWalletTheme.\n */\n theme?: string;\n discoverMs?: number;\n payOptions?: PayOptions;\n onError?: (e: Error) => void;\n}\n\nconst empty: BwalletXState = {\n status: 'idle', identityKey: null, handle: null, paymail: null, wallet: null, walletId: null, walletType: null, transitional: false, signedIn: false, error: null,\n};\n\nconst Ctx = createContext<BwalletXContextValue | null>(null);\n\nasync function postJson(url: string, body: unknown): Promise<Record<string, unknown>> {\n const r = await fetch(url, { method: 'POST', credentials: 'same-origin', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });\n const j = (await r.json().catch(() => ({}))) as Record<string, unknown>;\n if (!r.ok) throw Object.assign(new Error(String(j.error ?? `${url} answered ${r.status}`)), { status: r.status, code: j.code });\n return j;\n}\n\n/** Holds the connected wallet and session for the tree. Renders nothing itself; SSR-safe. */\nexport function BwalletXProvider(p: BwalletXProviderProps): ReactElement {\n const [state, setState] = useState<BwalletXState>(empty);\n const [wallets, setWallets] = useState<WalletOption[]>([]);\n const props = useRef(p);\n props.current = p;\n const payer = useRef<ConnectedWallet['payer']>(null);\n\n const fail = useCallback((e: unknown) => {\n const err = e instanceof Error ? e : new Error(String(e));\n setState((s) => ({ ...s, status: 'error', error: err }));\n props.current.onError?.(err);\n return err;\n }, []);\n\n const themeOn = !!p.theme && (state.status === 'connecting' || state.status === 'connected');\n useEffect(() => {\n if (!themeOn) return;\n declareWalletTheme(p.theme);\n return () => declareWalletTheme(null);\n }, [themeOn, p.theme]);\n\n useEffect(() => {\n const url = p.sessionUrl;\n if (!url) return;\n let live = true;\n setState((s) => ({ ...s, status: 'restoring' }));\n fetch(url, { credentials: 'same-origin' })\n .then((r) => r.json())\n .then((j: { identityKey?: string | null; handle?: string; paymail?: string }) => {\n if (!live) return;\n setState((s) =>\n j.identityKey\n ? { ...s, status: 'connected', identityKey: j.identityKey, handle: j.handle ?? null, paymail: j.paymail ?? null, signedIn: true }\n : { ...s, status: s.status === 'restoring' ? 'idle' : s.status },\n );\n })\n .catch(() => live && setState((s) => ({ ...s, status: s.status === 'restoring' ? 'idle' : s.status })));\n return () => {\n live = false;\n };\n }, [p.sessionUrl]);\n\n const listWallets = useCallback(async () => {\n const c = props.current;\n const list = await discoverWallets({\n ms: c.discoverMs,\n probeLocal: c.probeLocal,\n extra: c.extraWallets,\n pairing: c.pairing === false ? undefined : () => pairingModal(),\n });\n setWallets(list);\n return list;\n }, []);\n\n const connect = useCallback(\n async (opts: { walletId?: string; chooser?: boolean } = {}) => {\n const c = props.current;\n setState((s) => ({ ...s, status: 'connecting', error: null }));\n try {\n const list = await listWallets();\n const fast = c.fastPath !== false;\n const r = await runConnect({\n list,\n walletId: opts.walletId,\n chooser: opts.chooser,\n picker: c.picker,\n fastPath: fast,\n pick: fast ? (l) => chooseWallet(l) : pickWallet,\n onAttempt: c.onAttempt,\n challengeUrl: c.challengeUrl,\n verifyUrl: c.verifyUrl,\n challengeNeedsIdentityKey: c.challengeNeedsIdentityKey,\n post: postJson,\n });\n if (!r) {\n setState((s) => ({ ...s, status: s.identityKey && s.signedIn ? 'connected' : 'idle' }));\n return null;\n }\n const cw = r.connected;\n payer.current = cw.payer;\n setState({\n status: 'connected',\n identityKey: r.identityKey,\n handle: r.handle,\n paymail: r.paymail,\n wallet: cw.wallet,\n walletId: cw.optionId,\n walletType: r.walletType,\n transitional: !!cw.transitional,\n signedIn: r.signedIn,\n error: null,\n });\n return { ...cw, identityKey: r.identityKey, handle: r.handle ?? undefined, paymail: r.paymail ?? undefined };\n } catch (e) {\n if ((e as Error)?.message === 'cancelled') {\n setState((s) => ({ ...s, status: s.identityKey && s.signedIn ? 'connected' : 'idle' }));\n return null;\n }\n throw fail(e);\n }\n },\n [fail, listWallets],\n );\n\n const disconnect = useCallback(async () => {\n const c = props.current;\n if (c.logoutUrl) await postJson(c.logoutUrl, {}).catch(() => {});\n if (state.walletId === 'bwalletx-pairing') await clearPairing();\n payer.current = null;\n setState(empty);\n }, [state.walletId]);\n\n // Best-effort paymail for the badge when the server did not send one (pay.bwallet.space lookup by key).\n useEffect(() => {\n const key = state.identityKey;\n if (!key || state.paymail || p.lookupUrl === 'off') return;\n let live = true;\n void lookupPaymail(key, p.lookupUrl).then((pm) => {\n if (live && pm) setState((s) => (s.identityKey === key && !s.paymail ? { ...s, paymail: pm } : s));\n });\n return () => {\n live = false;\n };\n }, [state.identityKey, state.paymail, p.lookupUrl]);\n\n const need = () => {\n if (!payer.current) throw new PaymentError('NOT_SUPPORTED', 'Connect a wallet first (connect()).');\n return payer.current;\n };\n\n const value = useMemo<BwalletXContextValue>(\n () => ({\n ...state,\n wallets,\n connect,\n disconnect,\n listWallets,\n pay: (req) => pay(need(), req, props.current.payOptions),\n tip: (req) => tip(need(), req, props.current.payOptions),\n buyToken: (req) => buyToken(need(), req),\n }),\n [state, wallets, connect, disconnect, listWallets],\n );\n\n return createElement(Ctx.Provider, { value }, p.children);\n}\n\n/** The wallet + session state. Must be inside <BwalletXProvider>. */\nexport function useBwalletX(): BwalletXContextValue {\n const v = useContext(Ctx);\n if (!v) throw new Error('useBwalletX() must be used inside <BwalletXProvider>');\n return v;\n}\n\nexport type BwalletXAccountStatus = 'restoring' | 'signed-out' | 'pending' | 'signed-in' | 'error';\n\nexport interface BwalletXAccount {\n status: BwalletXAccountStatus;\n identityKey: string | null;\n paymail: string | null;\n handle: string | null;\n /** $alice, a site handle, the paymail, or a short key: what the badge shows. */\n label: string | null;\n /** The bAvatar picture for the identity key (gold by default), or null when signed out. */\n avatarUrl: string | null;\n /** One plain line for the last error (\"Sign-in cancelled\" for a no in the wallet), or null. */\n error: string | null;\n /** Sign in (the provider's connect(): one wallet sheet). Never throws; failures show in `status`/`error`. */\n login(): Promise<void>;\n /** Sign out: the provider's disconnect() (posts logoutUrl). */\n logout(): Promise<void>;\n}\n\n/** The account view of a provider state (what useBwalletXAccount returns). */\nexport function accountFor(v: BwalletXContextValue): BwalletXAccount {\n const status = signinStateFor(v) as BwalletXAccountStatus;\n const signedIn = status === 'signed-in';\n return {\n status,\n identityKey: signedIn ? v.identityKey : null,\n paymail: signedIn ? v.paymail : null,\n handle: signedIn ? v.handle : null,\n label: signedIn ? accountLabel(v) : null,\n avatarUrl: signedIn ? avatarUrlFor(v.identityKey) : null,\n error: status === 'error' ? signInErrorText(v.error) : null,\n login: () => v.connect().then(() => undefined, () => undefined),\n logout: () => v.disconnect(),\n };\n}\n\n/** The signed-in account for your own UI: { status, identityKey, paymail, handle, avatarUrl, logout, … }. Inside <BwalletXProvider>. */\nexport function useBwalletXAccount(): BwalletXAccount {\n return accountFor(useBwalletX());\n}\n\nexport { handcashAdapter, brc100Option, pairingOption, discoverWallets, autoChoice } from './wallets.js';\nexport type { WalletOption, ConnectedWallet, HandCashAdapterOptions } from './wallets.js';\nexport { PaymentError } from './pay.js';\nexport type { PayRequest, PayResult, BuyTokenRequest, PayOptions } from './pay.js';\n/** Alias with the casing used by <BwalletxSignin>. */\nexport const BwalletxProvider = BwalletXProvider;\n"],"mappings":"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","names":[]}
package/dist/server.cjs CHANGED
@@ -787,12 +787,14 @@ function randomNonce2() {
787
787
  return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
788
788
  }
789
789
  async function createChallenge(opts) {
790
- if (!isIdentityKey(opts.identityKey)) throw new Error("identityKey must be a compressed public key (66 hex chars)");
790
+ const given = opts.identityKey !== void 0 && opts.identityKey !== null && opts.identityKey !== "";
791
+ if (given && !isIdentityKey(opts.identityKey)) throw new Error("identityKey must be a compressed public key (66 hex chars)");
792
+ const boundKey = given ? opts.identityKey.toLowerCase() : "";
791
793
  const origin = normalOrigin2(opts.origin);
792
794
  const nonce = randomNonce2();
793
795
  const expiresAt = Date.now() + CHALLENGE_LIFETIME_MS;
794
796
  const message = loginMessage(origin, nonce, expiresAt);
795
- await storeOrDefault(opts.store).put(nonce, { identityKey: opts.identityKey.toLowerCase(), origin, message, expiresAt });
797
+ await storeOrDefault(opts.store).put(nonce, { identityKey: boundKey, origin, message, expiresAt });
796
798
  return { nonce, message, protocolID: LOGIN_PROTOCOL, keyID: nonce, expiresAt };
797
799
  }
798
800
  async function verifySignIn(opts) {
@@ -808,7 +810,7 @@ async function verifySignIn(opts) {
808
810
  if (c.origin !== origin) return null;
809
811
  if (!isIdentityKey(opts.identityKey)) return null;
810
812
  const identityKey = opts.identityKey.toLowerCase();
811
- if (c.identityKey !== identityKey) return null;
813
+ if (c.identityKey && c.identityKey !== identityKey) return null;
812
814
  const sig = opts.signature;
813
815
  if (!Array.isArray(sig) || sig.length < 8 || sig.length > 80 || !sig.every((b) => Number.isInteger(b) && b >= 0 && b <= 255)) {
814
816
  return null;
@@ -1 +1 @@
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"]}
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 /** 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 * $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
@@ -316,6 +316,7 @@ declare function checkTokenHolding(require: AccessRequirement, addresses: string
316
316
  declare const LOGIN_PROTOCOL: WalletProtocol;
317
317
  declare const CHALLENGE_LIFETIME_MS: number;
318
318
  interface PendingChallenge {
319
+ /** The key the challenge was made for, or '' when none was given (verifySignIn then binds the claimed key). */
319
320
  identityKey: string;
320
321
  origin: string;
321
322
  message: string;
@@ -342,8 +343,12 @@ declare function loginMessage(origin: string, nonce: string, expiresAt: number):
342
343
  interface CreateChallengeOptions {
343
344
  /** YOUR site's origin from config, e.g. "https://example.com". Never take it from the request. */
344
345
  origin: string;
345
- /** The identity key the browser claims. Checked again at verify. */
346
- identityKey: unknown;
346
+ /**
347
+ * The identity key the browser claims, checked again at verify. Optional since 0.5.5: leave it out so the browser
348
+ * can ask the wallet for the key and the signature in ONE sheet; verifySignIn then binds the key the browser claims,
349
+ * and the signature only verifies for that key. A challenge made WITH a key still only verifies for that key.
350
+ */
351
+ identityKey?: unknown;
347
352
  /** Default: a process-wide in-memory store (dev only). */
348
353
  store?: NonceStore;
349
354
  }
@@ -354,7 +359,7 @@ interface ChallengeResponse {
354
359
  keyID: string;
355
360
  expiresAt: number;
356
361
  }
357
- /** Step 1. Throws if identityKey is not a valid compressed public key. Send the result to the browser. */
362
+ /** Step 1. Throws if an identityKey is given and is not a valid compressed public key. Send the result to the browser. */
358
363
  declare function createChallenge(opts: CreateChallengeOptions): Promise<ChallengeResponse>;
359
364
  interface VerifySignInOptions {
360
365
  /** YOUR site's origin from config. Must equal the origin the challenge was made for. */