@bwalletx/connect 0.2.0 → 0.4.1

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 (55) hide show
  1. package/BRAND.md +16 -0
  2. package/CHANGELOG.md +25 -0
  3. package/README.md +189 -4
  4. package/dist/bwalletx-connect.global.js +41 -3
  5. package/dist/bwalletx-connect.global.js.map +1 -1
  6. package/dist/bwalletx-shell.global.js +54 -0
  7. package/dist/bwalletx-shell.global.js.map +1 -0
  8. package/dist/chunk-7NAKOJGZ.js +442 -0
  9. package/dist/chunk-7NAKOJGZ.js.map +1 -0
  10. package/dist/chunk-FUQ46RJV.js +404 -0
  11. package/dist/chunk-FUQ46RJV.js.map +1 -0
  12. package/dist/chunk-OMEKHKMM.js +530 -0
  13. package/dist/chunk-OMEKHKMM.js.map +1 -0
  14. package/dist/index.cjs +425 -5
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +23 -62
  17. package/dist/index.d.ts +23 -62
  18. package/dist/index.js +35 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/manifest-Caj_Raqm.d.cts +81 -0
  21. package/dist/manifest-Caj_Raqm.d.ts +81 -0
  22. package/dist/next.cjs +415 -0
  23. package/dist/next.cjs.map +1 -0
  24. package/dist/next.d.cts +48 -0
  25. package/dist/next.d.ts +48 -0
  26. package/dist/next.js +107 -0
  27. package/dist/next.js.map +1 -0
  28. package/dist/react.cjs +586 -36
  29. package/dist/react.cjs.map +1 -1
  30. package/dist/react.d.cts +74 -28
  31. package/dist/react.d.ts +74 -28
  32. package/dist/react.js +175 -3
  33. package/dist/react.js.map +1 -1
  34. package/dist/server.cjs +339 -4
  35. package/dist/server.cjs.map +1 -1
  36. package/dist/server.d.cts +204 -2
  37. package/dist/server.d.ts +204 -2
  38. package/dist/server.js +36 -107
  39. package/dist/server.js.map +1 -1
  40. package/dist/shell-react.cjs +361 -0
  41. package/dist/shell-react.cjs.map +1 -0
  42. package/dist/shell-react.d.cts +58 -0
  43. package/dist/shell-react.d.ts +58 -0
  44. package/dist/shell-react.js +52 -0
  45. package/dist/shell-react.js.map +1 -0
  46. package/dist/shell.cjs +578 -0
  47. package/dist/shell.cjs.map +1 -0
  48. package/dist/shell.d.cts +132 -0
  49. package/dist/shell.d.ts +132 -0
  50. package/dist/shell.js +51 -0
  51. package/dist/shell.js.map +1 -0
  52. package/dist/wallets-VOKIKBbO.d.cts +250 -0
  53. package/dist/wallets-VOKIKBbO.d.ts +250 -0
  54. package/package.json +65 -14
  55. package/schema/bapp.schema.json +95 -0
package/BRAND.md CHANGED
@@ -59,6 +59,22 @@ Leave space around the button equal to half its height (at least 8px) free of ot
59
59
 
60
60
  Black on the gold gradient is above 9:1, and the subtitle stays above 4.5:1 (WCAG AA) at every point of the gradient.
61
61
 
62
+ ## bApp shell (bar, top bar, drawer)
63
+
64
+ The shell copies the bWalletX dock exactly. Don't restyle it per app; `theme.accent` in bapp.json is the only override.
65
+
66
+ | Token | Value |
67
+ |---|---|
68
+ | Bar | 88px, background `#0A0B0D`, 1px top hairline `#1C1C1E` |
69
+ | Slot icons | 20px, inactive `#F2F2F0`, active `#FFD24D` |
70
+ | Slot labels | 10px, `#98A2B3`; active gold and bold |
71
+ | (b) | 52px black disc, 2px gold ring, snug in the bar (no float gap) |
72
+ | Disabled slot | 35% opacity, not clickable, never hidden |
73
+ | Top bar | 28px strip + 56px row; round 36px tools, ring `#2A2A2C`, icons `#F5B800` |
74
+ | Order | Wallet · Exchange · (b) · Feed · Chat. Top left: ☰ (in the wallet) then the app icon; ✕ on the right in the wallet |
75
+
76
+ Wording on Feed likes: "fund", "back", "support". Never "invest", "returns" or "earn".
77
+
62
78
  ## Files
