@bwalletx/connect 0.5.3 → 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 (50) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +80 -8
  3. package/dist/bwalletx-connect.global.js +92 -25
  4. package/dist/bwalletx-connect.global.js.map +1 -1
  5. package/dist/{wallets-VOKIKBbO.d.ts → choice-DCh-tgpT.d.cts} +113 -5
  6. package/dist/{wallets-VOKIKBbO.d.cts → choice-DCh-tgpT.d.ts} +113 -5
  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 +1221 -67
  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 +1539 -449
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +185 -12
  26. package/dist/index.d.ts +185 -12
  27. package/dist/index.js +106 -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 +1073 -194
  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/dist/shell-react.cjs.map +1 -1
  44. package/dist/shell-react.js.map +1 -1
  45. package/package.json +1 -1
  46. package/dist/chunk-IUDD7YTQ.js +0 -839
  47. package/dist/chunk-IUDD7YTQ.js.map +0 -1
  48. package/dist/chunk-PI42H3XY.js +0 -459
  49. package/dist/chunk-PI42H3XY.js.map +0 -1
  50. package/dist/chunk-QRXFL66W.js.map +0 -1
@@ -175,6 +175,14 @@ declare function buyToken(source: PaySource, req: BuyTokenRequest): Promise<PayR
175
175
  */
176
176
 
177
177
  type WalletKind = 'brc100' | 'pairing' | 'handcash' | 'custom';
