@bwalletx/connect 0.5.6 → 0.7.0

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 (36) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +22 -2
  3. package/dist/bwalletx-connect.global.js +15 -10
  4. package/dist/bwalletx-connect.global.js.map +1 -1
  5. package/dist/{choice-DCh-tgpT.d.cts → choice-B97AjqF4.d.cts} +87 -3
  6. package/dist/{choice-DCh-tgpT.d.ts → choice-B97AjqF4.d.ts} +87 -3
  7. package/dist/{chunk-LS6AYEBO.js → chunk-6EFFOIHK.js} +596 -41
  8. package/dist/chunk-6EFFOIHK.js.map +1 -0
  9. package/dist/{chunk-34DKBQNN.js → chunk-DIKJFKND.js} +22 -5
  10. package/dist/chunk-DIKJFKND.js.map +1 -0
  11. package/dist/{chunk-IFGOAROM.js → chunk-TNRI7ARM.js} +14 -63
  12. package/dist/chunk-TNRI7ARM.js.map +1 -0
  13. package/dist/{element-D9Zl-x12.d.cts → element-BXEoOdXY.d.cts} +11 -4
  14. package/dist/{element-D9Zl-x12.d.ts → element-BXEoOdXY.d.ts} +11 -4
  15. package/dist/element.cjs +480 -50
  16. package/dist/element.cjs.map +1 -1
  17. package/dist/element.d.cts +7 -2
  18. package/dist/element.d.ts +7 -2
  19. package/dist/element.js +8 -2
  20. package/dist/element.js.map +1 -1
  21. package/dist/index.cjs +676 -100
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +169 -11
  24. package/dist/index.d.ts +169 -11
  25. package/dist/index.js +92 -6
  26. package/dist/index.js.map +1 -1
  27. package/dist/react.cjs +683 -103
  28. package/dist/react.cjs.map +1 -1
  29. package/dist/react.d.cts +43 -4
  30. package/dist/react.d.ts +43 -4
  31. package/dist/react.js +147 -12
  32. package/dist/react.js.map +1 -1
  33. package/package.json +1 -1
  34. package/dist/chunk-34DKBQNN.js.map +0 -1
  35. package/dist/chunk-IFGOAROM.js.map +0 -1
  36. package/dist/chunk-LS6AYEBO.js.map +0 -1
