@bwalletx/connect 0.5.0 → 0.5.4

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.
package/CHANGELOG.md CHANGED
@@ -1,7 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.4 (unreleased)
4
+
5
+ - **Connect is fast.** A wallet that is present now resolves at once: the extension answers `brc100:requestWallet` synchronously, and `window.CWI` (in-app browser) is taken straight away instead of after the wait. The wait for "nothing here" before the pairing QR drops from 1200 ms to 300 ms (`DEFAULT_DISCOVER_MS`); in a bWalletX frame the XDM probe now starts in parallel. `discoverWallets` stops waiting 50 ms after a wallet has announced. Measured in Chrome: pairing modal 1.24–1.34 s → 0.33–0.34 s after the click.
6
+ - **`onStatus`** on `signInWithBwalletX` / `connectBwalletX`: `'finding' | 'opening' | 'pairing' | 'signing'`, so the button can say "Opening your wallet…" the moment it is clicked.
7
+ - **`prewarmBwalletX()` / `bindPrewarm(el)`**: on hover, focus or touch, send a prompt-free `isAuthenticated` so a sleeping extension worker starts reopening the wallet before the click (about 0.5 s off a cold Connect). `<bwalletx-signin>` binds it automatically.
8
+ - **Security: relay role claims for the site (b-Aware 26 Oct 2026, N3-1/N3-2).** `pairBwalletX` now opens the relay channel with its own random site claim and stores it with the pairing (`sc`); every reconnect sends it, so someone who learns the channel ID can no longer replace your socket or lock you out. It also sends `wc`, the SHA-256 of the wallet claim only the QR can produce (`walletClaimFor(channel, site pubkey)`), so nobody can take the wallet role before the phone scans. Pairings stored by older versions get a claim on their next reconnect. **Upgrade before 30 Nov 2026**: after that the relay refuses new channels without a site claim. See "Relay claims" in the README.
9
+
10
+ ## 0.5.3 (unreleased)
11
+
12
+ - **Phones get "Open bWalletX", not a QR.** On a touch device (`isTouchDevice()`), `pairingModal()` no longer shows a QR the user would have to scan with the same phone. It shows **Open bWalletX**, a link to the pairing universal link (`https://www.bwallet.space/pair?…`, the iOS universal link / Android app link the app already handles), plus **Use web.bwalletx.com instead** (copies the link and opens the web wallet to paste it), and the match code. The page keeps waiting on the pairing channel, so the user approves in the wallet and comes back. Desktops still get the QR. Force either with `pairingModal({ mode: 'qr' | 'app' })`.
13
+
14
+ ## 0.5.2 (unreleased)
15
+
16
+ - **Security: framed pages only ask bWalletX itself over XDM.** `findBwalletX` / `discoverBwalletX` in a frame used to post to whatever page framed it and take its answers, so any page that framed your site became "the wallet" (saw every call, could return a fake txid). Now the parent's origin (from `location.ancestorOrigins`, else the referrer) must be one of `BWALLETX_PARENT_ORIGINS` (web.bwalletx.com, bwallet.space, the iOS/Android/Desktop app shells, the Chrome extension); messages go to that exact origin and only its answers count. `probeXdm(ms, allowed)` takes your own list if you host the wallet elsewhere.
17
+ - Docs: verify payments on your server (`verifyPayment`); never trust a txid from the page.
18
+
3
19
  ## 0.5.0 (unreleased)
4
20
 
21
+ - **Site theme in the wallet**: `BwalletXProvider theme="npgx"` (or `declareWalletTheme('npgx')`) declares `<meta name="bwalletx-theme">` while connecting/connected and removes it on disconnect. bWalletX wears that theme for this origin only, from the themes it bundles (brand editions locked to their own sites); only the id is sent, never colours or CSS.
22
+ - Sign-in errors carry `err.stage` (`identity` | `challenge` | `signature` | `verify`) with the original message, so a site can say what actually failed instead of guessing from the text.
5
23
  - **Access protocol v1 ($401 identity / $403 token-gated access)**, spec in `docs/ACCESS-PROTOCOL.md`. Standard BRC-100 calls only, so any BRC-100 wallet works.
6
24
  - Client: `requestAccess()` signs the site's challenge and proves the keys that hold the token (`proveHoldings()`). Errors are `AccessError` with a protocol code.
7
25
  - Server: `createAccessChallenge`, `verifyAccess`, `checkTokenHolding`, plus `oneSatResolver()` (1Sat indexer) and `bitSignResolver()` (identity level / KYC). Fails closed: an indexer outage is `indexer_unavailable`, never a grant.