63
79
 
64
80
  `assets/` has the mark (`bwalletx-mark.svg`, `bwalletx-mark-circle.svg`), full buttons (`signin-gold.svg`, `signin-dark.svg`, `signin-light.svg`) and a no-JavaScript HTML/CSS version (`snippet.html`).
package/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ ## 0.4.1 (2026-10-12)
4
+
5
+ - React: `BwalletXProvider` and `BwalletxSignin` declare `ReactElement` return types, so they type-check as JSX under React 18 `@types/react`.
6
+ - First public release of the 0.4 line (0.4.0 was staged on npm but never released).
7
+
8
+ ## 0.4.0 (2026-10-12)
9
+
10
+ Everything from 0.3 still works the same way. All additions are BRC-100 first: bWalletX is the main target, but any BRC-100 wallet goes through the same interface.
11
+
12
+ - **React** (`@bwalletx/connect/react`): `<BwalletXProvider>` and `useBwalletX()` give you status, identityKey, handle and paymail, connect/disconnect, the BRC-100 `wallet`, and pay/tip/buyToken. Safe to server-render. `<BwalletxSignin>` is unchanged.
13
+ - **Next.js adapter** (`@bwalletx/connect/next`, new): `createBwalletXAuth()` gives you challenge, verify, logout and session route handlers, an HMAC-signed httpOnly session cookie, and `getSession()` for server components. The secret comes from `BWALLETX_SESSION_SECRET` (or `secret`). There is no default, so it throws if the secret is missing.
14
+ - **Nonce stores**: `redisStore` (Upstash, node-redis, ioredis) and `pgStore` (any `query` function), plus `pgNonceTableSql()`. `memoryStore` now warns when it runs in production.
15
+ - **Sessions**: `signSession`, `verifySession`, `serializeCookie` and `readCookie` (WebCrypto, so they work on Edge).
16
+ - **Payments**: `pay` (to a paymail, address or BRC-29 identity key; in sats or USD; optional OP_RETURN memo), `tip`, and a `buyToken` quote hook. Each makes one `createAction` and is never retried. Errors are `PaymentError` with a `code` and a `maybeSent` flag.
17
+ - **Server**: `verifyPayment` checks a txid, raw tx, BEEF or Atomic BEEF against the expected outputs, with optional WhatsOnChain or ARC status checks and SPV.
18
+ - **Account linking**: `linkIdentity`, `signInOrLink`, and `IdentityLinkConflictError` (the Next adapter turns it into a 409).
19
+ - **Wallet picker**: `discoverWallets` finds every BRC-100 wallet that announces itself, plus the in-app wallet, optional localhost desktop wallets and phone pairing. `pickWallet` shows the picker UI. `handcashAdapter` is a transitional slot that wraps an app's own HandCash login and imports no HandCash SDK.
20
+ - **Pairing QR modal**: `pairingModal()` and `qrSvg()`, using the bundled `qrcode-generator` (MIT).
21
+ - Tooling: ESLint (`pnpm lint`) and a CI workflow on pull requests.
22
+
23
+ ## 0.3.0
24
+
25
+ - bApp shell: the `bapp.json` manifest, 5-slot bar, top bar, drawer and in-wallet bridge.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @bwalletx/connect
2
2
 
3
- Add "Sign in with bWalletX" to a website.
3
+ Add "Sign in with bWalletX" to a website. Built on the open BRC-100 wallet interface, so every BRC-100 wallet works the same way, with bWalletX first.
4
4
 
5
5
  The user's wallet signs a one-time challenge from your server with a key derived from its identity key. Your server checks the signature and signs the user in as that identity key. You don't need an API key or an account with us, and nothing passes through bwalletx.com. The only service of ours involved is the encrypted pairing relay (`relay.bwallet.space`), and only when the user signs in from a phone or the web wallet.
