@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/react.cjs CHANGED
@@ -484,6 +484,7 @@ function isUserRejection(e) {
484
484
  }
485
485
  function signInErrorText(e) {
486
486
  if (isUserRejection(e)) return "Sign-in cancelled";
487
+ if (e?.name === "MetanetUnreachableError") return e.message;
487
488
  const code = e?.code;
488
489
  if (code === "NOT_FOUND") return "bWalletX was not found in this browser";
489
490
  const msg = e instanceof Error ? e.message : String(e ?? "");
@@ -528,7 +529,7 @@ function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
528
529
  return new Promise((resolve) => {
529
530
  let done = false;
530
531
  let timer;
531
- const finish = (r) => {
532
+ const finish2 = (r) => {
532
533
  if (done) return;
533
534
  done = true;
534
535
  if (timer !== void 0) clearTimeout(timer);
@@ -539,19 +540,19 @@ function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
539
540
  const d = e.detail;
540
541
  const rdns = d?.info?.rdns ?? "";
541
542
  if (d?.wallet && BWALLETX_RDNS.includes(rdns)) {
542
- finish({ wallet: d.wallet, method: rdns === RDNS_EXTENSION ? "extension" : "in-app", rdns });
543
+ finish2({ wallet: d.wallet, method: rdns === RDNS_EXTENSION ? "extension" : "in-app", rdns });
543
544
  }
544
545
  };
545
546
  window.addEventListener("brc100:announceWallet", onAnnounce);
546
547
  window.dispatchEvent(new Event("brc100:requestWallet"));
547
548
  if (done) return;
548
549
  if (isFramed()) {
549
- void probeXdm(Math.max(ms, 1200)).then((w) => finish(w ? { wallet: w, method: "in-app", rdns: null } : null));
550
+ void probeXdm(Math.max(ms, 1200)).then((w) => finish2(w ? { wallet: w, method: "in-app", rdns: null } : null));
550
551
  return;
551
552
  }
552
553
  timer = setTimeout(() => {
553
554
  timer = void 0;
554
- finish(null);
555
+ finish2(null);
555
556
  }, ms);
556
557
  });
557
558
  }
@@ -727,8 +728,173 @@ async function atStage(stage, run) {
727
728
  }
728
729
  }
729
730
 
730
- // src/wallets.ts
731
+ // src/metanet.ts
731
732
  var import_sdk4 = require("@bsv/sdk");
