@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
@@ -17,7 +17,7 @@ function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
17
17
  return new Promise((resolve) => {
18
18
  let done = false;
19
19
  let timer;
20
- const finish = (r) => {
20
+ const finish2 = (r) => {
21
21
  if (done) return;
22
22
  done = true;
23
23
  if (timer !== void 0) clearTimeout(timer);
@@ -28,19 +28,19 @@ function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
28
28
  const d = e.detail;
29
29
  const rdns = d?.info?.rdns ?? "";
30
30
  if (d?.wallet && BWALLETX_RDNS.includes(rdns)) {
31
- finish({ wallet: d.wallet, method: rdns === RDNS_EXTENSION ? "extension" : "in-app", rdns });
31
+ finish2({ wallet: d.wallet, method: rdns === RDNS_EXTENSION ? "extension" : "in-app", rdns });
32
32
  }
33
33
  };
34
34
  window.addEventListener("brc100:announceWallet", onAnnounce);
35
35
  window.dispatchEvent(new Event("brc100:requestWallet"));
36
36
  if (done) return;
37
37
  if (isFramed()) {
38
- void probeXdm(Math.max(ms, 1200)).then((w) => finish(w ? { wallet: w, method: "in-app", rdns: null } : null));
38
+ void probeXdm(Math.max(ms, 1200)).then((w) => finish2(w ? { wallet: w, method: "in-app", rdns: null } : null));
39
39
  return;
40
40
  }
41
41
  timer = setTimeout(() => {
42
42
  timer = void 0;
43
- finish(null);
43
+ finish2(null);
44
44
  }, ms);
45
45
  });
46
46
  }
@@ -733,15 +733,20 @@ function desktopOption(ms = 1500) {
733
733
  return opt;
734
734
  }
735
735
  function autoChoice(list, force = false) {
736
- const detected = list.filter((w) => w.kind === "brc100");
737
- const others = list.filter((w) => w.kind === "handcash" || w.kind === "custom");
738
- if (force) return detected[0] ?? list.find((w) => w.kind !== "pairing") ?? list[0] ?? null;
736
+ const auto = list.filter((w) => !isLocalDesktop(w));
737
+ const detected = auto.filter((w) => w.kind === "brc100");
738
+ const others = auto.filter((w) => w.kind === "handcash" || w.kind === "custom");
739
+ const hasLocal = auto.length !== list.length;
740
+ if (force) return detected[0] ?? auto.find((w) => w.kind !== "pairing") ?? auto[0] ?? null;
739
741
  if (!list.length) return null;
740
- if (list.length === 1) return list[0];
741
- if (detected.length === 1 && others.length === 0) return detected[0];
742
- if (detected.length === 0 && others.length === 0) return list[0];
742
+ if (list.length === 1) return hasLocal ? void 0 : list[0];
743
+ if (detected.length === 1 && others.length === 0 && !hasLocal) return detected[0];
744
+ if (detected.length === 0 && others.length === 0) return auto[0] ?? void 0;
743
745
  return void 0;
744
746
  }
747
+ function isLocalDesktop(w) {
748
+ return w.substrate === "json-api" || w.substrate === "cicada" || w.id === "localhost:3321" || w.id === "localhost:3301" || w.id === DESKTOP_OPTION_ID;
749
+ }
745
750
 
746
751
  // src/account.ts
747
752
  var PAYMAIL_LOOKUP_URL = "https://pay.bwallet.space/api/paymail/lookup?key=";
@@ -777,6 +782,7 @@ function isUserRejection(e) {
777
782
  }
778
783
  function signInErrorText(e) {
779
784
  if (isUserRejection(e)) return "Sign-in cancelled";
785
+ if (e?.name === "MetanetUnreachableError") return e.message;
780
786
  const code = e?.code;
781
787
  if (code === "NOT_FOUND") return "bWalletX was not found in this browser";
782
788
  const msg = e instanceof Error ? e.message : String(e ?? "");
@@ -810,6 +816,173 @@ function rememberIdentity(key) {
810
816
  }
811
817
  }
812
818
 
