@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/dist/index.cjs CHANGED
@@ -36,6 +36,7 @@ __export(src_exports, {
36
36
  AccessError: () => AccessError,
37
37
  BRC29_PROTOCOL: () => BRC29_PROTOCOL,
38
38
  BWALLETX_RDNS: () => BWALLETX_RDNS,
39
+ DEFAULT_DISCOVER_MS: () => DEFAULT_DISCOVER_MS,
39
40
  GET_URL: () => GET_URL,
40
41
  LOGIN_PROTOCOL: () => LOGIN_PROTOCOL,
41
42
  LOGO_URL: () => LOGO_URL,
@@ -45,15 +46,18 @@ __export(src_exports, {
45
46
  RDNS_MOBILE: () => RDNS_MOBILE,
46
47
  SIGNIN_CSS: () => SIGNIN_CSS,
47
48
  SIGNIN_TAG: () => SIGNIN_TAG,
49
+ WALLET_THEME_META: () => WALLET_THEME_META,
48
50
  WEB_WALLET_URL: () => WEB_WALLET_URL,
49
51
  accessErrorMessage: () => accessErrorMessage,
50
52
  amountInSats: () => amountInSats,
53
+ bindPrewarm: () => bindPrewarm,
51
54
  brc100Option: () => brc100Option,
52
55
  buildAuthorizeUrl: () => buildAuthorizeUrl,
53
56
  buyToken: () => buyToken,
54
57
  canonicalJson: () => canonicalJson,
55
58
  clearPairing: () => clearPairing,
56
59
  connectBwalletX: () => connectBwalletX,
60
+ declareWalletTheme: () => declareWalletTheme,
57
61
  defineBwalletxSignin: () => defineBwalletxSignin,
58
62
  discoverBwalletX: () => discoverBwalletX,
59
63
  discoverWallets: () => discoverWallets,
@@ -62,12 +66,14 @@ __export(src_exports, {
62
66
  inOrder: () => inOrder,
63
67
  isIdentityKeyHex: () => isIdentityKeyHex,
64
68
  isPaymail: () => isPaymail,
69
+ isTouchDevice: () => isTouchDevice,
65
70
  markSvg: () => markSvg,
66
71
  pairBwalletX: () => pairBwalletX,
67
72
  pairingModal: () => pairingModal,
68
73
  pairingOption: () => pairingOption,
69
74
  pay: () => pay,
70
75
  pickWallet: () => pickWallet,
76
+ prewarmBwalletX: () => prewarmBwalletX,
71
77
  protocolMediaUrl: () => protocolMediaUrl,
72
78
  proveHoldings: () => proveHoldings,
73
79
  qrSvg: () => qrSvg,
@@ -84,16 +90,20 @@ __export(src_exports, {
84
90
  module.exports = __toCommonJS(src_exports);
85
91
 
86
92
  // src/discover.ts
93
+ var import_sdk = require("@bsv/sdk");
87
94
  var RDNS_EXTENSION = "com.bwalletx.extension";
88
95
  var RDNS_MOBILE = "space.bwallet.mobile";
89
96
  var BWALLETX_RDNS = [RDNS_EXTENSION, RDNS_MOBILE];
90
- function discoverBwalletX(ms = 1200) {
97
+ var DEFAULT_DISCOVER_MS = 300;
98
+ function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
91
99
  if (typeof window === "undefined") return Promise.resolve(null);
92
100
  return new Promise((resolve) => {
93
101
  let done = false;
102
+ let timer;
94
103
  const finish = (r) => {
95
104
  if (done) return;
96
105
  done = true;
106
+ if (timer !== void 0) clearTimeout(timer);
97
107
  window.removeEventListener("brc100:announceWallet", onAnnounce);
98
108
  resolve(r);
99
109
  };
@@ -104,20 +114,97 @@ function discoverBwalletX(ms = 1200) {
104
114
  finish({ wallet: d.wallet, method: rdns === RDNS_EXTENSION ? "extension" : "in-app", rdns });
105
115
  }
106
116
  };
117
+ const cwiNow = () => {
118
+ const cwi = window.CWI;
119
+ if (cwi) finish({ wallet: cwi, method: "in-app", rdns: null });
120
+ return !!cwi;
121
+ };
107
122
  window.addEventListener("brc100:announceWallet", onAnnounce);
108
123
  window.dispatchEvent(new Event("brc100:requestWallet"));
109
- setTimeout(() => {
110
- const cwi = window.CWI;
111
- finish(cwi ? { wallet: cwi, method: "in-app", rdns: null } : null);
124
+ if (done || cwiNow()) return;
125
+ if (isFramed()) {
126
+ void probeXdm(Math.max(ms, 1200)).then((w) => finish(w ? { wallet: w, method: "in-app", rdns: null } : null));
127
+ return;
128
+ }
129
+ timer = setTimeout(() => {
130
+ timer = void 0;
131
+ if (!cwiNow()) finish(null);
112
132
  }, ms);
113
133
  });
114
134
  }
115
- async function findBwalletX(ms = 1200) {
135
+ async function findBwalletX(ms = DEFAULT_DISCOVER_MS) {
116
136
  return (await discoverBwalletX(ms))?.wallet ?? null;
117
137
  }
138
+ var isFramed = () => {
139
+ try {
140
+ return window.parent !== window;
141
+ } catch {
142
+ return true;
143
+ }
144
+ };
145
+ var BWALLETX_PARENT_ORIGINS = [
146
+ "https://web.bwalletx.com",
147
+ "https://desktop.bwalletx.com",
148
+ "https://bwalletx.com",
149
+ "https://www.bwallet.space",
150
+ "https://bwallet.space",
151
+ "capacitor://localhost",
152
+ // iOS app shell
153
+ "https://localhost",
154
+ // Android app shell
155
+ "tauri://localhost",
156
+ // bWalletX Desktop (macOS)
157
+ "https://tauri.localhost",
158
+ // bWalletX Desktop (Windows)
159
+ "chrome-extension://bombmmcjghjdlbmcclnhjodgcelnehgl"
160
+ // bWalletX for Chrome (fixed id)
161
+ ];
162
+ var parentOrigin = () => {
163
+ try {
164
+ const anc = window.location.ancestorOrigins;
165
+ if (anc && anc.length > 0) return anc[0] ?? null;
166
+ if (!document.referrer) return null;
167
+ const u = new URL(document.referrer);
168
+ return `${u.protocol}//${u.host}`;
169
+ } catch {
170
+ return null;
171
+ }
172
+ };
173
+ async function probeXdm(ms = 1200, allowed = BWALLETX_PARENT_ORIGINS) {
174
+ const origin = parentOrigin();
175
+ if (!origin || !allowed.includes(origin)) return null;
176
+ try {
177
+ const w = new import_sdk.WalletClient(new import_sdk.XDM(origin));
178
+ const ok = await Promise.race([
179
+ w.isAuthenticated({}).then(
180
+ () => true,
181
+ () => false
182
+ ),
183
+ new Promise((r) => setTimeout(() => r(false), ms))
184
+ ]);
185
+ return ok ? w : null;
186
+ } catch {
187
+ return null;
188
+ }
189
+ }
190
+ var warmedAt = 0;
191
+ function prewarmBwalletX() {
192
+ if (typeof window === "undefined" || Date.now() - warmedAt < 2e4) return;
193
+ warmedAt = Date.now();
194
+ void discoverBwalletX(0).then((f) => {
195
+ if (f && f.method === "extension") void f.wallet.isAuthenticated({}).catch(() => {
196
+ });
197
+ });
198
+ }
199
+ function bindPrewarm(el2) {
200
+ const on = () => prewarmBwalletX();
201
+ const evs = ["pointerenter", "focus", "touchstart"];
202
+ for (const e of evs) el2.addEventListener(e, on, { passive: true });
203
+ return () => evs.forEach((e) => el2.removeEventListener(e, on));
204
+ }
118
205
 
119
206
  // src/pair.ts
120
- var import_sdk2 = require("@bsv/sdk");
207
+ var import_sdk3 = require("@bsv/sdk");
121
208
 
122
209
  // src/in-order.ts
123
210
  function inOrder(handle, onError) {
@@ -129,7 +216,7 @@ function inOrder(handle, onError) {
129
216
  }
130
217
 
131
218
  // src/protocol.ts
132
- var import_sdk = require("@bsv/sdk");
219
+ var import_sdk2 = require("@bsv/sdk");
133
220
  var PAIR_VERSION = "1";
134
221
  var PAIR_INFO = "bwallet-pair-v1";
135
222
  var PAIR_HOST = "www.bwallet.space";
@@ -155,14 +242,26 @@ function pairUrl(link) {
155
242
  const q = new URLSearchParams({ v: link.v, r: link.r, c: link.c, k: link.k, o: link.o, e: String(link.e) });
156
243
  return `https://${PAIR_HOST}/pair?${q}`;
157
244
  }
158
- var relaySocketUrl = (relay, channel, role, expiry) => {
245
+ var newClaim = () => b64url(crypto.getRandomValues(new Uint8Array(16)));
246
+ var WALLET_CLAIM_INFO = "bwallet-pair-wallet-claim-v1";
247
+ var hex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
248
+ async function walletClaimFor(channel, sitePub) {
249
+ const d = await crypto.subtle.digest("SHA-256", enc.encode(`${WALLET_CLAIM_INFO}|${channel}|${sitePub}`));
250
+ return b64url(new Uint8Array(d));
251
+ }
252
+ async function claimDigest(claim) {
253
+ return hex(new Uint8Array(await crypto.subtle.digest("SHA-256", enc.encode(claim))));
254
+ }
255
+ var relaySocketUrl = (relay, channel, role, expiry, claim, walletClaimHash) => {
159
256
  const local = /^(localhost|127\.0\.0\.1)(:\d+)?$/.test(relay);
160
257
  const q = new URLSearchParams({ role });
161
258
  if (expiry) q.set("e", String(expiry));
259
+ if (claim) q.set("claim", claim);
260
+ if (walletClaimHash && role === "site") q.set("wc", walletClaimHash);
162
261
  return `${local ? "ws" : "wss"}://${relay}/v1/c/${channel}?${q}`;
163
262
  };
164
263
  async function deriveSession(mine, theirs, channel) {
165
- const point = mine.deriveSharedSecret(import_sdk.PublicKey.fromString(theirs));
264
+ const point = mine.deriveSharedSecret(import_sdk2.PublicKey.fromString(theirs));
166
265
  const ikm = new Uint8Array(point.encode(true));
167
266
  const base = await crypto.subtle.importKey("raw", ikm, "HKDF", false, ["deriveBits"]);
168
267
  const bits = new Uint8Array(
@@ -256,6 +355,7 @@ function parseStored(raw) {
256
355
  if (typeof p.s !== "string" || !/^[0-9a-f]{64}$/i.test(p.s)) return null;
257
356
  if (typeof p.k !== "string" || !/^0[23][0-9a-f]{64}$/i.test(p.k)) return null;
258
357
  if (!n(p.sent) || !n(p.lastSeen) || !n(p.pairedAt)) return null;
358
+ if (p.sc !== void 0 && (typeof p.sc !== "string" || !/^[A-Za-z0-9_-]{22,64}$/.test(p.sc))) return null;
259
359
  return p;
260
360
  } catch {
261
361
  return null;
@@ -313,7 +413,12 @@ var Link = class {
313
413
  await this.opening;
314
414
  }
315
415
  reopen() {
316
- const ws = this.socket(relaySocketUrl(this.p.r, this.p.c, "site", Math.floor(Date.now() / 1e3) + QR_LIFETIME_S - 10));
416
+ if (!this.p.sc) {
417
+ this.p = { ...this.p, sc: newClaim() };
418
+ this.persist();
419
+ }
420
+ const e = Math.floor(Date.now() / 1e3) + QR_LIFETIME_S - 10;
421
+ const ws = this.socket(relaySocketUrl(this.p.r, this.p.c, "site", e, this.p.sc));
317
422
  return new Promise((resolve, reject) => {
318
423
  ws.onopen = () => {
319
424
  this.ws = ws;
@@ -404,14 +509,16 @@ function pairBwalletX(opts) {
404
509
  const relay = opts.relay ?? DEFAULT_RELAY;
405
510
  const origin = opts.origin ?? (typeof location !== "undefined" ? location.origin : "");
406
511
  if (!origin) return Promise.reject(new Error("pairBwalletX needs options.origin outside a browser"));
407
- const siteKey = import_sdk2.PrivateKey.fromRandom();
512
+ const siteKey = import_sdk3.PrivateKey.fromRandom();
408
513
  const channel = newChannel();
409
514
  const expiry = Math.floor(Date.now() / 1e3) + QR_LIFETIME_S;
410
- return new Promise((resolve, reject) => {
515
+ const siteClaim = newClaim();
516
+ const sitePub = siteKey.toPublicKey().toString();
517
+ return walletClaimFor(channel, sitePub).then(claimDigest).then((wc) => new Promise((resolve, reject) => {
411
518
  if (opts.signal?.aborted) return reject(new Error("cancelled"));
412
519
  let ws;
413
520
  try {
414
- ws = socket(relaySocketUrl(relay, channel, "site", expiry));
521
+ ws = socket(relaySocketUrl(relay, channel, "site", expiry, siteClaim, wc));
415
522
  } catch (e) {
416
523
  return reject(e);
417
524
  }
@@ -450,7 +557,7 @@ function pairBwalletX(opts) {
450
557
  if (msg.t === "ready") {
451
558
  settled = true;
452
559
  clearTimeout(timer);
453
- const p = { v: 1, c: channel, r: relay, s: siteKey.toHex(), k: walletKey, ...sealer.counters, pairedAt: Date.now() };
560
+ const p = { v: 1, c: channel, r: relay, s: siteKey.toHex(), k: walletKey, ...sealer.counters, pairedAt: Date.now(), sc: siteClaim };
454
561
  await Promise.resolve(storage.setItem(key, JSON.stringify(p))).catch(() => {
455
562
  });
456
563
  const link = new Link(p, sealer, storage, key, socket, opts.callTimeoutMs ?? 12e4);
@@ -460,15 +567,15 @@ function pairBwalletX(opts) {
460
567
  fail(new Error("Pairing was declined in bWalletX"));
461
568
  }
462
569
  });
463
- opts.onLink(pairUrl({ v: PAIR_VERSION, r: relay, c: channel, k: siteKey.toPublicKey().toString(), o: origin, e: expiry }));
464
- });
570
+ opts.onLink(pairUrl({ v: PAIR_VERSION, r: relay, c: channel, k: sitePub, o: origin, e: expiry }));
571
+ }));
465
572
  }
466
573
  async function restorePairing(opts = {}) {
467
574
  const storage = opts.storage === void 0 ? defaultStorage() : opts.storage ?? memoryStorage();
468
575
  const key = opts.storageKey ?? DEFAULT_KEY;
469
576
  const p = parseStored(await Promise.resolve(storage.getItem(key)).catch(() => null));
470
577
  if (!p) return null;
471
- const { key: aes } = await deriveSession(import_sdk2.PrivateKey.fromHex(p.s), p.k, p.c);
578
+ const { key: aes } = await deriveSession(import_sdk3.PrivateKey.fromHex(p.s), p.k, p.c);
472
579
  const sealer = new Sealer(aes, "site");
473
580
  sealer.restore({ sent: p.sent, lastSeen: p.lastSeen });
474
581
  return new Link(p, sealer, storage, key, opts.socket ?? defaultSocket(), opts.callTimeoutMs ?? 12e4).wallet();
@@ -482,32 +589,65 @@ async function clearPairing(opts = {}) {
482
589
  // src/signin.ts
483
590
  var LOGIN_PROTOCOL = [2, "bwallet sign in"];
484
591
  async function connectBwalletX(opts = {}) {
485
- if (opts.wallet) return { wallet: opts.wallet, method: "wallet" };
486
- const found = await discoverBwalletX(opts.discoverMs);
487
- if (found) return { wallet: found.wallet, method: found.method };
592
+ const status = (s) => {
593
+ try {
594
+ opts.onStatus?.(s);
595
+ } catch {
596
+ }
597
+ };
598
+ if (opts.wallet) {
599
+ status("opening");
600
+ return { wallet: opts.wallet, method: "wallet" };
601
+ }
602
+ status("finding");
603
+ const found = await discoverBwalletX(opts.discoverMs ?? DEFAULT_DISCOVER_MS);
604
+ if (found) {
605
+ status("opening");
606
+ return { wallet: found.wallet, method: found.method };
607
+ }
488
608
  if (opts.reusePairing !== false) {
489
609
  const restored = await restorePairing(opts.pairing ?? {});
490
- if (restored) return { wallet: restored, method: "pairing" };
610
+ if (restored) {
611
+ status("opening");
612
+ return { wallet: restored, method: "pairing" };
613
+ }
491
614
  }
492
615
  if (!opts.pairing) {
493
616
  throw Object.assign(new Error("bWalletX was not found in this browser"), { code: "NOT_FOUND" });
494
617
  }
618
+ status("pairing");
495
619
  return { wallet: await pairBwalletX(opts.pairing), method: "pairing" };
496
620
  }
497
621
  async function signInWithBwalletX(opts) {
498
622
  const { wallet, method } = await connectBwalletX(opts);
499
623
  if (method !== "pairing") await wallet.waitForAuthentication({}).catch(() => {
500
624
  });
501
- const { publicKey: identityKey } = await wallet.getPublicKey({ identityKey: true });
502
- const c = await opts.challenge(identityKey);
503
- const { signature } = await wallet.createSignature({
504
- protocolID: c.protocolID ?? LOGIN_PROTOCOL,
505
- keyID: c.keyID,
506
- counterparty: "anyone",
507
- data: Array.from(new TextEncoder().encode(c.message))
508
- });
625
+ const { publicKey: identityKey } = await atStage("identity", () => wallet.getPublicKey({ identityKey: true }));
626
+ try {
627
+ opts.onStatus?.("signing");
628
+ } catch {
629
+ }
630
+ const c = await atStage("challenge", () => opts.challenge(identityKey));
631
+ const { signature } = await atStage(
632
+ "signature",
633
+ () => wallet.createSignature({
634
+ protocolID: c.protocolID ?? LOGIN_PROTOCOL,
635
+ keyID: c.keyID,
636
+ counterparty: "anyone",
637
+ data: Array.from(new TextEncoder().encode(c.message))
638
+ })
639
+ );
509
640
  return { identityKey, signature: Array.from(signature), nonce: c.nonce, method, wallet };
510
641
  }
642
+ async function atStage(stage, run) {
643
+ try {
644
+ return await run();
645
+ } catch (e) {
646
+ const err = e instanceof Error ? e : new Error(String(e));
647
+ if (!err.stage) Object.assign(err, { stage });
648
+ throw err;
649
+ }
650
+ }
511
651
 
512
652
  // src/button.ts
513
653
  var CSS = `
@@ -554,7 +694,7 @@ function renderButton(el2, opts = {}) {
554
694
  }
555
695
 
556
696
  // src/access-client.ts
557
- var import_sdk3 = require("@bsv/sdk");
697
+ var import_sdk4 = require("@bsv/sdk");
558
698
 
559
699
  // src/access.ts
560
700
  var ACCESS_VERSION = 1;
@@ -740,7 +880,7 @@ async function proveHoldings(wallet, challenge, tokenId, basket = "bsv21") {
740
880
  const counterparty = ci.counterparty ?? "self";
741
881
  for (const protocolID of ci.protocolID ? [ci.protocolID] : [ONESAT, LEGACY_ONESAT]) {
742
882
  const { publicKey } = await wallet.getPublicKey({ protocolID, keyID: ci.keyID, counterparty });
743
- const h = import_sdk3.Utils.toHex(import_sdk3.Hash.hash160(import_sdk3.PublicKey.fromString(publicKey).encode(true)));
883
+ const h = import_sdk4.Utils.toHex(import_sdk4.Hash.hash160(import_sdk4.PublicKey.fromString(publicKey).encode(true)));
744
884
  if (want && h !== want) continue;
745
885
  const existing = byKey.get(publicKey);
746
886
  if (existing) {
@@ -936,6 +1076,7 @@ function createClass() {
936
1076
  const nf = this.#notFound ? `<p class="nf" part="not-found" role="status">bWalletX wasn't found in this browser. <a href="${esc(getUrl)}" target="_blank" rel="noopener">Get ${brand}</a> or <a href="${esc(webUrl)}" target="_blank" rel="noopener">use the web wallet</a>.</p>` : "";
937
1077
  this.#root.innerHTML = `<style>${SIGNIN_CSS}</style><button type="button" part="button" aria-label="${esc(aria)}" title="${esc(subtitle ? `${text} (${subtitle})` : text)}"${disabled ? " disabled" : ""}${loading ? ' aria-busy="true"' : ""}><span class="mark" part="mark">${mark}</span>${label}</button>${loading ? '<span class="sr" role="status">Connecting to bWalletX\u2026</span>' : ""}${nf}`;
938
1078
  this.button.addEventListener("click", () => void this.#click());
1079
+ bindPrewarm(this.button);
939
1080
  }
940
1081
  #emit(type, detail, cancelable = false) {
941
1082
  return this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable }));
@@ -962,7 +1103,7 @@ function createClass() {
962
1103
  this.#notFound = false;
963
1104
  this.#setBusy(true);
964
1105
  try {
965
- const ms = Number(this.#attr("discover-ms")) || 1200;
1106
+ const ms = Number(this.#attr("discover-ms")) || DEFAULT_DISCOVER_MS;
966
1107
  const found = await discoverBwalletX(ms);
967
1108
  if (!found) {
968
1109
  this.#notFound = true;
@@ -1011,7 +1152,7 @@ function defineBwalletxSignin(tag = SIGNIN_TAG) {
1011
1152
  }
1012
1153
 
1013
1154
  // src/pay.ts
1014
- var import_sdk4 = require("@bsv/sdk");
1155
+ var import_sdk5 = require("@bsv/sdk");
1015
1156
  var BRC29_PROTOCOL = [2, "3241645161d8"];
1016
1157
  var PaymentError = class extends Error {
1017
1158
  constructor(code, message, maybeSent = false, cause) {
@@ -1031,13 +1172,13 @@ var isPaymail = (s) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
1031
1172
  function randomB64(n) {
1032
1173
  const b = new Uint8Array(n);
1033
1174
  globalThis.crypto.getRandomValues(b);
1034
- return import_sdk4.Utils.toBase64(Array.from(b));
1175
+ return import_sdk5.Utils.toBase64(Array.from(b));
1035
1176
  }
1036
1177
  function opReturn(text) {
1037
- const d = import_sdk4.Utils.toArray(text, "utf8");
1178
+ const d = import_sdk5.Utils.toArray(text, "utf8");
1038
1179
  const len = d.length;
1039
1180
  const push = len < 76 ? [len] : len <= 255 ? [76, len] : [77, len & 255, len >> 8];
1040
- return import_sdk4.Utils.toHex([0, 106, ...push, ...d]);
1181
+ return import_sdk5.Utils.toHex([0, 106, ...push, ...d]);
1041
1182
  }
1042
1183
  var clip = (s, n) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
1043
1184
  var describe = (s) => clip(s.length < 5 ? `${s} payment`.trim() : s, 50);
@@ -1090,11 +1231,11 @@ async function resolvePaymailP2P(paymail, sats, f = fetch) {
1090
1231
  return {
1091
1232
  outputs: body.outputs.map((o) => ({ script: o.script, satoshis: o.satoshis })),
1092
1233
  reference: body.reference,
1093
- submit: receive ? async (hex) => {
1234
+ submit: receive ? async (hex2) => {
1094
1235
  await f(fill(receive), {
1095
1236
  method: "POST",
1096
1237
  headers: { "content-type": "application/json" },
1097
- body: JSON.stringify({ hex, reference: body.reference, metadata: {} })
1238
+ body: JSON.stringify({ hex: hex2, reference: body.reference, metadata: {} })
1098
1239
  });
1099
1240
  } : void 0
1100
1241
  };
@@ -1133,7 +1274,7 @@ async function pay(source, req, o = {}) {
1133
1274
  } catch (e) {
1134
1275
  throw new PaymentError("WALLET_ERROR", `The wallet could not derive the payment key: ${e.message}`, false, e);
1135
1276
  }
1136
- outputs = [{ script: new import_sdk4.P2PKH().lock(import_sdk4.PublicKey.fromString(pk).toHash()).toHex(), satoshis: sats, description: "payment" }];
1277
+ outputs = [{ script: new import_sdk5.P2PKH().lock(import_sdk5.PublicKey.fromString(pk).toHash()).toHex(), satoshis: sats, description: "payment" }];
1137
1278
  remittance = { senderIdentityKey: me, derivationPrefix, derivationSuffix, outputIndex: 0 };
1138
1279
  } else if (isPaymail(to)) {
1139
1280
  const r2 = await (o.resolvePaymail ?? ((p, s) => resolvePaymailP2P(p, s, o.fetch ?? fetch)))(to, sats);
@@ -1141,7 +1282,7 @@ async function pay(source, req, o = {}) {
1141
1282
  submit = r2.submit;
1142
1283
  } else {
1143
1284
  try {
1144
- outputs = [{ script: new import_sdk4.P2PKH().lock(to).toHex(), satoshis: sats, description: "payment" }];
1285
+ outputs = [{ script: new import_sdk5.P2PKH().lock(to).toHex(), satoshis: sats, description: "payment" }];
1145
1286
  } catch {
1146
1287
  throw new PaymentError("BAD_REQUEST", `Not a paymail, address or identity key: ${to}`);
1147
1288
  }
@@ -1151,7 +1292,7 @@ async function pay(source, req, o = {}) {
1151
1292
  if (!r.txid) throw new PaymentError("WALLET_ERROR", "The wallet did not return a txid", true);
1152
1293
  const tx = r.tx ? Array.from(r.tx) : void 0;
1153
1294
  if (submit && tx) {
1154
- await submit(import_sdk4.Transaction.fromAtomicBEEF(tx).toHex()).catch(() => {
1295
+ await submit(import_sdk5.Transaction.fromAtomicBEEF(tx).toHex()).catch(() => {
1155
1296
  });
1156
1297
  }
1157
1298
  return { txid: r.txid, tx, sats, outputs, remittance };
@@ -1172,7 +1313,7 @@ async function buyToken(source, req) {
1172
1313
  }
1173
1314
 
1174
1315
  // src/wallets.ts
1175
- var import_sdk5 = require("@bsv/sdk");
1316
+ var import_sdk6 = require("@bsv/sdk");
1176
1317
  async function brc100Connected(wallet, o) {
1177
1318
  await wallet.waitForAuthentication({}).catch(() => {
1178
1319
  });
@@ -1231,7 +1372,7 @@ function handcashAdapter(o) {
1231
1372
  var NAMES = { [RDNS_EXTENSION]: "bWalletX", "space.bwallet.mobile": "bWalletX" };
1232
1373
  async function probe(substrate, ms) {
1233
1374
  try {
1234
- const w = new import_sdk5.WalletClient(substrate);
1375
+ const w = new import_sdk6.WalletClient(substrate);
1235
1376
  const ok = await Promise.race([w.isAuthenticated({}).then(() => true), new Promise((r) => setTimeout(() => r(false), ms))]);
1236
1377
  return ok ? w : null;
1237
1378
  } catch {
@@ -1240,7 +1381,7 @@ async function probe(substrate, ms) {
1240
1381
  }
1241
1382
  async function discoverWallets(o = {}) {
1242
1383
  const out = [];
1243
- const ms = o.ms ?? 1200;
1384
+ const ms = o.ms ?? 300;
1244
1385
  if (typeof window !== "undefined") {
1245
1386
  const seen = /* @__PURE__ */ new Map();
1246
1387
  const onAnnounce = (e) => {
@@ -1254,7 +1395,7 @@ async function discoverWallets(o = {}) {
1254
1395
  window.addEventListener("brc100:announceWallet", onAnnounce);
1255
1396
  window.dispatchEvent(new Event("brc100:requestWallet"));
1256
1397
  const local = o.probeLocal ? Promise.all([probe("json-api", ms), probe("Cicada", ms)]) : Promise.resolve([null, null]);
1257
- await new Promise((r) => setTimeout(r, ms));
1398
+ await new Promise((r) => setTimeout(r, seen.size > 0 || window.CWI ? Math.min(ms, 50) : ms));
1258
1399
  window.removeEventListener("brc100:announceWallet", onAnnounce);
1259
1400
  out.push(...seen.values());
1260
1401
  const cwi = window.CWI;
@@ -1284,6 +1425,9 @@ var CSS2 = `
1284
1425
  .bwx-row{display:flex;gap:8px;margin-top:14px}
1285
1426
  .bwx-btn{flex:1;border:1px solid #3a3220;background:#15130e;color:#f4f1e8;border-radius:10px;padding:10px 12px;font:inherit;cursor:pointer}
1286
1427
  .bwx-btn:hover{border-color:#e8c766}
1428
+ a.bwx-btn{display:block;text-align:center;text-decoration:none;box-sizing:border-box}
1429
+ .bwx-primary{background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;border:0;font-weight:700;min-height:48px;padding:13px 12px}
1430
+ .bwx-stack{display:flex;flex-direction:column;gap:8px}
1287
1431
  .bwx-list{display:flex;flex-direction:column;gap:8px}
1288
1432
  .bwx-opt{display:flex;align-items:center;gap:10px;text-align:left}
1289
1433
  .bwx-opt img{width:24px;height:24px;border-radius:6px}
@@ -1314,6 +1458,18 @@ function qrSvg(text) {
1314
1458
  q.make();
1315
1459
  return q.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
1316
1460
  }
1461
+ function isTouchDevice(env = globalThis) {
1462
+ const nav = env.navigator;
1463
+ if (!nav) return false;
1464
+ const ua = nav.userAgent ?? "";
1465
+ if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
1466
+ if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
1467
+ try {
1468
+ return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
1469
+ } catch {
1470
+ return false;
1471
+ }
1472
+ }
1317
1473
  function pairingModal(o = {}) {
1318
1474
  const ac = new AbortController();
1319
1475
  let ui = null;
@@ -1330,20 +1486,37 @@ function pairingModal(o = {}) {
1330
1486
  if (typeof document === "undefined") return;
1331
1487
  close();
1332
1488
  ui = mount();
1489
+ const app = o.mode === "app" || (o.mode ?? "auto") === "auto" && isTouchDevice();
1333
1490
  const h = el("h2", "", o.title ?? "Connect bWalletX");
1491
+ const cancel = el("button", "bwx-btn", "Cancel");
1492
+ cancel.onclick = () => {
1493
+ o.onCancel?.();
1494
+ ac.abort();
1495
+ };
1496
+ codeEl = el("div", "bwx-code", "");
1497
+ codeEl.setAttribute("aria-live", "polite");
1498
+ if (app) {
1499
+ const p2 = el("p", "", "Open bWalletX, approve, then come back to this page.");
1500
+ const stack = el("div", "bwx-stack");
1501
+ const open = el("a", "bwx-btn bwx-primary", "Open bWalletX");
1502
+ open.href = link;
1503
+ open.rel = "noopener";
1504
+ const web = el("button", "bwx-btn", "Use web.bwalletx.com instead");
1505
+ web.onclick = () => {
1506
+ void navigator.clipboard?.writeText(link).catch(() => void 0);
1507
+ window.open(WEB_WALLET_URL, "_blank", "noopener");
1508
+ p2.textContent = "Link copied. In web.bwalletx.com tap Connect and paste it, then come back here.";
1509
+ };
1510
+ stack.append(open, web, cancel);
1511
+ ui.box.append(h, p2, codeEl, stack);
1512
+ return;
1513
+ }
1334
1514
  const p = el("p", "", "Scan with bWalletX on your phone, or paste the link into web.bwalletx.com.");
1335
1515
  const qr = el("div", "bwx-qr");
1336
1516
  qr.innerHTML = qrSvg(link);
1337
- codeEl = el("div", "bwx-code", "");
1338
- codeEl.setAttribute("aria-live", "polite");
1339
1517
  const row = el("div", "bwx-row");
1340
1518
  const copy = el("button", "bwx-btn", "Copy link");
1341
1519
  copy.onclick = () => void navigator.clipboard?.writeText(link).then(() => copy.textContent = "Copied");
1342
- const cancel = el("button", "bwx-btn", "Cancel");
1343
- cancel.onclick = () => {
1344
- o.onCancel?.();
1345
- ac.abort();
1346
- };
1347
1520
  row.append(copy, cancel);
1348
1521
  ui.box.append(h, p, qr, codeEl, row);
1349
1522
  },
@@ -1387,6 +1560,25 @@ function pickWallet(options, o = {}) {
1387
1560
  ui.box.lastChild.append(cancel);
1388
1561
  });
1389
1562
  }
1563
+
1564
+ // src/theme.ts
1565
+ var WALLET_THEME_META = "bwalletx-theme";
1566
+ var ID = /^[a-z0-9-]{1,32}$/;
1567
+ function declareWalletTheme(theme) {
1568
+ if (typeof document === "undefined") return;
1569
+ const id = (theme ?? "").trim().toLowerCase();
1570
+ let meta = document.head.querySelector(`meta[name="${WALLET_THEME_META}"]`);
1571
+ if (!id || !ID.test(id)) {
1572
+ meta?.remove();
1573
+ return;
1574
+ }
1575
+ if (!meta) {
1576
+ meta = document.createElement("meta");
1577
+ meta.name = WALLET_THEME_META;
1578
+ document.head.appendChild(meta);
1579
+ }
1580
+ if (meta.content !== id) meta.content = id;
1581
+ }
1390
1582
  // Annotate the CommonJS export names for ESM import in node:
1391
1583
  0 && (module.exports = {
1392
1584
  ACCESS_ERROR_STATUS,
@@ -1395,6 +1587,7 @@ function pickWallet(options, o = {}) {
1395
1587
  AccessError,
1396
1588
  BRC29_PROTOCOL,
1397
1589
  BWALLETX_RDNS,
1590
+ DEFAULT_DISCOVER_MS,
1398
1591
  GET_URL,
1399
1592
  LOGIN_PROTOCOL,
1400
1593
  LOGO_URL,
@@ -1404,15 +1597,18 @@ function pickWallet(options, o = {}) {
1404
1597
  RDNS_MOBILE,
1405
1598
  SIGNIN_CSS,
1406
1599
  SIGNIN_TAG,
1600
+ WALLET_THEME_META,
1407
1601
  WEB_WALLET_URL,
1408
1602
  accessErrorMessage,
1409
1603
  amountInSats,
1604
+ bindPrewarm,
1410
1605
  brc100Option,
1411
1606
  buildAuthorizeUrl,
1412
1607
  buyToken,
1413
1608
  canonicalJson,
1414
1609
  clearPairing,
1415
1610
  connectBwalletX,
1611
+ declareWalletTheme,
1416
1612
  defineBwalletxSignin,
1417
1613
  discoverBwalletX,
1418
1614
  discoverWallets,
@@ -1421,12 +1617,14 @@ function pickWallet(options, o = {}) {
1421
1617
  inOrder,
1422
1618
  isIdentityKeyHex,
1423
1619
  isPaymail,
1620
+ isTouchDevice,
1424
1621
  markSvg,
1425
1622
  pairBwalletX,
1426
1623
  pairingModal,
1427
1624
  pairingOption,
1428
1625
  pay,
1429
1626
  pickWallet,
1627
+ prewarmBwalletX,
1430
1628
  protocolMediaUrl,
1431
1629
  proveHoldings,
1432
1630
  qrSvg,