733
+
734
+ // src/device.ts
735
+ function isTouchDevice(env = globalThis) {
736
+ const nav = env.navigator;
737
+ if (!nav) return false;
738
+ const ua = nav.userAgent ?? "";
739
+ if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
740
+ if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
741
+ try {
742
+ return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
743
+ } catch {
744
+ return false;
745
+ }
746
+ }
747
+
748
+ // src/metanet.ts
749
+ var METANET_TYPE = "metanet";
750
+ var METANET_OPTION_ID = METANET_TYPE;
751
+ var METANET_LABEL = "Use Metanet Desktop or another BRC-100 wallet";
752
+ var METANET_NOTE = "Metanet Desktop must be running on this computer";
753
+ var METANET_REMEMBERED_NOTE = "Signs in with Metanet Desktop on this computer.";
754
+ var NOT_YOU_LABEL = "Not you? Use another wallet";
755
+ var METANET_UNREACHABLE = "We couldn\u2019t reach Metanet Desktop. Open it, then try again.";
756
+ var METANET_ANSWER_MS = 4e3;
757
+ var METANET_SILENT_MS = 2e3;
758
+ var MetanetUnreachableError = class extends Error {
759
+ code = "NOT_FOUND";
760
+ constructor() {
761
+ super(METANET_UNREACHABLE);
762
+ this.name = "MetanetUnreachableError";
763
+ }
764
+ };
765
+ function inAnyFrame(win = typeof window === "undefined" ? void 0 : window) {
766
+ if (!win) return false;
767
+ try {
768
+ return win.top !== win.self;
769
+ } catch {
770
+ return true;
771
+ }
772
+ }
773
+ function metanetOffered(env = {}) {
774
+ const win = env.window ?? (typeof window === "undefined" ? void 0 : window);
775
+ if (!win) return false;
776
+ const nav = env.navigator ?? (typeof navigator === "undefined" ? void 0 : navigator);
777
+ const mm = env.matchMedia ?? (typeof matchMedia === "undefined" ? void 0 : (q) => matchMedia(q));
778
+ return !isTouchDevice({ navigator: nav, matchMedia: mm }) && !inAnyFrame(win);
779
+ }
780
+ function client(o) {
781
+ if (o.make) return o.make();
782
+ const originator = o.originator ?? (typeof location !== "undefined" && location.host ? location.host : void 0);
783
+ return new import_sdk4.WalletClient("json-api", originator);
784
+ }
785
+ async function answers(wallet) {
786
+ return Promise.resolve().then(() => wallet.getVersion({})).then((v) => typeof v?.version === "string", () => false);
787
+ }
788
+ async function connectMetanetDesktop(o = {}) {
789
+ let wallet;
790
+ try {
791
+ wallet = client(o);
792
+ } catch {
793
+ throw new MetanetUnreachableError();
794
+ }
795
+ let timer;
796
+ const ok = await Promise.race([answers(wallet), new Promise((r) => timer = setTimeout(() => r(false), o.timeoutMs ?? METANET_ANSWER_MS))]);
797
+ clearTimeout(timer);
798
+ if (!ok) throw new MetanetUnreachableError();
799
+ return wallet;
800
+ }
801
+ function metanetOption(o = {}) {
802
+ const opt = {
803
+ id: METANET_OPTION_ID,
804
+ name: METANET_LABEL,
805
+ kind: "brc100",
806
+ isBwalletX: false,
807
+ substrate: "json-api",
808
+ async connect() {
809
+ const wallet = await connectMetanetDesktop(o);
810
+ opt.wallet = wallet;
811
+ await waitIfLocked(wallet);
812
+ const { publicKey } = await wallet.getPublicKey({ identityKey: true });
813
+ return { wallet, payer: wallet, identityKey: publicKey, optionId: opt.id, kind: "brc100" };
814
+ }
815
+ };
816
+ return opt;
817
+ }
818
+ var KNOWN_KEYS_KEY = "bwx:known-keys";
819
+ var KEY_RE = /^0[23][0-9a-f]{64}$/;
820
+ var MAX_KNOWN = 8;
821
+ function knownIdentityKeys() {
822
+ try {
823
+ const v = JSON.parse(globalThis.localStorage?.getItem(KNOWN_KEYS_KEY) || "[]");
824
+ return Array.isArray(v) ? v.filter((k) => typeof k === "string" && KEY_RE.test(k)) : [];
825
+ } catch {
826
+ return [];
827
+ }
828
+ }
829
+ function isKnownIdentityKey(key) {
830
+ if (!key) return false;
831
+ const k = key.toLowerCase();
832
+ if (!KEY_RE.test(k)) return false;
833
+ return knownIdentityKeys().includes(k) || rememberedIdentity()?.toLowerCase() === k;
834
+ }
835
+ function rememberKnownIdentityKey(key) {
836
+ const k = (key ?? "").toLowerCase();
837
+ if (!KEY_RE.test(k)) return;
838
+ try {
839
+ const list = [k, ...knownIdentityKeys().filter((x) => x !== k)].slice(0, MAX_KNOWN);
840
+ globalThis.localStorage?.setItem(KNOWN_KEYS_KEY, JSON.stringify(list));
841
+ } catch {
842
+ }
843
+ }
844
+ var triedThisLoad = false;
845
+ function silentAttemptUsed() {
846
+ return triedThisLoad;
847
+ }
848
+ function claimSilentAttempt() {
849
+ if (triedThisLoad) return false;
850
+ triedThisLoad = true;
851
+ return true;
852
+ }
853
+ async function trySilentMetanet(o = {}) {
854
+ if (!metanetOffered()) return { ok: false, stop: "not-here" };
855
+ if (!claimSilentAttempt()) return { ok: false, stop: "used" };
856
+ const run = async () => {
857
+ let wallet;
858
+ try {
859
+ wallet = client(o);
860
+ } catch {
861
+ return { ok: false, stop: "unreachable" };
862
+ }
863
+ if (!await answers(wallet)) return { ok: false, stop: "unreachable" };
864
+ const unlocked = await Promise.resolve().then(() => wallet.isAuthenticated({})).then((r2) => r2?.authenticated === true, () => false);
865
+ if (!unlocked) return { ok: false, stop: "locked" };
866
+ const identityKey = await Promise.resolve().then(() => wallet.getPublicKey({ identityKey: true })).then((r2) => r2.publicKey, () => null);
867
+ if (!identityKey) return { ok: false, stop: "unreachable" };
868
+ if (!isKnownIdentityKey(identityKey)) return { ok: false, stop: "unknown-key" };
869
+ return { ok: true, wallet, identityKey };
870
+ };
871
+ let timer;
872
+ const r = await Promise.race([
873
+ run().catch(() => ({ ok: false, stop: "unreachable" })),
874
+ new Promise((res) => timer = setTimeout(() => res({ ok: false, stop: "slow" }), o.windowMs ?? METANET_SILENT_MS))
875
+ ]);
876
+ clearTimeout(timer);
877
+ return r;
878
+ }
879
+ var warnedLogout = false;
880
+ var isProduction = () => {
881
+ try {
882
+ return globalThis.process?.env?.NODE_ENV === "production";
883
+ } catch {
884
+ return false;
885
+ }
886
+ };
887
+ function warnIfNoLogout(o) {
888
+ if (!o.verifyUrl || o.logoutUrl || warnedLogout || isProduction() || typeof console === "undefined") return false;
889
+ warnedLogout = true;
890
+ console.warn(
891
+ `@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.`
892
+ );
893
+ return true;
894
+ }
895
+
896
+ // src/wallets.ts
897
+ var import_sdk5 = require("@bsv/sdk");
732
898
  async function brc100Connected(wallet, o, c = {}) {
733
899
  if (c.identity === false) return { wallet, payer: wallet, identityKey: null, optionId: o.id, kind: o.kind };
734
900
  await waitIfLocked(wallet);
@@ -791,7 +957,7 @@ function handcashAdapter(o) {
791
957
  var NAMES = { [RDNS_EXTENSION]: "bWalletX", "space.bwallet.mobile": "bWalletX" };
792
958
  async function probe(substrate, ms) {
793
959
  try {
794
- const w = new import_sdk4.WalletClient(substrate);
960
+ const w = new import_sdk5.WalletClient(substrate);
795
961
  const ok = await Promise.race([w.isAuthenticated({}).then(() => true), new Promise((r) => setTimeout(() => r(false), ms))]);
796
962
  return ok ? w : null;
797
963
  } catch {
@@ -839,34 +1005,21 @@ async function discoverWallets(o = {}) {
839
1005
  return out.map((w, i) => ({ w, i })).sort((a, b) => rank(a.w) - rank(b.w) || a.i - b.i).map((x) => x.w);
840
1006
  }
841
1007
  var DESKTOP_OPTION_ID = "desktop";
842
- function desktopOption(ms = 1500) {
843
- const opt = {
844
- id: DESKTOP_OPTION_ID,
845
- name: "Desktop wallet",
846
- kind: "brc100",
847
- isBwalletX: false,
848
- substrate: "json-api",
849
- async connect(c) {
850
- const [json, cicada] = await Promise.all([probe("json-api", ms), probe("Cicada", ms)]);
851
- const w = json ?? cicada;
852
- if (!w) throw Object.assign(new Error("No desktop wallet answered on this computer"), { code: "NOT_FOUND" });
853
- opt.wallet = w;
854
- opt.substrate = json ? "json-api" : "cicada";
855
- return brc100Connected(w, opt, c);
856
- }
857
- };
858
- return opt;
859
- }
860
1008
  function autoChoice(list, force = false) {
861
- const detected = list.filter((w) => w.kind === "brc100");
862
- const others = list.filter((w) => w.kind === "handcash" || w.kind === "custom");
863
- if (force) return detected[0] ?? list.find((w) => w.kind !== "pairing") ?? list[0] ?? null;
1009
+ const auto = list.filter((w) => !isLocalDesktop(w));
1010
+ const detected = auto.filter((w) => w.kind === "brc100");
1011
+ const others = auto.filter((w) => w.kind === "handcash" || w.kind === "custom");
1012
+ const hasLocal = auto.length !== list.length;
1013
+ if (force) return detected[0] ?? auto.find((w) => w.kind !== "pairing") ?? auto[0] ?? null;
864
1014
  if (!list.length) return null;
865
- if (list.length === 1) return list[0];
866
- if (detected.length === 1 && others.length === 0) return detected[0];
867
- if (detected.length === 0 && others.length === 0) return list[0];
1015
+ if (list.length === 1) return hasLocal ? void 0 : list[0];
1016
+ if (detected.length === 1 && others.length === 0 && !hasLocal) return detected[0];
1017
+ if (detected.length === 0 && others.length === 0) return auto[0] ?? void 0;
868
1018
  return void 0;
869
1019
  }
1020
+ function isLocalDesktop(w) {
1021
+ return w.substrate === "json-api" || w.substrate === "cicada" || w.id === "localhost:3321" || w.id === "localhost:3301" || w.id === DESKTOP_OPTION_ID;
1022
+ }
870
1023
 
871
1024
  // src/choice.ts
872
1025
  var BWALLETX_TYPE = "bwalletx";
@@ -909,6 +1062,10 @@ function decideWallet(list, o = {}) {
909
1062
  const a = autoChoice([...list], o.picker === false);
910
1063
  return a === void 0 ? { option: null, reason: "chooser" } : a === null ? { option: null, reason: "none" } : { option: a, reason: "legacy" };
911
1064
  }
1065
+ if (o.remembered && !o.preferBwalletX) {
1066
+ const m = matchRemembered(list, o.remembered);
1067
+ if (m) return { option: m, reason: "remembered" };
1068
+ }
912
1069
  const b = list.find(isPresentBwalletX);
913
1070
  if (b) return { option: b, reason: "bwalletx" };
914
1071
  if (o.picker === false) {
@@ -939,6 +1096,74 @@ function reportAttempt(hook, a) {
939
1096
  } catch {
940
1097
  }
941
1098
  }
1099
+ var WALLET_CHOICE_KEY = "bwx:wallet";
1100
+ var SUBSTRATES = ["extension", "in-app", "xdm", "injected", "window.CWI", "json-api", "cicada", "pairing", "adapter"];
1101
+ var TYPE_RE = /^[A-Za-z0-9._:-]{1,64}$/;
1102
+ 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;
1103
+ function isRememberable(o) {
1104
+ const t = walletTypeOf(o);
1105
+ return substrateOf(o) !== "window.CWI" && TYPE_RE.test(t) && !KEY_LIKE.test(t);
1106
+ }
1107
+ function isExplicitPick(o, reason) {
1108
+ return o.isBwalletX || reason === "chooser" || reason === "requested" || reason === "remembered";
1109
+ }
1110
+ function rememberWalletChoice(o) {
1111
+ try {
1112
+ if (!o) return void globalThis.localStorage?.removeItem(WALLET_CHOICE_KEY);
1113
+ if (!isRememberable(o)) return;
1114
+ const v = { type: walletTypeOf(o), substrate: substrateOf(o), v: 1 };
1115
+ globalThis.localStorage?.setItem(WALLET_CHOICE_KEY, JSON.stringify(v));
1116
+ } catch {
1117
+ }
1118
+ }
1119
+ function rememberedWalletChoice() {
1120
+ try {
1121
+ const raw = globalThis.localStorage?.getItem(WALLET_CHOICE_KEY);
1122
+ if (!raw) return null;
1123
+ const j = JSON.parse(raw);
1124
+ if (typeof j.type !== "string" || !TYPE_RE.test(j.type) || KEY_LIKE.test(j.type)) return null;
1125
+ if (!SUBSTRATES.includes(j.substrate) || j.substrate === "window.CWI") return null;
1126
+ return { type: j.type, substrate: j.substrate };
1127
+ } catch {
1128
+ return null;
1129
+ }
1130
+ }
1131
+ function forgetWalletChoice() {
1132
+ rememberWalletChoice(null);
1133
+ }
1134
+ function needsBwalletX(type) {
1135
+ return !!type && type !== BWALLETX_TYPE;
1136
+ }
1137
+ function tapGoesElsewhere(type) {
1138
+ return needsBwalletX(type) && type !== METANET_TYPE && type !== DESKTOP_OPTION_ID;
1139
+ }
1140
+ function isLocalDesktopType(r) {
1141
+ return r.type === METANET_TYPE || r.type === DESKTOP_OPTION_ID || r.substrate === "json-api" || r.substrate === "cicada";
1142
+ }
1143
+ function matchRemembered(list, r) {
1144
+ if (isLocalDesktopType(r)) return null;
1145
+ if (r.type === BWALLETX_TYPE) {
1146
+ return (r.substrate === "pairing" ? list.find((o) => o.kind === "pairing") : list.find(isPresentBwalletX)) ?? null;
1147
+ }
1148
+ return list.find((o) => !o.isBwalletX && !isLocalDesktop(o) && isRememberable(o) && walletTypeOf(o) === r.type) ?? null;
1149
+ }
1150
+ var ATTACHABLE = ["extension", "in-app", "xdm", "injected"];
1151
+ function planAutoConnect(o) {
1152
+ const r = o.remembered;
1153
+ if (!r || o.enabled === false) return { action: "none", remembered: r, needsBwalletX: false };
1154
+ if (r.type === METANET_TYPE) {
1155
+ return { action: o.metanetHere ?? metanetOffered() ? "silent-metanet" : "none", remembered: r, needsBwalletX: false };
1156
+ }
1157
+ if (isLocalDesktopType(r)) return { action: "none", remembered: r, needsBwalletX: false };
1158
+ const action = o.signedIn && ATTACHABLE.includes(r.substrate) ? "attach" : "wait-for-tap";
1159
+ return { action, remembered: r, needsBwalletX: needsBwalletX(r.type) };
1160
+ }
1161
+ async function attachRemembered(r, o = {}) {
1162
+ if (!ATTACHABLE.includes(r.substrate)) return null;
1163
+ const list = await discoverWallets({ ms: o.ms });
1164
+ const m = matchRemembered(list, r);
1165
+ return m && m.kind === "brc100" && m.wallet && ATTACHABLE.includes(substrateOf(m)) ? m : null;
1166
+ }
942
1167
 
943
1168
  // src/connect-flow.ts
944
1169
  async function runConnect(o) {
@@ -947,7 +1172,20 @@ async function runConnect(o) {
947
1172
  let choice = null;
948
1173
  let reason = "none";
949
1174
  try {
950
- const d = decideWallet(list, { walletId: o.walletId, chooser: o.chooser, picker: o.picker, fastPath: o.fastPath });
1175
+ if (o.metanet) {
1176
+ if (!metanetOffered()) throw Object.assign(new Error("Metanet Desktop runs on a computer, outside any frame."), { code: "NOT_SUPPORTED" });
1177
+ choice = metanetOption(typeof o.metanet === "object" ? o.metanet : {});
1178
+ reason = "requested";
1179
+ return await finish(o, choice, reason, started);
1180
+ }
1181
+ const d = decideWallet(list, {
1182
+ walletId: o.walletId,
1183
+ chooser: o.chooser,
1184
+ picker: o.picker,
1185
+ fastPath: o.fastPath,
1186
+ remembered: o.remembered,
1187
+ preferBwalletX: o.preferBwalletX
1188
+ });
951
1189
  choice = d.option;
952
1190
  reason = d.reason;
953
1191
  if (!choice && d.reason === "chooser") choice = await o.pick(list);
@@ -956,43 +1194,90 @@ async function runConnect(o) {
956
1194
  reportAttempt(o.onAttempt, attemptFor(null, "cancelled", started));
957
1195
  return null;
958
1196
  }
959
- const signIn = !!(o.challengeUrl && o.verifyUrl) && choice.kind !== "handcash";
960
- const cw = await choice.connect(signIn ? { identity: false } : void 0);
961
- let handle = cw.handle ?? null;
962
- let paymail = cw.paymail ?? null;
963
- let identityKey = cw.identityKey;
964
- let signedIn = false;
965
- if (cw.wallet && signIn) {
966
- const { result, verified } = await signInAndVerify({
967
- wallet: cw.wallet,
968
- identityKey: cw.identityKey ?? void 0,
969
- // The key that signed in here last time: a repeat sign-in is then the signature alone.
970
- identityKeyHint: rememberedIdentity(),
971
- challengeNeedsIdentityKey: o.challengeNeedsIdentityKey,
972
- challenge: (key) => o.post(o.challengeUrl, key ? { identityKey: key } : {}),
973
- verify: (r) => o.post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
974
- });
975
- rememberIdentity(result.identityKey);
976
- identityKey = result.identityKey;
977
- handle = verified.handle ?? handle;
978
- paymail = verified.paymail ?? paymail;
979
- signedIn = true;
980
- } else if (cw.wallet && !identityKey) {
981
- identityKey = (await cw.wallet.getPublicKey({ identityKey: true })).publicKey;
982
- }
983
- reportAttempt(o.onAttempt, attemptFor(choice, signedIn ? "signed-in" : "connected", started));
984
- return { connected: cw, identityKey, handle, paymail, signedIn, walletType: walletTypeOf(choice), substrate: substrateOf(choice), reason };
1197
+ return await finish(o, choice, reason, started);
985
1198
  } catch (e) {
986
1199
  reportAttempt(o.onAttempt, attemptFor(choice, outcomeOf(e), started));
987
1200
  throw e;
988
1201
  }
989
1202
  }
1203
+ async function finish(o, choice, reason, started) {
1204
+ const signIn = !!(o.challengeUrl && o.verifyUrl) && choice.kind !== "handcash";
1205
+ const cw = await choice.connect(signIn ? { identity: false } : void 0);
1206
+ let handle = cw.handle ?? null;
1207
+ let paymail = cw.paymail ?? null;
1208
+ let identityKey = cw.identityKey;
1209
+ let signedIn = false;
1210
+ if (cw.wallet && signIn) {
1211
+ const { result, verified } = await signInAndVerify({
1212
+ wallet: cw.wallet,
1213
+ identityKey: cw.identityKey ?? void 0,
1214
+ // Metanet's connect() just checked the lock: don't ask again (rule 7, the minimum).
1215
+ skipWaitForAuthentication: choice.id === METANET_OPTION_ID,
1216
+ // The key that signed in here last time: a repeat sign-in is then the signature alone.
1217
+ identityKeyHint: rememberedIdentity(),
1218
+ challengeNeedsIdentityKey: o.challengeNeedsIdentityKey,
1219
+ challenge: (key) => o.post(o.challengeUrl, key ? { identityKey: key } : {}),
1220
+ verify: (r) => o.post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
1221
+ });
1222
+ rememberIdentity(result.identityKey);
1223
+ identityKey = result.identityKey;
1224
+ handle = verified.handle ?? handle;
1225
+ paymail = verified.paymail ?? paymail;
1226
+ signedIn = true;
1227
+ } else if (cw.wallet && !identityKey) {
1228
+ identityKey = (await cw.wallet.getPublicKey({ identityKey: true })).publicKey;
1229
+ }
1230
+ if (o.remember && isExplicitPick(choice, reason)) rememberWalletChoice(choice);
1231
+ if (o.remember && signedIn) rememberKnownIdentityKey(identityKey);
1232
+ reportAttempt(o.onAttempt, attemptFor(choice, signedIn ? "signed-in" : "connected", started));
1233
+ return { connected: cw, identityKey, handle, paymail, signedIn, walletType: walletTypeOf(choice), substrate: substrateOf(choice), reason };
1234
+ }
1235
+ async function runSilentMetanet(o = {}) {
1236
+ const s = await trySilentMetanet(o);
1237
+ if (!s.ok || o.isAbandoned?.()) return null;
1238
+ const started = Date.now();
1239
+ const opt = metanetOption(o);
1240
+ opt.wallet = s.wallet;
1241
+ const cw = { wallet: s.wallet, payer: s.wallet, identityKey: s.identityKey, optionId: METANET_OPTION_ID, kind: "brc100" };
1242
+ const done = (signedIn, handle = null, paymail = null) => {
1243
+ rememberWalletChoice(opt);
1244
+ if (signedIn) rememberKnownIdentityKey(s.identityKey);
1245
+ reportAttempt(o.onAttempt, attemptFor(opt, signedIn ? "signed-in" : "connected", started));
1246
+ return { connected: cw, identityKey: s.identityKey, handle, paymail, signedIn, walletType: walletTypeOf(opt), substrate: "json-api", reason: "remembered" };
1247
+ };
1248
+ if (o.sessionKey !== void 0 && o.sessionKey !== null) {
1249
+ return o.sessionKey.toLowerCase() === s.identityKey.toLowerCase() ? done(true) : null;
1250
+ }
1251
+ if (!o.challengeUrl || !o.verifyUrl || !o.post) return done(false);
1252
+ if (!isKnownIdentityKey(s.identityKey)) return null;
1253
+ try {
1254
+ o.onSignIn?.();
1255
+ const post = o.post;
1256
+ const { result, verified } = await signInAndVerify({
1257
+ wallet: s.wallet,
1258
+ identityKey: s.identityKey,
1259
+ skipWaitForAuthentication: true,
1260
+ // trySilentMetanet just saw it unlocked
1261
+ challenge: (key) => post(o.challengeUrl, key ? { identityKey: key } : {}),
1262
+ verify: (r) => post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
1263
+ });
1264
+ if (o.isAbandoned?.()) {
1265
+ await Promise.resolve(o.logout?.()).catch(() => void 0);
1266
+ return null;
1267
+ }
1268
+ rememberIdentity(result.identityKey);
1269
+ return done(true, verified.handle ?? null, verified.paymail ?? null);
1270
+ } catch (e) {
1271
+ reportAttempt(o.onAttempt, attemptFor(opt, outcomeOf(e), started));
1272
+ return null;
1273
+ }
1274
+ }
990
1275
 
991
1276
  // src/modal.ts
992
1277
  var import_qrcode_generator = __toESM(require("qrcode-generator"), 1);
993
1278
 
994
1279
  // src/access-client.ts
995
- var import_sdk5 = require("@bsv/sdk");
1280
+ var import_sdk6 = require("@bsv/sdk");
996
1281
 
997
1282
  // src/access.ts
998
1283
  var ACCESS_PROTOCOL = [2, "bwalletx access"];
@@ -1130,7 +1415,7 @@ async function proveHoldings(wallet, challenge, tokenId, basket = "bsv21") {
1130
1415
  const counterparty = ci.counterparty ?? "self";
1131
1416
  for (const protocolID of ci.protocolID ? [ci.protocolID] : [ONESAT, LEGACY_ONESAT]) {
1132
1417
  const { publicKey } = await wallet.getPublicKey({ protocolID, keyID: ci.keyID, counterparty });
1133
- const h = import_sdk5.Utils.toHex(import_sdk5.Hash.hash160(import_sdk5.PublicKey.fromString(publicKey).encode(true)));
1418
+ const h = import_sdk6.Utils.toHex(import_sdk6.Hash.hash160(import_sdk6.PublicKey.fromString(publicKey).encode(true)));
1134
1419
  if (want && h !== want) continue;
1135
1420
  const existing = byKey.get(publicKey);
1136
1421
  if (existing) {
@@ -1241,6 +1526,11 @@ button[aria-busy=true]{cursor:wait}
1241
1526
  /* "Other wallets" (0.5.6): one small link under the button */
1242
1527
  .ow{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
1243
1528
  :host([theme=dark]) .ow,:host-context(.dark) .ow{color:#a1a1aa}
1529
+ .sw{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#a16207;text-align:center}
1530
+ :host([theme=dark]) .sw,:host-context(.dark) .sw{color:#fcd34d}
1531
+ .mn{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
1532
+ .mn small{display:block;font-size:11px;opacity:.85}
1533
+ :host([theme=dark]) .mn,:host-context(.dark) .mn{color:#a1a1aa}
1244
1534
  .lnk{all:unset;cursor:pointer;color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px}
1245
1535
  .lnk:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
1246
1536
  /* error: one line plus Retry */
@@ -1279,6 +1569,11 @@ var MENU_CSS = `
1279
1569
  var PENDING_NOTE = "Sign in: shares only your public identity key, moves no money";
1280
1570
  var PENDING_LABEL = "Approve in bWalletX\u2026";
1281
1571
  var PENDING_LABEL_OTHER = "Approve in your wallet\u2026";
1572
+ var SWITCH_NOTE = "Some features need bWalletX";
1573
+ var CONTINUE_OTHER_LABEL = "Continue with your wallet";
1574
+ function walletGlyph(px) {
1575
+ 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>`;
1576
+ }
1282
1577
  function b64url2(bytes) {
1283
1578
  let s = "";
1284
1579
  for (const b of bytes) s += String.fromCharCode(b);
@@ -1318,7 +1613,7 @@ async function postAccess(url, body) {
1318
1613
  const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
1319
1614
  return res.json().catch(() => ({ error: "bad_request", message: `HTTP ${res.status}` }));
1320
1615
  }
1321
- 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"];
1616
+ 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"];
1322
1617
  var STATES = ["signed-out", "restoring", "pending", "signed-in", "error"];
1323
1618
  function createClass() {
1324
1619
  return class BwalletxSigninElement extends HTMLElement {
@@ -1333,14 +1628,21 @@ function createClass() {
1333
1628
  #error = "";
1334
1629
  #restored = false;
1335
1630
  #pendingLabel = PENDING_LABEL;
1631
+ /** The wallet type signed in with here (0.6.0), for the Switch note. */
1632
+ #signedType = null;
1336
1633
  #menu = null;
1634
+ /** (0.7.0) A Metanet sign-in (tapped or silent) is in flight. */
1635
+ #viaMetanet = false;
1636
+ /** (0.7.0) Bumped by "Not you?": a Metanet sign-in started before it lands nobody. */
1637
+ #run = 0;
1337
1638
  constructor() {
1338
1639
  super();
1339
1640
  this.#root = this.attachShadow({ mode: "open" });
1340
1641
  }
1341
1642
  connectedCallback() {
1643
+ if (!this.#controlled && this.#stateful) warnIfNoLogout({ verifyUrl: this.#attr("verify-url"), logoutUrl: this.#attr("logout-url"), where: "<bwalletx-signin>" });
1342
1644
  this.#render();
1343
- void this.#restore();
1645
+ void this.#restore().then(() => this.#silentMetanet());
1344
1646
  }
1345
1647
  disconnectedCallback() {
1346
1648
  this.#menu?.close(false);
@@ -1446,10 +1748,11 @@ function createClass() {
1446
1748
  const restoring = state === "restoring";
1447
1749
  const error = state === "error";
1448
1750
  const pendingLabel = this.#controlled ? PENDING_LABEL : this.#pendingLabel;
1449
- const text = pending ? pendingLabel : error ? "Retry" : signinText(this.#attr("label") ?? "signin", brand);
1751
+ const elsewhere = state === "signed-out" && tapGoesElsewhere(this.#walletType());
1752
+ const text = pending ? pendingLabel : error ? "Retry" : elsewhere ? CONTINUE_OTHER_LABEL : signinText(this.#attr("label") ?? "signin", brand);
1450
1753
  const subAttr = this.#attr("subtitle");
1451
1754
  const subDefault = brand === "bWalletX" && (size === "lg" || size === "md") ? "also works with bWallet" : null;
1452
- const subtitle = pending || error ? null : subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
1755
+ const subtitle = pending || error || elsewhere ? null : subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
1453
1756
  const loading = this.hasAttribute("loading") || this.#busy || pending || restoring;
1454
1757
  const disabled = this.hasAttribute("disabled");
1455
1758
  const markPx = size === "icon" ? 24 : size === "sm" ? 15 : size === "md" ? 17 : 20;
@@ -1458,18 +1761,26 @@ function createClass() {
1458
1761
  const webUrl = this.#attr("web-url") ?? WEB_WALLET_URL;
1459
1762
  const errText = error ? (this.#controlled ? this.#attr("error") : this.#error) || "Sign-in failed" : "";
1460
1763
  const label = size === "icon" ? "" : `<span class="txt" part="text"><b part="label">${esc(text)}</b>${subtitle ? `<small part="subtitle">${esc(subtitle)}</small>` : ""}</span>`;
1461
- const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : markSvg(markPx);
1764
+ const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : elsewhere ? walletGlyph(markPx - 4) : markSvg(markPx);
1462
1765
  const otherOff = this.#attr("other-wallets") === "off";
1463
1766
  const otherLink = '<button type="button" class="lnk" data-act="other" part="other-wallets-link">Other wallets</button>';
1464
1767
  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>` : "";
1465
1768
  const ow = !this.#notFound && !pending && !restoring && this.#otherWalletsShown(size) ? `<p class="ow" part="other-wallets">${otherLink}</p>` : "";
1769
+ 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>` : "";
1466
1770
  const err = error ? `<p class="err" part="error" role="alert">${esc(errText)}</p>` : "";
1771
+ const mnHere = this.#metanetShown(size);
1772
+ const mnRemembered = mnHere && (pending ? this.#metanetPending() : !restoring && this.#walletType() === METANET_TYPE);
1773
+ const notYou = `<button type="button" class="lnk" data-act="not-you" part="not-you-link">${esc(NOT_YOU_LABEL)}</button>`;
1774
+ 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>`;
1467
1775
  const compact = size === "sm" || size === "icon";
1468
1776
  const note = pending && !compact ? `<p class="note" part="note">${esc(PENDING_NOTE)}</p>` : "";
1469
1777
  const status = pending ? `<span class="sr" role="status">${esc(pendingLabel)}</span>` : loading ? '<span class="sr" role="status">Connecting to bWalletX\u2026</span>' : "";
1470
- 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}`;
1778
+ 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}`;
1471
1779
  this.button.addEventListener("click", () => void this.#click());
1472
1780
  for (const l of Array.from(this.#root.querySelectorAll("[data-act=other]"))) l.addEventListener("click", () => void this.otherWallets());
1781
+ this.#root.querySelector("[data-act=switch]")?.addEventListener("click", () => void this.switchToBwalletX());
1782
+ this.#root.querySelector("[data-act=metanet]")?.addEventListener("click", () => void this.useMetanet());
1783
+ this.#root.querySelector("[data-act=not-you]")?.addEventListener("click", () => this.notYou());
1473
1784
  bindPrewarm(this.button);
1474
1785
  if (pending && compact) this.#showTip(PENDING_NOTE);
1475
1786
  else this.#hideTip();
@@ -1537,7 +1848,20 @@ function createClass() {
1537
1848
  close(false);
1538
1849
  void this.signOut();
1539
1850
  });
1540
- menu.append(who, copy, out);
1851
+ const items = [copy, out];
1852
+ menu.append(who);
1853
+ if (needsBwalletX(this.#walletType())) {
1854
+ const sw = item("Switch to bWalletX", () => {
1855
+ close(false);
1856
+ void this.switchToBwalletX();
1857
+ });
1858
+ const line = document.createElement("div");
1859
+ line.className = "who";
1860
+ line.textContent = SWITCH_NOTE;
1861
+ menu.append(line, sw);
1862
+ items.unshift(sw);
1863
+ }
1864
+ menu.append(copy, out);
1541
1865
  root.append(style, menu);
1542
1866
  document.body.appendChild(host);
1543
1867
  const place = () => {
@@ -1553,7 +1877,6 @@ function createClass() {
1553
1877
  menu.style.top = `${below + h > vh && r.top - 8 - h > 0 ? r.top - 8 - h : below}px`;
1554
1878
  };
1555
1879
  place();
1556
- const items = [copy, out];
1557
1880
  const focusAt = (i) => items[(i + items.length) % items.length].focus();
1558
1881
  const onKey = (e) => {
1559
1882
  const i = items.indexOf(root.activeElement);
@@ -1608,6 +1931,8 @@ function createClass() {
1608
1931
  if (this.#controlled) return;
1609
1932
  const url = this.#attr("logout-url");
1610
1933
  if (url) await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
1934
+ if (this.#remember) forgetWalletChoice();
1935
+ this.#signedType = null;
1611
1936
  this.#account = null;
1612
1937
  this.#set("signed-out");
1613
1938
  this.#emit("bwalletx-signed-out");
@@ -1627,6 +1952,110 @@ function createClass() {
1627
1952
  otherWallets() {
1628
1953
  return this.#click("chooser");
1629
1954
  }
1955
+ /** (0.6.0) "Switch": sign in with bWalletX (its sheet when here, else the chooser with Get bWalletX first). */
1956
+ switchToBwalletX() {
1957
+ return this.#click("bwalletx");
1958
+ }
1959
+ get #remember() {
1960
+ return this.#attr("remember") !== "off";
1961
+ }
1962
+ /**
1963
+ * (0.7.0) "Use Metanet Desktop or another BRC-100 wallet" is offered: metanet on, or auto at lg/md when this
1964
+ * element signs in or follows a provider. Never on a phone or inside a frame.
1965
+ */
1966
+ #metanetShown(size) {
1967
+ const a = this.#attr("metanet");
1968
+ if (a === "off" || !metanetOffered()) return false;
1969
+ if (a === "on") return true;
1970
+ return (size === "lg" || size === "md") && (this.#controlled || !!this.#attr("challenge-url"));
1971
+ }
1972
+ /** A Metanet sign-in is on screen: our own, or (driven) the provider's with Metanet remembered. */
1973
+ #metanetPending() {
1974
+ return this.#controlled ? this.#attr("wallet-type") === METANET_TYPE : this.#viaMetanet;
1975
+ }
1976
+ /** (0.7.0) The Metanet link: connect to Metanet Desktop on localhost:3321 (after this tap, never before). */
1977
+ useMetanet() {
1978
+ return this.#click("metanet");
1979
+ }
1980
+ /**
1981
+ * (0.7.0) "Not you? Use another wallet": forget the remembered wallet here, and drop a Metanet sign-in in flight
1982
+ * (if its verify already set a session, logout-url ends it). Emits bwalletx-not-you (cancelable).
1983
+ */
1984
+ notYou() {
1985
+ if (!this.#emit("bwalletx-not-you", { element: this }, true)) return;
1986
+ this.#run++;
1987
+ if (this.#remember) forgetWalletChoice();
1988
+ this.#signedType = null;
1989
+ if (this.#viaMetanet && !this.#controlled) {
1990
+ this.#viaMetanet = false;
1991
+ this.#busy = false;
1992
+ if (this.#phase === "pending") this.#phase = "signed-out";
1993
+ }
1994
+ this.#render();
1995
+ }
1996
+ /**
1997
+ * (0.7.0) Rule 4: back with Metanet remembered and no session, ONE silent attempt per page load (2 s, a known
1998
+ * identity key only), then the signature alone. Any stop or failure is silent: the normal button stays.
1999
+ */
2000
+ async #silentMetanet() {
2001
+ if (this.#controlled || !this.#stateful || !this.#remember || this.#attr("require-token")) return;
2002
+ if (!this.#metanetShown(this.#attr("size") ?? "lg")) return;
2003
+ const challengeUrl = this.#attr("challenge-url");
2004
+ const verifyUrl = this.#attr("verify-url");
2005
+ if (!challengeUrl || !verifyUrl || this.#phase !== "signed-out" || this.#busy || silentAttemptUsed()) return;
2006
+ if (rememberedWalletChoice()?.type !== METANET_TYPE || !metanetOffered()) return;
2007
+ const run = ++this.#run;
2008
+ const live = () => run === this.#run;
2009
+ const started = Date.now();
2010
+ const r = await runSilentMetanet({
2011
+ challengeUrl,
2012
+ verifyUrl,
2013
+ post: (url, body) => postJson(url, body),
2014
+ isAbandoned: () => !live(),
2015
+ logout: async () => {
2016
+ const out = this.#attr("logout-url");
2017
+ if (out) await fetch(out, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
2018
+ },
2019
+ onSignIn: () => {
2020
+ if (!live() || this.#phase !== "signed-out" || this.#busy) return;
2021
+ this.#viaMetanet = true;
2022
+ this.#busy = true;
2023
+ this.#pendingLabel = PENDING_LABEL_OTHER;
2024
+ this.#set("pending");
2025
+ },
2026
+ onAttempt: (a) => this.#emit("bwalletx-attempt", a)
2027
+ });
2028
+ if (!live()) return;
2029
+ this.#viaMetanet = false;
2030
+ this.#busy = false;
2031
+ if (!r) {
2032
+ if (this.#phase === "pending") this.#set("signed-out");
2033
+ return;
2034
+ }
2035
+ this.#signedType = r.walletType;
2036
+ this.#signedIn({ identityKey: r.identityKey, handle: r.handle, paymail: r.paymail });
2037
+ 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 });
2038
+ }
2039
+ /** The wallet type the Switch note judges: the provider's, else what signed in here, else the remembered one. */
2040
+ #walletType() {
2041
+ if (this.#controlled) return this.#attr("wallet-type");
2042
+ return this.#signedType ?? (this.#remember ? rememberedWalletChoice()?.type ?? null : null);
2043
+ }
2044
+ #switchNoteShown(size) {
2045
+ const a = this.#attr("switch-note");
2046
+ if (a === "off" || !tapGoesElsewhere(this.#walletType())) return false;
2047
+ return a === "on" || size === "lg" || size === "md";
2048
+ }
2049
+ /**
2050
+ * Signed in: note the wallet type, and remember it for this origin when the user picked it (0.6.0), plus the
2051
+ * identity key as known here (0.7.0, public keys only: Metanet's silent attempt goes on only for these). Pass a key
2052
+ * only after the site's verify route accepted it.
2053
+ */
2054
+ #picked(option, reason, identityKey) {
2055
+ this.#signedType = walletTypeOf(option);
2056
+ if (this.#remember && isExplicitPick(option, reason)) rememberWalletChoice(option);
2057
+ if (this.#remember) rememberKnownIdentityKey(identityKey);
2058
+ }
1630
2059
  #attempt(option, outcome, started) {
1631
2060
  this.#emit("bwalletx-attempt", attemptFor(option, outcome, started));
1632
2061
  }
@@ -1635,16 +2064,24 @@ function createClass() {
1635
2064
  * when another wallet is here or the user asked; otherwise 'not-found'. null: the chooser was closed.
1636
2065
  */
1637
2066
  async #choose(mode, ms, pairing) {
2067
+ if (mode === "metanet") return { option: metanetOption(), reason: "requested" };
2068
+ const r = mode === "signin" && this.#remember ? rememberedWalletChoice() : null;
2069
+ if (r && r.type !== METANET_TYPE && !(r.type === BWALLETX_TYPE && r.substrate !== "pairing")) {
2070
+ const m = matchRemembered(await discoverWallets({ ms, pairing: () => pairing }), r);
2071
+ if (m) return { option: m, reason: "remembered" };
2072
+ }
1638
2073
  if (mode !== "chooser") {
1639
2074
  const f = await discoverBwalletX(ms);
1640
2075
  if (f) {
1641
2076
  const substrate = f.rdns === RDNS_EXTENSION ? "extension" : f.rdns === RDNS_MOBILE ? "in-app" : "xdm";
1642
- return brc100Option(f.wallet, { id: f.rdns ?? "bwalletx-xdm", rdns: f.rdns ?? void 0, name: "bWalletX", isBwalletX: true, substrate });
2077
+ const option = brc100Option(f.wallet, { id: f.rdns ?? "bwalletx-xdm", rdns: f.rdns ?? void 0, name: "bWalletX", isBwalletX: true, substrate });
2078
+ return { option, reason: "bwalletx" };
1643
2079
  }
1644
2080
  }
1645
2081
  const list = await discoverWallets({ ms: 50, pairing: () => pairing });
1646
2082
  if (mode !== "chooser" && !hasBwalletX(list) && !otherDetected(list).length) return "not-found";
1647
- return chooseWallet(list, { getUrl: this.#attr("get-url") ?? void 0 });
2083
+ const picked = await chooseWallet(list, { getUrl: this.#attr("get-url") ?? void 0 });
2084
+ return picked ? { option: picked, reason: "chooser" } : null;
1648
2085
  }
1649
2086
  async #click(mode = "signin") {
1650
2087
  if (this.hasAttribute("disabled") || this.hasAttribute("loading") || this.#busy || this.state === "pending") return;
@@ -1668,9 +2105,12 @@ function createClass() {
1668
2105
  const started = Date.now();
1669
2106
  const pairing = pairingModal();
1670
2107
  let option = null;
2108
+ const run = ++this.#run;
2109
+ const live = () => run === this.#run;
1671
2110
  this.#notFound = false;
1672
2111
  this.#busy = true;
1673
- this.#pendingLabel = PENDING_LABEL;
2112
+ this.#viaMetanet = mode === "metanet";
2113
+ this.#pendingLabel = mode === "metanet" ? PENDING_LABEL_OTHER : PENDING_LABEL;
1674
2114
  if (own) this.#set("pending");
1675
2115
  else this.#render();
1676
2116
  try {
@@ -1689,12 +2129,14 @@ function createClass() {
1689
2129
  this.#attempt(null, "cancelled", started);
1690
2130
  return;
1691
2131
  }
1692
- option = picked;
2132
+ option = picked.option;
2133
+ const reason = picked.reason;
1693
2134
  if (!option.isBwalletX) {
1694
2135
  this.#pendingLabel = PENDING_LABEL_OTHER;
1695
2136
  if (own) this.#render();
1696
2137
  }
1697
2138
  const cw = await option.connect({ identity: false });
2139
+ if (!live()) return;
1698
2140
  const wallet = cw.wallet;
1699
2141
  if (!wallet) throw Object.assign(new Error("This wallet cannot sign in here"), { code: "NOT_SUPPORTED" });
1700
2142
  const sub = substrateOf(option);
@@ -1712,6 +2154,7 @@ function createClass() {
1712
2154
  challenge: (identityKey) => postJson(challengeUrl, { identityKey, require: hint }),
1713
2155
  verify: verifyUrl2 ? (response2) => postAccess(verifyUrl2, response2) : void 0
1714
2156
  });
2157
+ this.#picked(option, reason, verifyUrl2 ? a.response.identityKey : null);
1715
2158
  this.#attempt(option, "signed-in", started);
1716
2159
  this.#emit("bwalletx-access-granted", { identityKey: a.response.identityKey, response: a.verified });
1717
2160
  } catch (e) {
@@ -1724,18 +2167,29 @@ function createClass() {
1724
2167
  const verifyUrl = this.#attr("verify-url");
1725
2168
  const { result: r, verified } = await signInAndVerify({
1726
2169
  wallet,
2170
+ // Metanet hands over its key on connect (one request at a time), so its sign-in is the signature alone.
2171
+ identityKey: cw.identityKey ?? void 0,
2172
+ skipWaitForAuthentication: option.id === METANET_OPTION_ID,
2173
+ // its connect() just checked the lock
1727
2174
  identityKeyHint: verifyUrl ? rememberedIdentity() : void 0,
1728
2175
  challenge: (identityKey) => postJson(challengeUrl, identityKey ? { identityKey } : {}),
1729
2176
  verify: async (res) => verifyUrl ? await postJson(verifyUrl, { identityKey: res.identityKey, nonce: res.nonce, signature: res.signature }) : {}
1730
2177
  });
2178
+ if (!live()) {
2179
+ const out = this.#attr("logout-url");
2180
+ if (verifyUrl && out) await fetch(out, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
2181
+ return;
2182
+ }
1731
2183
  if (verifyUrl) rememberIdentity(r.identityKey);
1732
2184
  const response = verifyUrl ? verified : null;
2185
+ this.#picked(option, reason, verifyUrl ? r.identityKey : null);
1733
2186
  if (own) this.#signedIn({ identityKey: r.identityKey, handle: response?.handle ?? null, paymail: response?.paymail ?? null });
1734
2187
  this.#attempt(option, "signed-in", started);
1735
2188
  this.#emit("bwalletx-signed-in", { identityKey: r.identityKey, method, nonce: r.nonce, signature: r.signature, response });
1736
2189
  } catch (error) {
1737
2190
  const outcome = outcomeOf(error);
1738
2191
  this.#attempt(option, outcome, started);
2192
+ if (!live()) return;
1739
2193
  const closedQr = outcome === "cancelled" && option?.kind === "pairing";
1740
2194
  if (own) {
1741
2195
  if (closedQr) this.#phase = prev;
@@ -1746,12 +2200,15 @@ function createClass() {
1746
2200
  }
1747
2201
  if (!closedQr) this.#emit("bwalletx-error", { error });
1748
2202
  } finally {
1749
- this.#busy = false;
1750
2203
  try {
1751
2204
  pairing.close();
1752
2205
  } catch {
1753
2206
  }
1754
- this.#render();
2207
+ if (live()) {
2208
+ this.#busy = false;
2209
+ this.#viaMetanet = false;
2210
+ this.#render();
2211
+ }
1755
2212
  }
1756
2213
  }
1757
2214
  };
@@ -1824,18 +2281,6 @@ function qrSvg(text) {
1824
2281
  q.make();
1825
2282
  return q.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
1826
2283
  }
1827
- function isTouchDevice(env = globalThis) {
1828
- const nav = env.navigator;
1829
- if (!nav) return false;
1830
- const ua = nav.userAgent ?? "";
1831
- if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
1832
- if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
1833
- try {
1834
- return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
1835
- } catch {
1836
- return false;
1837
- }
1838
- }
1839
2284
  function pairingModal(o = {}) {
1840
2285
  const ac = new AbortController();
1841
2286
  let ui = null;
@@ -1996,10 +2441,11 @@ function chooseWallet(options, o = {}) {
1996
2441
  row2.onclick = () => done(w);
1997
2442
  list.append(row2);
1998
2443
  }
1999
- if (o.desktop !== false && !options.some((w) => w.substrate === "json-api" || w.substrate === "cicada")) {
2444
+ if (o.desktop !== false && metanetOffered() && !options.some((w) => w.substrate === "json-api" || w.substrate === "cicada")) {
2000
2445
  if (!here && !sep) list.append(el("p", "bwx-sep", "Or use"));
2001
- const d = desktopOption();
2002
- const row2 = optionRow(d, false, "Metanet or BSV Desktop on this computer", "Fewer features");
2446
+ const d = metanetOption();
2447
+ const row2 = optionRow(d, false, METANET_NOTE);
2448
+ row2.title = "Connects to Metanet Desktop on this computer, never to a browser extension.";
2003
2449
  row2.onclick = () => done(d);
2004
2450
  list.append(row2);
2005
2451
  }
@@ -2017,8 +2463,17 @@ function chooseWallet(options, o = {}) {
2017
2463
  }
2018
2464
 
2019
2465
  // src/pay.ts
2020
- var import_sdk6 = require("@bsv/sdk");
2466
+ var import_sdk7 = require("@bsv/sdk");
2021
2467
  var BRC29_PROTOCOL = [2, "3241645161d8"];
2468
+ var NO_COINS_TEXT = "Your wallet doesn't have enough to cover this payment and its small network fee. Add a little and try again.";
2469
+ function isInsufficientFunds(e) {
2470
+ const err = e;
2471
+ if (!err) return false;
2472
+ if (err.name === "WERR_INSUFFICIENT_FUNDS" || err.code === "WERR_INSUFFICIENT_FUNDS" || err.code === "ERR_INSUFFICIENT_FUNDS") return true;
2473
+ if (err.isError === true && err.code === 7) return true;
2474
+ const msg = typeof err.message === "string" ? err.message : String(e);
2475
+ return /insufficient (funds|balance)|not enough (funds|coins|satoshis|sats)|more satoshis are needed/i.test(msg);
2476
+ }
2022
2477
  var PaymentError = class extends Error {
2023
2478
  constructor(code, message, maybeSent = false, cause) {
2024
2479
  super(message);
@@ -2037,13 +2492,13 @@ var isPaymail = (s) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
2037
2492
  function randomB64(n) {
2038
2493
  const b = new Uint8Array(n);
2039
2494
  globalThis.crypto.getRandomValues(b);
2040
- return import_sdk6.Utils.toBase64(Array.from(b));
2495
+ return import_sdk7.Utils.toBase64(Array.from(b));
2041
2496
  }
2042
2497
  function opReturn(text) {
2043
- const d = import_sdk6.Utils.toArray(text, "utf8");
2498
+ const d = import_sdk7.Utils.toArray(text, "utf8");
2044
2499
  const len = d.length;
2045
2500
  const push = len < 76 ? [len] : len <= 255 ? [76, len] : [77, len & 255, len >> 8];
2046
- return import_sdk6.Utils.toHex([0, 106, ...push, ...d]);
2501
+ return import_sdk7.Utils.toHex([0, 106, ...push, ...d]);
2047
2502
  }
2048
2503
  var clip = (s, n) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
2049
2504
  var describe = (s) => clip(s.length < 5 ? `${s} payment`.trim() : s, 50);
@@ -2114,6 +2569,7 @@ async function runAction(wallet, description, outputs, labels) {
2114
2569
  options: { randomizeOutputs: false }
2115
2570
  });
2116
2571
  } catch (e) {
2572
+ if (isInsufficientFunds(e)) throw new PaymentError("INSUFFICIENT_FUNDS", NO_COINS_TEXT, false, e);
2117
2573
  const msg = e?.message ?? String(e);
2118
2574
  const rejected = /denied|reject|cancel|abort|user/i.test(msg) || e?.code === 4001;
2119
2575
  throw new PaymentError(rejected ? "WALLET_REJECTED" : "WALLET_ERROR", rejected ? "The payment was declined in the wallet." : `The wallet could not make the payment: ${msg}`, !rejected, e);
@@ -2139,7 +2595,7 @@ async function pay(source, req, o = {}) {
2139
2595
  } catch (e) {
2140
2596
  throw new PaymentError("WALLET_ERROR", `The wallet could not derive the payment key: ${e.message}`, false, e);
2141
2597
  }
2142
- outputs = [{ script: new import_sdk6.P2PKH().lock(import_sdk6.PublicKey.fromString(pk).toHash()).toHex(), satoshis: sats, description: "payment" }];
2598
+ outputs = [{ script: new import_sdk7.P2PKH().lock(import_sdk7.PublicKey.fromString(pk).toHash()).toHex(), satoshis: sats, description: "payment" }];
2143
2599
  remittance = { senderIdentityKey: me, derivationPrefix, derivationSuffix, outputIndex: 0 };
2144
2600
  } else if (isPaymail(to)) {
2145
2601
  const r2 = await (o.resolvePaymail ?? ((p, s) => resolvePaymailP2P(p, s, o.fetch ?? fetch)))(to, sats);
@@ -2147,7 +2603,7 @@ async function pay(source, req, o = {}) {
2147
2603
  submit = r2.submit;
2148
2604
  } else {
2149
2605
  try {
2150
- outputs = [{ script: new import_sdk6.P2PKH().lock(to).toHex(), satoshis: sats, description: "payment" }];
2606
+ outputs = [{ script: new import_sdk7.P2PKH().lock(to).toHex(), satoshis: sats, description: "payment" }];
2151
2607
  } catch {
2152
2608
  throw new PaymentError("BAD_REQUEST", `Not a paymail, address or identity key: ${to}`);
2153
2609
  }
@@ -2157,7 +2613,7 @@ async function pay(source, req, o = {}) {
2157
2613
  if (!r.txid) throw new PaymentError("WALLET_ERROR", "The wallet did not return a txid", true);
2158
2614
  const tx = r.tx ? Array.from(r.tx) : void 0;
2159
2615
  if (submit && tx) {
2160
- await submit(import_sdk6.Transaction.fromAtomicBEEF(tx).toHex()).catch(() => {
2616
+ await submit(import_sdk7.Transaction.fromAtomicBEEF(tx).toHex()).catch(() => {
2161
2617
  });
2162
2618
  }
2163
2619
  return { txid: r.txid, tx, sats, outputs, remittance };
@@ -2206,7 +2662,7 @@ function BwalletxSignin(props) {
2206
2662
  if (!e.defaultPrevented && c && cb.current.stateful !== false) {
2207
2663
  e.preventDefault();
2208
2664
  const mode = e.detail?.mode;
2209
- c.connect(mode === "chooser" ? { chooser: true } : {}).catch(() => void 0);
2665
+ c.connect(mode === "chooser" ? { chooser: true } : mode === "bwalletx" ? { bwalletx: true } : mode === "metanet" ? { metanet: true } : {}).catch(() => void 0);
2210
2666
  }
2211
2667
  }
2212
2668
  ],
@@ -2223,7 +2679,17 @@ function BwalletxSignin(props) {
2223
2679
  }
2224
2680
  }
2225
2681
  ],
2226
- ["bwalletx-signed-out", () => cb.current.onSignedOut?.()]
2682
+ ["bwalletx-signed-out", () => cb.current.onSignedOut?.()],
2683
+ [
2684
+ "bwalletx-not-you",
2685
+ (e) => {
2686
+ const c = ctxRef.current;
2687
+ if (c && cb.current.stateful !== false) {
2688
+ e.preventDefault();
2689
+ c.forgetWallet();
2690
+ }
2691
+ }
2692
+ ]
2227
2693
  ];
2228
2694
  for (const [t, f] of on) el2.addEventListener(t, f);
2229
2695
  return () => {
@@ -2267,6 +2733,8 @@ function BwalletxSignin(props) {
2267
2733
  scope: p.scope,
2268
2734
  "discover-ms": p.discoverMs,
2269
2735
  "other-wallets": p.otherWallets,
2736
+ metanet: p.metanet,
2737
+ "wallet-type": driven ? ctx.walletType ?? ctx.rememberedWallet?.type ?? void 0 : void 0,
2270
2738
  "aria-label": p["aria-label"]
2271
2739
  });
2272
2740
  }
@@ -2281,7 +2749,10 @@ var empty = {
2281
2749
  walletType: null,
2282
2750
  transitional: false,
2283
2751
  signedIn: false,
2284
- error: null
2752
+ error: null,
2753
+ rememberedWallet: null,
2754
+ needsBwalletX: false,
2755
+ metanetOffered: false
2285
2756
  };
2286
2757
  var Ctx = (0, import_react.createContext)(null);
2287
2758
  async function postJson2(url, body) {
@@ -2292,6 +2763,10 @@ async function postJson2(url, body) {
2292
2763
  }
2293
2764
  function BwalletXProvider(p) {
2294
2765
  const [state, setState] = (0, import_react.useState)(empty);
2766
+ const stateRef = (0, import_react.useRef)(state);
2767
+ stateRef.current = state;
2768
+ const silentRun = (0, import_react.useRef)(0);
2769
+ const sessionChecked = (0, import_react.useRef)(!p.sessionUrl);
2295
2770
  const [wallets, setWallets] = (0, import_react.useState)([]);
2296
2771
  const props = (0, import_react.useRef)(p);
2297
2772
  props.current = p;
@@ -2302,6 +2777,9 @@ function BwalletXProvider(p) {
2302
2777
  props.current.onError?.(err);
2303
2778
  return err;
2304
2779
  }, []);
2780
+ (0, import_react.useEffect)(() => {
2781
+ warnIfNoLogout({ verifyUrl: p.verifyUrl, logoutUrl: p.logoutUrl, where: "<BwalletXProvider>" });
2782
+ }, [p.verifyUrl, p.logoutUrl]);
2305
2783
  const themeOn = !!p.theme && (state.status === "connecting" || state.status === "connected");
2306
2784
  (0, import_react.useEffect)(() => {
2307
2785
  if (!themeOn) return;
@@ -2315,10 +2793,15 @@ function BwalletXProvider(p) {
2315
2793
  setState((s) => ({ ...s, status: "restoring" }));
2316
2794
  fetch(url, { credentials: "same-origin" }).then((r) => r.json()).then((j) => {
2317
2795
  if (!live) return;
2796
+ sessionChecked.current = true;
2318
2797
  setState(
2319
2798
  (s) => j.identityKey ? { ...s, status: "connected", identityKey: j.identityKey, handle: j.handle ?? null, paymail: j.paymail ?? null, signedIn: true } : { ...s, status: s.status === "restoring" ? "idle" : s.status }
2320
2799
  );
2321
- }).catch(() => live && setState((s) => ({ ...s, status: s.status === "restoring" ? "idle" : s.status })));
2800
+ }).catch(() => {
2801
+ if (!live) return;
2802
+ sessionChecked.current = true;
2803
+ setState((s) => ({ ...s, status: s.status === "restoring" ? "idle" : s.status }));
2804
+ });
2322
2805
  return () => {
2323
2806
  live = false;
2324
2807
  };
@@ -2339,7 +2822,7 @@ function BwalletXProvider(p) {
2339
2822
  const c = props.current;
2340
2823
  setState((s) => ({ ...s, status: "connecting", error: null }));
2341
2824
  try {
2342
- const list = await listWallets();
2825
+ const list = opts.metanet ? [] : await listWallets();
2343
2826
  const fast = c.fastPath !== false;
2344
2827
  const r = await runConnect({
2345
2828
  list,
@@ -2349,6 +2832,10 @@ function BwalletXProvider(p) {
2349
2832
  fastPath: fast,
2350
2833
  pick: fast ? (l) => chooseWallet(l) : pickWallet,
2351
2834
  onAttempt: c.onAttempt,
2835
+ remembered: fast && c.rememberChoice !== false ? rememberedWalletChoice() : null,
2836
+ remember: fast && c.rememberChoice !== false,
2837
+ preferBwalletX: opts.bwalletx,
2838
+ metanet: opts.metanet,
2352
2839
  challengeUrl: c.challengeUrl,
2353
2840
  verifyUrl: c.verifyUrl,
2354
2841
  challengeNeedsIdentityKey: c.challengeNeedsIdentityKey,
@@ -2370,7 +2857,10 @@ function BwalletXProvider(p) {
2370
2857
  walletType: r.walletType,
2371
2858
  transitional: !!cw.transitional,
2372
2859
  signedIn: r.signedIn,
2373
- error: null
2860
+ error: null,
2861
+ rememberedWallet: c.rememberChoice !== false ? rememberedWalletChoice() : null,
2862
+ needsBwalletX: needsBwalletX(r.walletType),
2863
+ metanetOffered: metanetOffered()
2374
2864
  });
2375
2865
  return { ...cw, identityKey: r.identityKey, handle: r.handle ?? void 0, paymail: r.paymail ?? void 0 };
2376
2866
  } catch (e) {
@@ -2388,9 +2878,96 @@ function BwalletXProvider(p) {
2388
2878
  if (c.logoutUrl) await postJson2(c.logoutUrl, {}).catch(() => {
2389
2879
  });
2390
2880
  if (state.walletId === "bwalletx-pairing") await clearPairing();
2881
+ if (c.rememberChoice !== false) forgetWalletChoice();
2882
+ silentRun.current++;
2391
2883
  payer.current = null;
2392
- setState(empty);
2884
+ setState({ ...empty, metanetOffered: metanetOffered() });
2393
2885
  }, [state.walletId]);
2886
+ const switchToBwalletX = (0, import_react.useCallback)(() => connect({ bwalletx: true }), [connect]);
2887
+ const connectMetanet = (0, import_react.useCallback)(() => connect({ metanet: true }), [connect]);
2888
+ const forgetWallet = (0, import_react.useCallback)(() => {
2889
+ if (props.current.rememberChoice !== false) forgetWalletChoice();
2890
+ silentRun.current++;
2891
+ setState((s) => ({
2892
+ ...s,
2893
+ rememberedWallet: null,
2894
+ needsBwalletX: s.walletType ? needsBwalletX(s.walletType) : false,
2895
+ // A silent Metanet sign-in on screen goes back to the normal choice.
2896
+ status: s.status === "connecting" && !s.wallet ? s.signedIn && s.identityKey ? "connected" : "idle" : s.status
2897
+ }));
2898
+ }, []);
2899
+ (0, import_react.useEffect)(() => {
2900
+ setState((s) => s.metanetOffered === metanetOffered() ? s : { ...s, metanetOffered: metanetOffered() });
2901
+ if (p.rememberChoice === false) return;
2902
+ const r = rememberedWalletChoice();
2903
+ if (r) setState((s) => s.rememberedWallet ? s : { ...s, rememberedWallet: r, needsBwalletX: s.walletType ? s.needsBwalletX : needsBwalletX(r.type) && r.type !== METANET_TYPE });
2904
+ }, [p.rememberChoice]);
2905
+ const sessionOnly = state.status === "connected" && state.signedIn && !state.wallet;
2906
+ (0, import_react.useEffect)(() => {
2907
+ const c = props.current;
2908
+ if (!sessionOnly || c.autoConnect === false || c.rememberChoice === false || c.fastPath === false) return;
2909
+ const plan = planAutoConnect({ remembered: rememberedWalletChoice(), signedIn: true });
2910
+ if (plan.action !== "attach" || !plan.remembered) return;
2911
+ let live = true;
2912
+ void attachRemembered(plan.remembered, { ms: c.discoverMs }).then((opt) => {
2913
+ if (!live || !opt?.wallet) return;
2914
+ payer.current = opt.wallet;
2915
+ setState(
2916
+ (s) => s.status === "connected" && !s.wallet ? { ...s, wallet: opt.wallet, walletId: opt.id, walletType: walletTypeOf(opt), needsBwalletX: needsBwalletX(walletTypeOf(opt)) } : s
2917
+ );
2918
+ });
2919
+ return () => {
2920
+ live = false;
2921
+ };
2922
+ }, [sessionOnly]);
2923
+ const silentReady = state.status === "idle" || sessionOnly;
2924
+ (0, import_react.useEffect)(() => {
2925
+ const c = props.current;
2926
+ if (!silentReady || !sessionChecked.current || c.autoConnect === false || c.rememberChoice === false || c.fastPath === false || silentAttemptUsed()) return;
2927
+ const plan = planAutoConnect({ remembered: rememberedWalletChoice(), signedIn: sessionOnly });
2928
+ if (plan.action !== "silent-metanet") return;
2929
+ const run = ++silentRun.current;
2930
+ const live = () => run === silentRun.current;
2931
+ let shown = false;
2932
+ const session = stateRef.current;
2933
+ void runSilentMetanet({
2934
+ sessionKey: sessionOnly ? session.identityKey : null,
2935
+ challengeUrl: c.challengeUrl,
2936
+ verifyUrl: c.verifyUrl,
2937
+ post: postJson2,
2938
+ onAttempt: c.onAttempt,
2939
+ isAbandoned: () => !live(),
2940
+ logout: () => props.current.logoutUrl ? postJson2(props.current.logoutUrl, {}) : Promise.resolve(),
2941
+ onSignIn: () => {
2942
+ if (!live()) return;
2943
+ shown = true;
2944
+ setState((s) => s.status === "idle" ? { ...s, status: "connecting", error: null } : s);
2945
+ }
2946
+ }).then((r) => {
2947
+ if (!live()) return;
2948
+ if (!r) {
2949
+ if (shown) setState((s) => s.status === "connecting" && !s.wallet ? { ...s, status: s.signedIn && s.identityKey ? "connected" : "idle" } : s);
2950
+ return;
2951
+ }
2952
+ const cw = r.connected;
2953
+ payer.current = cw.payer;
2954
+ setState((s) => ({
2955
+ ...s,
2956
+ status: "connected",
2957
+ identityKey: r.identityKey,
2958
+ handle: r.handle ?? s.handle,
2959
+ paymail: r.paymail ?? s.paymail,
2960
+ wallet: cw.wallet,
2961
+ walletId: cw.optionId,
2962
+ walletType: r.walletType,
2963
+ transitional: false,
2964
+ signedIn: r.signedIn || s.signedIn,
2965
+ error: null,
2966
+ rememberedWallet: rememberedWalletChoice(),
2967
+ needsBwalletX: true
2968
+ }));
2969
+ });
2970
+ }, [silentReady, sessionOnly]);
2394
2971
  (0, import_react.useEffect)(() => {
2395
2972
  const key = state.identityKey;
2396
2973
  if (!key || state.paymail || p.lookupUrl === "off") return;
@@ -2412,12 +2989,15 @@ function BwalletXProvider(p) {
2412
2989
  wallets,
2413
2990
  connect,
2414
2991
  disconnect,
2992
+ switchToBwalletX,
2993
+ connectMetanet,
2994
+ forgetWallet,
2415
2995
  listWallets,
2416
2996
  pay: (req) => pay(need(), req, props.current.payOptions),
2417
2997
  tip: (req) => tip(need(), req, props.current.payOptions),
2418
2998
  buyToken: (req) => buyToken(need(), req)
2419
2999
  }),
2420
- [state, wallets, connect, disconnect, listWallets]
3000
+ [state, wallets, connect, disconnect, switchToBwalletX, connectMetanet, forgetWallet, listWallets]
2421
3001
  );
2422
3002
  return (0, import_react.createElement)(Ctx.Provider, { value }, p.children);
2423
3003
  }