819
+ // src/device.ts
820
+ function isTouchDevice(env = globalThis) {
821
+ const nav = env.navigator;
822
+ if (!nav) return false;
823
+ const ua = nav.userAgent ?? "";
824
+ if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
825
+ if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
826
+ try {
827
+ return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
828
+ } catch {
829
+ return false;
830
+ }
831
+ }
832
+
833
+ // src/metanet.ts
834
+ import { WalletClient as WalletClient3 } from "@bsv/sdk";
835
+ var METANET_TYPE = "metanet";
836
+ var METANET_OPTION_ID = METANET_TYPE;
837
+ var METANET_JSON_API = "http://localhost:3321";
838
+ var METANET_LABEL = "Use Metanet Desktop or another BRC-100 wallet";
839
+ var METANET_NOTE = "Metanet Desktop must be running on this computer";
840
+ var METANET_REMEMBERED_NOTE = "Signs in with Metanet Desktop on this computer.";
841
+ var NOT_YOU_LABEL = "Not you? Use another wallet";
842
+ var METANET_UNREACHABLE = "We couldn\u2019t reach Metanet Desktop. Open it, then try again.";
843
+ var METANET_ANSWER_MS = 4e3;
844
+ var METANET_SILENT_MS = 2e3;
845
+ var MetanetUnreachableError = class extends Error {
846
+ code = "NOT_FOUND";
847
+ constructor() {
848
+ super(METANET_UNREACHABLE);
849
+ this.name = "MetanetUnreachableError";
850
+ }
851
+ };
852
+ function inAnyFrame(win = typeof window === "undefined" ? void 0 : window) {
853
+ if (!win) return false;
854
+ try {
855
+ return win.top !== win.self;
856
+ } catch {
857
+ return true;
858
+ }
859
+ }
860
+ function metanetOffered(env = {}) {
861
+ const win = env.window ?? (typeof window === "undefined" ? void 0 : window);
862
+ if (!win) return false;
863
+ const nav = env.navigator ?? (typeof navigator === "undefined" ? void 0 : navigator);
864
+ const mm = env.matchMedia ?? (typeof matchMedia === "undefined" ? void 0 : (q) => matchMedia(q));
865
+ return !isTouchDevice({ navigator: nav, matchMedia: mm }) && !inAnyFrame(win);
866
+ }
867
+ function client(o) {
868
+ if (o.make) return o.make();
869
+ const originator = o.originator ?? (typeof location !== "undefined" && location.host ? location.host : void 0);
870
+ return new WalletClient3("json-api", originator);
871
+ }
872
+ async function answers(wallet) {
873
+ return Promise.resolve().then(() => wallet.getVersion({})).then((v) => typeof v?.version === "string", () => false);
874
+ }
875
+ async function connectMetanetDesktop(o = {}) {
876
+ let wallet;
877
+ try {
878
+ wallet = client(o);
879
+ } catch {
880
+ throw new MetanetUnreachableError();
881
+ }
882
+ let timer;
883
+ const ok = await Promise.race([answers(wallet), new Promise((r) => timer = setTimeout(() => r(false), o.timeoutMs ?? METANET_ANSWER_MS))]);
884
+ clearTimeout(timer);
885
+ if (!ok) throw new MetanetUnreachableError();
886
+ return wallet;
887
+ }
888
+ function metanetOption(o = {}) {
889
+ const opt = {
890
+ id: METANET_OPTION_ID,
891
+ name: METANET_LABEL,
892
+ kind: "brc100",
893
+ isBwalletX: false,
894
+ substrate: "json-api",
895
+ async connect() {
896
+ const wallet = await connectMetanetDesktop(o);
897
+ opt.wallet = wallet;
898
+ await waitIfLocked(wallet);
899
+ const { publicKey } = await wallet.getPublicKey({ identityKey: true });
900
+ return { wallet, payer: wallet, identityKey: publicKey, optionId: opt.id, kind: "brc100" };
901
+ }
902
+ };
903
+ return opt;
904
+ }
905
+ var KNOWN_KEYS_KEY = "bwx:known-keys";
906
+ var KEY_RE = /^0[23][0-9a-f]{64}$/;
907
+ var MAX_KNOWN = 8;
908
+ function knownIdentityKeys() {
909
+ try {
910
+ const v = JSON.parse(globalThis.localStorage?.getItem(KNOWN_KEYS_KEY) || "[]");
911
+ return Array.isArray(v) ? v.filter((k) => typeof k === "string" && KEY_RE.test(k)) : [];
912
+ } catch {
913
+ return [];
914
+ }
915
+ }
916
+ function isKnownIdentityKey(key) {
917
+ if (!key) return false;
918
+ const k = key.toLowerCase();
919
+ if (!KEY_RE.test(k)) return false;
920
+ return knownIdentityKeys().includes(k) || rememberedIdentity()?.toLowerCase() === k;
921
+ }
922
+ function rememberKnownIdentityKey(key) {
923
+ const k = (key ?? "").toLowerCase();
924
+ if (!KEY_RE.test(k)) return;
925
+ try {
926
+ const list = [k, ...knownIdentityKeys().filter((x) => x !== k)].slice(0, MAX_KNOWN);
927
+ globalThis.localStorage?.setItem(KNOWN_KEYS_KEY, JSON.stringify(list));
928
+ } catch {
929
+ }
930
+ }
931
+ var triedThisLoad = false;
932
+ function silentAttemptUsed() {
933
+ return triedThisLoad;
934
+ }
935
+ function claimSilentAttempt() {
936
+ if (triedThisLoad) return false;
937
+ triedThisLoad = true;
938
+ return true;
939
+ }
940
+ function resetSilentAttempt() {
941
+ triedThisLoad = false;
942
+ }
943
+ async function trySilentMetanet(o = {}) {
944
+ if (!metanetOffered()) return { ok: false, stop: "not-here" };
945
+ if (!claimSilentAttempt()) return { ok: false, stop: "used" };
946
+ const run = async () => {
947
+ let wallet;
948
+ try {
949
+ wallet = client(o);
950
+ } catch {
951
+ return { ok: false, stop: "unreachable" };
952
+ }
953
+ if (!await answers(wallet)) return { ok: false, stop: "unreachable" };
954
+ const unlocked = await Promise.resolve().then(() => wallet.isAuthenticated({})).then((r2) => r2?.authenticated === true, () => false);
955
+ if (!unlocked) return { ok: false, stop: "locked" };
956
+ const identityKey = await Promise.resolve().then(() => wallet.getPublicKey({ identityKey: true })).then((r2) => r2.publicKey, () => null);
957
+ if (!identityKey) return { ok: false, stop: "unreachable" };
958
+ if (!isKnownIdentityKey(identityKey)) return { ok: false, stop: "unknown-key" };
959
+ return { ok: true, wallet, identityKey };
960
+ };
961
+ let timer;
962
+ const r = await Promise.race([
963
+ run().catch(() => ({ ok: false, stop: "unreachable" })),
964
+ new Promise((res) => timer = setTimeout(() => res({ ok: false, stop: "slow" }), o.windowMs ?? METANET_SILENT_MS))
965
+ ]);
966
+ clearTimeout(timer);
967
+ return r;
968
+ }
969
+ var warnedLogout = false;
970
+ var isProduction = () => {
971
+ try {
972
+ return globalThis.process?.env?.NODE_ENV === "production";
973
+ } catch {
974
+ return false;
975
+ }
976
+ };
977
+ function warnIfNoLogout(o) {
978
+ if (!o.verifyUrl || o.logoutUrl || warnedLogout || isProduction() || typeof console === "undefined") return false;
979
+ warnedLogout = true;
980
+ console.warn(
981
+ `@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.`
982
+ );
983
+ return true;
984
+ }
985
+
813
986
  // src/choice.ts
814
987
  var BWALLETX_TYPE = "bwalletx";
