@bwalletx/connect 0.5.4 → 0.5.6

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 (48) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +76 -8
  3. package/dist/bwalletx-connect.global.js +92 -25
  4. package/dist/bwalletx-connect.global.js.map +1 -1
  5. package/dist/{wallets-CbzMtxrf.d.ts → choice-DCh-tgpT.d.cts} +112 -4
  6. package/dist/{wallets-CbzMtxrf.d.cts → choice-DCh-tgpT.d.ts} +112 -4
  7. package/dist/chunk-34DKBQNN.js +222 -0
  8. package/dist/chunk-34DKBQNN.js.map +1 -0
  9. package/dist/{chunk-QRXFL66W.js → chunk-A2WLGS25.js} +6 -4
  10. package/dist/chunk-A2WLGS25.js.map +1 -0
  11. package/dist/chunk-IFGOAROM.js +255 -0
  12. package/dist/chunk-IFGOAROM.js.map +1 -0
  13. package/dist/chunk-LS6AYEBO.js +1886 -0
  14. package/dist/chunk-LS6AYEBO.js.map +1 -0
  15. package/dist/element-D9Zl-x12.d.cts +46 -0
  16. package/dist/element-D9Zl-x12.d.ts +46 -0
  17. package/dist/element.cjs +1147 -61
  18. package/dist/element.cjs.map +1 -1
  19. package/dist/element.d.cts +23 -28
  20. package/dist/element.d.ts +23 -28
  21. package/dist/element.js +29 -2
  22. package/dist/element.js.map +1 -1
  23. package/dist/index.cjs +1469 -453
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +163 -14
  26. package/dist/index.d.ts +163 -14
  27. package/dist/index.js +100 -12
  28. package/dist/index.js.map +1 -1
  29. package/dist/next.cjs +5 -3
  30. package/dist/next.cjs.map +1 -1
  31. package/dist/next.js +1 -1
  32. package/dist/react.cjs +994 -183
  33. package/dist/react.cjs.map +1 -1
  34. package/dist/react.d.cts +75 -8
  35. package/dist/react.d.ts +75 -8
  36. package/dist/react.js +121 -40
  37. package/dist/react.js.map +1 -1
  38. package/dist/server.cjs +5 -3
  39. package/dist/server.cjs.map +1 -1
  40. package/dist/server.d.cts +8 -3
  41. package/dist/server.d.ts +8 -3
  42. package/dist/server.js +1 -1
  43. package/package.json +10 -9
  44. package/dist/chunk-EXRQJTXF.js +0 -910
  45. package/dist/chunk-EXRQJTXF.js.map +0 -1
  46. package/dist/chunk-QRXFL66W.js.map +0 -1
  47. package/dist/chunk-RIOHG5JH.js +0 -459
  48. package/dist/chunk-RIOHG5JH.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -34,12 +34,23 @@ __export(src_exports, {
34
34
  ACCESS_PROTOCOL: () => ACCESS_PROTOCOL,
35
35
  ACCESS_VERSION: () => ACCESS_VERSION,
36
36
  AccessError: () => AccessError,
37
+ BAVATAR_URL: () => BAVATAR_URL,
37
38
  BRC29_PROTOCOL: () => BRC29_PROTOCOL,
39
+ BUTTON_TAG: () => BUTTON_TAG,
38
40
  BWALLETX_RDNS: () => BWALLETX_RDNS,
41
+ BWALLETX_TYPE: () => BWALLETX_TYPE,
42
+ CONNECTED_KEY: () => CONNECTED_KEY,
39
43
  DEFAULT_DISCOVER_MS: () => DEFAULT_DISCOVER_MS,
44
+ DESKTOP_OPTION_ID: () => DESKTOP_OPTION_ID,
40
45
  GET_URL: () => GET_URL,
46
+ IDENTITY_HINT_KEY: () => IDENTITY_HINT_KEY,
41
47
  LOGIN_PROTOCOL: () => LOGIN_PROTOCOL,
42
48
  LOGO_URL: () => LOGO_URL,
49
+ MENU_CSS: () => MENU_CSS,
50
+ PAYMAIL_LOOKUP_URL: () => PAYMAIL_LOOKUP_URL,
51
+ PENDING_LABEL: () => PENDING_LABEL,
52
+ PENDING_LABEL_OTHER: () => PENDING_LABEL_OTHER,
53
+ PENDING_NOTE: () => PENDING_NOTE,
43
54
  PROTOCOL_MEDIA: () => PROTOCOL_MEDIA,
44
55
  PaymentError: () => PaymentError,
45
56
  RDNS_EXTENSION: () => RDNS_EXTENSION,
@@ -49,25 +60,46 @@ __export(src_exports, {
49
60
  WALLET_THEME_META: () => WALLET_THEME_META,
50
61
  WEB_WALLET_URL: () => WEB_WALLET_URL,
51
62
  accessErrorMessage: () => accessErrorMessage,
63
+ accountLabel: () => accountLabel,
52
64
  amountInSats: () => amountInSats,
65
+ attemptFor: () => attemptFor,
66
+ autoChoice: () => autoChoice,
67
+ avatarUrlFor: () => avatarUrlFor,
53
68
  bindPrewarm: () => bindPrewarm,
54
69
  brc100Option: () => brc100Option,
55
70
  buildAuthorizeUrl: () => buildAuthorizeUrl,
56
71
  buyToken: () => buyToken,
57
72
  canonicalJson: () => canonicalJson,
73
+ challengeWantsIdentityKey: () => challengeWantsIdentityKey,
74
+ chooseWallet: () => chooseWallet,
75
+ chooserOrder: () => chooserOrder,
58
76
  clearPairing: () => clearPairing,
59
77
  connectBwalletX: () => connectBwalletX,
78
+ cwiIsAnnounced: () => cwiIsAnnounced,
79
+ decideWallet: () => decideWallet,
60
80
  declareWalletTheme: () => declareWalletTheme,
81
+ defineBwalletxButton: () => defineBwalletxButton,
61
82
  defineBwalletxSignin: () => defineBwalletxSignin,
83
+ desktopOption: () => desktopOption,
62
84
  discoverBwalletX: () => discoverBwalletX,
63
85
  discoverWallets: () => discoverWallets,
86
+ dropLegacyConnectedKey: () => dropLegacyConnectedKey,
64
87
  findBwalletX: () => findBwalletX,
88
+ getUrlFor: () => getUrlFor,
65
89
  handcashAdapter: () => handcashAdapter,
90
+ handleLabel: () => handleLabel,
91
+ hasBwalletX: () => hasBwalletX,
66
92
  inOrder: () => inOrder,
93
+ isFramed: () => isFramed,
67
94
  isIdentityKeyHex: () => isIdentityKeyHex,
68
95
  isPaymail: () => isPaymail,
96
+ isPresentBwalletX: () => isPresentBwalletX,
69
97
  isTouchDevice: () => isTouchDevice,
98
+ isUserRejection: () => isUserRejection,
99
+ lookupPaymail: () => lookupPaymail,
70
100
  markSvg: () => markSvg,
101
+ otherDetected: () => otherDetected,
102
+ outcomeOf: () => outcomeOf,
71
103
  pairBwalletX: () => pairBwalletX,
72
104
  pairingModal: () => pairingModal,
73
105
  pairingOption: () => pairingOption,
@@ -77,15 +109,26 @@ __export(src_exports, {
77
109
  protocolMediaUrl: () => protocolMediaUrl,
78
110
  proveHoldings: () => proveHoldings,
79
111
  qrSvg: () => qrSvg,
112
+ rememberIdentity: () => rememberIdentity,
113
+ rememberedIdentity: () => rememberedIdentity,
80
114
  renderButton: () => renderButton,
115
+ reportAttempt: () => reportAttempt,
81
116
  requestAccess: () => requestAccess,
82
117
  resolvePaymailP2P: () => resolvePaymailP2P,
83
118
  restorePairing: () => restorePairing,
119
+ runConnect: () => runConnect,
120
+ shortKey: () => shortKey,
121
+ signInAndVerify: () => signInAndVerify,
122
+ signInErrorText: () => signInErrorText,
84
123
  signInWithBwalletX: () => signInWithBwalletX,
85
124
  signinText: () => signinText,
125
+ stateFor: () => stateFor,
126
+ substrateOf: () => substrateOf,
86
127
  throwIfAccessError: () => throwIfAccessError,
87
128
  tip: () => tip,
88
- toAccessError: () => toAccessError
129
+ toAccessError: () => toAccessError,
130
+ waitIfLocked: () => waitIfLocked,
131
+ walletTypeOf: () => walletTypeOf
89
132
  });
90
133
  module.exports = __toCommonJS(src_exports);
91
134
 
@@ -114,21 +157,16 @@ function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
114
157
  finish({ wallet: d.wallet, method: rdns === RDNS_EXTENSION ? "extension" : "in-app", rdns });
115
158
  }
116
159
  };
117
- const cwiNow = () => {
118
- const cwi = window.CWI;
119
- if (cwi) finish({ wallet: cwi, method: "in-app", rdns: null });
120
- return !!cwi;
121
- };
122
160
  window.addEventListener("brc100:announceWallet", onAnnounce);
123
161
  window.dispatchEvent(new Event("brc100:requestWallet"));
124
- if (done || cwiNow()) return;
162
+ if (done) return;
125
163
  if (isFramed()) {
126
164
  void probeXdm(Math.max(ms, 1200)).then((w) => finish(w ? { wallet: w, method: "in-app", rdns: null } : null));
127
165
  return;
128
166
  }
129
167
  timer = setTimeout(() => {
130
168
  timer = void 0;
131
- if (!cwiNow()) finish(null);
169
+ finish(null);
132
170
  }, ms);
133
171
  });
134
172
  }
@@ -618,27 +656,68 @@ async function connectBwalletX(opts = {}) {
618
656
  status("pairing");
619
657
  return { wallet: await pairBwalletX(opts.pairing), method: "pairing" };
620
658
  }