package/README.md CHANGED
@@ -99,6 +99,8 @@ function Account() {
99
99
 
100
100
  ## Payments
101
101
 
102
+ Other bCorp apps: use `tip()` for the default Tip button (the same one-sheet tip bWalletX shows on artists, NFTs, chat, Friends and Spaces).
103
+
102
104
  ```ts
103
105
  import { pay, tip, buyToken } from '@bwalletx/connect';
104
106
 
@@ -110,7 +112,9 @@ await buyToken(wallet, { token: '$BOASE', amount: 100, quote: (q) => fetch('/api
110
112
 
111
113
  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
114
 
113
- On the server, check the payment before you deliver anything:
115
+ **Verify payments on your server.** A txid or BEEF that comes back to the page is only what the page was told: a
116
+ browser extension, a modified page or (before 0.5.2) a parent frame posing as the wallet can return a fake one.
117
+ Deliver nothing until your server has checked the payment:
114
118
 
115
119
  ```ts
116
120
  import { verifyPayment } from '@bwalletx/connect/server';
@@ -134,7 +138,7 @@ Every expected output has to be matched by a different output that carries at le
134
138
  - phone pairing (`pairing: pairingModal()`)
135
139
  - any adapters you add in `extra`
136
140
 
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.
141
+ `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. On phones and tablets it shows an **Open bWalletX** button (the pairing universal link opens the app) and a web.bwalletx.com fallback instead of a QR; pass `mode: 'qr'` or `'app'` to force one.
138
142
 
139
143
  ## Migrating from HandCash Connect
140
144
 
@@ -322,6 +326,10 @@ The pairing is saved under `bwalletx.connect.pairing.v1` in `localStorage` by de
322
326
 
323
327
  The phone sends a plain `hello` and then, as soon as the user taps Connect, an encrypted `ready`. An async `onmessage` handler that is still deriving the key from `hello` when `ready` arrives drops `ready`, and pairing never finishes. This package handles frames one at a time in arrival order (`inOrder`). `test/pair.test.ts` reproduces the race.
324
328
 
329
+ ### Relay claims
330
+
331
+ Since 0.5.4 the site side sends a role claim to the relay on every connect, and a digest of the QR-bound wallet claim when it opens a channel. Only the socket with your claim can take the site role, and only a phone that scanned your QR can take the wallet role. The relay (relay.bwallet.space) keeps a compatibility window for older sites and wallets until **30 Nov 2026**: until then a site without a claim can still open a channel and an older wallet can still join one. After that date the relay refuses both, so update `@bwalletx/connect` (or a copied `protocol.ts` + pairing code) before then. Sites that copied the pairing code must send `claim` on every site socket (keep it with the pairing) and `wc = claimDigest(await walletClaimFor(channel, sitePub))` when opening a new channel.
332
+
325
333
  ## One script tag
326
334
 
327
335
  ```html
@@ -115,4 +115,4 @@ declare function accessErrorMessage(code: AccessErrorCode, e?: {
115
115
  need?: string | number;
116
116
  }): string;
117
117
 
118
- export { AccessError as A, type HoldingProof as H, MAX_HOLDING_PROOFS as M, PROTOCOL_MEDIA as P, type TokenRequirement as T, type AccessRequirement as a, type AccessResponse as b, ACCESS_ERROR_STATUS as c, ACCESS_PROTOCOL as d, ACCESS_VERSION as e, type AccessChallenge as f, type AccessErrorBody as g, type AccessErrorCode as h, type AccessKind as i, accessErrorMessage as j, canonicalJson as k, ACCESS_CHALLENGE_LIFETIME_S as l, atLeastRaw as m, challengeBytes as n, kindFor as o, protocolMediaUrl as p, normaliseRequirement as q, parseAccessChallenge as r, tokenNeedRaw as t };
118
+ export { AccessError as A, type HoldingProof as H, MAX_HOLDING_PROOFS as M, PROTOCOL_MEDIA as P, type TokenRequirement as T, type AccessRequirement as a, type AccessResponse as b, type AccessKind as c, ACCESS_PROTOCOL as d, ACCESS_CHALLENGE_LIFETIME_S as e, ACCESS_ERROR_STATUS as f, ACCESS_VERSION as g, type AccessChallenge as h, type AccessErrorBody as i, type AccessErrorCode as j, accessErrorMessage as k, atLeastRaw as l, canonicalJson as m, challengeBytes as n, kindFor as o, normaliseRequirement as p, parseAccessChallenge as q, protocolMediaUrl as r, tokenNeedRaw as t };
@@ -115,4 +115,4 @@ declare function accessErrorMessage(code: AccessErrorCode, e?: {
115
115
  need?: string | number;
116
116
  }): string;
117
117
 
118
- export { AccessError as A, type HoldingProof as H, MAX_HOLDING_PROOFS as M, PROTOCOL_MEDIA as P, type TokenRequirement as T, type AccessRequirement as a, type AccessResponse as b, ACCESS_ERROR_STATUS as c, ACCESS_PROTOCOL as d, ACCESS_VERSION as e, type AccessChallenge as f, type AccessErrorBody as g, type AccessErrorCode as h, type AccessKind as i, accessErrorMessage as j, canonicalJson as k, ACCESS_CHALLENGE_LIFETIME_S as l, atLeastRaw as m, challengeBytes as n, kindFor as o, protocolMediaUrl as p, normaliseRequirement as q, parseAccessChallenge as r, tokenNeedRaw as t };
118
+ export { AccessError as A, type HoldingProof as H, MAX_HOLDING_PROOFS as M, PROTOCOL_MEDIA as P, type TokenRequirement as T, type AccessRequirement as a, type AccessResponse as b, type AccessKind as c, ACCESS_PROTOCOL as d, ACCESS_CHALLENGE_LIFETIME_S as e, ACCESS_ERROR_STATUS as f, ACCESS_VERSION as g, type AccessChallenge as h, type AccessErrorBody as i, type AccessErrorCode as j, accessErrorMessage as k, atLeastRaw as l, canonicalJson as m, challengeBytes as n, kindFor as o, normaliseRequirement as p, parseAccessChallenge as q, protocolMediaUrl as r, tokenNeedRaw as t };