6
6
 
@@ -14,6 +14,144 @@ pnpm add @bwalletx/connect @bsv/sdk
14
14
 
15
15
  `@bsv/sdk` (2.2 or later, 2.x or 3.x) is a peer dependency.
16
16
 
17
+ ## Next.js quick start (App Router)
18
+
19
+ Works with bWalletX and any other BRC-100 wallet. Set a session secret (32+ characters) in your environment. There is no default: the routes throw without it.
20
+
21
+ ```sh
22
+ # .env.local (never commit it)
23
+ BWALLETX_SESSION_SECRET= # e.g. the output of: openssl rand -base64 48
24
+ APP_ORIGIN=https://example.com
25
+ ```
26
+
27
+ ```ts
28
+ // lib/bwalletx.ts
29
+ import { createBwalletXAuth, pgStore, signInOrLink } from '@bwalletx/connect/next';
30
+ import { query } from './db'; // (sql, params) => Promise<{ rows }>
31
+
32
+ export const auth = createBwalletXAuth({
33
+ origin: process.env.APP_ORIGIN!,
34
+ store: pgStore({ query }), // or redisStore({ client: upstash })
35
+ // Optional: link the identity key to your user table.
36
+ onVerified: async (identityKey) => {
37
+ const r = await signInOrLink(identityKey, {
38
+ findUserByIdentityKey: async (k) => (await query('select user_id from wallet_links where identity_key = $1', [k])).rows[0]?.user_id ?? null,
39
+ attach: async (uid, k) => void (await query('insert into wallet_links (user_id, identity_key) values ($1, $2)', [uid, k])),
40
+ createUser: async () => (await query('insert into users default values returning id', [])).rows[0].id,
41
+ });
42
+ return { uid: r.userId ?? undefined };
43
+ },
44
+ });
45
+ ```
46
+
47
+ ```ts
48
+ // app/api/bwalletx/challenge/route.ts
49
+ import { auth } from '@/lib/bwalletx';
50
+ export const POST = auth.challenge;
51
+ // app/api/bwalletx/verify/route.ts → export const POST = auth.verify;
52
+ // app/api/bwalletx/logout/route.ts → export const POST = auth.logout;
53
+ // app/api/bwalletx/session/route.ts → export const GET = auth.session;
54
+ ```
55
+
56
+ ```tsx
57
+ // any server component or route handler
58
+ const session = await auth.getSession(); // { sub: identityKey, uid?, handle?, paymail?, exp } | null
59
+ ```
60
+
61
+ The cookie (`bwx_session`) is `HttpOnly; SameSite=Lax; Secure` (Secure when the origin is https), HMAC-SHA256 signed, 7 days by default (`maxAge`). Both POST routes check the `Origin` header against `origin`. `signSession` / `verifySession` are also exported from `@bwalletx/connect/server` if you want the token without the adapter.
62
+
63
+ Create the nonce table once (a migration): `pgNonceTableSql()` returns the SQL.
64
+
65
+ ## React quick start
66
+
67
+ ```tsx
68
+ 'use client';
69
+ import { BwalletXProvider, useBwalletX } from '@bwalletx/connect/react';
70
+
71
+ export function Providers({ children }: { children: React.ReactNode }) {
72
+ return (
73
+ <BwalletXProvider
74
+ challengeUrl="/api/bwalletx/challenge"
75
+ verifyUrl="/api/bwalletx/verify"
76
+ logoutUrl="/api/bwalletx/logout"
77
+ sessionUrl="/api/bwalletx/session">
78
+ {children}
79
+ </BwalletXProvider>
80
+ );
81
+ }
82
+
83
+ function Account() {
84
+ const { status, identityKey, handle, connect, disconnect, tip } = useBwalletX();
85
+ if (status !== 'connected') return <button onClick={() => connect()}>Connect wallet</button>;
86
+ return (
87
+ <>
88
+ <span>{handle ?? identityKey?.slice(0, 10)}</span>
89
+ <button onClick={() => tip({ to: 'author@example.com', usd: 0.05 })}>Tip 5¢</button>
90
+ <button onClick={disconnect}>Sign out</button>
91
+ </>
92
+ );
93
+ }
94
+ ```
95
+
96
+ `useBwalletX()` gives `status` (`idle | restoring | connecting | connected | error`), `identityKey`, `handle`, `paymail`, `wallet` (the BRC-100 `WalletInterface`), `walletId`, `signedIn`, `error`, `wallets`, `connect({ walletId? })`, `disconnect()`, `listWallets()`, `pay`, `tip` and `buyToken`.
97
+
98
+ `connect()` finds wallets, shows the built-in picker when there is more than one (bWalletX first), shows the pairing QR modal for phones, and signs in through your routes when `challengeUrl` and `verifyUrl` are set. Nothing touches `window` during render, so the provider is safe in server-rendered pages. After a reload the session comes back from `sessionUrl`, but `wallet` stays `null` until the user calls `connect()` again.
99
+
100
+ ## Payments
101
+
102
+ ```ts
103
+ import { pay, tip, buyToken } from '@bwalletx/connect';
104
+
105
+ await pay(wallet, { to: 'alice@example.com', sats: 5000, memo: 'Album' }); // paymail
106
+ await pay(wallet, { to: '1Abc...', usd: 1 }); // address; WhatsOnChain rate, or pass usdPerBsv
107
+ const r = await pay(wallet, { to: identityKeyHex, sats: 100 }); // BRC-29; r.remittance goes to the recipient
108
+ await buyToken(wallet, { token: '$BOASE', amount: 100, quote: (q) => fetch('/api/quote', { method: 'POST', body: JSON.stringify(q) }).then((x) => x.json()) });
109
+ ```
110
+
111
+ Each call makes one `createAction`, and nothing is ever retried. Failures throw a `PaymentError` with a `code` (`BAD_REQUEST`, `RESOLVE_FAILED`, `RATE_FAILED`, `WALLET_REJECTED`, `WALLET_ERROR`, `NOT_SUPPORTED`). When `maybeSent` is true, the wallet may already have broadcast the payment, so check before you ask the user again. Browsers often can't resolve paymails themselves because of CORS, so pass `resolvePaymail` to do it on your server.
112
+
113
+ On the server, check the payment before you deliver anything:
114
+
115
+ ```ts
116
+ import { verifyPayment } from '@bwalletx/connect/server';
117
+
118
+ const r = await verifyPayment({
119
+ tx: body.tx ?? body.txid, // Atomic BEEF / BEEF / raw hex, or a txid (fetched from WhatsOnChain)
120
+ expected: [{ address: SELLER_ADDRESS, satoshis: price }], // or { script: hex, satoshis }
121
+ requireSeen: true, // status from WhatsOnChain; or status: 'arc', arcUrl, arcApiKey
122
+ });
123
+ ```
124
+
125
+ Every expected output has to be matched by a different output that carries at least the expected amount. Otherwise `verifyPayment` throws a `PaymentVerificationError` with a `code` (`OUTPUT_MISSING`, `TX_NOT_FOUND`, `NOT_SEEN`, `REJECTED`, `SPV_FAILED`, `BAD_TX`, `NETWORK`). Pass `spv: chainTracker` to check the merkle proofs in a BEEF as well.
126
+
127
+ ## Wallets and the picker
128
+
129
+ `discoverWallets()` lists everything it can find, with bWalletX first:
130
+
131
+ - every wallet that announces itself through BRC-100 discovery (bWalletX, Yours, Metanet and others)
132
+ - the in-app browser's `window.CWI`
133
+ - desktop wallets on localhost ports 3321 and 3301 (only with `probeLocal: true`)
134
+ - phone pairing (`pairing: pairingModal()`)
135
+ - any adapters you add in `extra`
136
+
137
+ `pickWallet(list)` shows the picker. `pairingModal()` returns `PairingOptions` that draw the QR code and match code, so you no longer need your own QR library.
138
+
139
+ ## Migrating from HandCash Connect
140
+
141
+ 1. Keep your HandCash login for now and put it behind the same picker:
142
+ ```ts
143
+ import { handcashAdapter } from '@bwalletx/connect';
144
+ const handcash = handcashAdapter({
145
+ login: () => myExistingHandCashLogin(), // resolves { handle, paymail? } once signed in
146
+ pay: (req) => fetch('/api/handcash/pay', { method: 'POST', body: JSON.stringify(req) }).then((r) => r.json()),
147
+ });
148
+ <BwalletXProvider extraWallets={[handcash]} ... />
149
+ ```
150
+ **This adapter is transitional.** It wraps your own HandCash code; this package does not import any HandCash SDK. HandCash announced BRC-100 support in October 2026. Once that ships, HandCash shows up as an ordinary BRC-100 wallet and you can delete the adapter.
151
+ 2. Let existing users link their wallet. When someone already signed in with HandCash connects bWalletX, call `signInOrLink(identityKey, { currentUserId, ... })` (or `linkIdentity`) in `onVerified`. This links the identity to their existing account instead of creating a new one. If the identity is already linked to someone else, `IdentityLinkConflictError` turns into a 409 response.
152
+ 3. Replace HandCash payment calls with `pay` / `tip` / `buyToken`, and check them on the server with `verifyPayment` instead of HandCash payment webhooks.
153
+ 4. Remove the adapter when your users have moved over.
154
+
17
155
  ## The button