815
988
  function substrateOf(o) {
@@ -851,6 +1024,10 @@ function decideWallet(list, o = {}) {
851
1024
  const a = autoChoice([...list], o.picker === false);
852
1025
  return a === void 0 ? { option: null, reason: "chooser" } : a === null ? { option: null, reason: "none" } : { option: a, reason: "legacy" };
853
1026
  }
1027
+ if (o.remembered && !o.preferBwalletX) {
1028
+ const m = matchRemembered(list, o.remembered);
1029
+ if (m) return { option: m, reason: "remembered" };
1030
+ }
854
1031
  const b = list.find(isPresentBwalletX);
855
1032
  if (b) return { option: b, reason: "bwalletx" };
856
1033
  if (o.picker === false) {
@@ -881,6 +1058,74 @@ function reportAttempt(hook, a) {
881
1058
  } catch {
882
1059
  }
883
1060
  }
1061
+ var WALLET_CHOICE_KEY = "bwx:wallet";
1062
+ var SUBSTRATES = ["extension", "in-app", "xdm", "injected", "window.CWI", "json-api", "cicada", "pairing", "adapter"];
1063
+ var TYPE_RE = /^[A-Za-z0-9._:-]{1,64}$/;
1064
+ 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;
1065
+ function isRememberable(o) {
1066
+ const t = walletTypeOf(o);
1067
+ return substrateOf(o) !== "window.CWI" && TYPE_RE.test(t) && !KEY_LIKE.test(t);
1068
+ }
1069
+ function isExplicitPick(o, reason) {
1070
+ return o.isBwalletX || reason === "chooser" || reason === "requested" || reason === "remembered";
1071
+ }
1072
+ function rememberWalletChoice(o) {
1073
+ try {
1074
+ if (!o) return void globalThis.localStorage?.removeItem(WALLET_CHOICE_KEY);
1075
+ if (!isRememberable(o)) return;
1076
+ const v = { type: walletTypeOf(o), substrate: substrateOf(o), v: 1 };
1077
+ globalThis.localStorage?.setItem(WALLET_CHOICE_KEY, JSON.stringify(v));
1078
+ } catch {
1079
+ }
1080
+ }
1081
+ function rememberedWalletChoice() {
1082
+ try {
1083
+ const raw = globalThis.localStorage?.getItem(WALLET_CHOICE_KEY);
1084
+ if (!raw) return null;
1085
+ const j = JSON.parse(raw);
1086
+ if (typeof j.type !== "string" || !TYPE_RE.test(j.type) || KEY_LIKE.test(j.type)) return null;
1087
+ if (!SUBSTRATES.includes(j.substrate) || j.substrate === "window.CWI") return null;
1088
+ return { type: j.type, substrate: j.substrate };
1089
+ } catch {
1090
+ return null;
1091
+ }
1092
+ }
1093
+ function forgetWalletChoice() {
1094
+ rememberWalletChoice(null);
1095
+ }
1096
+ function needsBwalletX(type) {
1097
+ return !!type && type !== BWALLETX_TYPE;
1098
+ }
1099
+ function tapGoesElsewhere(type) {
1100
+ return needsBwalletX(type) && type !== METANET_TYPE && type !== DESKTOP_OPTION_ID;
1101
+ }
1102
+ function isLocalDesktopType(r) {
1103
+ return r.type === METANET_TYPE || r.type === DESKTOP_OPTION_ID || r.substrate === "json-api" || r.substrate === "cicada";
1104
+ }
1105
+ function matchRemembered(list, r) {
1106
+ if (isLocalDesktopType(r)) return null;
1107
+ if (r.type === BWALLETX_TYPE) {
1108
+ return (r.substrate === "pairing" ? list.find((o) => o.kind === "pairing") : list.find(isPresentBwalletX)) ?? null;
1109
+ }
1110
+ return list.find((o) => !o.isBwalletX && !isLocalDesktop(o) && isRememberable(o) && walletTypeOf(o) === r.type) ?? null;
1111
+ }
1112
+ var ATTACHABLE = ["extension", "in-app", "xdm", "injected"];
1113
+ function planAutoConnect(o) {
1114
+ const r = o.remembered;
1115
+ if (!r || o.enabled === false) return { action: "none", remembered: r, needsBwalletX: false };
1116
+ if (r.type === METANET_TYPE) {
1117
+ return { action: o.metanetHere ?? metanetOffered() ? "silent-metanet" : "none", remembered: r, needsBwalletX: false };
1118
+ }
1119
+ if (isLocalDesktopType(r)) return { action: "none", remembered: r, needsBwalletX: false };
1120
+ const action = o.signedIn && ATTACHABLE.includes(r.substrate) ? "attach" : "wait-for-tap";
1121
+ return { action, remembered: r, needsBwalletX: needsBwalletX(r.type) };
1122
+ }
1123
+ async function attachRemembered(r, o = {}) {
1124
+ if (!ATTACHABLE.includes(r.substrate)) return null;
1125
+ const list = await discoverWallets({ ms: o.ms });
1126
+ const m = matchRemembered(list, r);
1127
+ return m && m.kind === "brc100" && m.wallet && ATTACHABLE.includes(substrateOf(m)) ? m : null;
1128
+ }
884
1129
 
885
1130
  // src/modal.ts
886
1131
  import qrcode from "qrcode-generator";
@@ -971,6 +1216,114 @@ async function requestAccess(o) {
971
1216
  }
972
1217
  }
973
1218
 
