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