18
156
 
19
157
  `<bwalletx-signin>` is the standard "Sign in with bWalletX" button: a web component with no dependencies (Shadow DOM), plus a React wrapper. It is the gold button from bit-sign's sign-in card.
@@ -108,7 +246,10 @@ interface NonceStore {
108
246
 
109
247
  `take` must read and delete in one step. `verifySignIn` calls it before checking anything else, so each nonce gets exactly one attempt.
110
248
 
111
- `memoryStore()` keeps challenges in the process. It is lost on restart and isn't shared between processes or serverless instances, so it prints a warning the first time it is used. If you leave out `store`, a shared in-memory store is used and you get the same warning. In production, use Redis (`GETDEL`) or a database row you delete with `DELETE ... RETURNING`.
249
+ `memoryStore()` keeps challenges in the process. It is lost on restart and isn't shared between processes or serverless instances, so it prints a warning the first time it is used. If you leave out `store`, a shared in-memory store is used and you get the same warning. In production it warns louder. Use one of the built-in shared stores, which need no driver dependency because you pass your own client:
250
+
251
+ - `redisStore({ client })`: Upstash, Vercel KV or node-redis as they are, or ioredis with `flavor: 'ioredis'`. It uses `GETDEL`, so you need Redis 6.2 or later.
252
+ - `pgStore({ query })`: any `(sql, params)` function (node-postgres, postgres.js, Neon). It deletes each nonce with `DELETE ... RETURNING`, and `pgNonceTableSql()` gives you the table.
112
253
 
113
254
  Also rate-limit the challenge route per IP. It is unauthenticated.
114
255
 
@@ -192,12 +333,56 @@ The phone sends a plain `hello` and then, as soon as the user taps Connect, an e
192
333
 
193
334
  This build includes `@bsv/sdk` (about 125 KB minified). The server functions are not in it.
194
335
 
336
+ ## bApp shell
337
+
338
+ `@bwalletx/connect/shell` gives every bApp the same 5-slot bar as the bWalletX dock: **Wallet · Exchange · (b) · Feed · Chat**, each scoped to your app. Your app's Home and sections live in a drawer opened from the app icon, top left. On the web the package draws the bar; inside bWalletX it hides it and the wallet's native dock drives your app instead.
339
+
340
+ ![bApp shell at 390px](docs/screens/shell-390.png)
341
+
342
+ 1. Serve `/.well-known/bapp.json` (schema: [`schema/bapp.schema.json`](schema/bapp.schema.json), sample: [`examples/bapp.bmovies.json`](examples/bapp.bmovies.json)). All paths are same-origin; a slot you don't use is `"enabled": false` (shown greyed out, never hidden).
343
+ 2. Add the elements:
344
+
345
+ ```html
346
+ <bwalletx-topbar drawer="sections"></bwalletx-topbar>
347
+ <main>…</main>
348
+ <bwalletx-drawer id="sections"></bwalletx-drawer>
349
+ <bwalletx-bar active="feed"></bwalletx-bar>
350
+ <script type="module">
351
+ import { defineBappShell, loadManifest, applyManifest, startShellBridge } from '@bwalletx/connect/shell';
352
+ defineBappShell();
353
+ const manifest = await loadManifest(); // fetches and validates /.well-known/bapp.json
354
+ applyManifest(manifest);
355
+ startShellBridge({ manifest }); // inside bWalletX: hide the bar, follow the wallet's dock
356
+ </script>
357
+ ```
358
+
359
+ React:
360
+
361
+ ```tsx
362
+ import { BappTopbar, BappDrawer, BappBar } from '@bwalletx/connect/shell/react';
363
+ <BappTopbar manifest={m} drawer="sections" />
364
+ <BappDrawer id="sections" manifest={m} manualNavigation onNavigate={(d) => router.push(d.path!)} />
365
+ <BappBar manifest={m} active={slot} manualNavigation onNavigate={(d) => router.push(d.path!)} />
366
+ ```
367
+
368
+ No bundler: `<script src="https://unpkg.com/@bwalletx/connect/dist/bwalletx-shell.global.js">` registers the elements and exposes `window.bWalletXShell`.
369
+
370
+ | Element | Attributes | Events (bubble, composed) |
371
+ |---|---|---|
372
+ | `<bwalletx-bar>` | `active`, `in-wallet` (`auto`/`true`/`false`), `navigate="manual"`; property `manifest` | `bapp-navigate {slot, path}` (cancelable), `bapp-active {slot}`, `bapp-b-press`, `bapp-b-hold {phase}` |
373
+ | `<bwalletx-topbar>` | `title`, `subtitle`, `drawer` (id), `menu="true"`, `strip="off"`, `in-wallet`; slots `strip-left`, `strip-right`, `tools` | `bapp-menu`, `bapp-close`, `bapp-drawer-toggle` (cancelable) |
374
+ | `<bwalletx-drawer>` | `open`, `current` (path), `in-wallet`, `navigate="manual"` | `bapp-navigate {path}` (cancelable), `bapp-close` |
375
+
376
+ `bapp-navigate` goes to the manifest path with `location.assign` unless you call `preventDefault()` or set `navigate="manual"`, so SPAs route themselves. Inside the wallet, ☰ and ✕ appear in the top bar and the drawer gets "Back to bWalletX".
377
+
378
+ API: `validateManifest(json)` → `{ok, manifest, errors}`, `parseManifest`, `loadManifest(path?)`, `isSameOriginPath`, `slotPath`, `detectWalletUA(ua?)`, `walletState()`, `ShellBridge` / `startShellBridge({manifest, walletOrigins?})`, `defineBappShell()`, `applyManifest(m)`, `TOKENS`. The wallet message protocol is in [docs/SHELL-PROTOCOL.md](docs/SHELL-PROTOCOL.md). Demo: `examples/shell.html` (after `pnpm build`; `?wallet=1` shows the in-wallet top bar, `#drawer` opens the drawer).
379
+
195
380
  ## Limits
196
381
 
197
- - You get the identity key and nothing else: no handle, paymail or name.
382
+ - Sign-in proves the identity key and nothing else: no handle, paymail or name. Use `onVerified` to look them up and add them yourself.
198
383
  - The identity key is the same on every site that uses this method, so two sites can tell it is the same wallet.
199
384
  - `relay.bwallet.space` is run by The Bitcoin Corporation. It has no published uptime guarantee and allows about 60 frames a minute per channel.
200
- - There is no hosted sign-in page or OAuth-style redirect. You run the two routes yourself.
385
+ - There is no hosted sign-in page or OAuth-style redirect (auth.bwalletx.com is planned). You run the routes yourself; `@bwalletx/connect/next` gives you ready-made ones.
201
386
 
202
387
  ## Testing
203
388