@bwalletx/connect 0.5.0 → 0.5.3

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/element.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  defineBwalletxSignin,
8
8
  markSvg,
9
9
  signinText
10
- } from "./chunk-CVLXFNAG.js";
10
+ } from "./chunk-IUDD7YTQ.js";
11
11
  import "./chunk-MWD4BCO5.js";
12
12
 
13
13
  // src/define.ts
package/dist/index.cjs CHANGED
@@ -45,6 +45,7 @@ __export(src_exports, {
45
45
  RDNS_MOBILE: () => RDNS_MOBILE,
46
46
  SIGNIN_CSS: () => SIGNIN_CSS,
47
47
  SIGNIN_TAG: () => SIGNIN_TAG,
48
+ WALLET_THEME_META: () => WALLET_THEME_META,
48
49
  WEB_WALLET_URL: () => WEB_WALLET_URL,
49
50
  accessErrorMessage: () => accessErrorMessage,
50
51
  amountInSats: () => amountInSats,
@@ -54,6 +55,7 @@ __export(src_exports, {
54
55
  canonicalJson: () => canonicalJson,
55
56
  clearPairing: () => clearPairing,
56
57
  connectBwalletX: () => connectBwalletX,
58
+ declareWalletTheme: () => declareWalletTheme,
57
59
  defineBwalletxSignin: () => defineBwalletxSignin,
58
60
  discoverBwalletX: () => discoverBwalletX,
59
61
  discoverWallets: () => discoverWallets,
@@ -62,6 +64,7 @@ __export(src_exports, {
62
64
  inOrder: () => inOrder,
63
65
  isIdentityKeyHex: () => isIdentityKeyHex,
64
66
  isPaymail: () => isPaymail,
67
+ isTouchDevice: () => isTouchDevice,
65
68
  markSvg: () => markSvg,
66
69
  pairBwalletX: () => pairBwalletX,
67
70
  pairingModal: () => pairingModal,
@@ -84,6 +87,7 @@ __export(src_exports, {
84
87
  module.exports = __toCommonJS(src_exports);
85
88
 
86
89
  // src/discover.ts
90
+ var import_sdk = require("@bsv/sdk");
87
91
  var RDNS_EXTENSION = "com.bwalletx.extension";
88
92
  var RDNS_MOBILE = "space.bwallet.mobile";
89
93
  var BWALLETX_RDNS = [RDNS_EXTENSION, RDNS_MOBILE];
@@ -108,16 +112,70 @@ function discoverBwalletX(ms = 1200) {
108
112
  window.dispatchEvent(new Event("brc100:requestWallet"));
109
113
  setTimeout(() => {
110
114
  const cwi = window.CWI;
111
- finish(cwi ? { wallet: cwi, method: "in-app", rdns: null } : null);
115
+ if (cwi) return finish({ wallet: cwi, method: "in-app", rdns: null });
116
+ if (!isFramed()) return finish(null);
117
+ void probeXdm(ms).then((w) => finish(w ? { wallet: w, method: "in-app", rdns: null } : null));
112
118
  }, ms);
113
119
  });
114
120
  }
115
121
  async function findBwalletX(ms = 1200) {
116
122
  return (await discoverBwalletX(ms))?.wallet ?? null;
117
123
  }
124
+ var isFramed = () => {
125
+ try {
126
+ return window.parent !== window;
127
+ } catch {
128
+ return true;
129
+ }
130
+ };
131
+ var BWALLETX_PARENT_ORIGINS = [
132
+ "https://web.bwalletx.com",
133
+ "https://desktop.bwalletx.com",
134
+ "https://bwalletx.com",
135
+ "https://www.bwallet.space",
136
+ "https://bwallet.space",
137
+ "capacitor://localhost",
138
+ // iOS app shell
139
+ "https://localhost",
140
+ // Android app shell
141
+ "tauri://localhost",
142
+ // bWalletX Desktop (macOS)
143
+ "https://tauri.localhost",
144
+ // bWalletX Desktop (Windows)
145
+ "chrome-extension://bombmmcjghjdlbmcclnhjodgcelnehgl"
146
+ // bWalletX for Chrome (fixed id)
147
+ ];
148
+ var parentOrigin = () => {
149
+ try {
150
+ const anc = window.location.ancestorOrigins;
151
+ if (anc && anc.length > 0) return anc[0] ?? null;
152
+ if (!document.referrer) return null;
153
+ const u = new URL(document.referrer);
154
+ return `${u.protocol}//${u.host}`;
155
+ } catch {
156
+ return null;
157
+ }
158
+ };
159
+ async function probeXdm(ms = 1200, allowed = BWALLETX_PARENT_ORIGINS) {
160
+ const origin = parentOrigin();
161
+ if (!origin || !allowed.includes(origin)) return null;
162
+ try {
163
+ const w = new import_sdk.WalletClient(new import_sdk.XDM(origin));
164
+ const ok = await Promise.race([
165
+ w.isAuthenticated({}).then(
166
+ () => true,
167
+ () => false
168
+ ),
169
+ new Promise((r) => setTimeout(() => r(false), ms))
170
+ ]);
171
+ return ok ? w : null;
172
+ } catch {
173
+ return null;
174
+ }
175
+ }
118
176
 
119
177
  // src/pair.ts
120
- var import_sdk2 = require("@bsv/sdk");
178
+ var import_sdk3 = require("@bsv/sdk");
121
179
 
122
180
  // src/in-order.ts
123
181
  function inOrder(handle, onError) {
@@ -129,7 +187,7 @@ function inOrder(handle, onError) {
129
187
  }
130
188
 
131
189
  // src/protocol.ts
132
- var import_sdk = require("@bsv/sdk");
190
+ var import_sdk2 = require("@bsv/sdk");
133
191
  var PAIR_VERSION = "1";
134
192
  var PAIR_INFO = "bwallet-pair-v1";
135
193
  var PAIR_HOST = "www.bwallet.space";
@@ -162,7 +220,7 @@ var relaySocketUrl = (relay, channel, role, expiry) => {
162
220
  return `${local ? "ws" : "wss"}://${relay}/v1/c/${channel}?${q}`;
163
221
  };
164
222
  async function deriveSession(mine, theirs, channel) {
165
- const point = mine.deriveSharedSecret(import_sdk.PublicKey.fromString(theirs));
223
+ const point = mine.deriveSharedSecret(import_sdk2.PublicKey.fromString(theirs));
166
224
  const ikm = new Uint8Array(point.encode(true));
167
225
  const base = await crypto.subtle.importKey("raw", ikm, "HKDF", false, ["deriveBits"]);
168
226
  const bits = new Uint8Array(
@@ -404,7 +462,7 @@ function pairBwalletX(opts) {
404
462
  const relay = opts.relay ?? DEFAULT_RELAY;
405
463
  const origin = opts.origin ?? (typeof location !== "undefined" ? location.origin : "");
406
464
  if (!origin) return Promise.reject(new Error("pairBwalletX needs options.origin outside a browser"));
407
- const siteKey = import_sdk2.PrivateKey.fromRandom();
465
+ const siteKey = import_sdk3.PrivateKey.fromRandom();
408
466
  const channel = newChannel();
409
467
  const expiry = Math.floor(Date.now() / 1e3) + QR_LIFETIME_S;
410
468
  return new Promise((resolve, reject) => {
@@ -468,7 +526,7 @@ async function restorePairing(opts = {}) {
468
526
  const key = opts.storageKey ?? DEFAULT_KEY;
469
527
  const p = parseStored(await Promise.resolve(storage.getItem(key)).catch(() => null));
470
528
  if (!p) return null;
471
- const { key: aes } = await deriveSession(import_sdk2.PrivateKey.fromHex(p.s), p.k, p.c);
529
+ const { key: aes } = await deriveSession(import_sdk3.PrivateKey.fromHex(p.s), p.k, p.c);
472
530
  const sealer = new Sealer(aes, "site");
473
531
  sealer.restore({ sent: p.sent, lastSeen: p.lastSeen });
474
532
  return new Link(p, sealer, storage, key, opts.socket ?? defaultSocket(), opts.callTimeoutMs ?? 12e4).wallet();
@@ -498,16 +556,28 @@ async function signInWithBwalletX(opts) {
498
556
  const { wallet, method } = await connectBwalletX(opts);
499
557
  if (method !== "pairing") await wallet.waitForAuthentication({}).catch(() => {
500
558
  });
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
- });
559
+ const { publicKey: identityKey } = await atStage("identity", () => wallet.getPublicKey({ identityKey: true }));
560
+ const c = await atStage("challenge", () => opts.challenge(identityKey));
561
+ const { signature } = await atStage(
562
+ "signature",
563
+ () => wallet.createSignature({
564
+ protocolID: c.protocolID ?? LOGIN_PROTOCOL,
565
+ keyID: c.keyID,
566
+ counterparty: "anyone",
567
+ data: Array.from(new TextEncoder().encode(c.message))
568
+ })
569
+ );
509
570
  return { identityKey, signature: Array.from(signature), nonce: c.nonce, method, wallet };
510
571
  }
572
+ async function atStage(stage, run) {
573
+ try {
574
+ return await run();
575
+ } catch (e) {
576
+ const err = e instanceof Error ? e : new Error(String(e));
577
+ if (!err.stage) Object.assign(err, { stage });
578
+ throw err;
579
+ }
580
+ }
511
581
 
512
582
  // src/button.ts
513
583
  var CSS = `
@@ -554,7 +624,7 @@ function renderButton(el2, opts = {}) {
554
624
  }
555
625
 
556
626
  // src/access-client.ts
557
- var import_sdk3 = require("@bsv/sdk");
627
+ var import_sdk4 = require("@bsv/sdk");
558
628
 
559
629
  // src/access.ts
560
630
  var ACCESS_VERSION = 1;
@@ -740,7 +810,7 @@ async function proveHoldings(wallet, challenge, tokenId, basket = "bsv21") {
740
810
  const counterparty = ci.counterparty ?? "self";
741
811
  for (const protocolID of ci.protocolID ? [ci.protocolID] : [ONESAT, LEGACY_ONESAT]) {
742
812
  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)));
813
+ const h = import_sdk4.Utils.toHex(import_sdk4.Hash.hash160(import_sdk4.PublicKey.fromString(publicKey).encode(true)));
744
814
  if (want && h !== want) continue;
745
815
  const existing = byKey.get(publicKey);
746
816
  if (existing) {
@@ -1011,7 +1081,7 @@ function defineBwalletxSignin(tag = SIGNIN_TAG) {
1011
1081
  }
1012
1082
 
1013
1083
  // src/pay.ts
1014
- var import_sdk4 = require("@bsv/sdk");
1084
+ var import_sdk5 = require("@bsv/sdk");
1015
1085
  var BRC29_PROTOCOL = [2, "3241645161d8"];
1016
1086
  var PaymentError = class extends Error {
1017
1087
  constructor(code, message, maybeSent = false, cause) {
@@ -1031,13 +1101,13 @@ var isPaymail = (s) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
1031
1101
  function randomB64(n) {
1032
1102
  const b = new Uint8Array(n);
1033
1103
  globalThis.crypto.getRandomValues(b);
1034
- return import_sdk4.Utils.toBase64(Array.from(b));
1104
+ return import_sdk5.Utils.toBase64(Array.from(b));
1035
1105
  }
1036
1106
  function opReturn(text) {
1037
- const d = import_sdk4.Utils.toArray(text, "utf8");
1107
+ const d = import_sdk5.Utils.toArray(text, "utf8");
1038
1108
  const len = d.length;
1039
1109
  const push = len < 76 ? [len] : len <= 255 ? [76, len] : [77, len & 255, len >> 8];
1040
- return import_sdk4.Utils.toHex([0, 106, ...push, ...d]);
1110
+ return import_sdk5.Utils.toHex([0, 106, ...push, ...d]);
1041
1111
  }
1042
1112
  var clip = (s, n) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
1043
1113
  var describe = (s) => clip(s.length < 5 ? `${s} payment`.trim() : s, 50);
@@ -1133,7 +1203,7 @@ async function pay(source, req, o = {}) {
1133
1203
  } catch (e) {
1134
1204
  throw new PaymentError("WALLET_ERROR", `The wallet could not derive the payment key: ${e.message}`, false, e);
1135
1205
  }
1136
- outputs = [{ script: new import_sdk4.P2PKH().lock(import_sdk4.PublicKey.fromString(pk).toHash()).toHex(), satoshis: sats, description: "payment" }];
1206
+ outputs = [{ script: new import_sdk5.P2PKH().lock(import_sdk5.PublicKey.fromString(pk).toHash()).toHex(), satoshis: sats, description: "payment" }];
1137
1207
  remittance = { senderIdentityKey: me, derivationPrefix, derivationSuffix, outputIndex: 0 };
1138
1208
  } else if (isPaymail(to)) {
1139
1209
  const r2 = await (o.resolvePaymail ?? ((p, s) => resolvePaymailP2P(p, s, o.fetch ?? fetch)))(to, sats);
@@ -1141,7 +1211,7 @@ async function pay(source, req, o = {}) {
1141
1211
  submit = r2.submit;
1142
1212
  } else {
1143
1213
  try {
1144
- outputs = [{ script: new import_sdk4.P2PKH().lock(to).toHex(), satoshis: sats, description: "payment" }];
1214
+ outputs = [{ script: new import_sdk5.P2PKH().lock(to).toHex(), satoshis: sats, description: "payment" }];
1145
1215
  } catch {
1146
1216
  throw new PaymentError("BAD_REQUEST", `Not a paymail, address or identity key: ${to}`);
1147
1217
  }
@@ -1151,7 +1221,7 @@ async function pay(source, req, o = {}) {
1151
1221
  if (!r.txid) throw new PaymentError("WALLET_ERROR", "The wallet did not return a txid", true);
1152
1222
  const tx = r.tx ? Array.from(r.tx) : void 0;
1153
1223
  if (submit && tx) {
1154
- await submit(import_sdk4.Transaction.fromAtomicBEEF(tx).toHex()).catch(() => {
1224
+ await submit(import_sdk5.Transaction.fromAtomicBEEF(tx).toHex()).catch(() => {
1155
1225
  });
1156
1226
  }
1157
1227
  return { txid: r.txid, tx, sats, outputs, remittance };
@@ -1172,7 +1242,7 @@ async function buyToken(source, req) {
1172
1242
  }
1173
1243
 
1174
1244
  // src/wallets.ts
1175
- var import_sdk5 = require("@bsv/sdk");
1245
+ var import_sdk6 = require("@bsv/sdk");
1176
1246
  async function brc100Connected(wallet, o) {
1177
1247
  await wallet.waitForAuthentication({}).catch(() => {
1178
1248
  });
@@ -1231,7 +1301,7 @@ function handcashAdapter(o) {
1231
1301
  var NAMES = { [RDNS_EXTENSION]: "bWalletX", "space.bwallet.mobile": "bWalletX" };
1232
1302
  async function probe(substrate, ms) {
1233
1303
  try {
1234
- const w = new import_sdk5.WalletClient(substrate);
1304
+ const w = new import_sdk6.WalletClient(substrate);
1235
1305
  const ok = await Promise.race([w.isAuthenticated({}).then(() => true), new Promise((r) => setTimeout(() => r(false), ms))]);
1236
1306
  return ok ? w : null;
1237
1307
  } catch {
@@ -1284,6 +1354,9 @@ var CSS2 = `
1284
1354
  .bwx-row{display:flex;gap:8px;margin-top:14px}
1285
1355
  .bwx-btn{flex:1;border:1px solid #3a3220;background:#15130e;color:#f4f1e8;border-radius:10px;padding:10px 12px;font:inherit;cursor:pointer}
1286
1356
  .bwx-btn:hover{border-color:#e8c766}
1357
+ a.bwx-btn{display:block;text-align:center;text-decoration:none;box-sizing:border-box}
1358
+ .bwx-primary{background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;border:0;font-weight:700;min-height:48px;padding:13px 12px}
1359
+ .bwx-stack{display:flex;flex-direction:column;gap:8px}
1287
1360
  .bwx-list{display:flex;flex-direction:column;gap:8px}
1288
1361
  .bwx-opt{display:flex;align-items:center;gap:10px;text-align:left}
1289
1362
  .bwx-opt img{width:24px;height:24px;border-radius:6px}
@@ -1314,6 +1387,18 @@ function qrSvg(text) {
1314
1387
  q.make();
1315
1388
  return q.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
1316
1389
  }
1390
+ function isTouchDevice(env = globalThis) {
1391
+ const nav = env.navigator;
1392
+ if (!nav) return false;
1393
+ const ua = nav.userAgent ?? "";
1394
+ if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
1395
+ if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
1396
+ try {
1397
+ return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
1398
+ } catch {
1399
+ return false;
1400
+ }
1401
+ }
1317
1402
  function pairingModal(o = {}) {
1318
1403
  const ac = new AbortController();
1319
1404
  let ui = null;
@@ -1330,20 +1415,37 @@ function pairingModal(o = {}) {
1330
1415
  if (typeof document === "undefined") return;
1331
1416
  close();
1332
1417
  ui = mount();
1418
+ const app = o.mode === "app" || (o.mode ?? "auto") === "auto" && isTouchDevice();
1333
1419
  const h = el("h2", "", o.title ?? "Connect bWalletX");
1420
+ const cancel = el("button", "bwx-btn", "Cancel");
1421
+ cancel.onclick = () => {
1422
+ o.onCancel?.();
1423
+ ac.abort();
1424
+ };
1425
+ codeEl = el("div", "bwx-code", "");
1426
+ codeEl.setAttribute("aria-live", "polite");
1427
+ if (app) {
1428
+ const p2 = el("p", "", "Open bWalletX, approve, then come back to this page.");
1429
+ const stack = el("div", "bwx-stack");
1430
+ const open = el("a", "bwx-btn bwx-primary", "Open bWalletX");
1431
+ open.href = link;
1432
+ open.rel = "noopener";
1433
+ const web = el("button", "bwx-btn", "Use web.bwalletx.com instead");
1434
+ web.onclick = () => {
1435
+ void navigator.clipboard?.writeText(link).catch(() => void 0);
1436
+ window.open(WEB_WALLET_URL, "_blank", "noopener");
1437
+ p2.textContent = "Link copied. In web.bwalletx.com tap Connect and paste it, then come back here.";
1438
+ };
1439
+ stack.append(open, web, cancel);
1440
+ ui.box.append(h, p2, codeEl, stack);
1441
+ return;
1442
+ }
1334
1443
  const p = el("p", "", "Scan with bWalletX on your phone, or paste the link into web.bwalletx.com.");
1335
1444
  const qr = el("div", "bwx-qr");
1336
1445
  qr.innerHTML = qrSvg(link);
1337
- codeEl = el("div", "bwx-code", "");
1338
- codeEl.setAttribute("aria-live", "polite");
1339
1446
  const row = el("div", "bwx-row");
1340
1447
  const copy = el("button", "bwx-btn", "Copy link");
1341
1448
  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
1449
  row.append(copy, cancel);
1348
1450
  ui.box.append(h, p, qr, codeEl, row);
1349
1451
  },
@@ -1387,6 +1489,25 @@ function pickWallet(options, o = {}) {
1387
1489
  ui.box.lastChild.append(cancel);
1388
1490
  });
1389
1491
  }
1492
+
1493
+ // src/theme.ts
1494
+ var WALLET_THEME_META = "bwalletx-theme";
1495
+ var ID = /^[a-z0-9-]{1,32}$/;
1496
+ function declareWalletTheme(theme) {
1497
+ if (typeof document === "undefined") return;
1498
+ const id = (theme ?? "").trim().toLowerCase();
1499
+ let meta = document.head.querySelector(`meta[name="${WALLET_THEME_META}"]`);
1500
+ if (!id || !ID.test(id)) {
1501
+ meta?.remove();
1502
+ return;
1503
+ }
1504
+ if (!meta) {
1505
+ meta = document.createElement("meta");
1506
+ meta.name = WALLET_THEME_META;
1507
+ document.head.appendChild(meta);
1508
+ }
1509
+ if (meta.content !== id) meta.content = id;
1510
+ }
1390
1511
  // Annotate the CommonJS export names for ESM import in node:
1391
1512
  0 && (module.exports = {
1392
1513
  ACCESS_ERROR_STATUS,
@@ -1404,6 +1525,7 @@ function pickWallet(options, o = {}) {
1404
1525
  RDNS_MOBILE,
1405
1526
  SIGNIN_CSS,
1406
1527
  SIGNIN_TAG,
1528
+ WALLET_THEME_META,
1407
1529
  WEB_WALLET_URL,
1408
1530
  accessErrorMessage,
1409
1531
  amountInSats,
@@ -1413,6 +1535,7 @@ function pickWallet(options, o = {}) {
1413
1535
  canonicalJson,
1414
1536
  clearPairing,
1415
1537
  connectBwalletX,
1538
+ declareWalletTheme,
1416
1539
  defineBwalletxSignin,
1417
1540
  discoverBwalletX,
1418
1541
  discoverWallets,
@@ -1421,6 +1544,7 @@ function pickWallet(options, o = {}) {
1421
1544
  inOrder,
1422
1545
  isIdentityKeyHex,
1423
1546
  isPaymail,
1547
+ isTouchDevice,
1424
1548
  markSvg,
1425
1549
  pairBwalletX,
1426
1550
  pairingModal,