178
+ /**
179
+ * How a wallet reaches the page. 'extension' / 'in-app': bWalletX announced itself (its own rdns); 'xdm': the bWalletX
180
+ * app frame around the page; 'injected': another wallet that announced itself; 'window.CWI': something set window.CWI
181
+ * without announcing (it can't be told apart from any other wallet); 'json-api' / 'cicada': a desktop wallet on
182
+ * localhost (Metanet / BSV Desktop on 3321, Cicada on 3301); 'pairing': bWalletX on a phone over the relay;
183
+ * 'adapter': an app-supplied option (HandCash today).
184
+ */
185
+ type Substrate = 'extension' | 'in-app' | 'xdm' | 'injected' | 'window.CWI' | 'json-api' | 'cicada' | 'pairing' | 'adapter';
178
186
  interface ConnectedWallet {
179
187
  /** The BRC-100 wallet. null for a non-BRC-100 adapter (HandCash today). */
180
188
  wallet: WalletInterface | null;
@@ -198,7 +206,20 @@ interface WalletOption {
198
206
  rdns?: string;
199
207
  /** True for bWalletX (extension, in-app or paired). */
200
208
  isBwalletX: boolean;
201
- connect(): Promise<ConnectedWallet>;
209
+ /** How it reaches the page (0.5.6). Options built by this package always set it. */
210
+ substrate?: Substrate;
211
+ /** The BRC-100 wallet behind a detected option (brc100 kind), when known before connecting. */
212
+ wallet?: WalletInterface;
213
+ /**
214
+ * Open the wallet. By default it also asks for the identity key (one wallet call). Pass `{ identity: false }` when
215
+ * you sign in straight after (BwalletXProvider does): the key then comes with the sign-in signature, in ONE sheet,
216
+ * and `identityKey` is null here.
217
+ */
218
+ connect(opts?: ConnectOptions): Promise<ConnectedWallet>;
219
+ }
220
+ interface ConnectOptions {
221
+ /** Ask for the identity key now. Default true. */
222
+ identity?: boolean;
202
223
  }
203
224
  /** Wrap any BRC-100 wallet as a picker option. */
204
225
  declare function brc100Option(wallet: WalletInterface, info: {
@@ -207,6 +228,7 @@ declare function brc100Option(wallet: WalletInterface, info: {
207
228
  icon?: string;
208
229
  rdns?: string;
209
230
  isBwalletX?: boolean;
231
+ substrate?: Substrate;
210
232
  }): WalletOption;
211
233
  /** Phone / web.bwalletx.com pairing as an option (shows a QR through `pairing`). Reuses a stored pairing first. */
212
234
  declare function pairingOption(pairing: PairingOptions | (() => PairingOptions)): WalletOption;
@@ -232,11 +254,12 @@ interface HandCashAdapterOptions {
232
254
  */
233
255
  declare function handcashAdapter(o: HandCashAdapterOptions): WalletOption;
234
256
  interface DiscoverWalletsOptions {
235
- /** How long to collect BRC-100 announcements, ms. Default 1200. */
257
+ /** Longest wait for BRC-100 announcements, ms. Default 300; ends early once a wallet has announced. */
236
258
  ms?: number;
237
259
  /**
238
- * Also probe desktop wallets on localhost (Metanet / json-api on 3321, Cicada on 3301).
239
- * Off by default: some browsers prompt for local-network access.
260
+ * Also probe desktop wallets on localhost (Metanet / json-api on 3321, Cicada on 3301) while listing.
261
+ * Off by default, and best left off: Chrome asks for Local Network Access on the first probe. The chooser
262
+ * (chooseWallet) offers "Desktop wallet" instead, which probes only when the user taps it (desktopOption).
240
263
  */
241
264
  probeLocal?: boolean;
242
265
  /** Add the phone pairing option (needs a way to show the QR, e.g. pairingModal()). */
@@ -244,7 +267,92 @@ interface DiscoverWalletsOptions {
244
267
  /** App-supplied options appended at the end (e.g. handcashAdapter(...)). */
245
268
  extra?: WalletOption[];
246
269
  }
270
+ /**
271
+ * True when window.CWI is a wallet that already announced itself, so the picker must not list it twice. bWalletX
272
+ * (extension and in-app) sets window.CWI to the very object it announces; the in-app browser is matched by rdns too.
273
+ */
274
+ declare function cwiIsAnnounced(cwi: WalletInterface, announced: WalletOption[]): boolean;
247
275
  /** All wallets we can see, bWalletX first. SSR-safe: resolves [] (plus `extra`) outside a browser. */
248
276
  declare function discoverWallets(o?: DiscoverWalletsOptions): Promise<WalletOption[]>;
277
+ /** Id of the "Desktop wallet" option (desktopOption). */
278
+ declare const DESKTOP_OPTION_ID = "desktop";
279
+ /**
280
+ * "Desktop wallet" (Metanet / BSV Desktop on localhost:3321, Cicada on 3301) as an option that looks for the wallet
281
+ * only when connect() runs, i.e. after the user picked it. Nothing touches localhost before that: the first request
282
+ * makes Chrome ask for Local Network Access, which must follow a tap, never a page load. connect() rejects with
283
+ * code NOT_FOUND when neither port answers within `ms`.
284
+ */
285
+ declare function desktopOption(ms?: number): WalletOption;
286
+ /**
287
+ * The 0.5.5 rule, kept for `fastPath: false` (0.5.6 sites use decideWallet, which never picks another wallet unasked).
288
+ * Which option to use without showing the picker, or undefined when the user has a real choice.
289
+ * - nothing found → null;
290
+ * - one option → it;
291
+ * - exactly one wallet detected in this browser and no app-supplied option (HandCash, custom) → that wallet, with no
292
+ * picker: phone pairing is a fallback for "no wallet here", not a second choice;
293
+ * - only phone pairing → pairing.
294
+ * `force` (picker off): the first detected wallet, else the first non-pairing option, else pairing.
295
+ */
296
+ declare function autoChoice(list: WalletOption[], force?: boolean): WalletOption | null | undefined;
297
+
298
+ /** The wallet type every bWalletX (extension, in-app, app frame, phone) reports. */
299
+ declare const BWALLETX_TYPE = "bwalletx";
300
+ /** How the option reaches the page (WalletOption.substrate, inferred for options built elsewhere). */
301
+ declare function substrateOf(o: WalletOption): Substrate;
302
+ /** 'bwalletx' for any bWalletX; otherwise the wallet's rdns, else its option id ('desktop', 'window.CWI', 'handcash'). */
303
+ declare function walletTypeOf(o: WalletOption): string;
304
+ /**
305
+ * bWalletX found in this browser by its own marker (announcement or app frame). A wallet that only sits on
306
+ * window.CWI never counts, whatever it claims to be.
307
+ */
308
+ declare function isPresentBwalletX(o: WalletOption): boolean;
309
+ /** True when bWalletX itself is in this browser (not just phone pairing). */
310
+ declare function hasBwalletX(list: readonly WalletOption[]): boolean;
311
+ /** Wallets other than bWalletX that are actually in this browser (announced, window.CWI, or a probed desktop wallet). */
312
+ declare function otherDetected(list: readonly WalletOption[]): WalletOption[];
313
+ /** The chooser's order: bWalletX here, phone pairing, other detected wallets, app-supplied options last. */
314
+ declare function chooserOrder(list: readonly WalletOption[]): WalletOption[];
315
+ /**
316
+ * Why decideWallet chose what it did. 'requested': walletId; 'bwalletx': bWalletX is here (fast path);
317
+ * 'bwalletx-phone': picker off and no bWalletX here, so the phone; 'legacy': fastPath false (0.5.5 autoChoice);
318
+ * 'chooser': show the chooser; 'none': nothing to offer.
319
+ */
320
+ type ChoiceReason = 'requested' | 'bwalletx' | 'bwalletx-phone' | 'legacy' | 'chooser' | 'none';
321
+ interface WalletChoice {
322
+ option: WalletOption | null;
323
+ reason: ChoiceReason;
324
+ }
325
+ interface DecideWalletOptions {
326
+ /** Use this option (an explicit choice the site already has). */
327
+ walletId?: string;
328
+ /** "Other wallets": the user asked for the chooser, so show it even with bWalletX here. */
329
+ chooser?: boolean;
330
+ /** false: never show the chooser (bWalletX, else the phone, else nothing; never another wallet). */
331
+ picker?: boolean;
332
+ /** The bWalletX-first rule. Default true; false restores the 0.5.5 behaviour (autoChoice). */
333
+ fastPath?: boolean;
334
+ }
335
+ /** The wallet to connect now, or the chooser ({ option: null, reason: 'chooser' }). */
336
+ declare function decideWallet(list: readonly WalletOption[], o?: DecideWalletOptions): WalletChoice;
337
+ /** How a connect attempt ended. */
338
+ type AttemptOutcome = 'signed-in' | 'connected' | 'cancelled' | 'rejected' | 'not-found' | 'error';
339
+ /**
340
+ * One connect attempt, for your metrics. Deliberately nothing else: no identity key, address, paymail, handle,
341
+ * origin or error text. `walletType`/`substrate` are null when no wallet was chosen (chooser closed, nothing found).
342
+ */
343
+ interface ConnectAttempt {
344
+ walletType: string | null;
345
+ substrate: Substrate | null;
346
+ outcome: AttemptOutcome;
347
+ /** From the tap to the outcome, rounded to whole ms. */
348
+ ms: number;
349
+ }
350
+ type AttemptHook = (attempt: ConnectAttempt) => void;
351
+ /** The outcome for an error thrown by a connect attempt. */
352
+ declare function outcomeOf(e: unknown): AttemptOutcome;
353
+ /** Build the attempt record for `option` (or none). */
354
+ declare function attemptFor(option: WalletOption | null | undefined, outcome: AttemptOutcome, startedAt: number, now?: number): ConnectAttempt;
355
+ /** Call the hook with exactly the four fields; a throwing hook never breaks sign-in. */
356
+ declare function reportAttempt(hook: AttemptHook | undefined, a: ConnectAttempt): void;
249
357
 
250
- export { restorePairing as A, type BuyTokenRequest as B, type ConnectedWallet as C, type DiscoverWalletsOptions as D, tip as E, type HandCashAdapterOptions as H, type LegacyPayer as L, type PayRequest as P, type SocketFactory as S, type TokenQuote as T, type WalletOption as W, type PayResult as a, type PayOptions as b, PaymentError as c, brc100Option as d, discoverWallets as e, type PairingOptions as f, BRC29_PROTOCOL as g, handcashAdapter as h, type Brc29Remittance as i, type PairConnectionOptions as j, type PairStorage as k, type PairedWallet as l, type PayOutput as m, type PaySource as n, type PaymentErrorCode as o, pairingOption as p, type SocketLike as q, type WalletKind as r, amountInSats as s, buyToken as t, clearPairing as u, isIdentityKeyHex as v, isPaymail as w, pairBwalletX as x, pay as y, resolvePaymailP2P as z };
358
+ export { pay as $, type AttemptHook as A, type BuyTokenRequest as B, type ConnectedWallet as C, DESKTOP_OPTION_ID as D, type WalletChoice as E, type WalletKind as F, amountInSats as G, type HandCashAdapterOptions as H, attemptFor as I, buyToken as J, chooserOrder as K, type LegacyPayer as L, clearPairing as M, cwiIsAnnounced as N, decideWallet as O, type PayRequest as P, desktopOption as Q, hasBwalletX as R, type Substrate as S, type TokenQuote as T, isIdentityKeyHex as U, isPaymail as V, type WalletOption as W, isPresentBwalletX as X, otherDetected as Y, outcomeOf as Z, pairBwalletX as _, type PayResult as a, reportAttempt as a0, resolvePaymailP2P as a1, restorePairing as a2, substrateOf as a3, tip as a4, walletTypeOf as a5, type PayOptions as b, PaymentError as c, autoChoice as d, brc100Option as e, discoverWallets as f, type PairingOptions as g, handcashAdapter as h, type ChoiceReason as i, type AttemptOutcome as j, BRC29_PROTOCOL as k, BWALLETX_TYPE as l, type Brc29Remittance as m, type ConnectAttempt as n, type ConnectOptions as o, pairingOption as p, type DecideWalletOptions as q, type DiscoverWalletsOptions as r, type PairConnectionOptions as s, type PairStorage as t, type PairedWallet as u, type PayOutput as v, type PaySource as w, type PaymentErrorCode as x, type SocketFactory as y, type SocketLike as z };
@@ -175,6 +175,14 @@ declare function buyToken(source: PaySource, req: BuyTokenRequest): Promise<PayR
175
175
  */
176
176
 
177
177
  type WalletKind = 'brc100' | 'pairing' | 'handcash' | 'custom';
178
+ /**
179
+ * How a wallet reaches the page. 'extension' / 'in-app': bWalletX announced itself (its own rdns); 'xdm': the bWalletX
180
+ * app frame around the page; 'injected': another wallet that announced itself; 'window.CWI': something set window.CWI
181
+ * without announcing (it can't be told apart from any other wallet); 'json-api' / 'cicada': a desktop wallet on
182
+ * localhost (Metanet / BSV Desktop on 3321, Cicada on 3301); 'pairing': bWalletX on a phone over the relay;
183
+ * 'adapter': an app-supplied option (HandCash today).
184
+ */
185
+ type Substrate = 'extension' | 'in-app' | 'xdm' | 'injected' | 'window.CWI' | 'json-api' | 'cicada' | 'pairing' | 'adapter';
178
186
  interface ConnectedWallet {
179
187
  /** The BRC-100 wallet. null for a non-BRC-100 adapter (HandCash today). */
180
188
  wallet: WalletInterface | null;
@@ -198,7 +206,20 @@ interface WalletOption {
198
206
  rdns?: string;
199
207
  /** True for bWalletX (extension, in-app or paired). */
200
208
  isBwalletX: boolean;
201
- connect(): Promise<ConnectedWallet>;
209
+ /** How it reaches the page (0.5.6). Options built by this package always set it. */
210
+ substrate?: Substrate;
211
+ /** The BRC-100 wallet behind a detected option (brc100 kind), when known before connecting. */
212
+ wallet?: WalletInterface;
213
+ /**
214
+ * Open the wallet. By default it also asks for the identity key (one wallet call). Pass `{ identity: false }` when
215
+ * you sign in straight after (BwalletXProvider does): the key then comes with the sign-in signature, in ONE sheet,
216
+ * and `identityKey` is null here.
217
+ */
218
+ connect(opts?: ConnectOptions): Promise<ConnectedWallet>;
219
+ }
220
+ interface ConnectOptions {
221
+ /** Ask for the identity key now. Default true. */
222
+ identity?: boolean;
202
223
  }
203
224
  /** Wrap any BRC-100 wallet as a picker option. */
204
225
  declare function brc100Option(wallet: WalletInterface, info: {
@@ -207,6 +228,7 @@ declare function brc100Option(wallet: WalletInterface, info: {
207
228
  icon?: string;
208
229
  rdns?: string;
209
230
  isBwalletX?: boolean;
231
+ substrate?: Substrate;
210
232
  }): WalletOption;
211
233
  /** Phone / web.bwalletx.com pairing as an option (shows a QR through `pairing`). Reuses a stored pairing first. */
212
234
  declare function pairingOption(pairing: PairingOptions | (() => PairingOptions)): WalletOption;
@@ -232,11 +254,12 @@ interface HandCashAdapterOptions {
232
254
  */
233
255
  declare function handcashAdapter(o: HandCashAdapterOptions): WalletOption;
234
256
  interface DiscoverWalletsOptions {
235
- /** How long to collect BRC-100 announcements, ms. Default 1200. */
257
+ /** Longest wait for BRC-100 announcements, ms. Default 300; ends early once a wallet has announced. */
236
258
  ms?: number;
237
259
  /**
238
- * Also probe desktop wallets on localhost (Metanet / json-api on 3321, Cicada on 3301).
239
- * Off by default: some browsers prompt for local-network access.
260
+ * Also probe desktop wallets on localhost (Metanet / json-api on 3321, Cicada on 3301) while listing.
261
+ * Off by default, and best left off: Chrome asks for Local Network Access on the first probe. The chooser
262
+ * (chooseWallet) offers "Desktop wallet" instead, which probes only when the user taps it (desktopOption).
240
263
  */
241
264
  probeLocal?: boolean;
242
265
  /** Add the phone pairing option (needs a way to show the QR, e.g. pairingModal()). */
@@ -244,7 +267,92 @@ interface DiscoverWalletsOptions {
244
267
  /** App-supplied options appended at the end (e.g. handcashAdapter(...)). */
245
268
  extra?: WalletOption[];
246
269
  }
270
+ /**
271
+ * True when window.CWI is a wallet that already announced itself, so the picker must not list it twice. bWalletX
272
+ * (extension and in-app) sets window.CWI to the very object it announces; the in-app browser is matched by rdns too.
273
+ */
274
+ declare function cwiIsAnnounced(cwi: WalletInterface, announced: WalletOption[]): boolean;
247
275
  /** All wallets we can see, bWalletX first. SSR-safe: resolves [] (plus `extra`) outside a browser. */
248
276
  declare function discoverWallets(o?: DiscoverWalletsOptions): Promise<WalletOption[]>;
277
+ /** Id of the "Desktop wallet" option (desktopOption). */
278
+ declare const DESKTOP_OPTION_ID = "desktop";
279
+ /**
280
+ * "Desktop wallet" (Metanet / BSV Desktop on localhost:3321, Cicada on 3301) as an option that looks for the wallet
281
+ * only when connect() runs, i.e. after the user picked it. Nothing touches localhost before that: the first request
282
+ * makes Chrome ask for Local Network Access, which must follow a tap, never a page load. connect() rejects with
283
+ * code NOT_FOUND when neither port answers within `ms`.
284
+ */
285
+ declare function desktopOption(ms?: number): WalletOption;
286
+ /**
287
+ * The 0.5.5 rule, kept for `fastPath: false` (0.5.6 sites use decideWallet, which never picks another wallet unasked).
288
+ * Which option to use without showing the picker, or undefined when the user has a real choice.
289
+ * - nothing found → null;
290
+ * - one option → it;
291
+ * - exactly one wallet detected in this browser and no app-supplied option (HandCash, custom) → that wallet, with no
292
+ * picker: phone pairing is a fallback for "no wallet here", not a second choice;
293
+ * - only phone pairing → pairing.
294
+ * `force` (picker off): the first detected wallet, else the first non-pairing option, else pairing.
295
+ */
296
+ declare function autoChoice(list: WalletOption[], force?: boolean): WalletOption | null | undefined;
297
+
298
+ /** The wallet type every bWalletX (extension, in-app, app frame, phone) reports. */
299
+ declare const BWALLETX_TYPE = "bwalletx";
300
+ /** How the option reaches the page (WalletOption.substrate, inferred for options built elsewhere). */
301
+ declare function substrateOf(o: WalletOption): Substrate;
302
+ /** 'bwalletx' for any bWalletX; otherwise the wallet's rdns, else its option id ('desktop', 'window.CWI', 'handcash'). */
303
+ declare function walletTypeOf(o: WalletOption): string;
304
+ /**
305
+ * bWalletX found in this browser by its own marker (announcement or app frame). A wallet that only sits on
306
+ * window.CWI never counts, whatever it claims to be.
307
+ */
308
+ declare function isPresentBwalletX(o: WalletOption): boolean;
309
+ /** True when bWalletX itself is in this browser (not just phone pairing). */
310
+ declare function hasBwalletX(list: readonly WalletOption[]): boolean;
311
+ /** Wallets other than bWalletX that are actually in this browser (announced, window.CWI, or a probed desktop wallet). */
312
+ declare function otherDetected(list: readonly WalletOption[]): WalletOption[];
313
+ /** The chooser's order: bWalletX here, phone pairing, other detected wallets, app-supplied options last. */
314
+ declare function chooserOrder(list: readonly WalletOption[]): WalletOption[];
315
+ /**
316
+ * Why decideWallet chose what it did. 'requested': walletId; 'bwalletx': bWalletX is here (fast path);
317
+ * 'bwalletx-phone': picker off and no bWalletX here, so the phone; 'legacy': fastPath false (0.5.5 autoChoice);
318
+ * 'chooser': show the chooser; 'none': nothing to offer.
319
+ */
320
+ type ChoiceReason = 'requested' | 'bwalletx' | 'bwalletx-phone' | 'legacy' | 'chooser' | 'none';
321
+ interface WalletChoice {
322
+ option: WalletOption | null;
323
+ reason: ChoiceReason;
324
+ }
325
+ interface DecideWalletOptions {
326
+ /** Use this option (an explicit choice the site already has). */
327
+ walletId?: string;
328
+ /** "Other wallets": the user asked for the chooser, so show it even with bWalletX here. */
329
+ chooser?: boolean;
330
+ /** false: never show the chooser (bWalletX, else the phone, else nothing; never another wallet). */
331
+ picker?: boolean;
332
+ /** The bWalletX-first rule. Default true; false restores the 0.5.5 behaviour (autoChoice). */
333
+ fastPath?: boolean;
334
+ }
335
+ /** The wallet to connect now, or the chooser ({ option: null, reason: 'chooser' }). */
336
+ declare function decideWallet(list: readonly WalletOption[], o?: DecideWalletOptions): WalletChoice;
337
+ /** How a connect attempt ended. */
338
+ type AttemptOutcome = 'signed-in' | 'connected' | 'cancelled' | 'rejected' | 'not-found' | 'error';
339
+ /**
340
+ * One connect attempt, for your metrics. Deliberately nothing else: no identity key, address, paymail, handle,
341
+ * origin or error text. `walletType`/`substrate` are null when no wallet was chosen (chooser closed, nothing found).
342
+ */
343
+ interface ConnectAttempt {
344
+ walletType: string | null;
345
+ substrate: Substrate | null;
346
+ outcome: AttemptOutcome;
347
+ /** From the tap to the outcome, rounded to whole ms. */
348
+ ms: number;
349
+ }
350
+ type AttemptHook = (attempt: ConnectAttempt) => void;
351
+ /** The outcome for an error thrown by a connect attempt. */
352
+ declare function outcomeOf(e: unknown): AttemptOutcome;
353
+ /** Build the attempt record for `option` (or none). */
354
+ declare function attemptFor(option: WalletOption | null | undefined, outcome: AttemptOutcome, startedAt: number, now?: number): ConnectAttempt;
355
+ /** Call the hook with exactly the four fields; a throwing hook never breaks sign-in. */
356
+ declare function reportAttempt(hook: AttemptHook | undefined, a: ConnectAttempt): void;
249
357
 
250
- export { restorePairing as A, type BuyTokenRequest as B, type ConnectedWallet as C, type DiscoverWalletsOptions as D, tip as E, type HandCashAdapterOptions as H, type LegacyPayer as L, type PayRequest as P, type SocketFactory as S, type TokenQuote as T, type WalletOption as W, type PayResult as a, type PayOptions as b, PaymentError as c, brc100Option as d, discoverWallets as e, type PairingOptions as f, BRC29_PROTOCOL as g, handcashAdapter as h, type Brc29Remittance as i, type PairConnectionOptions as j, type PairStorage as k, type PairedWallet as l, type PayOutput as m, type PaySource as n, type PaymentErrorCode as o, pairingOption as p, type SocketLike as q, type WalletKind as r, amountInSats as s, buyToken as t, clearPairing as u, isIdentityKeyHex as v, isPaymail as w, pairBwalletX as x, pay as y, resolvePaymailP2P as z };
358
+ export { pay as $, type AttemptHook as A, type BuyTokenRequest as B, type ConnectedWallet as C, DESKTOP_OPTION_ID as D, type WalletChoice as E, type WalletKind as F, amountInSats as G, type HandCashAdapterOptions as H, attemptFor as I, buyToken as J, chooserOrder as K, type LegacyPayer as L, clearPairing as M, cwiIsAnnounced as N, decideWallet as O, type PayRequest as P, desktopOption as Q, hasBwalletX as R, type Substrate as S, type TokenQuote as T, isIdentityKeyHex as U, isPaymail as V, type WalletOption as W, isPresentBwalletX as X, otherDetected as Y, outcomeOf as Z, pairBwalletX as _, type PayResult as a, reportAttempt as a0, resolvePaymailP2P as a1, restorePairing as a2, substrateOf as a3, tip as a4, walletTypeOf as a5, type PayOptions as b, PaymentError as c, autoChoice as d, brc100Option as e, discoverWallets as f, type PairingOptions as g, handcashAdapter as h, type ChoiceReason as i, type AttemptOutcome as j, BRC29_PROTOCOL as k, BWALLETX_TYPE as l, type Brc29Remittance as m, type ConnectAttempt as n, type ConnectOptions as o, pairingOption as p, type DecideWalletOptions as q, type DiscoverWalletsOptions as r, type PairConnectionOptions as s, type PairStorage as t, type PairedWallet as u, type PayOutput as v, type PaySource as w, type PaymentErrorCode as x, type SocketFactory as y, type SocketLike as z };
@@ -0,0 +1,222 @@
1
+ import {
2
+ SIGNIN_CSS,
3
+ clearPairing,
4
+ connectBwalletX,
5
+ discoverBwalletX,
6
+ markSvg,
7
+ pairingModal
8
+ } from "./chunk-LS6AYEBO.js";
9
+
10
+ // src/wallet-button.ts
11
+ var BUTTON_TAG = "bwalletx-button";
12
+ var CONNECTED_KEY = "bwx:connected";
13
+ function dropLegacyConnectedKey() {
14
+ try {
15
+ const had = globalThis.localStorage?.getItem(CONNECTED_KEY) != null;
16
+ if (had) globalThis.localStorage?.removeItem(CONNECTED_KEY);
17
+ return had;
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+ var LOOKUP = "https://pay.bwallet.space/api/paymail/lookup?key=";
23
+ var LEGACY = '[data-bwx-connect], .nav-connect, .nav-acct, a[href^="https://bwalletx.com/get"], a[href^="https://www.bwalletx.com/get"]';
24
+ function getUrlFor(ua, touchMac = false) {
25
+ if (/iPhone|iPad|iPod/i.test(ua) || touchMac && /Macintosh/.test(ua)) return "https://bwalletx.com/iphone";
26
+ if (/Android/i.test(ua)) return "https://bwalletx.com/android";
27
+ if (/Chrome\//.test(ua) && !/Edg\/|OPR\//.test(ua)) return "https://bwalletx.com/extension";
28
+ return "https://bwalletx.com/get";
29
+ }
30
+ function stateFor(o) {
31
+ if (o.connected) return "connected";
32
+ if (o.found === null) return o.phone ? "available" : "checking";
33
+ return o.found || o.phone ? "available" : "none";
34
+ }
35
+ function handleLabel(key, paymail) {
36
+ if (paymail && /@bwalletx\.com$/i.test(paymail)) return "$" + paymail.split("@")[0];
37
+ if (paymail) return paymail;
38
+ return key.slice(0, 6) + "\u2026" + key.slice(-4);
39
+ }
40
+ var CHIP_CSS = `
41
+ a.b,button.b{all:unset;box-sizing:border-box}
42
+ .chip{background:#0b0b0c;color:#ffd24d;border:1px solid rgba(255,210,77,.45);box-shadow:none}
43
+ .chip:hover{background:#151517;border-color:#ffd24d}
44
+ .chip .dot{width:8px;height:8px;border-radius:50%;background:#34d399;flex:none}
45
+ .txt b{overflow:hidden;text-overflow:ellipsis;max-width:min(40vw,220px)}
46
+ :host(:not([size])) button,:host(:not([size])) a.btn,:host([size=sm]) a.btn{padding:6px 12px 6px 6px;border-radius:8px;font-size:13px;gap:8px;min-height:36px}
47
+ :host(:not([size])) .mark,:host([size=sm]) a.btn .mark{width:22px;height:22px}
48
+ a.btn{all:unset;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:12px;cursor:pointer;
49
+ padding:16px 20px;border-radius:12px;font-size:16px;font-weight:700;line-height:1.2;background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;
50
+ box-shadow:0 10px 15px -3px rgba(245,158,11,.2),0 4px 6px -4px rgba(245,158,11,.2)}
51
+ a.btn:hover{background:linear-gradient(180deg,#fde68a,#fbbf24)}
52
+ a.btn:focus-visible{outline:2px solid #b45309;outline-offset:3px}
53
+ :host([size=md]) a.btn{padding:10px 16px;border-radius:10px;font-size:15px;gap:10px}
54
+ `;
55
+ var live = /* @__PURE__ */ new Set();
56
+ function createClass() {
57
+ return class BwalletxButtonElement extends HTMLElement {
58
+ #root;
59
+ #state = "checking";
60
+ #key = null;
61
+ #paymail = null;
62
+ #duplicate = false;
63
+ constructor() {
64
+ super();
65
+ this.#root = this.attachShadow({ mode: "open" });
66
+ }
67
+ get state() {
68
+ return this.#state;
69
+ }
70
+ get identityKey() {
71
+ return this.#key;
72
+ }
73
+ connectedCallback() {
74
+ if (live.size && ![...live].includes(this)) {
75
+ this.#duplicate = true;
76
+ this.hidden = true;
77
+ console.warn("[bwalletx] A second <bwalletx-button> is on this page; it is hidden. Use one per page.");
78
+ return;
79
+ }
80
+ live.add(this);
81
+ this.#hideLegacy();
82
+ dropLegacyConnectedKey();
83
+ const phone = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
84
+ this.#set(stateFor({ connected: false, found: null, phone }));
85
+ const ms = Number(this.getAttribute("discover-ms")) || 800;
86
+ void discoverBwalletX(ms).then((f) => {
87
+ if (!this.isConnected) return;
88
+ if (this.#state !== "connected" && this.#state !== "connecting") this.#set(stateFor({ connected: false, found: !!f, phone }));
89
+ }).catch(() => {
90
+ if (this.#state === "checking") this.#set("none");
91
+ });
92
+ void this.#restoreSession();
93
+ }
94
+ /** A live session your server reports (session-url): the chip, with no wallet call. */
95
+ async #restoreSession() {
96
+ const url = this.getAttribute("session-url");
97
+ if (!url) return;
98
+ try {
99
+ const r = await fetch(url, { credentials: "include", headers: { accept: "application/json" } });
100
+ const j = await r.json().catch(() => ({}));
101
+ if (j.identityKey && this.isConnected && this.#state !== "connected" && this.#state !== "connecting") {
102
+ this.#connected(j.identityKey);
103
+ if (j.paymail) {
104
+ this.#paymail = j.paymail;
105
+ this.#render();
106
+ }
107
+ }
108
+ } catch {
109
+ }
110
+ }
111
+ disconnectedCallback() {
112
+ if (!this.#duplicate) live.delete(this);
113
+ }
114
+ #hideLegacy() {
115
+ const scope = this.closest("header, nav") ?? document;
116
+ for (const el of Array.from(scope.querySelectorAll(LEGACY))) {
117
+ if (el === this || el.contains(this)) continue;
118
+ el.hidden = true;
119
+ el.style.setProperty("display", "none", "important");
120
+ console.warn("[bwalletx] Hid a second wallet control next to <bwalletx-button>:", el, ". Use only <bwalletx-button>; don't add your own Get or Connect links.");
121
+ }
122
+ }
123
+ #emit(type, detail) {
124
+ this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
125
+ }
126
+ #set(s) {
127
+ this.#state = s;
128
+ this.setAttribute("state", s);
129
+ this.#render();
130
+ }
131
+ #connected(key) {
132
+ this.#key = key;
133
+ this.#set("connected");
134
+ this.#emit("bwalletx-connected", { identityKey: key });
135
+ const lookup = this.getAttribute("lookup-url");
136
+ if (lookup === "off") return;
137
+ void fetch((lookup ?? LOOKUP) + encodeURIComponent(key)).then((r) => r.ok ? r.json() : null).then((j) => {
138
+ if (j && typeof j.paymail === "string" && this.#key === key) {
139
+ this.#paymail = j.paymail;
140
+ this.#render();
141
+ }
142
+ }).catch(() => void 0);
143
+ }
144
+ async connect() {
145
+ if (this.#state === "connecting" || this.#state === "connected") return;
146
+ const modal = pairingModal({ title: "Connect bWalletX" });
147
+ this.#set("connecting");
148
+ try {
149
+ const { wallet } = await connectBwalletX({ pairing: modal });
150
+ const { publicKey } = await wallet.getPublicKey({ identityKey: true });
151
+ this.#connected(publicKey);
152
+ } catch (e) {
153
+ if (e?.code === "NOT_FOUND") this.#set("none");
154
+ else {
155
+ this.#set("available");
156
+ if (e?.name !== "AbortError") console.warn("[bwalletx] connect:", e);
157
+ }
158
+ } finally {
159
+ try {
160
+ modal.close();
161
+ } catch {
162
+ }
163
+ }
164
+ }
165
+ disconnect() {
166
+ try {
167
+ clearPairing();
168
+ } catch {
169
+ }
170
+ try {
171
+ window.postMessage({ type: "bwallet:disconnect" }, location.origin);
172
+ } catch {
173
+ }
174
+ this.#key = null;
175
+ this.#paymail = null;
176
+ dropLegacyConnectedKey();
177
+ this.#set("available");
178
+ this.#emit("bwalletx-disconnected");
179
+ }
180
+ #render() {
181
+ const esc = (s2) => s2.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
182
+ const size = this.getAttribute("size") ?? "sm";
183
+ const px = size === "lg" ? 20 : size === "md" ? 17 : 15;
184
+ const mark = `<span class="mark" part="mark">${markSvg(px)}</span>`;
185
+ const css = `<style>${SIGNIN_CSS}${CHIP_CSS}</style>`;
186
+ const s = this.#state;
187
+ if (s === "none") {
188
+ const href = this.getAttribute("get-url") ?? getUrlFor(navigator.userAgent, (navigator.maxTouchPoints ?? 0) > 1);
189
+ this.#root.innerHTML = `${css}<a class="btn" part="button" href="${esc(href)}" target="_blank" rel="noopener">${mark}<span class="txt" part="text"><b part="label">Get bWalletX</b></span></a>`;
190
+ return;
191
+ }
192
+ if (s === "connected" && this.#key) {
193
+ const name = handleLabel(this.#key, this.#paymail);
194
+ this.#root.innerHTML = `${css}<button type="button" class="chip" part="button chip" title="Connected to bWalletX. Click to disconnect." aria-label="Connected as ${esc(name)}. Disconnect">${mark}<span class="txt" part="text"><b part="label">${esc(name)}</b></span><span class="dot" aria-hidden="true"></span></button>`;
195
+ this.#root.querySelector("button").onclick = () => this.disconnect();
196
+ return;
197
+ }
198
+ const busy = s === "connecting" || s === "checking";
199
+ this.#root.innerHTML = `${css}<button type="button" part="button"${busy ? ' aria-busy="true"' : ""}>${busy ? '<span class="mark" part="mark"><span class="spin" aria-hidden="true"></span></span>' : mark}<span class="txt" part="text"><b part="label">${s === "connecting" ? "Connecting\u2026" : "Connect bWalletX"}</b></span></button>`;
200
+ this.#root.querySelector("button").onclick = () => void this.connect();
201
+ }
202
+ };
203
+ }
204
+ function defineBwalletxButton(tag = BUTTON_TAG) {
205
+ if (typeof customElements === "undefined" || customElements.get(tag)) return;
206
+ customElements.define(tag, createClass());
207
+ }
208
+ function _resetBwalletxButtons() {
209
+ live.clear();
210
+ }
211
+
212
+ export {
213
+ BUTTON_TAG,
214
+ CONNECTED_KEY,
215
+ dropLegacyConnectedKey,
216
+ getUrlFor,
217
+ stateFor,
218
+ handleLabel,
219
+ defineBwalletxButton,
220
+ _resetBwalletxButtons
221
+ };
222
+ //# sourceMappingURL=chunk-34DKBQNN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/wallet-button.ts"],"sourcesContent":["/**\n * <bwalletx-button>: THE one wallet control for a site's nav (owner, 28 Oct 2026: no more duplicate Connect / Get\n * bWalletX / handle badges side by side). It renders exactly one thing, by state:\n *\n * no wallet in this browser → \"Get bWalletX\" (a link: extension / iphone / android / get, by platform)\n * a wallet here, not connected → \"Connect bWalletX\" (the extension, the in-app browser, or a QR to the phone)\n * connected → the $handle chip (gold b + handle); click to disconnect\n *\n * Phones can't be told apart from \"not installed\" (the app is a separate browser), so they get Connect: it pairs.\n * The look is the official button (SIGNIN_CSS + the gold b mark from <bwalletx-signin>).\n *\n * GUARD: one per page. A second <bwalletx-button> hides itself, and legacy controls inside the same header/nav\n * (`[data-bwx-connect]`, `.nav-connect`, `.nav-acct`, links to bwalletx.com/get) are hidden, each with a\n * console warning. Use only this element; don't add your own Get or Connect links.\n *\n * ON LOAD: discovery only (0.5.6). No wallet call, so no key leaves the wallet and no wallet shows a prompt without a\n * tap. A reload shows Connect again unless `session-url` reports a live session from your server (no wallet call);\n * one tap reconnects (bWalletX answers an approved site without a sheet). Nothing is stored: the old `bwx:connected`\n * entry held the identity key and is deleted on load.\n *\n * Attributes: size (sm | md | lg, default sm), get-url (overrides the platform link), discover-ms (default 800),\n * lookup-url (handle lookup, default pay.bwallet.space; \"off\" to skip), session-url (GET → { identityKey, paymail? }\n * from your server: shows the chip on load with no wallet call).\n * Events (bubble, composed): bwalletx-connected { identityKey }, bwalletx-disconnected.\n */\nimport { discoverBwalletX } from './discover.js';\nimport { clearPairing } from './pair.js';\nimport { connectBwalletX } from './signin.js';\nimport { pairingModal } from './modal.js';\nimport { markSvg, SIGNIN_CSS } from './element.js';\n\nexport const BUTTON_TAG = 'bwalletx-button';\n/** Legacy (0.5.5) localStorage key that held the identity key. Deleted on load since 0.5.6; never written. */\nexport const CONNECTED_KEY = 'bwx:connected';\n\n/** Delete the 0.5.5 `bwx:connected` entry (it held the identity key). True when one was there. */\nexport function dropLegacyConnectedKey(): boolean {\n try {\n const had = globalThis.localStorage?.getItem(CONNECTED_KEY) != null;\n if (had) globalThis.localStorage?.removeItem(CONNECTED_KEY);\n return had;\n } catch {\n return false;\n }\n}\nconst LOOKUP = 'https://pay.bwallet.space/api/paymail/lookup?key=';\nconst LEGACY = '[data-bwx-connect], .nav-connect, .nav-acct, a[href^=\"https://bwalletx.com/get\"], a[href^=\"https://www.bwalletx.com/get\"]';\n\nexport type ButtonState = 'checking' | 'none' | 'available' | 'connecting' | 'connected';\n\n/** Where \"Get bWalletX\" goes for this user agent. */\nexport function getUrlFor(ua: string, touchMac = false): string {\n if (/iPhone|iPad|iPod/i.test(ua) || (touchMac && /Macintosh/.test(ua))) return 'https://bwalletx.com/iphone';\n if (/Android/i.test(ua)) return 'https://bwalletx.com/android';\n if (/Chrome\\//.test(ua) && !/Edg\\/|OPR\\//.test(ua)) return 'https://bwalletx.com/extension';\n return 'https://bwalletx.com/get';\n}\n\n/** What a signed-out button should say before/after discovery. */\nexport function stateFor(o: { connected: boolean; found: boolean | null; phone: boolean }): ButtonState {\n if (o.connected) return 'connected';\n if (o.found === null) return o.phone ? 'available' : 'checking';\n return o.found || o.phone ? 'available' : 'none';\n}\n\n/** $alice for alice@bwalletx.com, other paymails in full, else a short key. */\nexport function handleLabel(key: string, paymail: string | null): string {\n if (paymail && /@bwalletx\\.com$/i.test(paymail)) return '$' + paymail.split('@')[0];\n if (paymail) return paymail;\n return key.slice(0, 6) + '…' + key.slice(-4);\n}\n\nconst CHIP_CSS = `\na.b,button.b{all:unset;box-sizing:border-box}\n.chip{background:#0b0b0c;color:#ffd24d;border:1px solid rgba(255,210,77,.45);box-shadow:none}\n.chip:hover{background:#151517;border-color:#ffd24d}\n.chip .dot{width:8px;height:8px;border-radius:50%;background:#34d399;flex:none}\n.txt b{overflow:hidden;text-overflow:ellipsis;max-width:min(40vw,220px)}\n:host(:not([size])) button,:host(:not([size])) a.btn,:host([size=sm]) a.btn{padding:6px 12px 6px 6px;border-radius:8px;font-size:13px;gap:8px;min-height:36px}\n:host(:not([size])) .mark,:host([size=sm]) a.btn .mark{width:22px;height:22px}\na.btn{all:unset;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:12px;cursor:pointer;\n padding:16px 20px;border-radius:12px;font-size:16px;font-weight:700;line-height:1.2;background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;\n box-shadow:0 10px 15px -3px rgba(245,158,11,.2),0 4px 6px -4px rgba(245,158,11,.2)}\na.btn:hover{background:linear-gradient(180deg,#fde68a,#fbbf24)}\na.btn:focus-visible{outline:2px solid #b45309;outline-offset:3px}\n:host([size=md]) a.btn{padding:10px 16px;border-radius:10px;font-size:15px;gap:10px}\n`;\n\nconst live = new Set<HTMLElement>();\n\nfunction createClass(): CustomElementConstructor {\n return class BwalletxButtonElement extends HTMLElement {\n #root: ShadowRoot;\n #state: ButtonState = 'checking';\n #key: string | null = null;\n #paymail: string | null = null;\n #duplicate = false;\n\n constructor() {\n super();\n this.#root = this.attachShadow({ mode: 'open' });\n }\n\n get state(): ButtonState { return this.#state; }\n get identityKey(): string | null { return this.#key; }\n\n connectedCallback() {\n if (live.size && ![...live].includes(this)) {\n this.#duplicate = true;\n this.hidden = true;\n console.warn('[bwalletx] A second <bwalletx-button> is on this page; it is hidden. Use one per page.');\n return;\n }\n live.add(this);\n this.#hideLegacy();\n dropLegacyConnectedKey();\n const phone = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);\n this.#set(stateFor({ connected: false, found: null, phone }));\n const ms = Number(this.getAttribute('discover-ms')) || 800;\n // Discovery only: no wallet call on load (no key leaves the wallet, no prompt without a tap).\n void discoverBwalletX(ms).then((f) => {\n if (!this.isConnected) return;\n if (this.#state !== 'connected' && this.#state !== 'connecting') this.#set(stateFor({ connected: false, found: !!f, phone }));\n }).catch(() => { if (this.#state === 'checking') this.#set('none'); });\n void this.#restoreSession();\n }\n\n /** A live session your server reports (session-url): the chip, with no wallet call. */\n async #restoreSession() {\n const url = this.getAttribute('session-url');\n if (!url) return;\n try {\n const r = await fetch(url, { credentials: 'include', headers: { accept: 'application/json' } });\n const j = (await r.json().catch(() => ({}))) as { identityKey?: string | null; paymail?: string | null };\n if (j.identityKey && this.isConnected && this.#state !== 'connected' && this.#state !== 'connecting') {\n this.#connected(j.identityKey);\n if (j.paymail) { this.#paymail = j.paymail; this.#render(); }\n }\n } catch { /* no session */ }\n }\n\n disconnectedCallback() { if (!this.#duplicate) live.delete(this); }\n\n\n #hideLegacy() {\n const scope = this.closest('header, nav') ?? document;\n for (const el of Array.from(scope.querySelectorAll<HTMLElement>(LEGACY))) {\n if (el === this || el.contains(this)) continue;\n el.hidden = true;\n el.style.setProperty('display', 'none', 'important');\n console.warn('[bwalletx] Hid a second wallet control next to <bwalletx-button>:', el, '. Use only <bwalletx-button>; don\\'t add your own Get or Connect links.');\n }\n }\n\n #emit(type: string, detail?: unknown) { this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true })); }\n\n #set(s: ButtonState) { this.#state = s; this.setAttribute('state', s); this.#render(); }\n\n #connected(key: string) {\n this.#key = key; this.#set('connected');\n this.#emit('bwalletx-connected', { identityKey: key });\n const lookup = this.getAttribute('lookup-url');\n if (lookup === 'off') return;\n void fetch((lookup ?? LOOKUP) + encodeURIComponent(key)).then((r) => (r.ok ? r.json() : null)).then((j: { paymail?: unknown } | null) => {\n if (j && typeof j.paymail === 'string' && this.#key === key) { this.#paymail = j.paymail; this.#render(); }\n }).catch(() => undefined);\n }\n\n async connect() {\n if (this.#state === 'connecting' || this.#state === 'connected') return;\n const modal = pairingModal({ title: 'Connect bWalletX' });\n this.#set('connecting');\n try {\n const { wallet } = await connectBwalletX({ pairing: modal });\n const { publicKey } = await wallet.getPublicKey({ identityKey: true });\n this.#connected(publicKey);\n } catch (e) {\n if ((e as { code?: string })?.code === 'NOT_FOUND') this.#set('none');\n else { this.#set('available'); if ((e as { name?: string })?.name !== 'AbortError') console.warn('[bwalletx] connect:', e); }\n } finally {\n try { modal.close(); } catch { /* closed */ }\n }\n }\n\n disconnect() {\n try { clearPairing(); } catch { /* none */ }\n try { window.postMessage({ type: 'bwallet:disconnect' }, location.origin); } catch { /* no window */ }\n this.#key = null; this.#paymail = null; dropLegacyConnectedKey();\n this.#set('available');\n this.#emit('bwalletx-disconnected');\n }\n\n #render() {\n const esc = (s: string) => s.replace(/[&<>\"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' })[c]!);\n const size = this.getAttribute('size') ?? 'sm';\n const px = size === 'lg' ? 20 : size === 'md' ? 17 : 15;\n const mark = `<span class=\"mark\" part=\"mark\">${markSvg(px)}</span>`;\n const css = `<style>${SIGNIN_CSS}${CHIP_CSS}</style>`;\n const s = this.#state;\n if (s === 'none') {\n const href = this.getAttribute('get-url') ?? getUrlFor(navigator.userAgent, (navigator.maxTouchPoints ?? 0) > 1);\n this.#root.innerHTML = `${css}<a class=\"btn\" part=\"button\" href=\"${esc(href)}\" target=\"_blank\" rel=\"noopener\">${mark}<span class=\"txt\" part=\"text\"><b part=\"label\">Get bWalletX</b></span></a>`;\n return;\n }\n if (s === 'connected' && this.#key) {\n const name = handleLabel(this.#key, this.#paymail);\n this.#root.innerHTML = `${css}<button type=\"button\" class=\"chip\" part=\"button chip\" title=\"Connected to bWalletX. Click to disconnect.\" aria-label=\"Connected as ${esc(name)}. Disconnect\">${mark}<span class=\"txt\" part=\"text\"><b part=\"label\">${esc(name)}</b></span><span class=\"dot\" aria-hidden=\"true\"></span></button>`;\n this.#root.querySelector('button')!.onclick = () => this.disconnect();\n return;\n }\n const busy = s === 'connecting' || s === 'checking';\n this.#root.innerHTML = `${css}<button type=\"button\" part=\"button\"${busy ? ' aria-busy=\"true\"' : ''}>${busy ? '<span class=\"mark\" part=\"mark\"><span class=\"spin\" aria-hidden=\"true\"></span></span>' : mark}<span class=\"txt\" part=\"text\"><b part=\"label\">${s === 'connecting' ? 'Connecting…' : 'Connect bWalletX'}</b></span></button>`;\n this.#root.querySelector('button')!.onclick = () => void this.connect();\n }\n };\n}\n\n/** Register <bwalletx-button> (idempotent; no-op on a server). */\nexport function defineBwalletxButton(tag = BUTTON_TAG): void {\n if (typeof customElements === 'undefined' || customElements.get(tag)) return;\n customElements.define(tag, createClass());\n}\n\n/** Test hook: forget which button owns the page. */\nexport function _resetBwalletxButtons() { live.clear(); }\n"],"mappings":"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","names":["s"]}
@@ -572,12 +572,14 @@ function randomNonce2() {
572
572
  return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
573
573
  }
574
574
  async function createChallenge(opts) {
575
- if (!isIdentityKey(opts.identityKey)) throw new Error("identityKey must be a compressed public key (66 hex chars)");
575
+ const given = opts.identityKey !== void 0 && opts.identityKey !== null && opts.identityKey !== "";
576
+ if (given && !isIdentityKey(opts.identityKey)) throw new Error("identityKey must be a compressed public key (66 hex chars)");
577
+ const boundKey = given ? opts.identityKey.toLowerCase() : "";
576
578
  const origin = normalOrigin2(opts.origin);
577
579
  const nonce = randomNonce2();
578
580
  const expiresAt = Date.now() + CHALLENGE_LIFETIME_MS;
579
581
  const message = loginMessage(origin, nonce, expiresAt);
580
- await storeOrDefault(opts.store).put(nonce, { identityKey: opts.identityKey.toLowerCase(), origin, message, expiresAt });
582
+ await storeOrDefault(opts.store).put(nonce, { identityKey: boundKey, origin, message, expiresAt });
581
583
  return { nonce, message, protocolID: LOGIN_PROTOCOL, keyID: nonce, expiresAt };
582
584
  }
583
585
  async function verifySignIn(opts) {
@@ -593,7 +595,7 @@ async function verifySignIn(opts) {
593
595
  if (c.origin !== origin) return null;
594
596
  if (!isIdentityKey(opts.identityKey)) return null;
595
597
  const identityKey = opts.identityKey.toLowerCase();
596
- if (c.identityKey !== identityKey) return null;
598
+ if (c.identityKey && c.identityKey !== identityKey) return null;
597
599
  const sig = opts.signature;
598
600
  if (!Array.isArray(sig) || sig.length < 8 || sig.length > 80 || !sig.every((b) => Number.isInteger(b) && b >= 0 && b <= 255)) {
599
601
  return null;
@@ -649,4 +651,4 @@ export {
649
651
  verifySignIn,
650
652
  originAllowed
651
653
  };
652
- //# sourceMappingURL=chunk-QRXFL66W.js.map
654
+ //# sourceMappingURL=chunk-A2WLGS25.js.map