659
+ async function waitIfLocked(wallet) {
660
+ let ok = false;
661
+ try {
662
+ ok = !!(await wallet.isAuthenticated({}))?.authenticated;
663
+ } catch {
664
+ }
665
+ if (!ok) await Promise.resolve().then(() => wallet.waitForAuthentication({})).catch(() => {
666
+ });
667
+ }
668
+ function challengeWantsIdentityKey(e) {
669
+ const msg = e instanceof Error ? e.message : String(e ?? "");
670
+ return /identityKey/i.test(msg);
671
+ }
621
672
  async function signInWithBwalletX(opts) {
622
673
  const { wallet, method } = await connectBwalletX(opts);
623
- if (method !== "pairing") await wallet.waitForAuthentication({}).catch(() => {
674
+ if (method !== "pairing" && !opts.skipWaitForAuthentication) await waitIfLocked(wallet);
675
+ const status = () => {
676
+ try {
677
+ opts.onStatus?.("signing");
678
+ } catch {
679
+ }
680
+ };
681
+ const sign = (c2) => wallet.createSignature({
682
+ protocolID: c2.protocolID ?? LOGIN_PROTOCOL,
683
+ keyID: c2.keyID,
684
+ counterparty: "anyone",
685
+ data: Array.from(new TextEncoder().encode(c2.message))
624
686
  });
625
- const { publicKey: identityKey } = await atStage("identity", () => wallet.getPublicKey({ identityKey: true }));
687
+ const keyFirst = async () => {
688
+ const { publicKey: identityKey2 } = await atStage("identity", () => wallet.getPublicKey({ identityKey: true }));
689
+ status();
690
+ const c2 = await atStage("challenge", () => opts.challenge(identityKey2));
691
+ const { signature: signature2 } = await atStage("signature", () => sign(c2));
692
+ return { identityKey: identityKey2, signature: Array.from(signature2), nonce: c2.nonce, method, wallet };
693
+ };
694
+ if (opts.challengeNeedsIdentityKey && !opts.identityKey) return keyFirst();
695
+ let c;
626
696
  try {
627
- opts.onStatus?.("signing");
628
- } catch {
697
+ c = await atStage("challenge", () => opts.challenge(opts.identityKey));
698
+ } catch (e) {
699
+ if (!opts.identityKey && challengeWantsIdentityKey(e)) return keyFirst();
700
+ throw e;
629
701
  }
630
- const c = await atStage("challenge", () => opts.challenge(identityKey));
631
- const { signature } = await atStage(
632
- "signature",
633
- () => wallet.createSignature({
634
- protocolID: c.protocolID ?? LOGIN_PROTOCOL,
635
- keyID: c.keyID,
636
- counterparty: "anyone",
637
- data: Array.from(new TextEncoder().encode(c.message))
638
- })
639
- );
702
+ status();
703
+ const [{ publicKey: identityKey }, { signature }] = await Promise.all([
704
+ opts.identityKey ? Promise.resolve({ publicKey: opts.identityKey }) : atStage("identity", () => wallet.getPublicKey({ identityKey: true })),
705
+ atStage("signature", () => sign(c))
706
+ ]);
640
707
  return { identityKey, signature: Array.from(signature), nonce: c.nonce, method, wallet };
641
708
  }
709
+ async function signInAndVerify(opts) {
710
+ const hinted = !opts.identityKey && !!opts.identityKeyHint;
711
+ const r = await signInWithBwalletX({ ...opts, identityKey: opts.identityKey ?? opts.identityKeyHint });
712
+ try {
713
+ return { result: r, verified: await atStage("verify", () => opts.verify(r)) };
714
+ } catch (e) {
715
+ const status = e?.status;
716
+ if (!hinted || status !== 401) throw e;
717
+ const again = await signInWithBwalletX({ ...opts, wallet: r.wallet, identityKey: void 0, skipWaitForAuthentication: true });
718
+ return { result: again, verified: await atStage("verify", () => opts.verify(again)) };
719
+ }
720
+ }
642
721
  async function atStage(stage, run) {
643
722
  try {
644
723
  return await run();
@@ -693,157 +772,689 @@ function renderButton(el2, opts = {}) {
693
772
  return b;
694
773
  }
695
774
 
696
- // src/access-client.ts
775
+ // src/wallets.ts
697
776
  var import_sdk4 = require("@bsv/sdk");
698
-
699
- // src/access.ts
700
- var ACCESS_VERSION = 1;
701
- var ACCESS_PROTOCOL = [2, "bwalletx access"];
702
- var MAX_HOLDING_PROOFS = 50;
703
- var ACCESS_ERROR_STATUS = {
704
- not_installed: 424,
705
- user_rejected: 403,
706
- wrong_account: 409,
707
- insufficient_tokens: 403,
708
- identity_level_too_low: 403,
709
- kyc_required: 403,
710
- indexer_unavailable: 503,
711
- expired: 401,
712
- replay: 401,
713
- invalid_signature: 401,
714
- bad_request: 400
715
- };
716
- var AccessError = class extends Error {
717
- code;
718
- have;
719
- need;
720
- constructor(code, message, extra = {}) {
721
- super(message ?? code);
722
- this.name = "AccessError";
723
- this.code = code;
724
- this.have = extra.have;
725
- this.need = extra.need;
726
- }
727
- toJSON() {
728
- const b = { error: this.code, message: this.message };
729
- if (this.have !== void 0) b.have = this.have;
730
- if (this.need !== void 0) b.need = this.need;
731
- return b;
732
- }
733
- };
734
- var PROTOCOL_MEDIA = {
735
- "401": { name: "Identity", color: "#22c55e", video: "assets/protocol/401.mp4", poster: "assets/protocol/401.jpg" },
736
- "402": { name: "Payment", color: "#3b82f6", video: "assets/protocol/402.mp4", poster: "assets/protocol/402.jpg" },
737
- "403": { name: "Access", color: "#ef4444", video: "assets/protocol/403.mp4", poster: "assets/protocol/403.jpg" }
738
- };
739
- function protocolMediaUrl(kind, which, version = "latest") {
740
- return `https://unpkg.com/@bwalletx/connect@${version}/${PROTOCOL_MEDIA[kind][which]}`;
777
+ async function brc100Connected(wallet, o, c = {}) {
778
+ if (c.identity === false) return { wallet, payer: wallet, identityKey: null, optionId: o.id, kind: o.kind };
779
+ await waitIfLocked(wallet);
780
+ const { publicKey } = await wallet.getPublicKey({ identityKey: true });
781
+ return { wallet, payer: wallet, identityKey: publicKey, optionId: o.id, kind: o.kind };
741
782
  }
742
- function canonicalJson(v) {
743
- if (v === null) return "null";
744
- if (typeof v === "string" || typeof v === "boolean") return JSON.stringify(v);
745
- if (typeof v === "number") {
746
- if (!Number.isFinite(v)) throw new Error("canonicalJson: non-finite number");
747
- return JSON.stringify(v);
748
- }
749
- if (Array.isArray(v)) return `[${v.map((x) => canonicalJson(x)).join(",")}]`;
750
- if (typeof v === "object") {
751
- const o = v;
752
- const keys = Object.keys(o).filter((k) => o[k] !== void 0).sort();
753
- return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(o[k])}`).join(",")}}`;
754
- }
755
- throw new Error(`canonicalJson: unsupported ${typeof v}`);
783
+ function brc100Option(wallet, info) {
784
+ const opt = {
785
+ id: info.id,
786
+ name: info.name,
787
+ icon: info.icon,
788
+ rdns: info.rdns,
789
+ kind: "brc100",
790
+ isBwalletX: info.isBwalletX ?? false,
791
+ substrate: info.substrate,
792
+ wallet,
793
+ connect: (c) => brc100Connected(wallet, opt, c)
794
+ };
795
+ return opt;
756
796
  }
757
- function challengeBytes(challenge) {
758
- return Array.from(new TextEncoder().encode(challenge));
797
+ function pairingOption(pairing) {
798
+ const opt = {
799
+ id: "bwalletx-pairing",
800
+ name: "bWalletX on your phone",
801
+ kind: "pairing",
802
+ isBwalletX: true,
803
+ substrate: "pairing",
804
+ async connect(c) {
805
+ const p = typeof pairing === "function" ? pairing() : pairing;
806
+ const w = await restorePairing(p) ?? await pairBwalletX(p);
807
+ return brc100Connected(w, opt, c);
808
+ }
809
+ };
810
+ return opt;
759
811
  }
760
- var TOKEN_ID = /^bsv21:[0-9a-f]{64}_\d{1,6}$/;
761
- var RAW = /^[1-9]\d{0,38}$/;
762
- function normaliseRequirement(r) {
763
- if (!r || typeof r !== "object") throw new AccessError("bad_request", "require must be an object");
764
- const req = r;
765
- const out = {};
766
- if (req.token !== void 0) {
767
- const t = req.token;
768
- if (!t || typeof t !== "object" || typeof t.id !== "string") throw new AccessError("bad_request", "require.token.id is required");
769
- const id = t.id.trim().toLowerCase();
770
- if (!TOKEN_ID.test(id)) throw new AccessError("bad_request", "require.token.id must be 'bsv21:<txid>_<vout>'");
771
- const hasMin = t.min !== void 0;
772
- const hasBps = t.minBps !== void 0;
773
- if (hasMin === hasBps) throw new AccessError("bad_request", "require.token needs exactly one of min or minBps");
774
- out.token = { id };
775
- if (hasMin) {
776
- if (typeof t.min !== "string" || !RAW.test(t.min)) throw new AccessError("bad_request", "require.token.min must be a positive integer string (raw units)");
777
- out.token.min = t.min;
778
- } else {
779
- if (!Number.isInteger(t.minBps) || t.minBps < 1 || t.minBps > 1e4) {
780
- throw new AccessError("bad_request", "require.token.minBps must be an integer 1..10000");
781
- }
782
- out.token.minBps = t.minBps;
812
+ function handcashAdapter(o) {
813
+ const opt = {
814
+ id: "handcash",
815
+ name: "HandCash",
816
+ icon: o.icon,
817
+ kind: "handcash",
818
+ isBwalletX: false,
819
+ substrate: "adapter",
820
+ async connect() {
821
+ const p = await o.login();
822
+ return {
823
+ wallet: null,
824
+ payer: o.pay ? { pay: o.pay } : null,
825
+ identityKey: p.identityKey ?? null,
826
+ handle: p.handle,
827
+ paymail: p.paymail ?? `${p.handle.replace(/^\$/, "")}@handcash.io`,
828
+ optionId: opt.id,
829
+ kind: "handcash",
830
+ transitional: true
831
+ };
783
832
  }
833
+ };
834
+ return opt;
835
+ }
836
+ var NAMES = { [RDNS_EXTENSION]: "bWalletX", "space.bwallet.mobile": "bWalletX" };
837
+ async function probe(substrate, ms) {
838
+ try {
839
+ const w = new import_sdk4.WalletClient(substrate);
840
+ const ok = await Promise.race([w.isAuthenticated({}).then(() => true), new Promise((r) => setTimeout(() => r(false), ms))]);
841
+ return ok ? w : null;
842
+ } catch {
843
+ return null;
784
844
  }
785
- if (req.identityLevel !== void 0) {
786
- if (!Number.isInteger(req.identityLevel) || req.identityLevel < 1 || req.identityLevel > 10) {
787
- throw new AccessError("bad_request", "require.identityLevel must be an integer 1..10");
845
+ }
846
+ function cwiIsAnnounced(cwi, announced) {
847
+ return announced.some((o) => o.wallet === cwi || o.rdns === RDNS_MOBILE);
848
+ }
849
+ async function discoverWallets(o = {}) {
850
+ const out = [];
851
+ const ms = o.ms ?? 300;
852
+ if (typeof window !== "undefined") {
853
+ const seen = /* @__PURE__ */ new Map();
854
+ const onAnnounce = (e) => {
855
+ const d = e.detail;
856
+ if (!d?.wallet) return;
857
+ const rdns = d.info?.rdns ?? d.info?.uuid ?? `wallet-${seen.size}`;
858
+ if (seen.has(rdns)) return;
859
+ const isB = BWALLETX_RDNS.includes(rdns);
860
+ const substrate = rdns === RDNS_EXTENSION ? "extension" : rdns === RDNS_MOBILE ? "in-app" : "injected";
861
+ seen.set(rdns, brc100Option(d.wallet, { id: rdns, rdns, name: NAMES[rdns] ?? d.info?.name ?? rdns, icon: d.info?.icon, isBwalletX: isB, substrate }));
862
+ };
863
+ window.addEventListener("brc100:announceWallet", onAnnounce);
864
+ window.dispatchEvent(new Event("brc100:requestWallet"));
865
+ const local = o.probeLocal ? Promise.all([probe("json-api", ms), probe("Cicada", ms)]) : Promise.resolve([null, null]);
866
+ await new Promise((r) => setTimeout(r, seen.size > 0 || window.CWI ? Math.min(ms, 50) : ms));
867
+ window.removeEventListener("brc100:announceWallet", onAnnounce);
868
+ out.push(...seen.values());
869
+ if (![...seen.values()].some((w) => w.isBwalletX) && isFramed()) {
870
+ const x = await probeXdm(Math.max(ms, 1200));
871
+ if (x) out.push(brc100Option(x, { id: "bwalletx-xdm", name: "bWalletX", isBwalletX: true, substrate: "xdm" }));
788
872
  }
789
- out.identityLevel = req.identityLevel;
873
+ const cwi = window.CWI;
874
+ if (cwi && !cwiIsAnnounced(cwi, [...seen.values()])) {
875
+ out.push(brc100Option(cwi, { id: "window.CWI", name: seen.size ? "Another browser wallet" : "Browser wallet", isBwalletX: false, substrate: "window.CWI" }));
876
+ }
877
+ const [json, cicada] = await local;
878
+ if (json) out.push(brc100Option(json, { id: "localhost:3321", name: "Desktop wallet (localhost:3321)", substrate: "json-api" }));
879
+ if (cicada) out.push(brc100Option(cicada, { id: "localhost:3301", name: "Desktop wallet (localhost:3301)", substrate: "cicada" }));
790
880
  }
791
- if (req.kyc !== void 0) {
792
- if (typeof req.kyc !== "boolean") throw new AccessError("bad_request", "require.kyc must be a boolean");
793
- if (req.kyc) out.kyc = true;
881
+ if (o.pairing) out.push(pairingOption(o.pairing));
882
+ out.push(...o.extra ?? []);
883
+ const rank = (w) => w.isBwalletX ? 0 : w.kind === "handcash" ? 2 : 1;
884
+ return out.map((w, i) => ({ w, i })).sort((a, b) => rank(a.w) - rank(b.w) || a.i - b.i).map((x) => x.w);
885
+ }
886
+ var DESKTOP_OPTION_ID = "desktop";
887
+ function desktopOption(ms = 1500) {
888
+ const opt = {
889
+ id: DESKTOP_OPTION_ID,
890
+ name: "Desktop wallet",
891
+ kind: "brc100",
892
+ isBwalletX: false,
893
+ substrate: "json-api",
894
+ async connect(c) {
895
+ const [json, cicada] = await Promise.all([probe("json-api", ms), probe("Cicada", ms)]);
896
+ const w = json ?? cicada;
897
+ if (!w) throw Object.assign(new Error("No desktop wallet answered on this computer"), { code: "NOT_FOUND" });
898
+ opt.wallet = w;
899
+ opt.substrate = json ? "json-api" : "cicada";
900
+ return brc100Connected(w, opt, c);
901
+ }
902
+ };
903
+ return opt;
904
+ }
905
+ function autoChoice(list, force = false) {
906
+ const detected = list.filter((w) => w.kind === "brc100");
907
+ const others = list.filter((w) => w.kind === "handcash" || w.kind === "custom");
908
+ if (force) return detected[0] ?? list.find((w) => w.kind !== "pairing") ?? list[0] ?? null;
909
+ if (!list.length) return null;
910
+ if (list.length === 1) return list[0];
911
+ if (detected.length === 1 && others.length === 0) return detected[0];
912
+ if (detected.length === 0 && others.length === 0) return list[0];
913
+ return void 0;
914
+ }
915
+
916
+ // src/account.ts
917
+ var PAYMAIL_LOOKUP_URL = "https://pay.bwallet.space/api/paymail/lookup?key=";
918
+ var BAVATAR_URL = "https://chat.bwalletx.com/api/bavatar/";
919
+ function shortKey(key) {
920
+ return key.length > 12 ? `${key.slice(0, 6)}\u2026${key.slice(-4)}` : key;
921
+ }
922
+ function accountLabel(a) {
923
+ if (a.paymail && /@bwalletx\.com$/i.test(a.paymail)) return "$" + a.paymail.split("@")[0];
924
+ if (a.handle) return a.handle;
925
+ if (a.paymail) return a.paymail;
926
+ return a.identityKey ? shortKey(a.identityKey) : "";
927
+ }
928
+ function avatarUrlFor(identityKey) {
929
+ return identityKey && /^0[23][0-9a-fA-F]{64}$/.test(identityKey) ? `${BAVATAR_URL}${identityKey.toLowerCase()}.png` : null;
930
+ }
931
+ var cache = /* @__PURE__ */ new Map();
932
+ function lookupPaymail(identityKey, url = PAYMAIL_LOOKUP_URL) {
933
+ if (url === "off" || typeof fetch === "undefined") return Promise.resolve(null);
934
+ const k = `${url}|${identityKey}`;
935
+ let p = cache.get(k);
936
+ if (!p) {
937
+ p = fetch(url + encodeURIComponent(identityKey)).then((r) => r.ok ? r.json() : null).then((j) => j && typeof j.paymail === "string" ? j.paymail : null).catch(() => null);
938
+ cache.set(k, p);
794
939
  }
795
- if (!out.token && !out.identityLevel && !out.kyc) throw new AccessError("bad_request", "require is empty");
796
- return out;
940
+ return p;
797
941
  }
798
- function parseAccessChallenge(s) {
799
- if (typeof s !== "string" || s.length > 4096) throw new AccessError("bad_request", "challenge must be a string");
800
- let c;
942
+ function isUserRejection(e) {
943
+ const code = e?.code;
944
+ if (code === "ERR_PERMISSION_DENIED" || code === 4001) return true;
945
+ const msg = e instanceof Error ? e.message : String(e ?? "");
946
+ return /denied|reject|cancel|declin|refused|not now|user closed/i.test(msg);
947
+ }
948
+ function signInErrorText(e) {
949
+ if (isUserRejection(e)) return "Sign-in cancelled";
950
+ const code = e?.code;
951
+ if (code === "NOT_FOUND") return "bWalletX was not found in this browser";
952
+ const msg = e instanceof Error ? e.message : String(e ?? "");
953
+ if (/lock/i.test(msg)) return "Your wallet is locked. Unlock it and try again";
954
+ if (/timeout|timed out|expired/i.test(msg)) return "The wallet did not answer in time";
955
+ if (/origin/i.test(msg)) return "This page is not on the site\u2019s configured origin";
956
+ return "Sign-in failed";
957
+ }
958
+ async function copyText(text) {
801
959
  try {
802
- c = JSON.parse(s);
960
+ await navigator.clipboard.writeText(text);
961
+ return true;
803
962
  } catch {
804
- throw new AccessError("bad_request", "challenge is not JSON");
963
+ return false;
805
964
  }
806
- if (!c || typeof c !== "object" || c.v !== 1) throw new AccessError("bad_request", "unsupported challenge version");
807
- if (c.kind !== "401" && c.kind !== "402" && c.kind !== "403") throw new AccessError("bad_request", "bad kind");
808
- if (typeof c.origin !== "string" || typeof c.nonce !== "string" || !/^[A-Za-z0-9_-]{16,64}$/.test(c.nonce)) {
809
- throw new AccessError("bad_request", "bad origin or nonce");
965
+ }
966
+ var IDENTITY_HINT_KEY = "bwx:identityKey";
967
+ function rememberedIdentity() {
968
+ try {
969
+ const k = globalThis.localStorage?.getItem(IDENTITY_HINT_KEY) ?? "";
970
+ return /^0[23][0-9a-f]{64}$/i.test(k) ? k : void 0;
971
+ } catch {
972
+ return void 0;
810
973
  }
811
- if (!Number.isInteger(c.iat) || !Number.isInteger(c.exp) || c.exp <= c.iat) throw new AccessError("bad_request", "bad iat/exp");
812
- if (c.sub !== void 0 && (typeof c.sub !== "string" || !/^0[23][0-9a-f]{64}$/.test(c.sub))) throw new AccessError("bad_request", "bad sub");
813
- c.require = normaliseRequirement(c.require);
814
- if (canonicalJson(c) !== s) throw new AccessError("bad_request", "challenge is not canonical");
815
- return c;
816
974
  }
817
- function accessErrorMessage(code, e = {}) {
818
- switch (code) {
819
- case "not_installed":
820
- return "No bWalletX (or other BRC-100 wallet) was found.";
821
- case "user_rejected":
822
- return "The request was declined in the wallet.";
823
- case "wrong_account":
824
- return "The wallet is signed in to a different account than this site expects.";
825
- case "insufficient_tokens":
826
- return `Not enough tokens: holding ${e.have ?? 0}, need ${e.need ?? "?"}.`;
827
- case "identity_level_too_low":
828
- return `Identity level ${e.have ?? 0} is below the required level ${e.need ?? "?"}.`;
829
- case "kyc_required":
830
- return "A verified identity (KYC) is required.";
831
- case "indexer_unavailable":
832
- return "Holdings could not be checked right now. Try again shortly.";
833
- case "expired":
834
- return "The request expired. Try again.";
835
- case "replay":
836
- return "That request was already used. Try again.";
837
- case "invalid_signature":
838
- return "The wallet signature did not verify.";
839
- case "bad_request":
840
- return "The request was malformed.";
975
+ function rememberIdentity(key) {
976
+ try {
977
+ if (key) globalThis.localStorage?.setItem(IDENTITY_HINT_KEY, key);
978
+ else globalThis.localStorage?.removeItem(IDENTITY_HINT_KEY);
979
+ } catch {
841
980
  }
842
981
  }
843
982
 
844
- // src/access-client.ts
845
- var ONESAT = [0, "onesat"];
846
- var LEGACY_ONESAT = [0, "p 1sat"];
983
+ // src/choice.ts
984
+ var BWALLETX_TYPE = "bwalletx";
985
+ function substrateOf(o) {
986
+ if (o.substrate) return o.substrate;
987
+ if (o.kind === "pairing") return "pairing";
988
+ if (o.isBwalletX && o.kind === "brc100") return o.rdns === RDNS_MOBILE ? "in-app" : o.id === "bwalletx-xdm" ? "xdm" : "extension";
989
+ if (o.kind === "handcash" || o.kind === "custom") return "adapter";
990
+ if (o.rdns === RDNS_EXTENSION) return "extension";
991
+ if (o.rdns === RDNS_MOBILE) return "in-app";
992
+ if (o.id === "window.CWI") return "window.CWI";
993
+ if (o.id === "localhost:3321" || o.id === DESKTOP_OPTION_ID) return "json-api";
994
+ if (o.id === "localhost:3301") return "cicada";
995
+ return "injected";
996
+ }
997
+ function walletTypeOf(o) {
998
+ return o.isBwalletX ? BWALLETX_TYPE : o.rdns ?? o.id;
999
+ }
1000
+ var OWN_MARKERS = ["extension", "in-app", "xdm"];
1001
+ function isPresentBwalletX(o) {
1002
+ return o.isBwalletX && o.kind === "brc100" && !!o.wallet && OWN_MARKERS.includes(substrateOf(o));
1003
+ }
1004
+ function hasBwalletX(list) {
1005
+ return list.some(isPresentBwalletX);
1006
+ }
1007
+ function otherDetected(list) {
1008
+ return list.filter((o) => !o.isBwalletX && o.kind === "brc100" && !!o.wallet);
1009
+ }
1010
+ function chooserOrder(list) {
1011
+ const rank = (o) => isPresentBwalletX(o) ? 0 : o.isBwalletX ? 1 : o.kind === "brc100" ? 2 : o.kind === "custom" ? 3 : 4;
1012
+ return list.map((o, i) => ({ o, i })).sort((a, b) => rank(a.o) - rank(b.o) || a.i - b.i).map((x) => x.o);
1013
+ }
1014
+ function decideWallet(list, o = {}) {
1015
+ if (o.walletId) {
1016
+ const w = list.find((x) => x.id === o.walletId);
1017
+ if (w) return { option: w, reason: "requested" };
1018
+ }
1019
+ if (o.chooser) return { option: null, reason: list.length || o.picker !== false ? "chooser" : "none" };
1020
+ if (o.fastPath === false) {
1021
+ const a = autoChoice([...list], o.picker === false);
1022
+ return a === void 0 ? { option: null, reason: "chooser" } : a === null ? { option: null, reason: "none" } : { option: a, reason: "legacy" };
1023
+ }
1024
+ const b = list.find(isPresentBwalletX);
1025
+ if (b) return { option: b, reason: "bwalletx" };
1026
+ if (o.picker === false) {
1027
+ const phone = list.find((x) => x.isBwalletX);
1028
+ return phone ? { option: phone, reason: "bwalletx-phone" } : { option: null, reason: "none" };
1029
+ }
1030
+ return list.length ? { option: null, reason: "chooser" } : { option: null, reason: "none" };
1031
+ }
1032
+ function outcomeOf(e) {
1033
+ const err = e;
1034
+ if (err?.code === "NOT_FOUND") return "not-found";
1035
+ if (err?.name === "AbortError" || err?.message === "cancelled") return "cancelled";
1036
+ if (isUserRejection(e)) return "rejected";
1037
+ return "error";
1038
+ }
1039
+ function attemptFor(option, outcome, startedAt, now = Date.now()) {
1040
+ return {
1041
+ walletType: option ? walletTypeOf(option) : null,
1042
+ substrate: option ? substrateOf(option) : null,
1043
+ outcome,
1044
+ ms: Math.max(0, Math.round(now - startedAt))
1045
+ };
1046
+ }
1047
+ function reportAttempt(hook, a) {
1048
+ if (!hook) return;
1049
+ try {
1050
+ hook(Object.freeze({ walletType: a.walletType, substrate: a.substrate, outcome: a.outcome, ms: a.ms }));
1051
+ } catch {
1052
+ }
1053
+ }
1054
+
1055
+ // src/modal.ts
1056
+ var import_qrcode_generator = __toESM(require("qrcode-generator"), 1);
1057
+ var CSS2 = `
1058
+ .bwx-ov{position:fixed;inset:0;z-index:2147483000;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;padding:16px;font:15px/1.4 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}
1059
+ .bwx-box{background:#0b0b0b;color:#f4f1e8;border:1px solid #3a3220;border-radius:16px;width:min(360px,100%);padding:20px;box-shadow:0 20px 60px rgba(0,0,0,.5)}
1060
+ .bwx-box h2{margin:0 0 6px;font-size:18px;color:#e8c766}
1061
+ .bwx-box p{margin:0 0 14px;color:#bdb6a3;font-size:14px}
1062
+ .bwx-qr{background:#fff;border-radius:12px;padding:12px;display:flex;justify-content:center}
1063
+ .bwx-qr svg{width:100%;height:auto;max-width:260px}
1064
+ .bwx-code{margin:14px 0 0;text-align:center;font-size:28px;letter-spacing:.3em;color:#e8c766;font-variant-numeric:tabular-nums}
1065
+ .bwx-row{display:flex;gap:8px;margin-top:14px}
1066
+ .bwx-btn{flex:1;border:1px solid #3a3220;background:#15130e;color:#f4f1e8;border-radius:10px;padding:10px 12px;font:inherit;cursor:pointer}
1067
+ .bwx-btn:hover{border-color:#e8c766}
1068
+ a.bwx-btn{display:block;text-align:center;text-decoration:none;box-sizing:border-box}
1069
+ .bwx-primary{background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;border:0;font-weight:700;min-height:48px;padding:13px 12px}
1070
+ .bwx-stack{display:flex;flex-direction:column;gap:8px}
1071
+ .bwx-list{display:flex;flex-direction:column;gap:8px}
1072
+ .bwx-opt{display:flex;align-items:center;gap:10px;text-align:left}
1073
+ .bwx-opt img{width:24px;height:24px;border-radius:6px}
1074
+ .bwx-tag{margin-left:auto;font-size:11px;color:#8d866f;white-space:nowrap}
1075
+ .bwx-opt{min-height:48px;box-sizing:border-box}
1076
+ a.bwx-btn.bwx-opt{display:flex;text-align:left}
1077
+ .bwx-btn:focus{outline:none}
1078
+ .bwx-btn:focus-visible{outline:2px solid #e8c766;outline-offset:2px}
1079
+ .bwx-mk{display:inline-flex;align-items:center;justify-content:center;flex:none;width:28px;height:28px;border-radius:50%;background:#000}
1080
+ .bwx-ico{display:inline-grid;place-items:center;flex:none;width:28px;height:28px;border-radius:8px;background:#2a2518;color:#e8c766;font-weight:700;font-size:13px}
1081
+ .bwx-opt img{width:28px;height:28px;border-radius:8px;flex:none}
1082
+ .bwx-txt{display:flex;flex-direction:column;min-width:0;gap:1px}
1083
+ .bwx-txt b{font-weight:600}
1084
+ .bwx-txt small{font-size:12px;color:#8d866f;font-weight:400}
1085
+ .bwx-primary .bwx-txt small{color:rgba(0,0,0,.68)}
1086
+ .bwx-primary .bwx-tag{color:rgba(0,0,0,.6)}
1087
+ .bwx-sep{font-size:12px;color:#8d866f;margin:6px 2px 0}
1088
+ .bwx-foot{font-size:12px;color:#8d866f;margin:12px 0 0}
1089
+ `;
1090
+ function mount() {
1091
+ const root = document.createElement("div");
1092
+ root.className = "bwx-ov";
1093
+ root.setAttribute("role", "dialog");
1094
+ root.setAttribute("aria-modal", "true");
1095
+ const style = document.createElement("style");
1096
+ style.textContent = CSS2;
1097
+ const box = document.createElement("div");
1098
+ box.className = "bwx-box";
1099
+ root.append(style, box);
1100
+ document.body.appendChild(root);
1101
+ return { root, box, close: () => root.remove() };
1102
+ }
1103
+ function el(tag, cls, text) {
1104
+ const e = document.createElement(tag);
1105
+ if (cls) e.className = cls;
1106
+ if (text != null) e.textContent = text;
1107
+ return e;
1108
+ }
1109
+ function qrSvg(text) {
1110
+ const q = (0, import_qrcode_generator.default)(0, "M");
1111
+ q.addData(text);
1112
+ q.make();
1113
+ return q.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
1114
+ }
1115
+ function isTouchDevice(env = globalThis) {
1116
+ const nav = env.navigator;
1117
+ if (!nav) return false;
1118
+ const ua = nav.userAgent ?? "";
1119
+ if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
1120
+ if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
1121
+ try {
1122
+ return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
1123
+ } catch {
1124
+ return false;
1125
+ }
1126
+ }
1127
+ function pairingModal(o = {}) {
1128
+ const ac = new AbortController();
1129
+ let ui = null;
1130
+ let codeEl = null;
1131
+ const close = () => {
1132
+ ui?.close();
1133
+ ui = null;
1134
+ };
1135
+ ac.signal.addEventListener("abort", close);
1136
+ return {
1137
+ signal: ac.signal,
1138
+ close,
1139
+ onLink(link) {
1140
+ if (typeof document === "undefined") return;
1141
+ close();
1142
+ ui = mount();
1143
+ const app = o.mode === "app" || (o.mode ?? "auto") === "auto" && isTouchDevice();
1144
+ const h = el("h2", "", o.title ?? "Connect bWalletX");
1145
+ const cancel = el("button", "bwx-btn", "Cancel");
1146
+ cancel.onclick = () => {
1147
+ o.onCancel?.();
1148
+ ac.abort();
1149
+ };
1150
+ codeEl = el("div", "bwx-code", "");
1151
+ codeEl.setAttribute("aria-live", "polite");
1152
+ if (app) {
1153
+ const p2 = el("p", "", "Open bWalletX, approve, then come back to this page.");
1154
+ const stack = el("div", "bwx-stack");
1155
+ const open = el("a", "bwx-btn bwx-primary", "Open bWalletX");
1156
+ open.href = link;
1157
+ open.rel = "noopener";
1158
+ const web = el("button", "bwx-btn", "Use web.bwalletx.com instead");
1159
+ web.onclick = () => {
1160
+ void navigator.clipboard?.writeText(link).catch(() => void 0);
1161
+ window.open(WEB_WALLET_URL, "_blank", "noopener");
1162
+ p2.textContent = "Link copied. In web.bwalletx.com tap Connect and paste it, then come back here.";
1163
+ };
1164
+ stack.append(open, web, cancel);
1165
+ ui.box.append(h, p2, codeEl, stack);
1166
+ return;
1167
+ }
1168
+ const p = el("p", "", "Scan with bWalletX on your phone, or paste the link into web.bwalletx.com.");
1169
+ const qr = el("div", "bwx-qr");
1170
+ qr.innerHTML = qrSvg(link);
1171
+ const row = el("div", "bwx-row");
1172
+ const copy = el("button", "bwx-btn", "Copy link");
1173
+ copy.onclick = () => void navigator.clipboard?.writeText(link).then(() => copy.textContent = "Copied");
1174
+ row.append(copy, cancel);
1175
+ ui.box.append(h, p, qr, codeEl, row);
1176
+ },
1177
+ onCode(code) {
1178
+ if (codeEl) {
1179
+ codeEl.textContent = code;
1180
+ const p = ui?.box.querySelector("p");
1181
+ if (p) p.textContent = "Check bWalletX shows the same code, then tap Connect.";
1182
+ }
1183
+ }
1184
+ };
1185
+ }
1186
+ function pickWallet(options, o = {}) {
1187
+ if (typeof document === "undefined") return Promise.resolve(null);
1188
+ return new Promise((resolve) => {
1189
+ const ui = mount();
1190
+ const done = (w) => {
1191
+ ui.close();
1192
+ resolve(w);
1193
+ };
1194
+ ui.box.append(el("h2", "", o.title ?? "Choose a wallet"));
1195
+ if (!options.length) ui.box.append(el("p", "", "No wallet found. Get bWalletX at bwalletx.com."));
1196
+ const list = el("div", "bwx-list");
1197
+ for (const w of options) {
1198
+ const b = el("button", "bwx-btn bwx-opt");
1199
+ if (w.icon) {
1200
+ const img = el("img");
1201
+ img.src = w.icon;
1202
+ img.alt = "";
1203
+ b.append(img);
1204
+ }
1205
+ b.append(el("span", "", w.name));
1206
+ if (w.kind === "handcash") b.append(el("span", "bwx-tag", "until BRC-100"));
1207
+ b.onclick = () => done(w);
1208
+ list.append(b);
1209
+ }
1210
+ const cancel = el("button", "bwx-btn", "Cancel");
1211
+ cancel.onclick = () => done(null);
1212
+ ui.root.onclick = (e) => e.target === ui.root && done(null);
1213
+ ui.box.append(list, el("div", "bwx-row"));
1214
+ ui.box.lastChild.append(cancel);
1215
+ });
1216
+ }
1217
+ function optionRow(w, primary, sub, tag) {
1218
+ const b = el("button", `bwx-btn bwx-opt${primary ? " bwx-primary" : ""}`);
1219
+ b.type = "button";
1220
+ b.dataset.wallet = w.id;
1221
+ if (w.isBwalletX) {
1222
+ const mk = el("span", "bwx-mk");
1223
+ mk.innerHTML = markSvg(18);
1224
+ b.append(mk);
1225
+ } else if (w.icon) {
1226
+ const img = el("img");
1227
+ img.src = w.icon;
1228
+ img.alt = "";
1229
+ b.append(img);
1230
+ } else {
1231
+ b.append(el("span", "bwx-ico", (w.name.trim().charAt(0) || "W").toUpperCase()));
1232
+ }
1233
+ const t = el("span", "bwx-txt");
1234
+ t.append(el("b", "", w.name));
1235
+ if (sub) t.append(el("small", "", sub));
1236
+ b.append(t);
1237
+ if (tag) b.append(el("span", "bwx-tag", tag));
1238
+ return b;
1239
+ }
1240
+ function chooseWallet(options, o = {}) {
1241
+ if (typeof document === "undefined") return Promise.resolve(null);
1242
+ return new Promise((resolve) => {
1243
+ const ui = mount();
1244
+ ui.root.dataset.bwxChooser = "";
1245
+ const done = (w) => {
1246
+ document.removeEventListener("keydown", onKey, true);
1247
+ ui.close();
1248
+ resolve(w);
1249
+ };
1250
+ const onKey = (e) => {
1251
+ if (e.key === "Escape") {
1252
+ e.preventDefault();
1253
+ done(null);
1254
+ }
1255
+ };
1256
+ document.addEventListener("keydown", onKey, true);
1257
+ ui.box.append(el("h2", "", o.title ?? "Choose a wallet"));
1258
+ const list = el("div", "bwx-list");
1259
+ const here = hasBwalletX(options);
1260
+ if (!here) {
1261
+ const get = el("a", "bwx-btn bwx-opt bwx-primary");
1262
+ get.href = o.getUrl ?? GET_URL;
1263
+ get.target = "_blank";
1264
+ get.rel = "noopener";
1265
+ get.dataset.wallet = "get-bwalletx";
1266
+ const mk = el("span", "bwx-mk");
1267
+ mk.innerHTML = markSvg(18);
1268
+ const t = el("span", "bwx-txt");
1269
+ t.append(el("b", "", "Get bWalletX"), el("small", "", "Free. Every feature on this site"));
1270
+ get.append(mk, t);
1271
+ list.append(get);
1272
+ }
1273
+ const ordered = chooserOrder(options);
1274
+ const touch = isTouchDevice();
1275
+ let sep = false;
1276
+ for (const w of ordered) {
1277
+ if (!here && !sep) {
1278
+ list.append(el("p", "bwx-sep", "Or use"));
1279
+ sep = true;
1280
+ }
1281
+ const sub = isPresentBwalletX(w) ? "Recommended" : w.kind === "pairing" ? touch ? "Open the bWalletX app" : "Scan a QR with your phone" : void 0;
1282
+ const tag = w.kind === "handcash" ? "until BRC-100" : !w.isBwalletX && w.kind === "brc100" ? "Fewer features" : void 0;
1283
+ const row2 = optionRow(w, isPresentBwalletX(w), sub, tag);
1284
+ row2.onclick = () => done(w);
1285
+ list.append(row2);
1286
+ }
1287
+ if (o.desktop !== false && !options.some((w) => w.substrate === "json-api" || w.substrate === "cicada")) {
1288
+ if (!here && !sep) list.append(el("p", "bwx-sep", "Or use"));
1289
+ const d = desktopOption();
1290
+ const row2 = optionRow(d, false, "Metanet or BSV Desktop on this computer", "Fewer features");
1291
+ row2.onclick = () => done(d);
1292
+ list.append(row2);
1293
+ }
1294
+ ui.box.append(list);
1295
+ if (otherDetected(options).length || !here) ui.box.append(el("p", "bwx-foot", "Some features need bWalletX."));
1296
+ const row = el("div", "bwx-row");
1297
+ const cancel = el("button", "bwx-btn", "Cancel");
1298
+ cancel.type = "button";
1299
+ cancel.onclick = () => done(null);
1300
+ row.append(cancel);
1301
+ ui.box.append(row);
1302
+ ui.root.onclick = (e) => e.target === ui.root && done(null);
1303
+ list.querySelector("a,button")?.focus();
1304
+ });
1305
+ }
1306
+
1307
+ // src/access-client.ts
1308
+ var import_sdk5 = require("@bsv/sdk");
1309
+
1310
+ // src/access.ts
1311
+ var ACCESS_VERSION = 1;
1312
+ var ACCESS_PROTOCOL = [2, "bwalletx access"];
1313
+ var MAX_HOLDING_PROOFS = 50;
1314
+ var ACCESS_ERROR_STATUS = {
1315
+ not_installed: 424,
1316
+ user_rejected: 403,
1317
+ wrong_account: 409,
1318
+ insufficient_tokens: 403,
1319
+ identity_level_too_low: 403,
1320
+ kyc_required: 403,
1321
+ indexer_unavailable: 503,
1322
+ expired: 401,
1323
+ replay: 401,
1324
+ invalid_signature: 401,
1325
+ bad_request: 400
1326
+ };
1327
+ var AccessError = class extends Error {
1328
+ code;
1329
+ have;
1330
+ need;
1331
+ constructor(code, message, extra = {}) {
1332
+ super(message ?? code);
1333
+ this.name = "AccessError";
1334
+ this.code = code;
1335
+ this.have = extra.have;
1336
+ this.need = extra.need;
1337
+ }
1338
+ toJSON() {
1339
+ const b = { error: this.code, message: this.message };
1340
+ if (this.have !== void 0) b.have = this.have;
1341
+ if (this.need !== void 0) b.need = this.need;
1342
+ return b;
1343
+ }
1344
+ };
1345
+ var PROTOCOL_MEDIA = {
1346
+ "401": { name: "Identity", color: "#22c55e", video: "assets/protocol/401.mp4", poster: "assets/protocol/401.jpg" },
1347
+ "402": { name: "Payment", color: "#3b82f6", video: "assets/protocol/402.mp4", poster: "assets/protocol/402.jpg" },
1348
+ "403": { name: "Access", color: "#ef4444", video: "assets/protocol/403.mp4", poster: "assets/protocol/403.jpg" }
1349
+ };
1350
+ function protocolMediaUrl(kind, which, version = "latest") {
1351
+ return `https://unpkg.com/@bwalletx/connect@${version}/${PROTOCOL_MEDIA[kind][which]}`;
1352
+ }
1353
+ function canonicalJson(v) {
1354
+ if (v === null) return "null";
1355
+ if (typeof v === "string" || typeof v === "boolean") return JSON.stringify(v);
1356
+ if (typeof v === "number") {
1357
+ if (!Number.isFinite(v)) throw new Error("canonicalJson: non-finite number");
1358
+ return JSON.stringify(v);
1359
+ }
1360
+ if (Array.isArray(v)) return `[${v.map((x) => canonicalJson(x)).join(",")}]`;
1361
+ if (typeof v === "object") {
1362
+ const o = v;
1363
+ const keys = Object.keys(o).filter((k) => o[k] !== void 0).sort();
1364
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(o[k])}`).join(",")}}`;
1365
+ }
1366
+ throw new Error(`canonicalJson: unsupported ${typeof v}`);
1367
+ }
1368
+ function challengeBytes(challenge) {
1369
+ return Array.from(new TextEncoder().encode(challenge));
1370
+ }
1371
+ var TOKEN_ID = /^bsv21:[0-9a-f]{64}_\d{1,6}$/;
1372
+ var RAW = /^[1-9]\d{0,38}$/;
1373
+ function normaliseRequirement(r) {
1374
+ if (!r || typeof r !== "object") throw new AccessError("bad_request", "require must be an object");
1375
+ const req = r;
1376
+ const out = {};
1377
+ if (req.token !== void 0) {
1378
+ const t = req.token;
1379
+ if (!t || typeof t !== "object" || typeof t.id !== "string") throw new AccessError("bad_request", "require.token.id is required");
1380
+ const id = t.id.trim().toLowerCase();
1381
+ if (!TOKEN_ID.test(id)) throw new AccessError("bad_request", "require.token.id must be 'bsv21:<txid>_<vout>'");
1382
+ const hasMin = t.min !== void 0;
1383
+ const hasBps = t.minBps !== void 0;
1384
+ if (hasMin === hasBps) throw new AccessError("bad_request", "require.token needs exactly one of min or minBps");
1385
+ out.token = { id };
1386
+ if (hasMin) {
1387
+ if (typeof t.min !== "string" || !RAW.test(t.min)) throw new AccessError("bad_request", "require.token.min must be a positive integer string (raw units)");
1388
+ out.token.min = t.min;
1389
+ } else {
1390
+ if (!Number.isInteger(t.minBps) || t.minBps < 1 || t.minBps > 1e4) {
1391
+ throw new AccessError("bad_request", "require.token.minBps must be an integer 1..10000");
1392
+ }
1393
+ out.token.minBps = t.minBps;
1394
+ }
1395
+ }
1396
+ if (req.identityLevel !== void 0) {
1397
+ if (!Number.isInteger(req.identityLevel) || req.identityLevel < 1 || req.identityLevel > 10) {
1398
+ throw new AccessError("bad_request", "require.identityLevel must be an integer 1..10");
1399
+ }
1400
+ out.identityLevel = req.identityLevel;
1401
+ }
1402
+ if (req.kyc !== void 0) {
1403
+ if (typeof req.kyc !== "boolean") throw new AccessError("bad_request", "require.kyc must be a boolean");
1404
+ if (req.kyc) out.kyc = true;
1405
+ }
1406
+ if (!out.token && !out.identityLevel && !out.kyc) throw new AccessError("bad_request", "require is empty");
1407
+ return out;
1408
+ }
1409
+ function parseAccessChallenge(s) {
1410
+ if (typeof s !== "string" || s.length > 4096) throw new AccessError("bad_request", "challenge must be a string");
1411
+ let c;
1412
+ try {
1413
+ c = JSON.parse(s);
1414
+ } catch {
1415
+ throw new AccessError("bad_request", "challenge is not JSON");
1416
+ }
1417
+ if (!c || typeof c !== "object" || c.v !== 1) throw new AccessError("bad_request", "unsupported challenge version");
1418
+ if (c.kind !== "401" && c.kind !== "402" && c.kind !== "403") throw new AccessError("bad_request", "bad kind");
1419
+ if (typeof c.origin !== "string" || typeof c.nonce !== "string" || !/^[A-Za-z0-9_-]{16,64}$/.test(c.nonce)) {
1420
+ throw new AccessError("bad_request", "bad origin or nonce");
1421
+ }
1422
+ if (!Number.isInteger(c.iat) || !Number.isInteger(c.exp) || c.exp <= c.iat) throw new AccessError("bad_request", "bad iat/exp");
1423
+ if (c.sub !== void 0 && (typeof c.sub !== "string" || !/^0[23][0-9a-f]{64}$/.test(c.sub))) throw new AccessError("bad_request", "bad sub");
1424
+ c.require = normaliseRequirement(c.require);
1425
+ if (canonicalJson(c) !== s) throw new AccessError("bad_request", "challenge is not canonical");
1426
+ return c;
1427
+ }
1428
+ function accessErrorMessage(code, e = {}) {
1429
+ switch (code) {
1430
+ case "not_installed":
1431
+ return "No bWalletX (or other BRC-100 wallet) was found.";
1432
+ case "user_rejected":
1433
+ return "The request was declined in the wallet.";
1434
+ case "wrong_account":
1435
+ return "The wallet is signed in to a different account than this site expects.";
1436
+ case "insufficient_tokens":
1437
+ return `Not enough tokens: holding ${e.have ?? 0}, need ${e.need ?? "?"}.`;
1438
+ case "identity_level_too_low":
1439
+ return `Identity level ${e.have ?? 0} is below the required level ${e.need ?? "?"}.`;
1440
+ case "kyc_required":
1441
+ return "A verified identity (KYC) is required.";
1442
+ case "indexer_unavailable":
1443
+ return "Holdings could not be checked right now. Try again shortly.";
1444
+ case "expired":
1445
+ return "The request expired. Try again.";
1446
+ case "replay":
1447
+ return "That request was already used. Try again.";
1448
+ case "invalid_signature":
1449
+ return "The wallet signature did not verify.";
1450
+ case "bad_request":
1451
+ return "The request was malformed.";
1452
+ }
1453
+ }
1454
+
1455
+ // src/access-client.ts
1456
+ var ONESAT = [0, "onesat"];
1457
+ var LEGACY_ONESAT = [0, "p 1sat"];
847
1458
  function toAccessError(e) {
848
1459
  if (e instanceof AccessError) return e;
849
1460
  const err = e;
@@ -880,7 +1491,7 @@ async function proveHoldings(wallet, challenge, tokenId, basket = "bsv21") {
880
1491
  const counterparty = ci.counterparty ?? "self";
881
1492
  for (const protocolID of ci.protocolID ? [ci.protocolID] : [ONESAT, LEGACY_ONESAT]) {
882
1493
  const { publicKey } = await wallet.getPublicKey({ protocolID, keyID: ci.keyID, counterparty });
883
- const h = import_sdk4.Utils.toHex(import_sdk4.Hash.hash160(import_sdk4.PublicKey.fromString(publicKey).encode(true)));
1494
+ const h = import_sdk5.Utils.toHex(import_sdk5.Hash.hash160(import_sdk5.PublicKey.fromString(publicKey).encode(true)));
884
1495
  if (want && h !== want) continue;
885
1496
  const existing = byKey.get(publicKey);
886
1497
  if (existing) {
@@ -930,7 +1541,7 @@ async function requestAccess(o) {
930
1541
  var SIGNIN_TAG = "bwalletx-signin";
931
1542
  var GET_URL = "https://bwalletx.com/get";
932
1543
  var WEB_WALLET_URL = "https://web.bwalletx.com";
933
- var VERBS = { signin: "Sign in with", continue: "Continue with", connect: "Connect" };
1544
+ var VERBS = { signin: "Sign in with", continue: "Continue with", connect: "Connect", get: "Get", play: "Play with" };
934
1545
  function signinText(label = "signin", brand = "bWalletX") {
935
1546
  return `${VERBS[label] ?? VERBS.signin} ${brand}`;
936
1547
  }
@@ -985,7 +1596,50 @@ button[aria-busy=true]{cursor:wait}
985
1596
  .nf a:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
986
1597
  :host([theme=dark]) .nf,:host-context(.dark) .nf{color:#a1a1aa}
987
1598
  .sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
1599
+ /* pending: one plain line under the button */
1600
+ .note{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center;overflow-wrap:break-word}
1601
+ :host([theme=dark]) .note,:host-context(.dark) .note{color:#a1a1aa}
1602
+ /* "Other wallets" (0.5.6): one small link under the button */
1603
+ .ow{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
1604
+ :host([theme=dark]) .ow,:host-context(.dark) .ow{color:#a1a1aa}
1605
+ .lnk{all:unset;cursor:pointer;color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px}
1606
+ .lnk:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
1607
+ /* error: one line plus Retry */
1608
+ .err{margin:0 0 8px;width:0;min-width:100%;font-size:13px;font-weight:600;line-height:1.4;color:#b91c1c;text-align:center;overflow-wrap:anywhere}
1609
+ :host([theme=dark]) .err,:host-context(.dark) .err{color:#fca5a5}
1610
+ /* signed in: the badge */
1611
+ button.chip{display:inline-flex;align-items:center;justify-content:flex-start;gap:10px;width:auto;max-width:100%;padding:5px 14px 5px 5px;border-radius:999px;
1612
+ background:#0b0b0c;color:#ffd24d;border:1px solid rgba(255,210,77,.55);box-shadow:none;font-size:15px}
1613
+ button.chip:hover{background:#151517;border-color:#ffd24d}
1614
+ button.chip:active{background:#000;transform:none}
1615
+ button.chip:focus-visible{outline:2px solid #ffd24d;outline-offset:3px}
1616
+ :host([theme=light]) button.chip{background:#fff;color:#09090b;border-color:#f59e0b}
1617
+ :host([theme=light]) button.chip:hover{background:#fffbeb}
1618
+ :host([full-width]) button.chip{width:100%}
1619
+ .av{position:relative;flex:none;display:inline-grid;place-items:center;width:30px;height:30px;border-radius:50%;overflow:hidden;
1620
+ background:radial-gradient(circle at 30% 30%,#ffe27a,#f5b800 55%,#b88600);color:#000;font-weight:800;font-size:14px}
1621
+ .av img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
1622
+ .name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:min(52vw,260px)}
1623
+ .dot{flex:none;width:8px;height:8px;border-radius:50%;background:#ffd24d;box-shadow:0 0 0 3px rgba(255,210,77,.22)}
1624
+ :host([size=sm]) button.chip{font-size:13px;padding:3px 12px 3px 3px;gap:8px}
1625
+ :host([size=sm]) .av{width:26px;height:26px}
1626
+ :host([size=icon]) button.chip{width:48px;height:48px;padding:0;justify-content:center}
1627
+ :host([size=icon]) .name,:host([size=icon]) .dot{display:none}
1628
+ :host([size=icon]) .av{width:40px;height:40px}
1629
+ `;
1630
+ var MENU_CSS = `
1631
+ :host{all:initial}
1632
+ .menu{position:fixed;z-index:2147483600;box-sizing:border-box;min-width:200px;padding:6px;border-radius:14px;background:#0b0b0c;color:#ffd24d;
1633
+ border:1px solid rgba(255,210,77,.55);box-shadow:0 12px 32px rgba(0,0,0,.45);font:600 14px/1.3 Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
1634
+ .who{padding:8px 10px 10px;font-size:12px;font-weight:500;color:#e7d9a8;overflow-wrap:anywhere;border-bottom:1px solid rgba(255,210,77,.3);margin-bottom:4px}
1635
+ .item{all:unset;box-sizing:border-box;display:block;width:100%;padding:11px 10px;border-radius:10px;cursor:pointer;color:#ffd24d}
1636
+ .item:hover,.item:focus-visible{background:#ffd24d;color:#000;outline:none}
1637
+ .tip{position:fixed;z-index:2147483600;box-sizing:border-box;padding:8px 12px;border-radius:10px;background:#0b0b0c;color:#e7d9a8;
1638
+ border:1px solid rgba(255,210,77,.4);box-shadow:0 8px 24px rgba(0,0,0,.35);font:500 12px/1.4 Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
988
1639
  `;
1640
+ var PENDING_NOTE = "Sign in: shares only your public identity key, moves no money";
1641
+ var PENDING_LABEL = "Approve in bWalletX\u2026";
1642
+ var PENDING_LABEL_OTHER = "Approve in your wallet\u2026";
989
1643
  function b64url2(bytes) {
990
1644
  let s = "";
991
1645
  for (const b of bytes) s += String.fromCharCode(b);
@@ -1018,14 +1672,15 @@ async function buildAuthorizeUrl(p) {
1018
1672
  async function postJson(url, body) {
1019
1673
  const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
1020
1674
  const data = await res.json().catch(() => ({}));
1021
- if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);
1675
+ if (!res.ok) throw Object.assign(new Error(data.error ?? `HTTP ${res.status}`), { status: res.status });
1022
1676
  return data;
1023
1677
  }
1024
1678
  async function postAccess(url, body) {
1025
1679
  const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
1026
1680
  return res.json().catch(() => ({ error: "bad_request", message: `HTTP ${res.status}` }));
1027
1681
  }
1028
- var OBSERVED = ["size", "theme", "label", "brand", "subtitle", "loading", "disabled", "full-width", "aria-label", "get-url", "web-url"];
1682
+ 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"];
1683
+ var STATES = ["signed-out", "restoring", "pending", "signed-in", "error"];
1029
1684
  function createClass() {
1030
1685
  return class BwalletxSigninElement extends HTMLElement {
1031
1686
  static get observedAttributes() {
@@ -1034,12 +1689,23 @@ function createClass() {
1034
1689
  #root;
1035
1690
  #notFound = false;
1036
1691
  #busy = false;
1692
+ #phase = "signed-out";
1693
+ #account = null;
1694
+ #error = "";
1695
+ #restored = false;
1696
+ #pendingLabel = PENDING_LABEL;
1697
+ #menu = null;
1037
1698
  constructor() {
1038
1699
  super();
1039
1700
  this.#root = this.attachShadow({ mode: "open" });
1040
1701
  }
1041
1702
  connectedCallback() {
1042
1703
  this.#render();
1704
+ void this.#restore();
1705
+ }
1706
+ disconnectedCallback() {
1707
+ this.#menu?.close(false);
1708
+ this.#hideTip();
1043
1709
  }
1044
1710
  attributeChangedCallback() {
1045
1711
  if (this.isConnected) this.#render();
@@ -1051,108 +1717,616 @@ function createClass() {
1051
1717
  get notFound() {
1052
1718
  return this.#notFound;
1053
1719
  }
1720
+ /** The state shown now (the `state` attribute when set, else the element's own). */
1721
+ get state() {
1722
+ const a = this.#attr("state");
1723
+ return a && STATES.includes(a) ? a : this.#phase;
1724
+ }
1725
+ /** The signed-in account, or null. */
1726
+ get account() {
1727
+ if (!this.#controlled) return this.#account;
1728
+ const identityKey = this.#attr("identity-key");
1729
+ return identityKey ? { identityKey, paymail: this.#attr("paymail"), handle: this.#attr("handle"), avatarUrl: this.#attr("avatar-url") } : null;
1730
+ }
1731
+ /** True while the menu is open (tests). */
1732
+ get menuOpen() {
1733
+ return !!this.#menu;
1734
+ }
1054
1735
  focus(opts) {
1055
1736
  this.button?.focus(opts);
1056
1737
  }
1738
+ get #controlled() {
1739
+ return this.hasAttribute("state");
1740
+ }
1741
+ get #stateful() {
1742
+ return !this.hasAttribute("stateless");
1743
+ }
1057
1744
  #attr(name) {
1058
1745
  return this.getAttribute(name);
1059
1746
  }
1747
+ /** Restore the badge from session-url once, with no wallet call. */
1748
+ async #restore() {
1749
+ const url = this.#attr("session-url");
1750
+ if (this.#restored || !url || this.#controlled || !this.#stateful) return;
1751
+ this.#restored = true;
1752
+ this.#set("restoring");
1753
+ try {
1754
+ const r = await fetch(url, { credentials: "include", headers: { accept: "application/json" } });
1755
+ const j = await r.json().catch(() => ({}));
1756
+ if (this.#phase !== "restoring") return;
1757
+ if (j.identityKey) this.#signedIn({ identityKey: j.identityKey, handle: j.handle ?? null, paymail: j.paymail ?? null });
1758
+ else this.#set("signed-out");
1759
+ } catch {
1760
+ if (this.#phase === "restoring") this.#set("signed-out");
1761
+ }
1762
+ }
1763
+ #set(p) {
1764
+ this.#phase = p;
1765
+ if (p !== "signed-in") this.#menu?.close(false);
1766
+ this.#render();
1767
+ }
1768
+ #signedIn(a) {
1769
+ this.#account = a;
1770
+ this.#error = "";
1771
+ this.#set("signed-in");
1772
+ const lookup = this.#attr("lookup-url");
1773
+ if (!a.paymail && lookup !== "off") {
1774
+ void lookupPaymail(a.identityKey, lookup ?? void 0).then((pm) => {
1775
+ if (pm && this.#account?.identityKey === a.identityKey && !this.#account.paymail) {
1776
+ this.#account = { ...this.#account, paymail: pm };
1777
+ this.#render();
1778
+ }
1779
+ });
1780
+ }
1781
+ }
1060
1782
  #render() {
1061
1783
  const size = this.#attr("size") ?? "lg";
1062
1784
  const brand = this.#attr("brand") === "bWallet" ? "bWallet" : "bWalletX";
1063
- const text = signinText(this.#attr("label") ?? "signin", brand);
1785
+ const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
1786
+ const state = this.state;
1787
+ const acct = this.account;
1788
+ if (state === "signed-in" && acct) {
1789
+ this.#hideTip();
1790
+ const name = accountLabel(acct);
1791
+ const avatar = acct.avatarUrl ?? avatarUrlFor(acct.identityKey);
1792
+ const initial = (name.replace(/^\$/, "").charAt(0) || "b").toUpperCase();
1793
+ this.#root.innerHTML = `<style>${SIGNIN_CSS}</style><button type="button" class="chip" part="button chip" aria-haspopup="menu" aria-expanded="${this.#menu ? "true" : "false"}" aria-label="Signed in with bWalletX as ${esc(name)}. Account menu" title="Signed in as ${esc(name)}"><span class="av" part="avatar" aria-hidden="true">${esc(initial)}${avatar ? `<img alt="" src="${esc(avatar)}" referrerpolicy="no-referrer">` : ""}</span><span class="name" part="label">${esc(name)}</span><span class="dot" part="dot" aria-hidden="true"></span></button>`;
1794
+ const img = this.#root.querySelector("img");
1795
+ if (img) img.addEventListener("error", () => img.remove(), { once: true });
1796
+ const chip = this.button;
1797
+ chip.addEventListener("click", () => this.#menu ? this.#menu.close(true) : this.#openMenu());
1798
+ chip.addEventListener("keydown", (e) => {
1799
+ if ((e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") && !this.#menu) {
1800
+ e.preventDefault();
1801
+ this.#openMenu();
1802
+ }
1803
+ });
1804
+ return;
1805
+ }
1806
+ const pending = state === "pending";
1807
+ const restoring = state === "restoring";
1808
+ const error = state === "error";
1809
+ const pendingLabel = this.#controlled ? PENDING_LABEL : this.#pendingLabel;
1810
+ const text = pending ? pendingLabel : error ? "Retry" : signinText(this.#attr("label") ?? "signin", brand);
1064
1811
  const subAttr = this.#attr("subtitle");
1065
1812
  const subDefault = brand === "bWalletX" && (size === "lg" || size === "md") ? "also works with bWallet" : null;
1066
- const subtitle = subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
1067
- const loading = this.hasAttribute("loading") || this.#busy;
1813
+ const subtitle = pending || error ? null : subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
1814
+ const loading = this.hasAttribute("loading") || this.#busy || pending || restoring;
1068
1815
  const disabled = this.hasAttribute("disabled");
1069
1816
  const markPx = size === "icon" ? 24 : size === "sm" ? 15 : size === "md" ? 17 : 20;
1070
- const aria = this.#attr("aria-label") ?? (subtitle && size !== "icon" ? `${text}, ${subtitle}` : text);
1817
+ const aria = pending ? pendingLabel : this.#attr("aria-label") ?? (subtitle && size !== "icon" ? `${text}, ${subtitle}` : text);
1071
1818
  const getUrl = this.#attr("get-url") ?? GET_URL;
1072
1819
  const webUrl = this.#attr("web-url") ?? WEB_WALLET_URL;
1073
- const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
1820
+ const errText = error ? (this.#controlled ? this.#attr("error") : this.#error) || "Sign-in failed" : "";
1074
1821
  const label = size === "icon" ? "" : `<span class="txt" part="text"><b part="label">${esc(text)}</b>${subtitle ? `<small part="subtitle">${esc(subtitle)}</small>` : ""}</span>`;
1075
1822
  const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : markSvg(markPx);
1076
- const nf = this.#notFound ? `<p class="nf" part="not-found" role="status">bWalletX wasn't found in this browser. <a href="${esc(getUrl)}" target="_blank" rel="noopener">Get ${brand}</a> or <a href="${esc(webUrl)}" target="_blank" rel="noopener">use the web wallet</a>.</p>` : "";
1077
- this.#root.innerHTML = `<style>${SIGNIN_CSS}</style><button type="button" part="button" aria-label="${esc(aria)}" title="${esc(subtitle ? `${text} (${subtitle})` : text)}"${disabled ? " disabled" : ""}${loading ? ' aria-busy="true"' : ""}><span class="mark" part="mark">${mark}</span>${label}</button>${loading ? '<span class="sr" role="status">Connecting to bWalletX\u2026</span>' : ""}${nf}`;
1823
+ const otherOff = this.#attr("other-wallets") === "off";
1824
+ const otherLink = '<button type="button" class="lnk" data-act="other" part="other-wallets-link">Other wallets</button>';
1825
+ 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>` : "";
1826
+ const ow = !this.#notFound && !pending && !restoring && this.#otherWalletsShown(size) ? `<p class="ow" part="other-wallets">${otherLink}</p>` : "";
1827
+ const err = error ? `<p class="err" part="error" role="alert">${esc(errText)}</p>` : "";
1828
+ const compact = size === "sm" || size === "icon";
1829
+ const note = pending && !compact ? `<p class="note" part="note">${esc(PENDING_NOTE)}</p>` : "";
1830
+ const status = pending ? `<span class="sr" role="status">${esc(pendingLabel)}</span>` : loading ? '<span class="sr" role="status">Connecting to bWalletX\u2026</span>' : "";
1831
+ 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}`;
1078
1832
  this.button.addEventListener("click", () => void this.#click());
1833
+ for (const l of Array.from(this.#root.querySelectorAll("[data-act=other]"))) l.addEventListener("click", () => void this.otherWallets());
1079
1834
  bindPrewarm(this.button);
1835
+ if (pending && compact) this.#showTip(PENDING_NOTE);
1836
+ else this.#hideTip();
1837
+ }
1838
+ #tip = null;
1839
+ #showTip(text) {
1840
+ const b = this.button;
1841
+ if (!b || typeof document === "undefined") return;
1842
+ if (!this.#tip) {
1843
+ const host = document.createElement("div");
1844
+ host.setAttribute("data-bwalletx-tip", "");
1845
+ const root = host.attachShadow({ mode: "open" });
1846
+ root.innerHTML = `<style>${MENU_CSS}</style><div class="tip" role="note"></div>`;
1847
+ document.body.appendChild(host);
1848
+ this.#tip = host;
1849
+ }
1850
+ const tip2 = this.#tip.shadowRoot.querySelector(".tip");
1851
+ tip2.textContent = text;
1852
+ const r = b.getBoundingClientRect();
1853
+ const vw = window.innerWidth || document.documentElement.clientWidth || 360;
1854
+ const width = Math.min(280, vw - 32);
1855
+ tip2.style.width = `${width}px`;
1856
+ tip2.style.left = `${Math.max(16, Math.min(r.right - width, vw - width - 16))}px`;
1857
+ tip2.style.top = `${r.bottom + 8}px`;
1858
+ }
1859
+ #hideTip() {
1860
+ this.#tip?.remove();
1861
+ this.#tip = null;
1862
+ }
1863
+ #openMenu() {
1864
+ const acct = this.account;
1865
+ const chip = this.button;
1866
+ if (!acct || !chip || typeof document === "undefined") return;
1867
+ const name = accountLabel(acct);
1868
+ const host = document.createElement("div");
1869
+ host.setAttribute("data-bwalletx-menu", "");
1870
+ const root = host.attachShadow({ mode: "open" });
1871
+ const style = document.createElement("style");
1872
+ style.textContent = MENU_CSS;
1873
+ const menu = document.createElement("div");
1874
+ menu.className = "menu";
1875
+ menu.setAttribute("role", "menu");
1876
+ menu.setAttribute("aria-label", "bWalletX account");
1877
+ const who = document.createElement("div");
1878
+ who.className = "who";
1879
+ who.textContent = `Signed in as ${acct.paymail || name}`;
1880
+ const item = (text, run) => {
1881
+ const b = document.createElement("button");
1882
+ b.type = "button";
1883
+ b.className = "item";
1884
+ b.setAttribute("role", "menuitem");
1885
+ b.tabIndex = -1;
1886
+ b.textContent = text;
1887
+ b.addEventListener("click", () => run(b));
1888
+ return b;
1889
+ };
1890
+ const copyWhat = acct.paymail ? "paymail" : "key";
1891
+ const copy = item(`Copy ${copyWhat}`, (b) => {
1892
+ void copyText(acct.paymail || acct.identityKey).then((ok) => {
1893
+ b.textContent = ok ? "Copied" : `Copy ${copyWhat}`;
1894
+ setTimeout(() => close(true), ok ? 600 : 0);
1895
+ });
1896
+ });
1897
+ const out = item("Disconnect", () => {
1898
+ close(false);
1899
+ void this.signOut();
1900
+ });
1901
+ menu.append(who, copy, out);
1902
+ root.append(style, menu);
1903
+ document.body.appendChild(host);
1904
+ const place = () => {
1905
+ const r = chip.getBoundingClientRect();
1906
+ const vw = window.innerWidth || document.documentElement.clientWidth || 360;
1907
+ const vh = window.innerHeight || document.documentElement.clientHeight || 640;
1908
+ const width = Math.min(260, vw - 32);
1909
+ menu.style.width = `${width}px`;
1910
+ const left = Math.max(16, Math.min(r.right - width, vw - width - 16));
1911
+ menu.style.left = `${left}px`;
1912
+ const h = menu.offsetHeight || 150;
1913
+ const below = r.bottom + 8;
1914
+ menu.style.top = `${below + h > vh && r.top - 8 - h > 0 ? r.top - 8 - h : below}px`;
1915
+ };
1916
+ place();
1917
+ const items = [copy, out];
1918
+ const focusAt = (i) => items[(i + items.length) % items.length].focus();
1919
+ const onKey = (e) => {
1920
+ const i = items.indexOf(root.activeElement);
1921
+ if (e.key === "Escape") {
1922
+ e.preventDefault();
1923
+ close(true);
1924
+ } else if (e.key === "ArrowDown") {
1925
+ e.preventDefault();
1926
+ focusAt(i + 1);
1927
+ } else if (e.key === "ArrowUp") {
1928
+ e.preventDefault();
1929
+ focusAt(i - 1);
1930
+ } else if (e.key === "Home") {
1931
+ e.preventDefault();
1932
+ focusAt(0);
1933
+ } else if (e.key === "End") {
1934
+ e.preventDefault();
1935
+ focusAt(items.length - 1);
1936
+ } else if (e.key === "Tab") close(false);
1937
+ };
1938
+ const onDown = (e) => {
1939
+ const path = e.composedPath?.() ?? [];
1940
+ if (!path.includes(host) && !path.includes(this)) close(false);
1941
+ };
1942
+ const onMove = () => close(false);
1943
+ menu.addEventListener("keydown", onKey);
1944
+ document.addEventListener("pointerdown", onDown, true);
1945
+ window.addEventListener("resize", onMove);
1946
+ window.addEventListener("scroll", place, true);
1947
+ const close = (focusChip = false) => {
1948
+ if (this.#menu?.host !== host) return;
1949
+ this.#menu = null;
1950
+ menu.removeEventListener("keydown", onKey);
1951
+ document.removeEventListener("pointerdown", onDown, true);
1952
+ window.removeEventListener("resize", onMove);
1953
+ window.removeEventListener("scroll", place, true);
1954
+ host.remove();
1955
+ this.button?.setAttribute("aria-expanded", "false");
1956
+ if (focusChip) this.button?.focus();
1957
+ };
1958
+ this.#menu = { host, close };
1959
+ chip.setAttribute("aria-expanded", "true");
1960
+ focusAt(0);
1961
+ }
1962
+ /**
1963
+ * Disconnect: emits bwalletx-disconnect (cancelable). Unless a listener cancels it (BwalletXProvider signs out
1964
+ * itself) or the element is driven by `state`, posts logout-url and shows signed out.
1965
+ */
1966
+ async signOut() {
1967
+ this.#menu?.close(false);
1968
+ if (!this.#emit("bwalletx-disconnect", { element: this, account: this.account }, true)) return;
1969
+ if (this.#controlled) return;
1970
+ const url = this.#attr("logout-url");
1971
+ if (url) await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
1972
+ this.#account = null;
1973
+ this.#set("signed-out");
1974
+ this.#emit("bwalletx-signed-out");
1975
+ this.button?.focus();
1080
1976
  }
1081
1977
  #emit(type, detail, cancelable = false) {
1082
1978
  return this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable }));
1083
1979
  }
1084
- #setBusy(on) {
1085
- this.#busy = on;
1086
- this.#render();
1980
+ /** "Other wallets" is offered under the button: other-wallets on, or auto at lg/md when this element signs in. */
1981
+ #otherWalletsShown(size) {
1982
+ const a = this.#attr("other-wallets");
1983
+ if (a === "off") return false;
1984
+ if (a === "on") return true;
1985
+ return (size === "lg" || size === "md") && (this.#controlled || !!this.#attr("challenge-url"));
1087
1986
  }
1088
- async #click() {
1089
- if (this.hasAttribute("disabled") || this.hasAttribute("loading") || this.#busy) return;
1090
- if (!this.#emit("bwalletx-signin", { element: this }, true)) return;
1091
- const href = this.#attr("href");
1092
- const authorizeUrl = this.#attr("authorize-url");
1093
- if (authorizeUrl) {
1094
- location.assign(await buildAuthorizeUrl({ authorizeUrl, clientId: this.#attr("client-id"), redirectUri: this.#attr("redirect-uri"), scope: this.#attr("scope") }));
1095
- return;
1987
+ /** Open the wallet chooser (the "Other wallets" link): phone QR, other browser wallets, desktop wallets. */
1988
+ otherWallets() {
1989
+ return this.#click("chooser");
1990
+ }
1991
+ #attempt(option, outcome, started) {
1992
+ this.#emit("bwalletx-attempt", attemptFor(option, outcome, started));
1993
+ }
1994
+ /**
1995
+ * Which wallet: bWalletX by its own marker goes straight to its sheet; otherwise the chooser ("Get bWalletX" first)
1996
+ * when another wallet is here or the user asked; otherwise 'not-found'. null: the chooser was closed.
1997
+ */
1998
+ async #choose(mode, ms, pairing) {
1999
+ if (mode !== "chooser") {
2000
+ const f = await discoverBwalletX(ms);
2001
+ if (f) {
2002
+ const substrate = f.rdns === RDNS_EXTENSION ? "extension" : f.rdns === RDNS_MOBILE ? "in-app" : "xdm";
2003
+ return brc100Option(f.wallet, { id: f.rdns ?? "bwalletx-xdm", rdns: f.rdns ?? void 0, name: "bWalletX", isBwalletX: true, substrate });
2004
+ }
1096
2005
  }
1097
- if (href) {
1098
- location.assign(href);
1099
- return;
2006
+ const list = await discoverWallets({ ms: 50, pairing: () => pairing });
2007
+ if (mode !== "chooser" && !hasBwalletX(list) && !otherDetected(list).length) return "not-found";
2008
+ return chooseWallet(list, { getUrl: this.#attr("get-url") ?? void 0 });
2009
+ }
2010
+ async #click(mode = "signin") {
2011
+ if (this.hasAttribute("disabled") || this.hasAttribute("loading") || this.#busy || this.state === "pending") return;
2012
+ if (!this.#emit("bwalletx-signin", { element: this, mode }, true)) return;
2013
+ if (mode === "signin") {
2014
+ const href = this.#attr("href");
2015
+ const authorizeUrl = this.#attr("authorize-url");
2016
+ if (authorizeUrl) {
2017
+ location.assign(await buildAuthorizeUrl({ authorizeUrl, clientId: this.#attr("client-id"), redirectUri: this.#attr("redirect-uri"), scope: this.#attr("scope") }));
2018
+ return;
2019
+ }
2020
+ if (href) {
2021
+ location.assign(href);
2022
+ return;
2023
+ }
1100
2024
  }
1101
2025
  const challengeUrl = this.#attr("challenge-url");
1102
2026
  if (!challengeUrl) return;
2027
+ const own = this.#stateful && !this.#controlled;
2028
+ const prev = this.#phase === "signed-in" ? "signed-in" : "signed-out";
2029
+ const started = Date.now();
2030
+ const pairing = pairingModal();
2031
+ let option = null;
1103
2032
  this.#notFound = false;
1104
- this.#setBusy(true);
2033
+ this.#busy = true;
2034
+ this.#pendingLabel = PENDING_LABEL;
2035
+ if (own) this.#set("pending");
2036
+ else this.#render();
1105
2037
  try {
1106
2038
  const ms = Number(this.#attr("discover-ms")) || DEFAULT_DISCOVER_MS;
1107
- const found = await discoverBwalletX(ms);
1108
- if (!found) {
2039
+ const picked = await this.#choose(mode, ms, pairing);
2040
+ if (picked === "not-found") {
1109
2041
  this.#notFound = true;
2042
+ if (own) this.#phase = "signed-out";
2043
+ this.#attempt(null, "not-found", started);
1110
2044
  this.#emit("bwalletx-not-found");
1111
2045
  if (this.#attr("require-token")) this.#emit("bwalletx-access-denied", { code: "not_installed", message: "No wallet found" });
1112
2046
  return;
1113
2047
  }
2048
+ if (!picked) {
2049
+ if (own) this.#phase = prev;
2050
+ this.#attempt(null, "cancelled", started);
2051
+ return;
2052
+ }
2053
+ option = picked;
2054
+ if (!option.isBwalletX) {
2055
+ this.#pendingLabel = PENDING_LABEL_OTHER;
2056
+ if (own) this.#render();
2057
+ }
2058
+ const cw = await option.connect({ identity: false });
2059
+ const wallet = cw.wallet;
2060
+ if (!wallet) throw Object.assign(new Error("This wallet cannot sign in here"), { code: "NOT_SUPPORTED" });
2061
+ const sub = substrateOf(option);
2062
+ const method = sub === "extension" ? "extension" : sub === "in-app" || sub === "xdm" ? "in-app" : sub === "pairing" ? "pairing" : "wallet";
1114
2063
  const requireToken = this.#attr("require-token");
1115
2064
  if (requireToken) {
2065
+ if (own) this.#phase = "signed-out";
1116
2066
  const verifyUrl2 = this.#attr("verify-url");
1117
2067
  const min = this.#attr("min");
1118
2068
  const minBps = this.#attr("min-bps");
1119
2069
  const hint = { token: { id: requireToken, ...minBps ? { minBps: Number(minBps) } : { min: min ?? "1" } } };
1120
2070
  try {
1121
2071
  const a = await requestAccess({
1122
- wallet: found.wallet,
2072
+ wallet,
1123
2073
  challenge: (identityKey) => postJson(challengeUrl, { identityKey, require: hint }),
1124
2074
  verify: verifyUrl2 ? (response2) => postAccess(verifyUrl2, response2) : void 0
1125
2075
  });
2076
+ this.#attempt(option, "signed-in", started);
1126
2077
  this.#emit("bwalletx-access-granted", { identityKey: a.response.identityKey, response: a.verified });
1127
2078
  } catch (e) {
1128
2079
  const err = e instanceof AccessError ? e : new AccessError("bad_request", String(e));
2080
+ this.#attempt(option, outcomeOf(e), started);
1129
2081
  this.#emit("bwalletx-access-denied", { code: err.code, message: err.message, have: err.have, need: err.need });
1130
2082
  }
1131
2083
  return;
1132
2084
  }
1133
- const r = await signInWithBwalletX({ wallet: found.wallet, challenge: (identityKey) => postJson(challengeUrl, { identityKey }) });
1134
2085
  const verifyUrl = this.#attr("verify-url");
1135
- const response = verifyUrl ? await postJson(verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature }) : null;
1136
- this.#emit("bwalletx-signed-in", { identityKey: r.identityKey, method: found.method, nonce: r.nonce, signature: r.signature, response });
2086
+ const { result: r, verified } = await signInAndVerify({
2087
+ wallet,
2088
+ identityKeyHint: verifyUrl ? rememberedIdentity() : void 0,
2089
+ challenge: (identityKey) => postJson(challengeUrl, identityKey ? { identityKey } : {}),
2090
+ verify: async (res) => verifyUrl ? await postJson(verifyUrl, { identityKey: res.identityKey, nonce: res.nonce, signature: res.signature }) : {}
2091
+ });
2092
+ if (verifyUrl) rememberIdentity(r.identityKey);
2093
+ const response = verifyUrl ? verified : null;
2094
+ if (own) this.#signedIn({ identityKey: r.identityKey, handle: response?.handle ?? null, paymail: response?.paymail ?? null });
2095
+ this.#attempt(option, "signed-in", started);
2096
+ this.#emit("bwalletx-signed-in", { identityKey: r.identityKey, method, nonce: r.nonce, signature: r.signature, response });
1137
2097
  } catch (error) {
1138
- this.#emit("bwalletx-error", { error });
2098
+ const outcome = outcomeOf(error);
2099
+ this.#attempt(option, outcome, started);
2100
+ const closedQr = outcome === "cancelled" && option?.kind === "pairing";
2101
+ if (own) {
2102
+ if (closedQr) this.#phase = prev;
2103
+ else {
2104
+ this.#error = signInErrorText(error);
2105
+ this.#phase = "error";
2106
+ }
2107
+ }
2108
+ if (!closedQr) this.#emit("bwalletx-error", { error });
2109
+ } finally {
2110
+ this.#busy = false;
2111
+ try {
2112
+ pairing.close();
2113
+ } catch {
2114
+ }
2115
+ this.#render();
2116
+ }
2117
+ }
2118
+ };
2119
+ }
2120
+ function defineBwalletxSignin(tag = SIGNIN_TAG) {
2121
+ if (typeof window === "undefined" || typeof customElements === "undefined") return void 0;
2122
+ const existing = customElements.get(tag);
2123
+ if (existing) return existing;
2124
+ const C = createClass();
2125
+ customElements.define(tag, C);
2126
+ return C;
2127
+ }
2128
+
2129
+ // src/wallet-button.ts
2130
+ var BUTTON_TAG = "bwalletx-button";
2131
+ var CONNECTED_KEY = "bwx:connected";
2132
+ function dropLegacyConnectedKey() {
2133
+ try {
2134
+ const had = globalThis.localStorage?.getItem(CONNECTED_KEY) != null;
2135
+ if (had) globalThis.localStorage?.removeItem(CONNECTED_KEY);
2136
+ return had;
2137
+ } catch {
2138
+ return false;
2139
+ }
2140
+ }
2141
+ var LOOKUP = "https://pay.bwallet.space/api/paymail/lookup?key=";
2142
+ var LEGACY = '[data-bwx-connect], .nav-connect, .nav-acct, a[href^="https://bwalletx.com/get"], a[href^="https://www.bwalletx.com/get"]';
2143
+ function getUrlFor(ua, touchMac = false) {
2144
+ if (/iPhone|iPad|iPod/i.test(ua) || touchMac && /Macintosh/.test(ua)) return "https://bwalletx.com/iphone";
2145
+ if (/Android/i.test(ua)) return "https://bwalletx.com/android";
2146
+ if (/Chrome\//.test(ua) && !/Edg\/|OPR\//.test(ua)) return "https://bwalletx.com/extension";
2147
+ return "https://bwalletx.com/get";
2148
+ }
2149
+ function stateFor(o) {
2150
+ if (o.connected) return "connected";
2151
+ if (o.found === null) return o.phone ? "available" : "checking";
2152
+ return o.found || o.phone ? "available" : "none";
2153
+ }
2154
+ function handleLabel(key, paymail) {
2155
+ if (paymail && /@bwalletx\.com$/i.test(paymail)) return "$" + paymail.split("@")[0];
2156
+ if (paymail) return paymail;
2157
+ return key.slice(0, 6) + "\u2026" + key.slice(-4);
2158
+ }
2159
+ var CHIP_CSS = `
2160
+ a.b,button.b{all:unset;box-sizing:border-box}
2161
+ .chip{background:#0b0b0c;color:#ffd24d;border:1px solid rgba(255,210,77,.45);box-shadow:none}
2162
+ .chip:hover{background:#151517;border-color:#ffd24d}
2163
+ .chip .dot{width:8px;height:8px;border-radius:50%;background:#34d399;flex:none}
2164
+ .txt b{overflow:hidden;text-overflow:ellipsis;max-width:min(40vw,220px)}
2165
+ :host(:not([size])) button,:host(:not([size])) a.btn,:host([size=sm]) a.btn{padding:6px 12px 6px 6px;border-radius:8px;font-size:13px;gap:8px;min-height:36px}
2166
+ :host(:not([size])) .mark,:host([size=sm]) a.btn .mark{width:22px;height:22px}
2167
+ a.btn{all:unset;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:12px;cursor:pointer;
2168
+ padding:16px 20px;border-radius:12px;font-size:16px;font-weight:700;line-height:1.2;background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;
2169
+ box-shadow:0 10px 15px -3px rgba(245,158,11,.2),0 4px 6px -4px rgba(245,158,11,.2)}
2170
+ a.btn:hover{background:linear-gradient(180deg,#fde68a,#fbbf24)}
2171
+ a.btn:focus-visible{outline:2px solid #b45309;outline-offset:3px}
2172
+ :host([size=md]) a.btn{padding:10px 16px;border-radius:10px;font-size:15px;gap:10px}
2173
+ `;
2174
+ var live = /* @__PURE__ */ new Set();
2175
+ function createClass2() {
2176
+ return class BwalletxButtonElement extends HTMLElement {
2177
+ #root;
2178
+ #state = "checking";
2179
+ #key = null;
2180
+ #paymail = null;
2181
+ #duplicate = false;
2182
+ constructor() {
2183
+ super();
2184
+ this.#root = this.attachShadow({ mode: "open" });
2185
+ }
2186
+ get state() {
2187
+ return this.#state;
2188
+ }
2189
+ get identityKey() {
2190
+ return this.#key;
2191
+ }
2192
+ connectedCallback() {
2193
+ if (live.size && ![...live].includes(this)) {
2194
+ this.#duplicate = true;
2195
+ this.hidden = true;
2196
+ console.warn("[bwalletx] A second <bwalletx-button> is on this page; it is hidden. Use one per page.");
2197
+ return;
2198
+ }
2199
+ live.add(this);
2200
+ this.#hideLegacy();
2201
+ dropLegacyConnectedKey();
2202
+ const phone = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
2203
+ this.#set(stateFor({ connected: false, found: null, phone }));
2204
+ const ms = Number(this.getAttribute("discover-ms")) || 800;
2205
+ void discoverBwalletX(ms).then((f) => {
2206
+ if (!this.isConnected) return;
2207
+ if (this.#state !== "connected" && this.#state !== "connecting") this.#set(stateFor({ connected: false, found: !!f, phone }));
2208
+ }).catch(() => {
2209
+ if (this.#state === "checking") this.#set("none");
2210
+ });
2211
+ void this.#restoreSession();
2212
+ }
2213
+ /** A live session your server reports (session-url): the chip, with no wallet call. */
2214
+ async #restoreSession() {
2215
+ const url = this.getAttribute("session-url");
2216
+ if (!url) return;
2217
+ try {
2218
+ const r = await fetch(url, { credentials: "include", headers: { accept: "application/json" } });
2219
+ const j = await r.json().catch(() => ({}));
2220
+ if (j.identityKey && this.isConnected && this.#state !== "connected" && this.#state !== "connecting") {
2221
+ this.#connected(j.identityKey);
2222
+ if (j.paymail) {
2223
+ this.#paymail = j.paymail;
2224
+ this.#render();
2225
+ }
2226
+ }
2227
+ } catch {
2228
+ }
2229
+ }
2230
+ disconnectedCallback() {
2231
+ if (!this.#duplicate) live.delete(this);
2232
+ }
2233
+ #hideLegacy() {
2234
+ const scope = this.closest("header, nav") ?? document;
2235
+ for (const el2 of Array.from(scope.querySelectorAll(LEGACY))) {
2236
+ if (el2 === this || el2.contains(this)) continue;
2237
+ el2.hidden = true;
2238
+ el2.style.setProperty("display", "none", "important");
2239
+ console.warn("[bwalletx] Hid a second wallet control next to <bwalletx-button>:", el2, ". Use only <bwalletx-button>; don't add your own Get or Connect links.");
2240
+ }
2241
+ }
2242
+ #emit(type, detail) {
2243
+ this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
2244
+ }
2245
+ #set(s) {
2246
+ this.#state = s;
2247
+ this.setAttribute("state", s);
2248
+ this.#render();
2249
+ }
2250
+ #connected(key) {
2251
+ this.#key = key;
2252
+ this.#set("connected");
2253
+ this.#emit("bwalletx-connected", { identityKey: key });
2254
+ const lookup = this.getAttribute("lookup-url");
2255
+ if (lookup === "off") return;
2256
+ void fetch((lookup ?? LOOKUP) + encodeURIComponent(key)).then((r) => r.ok ? r.json() : null).then((j) => {
2257
+ if (j && typeof j.paymail === "string" && this.#key === key) {
2258
+ this.#paymail = j.paymail;
2259
+ this.#render();
2260
+ }
2261
+ }).catch(() => void 0);
2262
+ }
2263
+ async connect() {
2264
+ if (this.#state === "connecting" || this.#state === "connected") return;
2265
+ const modal = pairingModal({ title: "Connect bWalletX" });
2266
+ this.#set("connecting");
2267
+ try {
2268
+ const { wallet } = await connectBwalletX({ pairing: modal });
2269
+ const { publicKey } = await wallet.getPublicKey({ identityKey: true });
2270
+ this.#connected(publicKey);
2271
+ } catch (e) {
2272
+ if (e?.code === "NOT_FOUND") this.#set("none");
2273
+ else {
2274
+ this.#set("available");
2275
+ if (e?.name !== "AbortError") console.warn("[bwalletx] connect:", e);
2276
+ }
1139
2277
  } finally {
1140
- this.#setBusy(false);
2278
+ try {
2279
+ modal.close();
2280
+ } catch {
2281
+ }
2282
+ }
2283
+ }
2284
+ disconnect() {
2285
+ try {
2286
+ clearPairing();
2287
+ } catch {
2288
+ }
2289
+ try {
2290
+ window.postMessage({ type: "bwallet:disconnect" }, location.origin);
2291
+ } catch {
2292
+ }
2293
+ this.#key = null;
2294
+ this.#paymail = null;
2295
+ dropLegacyConnectedKey();
2296
+ this.#set("available");
2297
+ this.#emit("bwalletx-disconnected");
2298
+ }
2299
+ #render() {
2300
+ const esc = (s2) => s2.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
2301
+ const size = this.getAttribute("size") ?? "sm";
2302
+ const px = size === "lg" ? 20 : size === "md" ? 17 : 15;
2303
+ const mark = `<span class="mark" part="mark">${markSvg(px)}</span>`;
2304
+ const css = `<style>${SIGNIN_CSS}${CHIP_CSS}</style>`;
2305
+ const s = this.#state;
2306
+ if (s === "none") {
2307
+ const href = this.getAttribute("get-url") ?? getUrlFor(navigator.userAgent, (navigator.maxTouchPoints ?? 0) > 1);
2308
+ this.#root.innerHTML = `${css}<a class="btn" part="button" href="${esc(href)}" target="_blank" rel="noopener">${mark}<span class="txt" part="text"><b part="label">Get bWalletX</b></span></a>`;
2309
+ return;
2310
+ }
2311
+ if (s === "connected" && this.#key) {
2312
+ const name = handleLabel(this.#key, this.#paymail);
2313
+ this.#root.innerHTML = `${css}<button type="button" class="chip" part="button chip" title="Connected to bWalletX. Click to disconnect." aria-label="Connected as ${esc(name)}. Disconnect">${mark}<span class="txt" part="text"><b part="label">${esc(name)}</b></span><span class="dot" aria-hidden="true"></span></button>`;
2314
+ this.#root.querySelector("button").onclick = () => this.disconnect();
2315
+ return;
1141
2316
  }
2317
+ const busy = s === "connecting" || s === "checking";
2318
+ this.#root.innerHTML = `${css}<button type="button" part="button"${busy ? ' aria-busy="true"' : ""}>${busy ? '<span class="mark" part="mark"><span class="spin" aria-hidden="true"></span></span>' : mark}<span class="txt" part="text"><b part="label">${s === "connecting" ? "Connecting\u2026" : "Connect bWalletX"}</b></span></button>`;
2319
+ this.#root.querySelector("button").onclick = () => void this.connect();
1142
2320
  }
1143
2321
  };
1144
2322
  }
1145
- function defineBwalletxSignin(tag = SIGNIN_TAG) {
1146
- if (typeof window === "undefined" || typeof customElements === "undefined") return void 0;
1147
- const existing = customElements.get(tag);
1148
- if (existing) return existing;
1149
- const C = createClass();
1150
- customElements.define(tag, C);
1151
- return C;
2323
+ function defineBwalletxButton(tag = BUTTON_TAG) {
2324
+ if (typeof customElements === "undefined" || customElements.get(tag)) return;
2325
+ customElements.define(tag, createClass2());
1152
2326
  }
1153
2327
 
1154
2328
  // src/pay.ts
1155
- var import_sdk5 = require("@bsv/sdk");
2329
+ var import_sdk6 = require("@bsv/sdk");
1156
2330
  var BRC29_PROTOCOL = [2, "3241645161d8"];
1157
2331
  var PaymentError = class extends Error {
1158
2332
  constructor(code, message, maybeSent = false, cause) {
@@ -1172,13 +2346,13 @@ var isPaymail = (s) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
1172
2346
  function randomB64(n) {
1173
2347
  const b = new Uint8Array(n);
1174
2348
  globalThis.crypto.getRandomValues(b);
1175
- return import_sdk5.Utils.toBase64(Array.from(b));
2349
+ return import_sdk6.Utils.toBase64(Array.from(b));
1176
2350
  }
1177
2351
  function opReturn(text) {
1178
- const d = import_sdk5.Utils.toArray(text, "utf8");
2352
+ const d = import_sdk6.Utils.toArray(text, "utf8");
1179
2353
  const len = d.length;
1180
2354
  const push = len < 76 ? [len] : len <= 255 ? [76, len] : [77, len & 255, len >> 8];
1181
- return import_sdk5.Utils.toHex([0, 106, ...push, ...d]);
2355
+ return import_sdk6.Utils.toHex([0, 106, ...push, ...d]);
1182
2356
  }
1183
2357
  var clip = (s, n) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
1184
2358
  var describe = (s) => clip(s.length < 5 ? `${s} payment`.trim() : s, 50);
@@ -1274,7 +2448,7 @@ async function pay(source, req, o = {}) {
1274
2448
  } catch (e) {
1275
2449
  throw new PaymentError("WALLET_ERROR", `The wallet could not derive the payment key: ${e.message}`, false, e);
1276
2450
  }
1277
- outputs = [{ script: new import_sdk5.P2PKH().lock(import_sdk5.PublicKey.fromString(pk).toHash()).toHex(), satoshis: sats, description: "payment" }];
2451
+ outputs = [{ script: new import_sdk6.P2PKH().lock(import_sdk6.PublicKey.fromString(pk).toHash()).toHex(), satoshis: sats, description: "payment" }];
1278
2452
  remittance = { senderIdentityKey: me, derivationPrefix, derivationSuffix, outputIndex: 0 };
1279
2453
  } else if (isPaymail(to)) {
1280
2454
  const r2 = await (o.resolvePaymail ?? ((p, s) => resolvePaymailP2P(p, s, o.fetch ?? fetch)))(to, sats);
@@ -1282,7 +2456,7 @@ async function pay(source, req, o = {}) {
1282
2456
  submit = r2.submit;
1283
2457
  } else {
1284
2458
  try {
1285
- outputs = [{ script: new import_sdk5.P2PKH().lock(to).toHex(), satoshis: sats, description: "payment" }];
2459
+ outputs = [{ script: new import_sdk6.P2PKH().lock(to).toHex(), satoshis: sats, description: "payment" }];
1286
2460
  } catch {
1287
2461
  throw new PaymentError("BAD_REQUEST", `Not a paymail, address or identity key: ${to}`);
1288
2462
  }
@@ -1292,7 +2466,7 @@ async function pay(source, req, o = {}) {
1292
2466
  if (!r.txid) throw new PaymentError("WALLET_ERROR", "The wallet did not return a txid", true);
1293
2467
  const tx = r.tx ? Array.from(r.tx) : void 0;
1294
2468
  if (submit && tx) {
1295
- await submit(import_sdk5.Transaction.fromAtomicBEEF(tx).toHex()).catch(() => {
2469
+ await submit(import_sdk6.Transaction.fromAtomicBEEF(tx).toHex()).catch(() => {
1296
2470
  });
1297
2471
  }
1298
2472
  return { txid: r.txid, tx, sats, outputs, remittance };
@@ -1312,255 +2486,6 @@ async function buyToken(source, req) {
1312
2486
  return res;
1313
2487
  }
1314
2488
 
1315
- // src/wallets.ts
1316
- var import_sdk6 = require("@bsv/sdk");
1317
- async function brc100Connected(wallet, o) {
1318
- await wallet.waitForAuthentication({}).catch(() => {
1319
- });
1320
- const { publicKey } = await wallet.getPublicKey({ identityKey: true });
1321
- return { wallet, payer: wallet, identityKey: publicKey, optionId: o.id, kind: o.kind };
1322
- }
1323
- function brc100Option(wallet, info) {
1324
- const opt = {
1325
- id: info.id,
1326
- name: info.name,
1327
- icon: info.icon,
1328
- rdns: info.rdns,
1329
- kind: "brc100",
1330
- isBwalletX: info.isBwalletX ?? false,
1331
- connect: () => brc100Connected(wallet, opt)
1332
- };
1333
- return opt;
1334
- }
1335
- function pairingOption(pairing) {
1336
- const opt = {
1337
- id: "bwalletx-pairing",
1338
- name: "bWalletX on your phone",
1339
- kind: "pairing",
1340
- isBwalletX: true,
1341
- async connect() {
1342
- const p = typeof pairing === "function" ? pairing() : pairing;
1343
- const w = await restorePairing(p) ?? await pairBwalletX(p);
1344
- return brc100Connected(w, opt);
1345
- }
1346
- };
1347
- return opt;
1348
- }
1349
- function handcashAdapter(o) {
1350
- const opt = {
1351
- id: "handcash",
1352
- name: "HandCash",
1353
- icon: o.icon,
1354
- kind: "handcash",
1355
- isBwalletX: false,
1356
- async connect() {
1357
- const p = await o.login();
1358
- return {
1359
- wallet: null,
1360
- payer: o.pay ? { pay: o.pay } : null,
1361
- identityKey: p.identityKey ?? null,
1362
- handle: p.handle,
1363
- paymail: p.paymail ?? `${p.handle.replace(/^\$/, "")}@handcash.io`,
1364
- optionId: opt.id,
1365
- kind: "handcash",
1366
- transitional: true
1367
- };
1368
- }
1369
- };
1370
- return opt;
1371
- }
1372
- var NAMES = { [RDNS_EXTENSION]: "bWalletX", "space.bwallet.mobile": "bWalletX" };
1373
- async function probe(substrate, ms) {
1374
- try {
1375
- const w = new import_sdk6.WalletClient(substrate);
1376
- const ok = await Promise.race([w.isAuthenticated({}).then(() => true), new Promise((r) => setTimeout(() => r(false), ms))]);
1377
- return ok ? w : null;
1378
- } catch {
1379
- return null;
1380
- }
1381
- }
1382
- async function discoverWallets(o = {}) {
1383
- const out = [];
1384
- const ms = o.ms ?? 300;
1385
- if (typeof window !== "undefined") {
1386
- const seen = /* @__PURE__ */ new Map();
1387
- const onAnnounce = (e) => {
1388
- const d = e.detail;
1389
- if (!d?.wallet) return;
1390
- const rdns = d.info?.rdns ?? d.info?.uuid ?? `wallet-${seen.size}`;
1391
- if (seen.has(rdns)) return;
1392
- const isB = BWALLETX_RDNS.includes(rdns);
1393
- seen.set(rdns, brc100Option(d.wallet, { id: rdns, rdns, name: NAMES[rdns] ?? d.info?.name ?? rdns, icon: d.info?.icon, isBwalletX: isB }));
1394
- };
1395
- window.addEventListener("brc100:announceWallet", onAnnounce);
1396
- window.dispatchEvent(new Event("brc100:requestWallet"));
1397
- const local = o.probeLocal ? Promise.all([probe("json-api", ms), probe("Cicada", ms)]) : Promise.resolve([null, null]);
1398
- await new Promise((r) => setTimeout(r, seen.size > 0 || window.CWI ? Math.min(ms, 50) : ms));
1399
- window.removeEventListener("brc100:announceWallet", onAnnounce);
1400
- out.push(...seen.values());
1401
- const cwi = window.CWI;
1402
- if (cwi && ![...seen.values()].some((x) => x.rdns === "space.bwallet.mobile")) {
1403
- out.push(brc100Option(cwi, { id: "window.CWI", name: "This app\u2019s wallet", isBwalletX: false }));
1404
- }
1405
- const [json, cicada] = await local;
1406
- if (json) out.push(brc100Option(json, { id: "localhost:3321", name: "Desktop wallet (localhost:3321)" }));
1407
- if (cicada) out.push(brc100Option(cicada, { id: "localhost:3301", name: "Desktop wallet (localhost:3301)" }));
1408
- }
1409
- if (o.pairing) out.push(pairingOption(o.pairing));
1410
- out.push(...o.extra ?? []);
1411
- const rank = (w) => w.isBwalletX ? 0 : w.kind === "handcash" ? 2 : 1;
1412
- return out.map((w, i) => ({ w, i })).sort((a, b) => rank(a.w) - rank(b.w) || a.i - b.i).map((x) => x.w);
1413
- }
1414
-
1415
- // src/modal.ts
1416
- var import_qrcode_generator = __toESM(require("qrcode-generator"), 1);
1417
- var CSS2 = `
1418
- .bwx-ov{position:fixed;inset:0;z-index:2147483000;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;padding:16px;font:15px/1.4 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}
1419
- .bwx-box{background:#0b0b0b;color:#f4f1e8;border:1px solid #3a3220;border-radius:16px;width:min(360px,100%);padding:20px;box-shadow:0 20px 60px rgba(0,0,0,.5)}
1420
- .bwx-box h2{margin:0 0 6px;font-size:18px;color:#e8c766}
1421
- .bwx-box p{margin:0 0 14px;color:#bdb6a3;font-size:14px}
1422
- .bwx-qr{background:#fff;border-radius:12px;padding:12px;display:flex;justify-content:center}
1423
- .bwx-qr svg{width:100%;height:auto;max-width:260px}
1424
- .bwx-code{margin:14px 0 0;text-align:center;font-size:28px;letter-spacing:.3em;color:#e8c766;font-variant-numeric:tabular-nums}
1425
- .bwx-row{display:flex;gap:8px;margin-top:14px}
1426
- .bwx-btn{flex:1;border:1px solid #3a3220;background:#15130e;color:#f4f1e8;border-radius:10px;padding:10px 12px;font:inherit;cursor:pointer}
1427
- .bwx-btn:hover{border-color:#e8c766}
1428
- a.bwx-btn{display:block;text-align:center;text-decoration:none;box-sizing:border-box}
1429
- .bwx-primary{background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;border:0;font-weight:700;min-height:48px;padding:13px 12px}
1430
- .bwx-stack{display:flex;flex-direction:column;gap:8px}
1431
- .bwx-list{display:flex;flex-direction:column;gap:8px}
1432
- .bwx-opt{display:flex;align-items:center;gap:10px;text-align:left}
1433
- .bwx-opt img{width:24px;height:24px;border-radius:6px}
1434
- .bwx-tag{margin-left:auto;font-size:11px;color:#8d866f}
1435
- `;
1436
- function mount() {
1437
- const root = document.createElement("div");
1438
- root.className = "bwx-ov";
1439
- root.setAttribute("role", "dialog");
1440
- root.setAttribute("aria-modal", "true");
1441
- const style = document.createElement("style");
1442
- style.textContent = CSS2;
1443
- const box = document.createElement("div");
1444
- box.className = "bwx-box";
1445
- root.append(style, box);
1446
- document.body.appendChild(root);
1447
- return { root, box, close: () => root.remove() };
1448
- }
1449
- function el(tag, cls, text) {
1450
- const e = document.createElement(tag);
1451
- if (cls) e.className = cls;
1452
- if (text != null) e.textContent = text;
1453
- return e;
1454
- }
1455
- function qrSvg(text) {
1456
- const q = (0, import_qrcode_generator.default)(0, "M");
1457
- q.addData(text);
1458
- q.make();
1459
- return q.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
1460
- }
1461
- function isTouchDevice(env = globalThis) {
1462
- const nav = env.navigator;
1463
- if (!nav) return false;
1464
- const ua = nav.userAgent ?? "";
1465
- if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
1466
- if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
1467
- try {
1468
- return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
1469
- } catch {
1470
- return false;
1471
- }
1472
- }
1473
- function pairingModal(o = {}) {
1474
- const ac = new AbortController();
1475
- let ui = null;
1476
- let codeEl = null;
1477
- const close = () => {
1478
- ui?.close();
1479
- ui = null;
1480
- };
1481
- ac.signal.addEventListener("abort", close);
1482
- return {
1483
- signal: ac.signal,
1484
- close,
1485
- onLink(link) {
1486
- if (typeof document === "undefined") return;
1487
- close();
1488
- ui = mount();
1489
- const app = o.mode === "app" || (o.mode ?? "auto") === "auto" && isTouchDevice();
1490
- const h = el("h2", "", o.title ?? "Connect bWalletX");
1491
- const cancel = el("button", "bwx-btn", "Cancel");
1492
- cancel.onclick = () => {
1493
- o.onCancel?.();
1494
- ac.abort();
1495
- };
1496
- codeEl = el("div", "bwx-code", "");
1497
- codeEl.setAttribute("aria-live", "polite");
1498
- if (app) {
1499
- const p2 = el("p", "", "Open bWalletX, approve, then come back to this page.");
1500
- const stack = el("div", "bwx-stack");
1501
- const open = el("a", "bwx-btn bwx-primary", "Open bWalletX");
1502
- open.href = link;
1503
- open.rel = "noopener";
1504
- const web = el("button", "bwx-btn", "Use web.bwalletx.com instead");
1505
- web.onclick = () => {
1506
- void navigator.clipboard?.writeText(link).catch(() => void 0);
1507
- window.open(WEB_WALLET_URL, "_blank", "noopener");
1508
- p2.textContent = "Link copied. In web.bwalletx.com tap Connect and paste it, then come back here.";
1509
- };
1510
- stack.append(open, web, cancel);
1511
- ui.box.append(h, p2, codeEl, stack);
1512
- return;
1513
- }
1514
- const p = el("p", "", "Scan with bWalletX on your phone, or paste the link into web.bwalletx.com.");
1515
- const qr = el("div", "bwx-qr");
1516
- qr.innerHTML = qrSvg(link);
1517
- const row = el("div", "bwx-row");
1518
- const copy = el("button", "bwx-btn", "Copy link");
1519
- copy.onclick = () => void navigator.clipboard?.writeText(link).then(() => copy.textContent = "Copied");
1520
- row.append(copy, cancel);
1521
- ui.box.append(h, p, qr, codeEl, row);
1522
- },
1523
- onCode(code) {
1524
- if (codeEl) {
1525
- codeEl.textContent = code;
1526
- const p = ui?.box.querySelector("p");
1527
- if (p) p.textContent = "Check bWalletX shows the same code, then tap Connect.";
1528
- }
1529
- }
1530
- };
1531
- }
1532
- function pickWallet(options, o = {}) {
1533
- if (typeof document === "undefined") return Promise.resolve(null);
1534
- return new Promise((resolve) => {
1535
- const ui = mount();
1536
- const done = (w) => {
1537
- ui.close();
1538
- resolve(w);
1539
- };
1540
- ui.box.append(el("h2", "", o.title ?? "Choose a wallet"));
1541
- if (!options.length) ui.box.append(el("p", "", "No wallet found. Get bWalletX at bwalletx.com."));
1542
- const list = el("div", "bwx-list");
1543
- for (const w of options) {
1544
- const b = el("button", "bwx-btn bwx-opt");
1545
- if (w.icon) {
1546
- const img = el("img");
1547
- img.src = w.icon;
1548
- img.alt = "";
1549
- b.append(img);
1550
- }
1551
- b.append(el("span", "", w.name));
1552
- if (w.kind === "handcash") b.append(el("span", "bwx-tag", "until BRC-100"));
1553
- b.onclick = () => done(w);
1554
- list.append(b);
1555
- }
1556
- const cancel = el("button", "bwx-btn", "Cancel");
1557
- cancel.onclick = () => done(null);
1558
- ui.root.onclick = (e) => e.target === ui.root && done(null);
1559
- ui.box.append(list, el("div", "bwx-row"));
1560
- ui.box.lastChild.append(cancel);
1561
- });
1562
- }
1563
-
1564
2489
  // src/theme.ts
1565
2490
  var WALLET_THEME_META = "bwalletx-theme";
1566
2491
  var ID = /^[a-z0-9-]{1,32}$/;
@@ -1579,18 +2504,77 @@ function declareWalletTheme(theme) {
1579
2504
  }
1580
2505
  if (meta.content !== id) meta.content = id;
1581
2506
  }
2507
+
2508
+ // src/connect-flow.ts
2509
+ async function runConnect(o) {
2510
+ const started = Date.now();
2511
+ const { list } = o;
2512
+ let choice = null;
2513
+ let reason = "none";
2514
+ try {
2515
+ const d = decideWallet(list, { walletId: o.walletId, chooser: o.chooser, picker: o.picker, fastPath: o.fastPath });
2516
+ choice = d.option;
2517
+ reason = d.reason;
2518
+ if (!choice && d.reason === "chooser") choice = await o.pick(list);
2519
+ if (!choice) {
2520
+ if (d.reason === "none" || !list.length) throw Object.assign(new Error("No wallet found. Get bWalletX at bwalletx.com."), { code: "NOT_FOUND" });
2521
+ reportAttempt(o.onAttempt, attemptFor(null, "cancelled", started));
2522
+ return null;
2523
+ }
2524
+ const signIn = !!(o.challengeUrl && o.verifyUrl) && choice.kind !== "handcash";
2525
+ const cw = await choice.connect(signIn ? { identity: false } : void 0);
2526
+ let handle = cw.handle ?? null;
2527
+ let paymail = cw.paymail ?? null;
2528
+ let identityKey = cw.identityKey;
2529
+ let signedIn = false;
2530
+ if (cw.wallet && signIn) {
2531
+ const { result, verified } = await signInAndVerify({
2532
+ wallet: cw.wallet,
2533
+ identityKey: cw.identityKey ?? void 0,
2534
+ // The key that signed in here last time: a repeat sign-in is then the signature alone.
2535
+ identityKeyHint: rememberedIdentity(),
2536
+ challengeNeedsIdentityKey: o.challengeNeedsIdentityKey,
2537
+ challenge: (key) => o.post(o.challengeUrl, key ? { identityKey: key } : {}),
2538
+ verify: (r) => o.post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
2539
+ });
2540
+ rememberIdentity(result.identityKey);
2541
+ identityKey = result.identityKey;
2542
+ handle = verified.handle ?? handle;
2543
+ paymail = verified.paymail ?? paymail;
2544
+ signedIn = true;
2545
+ } else if (cw.wallet && !identityKey) {
2546
+ identityKey = (await cw.wallet.getPublicKey({ identityKey: true })).publicKey;
2547
+ }
2548
+ reportAttempt(o.onAttempt, attemptFor(choice, signedIn ? "signed-in" : "connected", started));
2549
+ return { connected: cw, identityKey, handle, paymail, signedIn, walletType: walletTypeOf(choice), substrate: substrateOf(choice), reason };
2550
+ } catch (e) {
2551
+ reportAttempt(o.onAttempt, attemptFor(choice, outcomeOf(e), started));
2552
+ throw e;
2553
+ }
2554
+ }
1582
2555
  // Annotate the CommonJS export names for ESM import in node:
1583
2556
  0 && (module.exports = {
1584
2557
  ACCESS_ERROR_STATUS,
1585
2558
  ACCESS_PROTOCOL,
1586
2559
  ACCESS_VERSION,
1587
2560
  AccessError,
2561
+ BAVATAR_URL,
1588
2562
  BRC29_PROTOCOL,
2563
+ BUTTON_TAG,
1589
2564
  BWALLETX_RDNS,
2565
+ BWALLETX_TYPE,
2566
+ CONNECTED_KEY,
1590
2567
  DEFAULT_DISCOVER_MS,
2568
+ DESKTOP_OPTION_ID,
1591
2569
  GET_URL,
2570
+ IDENTITY_HINT_KEY,
1592
2571
  LOGIN_PROTOCOL,
1593
2572
  LOGO_URL,
2573
+ MENU_CSS,
2574
+ PAYMAIL_LOOKUP_URL,
2575
+ PENDING_LABEL,
2576
+ PENDING_LABEL_OTHER,
2577
+ PENDING_NOTE,
1594
2578
  PROTOCOL_MEDIA,
1595
2579
  PaymentError,
1596
2580
  RDNS_EXTENSION,
@@ -1600,25 +2584,46 @@ function declareWalletTheme(theme) {
1600
2584
  WALLET_THEME_META,
1601
2585
  WEB_WALLET_URL,
1602
2586
  accessErrorMessage,
2587
+ accountLabel,
1603
2588
  amountInSats,
2589
+ attemptFor,
2590
+ autoChoice,
2591
+ avatarUrlFor,
1604
2592
  bindPrewarm,
1605
2593
  brc100Option,
1606
2594
  buildAuthorizeUrl,
1607
2595
  buyToken,
1608
2596
  canonicalJson,
2597
+ challengeWantsIdentityKey,
2598
+ chooseWallet,
2599
+ chooserOrder,
1609
2600
  clearPairing,
1610
2601
  connectBwalletX,
2602
+ cwiIsAnnounced,
2603
+ decideWallet,
1611
2604
  declareWalletTheme,
2605
+ defineBwalletxButton,
1612
2606
  defineBwalletxSignin,
2607
+ desktopOption,
1613
2608
  discoverBwalletX,
1614
2609
  discoverWallets,
2610
+ dropLegacyConnectedKey,
1615
2611
  findBwalletX,
2612
+ getUrlFor,
1616
2613
  handcashAdapter,
2614
+ handleLabel,
2615
+ hasBwalletX,
1617
2616
  inOrder,
2617
+ isFramed,
1618
2618
  isIdentityKeyHex,
1619
2619
  isPaymail,
2620
+ isPresentBwalletX,
1620
2621
  isTouchDevice,
2622
+ isUserRejection,
2623
+ lookupPaymail,
1621
2624
  markSvg,
2625
+ otherDetected,
2626
+ outcomeOf,
1622
2627
  pairBwalletX,
1623
2628
  pairingModal,
1624
2629
  pairingOption,
@@ -1628,14 +2633,25 @@ function declareWalletTheme(theme) {
1628
2633
  protocolMediaUrl,
1629
2634
  proveHoldings,
1630
2635
  qrSvg,
2636
+ rememberIdentity,
2637
+ rememberedIdentity,
1631
2638
  renderButton,
2639
+ reportAttempt,
1632
2640
  requestAccess,
1633
2641
  resolvePaymailP2P,
1634
2642
  restorePairing,
2643
+ runConnect,
2644
+ shortKey,
2645
+ signInAndVerify,
2646
+ signInErrorText,
1635
2647
  signInWithBwalletX,
1636
2648
  signinText,
2649
+ stateFor,
2650
+ substrateOf,
1637
2651
  throwIfAccessError,
1638
2652
  tip,
1639
- toAccessError
2653
+ toAccessError,
2654
+ waitIfLocked,
2655
+ walletTypeOf
1640
2656
  });
1641
2657
  //# sourceMappingURL=index.cjs.map