1219
+ // src/connect-flow.ts
1220
+ async function runConnect(o) {
1221
+ const started = Date.now();
1222
+ const { list } = o;
1223
+ let choice = null;
1224
+ let reason = "none";
1225
+ try {
1226
+ if (o.metanet) {
1227
+ if (!metanetOffered()) throw Object.assign(new Error("Metanet Desktop runs on a computer, outside any frame."), { code: "NOT_SUPPORTED" });
1228
+ choice = metanetOption(typeof o.metanet === "object" ? o.metanet : {});
1229
+ reason = "requested";
1230
+ return await finish(o, choice, reason, started);
1231
+ }
1232
+ const d = decideWallet(list, {
1233
+ walletId: o.walletId,
1234
+ chooser: o.chooser,
1235
+ picker: o.picker,
1236
+ fastPath: o.fastPath,
1237
+ remembered: o.remembered,
1238
+ preferBwalletX: o.preferBwalletX
1239
+ });
1240
+ choice = d.option;
1241
+ reason = d.reason;
1242
+ if (!choice && d.reason === "chooser") choice = await o.pick(list);
1243
+ if (!choice) {
1244
+ if (d.reason === "none" || !list.length) throw Object.assign(new Error("No wallet found. Get bWalletX at bwalletx.com."), { code: "NOT_FOUND" });
1245
+ reportAttempt(o.onAttempt, attemptFor(null, "cancelled", started));
1246
+ return null;
1247
+ }
1248
+ return await finish(o, choice, reason, started);
1249
+ } catch (e) {
1250
+ reportAttempt(o.onAttempt, attemptFor(choice, outcomeOf(e), started));
1251
+ throw e;
1252
+ }
1253
+ }
1254
+ async function finish(o, choice, reason, started) {
1255
+ const signIn = !!(o.challengeUrl && o.verifyUrl) && choice.kind !== "handcash";
1256
+ const cw = await choice.connect(signIn ? { identity: false } : void 0);
1257
+ let handle = cw.handle ?? null;
1258
+ let paymail = cw.paymail ?? null;
1259
+ let identityKey = cw.identityKey;
1260
+ let signedIn = false;
1261
+ if (cw.wallet && signIn) {
1262
+ const { result, verified } = await signInAndVerify({
1263
+ wallet: cw.wallet,
1264
+ identityKey: cw.identityKey ?? void 0,
1265
+ // Metanet's connect() just checked the lock: don't ask again (rule 7, the minimum).
1266
+ skipWaitForAuthentication: choice.id === METANET_OPTION_ID,
1267
+ // The key that signed in here last time: a repeat sign-in is then the signature alone.
1268
+ identityKeyHint: rememberedIdentity(),
1269
+ challengeNeedsIdentityKey: o.challengeNeedsIdentityKey,
1270
+ challenge: (key) => o.post(o.challengeUrl, key ? { identityKey: key } : {}),
1271
+ verify: (r) => o.post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
1272
+ });
1273
+ rememberIdentity(result.identityKey);
1274
+ identityKey = result.identityKey;
1275
+ handle = verified.handle ?? handle;
1276
+ paymail = verified.paymail ?? paymail;
1277
+ signedIn = true;
1278
+ } else if (cw.wallet && !identityKey) {
1279
+ identityKey = (await cw.wallet.getPublicKey({ identityKey: true })).publicKey;
1280
+ }
1281
+ if (o.remember && isExplicitPick(choice, reason)) rememberWalletChoice(choice);
1282
+ if (o.remember && signedIn) rememberKnownIdentityKey(identityKey);
1283
+ reportAttempt(o.onAttempt, attemptFor(choice, signedIn ? "signed-in" : "connected", started));
1284
+ return { connected: cw, identityKey, handle, paymail, signedIn, walletType: walletTypeOf(choice), substrate: substrateOf(choice), reason };
1285
+ }
1286
+ async function runSilentMetanet(o = {}) {
1287
+ const s = await trySilentMetanet(o);
1288
+ if (!s.ok || o.isAbandoned?.()) return null;
1289
+ const started = Date.now();
1290
+ const opt = metanetOption(o);
1291
+ opt.wallet = s.wallet;
1292
+ const cw = { wallet: s.wallet, payer: s.wallet, identityKey: s.identityKey, optionId: METANET_OPTION_ID, kind: "brc100" };
1293
+ const done = (signedIn, handle = null, paymail = null) => {
1294
+ rememberWalletChoice(opt);
1295
+ if (signedIn) rememberKnownIdentityKey(s.identityKey);
1296
+ reportAttempt(o.onAttempt, attemptFor(opt, signedIn ? "signed-in" : "connected", started));
1297
+ return { connected: cw, identityKey: s.identityKey, handle, paymail, signedIn, walletType: walletTypeOf(opt), substrate: "json-api", reason: "remembered" };
1298
+ };
1299
+ if (o.sessionKey !== void 0 && o.sessionKey !== null) {
1300
+ return o.sessionKey.toLowerCase() === s.identityKey.toLowerCase() ? done(true) : null;
1301
+ }
1302
+ if (!o.challengeUrl || !o.verifyUrl || !o.post) return done(false);
1303
+ if (!isKnownIdentityKey(s.identityKey)) return null;
1304
+ try {
1305
+ o.onSignIn?.();
1306
+ const post = o.post;
1307
+ const { result, verified } = await signInAndVerify({
1308
+ wallet: s.wallet,
1309
+ identityKey: s.identityKey,
1310
+ skipWaitForAuthentication: true,
1311
+ // trySilentMetanet just saw it unlocked
1312
+ challenge: (key) => post(o.challengeUrl, key ? { identityKey: key } : {}),
1313
+ verify: (r) => post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
1314
+ });
1315
+ if (o.isAbandoned?.()) {
1316
+ await Promise.resolve(o.logout?.()).catch(() => void 0);
1317
+ return null;
1318
+ }
1319
+ rememberIdentity(result.identityKey);
1320
+ return done(true, verified.handle ?? null, verified.paymail ?? null);
1321
+ } catch (e) {
1322
+ reportAttempt(o.onAttempt, attemptFor(opt, outcomeOf(e), started));
1323
+ return null;
1324
+ }
1325
+ }
1326
+
974
1327
  // src/element.ts
975
1328
  var SIGNIN_TAG = "bwalletx-signin";
976
1329
  var GET_URL = "https://bwalletx.com/get";
@@ -1036,6 +1389,11 @@ button[aria-busy=true]{cursor:wait}
1036
1389
  /* "Other wallets" (0.5.6): one small link under the button */