package/dist/element.cjs CHANGED
@@ -32,6 +32,7 @@ var define_exports = {};
32
32
  __export(define_exports, {
33
33
  BUTTON_TAG: () => BUTTON_TAG,
34
34
  CONNECTED_KEY: () => CONNECTED_KEY,
35
+ CONTINUE_OTHER_LABEL: () => CONTINUE_OTHER_LABEL,
35
36
  GET_URL: () => GET_URL,
36
37
  MENU_CSS: () => MENU_CSS,
37
38
  PENDING_LABEL: () => PENDING_LABEL,
@@ -39,6 +40,7 @@ __export(define_exports, {
39
40
  PENDING_NOTE: () => PENDING_NOTE,
40
41
  SIGNIN_CSS: () => SIGNIN_CSS,
41
42
  SIGNIN_TAG: () => SIGNIN_TAG,
43
+ SWITCH_NOTE: () => SWITCH_NOTE,
42
44
  WEB_WALLET_URL: () => WEB_WALLET_URL,
43
45
  _resetBwalletxButtons: () => _resetBwalletxButtons,
44
46
  buildAuthorizeUrl: () => buildAuthorizeUrl,
@@ -48,6 +50,7 @@ __export(define_exports, {
48
50
  getUrlFor: () => getUrlFor,
49
51
  handleLabel: () => handleLabel,
50
52
  markSvg: () => markSvg,
53
+ migrateConnectedKey: () => migrateConnectedKey,
51
54
  signinText: () => signinText,
52
55
  stateFor: () => stateFor
53
56
  });
@@ -736,23 +739,8 @@ async function discoverWallets(o = {}) {
736
739
  return out.map((w, i) => ({ w, i })).sort((a, b) => rank(a.w) - rank(b.w) || a.i - b.i).map((x) => x.w);
737
740
  }
738
741
  var DESKTOP_OPTION_ID = "desktop";
739
- function desktopOption(ms = 1500) {
740
- const opt = {
741
- id: DESKTOP_OPTION_ID,
742
- name: "Desktop wallet",
743
- kind: "brc100",
744
- isBwalletX: false,
745
- substrate: "json-api",
746
- async connect(c) {
747
- const [json, cicada] = await Promise.all([probe("json-api", ms), probe("Cicada", ms)]);
748
- const w = json ?? cicada;
749
- if (!w) throw Object.assign(new Error("No desktop wallet answered on this computer"), { code: "NOT_FOUND" });
750
- opt.wallet = w;
751
- opt.substrate = json ? "json-api" : "cicada";
752
- return brc100Connected(w, opt, c);
753
- }
754
- };
755
- return opt;
742
+ function isLocalDesktop(w) {
743
+ return w.substrate === "json-api" || w.substrate === "cicada" || w.id === "localhost:3321" || w.id === "localhost:3301" || w.id === DESKTOP_OPTION_ID;
756
744
  }
757
745
 
758
746
  // src/account.ts
@@ -789,6 +777,7 @@ function isUserRejection(e) {
789
777
  }
790
778
  function signInErrorText(e) {
791
779
  if (isUserRejection(e)) return "Sign-in cancelled";
780
+ if (e?.name === "MetanetUnreachableError") return e.message;
792
781
  const code = e?.code;
793
782
  if (code === "NOT_FOUND") return "bWalletX was not found in this browser";
794
783
  const msg = e instanceof Error ? e.message : String(e ?? "");
@@ -822,6 +811,171 @@ function rememberIdentity(key) {
822
811
  }
823
812
  }
824
813
 
814
+ // src/metanet.ts
815
+ var import_sdk5 = require("@bsv/sdk");
816
+
817
+ // src/device.ts
818
+ function isTouchDevice(env = globalThis) {
819
+ const nav = env.navigator;
820
+ if (!nav) return false;
821
+ const ua = nav.userAgent ?? "";
822
+ if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
823
+ if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
824
+ try {
825
+ return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
826
+ } catch {
827
+ return false;
828
+ }
829
+ }
830
+
831
+ // src/metanet.ts
832
+ var METANET_TYPE = "metanet";
833
+ var METANET_OPTION_ID = METANET_TYPE;
834
+ var METANET_LABEL = "Use Metanet Desktop or another BRC-100 wallet";
835
+ var METANET_NOTE = "Metanet Desktop must be running on this computer";
836
+ var METANET_REMEMBERED_NOTE = "Signs in with Metanet Desktop on this computer.";
837
+ var NOT_YOU_LABEL = "Not you? Use another wallet";
838
+ var METANET_UNREACHABLE = "We couldn\u2019t reach Metanet Desktop. Open it, then try again.";
839
+ var METANET_ANSWER_MS = 4e3;
840
+ var METANET_SILENT_MS = 2e3;
841
+ var MetanetUnreachableError = class extends Error {
842
+ code = "NOT_FOUND";
843
+ constructor() {
844
+ super(METANET_UNREACHABLE);
845
+ this.name = "MetanetUnreachableError";
846
+ }
847
+ };
848
+ function inAnyFrame(win = typeof window === "undefined" ? void 0 : window) {
849
+ if (!win) return false;
850
+ try {
851
+ return win.top !== win.self;
852
+ } catch {
853
+ return true;
854
+ }
855
+ }
856
+ function metanetOffered(env = {}) {
857
+ const win = env.window ?? (typeof window === "undefined" ? void 0 : window);
858
+ if (!win) return false;
859
+ const nav = env.navigator ?? (typeof navigator === "undefined" ? void 0 : navigator);
860
+ const mm = env.matchMedia ?? (typeof matchMedia === "undefined" ? void 0 : (q) => matchMedia(q));
861
+ return !isTouchDevice({ navigator: nav, matchMedia: mm }) && !inAnyFrame(win);
862
+ }
863
+ function client(o) {
864
+ if (o.make) return o.make();
865
+ const originator = o.originator ?? (typeof location !== "undefined" && location.host ? location.host : void 0);
866
+ return new import_sdk5.WalletClient("json-api", originator);
867
+ }
868
+ async function answers(wallet) {
869
+ return Promise.resolve().then(() => wallet.getVersion({})).then((v) => typeof v?.version === "string", () => false);
870
+ }
871
+ async function connectMetanetDesktop(o = {}) {
872
+ let wallet;
873
+ try {
874
+ wallet = client(o);
875
+ } catch {
876
+ throw new MetanetUnreachableError();
877
+ }
878
+ let timer;
879
+ const ok = await Promise.race([answers(wallet), new Promise((r) => timer = setTimeout(() => r(false), o.timeoutMs ?? METANET_ANSWER_MS))]);
880
+ clearTimeout(timer);
881
+ if (!ok) throw new MetanetUnreachableError();
882
+ return wallet;
883
+ }
884
+ function metanetOption(o = {}) {
885
+ const opt = {
886
+ id: METANET_OPTION_ID,
887
+ name: METANET_LABEL,
888
+ kind: "brc100",
889
+ isBwalletX: false,
890
+ substrate: "json-api",
891
+ async connect() {
892
+ const wallet = await connectMetanetDesktop(o);
893
+ opt.wallet = wallet;
894
+ await waitIfLocked(wallet);
895
+ const { publicKey } = await wallet.getPublicKey({ identityKey: true });
896
+ return { wallet, payer: wallet, identityKey: publicKey, optionId: opt.id, kind: "brc100" };
897
+ }
898
+ };
899
+ return opt;
900
+ }
901
+ var KNOWN_KEYS_KEY = "bwx:known-keys";
902
+ var KEY_RE = /^0[23][0-9a-f]{64}$/;
903
+ var MAX_KNOWN = 8;
904
+ function knownIdentityKeys() {
905
+ try {
906
+ const v = JSON.parse(globalThis.localStorage?.getItem(KNOWN_KEYS_KEY) || "[]");
907
+ return Array.isArray(v) ? v.filter((k) => typeof k === "string" && KEY_RE.test(k)) : [];
908
+ } catch {
909
+ return [];
910
+ }
911
+ }
912
+ function isKnownIdentityKey(key) {
913
+ if (!key) return false;
914
+ const k = key.toLowerCase();
915
+ if (!KEY_RE.test(k)) return false;
916
+ return knownIdentityKeys().includes(k) || rememberedIdentity()?.toLowerCase() === k;
917
+ }
918
+ function rememberKnownIdentityKey(key) {
919
+ const k = (key ?? "").toLowerCase();
920
+ if (!KEY_RE.test(k)) return;
921
+ try {
922
+ const list = [k, ...knownIdentityKeys().filter((x) => x !== k)].slice(0, MAX_KNOWN);
923
+ globalThis.localStorage?.setItem(KNOWN_KEYS_KEY, JSON.stringify(list));
924
+ } catch {
925
+ }
926
+ }
927
+ var triedThisLoad = false;
928
+ function silentAttemptUsed() {
929
+ return triedThisLoad;
930
+ }
931
+ function claimSilentAttempt() {
932
+ if (triedThisLoad) return false;
933
+ triedThisLoad = true;
934
+ return true;
935
+ }
936
+ async function trySilentMetanet(o = {}) {
937
+ if (!metanetOffered()) return { ok: false, stop: "not-here" };
938
+ if (!claimSilentAttempt()) return { ok: false, stop: "used" };
939
+ const run = async () => {
940
+ let wallet;
941
+ try {
942
+ wallet = client(o);
943
+ } catch {
944
+ return { ok: false, stop: "unreachable" };
945
+ }
946
+ if (!await answers(wallet)) return { ok: false, stop: "unreachable" };
947
+ const unlocked = await Promise.resolve().then(() => wallet.isAuthenticated({})).then((r2) => r2?.authenticated === true, () => false);
948
+ if (!unlocked) return { ok: false, stop: "locked" };
949
+ const identityKey = await Promise.resolve().then(() => wallet.getPublicKey({ identityKey: true })).then((r2) => r2.publicKey, () => null);
950
+ if (!identityKey) return { ok: false, stop: "unreachable" };
951
+ if (!isKnownIdentityKey(identityKey)) return { ok: false, stop: "unknown-key" };
952
+ return { ok: true, wallet, identityKey };
953
+ };
954
+ let timer;
955
+ const r = await Promise.race([
956
+ run().catch(() => ({ ok: false, stop: "unreachable" })),
957
+ new Promise((res) => timer = setTimeout(() => res({ ok: false, stop: "slow" }), o.windowMs ?? METANET_SILENT_MS))
958
+ ]);
959
+ clearTimeout(timer);
960
+ return r;
961
+ }
962
+ var warnedLogout = false;
963
+ var isProduction = () => {
964
+ try {
965
+ return globalThis.process?.env?.NODE_ENV === "production";
966
+ } catch {
967
+ return false;
968
+ }
969
+ };
970
+ function warnIfNoLogout(o) {
971
+ if (!o.verifyUrl || o.logoutUrl || warnedLogout || isProduction() || typeof console === "undefined") return false;
972
+ warnedLogout = true;
973
+ console.warn(
974
+ `@bwalletx/connect: ${o.where} has a verify URL but no logout URL. Set it: "Not you? Use another wallet" and Disconnect must be able to end a session the verify route created.`
975
+ );
976
+ return true;
977
+ }
978
+
825
979
  // src/choice.ts
826
980
  var BWALLETX_TYPE = "bwalletx";
827
981
  function substrateOf(o) {
@@ -868,6 +1022,64 @@ function attemptFor(option, outcome, startedAt, now = Date.now()) {
868
1022
  ms: Math.max(0, Math.round(now - startedAt))
869
1023
  };
870
1024
  }
1025
+ function reportAttempt(hook, a) {
1026
+ if (!hook) return;
1027
+ try {
1028
+ hook(Object.freeze({ walletType: a.walletType, substrate: a.substrate, outcome: a.outcome, ms: a.ms }));
1029
+ } catch {
1030
+ }
1031
+ }
1032
+ var WALLET_CHOICE_KEY = "bwx:wallet";
1033
+ var SUBSTRATES = ["extension", "in-app", "xdm", "injected", "window.CWI", "json-api", "cicada", "pairing", "adapter"];
1034
+ var TYPE_RE = /^[A-Za-z0-9._:-]{1,64}$/;
1035
+ var KEY_LIKE = /^(0[23][0-9a-f]{64}|04[0-9a-f]{128}|[13][1-9A-HJ-NP-Za-km-z]{25,34})$/i;
1036
+ function isRememberable(o) {
1037
+ const t = walletTypeOf(o);
1038
+ return substrateOf(o) !== "window.CWI" && TYPE_RE.test(t) && !KEY_LIKE.test(t);
1039
+ }
1040
+ function isExplicitPick(o, reason) {
1041
+ return o.isBwalletX || reason === "chooser" || reason === "requested" || reason === "remembered";
1042
+ }
1043
+ function rememberWalletChoice(o) {
1044
+ try {
1045
+ if (!o) return void globalThis.localStorage?.removeItem(WALLET_CHOICE_KEY);
1046
+ if (!isRememberable(o)) return;
1047
+ const v = { type: walletTypeOf(o), substrate: substrateOf(o), v: 1 };
1048
+ globalThis.localStorage?.setItem(WALLET_CHOICE_KEY, JSON.stringify(v));
1049
+ } catch {
1050
+ }
1051
+ }
1052
+ function rememberedWalletChoice() {
1053
+ try {
1054
+ const raw = globalThis.localStorage?.getItem(WALLET_CHOICE_KEY);
1055
+ if (!raw) return null;
1056
+ const j = JSON.parse(raw);
1057
+ if (typeof j.type !== "string" || !TYPE_RE.test(j.type) || KEY_LIKE.test(j.type)) return null;
1058
+ if (!SUBSTRATES.includes(j.substrate) || j.substrate === "window.CWI") return null;
1059
+ return { type: j.type, substrate: j.substrate };
1060
+ } catch {
1061
+ return null;
1062
+ }
1063
+ }
1064
+ function forgetWalletChoice() {
1065
+ rememberWalletChoice(null);
1066
+ }
1067
+ function needsBwalletX(type) {
1068
+ return !!type && type !== BWALLETX_TYPE;
1069
+ }
1070
+ function tapGoesElsewhere(type) {
1071
+ return needsBwalletX(type) && type !== METANET_TYPE && type !== DESKTOP_OPTION_ID;
1072
+ }
1073
+ function isLocalDesktopType(r) {
1074
+ return r.type === METANET_TYPE || r.type === DESKTOP_OPTION_ID || r.substrate === "json-api" || r.substrate === "cicada";
1075
+ }
1076
+ function matchRemembered(list, r) {
1077
+ if (isLocalDesktopType(r)) return null;
1078
+ if (r.type === BWALLETX_TYPE) {
1079
+ return (r.substrate === "pairing" ? list.find((o) => o.kind === "pairing") : list.find(isPresentBwalletX)) ?? null;
1080
+ }
1081
+ return list.find((o) => !o.isBwalletX && !isLocalDesktop(o) && isRememberable(o) && walletTypeOf(o) === r.type) ?? null;
1082
+ }
871
1083
 
872
1084
  // src/modal.ts
873
1085
  var import_qrcode_generator = __toESM(require("qrcode-generator"), 1);
@@ -929,18 +1141,6 @@ function qrSvg(text) {
929
1141
  q.make();
930
1142
  return q.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
931
1143
  }
932
- function isTouchDevice(env = globalThis) {
933
- const nav = env.navigator;
934
- if (!nav) return false;
935
- const ua = nav.userAgent ?? "";
936
- if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
937
- if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
938
- try {
939
- return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
940
- } catch {
941
- return false;
942
- }
943
- }
944
1144
  function pairingModal(o = {}) {
945
1145
  const ac = new AbortController();
946
1146
  let ui = null;
@@ -1070,10 +1270,11 @@ function chooseWallet(options, o = {}) {
1070
1270
  row2.onclick = () => done(w);
1071
1271
  list.append(row2);
1072
1272
  }
1073
- if (o.desktop !== false && !options.some((w) => w.substrate === "json-api" || w.substrate === "cicada")) {
1273
+ if (o.desktop !== false && metanetOffered() && !options.some((w) => w.substrate === "json-api" || w.substrate === "cicada")) {
1074
1274
  if (!here && !sep) list.append(el("p", "bwx-sep", "Or use"));
1075
- const d = desktopOption();
1076
- const row2 = optionRow(d, false, "Metanet or BSV Desktop on this computer", "Fewer features");
1275
+ const d = metanetOption();
1276
+ const row2 = optionRow(d, false, METANET_NOTE);
1277
+ row2.title = "Connects to Metanet Desktop on this computer, never to a browser extension.";
1077
1278
  row2.onclick = () => done(d);
1078
1279
  list.append(row2);
1079
1280
  }
@@ -1091,7 +1292,7 @@ function chooseWallet(options, o = {}) {
1091
1292
  }
1092
1293
 
1093
1294
  // src/access-client.ts
1094
- var import_sdk5 = require("@bsv/sdk");
1295
+ var import_sdk6 = require("@bsv/sdk");
1095
1296
 
1096
1297
  // src/access.ts
1097
1298
  var ACCESS_PROTOCOL = [2, "bwalletx access"];
@@ -1229,7 +1430,7 @@ async function proveHoldings(wallet, challenge, tokenId, basket = "bsv21") {
1229
1430
  const counterparty = ci.counterparty ?? "self";
1230
1431
  for (const protocolID of ci.protocolID ? [ci.protocolID] : [ONESAT, LEGACY_ONESAT]) {
1231
1432
  const { publicKey } = await wallet.getPublicKey({ protocolID, keyID: ci.keyID, counterparty });
1232
- const h = import_sdk5.Utils.toHex(import_sdk5.Hash.hash160(import_sdk5.PublicKey.fromString(publicKey).encode(true)));
1433
+ const h = import_sdk6.Utils.toHex(import_sdk6.Hash.hash160(import_sdk6.PublicKey.fromString(publicKey).encode(true)));
1233
1434
  if (want && h !== want) continue;
1234
1435
  const existing = byKey.get(publicKey);
1235
1436
  if (existing) {
@@ -1275,6 +1476,48 @@ async function requestAccess(o) {
1275
1476
  }
1276
1477
  }
1277
1478
 
1479
+ // src/connect-flow.ts
1480
+ async function runSilentMetanet(o = {}) {
1481
+ const s = await trySilentMetanet(o);
1482
+ if (!s.ok || o.isAbandoned?.()) return null;
1483
+ const started = Date.now();
1484
+ const opt = metanetOption(o);
1485
+ opt.wallet = s.wallet;
1486
+ const cw = { wallet: s.wallet, payer: s.wallet, identityKey: s.identityKey, optionId: METANET_OPTION_ID, kind: "brc100" };
1487
+ const done = (signedIn, handle = null, paymail = null) => {
1488
+ rememberWalletChoice(opt);
1489
+ if (signedIn) rememberKnownIdentityKey(s.identityKey);
1490
+ reportAttempt(o.onAttempt, attemptFor(opt, signedIn ? "signed-in" : "connected", started));
1491
+ return { connected: cw, identityKey: s.identityKey, handle, paymail, signedIn, walletType: walletTypeOf(opt), substrate: "json-api", reason: "remembered" };
1492
+ };
1493
+ if (o.sessionKey !== void 0 && o.sessionKey !== null) {
1494
+ return o.sessionKey.toLowerCase() === s.identityKey.toLowerCase() ? done(true) : null;
1495
+ }
1496
+ if (!o.challengeUrl || !o.verifyUrl || !o.post) return done(false);
1497
+ if (!isKnownIdentityKey(s.identityKey)) return null;
1498
+ try {
1499
+ o.onSignIn?.();
1500
+ const post = o.post;
1501
+ const { result, verified } = await signInAndVerify({
1502
+ wallet: s.wallet,
1503
+ identityKey: s.identityKey,
1504
+ skipWaitForAuthentication: true,
1505
+ // trySilentMetanet just saw it unlocked
1506
+ challenge: (key) => post(o.challengeUrl, key ? { identityKey: key } : {}),
1507
+ verify: (r) => post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
1508
+ });
1509
+ if (o.isAbandoned?.()) {
1510
+ await Promise.resolve(o.logout?.()).catch(() => void 0);
1511
+ return null;
1512
+ }
1513
+ rememberIdentity(result.identityKey);
1514
+ return done(true, verified.handle ?? null, verified.paymail ?? null);
1515
+ } catch (e) {
1516
+ reportAttempt(o.onAttempt, attemptFor(opt, outcomeOf(e), started));
1517
+ return null;
1518
+ }
1519
+ }
1520
+
1278
1521
  // src/element.ts
1279
1522
  var SIGNIN_TAG = "bwalletx-signin";
1280
1523
  var GET_URL = "https://bwalletx.com/get";
@@ -1340,6 +1583,11 @@ button[aria-busy=true]{cursor:wait}
1340
1583
  /* "Other wallets" (0.5.6): one small link under the button */
1341
1584
  .ow{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
1342
1585
  :host([theme=dark]) .ow,:host-context(.dark) .ow{color:#a1a1aa}
1586
+ .sw{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#a16207;text-align:center}
1587
+ :host([theme=dark]) .sw,:host-context(.dark) .sw{color:#fcd34d}
1588
+ .mn{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
1589
+ .mn small{display:block;font-size:11px;opacity:.85}
1590
+ :host([theme=dark]) .mn,:host-context(.dark) .mn{color:#a1a1aa}
1343
1591
  .lnk{all:unset;cursor:pointer;color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px}
1344
1592
  .lnk:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
1345
1593
  /* error: one line plus Retry */
@@ -1378,6 +1626,11 @@ var MENU_CSS = `
1378
1626
  var PENDING_NOTE = "Sign in: shares only your public identity key, moves no money";
1379
1627
  var PENDING_LABEL = "Approve in bWalletX\u2026";
1380
1628
  var PENDING_LABEL_OTHER = "Approve in your wallet\u2026";
1629
+ var SWITCH_NOTE = "Some features need bWalletX";
1630
+ var CONTINUE_OTHER_LABEL = "Continue with your wallet";
1631
+ function walletGlyph(px) {
1632
+ return `<svg viewBox="0 0 24 24" width="${px}" height="${px}" aria-hidden="true" focusable="false" fill="none" stroke="#FFD24D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 7V5a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2"/><path d="M3 6v12a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-3"/></svg>`;
1633
+ }
1381
1634
  function b64url2(bytes) {
1382
1635
  let s = "";
1383
1636
  for (const b of bytes) s += String.fromCharCode(b);
@@ -1417,7 +1670,7 @@ async function postAccess(url, body) {
1417
1670
  const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
1418
1671
  return res.json().catch(() => ({ error: "bad_request", message: `HTTP ${res.status}` }));
1419
1672
  }
1420
- var OBSERVED = ["size", "theme", "label", "brand", "subtitle", "loading", "disabled", "full-width", "aria-label", "get-url", "web-url", "state", "identity-key", "paymail", "handle", "avatar-url", "error", "other-wallets", "challenge-url"];
1673
+ var OBSERVED = ["size", "theme", "label", "brand", "subtitle", "loading", "disabled", "full-width", "aria-label", "get-url", "web-url", "state", "identity-key", "paymail", "handle", "avatar-url", "error", "other-wallets", "challenge-url", "wallet-type", "switch-note", "remember", "metanet"];
1421
1674
  var STATES = ["signed-out", "restoring", "pending", "signed-in", "error"];
1422
1675
  function createClass() {
1423
1676
  return class BwalletxSigninElement extends HTMLElement {
@@ -1432,14 +1685,21 @@ function createClass() {
1432
1685
  #error = "";
1433
1686
  #restored = false;
1434
1687
  #pendingLabel = PENDING_LABEL;
1688
+ /** The wallet type signed in with here (0.6.0), for the Switch note. */
1689
+ #signedType = null;
1435
1690
  #menu = null;
1691
+ /** (0.7.0) A Metanet sign-in (tapped or silent) is in flight. */
1692
+ #viaMetanet = false;
1693
+ /** (0.7.0) Bumped by "Not you?": a Metanet sign-in started before it lands nobody. */
1694
+ #run = 0;
1436
1695
  constructor() {
1437
1696
  super();
1438
1697
  this.#root = this.attachShadow({ mode: "open" });
1439
1698
  }
1440
1699
  connectedCallback() {
1700
+ if (!this.#controlled && this.#stateful) warnIfNoLogout({ verifyUrl: this.#attr("verify-url"), logoutUrl: this.#attr("logout-url"), where: "<bwalletx-signin>" });
1441
1701
  this.#render();
1442
- void this.#restore();
1702
+ void this.#restore().then(() => this.#silentMetanet());
1443
1703
  }
1444
1704
  disconnectedCallback() {
1445
1705
  this.#menu?.close(false);
@@ -1545,10 +1805,11 @@ function createClass() {
1545
1805
  const restoring = state === "restoring";
1546
1806
  const error = state === "error";
1547
1807
  const pendingLabel = this.#controlled ? PENDING_LABEL : this.#pendingLabel;
1548
- const text = pending ? pendingLabel : error ? "Retry" : signinText(this.#attr("label") ?? "signin", brand);
1808
+ const elsewhere = state === "signed-out" && tapGoesElsewhere(this.#walletType());
1809
+ const text = pending ? pendingLabel : error ? "Retry" : elsewhere ? CONTINUE_OTHER_LABEL : signinText(this.#attr("label") ?? "signin", brand);
1549
1810
  const subAttr = this.#attr("subtitle");
1550
1811
  const subDefault = brand === "bWalletX" && (size === "lg" || size === "md") ? "also works with bWallet" : null;
1551
- const subtitle = pending || error ? null : subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
1812
+ const subtitle = pending || error || elsewhere ? null : subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
1552
1813
  const loading = this.hasAttribute("loading") || this.#busy || pending || restoring;
1553
1814
  const disabled = this.hasAttribute("disabled");
1554
1815
  const markPx = size === "icon" ? 24 : size === "sm" ? 15 : size === "md" ? 17 : 20;
@@ -1557,18 +1818,26 @@ function createClass() {
1557
1818
  const webUrl = this.#attr("web-url") ?? WEB_WALLET_URL;
1558
1819
  const errText = error ? (this.#controlled ? this.#attr("error") : this.#error) || "Sign-in failed" : "";
1559
1820
  const label = size === "icon" ? "" : `<span class="txt" part="text"><b part="label">${esc(text)}</b>${subtitle ? `<small part="subtitle">${esc(subtitle)}</small>` : ""}</span>`;
1560
- const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : markSvg(markPx);
1821
+ const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : elsewhere ? walletGlyph(markPx - 4) : markSvg(markPx);
1561
1822
  const otherOff = this.#attr("other-wallets") === "off";
1562
1823
  const otherLink = '<button type="button" class="lnk" data-act="other" part="other-wallets-link">Other wallets</button>';
1563
1824
  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>.${otherOff ? "" : ` ${otherLink}`}</p>` : "";
1564
1825
  const ow = !this.#notFound && !pending && !restoring && this.#otherWalletsShown(size) ? `<p class="ow" part="other-wallets">${otherLink}</p>` : "";
1826
+ const sw = !pending && !restoring && this.#switchNoteShown(size) ? `<p class="sw" part="switch-note">${esc(SWITCH_NOTE)} \xB7 <button type="button" class="lnk" data-act="switch" part="switch-link">Switch</button></p>` : "";
1565
1827
  const err = error ? `<p class="err" part="error" role="alert">${esc(errText)}</p>` : "";
1828
+ const mnHere = this.#metanetShown(size);
1829
+ const mnRemembered = mnHere && (pending ? this.#metanetPending() : !restoring && this.#walletType() === METANET_TYPE);
1830
+ const notYou = `<button type="button" class="lnk" data-act="not-you" part="not-you-link">${esc(NOT_YOU_LABEL)}</button>`;
1831
+ const mn = !mnHere ? "" : pending ? mnRemembered ? `<p class="mn" part="metanet">${esc(METANET_REMEMBERED_NOTE)} ${notYou}</p>` : "" : restoring ? "" : `<p class="mn" part="metanet"><button type="button" class="lnk" data-act="metanet" part="metanet-link" title="Connects to Metanet Desktop on this computer, never to a browser extension.">${esc(METANET_LABEL)}</button><small>${esc(METANET_NOTE)}</small>${mnRemembered ? `<small>${esc(METANET_REMEMBERED_NOTE)} ${notYou}</small>` : ""}</p>`;
1566
1832
  const compact = size === "sm" || size === "icon";
1567
1833
  const note = pending && !compact ? `<p class="note" part="note">${esc(PENDING_NOTE)}</p>` : "";
1568
1834
  const status = pending ? `<span class="sr" role="status">${esc(pendingLabel)}</span>` : loading ? '<span class="sr" role="status">Connecting to bWalletX\u2026</span>' : "";
1569
- this.#root.innerHTML = `<style>${SIGNIN_CSS}</style>${err}<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>${status}${note}${ow}${nf}`;
1835
+ this.#root.innerHTML = `<style>${SIGNIN_CSS}</style>${err}<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>${status}${note}${sw}${mn}${ow}${nf}`;
1570
1836
  this.button.addEventListener("click", () => void this.#click());
1571
1837
  for (const l of Array.from(this.#root.querySelectorAll("[data-act=other]"))) l.addEventListener("click", () => void this.otherWallets());
1838
+ this.#root.querySelector("[data-act=switch]")?.addEventListener("click", () => void this.switchToBwalletX());
1839
+ this.#root.querySelector("[data-act=metanet]")?.addEventListener("click", () => void this.useMetanet());
1840
+ this.#root.querySelector("[data-act=not-you]")?.addEventListener("click", () => this.notYou());
1572
1841
  bindPrewarm(this.button);
1573
1842
  if (pending && compact) this.#showTip(PENDING_NOTE);
1574
1843
  else this.#hideTip();
@@ -1636,7 +1905,20 @@ function createClass() {
1636
1905
  close(false);
1637
1906
  void this.signOut();
1638
1907
  });
1639
- menu.append(who, copy, out);
1908
+ const items = [copy, out];
1909
+ menu.append(who);
1910
+ if (needsBwalletX(this.#walletType())) {
1911
+ const sw = item("Switch to bWalletX", () => {
1912
+ close(false);
1913
+ void this.switchToBwalletX();
1914
+ });
1915
+ const line = document.createElement("div");
1916
+ line.className = "who";
1917
+ line.textContent = SWITCH_NOTE;
1918
+ menu.append(line, sw);
1919
+ items.unshift(sw);
1920
+ }
1921
+ menu.append(copy, out);
1640
1922
  root.append(style, menu);
1641
1923
  document.body.appendChild(host);
1642
1924
  const place = () => {
@@ -1652,7 +1934,6 @@ function createClass() {
1652
1934
  menu.style.top = `${below + h > vh && r.top - 8 - h > 0 ? r.top - 8 - h : below}px`;
1653
1935
  };
1654
1936
  place();
1655
- const items = [copy, out];
1656
1937
  const focusAt = (i) => items[(i + items.length) % items.length].focus();
1657
1938
  const onKey = (e) => {
1658
1939
  const i = items.indexOf(root.activeElement);
@@ -1707,6 +1988,8 @@ function createClass() {
1707
1988
  if (this.#controlled) return;
1708
1989
  const url = this.#attr("logout-url");
1709
1990
  if (url) await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
1991
+ if (this.#remember) forgetWalletChoice();
1992
+ this.#signedType = null;
1710
1993
  this.#account = null;
1711
1994
  this.#set("signed-out");
1712
1995
  this.#emit("bwalletx-signed-out");
@@ -1726,6 +2009,110 @@ function createClass() {
1726
2009
  otherWallets() {
1727
2010
  return this.#click("chooser");
1728
2011
  }
2012
+ /** (0.6.0) "Switch": sign in with bWalletX (its sheet when here, else the chooser with Get bWalletX first). */
2013
+ switchToBwalletX() {
2014
+ return this.#click("bwalletx");
2015
+ }
2016
+ get #remember() {
2017
+ return this.#attr("remember") !== "off";
2018
+ }
2019
+ /**
2020
+ * (0.7.0) "Use Metanet Desktop or another BRC-100 wallet" is offered: metanet on, or auto at lg/md when this
2021
+ * element signs in or follows a provider. Never on a phone or inside a frame.
2022
+ */
2023
+ #metanetShown(size) {
2024
+ const a = this.#attr("metanet");
2025
+ if (a === "off" || !metanetOffered()) return false;
2026
+ if (a === "on") return true;
2027
+ return (size === "lg" || size === "md") && (this.#controlled || !!this.#attr("challenge-url"));
2028
+ }
2029
+ /** A Metanet sign-in is on screen: our own, or (driven) the provider's with Metanet remembered. */
2030
+ #metanetPending() {
2031
+ return this.#controlled ? this.#attr("wallet-type") === METANET_TYPE : this.#viaMetanet;
2032
+ }
2033
+ /** (0.7.0) The Metanet link: connect to Metanet Desktop on localhost:3321 (after this tap, never before). */
2034
+ useMetanet() {
2035
+ return this.#click("metanet");
2036
+ }
2037
+ /**
2038
+ * (0.7.0) "Not you? Use another wallet": forget the remembered wallet here, and drop a Metanet sign-in in flight
2039
+ * (if its verify already set a session, logout-url ends it). Emits bwalletx-not-you (cancelable).
2040
+ */
2041
+ notYou() {
2042
+ if (!this.#emit("bwalletx-not-you", { element: this }, true)) return;
2043
+ this.#run++;
2044
+ if (this.#remember) forgetWalletChoice();
2045
+ this.#signedType = null;
2046
+ if (this.#viaMetanet && !this.#controlled) {
2047
+ this.#viaMetanet = false;
2048
+ this.#busy = false;
2049
+ if (this.#phase === "pending") this.#phase = "signed-out";
2050
+ }
2051
+ this.#render();
2052
+ }
2053
+ /**
2054
+ * (0.7.0) Rule 4: back with Metanet remembered and no session, ONE silent attempt per page load (2 s, a known
2055
+ * identity key only), then the signature alone. Any stop or failure is silent: the normal button stays.
2056
+ */
2057
+ async #silentMetanet() {
2058
+ if (this.#controlled || !this.#stateful || !this.#remember || this.#attr("require-token")) return;
2059
+ if (!this.#metanetShown(this.#attr("size") ?? "lg")) return;
2060
+ const challengeUrl = this.#attr("challenge-url");
2061
+ const verifyUrl = this.#attr("verify-url");
2062
+ if (!challengeUrl || !verifyUrl || this.#phase !== "signed-out" || this.#busy || silentAttemptUsed()) return;
2063
+ if (rememberedWalletChoice()?.type !== METANET_TYPE || !metanetOffered()) return;
2064
+ const run = ++this.#run;
2065
+ const live2 = () => run === this.#run;
2066
+ const started = Date.now();
2067
+ const r = await runSilentMetanet({
2068
+ challengeUrl,
2069
+ verifyUrl,
2070
+ post: (url, body) => postJson(url, body),
2071
+ isAbandoned: () => !live2(),
2072
+ logout: async () => {
2073
+ const out = this.#attr("logout-url");
2074
+ if (out) await fetch(out, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
2075
+ },
2076
+ onSignIn: () => {
2077
+ if (!live2() || this.#phase !== "signed-out" || this.#busy) return;
2078
+ this.#viaMetanet = true;
2079
+ this.#busy = true;
2080
+ this.#pendingLabel = PENDING_LABEL_OTHER;
2081
+ this.#set("pending");
2082
+ },
2083
+ onAttempt: (a) => this.#emit("bwalletx-attempt", a)
2084
+ });
2085
+ if (!live2()) return;
2086
+ this.#viaMetanet = false;
2087
+ this.#busy = false;
2088
+ if (!r) {
2089
+ if (this.#phase === "pending") this.#set("signed-out");
2090
+ return;
2091
+ }
2092
+ this.#signedType = r.walletType;
2093
+ this.#signedIn({ identityKey: r.identityKey, handle: r.handle, paymail: r.paymail });
2094
+ this.#emit("bwalletx-signed-in", { identityKey: r.identityKey, method: "wallet", nonce: null, signature: null, response: { handle: r.handle, paymail: r.paymail }, silent: true, ms: Date.now() - started });
2095
+ }
2096
+ /** The wallet type the Switch note judges: the provider's, else what signed in here, else the remembered one. */
2097
+ #walletType() {
2098
+ if (this.#controlled) return this.#attr("wallet-type");
2099
+ return this.#signedType ?? (this.#remember ? rememberedWalletChoice()?.type ?? null : null);
2100
+ }
2101
+ #switchNoteShown(size) {
2102
+ const a = this.#attr("switch-note");
2103
+ if (a === "off" || !tapGoesElsewhere(this.#walletType())) return false;
2104
+ return a === "on" || size === "lg" || size === "md";
2105
+ }
2106
+ /**
2107
+ * Signed in: note the wallet type, and remember it for this origin when the user picked it (0.6.0), plus the
2108
+ * identity key as known here (0.7.0, public keys only: Metanet's silent attempt goes on only for these). Pass a key
2109
+ * only after the site's verify route accepted it.
2110
+ */
2111
+ #picked(option, reason, identityKey) {
2112
+ this.#signedType = walletTypeOf(option);
2113
+ if (this.#remember && isExplicitPick(option, reason)) rememberWalletChoice(option);
2114
+ if (this.#remember) rememberKnownIdentityKey(identityKey);
2115
+ }
1729
2116
  #attempt(option, outcome, started) {
1730
2117
  this.#emit("bwalletx-attempt", attemptFor(option, outcome, started));
1731
2118
  }
@@ -1734,16 +2121,24 @@ function createClass() {
1734
2121
  * when another wallet is here or the user asked; otherwise 'not-found'. null: the chooser was closed.
1735
2122
  */
1736
2123
  async #choose(mode, ms, pairing) {
2124
+ if (mode === "metanet") return { option: metanetOption(), reason: "requested" };
2125
+ const r = mode === "signin" && this.#remember ? rememberedWalletChoice() : null;
2126
+ if (r && r.type !== METANET_TYPE && !(r.type === BWALLETX_TYPE && r.substrate !== "pairing")) {
2127
+ const m = matchRemembered(await discoverWallets({ ms, pairing: () => pairing }), r);
2128
+ if (m) return { option: m, reason: "remembered" };
2129
+ }
1737
2130
  if (mode !== "chooser") {
1738
2131
  const f = await discoverBwalletX(ms);
1739
2132
  if (f) {
1740
2133
  const substrate = f.rdns === RDNS_EXTENSION ? "extension" : f.rdns === RDNS_MOBILE ? "in-app" : "xdm";
1741
- return brc100Option(f.wallet, { id: f.rdns ?? "bwalletx-xdm", rdns: f.rdns ?? void 0, name: "bWalletX", isBwalletX: true, substrate });
2134
+ const option = brc100Option(f.wallet, { id: f.rdns ?? "bwalletx-xdm", rdns: f.rdns ?? void 0, name: "bWalletX", isBwalletX: true, substrate });
2135
+ return { option, reason: "bwalletx" };
1742
2136
  }
1743
2137
  }
1744
2138
  const list = await discoverWallets({ ms: 50, pairing: () => pairing });
1745
2139
  if (mode !== "chooser" && !hasBwalletX(list) && !otherDetected(list).length) return "not-found";
1746
- return chooseWallet(list, { getUrl: this.#attr("get-url") ?? void 0 });
2140
+ const picked = await chooseWallet(list, { getUrl: this.#attr("get-url") ?? void 0 });
2141
+ return picked ? { option: picked, reason: "chooser" } : null;
1747
2142
  }
1748
2143
  async #click(mode = "signin") {
1749
2144
  if (this.hasAttribute("disabled") || this.hasAttribute("loading") || this.#busy || this.state === "pending") return;
@@ -1767,9 +2162,12 @@ function createClass() {
1767
2162
  const started = Date.now();
1768
2163
  const pairing = pairingModal();
1769
2164
  let option = null;
2165
+ const run = ++this.#run;
2166
+ const live2 = () => run === this.#run;
1770
2167
  this.#notFound = false;
1771
2168
  this.#busy = true;
1772
- this.#pendingLabel = PENDING_LABEL;
2169
+ this.#viaMetanet = mode === "metanet";
2170
+ this.#pendingLabel = mode === "metanet" ? PENDING_LABEL_OTHER : PENDING_LABEL;
1773
2171
  if (own) this.#set("pending");
1774
2172
  else this.#render();
1775
2173
  try {
@@ -1788,12 +2186,14 @@ function createClass() {
1788
2186
  this.#attempt(null, "cancelled", started);
1789
2187
  return;
1790
2188
  }
1791
- option = picked;
2189
+ option = picked.option;
2190
+ const reason = picked.reason;
1792
2191
  if (!option.isBwalletX) {
1793
2192
  this.#pendingLabel = PENDING_LABEL_OTHER;
1794
2193
  if (own) this.#render();
1795
2194
  }
1796
2195
  const cw = await option.connect({ identity: false });
2196
+ if (!live2()) return;
1797
2197
  const wallet = cw.wallet;
1798
2198
  if (!wallet) throw Object.assign(new Error("This wallet cannot sign in here"), { code: "NOT_SUPPORTED" });
1799
2199
  const sub = substrateOf(option);
@@ -1811,6 +2211,7 @@ function createClass() {
1811
2211
  challenge: (identityKey) => postJson(challengeUrl, { identityKey, require: hint }),
1812
2212
  verify: verifyUrl2 ? (response2) => postAccess(verifyUrl2, response2) : void 0
1813
2213
  });
2214
+ this.#picked(option, reason, verifyUrl2 ? a.response.identityKey : null);
1814
2215
  this.#attempt(option, "signed-in", started);
1815
2216
  this.#emit("bwalletx-access-granted", { identityKey: a.response.identityKey, response: a.verified });
1816
2217
  } catch (e) {
@@ -1823,18 +2224,29 @@ function createClass() {
1823
2224
  const verifyUrl = this.#attr("verify-url");
1824
2225
  const { result: r, verified } = await signInAndVerify({
1825
2226
  wallet,
2227
+ // Metanet hands over its key on connect (one request at a time), so its sign-in is the signature alone.
2228
+ identityKey: cw.identityKey ?? void 0,
2229
+ skipWaitForAuthentication: option.id === METANET_OPTION_ID,
2230
+ // its connect() just checked the lock
1826
2231
  identityKeyHint: verifyUrl ? rememberedIdentity() : void 0,
1827
2232
  challenge: (identityKey) => postJson(challengeUrl, identityKey ? { identityKey } : {}),
1828
2233
  verify: async (res) => verifyUrl ? await postJson(verifyUrl, { identityKey: res.identityKey, nonce: res.nonce, signature: res.signature }) : {}
1829
2234
  });
2235
+ if (!live2()) {
2236
+ const out = this.#attr("logout-url");
2237
+ if (verifyUrl && out) await fetch(out, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
2238
+ return;
2239
+ }
1830
2240
  if (verifyUrl) rememberIdentity(r.identityKey);
1831
2241
  const response = verifyUrl ? verified : null;
2242
+ this.#picked(option, reason, verifyUrl ? r.identityKey : null);
1832
2243
  if (own) this.#signedIn({ identityKey: r.identityKey, handle: response?.handle ?? null, paymail: response?.paymail ?? null });
1833
2244
  this.#attempt(option, "signed-in", started);
1834
2245
  this.#emit("bwalletx-signed-in", { identityKey: r.identityKey, method, nonce: r.nonce, signature: r.signature, response });
1835
2246
  } catch (error) {
1836
2247
  const outcome = outcomeOf(error);
1837
2248
  this.#attempt(option, outcome, started);
2249
+ if (!live2()) return;
1838
2250
  const closedQr = outcome === "cancelled" && option?.kind === "pairing";
1839
2251
  if (own) {
1840
2252
  if (closedQr) this.#phase = prev;
@@ -1845,12 +2257,15 @@ function createClass() {
1845
2257
  }
1846
2258
  if (!closedQr) this.#emit("bwalletx-error", { error });
1847
2259
  } finally {
1848
- this.#busy = false;
1849
2260
  try {
1850
2261
  pairing.close();
1851
2262
  } catch {
1852
2263
  }
1853
- this.#render();
2264
+ if (live2()) {
2265
+ this.#busy = false;
2266
+ this.#viaMetanet = false;
2267
+ this.#render();
2268
+ }
1854
2269
  }
1855
2270
  }
1856
2271
  };
@@ -1867,6 +2282,16 @@ function defineBwalletxSignin(tag = SIGNIN_TAG) {
1867
2282
  // src/wallet-button.ts
1868
2283
  var BUTTON_TAG = "bwalletx-button";
1869
2284
  var CONNECTED_KEY = "bwx:connected";
2285
+ function migrateConnectedKey() {
2286
+ const had = dropLegacyConnectedKey();
2287
+ if (had && !rememberedWalletChoice()) rememberBwalletX("extension");
2288
+ return had;
2289
+ }
2290
+ function rememberBwalletX(substrate) {
2291
+ rememberWalletChoice({ id: "bwalletx", name: "bWalletX", kind: substrate === "pairing" ? "pairing" : "brc100", isBwalletX: true, substrate, connect: async () => {
2292
+ throw new Error("unused");
2293
+ } });
2294
+ }
1870
2295
  function dropLegacyConnectedKey() {
1871
2296
  try {
1872
2297
  const had = globalThis.localStorage?.getItem(CONNECTED_KEY) != null;
@@ -1936,7 +2361,7 @@ function createClass2() {
1936
2361
  }
1937
2362
  live.add(this);
1938
2363
  this.#hideLegacy();
1939
- dropLegacyConnectedKey();
2364
+ migrateConnectedKey();
1940
2365
  const phone = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
1941
2366
  this.#set(stateFor({ connected: false, found: null, phone }));
1942
2367
  const ms = Number(this.getAttribute("discover-ms")) || 800;
@@ -2003,8 +2428,9 @@ function createClass2() {
2003
2428
  const modal = pairingModal({ title: "Connect bWalletX" });
2004
2429
  this.#set("connecting");
2005
2430
  try {
2006
- const { wallet } = await connectBwalletX({ pairing: modal });
2431
+ const { wallet, method } = await connectBwalletX({ pairing: modal });
2007
2432
  const { publicKey } = await wallet.getPublicKey({ identityKey: true });
2433
+ rememberBwalletX(method === "pairing" ? "pairing" : method === "in-app" ? "in-app" : "extension");
2008
2434
  this.#connected(publicKey);
2009
2435
  } catch (e) {
2010
2436
  if (e?.code === "NOT_FOUND") this.#set("none");
@@ -2031,6 +2457,7 @@ function createClass2() {
2031
2457
  this.#key = null;
2032
2458
  this.#paymail = null;
2033
2459
  dropLegacyConnectedKey();
2460
+ if (rememberedWalletChoice()?.type === BWALLETX_TYPE) forgetWalletChoice();
2034
2461
  this.#set("available");
2035
2462
  this.#emit("bwalletx-disconnected");
2036
2463
  }
@@ -2073,6 +2500,7 @@ defineBwalletxButton();
2073
2500
  0 && (module.exports = {
2074
2501
  BUTTON_TAG,
2075
2502
  CONNECTED_KEY,
2503
+ CONTINUE_OTHER_LABEL,
2076
2504
  GET_URL,
2077
2505
  MENU_CSS,
2078
2506
  PENDING_LABEL,
@@ -2080,6 +2508,7 @@ defineBwalletxButton();
2080
2508
  PENDING_NOTE,
2081
2509
  SIGNIN_CSS,
2082
2510
  SIGNIN_TAG,
2511
+ SWITCH_NOTE,
2083
2512
  WEB_WALLET_URL,
2084
2513
  _resetBwalletxButtons,
2085
2514
  buildAuthorizeUrl,
@@ -2089,6 +2518,7 @@ defineBwalletxButton();
2089
2518
  getUrlFor,
2090
2519
  handleLabel,
2091
2520
  markSvg,
2521
+ migrateConnectedKey,
2092
2522
  signinText,
2093
2523
  stateFor
2094
2524
  });