1037
1390
  .ow{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
1038
1391
  :host([theme=dark]) .ow,:host-context(.dark) .ow{color:#a1a1aa}
1392
+ .sw{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#a16207;text-align:center}
1393
+ :host([theme=dark]) .sw,:host-context(.dark) .sw{color:#fcd34d}
1394
+ .mn{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
1395
+ .mn small{display:block;font-size:11px;opacity:.85}
1396
+ :host([theme=dark]) .mn,:host-context(.dark) .mn{color:#a1a1aa}
1039
1397
  .lnk{all:unset;cursor:pointer;color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px}
1040
1398
  .lnk:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
1041
1399
  /* error: one line plus Retry */
@@ -1074,6 +1432,11 @@ var MENU_CSS = `
1074
1432
  var PENDING_NOTE = "Sign in: shares only your public identity key, moves no money";
1075
1433
  var PENDING_LABEL = "Approve in bWalletX\u2026";
1076
1434
  var PENDING_LABEL_OTHER = "Approve in your wallet\u2026";
1435
+ var SWITCH_NOTE = "Some features need bWalletX";
1436
+ var CONTINUE_OTHER_LABEL = "Continue with your wallet";
1437
+ function walletGlyph(px) {
1438
+ 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>`;
1439
+ }
1077
1440
  function b64url2(bytes) {
1078
1441
  let s = "";
1079
1442
  for (const b of bytes) s += String.fromCharCode(b);
@@ -1113,7 +1476,7 @@ async function postAccess(url, body) {
1113
1476
  const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
1114
1477
  return res.json().catch(() => ({ error: "bad_request", message: `HTTP ${res.status}` }));
1115
1478
  }
1116
- 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"];
1479
+ 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"];
1117
1480
  var STATES = ["signed-out", "restoring", "pending", "signed-in", "error"];
1118
1481
  function createClass() {
1119
1482
  return class BwalletxSigninElement extends HTMLElement {
@@ -1128,14 +1491,21 @@ function createClass() {
1128
1491
  #error = "";
1129
1492
  #restored = false;
1130
1493
  #pendingLabel = PENDING_LABEL;
1494
+ /** The wallet type signed in with here (0.6.0), for the Switch note. */
1495
+ #signedType = null;
1131
1496
  #menu = null;
1497
+ /** (0.7.0) A Metanet sign-in (tapped or silent) is in flight. */
1498
+ #viaMetanet = false;
1499
+ /** (0.7.0) Bumped by "Not you?": a Metanet sign-in started before it lands nobody. */
1500
+ #run = 0;
1132
1501
  constructor() {
1133
1502
  super();
1134
1503
  this.#root = this.attachShadow({ mode: "open" });
1135
1504
  }
1136
1505
  connectedCallback() {
1506
+ if (!this.#controlled && this.#stateful) warnIfNoLogout({ verifyUrl: this.#attr("verify-url"), logoutUrl: this.#attr("logout-url"), where: "<bwalletx-signin>" });
1137
1507
  this.#render();
1138
- void this.#restore();
1508
+ void this.#restore().then(() => this.#silentMetanet());
1139
1509
  }
1140
1510
  disconnectedCallback() {
1141
1511
  this.#menu?.close(false);
@@ -1241,10 +1611,11 @@ function createClass() {
1241
1611
  const restoring = state === "restoring";
1242
1612
  const error = state === "error";
1243
1613
  const pendingLabel = this.#controlled ? PENDING_LABEL : this.#pendingLabel;
1244
- const text = pending ? pendingLabel : error ? "Retry" : signinText(this.#attr("label") ?? "signin", brand);
1614
+ const elsewhere = state === "signed-out" && tapGoesElsewhere(this.#walletType());
1615
+ const text = pending ? pendingLabel : error ? "Retry" : elsewhere ? CONTINUE_OTHER_LABEL : signinText(this.#attr("label") ?? "signin", brand);
1245
1616
  const subAttr = this.#attr("subtitle");
1246
1617
  const subDefault = brand === "bWalletX" && (size === "lg" || size === "md") ? "also works with bWallet" : null;
1247
- const subtitle = pending || error ? null : subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
1618
+ const subtitle = pending || error || elsewhere ? null : subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
1248
1619
  const loading = this.hasAttribute("loading") || this.#busy || pending || restoring;
1249
1620
  const disabled = this.hasAttribute("disabled");
1250
1621
  const markPx = size === "icon" ? 24 : size === "sm" ? 15 : size === "md" ? 17 : 20;
@@ -1253,18 +1624,26 @@ function createClass() {
1253
1624
  const webUrl = this.#attr("web-url") ?? WEB_WALLET_URL;
1254
1625
  const errText = error ? (this.#controlled ? this.#attr("error") : this.#error) || "Sign-in failed" : "";
1255
1626
  const label = size === "icon" ? "" : `<span class="txt" part="text"><b part="label">${esc(text)}</b>${subtitle ? `<small part="subtitle">${esc(subtitle)}</small>` : ""}</span>`;
1256
- const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : markSvg(markPx);
1627
+ const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : elsewhere ? walletGlyph(markPx - 4) : markSvg(markPx);
1257
1628
  const otherOff = this.#attr("other-wallets") === "off";
1258
1629
  const otherLink = '<button type="button" class="lnk" data-act="other" part="other-wallets-link">Other wallets</button>';
1259
1630
  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>` : "";
1260
1631
  const ow = !this.#notFound && !pending && !restoring && this.#otherWalletsShown(size) ? `<p class="ow" part="other-wallets">${otherLink}</p>` : "";
1632
+ 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>` : "";
1261
1633
  const err = error ? `<p class="err" part="error" role="alert">${esc(errText)}</p>` : "";
1634
+ const mnHere = this.#metanetShown(size);
1635
+ const mnRemembered = mnHere && (pending ? this.#metanetPending() : !restoring && this.#walletType() === METANET_TYPE);
1636
+ const notYou = `<button type="button" class="lnk" data-act="not-you" part="not-you-link">${esc(NOT_YOU_LABEL)}</button>`;
1637
+ 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>`;
1262
1638
  const compact = size === "sm" || size === "icon";
1263
1639
  const note = pending && !compact ? `<p class="note" part="note">${esc(PENDING_NOTE)}</p>` : "";
1264
1640
  const status = pending ? `<span class="sr" role="status">${esc(pendingLabel)}</span>` : loading ? '<span class="sr" role="status">Connecting to bWalletX\u2026</span>' : "";
1265
- 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}`;
1641
+ 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}`;
1266
1642
  this.button.addEventListener("click", () => void this.#click());
1267
1643
  for (const l of Array.from(this.#root.querySelectorAll("[data-act=other]"))) l.addEventListener("click", () => void this.otherWallets());
1644
+ this.#root.querySelector("[data-act=switch]")?.addEventListener("click", () => void this.switchToBwalletX());
1645
+ this.#root.querySelector("[data-act=metanet]")?.addEventListener("click", () => void this.useMetanet());
1646
+ this.#root.querySelector("[data-act=not-you]")?.addEventListener("click", () => this.notYou());
1268
1647
  bindPrewarm(this.button);
1269
1648
  if (pending && compact) this.#showTip(PENDING_NOTE);
1270
1649
  else this.#hideTip();
@@ -1332,7 +1711,20 @@ function createClass() {
1332
1711
  close(false);
1333
1712
  void this.signOut();
1334
1713
  });
1335
- menu.append(who, copy, out);
1714
+ const items = [copy, out];
1715
+ menu.append(who);
1716
+ if (needsBwalletX(this.#walletType())) {
1717
+ const sw = item("Switch to bWalletX", () => {
1718
+ close(false);
1719
+ void this.switchToBwalletX();
1720
+ });
1721
+ const line = document.createElement("div");
1722
+ line.className = "who";
1723
+ line.textContent = SWITCH_NOTE;
1724
+ menu.append(line, sw);
1725
+ items.unshift(sw);
1726
+ }
1727
+ menu.append(copy, out);
1336
1728
  root.append(style, menu);
1337
1729
  document.body.appendChild(host);
1338
1730
  const place = () => {
@@ -1348,7 +1740,6 @@ function createClass() {
1348
1740
  menu.style.top = `${below + h > vh && r.top - 8 - h > 0 ? r.top - 8 - h : below}px`;
1349
1741
  };
1350
1742
  place();
1351
- const items = [copy, out];
1352
1743
  const focusAt = (i) => items[(i + items.length) % items.length].focus();
1353
1744
  const onKey = (e) => {
1354
1745
  const i = items.indexOf(root.activeElement);
@@ -1403,6 +1794,8 @@ function createClass() {
1403
1794
  if (this.#controlled) return;
1404
1795
  const url = this.#attr("logout-url");
1405
1796
  if (url) await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
1797
+ if (this.#remember) forgetWalletChoice();
1798
+ this.#signedType = null;
1406
1799
  this.#account = null;
1407
1800
  this.#set("signed-out");
1408
1801
  this.#emit("bwalletx-signed-out");
@@ -1422,6 +1815,110 @@ function createClass() {
1422
1815
  otherWallets() {
1423
1816
  return this.#click("chooser");
1424
1817
  }
1818
+ /** (0.6.0) "Switch": sign in with bWalletX (its sheet when here, else the chooser with Get bWalletX first). */
1819
+ switchToBwalletX() {
1820
+ return this.#click("bwalletx");
1821
+ }
1822
+ get #remember() {
1823
+ return this.#attr("remember") !== "off";
1824
+ }
1825
+ /**
1826
+ * (0.7.0) "Use Metanet Desktop or another BRC-100 wallet" is offered: metanet on, or auto at lg/md when this
1827
+ * element signs in or follows a provider. Never on a phone or inside a frame.
1828
+ */
1829
+ #metanetShown(size) {
1830
+ const a = this.#attr("metanet");
1831
+ if (a === "off" || !metanetOffered()) return false;
1832
+ if (a === "on") return true;
1833
+ return (size === "lg" || size === "md") && (this.#controlled || !!this.#attr("challenge-url"));
1834
+ }
1835
+ /** A Metanet sign-in is on screen: our own, or (driven) the provider's with Metanet remembered. */
1836
+ #metanetPending() {
1837
+ return this.#controlled ? this.#attr("wallet-type") === METANET_TYPE : this.#viaMetanet;
1838
+ }
1839
+ /** (0.7.0) The Metanet link: connect to Metanet Desktop on localhost:3321 (after this tap, never before). */
1840
+ useMetanet() {
1841
+ return this.#click("metanet");
1842
+ }
1843
+ /**
1844
+ * (0.7.0) "Not you? Use another wallet": forget the remembered wallet here, and drop a Metanet sign-in in flight
1845
+ * (if its verify already set a session, logout-url ends it). Emits bwalletx-not-you (cancelable).
1846
+ */
1847
+ notYou() {
1848
+ if (!this.#emit("bwalletx-not-you", { element: this }, true)) return;
1849
+ this.#run++;
1850
+ if (this.#remember) forgetWalletChoice();
1851
+ this.#signedType = null;
1852
+ if (this.#viaMetanet && !this.#controlled) {
1853
+ this.#viaMetanet = false;
1854
+ this.#busy = false;
1855
+ if (this.#phase === "pending") this.#phase = "signed-out";
1856
+ }
1857
+ this.#render();
1858
+ }
1859
+ /**
1860
+ * (0.7.0) Rule 4: back with Metanet remembered and no session, ONE silent attempt per page load (2 s, a known
1861
+ * identity key only), then the signature alone. Any stop or failure is silent: the normal button stays.
1862
+ */
1863
+ async #silentMetanet() {
1864
+ if (this.#controlled || !this.#stateful || !this.#remember || this.#attr("require-token")) return;
1865
+ if (!this.#metanetShown(this.#attr("size") ?? "lg")) return;
1866
+ const challengeUrl = this.#attr("challenge-url");
1867
+ const verifyUrl = this.#attr("verify-url");
1868
+ if (!challengeUrl || !verifyUrl || this.#phase !== "signed-out" || this.#busy || silentAttemptUsed()) return;
1869
+ if (rememberedWalletChoice()?.type !== METANET_TYPE || !metanetOffered()) return;
1870
+ const run = ++this.#run;
1871
+ const live = () => run === this.#run;
1872
+ const started = Date.now();
1873
+ const r = await runSilentMetanet({
1874
+ challengeUrl,
1875
+ verifyUrl,
1876
+ post: (url, body) => postJson(url, body),
1877
+ isAbandoned: () => !live(),
1878
+ logout: async () => {
1879
+ const out = this.#attr("logout-url");
1880
+ if (out) await fetch(out, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
1881
+ },
1882
+ onSignIn: () => {
1883
+ if (!live() || this.#phase !== "signed-out" || this.#busy) return;
1884
+ this.#viaMetanet = true;
1885
+ this.#busy = true;
1886
+ this.#pendingLabel = PENDING_LABEL_OTHER;
1887
+ this.#set("pending");
1888
+ },
1889
+ onAttempt: (a) => this.#emit("bwalletx-attempt", a)
1890
+ });
1891
+ if (!live()) return;
1892
+ this.#viaMetanet = false;
1893
+ this.#busy = false;
1894
+ if (!r) {
1895
+ if (this.#phase === "pending") this.#set("signed-out");
1896
+ return;
1897
+ }
1898
+ this.#signedType = r.walletType;
1899
+ this.#signedIn({ identityKey: r.identityKey, handle: r.handle, paymail: r.paymail });
1900
+ 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 });
1901
+ }
1902
+ /** The wallet type the Switch note judges: the provider's, else what signed in here, else the remembered one. */
1903
+ #walletType() {
1904
+ if (this.#controlled) return this.#attr("wallet-type");
1905
+ return this.#signedType ?? (this.#remember ? rememberedWalletChoice()?.type ?? null : null);
1906
+ }
1907
+ #switchNoteShown(size) {
1908
+ const a = this.#attr("switch-note");
1909
+ if (a === "off" || !tapGoesElsewhere(this.#walletType())) return false;
1910
+ return a === "on" || size === "lg" || size === "md";
1911
+ }
1912
+ /**
1913
+ * Signed in: note the wallet type, and remember it for this origin when the user picked it (0.6.0), plus the
1914
+ * identity key as known here (0.7.0, public keys only: Metanet's silent attempt goes on only for these). Pass a key
1915
+ * only after the site's verify route accepted it.
1916
+ */
1917
+ #picked(option, reason, identityKey) {
1918
+ this.#signedType = walletTypeOf(option);
1919
+ if (this.#remember && isExplicitPick(option, reason)) rememberWalletChoice(option);
1920
+ if (this.#remember) rememberKnownIdentityKey(identityKey);
1921
+ }
1425
1922
  #attempt(option, outcome, started) {
1426
1923
  this.#emit("bwalletx-attempt", attemptFor(option, outcome, started));
1427
1924
  }
@@ -1430,16 +1927,24 @@ function createClass() {
1430
1927
  * when another wallet is here or the user asked; otherwise 'not-found'. null: the chooser was closed.
1431
1928
  */
1432
1929
  async #choose(mode, ms, pairing) {
1930
+ if (mode === "metanet") return { option: metanetOption(), reason: "requested" };
1931
+ const r = mode === "signin" && this.#remember ? rememberedWalletChoice() : null;
1932
+ if (r && r.type !== METANET_TYPE && !(r.type === BWALLETX_TYPE && r.substrate !== "pairing")) {
1933
+ const m = matchRemembered(await discoverWallets({ ms, pairing: () => pairing }), r);
1934
+ if (m) return { option: m, reason: "remembered" };
1935
+ }
1433
1936
  if (mode !== "chooser") {
1434
1937
  const f = await discoverBwalletX(ms);
1435
1938
  if (f) {
1436
1939
  const substrate = f.rdns === RDNS_EXTENSION ? "extension" : f.rdns === RDNS_MOBILE ? "in-app" : "xdm";
1437
- return brc100Option(f.wallet, { id: f.rdns ?? "bwalletx-xdm", rdns: f.rdns ?? void 0, name: "bWalletX", isBwalletX: true, substrate });
1940
+ const option = brc100Option(f.wallet, { id: f.rdns ?? "bwalletx-xdm", rdns: f.rdns ?? void 0, name: "bWalletX", isBwalletX: true, substrate });
1941
+ return { option, reason: "bwalletx" };
1438
1942
  }
1439
1943
  }
1440
1944
  const list = await discoverWallets({ ms: 50, pairing: () => pairing });
1441
1945
  if (mode !== "chooser" && !hasBwalletX(list) && !otherDetected(list).length) return "not-found";
1442
- return chooseWallet(list, { getUrl: this.#attr("get-url") ?? void 0 });
1946
+ const picked = await chooseWallet(list, { getUrl: this.#attr("get-url") ?? void 0 });
1947
+ return picked ? { option: picked, reason: "chooser" } : null;
1443
1948
  }
1444
1949
  async #click(mode = "signin") {
1445
1950
  if (this.hasAttribute("disabled") || this.hasAttribute("loading") || this.#busy || this.state === "pending") return;
@@ -1463,9 +1968,12 @@ function createClass() {
1463
1968
  const started = Date.now();
1464
1969
  const pairing = pairingModal();
1465
1970
  let option = null;
1971
+ const run = ++this.#run;
1972
+ const live = () => run === this.#run;
1466
1973
  this.#notFound = false;
1467
1974
  this.#busy = true;
1468
- this.#pendingLabel = PENDING_LABEL;
1975
+ this.#viaMetanet = mode === "metanet";
1976
+ this.#pendingLabel = mode === "metanet" ? PENDING_LABEL_OTHER : PENDING_LABEL;
1469
1977
  if (own) this.#set("pending");
1470
1978
  else this.#render();
1471
1979
  try {
@@ -1484,12 +1992,14 @@ function createClass() {
1484
1992
  this.#attempt(null, "cancelled", started);
1485
1993
  return;
1486
1994
  }
1487
- option = picked;
1995
+ option = picked.option;
1996
+ const reason = picked.reason;
1488
1997
  if (!option.isBwalletX) {
1489
1998
  this.#pendingLabel = PENDING_LABEL_OTHER;
1490
1999
  if (own) this.#render();
1491
2000
  }
1492
2001
  const cw = await option.connect({ identity: false });
2002
+ if (!live()) return;
1493
2003
  const wallet = cw.wallet;
1494
2004
  if (!wallet) throw Object.assign(new Error("This wallet cannot sign in here"), { code: "NOT_SUPPORTED" });
1495
2005
  const sub = substrateOf(option);
@@ -1507,6 +2017,7 @@ function createClass() {
1507
2017
  challenge: (identityKey) => postJson(challengeUrl, { identityKey, require: hint }),
1508
2018
  verify: verifyUrl2 ? (response2) => postAccess(verifyUrl2, response2) : void 0
1509
2019
  });
2020
+ this.#picked(option, reason, verifyUrl2 ? a.response.identityKey : null);
1510
2021
  this.#attempt(option, "signed-in", started);
1511
2022
  this.#emit("bwalletx-access-granted", { identityKey: a.response.identityKey, response: a.verified });
1512
2023
  } catch (e) {
@@ -1519,18 +2030,29 @@ function createClass() {
1519
2030
  const verifyUrl = this.#attr("verify-url");
1520
2031
  const { result: r, verified } = await signInAndVerify({
1521
2032
  wallet,
2033
+ // Metanet hands over its key on connect (one request at a time), so its sign-in is the signature alone.
2034
+ identityKey: cw.identityKey ?? void 0,
2035
+ skipWaitForAuthentication: option.id === METANET_OPTION_ID,
2036
+ // its connect() just checked the lock
1522
2037
  identityKeyHint: verifyUrl ? rememberedIdentity() : void 0,
1523
2038
  challenge: (identityKey) => postJson(challengeUrl, identityKey ? { identityKey } : {}),
1524
2039
  verify: async (res) => verifyUrl ? await postJson(verifyUrl, { identityKey: res.identityKey, nonce: res.nonce, signature: res.signature }) : {}
1525
2040
  });
2041
+ if (!live()) {
2042
+ const out = this.#attr("logout-url");
2043
+ if (verifyUrl && out) await fetch(out, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
2044
+ return;
2045
+ }
1526
2046
  if (verifyUrl) rememberIdentity(r.identityKey);
1527
2047
  const response = verifyUrl ? verified : null;
2048
+ this.#picked(option, reason, verifyUrl ? r.identityKey : null);
1528
2049
  if (own) this.#signedIn({ identityKey: r.identityKey, handle: response?.handle ?? null, paymail: response?.paymail ?? null });
1529
2050
  this.#attempt(option, "signed-in", started);
1530
2051
  this.#emit("bwalletx-signed-in", { identityKey: r.identityKey, method, nonce: r.nonce, signature: r.signature, response });
1531
2052
  } catch (error) {
1532
2053
  const outcome = outcomeOf(error);
1533
2054
  this.#attempt(option, outcome, started);
2055
+ if (!live()) return;
1534
2056
  const closedQr = outcome === "cancelled" && option?.kind === "pairing";
1535
2057
  if (own) {
1536
2058
  if (closedQr) this.#phase = prev;
@@ -1541,12 +2063,15 @@ function createClass() {
1541
2063
  }
1542
2064
  if (!closedQr) this.#emit("bwalletx-error", { error });
1543
2065
  } finally {
1544
- this.#busy = false;
1545
2066
  try {
1546
2067
  pairing.close();
1547
2068
  } catch {
1548
2069
  }
1549
- this.#render();
2070
+ if (live()) {
2071
+ this.#busy = false;
2072
+ this.#viaMetanet = false;
2073
+ this.#render();
2074
+ }
1550
2075
  }
1551
2076
  }
1552
2077
  };
@@ -1619,18 +2144,6 @@ function qrSvg(text) {
1619
2144
  q.make();
1620
2145
  return q.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
1621
2146
  }
1622
- function isTouchDevice(env = globalThis) {
1623
- const nav = env.navigator;
1624
- if (!nav) return false;
1625
- const ua = nav.userAgent ?? "";
1626
- if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
1627
- if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
1628
- try {
1629
- return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
1630
- } catch {
1631
- return false;
1632
- }
1633
- }
1634
2147
  function pairingModal(o = {}) {
1635
2148
  const ac = new AbortController();
1636
2149
  let ui = null;
@@ -1791,10 +2304,11 @@ function chooseWallet(options, o = {}) {
1791
2304
  row2.onclick = () => done(w);
1792
2305
  list.append(row2);
1793
2306
  }
1794
- if (o.desktop !== false && !options.some((w) => w.substrate === "json-api" || w.substrate === "cicada")) {
2307
+ if (o.desktop !== false && metanetOffered() && !options.some((w) => w.substrate === "json-api" || w.substrate === "cicada")) {
1795
2308
  if (!here && !sep) list.append(el("p", "bwx-sep", "Or use"));
1796
- const d = desktopOption();
1797
- const row2 = optionRow(d, false, "Metanet or BSV Desktop on this computer", "Fewer features");
2309
+ const d = metanetOption();
2310
+ const row2 = optionRow(d, false, METANET_NOTE);
2311
+ row2.title = "Connects to Metanet Desktop on this computer, never to a browser extension.";
1798
2312
  row2.onclick = () => done(d);
1799
2313
  list.append(row2);
1800
2314
  }
@@ -1839,6 +2353,7 @@ export {
1839
2353
  DESKTOP_OPTION_ID,
1840
2354
  desktopOption,
1841
2355
  autoChoice,
2356
+ isLocalDesktop,
1842
2357
  PAYMAIL_LOOKUP_URL,
1843
2358
  BAVATAR_URL,
1844
2359
  shortKey,
@@ -1850,6 +2365,31 @@ export {
1850
2365
  IDENTITY_HINT_KEY,
1851
2366
  rememberedIdentity,
1852
2367
  rememberIdentity,
2368
+ isTouchDevice,
2369
+ METANET_TYPE,
2370
+ METANET_OPTION_ID,
2371
+ METANET_JSON_API,
2372
+ METANET_LABEL,
2373
+ METANET_NOTE,
2374
+ METANET_REMEMBERED_NOTE,
2375
+ NOT_YOU_LABEL,
2376
+ METANET_UNREACHABLE,
2377
+ METANET_ANSWER_MS,
2378
+ METANET_SILENT_MS,
2379
+ MetanetUnreachableError,
2380
+ inAnyFrame,
2381
+ metanetOffered,
2382
+ connectMetanetDesktop,
2383
+ metanetOption,
2384
+ KNOWN_KEYS_KEY,
2385
+ knownIdentityKeys,
2386
+ isKnownIdentityKey,
2387
+ rememberKnownIdentityKey,
2388
+ silentAttemptUsed,
2389
+ claimSilentAttempt,
2390
+ resetSilentAttempt,
2391
+ trySilentMetanet,
2392
+ warnIfNoLogout,
1853
2393
  BWALLETX_TYPE,
1854
2394
  substrateOf,
1855
2395
  walletTypeOf,
@@ -1861,8 +2401,19 @@ export {
1861
2401
  outcomeOf,
1862
2402
  attemptFor,
1863
2403
  reportAttempt,
2404
+ WALLET_CHOICE_KEY,
2405
+ isRememberable,
2406
+ isExplicitPick,
2407
+ rememberWalletChoice,
2408
+ rememberedWalletChoice,
2409
+ forgetWalletChoice,
2410
+ needsBwalletX,
2411
+ tapGoesElsewhere,
2412
+ isLocalDesktopType,
2413
+ matchRemembered,
2414
+ planAutoConnect,
2415
+ attachRemembered,
1864
2416
  qrSvg,
1865
- isTouchDevice,
1866
2417
  pairingModal,
1867
2418
  pickWallet,
1868
2419
  chooseWallet,
@@ -1870,6 +2421,8 @@ export {
1870
2421
  throwIfAccessError,
1871
2422
  proveHoldings,
1872
2423
  requestAccess,
2424
+ runConnect,
2425
+ runSilentMetanet,
1873
2426
  SIGNIN_TAG,
1874
2427
  GET_URL,
1875
2428
  WEB_WALLET_URL,
@@ -1880,7 +2433,9 @@ export {
1880
2433
  PENDING_NOTE,
1881
2434
  PENDING_LABEL,
1882
2435
  PENDING_LABEL_OTHER,
2436
+ SWITCH_NOTE,
2437
+ CONTINUE_OTHER_LABEL,
1883
2438
  buildAuthorizeUrl,
1884
2439
  defineBwalletxSignin
1885
2440
  };
1886
- //# sourceMappingURL=chunk-LS6AYEBO.js.map
2441
+ //# sourceMappingURL=chunk-6EFFOIHK.js.map