@bwalletx/connect 0.5.3 → 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.
- package/CHANGELOG.md +35 -0
- package/README.md +80 -8
- package/dist/bwalletx-connect.global.js +92 -25
- package/dist/bwalletx-connect.global.js.map +1 -1
- package/dist/{wallets-VOKIKBbO.d.ts → choice-DCh-tgpT.d.cts} +113 -5
- package/dist/{wallets-VOKIKBbO.d.cts → choice-DCh-tgpT.d.ts} +113 -5
- package/dist/chunk-34DKBQNN.js +222 -0
- package/dist/chunk-34DKBQNN.js.map +1 -0
- package/dist/{chunk-QRXFL66W.js → chunk-A2WLGS25.js} +6 -4
- package/dist/chunk-A2WLGS25.js.map +1 -0
- package/dist/chunk-IFGOAROM.js +255 -0
- package/dist/chunk-IFGOAROM.js.map +1 -0
- package/dist/chunk-LS6AYEBO.js +1886 -0
- package/dist/chunk-LS6AYEBO.js.map +1 -0
- package/dist/element-D9Zl-x12.d.cts +46 -0
- package/dist/element-D9Zl-x12.d.ts +46 -0
- package/dist/element.cjs +1221 -67
- package/dist/element.cjs.map +1 -1
- package/dist/element.d.cts +23 -28
- package/dist/element.d.ts +23 -28
- package/dist/element.js +29 -2
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +1539 -449
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +185 -12
- package/dist/index.d.ts +185 -12
- package/dist/index.js +106 -12
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +5 -3
- package/dist/next.cjs.map +1 -1
- package/dist/next.js +1 -1
- package/dist/react.cjs +1073 -194
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +75 -8
- package/dist/react.d.ts +75 -8
- package/dist/react.js +121 -40
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +5 -3
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +8 -3
- package/dist/server.d.ts +8 -3
- package/dist/server.js +1 -1
- package/dist/shell-react.cjs.map +1 -1
- package/dist/shell-react.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-IUDD7YTQ.js +0 -839
- package/dist/chunk-IUDD7YTQ.js.map +0 -1
- package/dist/chunk-PI42H3XY.js +0 -459
- package/dist/chunk-PI42H3XY.js.map +0 -1
- package/dist/chunk-QRXFL66W.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -34,11 +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,
|
|
43
|
+
DEFAULT_DISCOVER_MS: () => DEFAULT_DISCOVER_MS,
|
|
44
|
+
DESKTOP_OPTION_ID: () => DESKTOP_OPTION_ID,
|
|
39
45
|
GET_URL: () => GET_URL,
|
|
46
|
+
IDENTITY_HINT_KEY: () => IDENTITY_HINT_KEY,
|
|
40
47
|
LOGIN_PROTOCOL: () => LOGIN_PROTOCOL,
|
|
41
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,
|
|
42
54
|
PROTOCOL_MEDIA: () => PROTOCOL_MEDIA,
|
|
43
55
|
PaymentError: () => PaymentError,
|
|
44
56
|
RDNS_EXTENSION: () => RDNS_EXTENSION,
|
|
@@ -48,41 +60,75 @@ __export(src_exports, {
|
|
|
48
60
|
WALLET_THEME_META: () => WALLET_THEME_META,
|
|
49
61
|
WEB_WALLET_URL: () => WEB_WALLET_URL,
|
|
50
62
|
accessErrorMessage: () => accessErrorMessage,
|
|
63
|
+
accountLabel: () => accountLabel,
|
|
51
64
|
amountInSats: () => amountInSats,
|
|
65
|
+
attemptFor: () => attemptFor,
|
|
66
|
+
autoChoice: () => autoChoice,
|
|
67
|
+
avatarUrlFor: () => avatarUrlFor,
|
|
68
|
+
bindPrewarm: () => bindPrewarm,
|
|
52
69
|
brc100Option: () => brc100Option,
|
|
53
70
|
buildAuthorizeUrl: () => buildAuthorizeUrl,
|
|
54
71
|
buyToken: () => buyToken,
|
|
55
72
|
canonicalJson: () => canonicalJson,
|
|
73
|
+
challengeWantsIdentityKey: () => challengeWantsIdentityKey,
|
|
74
|
+
chooseWallet: () => chooseWallet,
|
|
75
|
+
chooserOrder: () => chooserOrder,
|
|
56
76
|
clearPairing: () => clearPairing,
|
|
57
77
|
connectBwalletX: () => connectBwalletX,
|
|
78
|
+
cwiIsAnnounced: () => cwiIsAnnounced,
|
|
79
|
+
decideWallet: () => decideWallet,
|
|
58
80
|
declareWalletTheme: () => declareWalletTheme,
|
|
81
|
+
defineBwalletxButton: () => defineBwalletxButton,
|
|
59
82
|
defineBwalletxSignin: () => defineBwalletxSignin,
|
|
83
|
+
desktopOption: () => desktopOption,
|
|
60
84
|
discoverBwalletX: () => discoverBwalletX,
|
|
61
85
|
discoverWallets: () => discoverWallets,
|
|
86
|
+
dropLegacyConnectedKey: () => dropLegacyConnectedKey,
|
|
62
87
|
findBwalletX: () => findBwalletX,
|
|
88
|
+
getUrlFor: () => getUrlFor,
|
|
63
89
|
handcashAdapter: () => handcashAdapter,
|
|
90
|
+
handleLabel: () => handleLabel,
|
|
91
|
+
hasBwalletX: () => hasBwalletX,
|
|
64
92
|
inOrder: () => inOrder,
|
|
93
|
+
isFramed: () => isFramed,
|
|
65
94
|
isIdentityKeyHex: () => isIdentityKeyHex,
|
|
66
95
|
isPaymail: () => isPaymail,
|
|
96
|
+
isPresentBwalletX: () => isPresentBwalletX,
|
|
67
97
|
isTouchDevice: () => isTouchDevice,
|
|
98
|
+
isUserRejection: () => isUserRejection,
|
|
99
|
+
lookupPaymail: () => lookupPaymail,
|
|
68
100
|
markSvg: () => markSvg,
|
|
101
|
+
otherDetected: () => otherDetected,
|
|
102
|
+
outcomeOf: () => outcomeOf,
|
|
69
103
|
pairBwalletX: () => pairBwalletX,
|
|
70
104
|
pairingModal: () => pairingModal,
|
|
71
105
|
pairingOption: () => pairingOption,
|
|
72
106
|
pay: () => pay,
|
|
73
107
|
pickWallet: () => pickWallet,
|
|
108
|
+
prewarmBwalletX: () => prewarmBwalletX,
|
|
74
109
|
protocolMediaUrl: () => protocolMediaUrl,
|
|
75
110
|
proveHoldings: () => proveHoldings,
|
|
76
111
|
qrSvg: () => qrSvg,
|
|
112
|
+
rememberIdentity: () => rememberIdentity,
|
|
113
|
+
rememberedIdentity: () => rememberedIdentity,
|
|
77
114
|
renderButton: () => renderButton,
|
|
115
|
+
reportAttempt: () => reportAttempt,
|
|
78
116
|
requestAccess: () => requestAccess,
|
|
79
117
|
resolvePaymailP2P: () => resolvePaymailP2P,
|
|
80
118
|
restorePairing: () => restorePairing,
|
|
119
|
+
runConnect: () => runConnect,
|
|
120
|
+
shortKey: () => shortKey,
|
|
121
|
+
signInAndVerify: () => signInAndVerify,
|
|
122
|
+
signInErrorText: () => signInErrorText,
|
|
81
123
|
signInWithBwalletX: () => signInWithBwalletX,
|
|
82
124
|
signinText: () => signinText,
|
|
125
|
+
stateFor: () => stateFor,
|
|
126
|
+
substrateOf: () => substrateOf,
|
|
83
127
|
throwIfAccessError: () => throwIfAccessError,
|
|
84
128
|
tip: () => tip,
|
|
85
|
-
toAccessError: () => toAccessError
|
|
129
|
+
toAccessError: () => toAccessError,
|
|
130
|
+
waitIfLocked: () => waitIfLocked,
|
|
131
|
+
walletTypeOf: () => walletTypeOf
|
|
86
132
|
});
|
|
87
133
|
module.exports = __toCommonJS(src_exports);
|
|
88
134
|
|
|
@@ -91,13 +137,16 @@ var import_sdk = require("@bsv/sdk");
|
|
|
91
137
|
var RDNS_EXTENSION = "com.bwalletx.extension";
|
|
92
138
|
var RDNS_MOBILE = "space.bwallet.mobile";
|
|
93
139
|
var BWALLETX_RDNS = [RDNS_EXTENSION, RDNS_MOBILE];
|
|
94
|
-
|
|
140
|
+
var DEFAULT_DISCOVER_MS = 300;
|
|
141
|
+
function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
|
|
95
142
|
if (typeof window === "undefined") return Promise.resolve(null);
|
|
96
143
|
return new Promise((resolve) => {
|
|
97
144
|
let done = false;
|
|
145
|
+
let timer;
|
|
98
146
|
const finish = (r) => {
|
|
99
147
|
if (done) return;
|
|
100
148
|
done = true;
|
|
149
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
101
150
|
window.removeEventListener("brc100:announceWallet", onAnnounce);
|
|
102
151
|
resolve(r);
|
|
103
152
|
};
|
|
@@ -110,15 +159,18 @@ function discoverBwalletX(ms = 1200) {
|
|
|
110
159
|
};
|
|
111
160
|
window.addEventListener("brc100:announceWallet", onAnnounce);
|
|
112
161
|
window.dispatchEvent(new Event("brc100:requestWallet"));
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
162
|
+
if (done) return;
|
|
163
|
+
if (isFramed()) {
|
|
164
|
+
void probeXdm(Math.max(ms, 1200)).then((w) => finish(w ? { wallet: w, method: "in-app", rdns: null } : null));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
timer = setTimeout(() => {
|
|
168
|
+
timer = void 0;
|
|
169
|
+
finish(null);
|
|
118
170
|
}, ms);
|
|
119
171
|
});
|
|
120
172
|
}
|
|
121
|
-
async function findBwalletX(ms =
|
|
173
|
+
async function findBwalletX(ms = DEFAULT_DISCOVER_MS) {
|
|
122
174
|
return (await discoverBwalletX(ms))?.wallet ?? null;
|
|
123
175
|
}
|
|
124
176
|
var isFramed = () => {
|
|
@@ -173,6 +225,21 @@ async function probeXdm(ms = 1200, allowed = BWALLETX_PARENT_ORIGINS) {
|
|
|
173
225
|
return null;
|
|
174
226
|
}
|
|
175
227
|
}
|
|
228
|
+
var warmedAt = 0;
|
|
229
|
+
function prewarmBwalletX() {
|
|
230
|
+
if (typeof window === "undefined" || Date.now() - warmedAt < 2e4) return;
|
|
231
|
+
warmedAt = Date.now();
|
|
232
|
+
void discoverBwalletX(0).then((f) => {
|
|
233
|
+
if (f && f.method === "extension") void f.wallet.isAuthenticated({}).catch(() => {
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function bindPrewarm(el2) {
|
|
238
|
+
const on = () => prewarmBwalletX();
|
|
239
|
+
const evs = ["pointerenter", "focus", "touchstart"];
|
|
240
|
+
for (const e of evs) el2.addEventListener(e, on, { passive: true });
|
|
241
|
+
return () => evs.forEach((e) => el2.removeEventListener(e, on));
|
|
242
|
+
}
|
|
176
243
|
|
|
177
244
|
// src/pair.ts
|
|
178
245
|
var import_sdk3 = require("@bsv/sdk");
|
|
@@ -213,10 +280,22 @@ function pairUrl(link) {
|
|
|
213
280
|
const q = new URLSearchParams({ v: link.v, r: link.r, c: link.c, k: link.k, o: link.o, e: String(link.e) });
|
|
214
281
|
return `https://${PAIR_HOST}/pair?${q}`;
|
|
215
282
|
}
|
|
216
|
-
var
|
|
283
|
+
var newClaim = () => b64url(crypto.getRandomValues(new Uint8Array(16)));
|
|
284
|
+
var WALLET_CLAIM_INFO = "bwallet-pair-wallet-claim-v1";
|
|
285
|
+
var hex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
286
|
+
async function walletClaimFor(channel, sitePub) {
|
|
287
|
+
const d = await crypto.subtle.digest("SHA-256", enc.encode(`${WALLET_CLAIM_INFO}|${channel}|${sitePub}`));
|
|
288
|
+
return b64url(new Uint8Array(d));
|
|
289
|
+
}
|
|
290
|
+
async function claimDigest(claim) {
|
|
291
|
+
return hex(new Uint8Array(await crypto.subtle.digest("SHA-256", enc.encode(claim))));
|
|
292
|
+
}
|
|
293
|
+
var relaySocketUrl = (relay, channel, role, expiry, claim, walletClaimHash) => {
|
|
217
294
|
const local = /^(localhost|127\.0\.0\.1)(:\d+)?$/.test(relay);
|
|
218
295
|
const q = new URLSearchParams({ role });
|
|
219
296
|
if (expiry) q.set("e", String(expiry));
|
|
297
|
+
if (claim) q.set("claim", claim);
|
|
298
|
+
if (walletClaimHash && role === "site") q.set("wc", walletClaimHash);
|
|
220
299
|
return `${local ? "ws" : "wss"}://${relay}/v1/c/${channel}?${q}`;
|
|
221
300
|
};
|
|
222
301
|
async function deriveSession(mine, theirs, channel) {
|
|
@@ -314,6 +393,7 @@ function parseStored(raw) {
|
|
|
314
393
|
if (typeof p.s !== "string" || !/^[0-9a-f]{64}$/i.test(p.s)) return null;
|
|
315
394
|
if (typeof p.k !== "string" || !/^0[23][0-9a-f]{64}$/i.test(p.k)) return null;
|
|
316
395
|
if (!n(p.sent) || !n(p.lastSeen) || !n(p.pairedAt)) return null;
|
|
396
|
+
if (p.sc !== void 0 && (typeof p.sc !== "string" || !/^[A-Za-z0-9_-]{22,64}$/.test(p.sc))) return null;
|
|
317
397
|
return p;
|
|
318
398
|
} catch {
|
|
319
399
|
return null;
|
|
@@ -371,7 +451,12 @@ var Link = class {
|
|
|
371
451
|
await this.opening;
|
|
372
452
|
}
|
|
373
453
|
reopen() {
|
|
374
|
-
|
|
454
|
+
if (!this.p.sc) {
|
|
455
|
+
this.p = { ...this.p, sc: newClaim() };
|
|
456
|
+
this.persist();
|
|
457
|
+
}
|
|
458
|
+
const e = Math.floor(Date.now() / 1e3) + QR_LIFETIME_S - 10;
|
|
459
|
+
const ws = this.socket(relaySocketUrl(this.p.r, this.p.c, "site", e, this.p.sc));
|
|
375
460
|
return new Promise((resolve, reject) => {
|
|
376
461
|
ws.onopen = () => {
|
|
377
462
|
this.ws = ws;
|
|
@@ -465,11 +550,13 @@ function pairBwalletX(opts) {
|
|
|
465
550
|
const siteKey = import_sdk3.PrivateKey.fromRandom();
|
|
466
551
|
const channel = newChannel();
|
|
467
552
|
const expiry = Math.floor(Date.now() / 1e3) + QR_LIFETIME_S;
|
|
468
|
-
|
|
553
|
+
const siteClaim = newClaim();
|
|
554
|
+
const sitePub = siteKey.toPublicKey().toString();
|
|
555
|
+
return walletClaimFor(channel, sitePub).then(claimDigest).then((wc) => new Promise((resolve, reject) => {
|
|
469
556
|
if (opts.signal?.aborted) return reject(new Error("cancelled"));
|
|
470
557
|
let ws;
|
|
471
558
|
try {
|
|
472
|
-
ws = socket(relaySocketUrl(relay, channel, "site", expiry));
|
|
559
|
+
ws = socket(relaySocketUrl(relay, channel, "site", expiry, siteClaim, wc));
|
|
473
560
|
} catch (e) {
|
|
474
561
|
return reject(e);
|
|
475
562
|
}
|
|
@@ -508,7 +595,7 @@ function pairBwalletX(opts) {
|
|
|
508
595
|
if (msg.t === "ready") {
|
|
509
596
|
settled = true;
|
|
510
597
|
clearTimeout(timer);
|
|
511
|
-
const p = { v: 1, c: channel, r: relay, s: siteKey.toHex(), k: walletKey, ...sealer.counters, pairedAt: Date.now() };
|
|
598
|
+
const p = { v: 1, c: channel, r: relay, s: siteKey.toHex(), k: walletKey, ...sealer.counters, pairedAt: Date.now(), sc: siteClaim };
|
|
512
599
|
await Promise.resolve(storage.setItem(key, JSON.stringify(p))).catch(() => {
|
|
513
600
|
});
|
|
514
601
|
const link = new Link(p, sealer, storage, key, socket, opts.callTimeoutMs ?? 12e4);
|
|
@@ -518,8 +605,8 @@ function pairBwalletX(opts) {
|
|
|
518
605
|
fail(new Error("Pairing was declined in bWalletX"));
|
|
519
606
|
}
|
|
520
607
|
});
|
|
521
|
-
opts.onLink(pairUrl({ v: PAIR_VERSION, r: relay, c: channel, k:
|
|
522
|
-
});
|
|
608
|
+
opts.onLink(pairUrl({ v: PAIR_VERSION, r: relay, c: channel, k: sitePub, o: origin, e: expiry }));
|
|
609
|
+
}));
|
|
523
610
|
}
|
|
524
611
|
async function restorePairing(opts = {}) {
|
|
525
612
|
const storage = opts.storage === void 0 ? defaultStorage() : opts.storage ?? memoryStorage();
|
|
@@ -540,35 +627,97 @@ async function clearPairing(opts = {}) {
|
|
|
540
627
|
// src/signin.ts
|
|
541
628
|
var LOGIN_PROTOCOL = [2, "bwallet sign in"];
|
|
542
629
|
async function connectBwalletX(opts = {}) {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
630
|
+
const status = (s) => {
|
|
631
|
+
try {
|
|
632
|
+
opts.onStatus?.(s);
|
|
633
|
+
} catch {
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
if (opts.wallet) {
|
|
637
|
+
status("opening");
|
|
638
|
+
return { wallet: opts.wallet, method: "wallet" };
|
|
639
|
+
}
|
|
640
|
+
status("finding");
|
|
641
|
+
const found = await discoverBwalletX(opts.discoverMs ?? DEFAULT_DISCOVER_MS);
|
|
642
|
+
if (found) {
|
|
643
|
+
status("opening");
|
|
644
|
+
return { wallet: found.wallet, method: found.method };
|
|
645
|
+
}
|
|
546
646
|
if (opts.reusePairing !== false) {
|
|
547
647
|
const restored = await restorePairing(opts.pairing ?? {});
|
|
548
|
-
if (restored)
|
|
648
|
+
if (restored) {
|
|
649
|
+
status("opening");
|
|
650
|
+
return { wallet: restored, method: "pairing" };
|
|
651
|
+
}
|
|
549
652
|
}
|
|
550
653
|
if (!opts.pairing) {
|
|
551
654
|
throw Object.assign(new Error("bWalletX was not found in this browser"), { code: "NOT_FOUND" });
|
|
552
655
|
}
|
|
656
|
+
status("pairing");
|
|
553
657
|
return { wallet: await pairBwalletX(opts.pairing), method: "pairing" };
|
|
554
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
|
+
}
|
|
555
672
|
async function signInWithBwalletX(opts) {
|
|
556
673
|
const { wallet, method } = await connectBwalletX(opts);
|
|
557
|
-
if (method !== "pairing") await wallet
|
|
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))
|
|
558
686
|
});
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
"
|
|
563
|
-
() =>
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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;
|
|
696
|
+
try {
|
|
697
|
+
c = await atStage("challenge", () => opts.challenge(opts.identityKey));
|
|
698
|
+
} catch (e) {
|
|
699
|
+
if (!opts.identityKey && challengeWantsIdentityKey(e)) return keyFirst();
|
|
700
|
+
throw e;
|
|
701
|
+
}
|
|
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
|
+
]);
|
|
570
707
|
return { identityKey, signature: Array.from(signature), nonce: c.nonce, method, wallet };
|
|
571
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
|
+
}
|
|
572
721
|
async function atStage(stage, run) {
|
|
573
722
|
try {
|
|
574
723
|
return await run();
|
|
@@ -623,139 +772,671 @@ function renderButton(el2, opts = {}) {
|
|
|
623
772
|
return b;
|
|
624
773
|
}
|
|
625
774
|
|
|
626
|
-
// src/
|
|
775
|
+
// src/wallets.ts
|
|
627
776
|
var import_sdk4 = require("@bsv/sdk");
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
var ACCESS_ERROR_STATUS = {
|
|
634
|
-
not_installed: 424,
|
|
635
|
-
user_rejected: 403,
|
|
636
|
-
wrong_account: 409,
|
|
637
|
-
insufficient_tokens: 403,
|
|
638
|
-
identity_level_too_low: 403,
|
|
639
|
-
kyc_required: 403,
|
|
640
|
-
indexer_unavailable: 503,
|
|
641
|
-
expired: 401,
|
|
642
|
-
replay: 401,
|
|
643
|
-
invalid_signature: 401,
|
|
644
|
-
bad_request: 400
|
|
645
|
-
};
|
|
646
|
-
var AccessError = class extends Error {
|
|
647
|
-
code;
|
|
648
|
-
have;
|
|
649
|
-
need;
|
|
650
|
-
constructor(code, message, extra = {}) {
|
|
651
|
-
super(message ?? code);
|
|
652
|
-
this.name = "AccessError";
|
|
653
|
-
this.code = code;
|
|
654
|
-
this.have = extra.have;
|
|
655
|
-
this.need = extra.need;
|
|
656
|
-
}
|
|
657
|
-
toJSON() {
|
|
658
|
-
const b = { error: this.code, message: this.message };
|
|
659
|
-
if (this.have !== void 0) b.have = this.have;
|
|
660
|
-
if (this.need !== void 0) b.need = this.need;
|
|
661
|
-
return b;
|
|
662
|
-
}
|
|
663
|
-
};
|
|
664
|
-
var PROTOCOL_MEDIA = {
|
|
665
|
-
"401": { name: "Identity", color: "#22c55e", video: "assets/protocol/401.mp4", poster: "assets/protocol/401.jpg" },
|
|
666
|
-
"402": { name: "Payment", color: "#3b82f6", video: "assets/protocol/402.mp4", poster: "assets/protocol/402.jpg" },
|
|
667
|
-
"403": { name: "Access", color: "#ef4444", video: "assets/protocol/403.mp4", poster: "assets/protocol/403.jpg" }
|
|
668
|
-
};
|
|
669
|
-
function protocolMediaUrl(kind, which, version = "latest") {
|
|
670
|
-
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 };
|
|
671
782
|
}
|
|
672
|
-
function
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
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;
|
|
796
|
+
}
|
|
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;
|
|
811
|
+
}
|
|
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
|
+
};
|
|
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;
|
|
684
844
|
}
|
|
685
|
-
throw new Error(`canonicalJson: unsupported ${typeof v}`);
|
|
686
845
|
}
|
|
687
|
-
function
|
|
688
|
-
return
|
|
846
|
+
function cwiIsAnnounced(cwi, announced) {
|
|
847
|
+
return announced.some((o) => o.wallet === cwi || o.rdns === RDNS_MOBILE);
|
|
689
848
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
if (
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
out.
|
|
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" }));
|
|
713
872
|
}
|
|
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" }));
|
|
714
880
|
}
|
|
715
|
-
if (
|
|
716
|
-
|
|
717
|
-
|
|
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);
|
|
718
901
|
}
|
|
719
|
-
|
|
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);
|
|
720
939
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
940
|
+
return p;
|
|
941
|
+
}
|
|
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) {
|
|
959
|
+
try {
|
|
960
|
+
await navigator.clipboard.writeText(text);
|
|
961
|
+
return true;
|
|
962
|
+
} catch {
|
|
963
|
+
return false;
|
|
724
964
|
}
|
|
725
|
-
if (!out.token && !out.identityLevel && !out.kyc) throw new AccessError("bad_request", "require is empty");
|
|
726
|
-
return out;
|
|
727
965
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
let c;
|
|
966
|
+
var IDENTITY_HINT_KEY = "bwx:identityKey";
|
|
967
|
+
function rememberedIdentity() {
|
|
731
968
|
try {
|
|
732
|
-
|
|
969
|
+
const k = globalThis.localStorage?.getItem(IDENTITY_HINT_KEY) ?? "";
|
|
970
|
+
return /^0[23][0-9a-f]{64}$/i.test(k) ? k : void 0;
|
|
733
971
|
} catch {
|
|
734
|
-
|
|
972
|
+
return void 0;
|
|
735
973
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
974
|
+
}
|
|
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 {
|
|
740
980
|
}
|
|
741
|
-
if (!Number.isInteger(c.iat) || !Number.isInteger(c.exp) || c.exp <= c.iat) throw new AccessError("bad_request", "bad iat/exp");
|
|
742
|
-
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");
|
|
743
|
-
c.require = normaliseRequirement(c.require);
|
|
744
|
-
if (canonicalJson(c) !== s) throw new AccessError("bad_request", "challenge is not canonical");
|
|
745
|
-
return c;
|
|
746
981
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
982
|
+
|
|
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 ?? "?"}.`;
|
|
759
1440
|
case "kyc_required":
|
|
760
1441
|
return "A verified identity (KYC) is required.";
|
|
761
1442
|
case "indexer_unavailable":
|
|
@@ -810,7 +1491,7 @@ async function proveHoldings(wallet, challenge, tokenId, basket = "bsv21") {
|
|
|
810
1491
|
const counterparty = ci.counterparty ?? "self";
|
|
811
1492
|
for (const protocolID of ci.protocolID ? [ci.protocolID] : [ONESAT, LEGACY_ONESAT]) {
|
|
812
1493
|
const { publicKey } = await wallet.getPublicKey({ protocolID, keyID: ci.keyID, counterparty });
|
|
813
|
-
const h =
|
|
1494
|
+
const h = import_sdk5.Utils.toHex(import_sdk5.Hash.hash160(import_sdk5.PublicKey.fromString(publicKey).encode(true)));
|
|
814
1495
|
if (want && h !== want) continue;
|
|
815
1496
|
const existing = byKey.get(publicKey);
|
|
816
1497
|
if (existing) {
|
|
@@ -860,7 +1541,7 @@ async function requestAccess(o) {
|
|
|
860
1541
|
var SIGNIN_TAG = "bwalletx-signin";
|
|
861
1542
|
var GET_URL = "https://bwalletx.com/get";
|
|
862
1543
|
var WEB_WALLET_URL = "https://web.bwalletx.com";
|
|
863
|
-
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" };
|
|
864
1545
|
function signinText(label = "signin", brand = "bWalletX") {
|
|
865
1546
|
return `${VERBS[label] ?? VERBS.signin} ${brand}`;
|
|
866
1547
|
}
|
|
@@ -915,7 +1596,50 @@ button[aria-busy=true]{cursor:wait}
|
|
|
915
1596
|
.nf a:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
|
|
916
1597
|
:host([theme=dark]) .nf,:host-context(.dark) .nf{color:#a1a1aa}
|
|
917
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}
|
|
918
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";
|
|
919
1643
|
function b64url2(bytes) {
|
|
920
1644
|
let s = "";
|
|
921
1645
|
for (const b of bytes) s += String.fromCharCode(b);
|
|
@@ -948,14 +1672,15 @@ async function buildAuthorizeUrl(p) {
|
|
|
948
1672
|
async function postJson(url, body) {
|
|
949
1673
|
const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
950
1674
|
const data = await res.json().catch(() => ({}));
|
|
951
|
-
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 });
|
|
952
1676
|
return data;
|
|
953
1677
|
}
|
|
954
1678
|
async function postAccess(url, body) {
|
|
955
1679
|
const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
956
1680
|
return res.json().catch(() => ({ error: "bad_request", message: `HTTP ${res.status}` }));
|
|
957
1681
|
}
|
|
958
|
-
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"];
|
|
959
1684
|
function createClass() {
|
|
960
1685
|
return class BwalletxSigninElement extends HTMLElement {
|
|
961
1686
|
static get observedAttributes() {
|
|
@@ -964,12 +1689,23 @@ function createClass() {
|
|
|
964
1689
|
#root;
|
|
965
1690
|
#notFound = false;
|
|
966
1691
|
#busy = false;
|
|
1692
|
+
#phase = "signed-out";
|
|
1693
|
+
#account = null;
|
|
1694
|
+
#error = "";
|
|
1695
|
+
#restored = false;
|
|
1696
|
+
#pendingLabel = PENDING_LABEL;
|
|
1697
|
+
#menu = null;
|
|
967
1698
|
constructor() {
|
|
968
1699
|
super();
|
|
969
1700
|
this.#root = this.attachShadow({ mode: "open" });
|
|
970
1701
|
}
|
|
971
1702
|
connectedCallback() {
|
|
972
1703
|
this.#render();
|
|
1704
|
+
void this.#restore();
|
|
1705
|
+
}
|
|
1706
|
+
disconnectedCallback() {
|
|
1707
|
+
this.#menu?.close(false);
|
|
1708
|
+
this.#hideTip();
|
|
973
1709
|
}
|
|
974
1710
|
attributeChangedCallback() {
|
|
975
1711
|
if (this.isConnected) this.#render();
|
|
@@ -981,107 +1717,616 @@ function createClass() {
|
|
|
981
1717
|
get notFound() {
|
|
982
1718
|
return this.#notFound;
|
|
983
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
|
+
}
|
|
984
1735
|
focus(opts) {
|
|
985
1736
|
this.button?.focus(opts);
|
|
986
1737
|
}
|
|
1738
|
+
get #controlled() {
|
|
1739
|
+
return this.hasAttribute("state");
|
|
1740
|
+
}
|
|
1741
|
+
get #stateful() {
|
|
1742
|
+
return !this.hasAttribute("stateless");
|
|
1743
|
+
}
|
|
987
1744
|
#attr(name) {
|
|
988
1745
|
return this.getAttribute(name);
|
|
989
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
|
+
}
|
|
990
1782
|
#render() {
|
|
991
1783
|
const size = this.#attr("size") ?? "lg";
|
|
992
1784
|
const brand = this.#attr("brand") === "bWallet" ? "bWallet" : "bWalletX";
|
|
993
|
-
const
|
|
1785
|
+
const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[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);
|
|
994
1811
|
const subAttr = this.#attr("subtitle");
|
|
995
1812
|
const subDefault = brand === "bWalletX" && (size === "lg" || size === "md") ? "also works with bWallet" : null;
|
|
996
|
-
const subtitle = subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
|
|
997
|
-
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;
|
|
998
1815
|
const disabled = this.hasAttribute("disabled");
|
|
999
1816
|
const markPx = size === "icon" ? 24 : size === "sm" ? 15 : size === "md" ? 17 : 20;
|
|
1000
|
-
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);
|
|
1001
1818
|
const getUrl = this.#attr("get-url") ?? GET_URL;
|
|
1002
1819
|
const webUrl = this.#attr("web-url") ?? WEB_WALLET_URL;
|
|
1003
|
-
const
|
|
1820
|
+
const errText = error ? (this.#controlled ? this.#attr("error") : this.#error) || "Sign-in failed" : "";
|
|
1004
1821
|
const label = size === "icon" ? "" : `<span class="txt" part="text"><b part="label">${esc(text)}</b>${subtitle ? `<small part="subtitle">${esc(subtitle)}</small>` : ""}</span>`;
|
|
1005
1822
|
const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : markSvg(markPx);
|
|
1006
|
-
const
|
|
1007
|
-
|
|
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}`;
|
|
1008
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());
|
|
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();
|
|
1009
1976
|
}
|
|
1010
1977
|
#emit(type, detail, cancelable = false) {
|
|
1011
1978
|
return this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable }));
|
|
1012
1979
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
this.#
|
|
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"));
|
|
1016
1986
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
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
|
+
}
|
|
1025
2005
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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
|
+
}
|
|
1029
2024
|
}
|
|
1030
2025
|
const challengeUrl = this.#attr("challenge-url");
|
|
1031
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;
|
|
1032
2032
|
this.#notFound = false;
|
|
1033
|
-
this.#
|
|
2033
|
+
this.#busy = true;
|
|
2034
|
+
this.#pendingLabel = PENDING_LABEL;
|
|
2035
|
+
if (own) this.#set("pending");
|
|
2036
|
+
else this.#render();
|
|
1034
2037
|
try {
|
|
1035
|
-
const ms = Number(this.#attr("discover-ms")) ||
|
|
1036
|
-
const
|
|
1037
|
-
if (
|
|
2038
|
+
const ms = Number(this.#attr("discover-ms")) || DEFAULT_DISCOVER_MS;
|
|
2039
|
+
const picked = await this.#choose(mode, ms, pairing);
|
|
2040
|
+
if (picked === "not-found") {
|
|
1038
2041
|
this.#notFound = true;
|
|
2042
|
+
if (own) this.#phase = "signed-out";
|
|
2043
|
+
this.#attempt(null, "not-found", started);
|
|
1039
2044
|
this.#emit("bwalletx-not-found");
|
|
1040
2045
|
if (this.#attr("require-token")) this.#emit("bwalletx-access-denied", { code: "not_installed", message: "No wallet found" });
|
|
1041
2046
|
return;
|
|
1042
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";
|
|
1043
2063
|
const requireToken = this.#attr("require-token");
|
|
1044
2064
|
if (requireToken) {
|
|
2065
|
+
if (own) this.#phase = "signed-out";
|
|
1045
2066
|
const verifyUrl2 = this.#attr("verify-url");
|
|
1046
2067
|
const min = this.#attr("min");
|
|
1047
2068
|
const minBps = this.#attr("min-bps");
|
|
1048
2069
|
const hint = { token: { id: requireToken, ...minBps ? { minBps: Number(minBps) } : { min: min ?? "1" } } };
|
|
1049
2070
|
try {
|
|
1050
2071
|
const a = await requestAccess({
|
|
1051
|
-
wallet
|
|
2072
|
+
wallet,
|
|
1052
2073
|
challenge: (identityKey) => postJson(challengeUrl, { identityKey, require: hint }),
|
|
1053
2074
|
verify: verifyUrl2 ? (response2) => postAccess(verifyUrl2, response2) : void 0
|
|
1054
2075
|
});
|
|
2076
|
+
this.#attempt(option, "signed-in", started);
|
|
1055
2077
|
this.#emit("bwalletx-access-granted", { identityKey: a.response.identityKey, response: a.verified });
|
|
1056
2078
|
} catch (e) {
|
|
1057
2079
|
const err = e instanceof AccessError ? e : new AccessError("bad_request", String(e));
|
|
2080
|
+
this.#attempt(option, outcomeOf(e), started);
|
|
1058
2081
|
this.#emit("bwalletx-access-denied", { code: err.code, message: err.message, have: err.have, need: err.need });
|
|
1059
2082
|
}
|
|
1060
2083
|
return;
|
|
1061
2084
|
}
|
|
1062
|
-
const r = await signInWithBwalletX({ wallet: found.wallet, challenge: (identityKey) => postJson(challengeUrl, { identityKey }) });
|
|
1063
2085
|
const verifyUrl = this.#attr("verify-url");
|
|
1064
|
-
const
|
|
1065
|
-
|
|
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 });
|
|
1066
2097
|
} catch (error) {
|
|
1067
|
-
|
|
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 });
|
|
1068
2109
|
} finally {
|
|
1069
|
-
this.#
|
|
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
|
+
}
|
|
2277
|
+
} finally {
|
|
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) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[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;
|
|
1070
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;
|
|
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();
|
|
1071
2320
|
}
|
|
1072
2321
|
};
|
|
1073
2322
|
}
|
|
1074
|
-
function
|
|
1075
|
-
if (typeof
|
|
1076
|
-
|
|
1077
|
-
if (existing) return existing;
|
|
1078
|
-
const C = createClass();
|
|
1079
|
-
customElements.define(tag, C);
|
|
1080
|
-
return C;
|
|
2323
|
+
function defineBwalletxButton(tag = BUTTON_TAG) {
|
|
2324
|
+
if (typeof customElements === "undefined" || customElements.get(tag)) return;
|
|
2325
|
+
customElements.define(tag, createClass2());
|
|
1081
2326
|
}
|
|
1082
2327
|
|
|
1083
2328
|
// src/pay.ts
|
|
1084
|
-
var
|
|
2329
|
+
var import_sdk6 = require("@bsv/sdk");
|
|
1085
2330
|
var BRC29_PROTOCOL = [2, "3241645161d8"];
|
|
1086
2331
|
var PaymentError = class extends Error {
|
|
1087
2332
|
constructor(code, message, maybeSent = false, cause) {
|
|
@@ -1101,13 +2346,13 @@ var isPaymail = (s) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
|
|
|
1101
2346
|
function randomB64(n) {
|
|
1102
2347
|
const b = new Uint8Array(n);
|
|
1103
2348
|
globalThis.crypto.getRandomValues(b);
|
|
1104
|
-
return
|
|
2349
|
+
return import_sdk6.Utils.toBase64(Array.from(b));
|
|
1105
2350
|
}
|
|
1106
2351
|
function opReturn(text) {
|
|
1107
|
-
const d =
|
|
2352
|
+
const d = import_sdk6.Utils.toArray(text, "utf8");
|
|
1108
2353
|
const len = d.length;
|
|
1109
2354
|
const push = len < 76 ? [len] : len <= 255 ? [76, len] : [77, len & 255, len >> 8];
|
|
1110
|
-
return
|
|
2355
|
+
return import_sdk6.Utils.toHex([0, 106, ...push, ...d]);
|
|
1111
2356
|
}
|
|
1112
2357
|
var clip = (s, n) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
|
|
1113
2358
|
var describe = (s) => clip(s.length < 5 ? `${s} payment`.trim() : s, 50);
|
|
@@ -1160,11 +2405,11 @@ async function resolvePaymailP2P(paymail, sats, f = fetch) {
|
|
|
1160
2405
|
return {
|
|
1161
2406
|
outputs: body.outputs.map((o) => ({ script: o.script, satoshis: o.satoshis })),
|
|
1162
2407
|
reference: body.reference,
|
|
1163
|
-
submit: receive ? async (
|
|
2408
|
+
submit: receive ? async (hex2) => {
|
|
1164
2409
|
await f(fill(receive), {
|
|
1165
2410
|
method: "POST",
|
|
1166
2411
|
headers: { "content-type": "application/json" },
|
|
1167
|
-
body: JSON.stringify({ hex, reference: body.reference, metadata: {} })
|
|
2412
|
+
body: JSON.stringify({ hex: hex2, reference: body.reference, metadata: {} })
|
|
1168
2413
|
});
|
|
1169
2414
|
} : void 0
|
|
1170
2415
|
};
|
|
@@ -1203,7 +2448,7 @@ async function pay(source, req, o = {}) {
|
|
|
1203
2448
|
} catch (e) {
|
|
1204
2449
|
throw new PaymentError("WALLET_ERROR", `The wallet could not derive the payment key: ${e.message}`, false, e);
|
|
1205
2450
|
}
|
|
1206
|
-
outputs = [{ script: new
|
|
2451
|
+
outputs = [{ script: new import_sdk6.P2PKH().lock(import_sdk6.PublicKey.fromString(pk).toHash()).toHex(), satoshis: sats, description: "payment" }];
|
|
1207
2452
|
remittance = { senderIdentityKey: me, derivationPrefix, derivationSuffix, outputIndex: 0 };
|
|
1208
2453
|
} else if (isPaymail(to)) {
|
|
1209
2454
|
const r2 = await (o.resolvePaymail ?? ((p, s) => resolvePaymailP2P(p, s, o.fetch ?? fetch)))(to, sats);
|
|
@@ -1211,7 +2456,7 @@ async function pay(source, req, o = {}) {
|
|
|
1211
2456
|
submit = r2.submit;
|
|
1212
2457
|
} else {
|
|
1213
2458
|
try {
|
|
1214
|
-
outputs = [{ script: new
|
|
2459
|
+
outputs = [{ script: new import_sdk6.P2PKH().lock(to).toHex(), satoshis: sats, description: "payment" }];
|
|
1215
2460
|
} catch {
|
|
1216
2461
|
throw new PaymentError("BAD_REQUEST", `Not a paymail, address or identity key: ${to}`);
|
|
1217
2462
|
}
|
|
@@ -1221,7 +2466,7 @@ async function pay(source, req, o = {}) {
|
|
|
1221
2466
|
if (!r.txid) throw new PaymentError("WALLET_ERROR", "The wallet did not return a txid", true);
|
|
1222
2467
|
const tx = r.tx ? Array.from(r.tx) : void 0;
|
|
1223
2468
|
if (submit && tx) {
|
|
1224
|
-
await submit(
|
|
2469
|
+
await submit(import_sdk6.Transaction.fromAtomicBEEF(tx).toHex()).catch(() => {
|
|
1225
2470
|
});
|
|
1226
2471
|
}
|
|
1227
2472
|
return { txid: r.txid, tx, sats, outputs, remittance };
|
|
@@ -1241,255 +2486,6 @@ async function buyToken(source, req) {
|
|
|
1241
2486
|
return res;
|
|
1242
2487
|
}
|
|
1243
2488
|
|
|
1244
|
-
// src/wallets.ts
|
|
1245
|
-
var import_sdk6 = require("@bsv/sdk");
|
|
1246
|
-
async function brc100Connected(wallet, o) {
|
|
1247
|
-
await wallet.waitForAuthentication({}).catch(() => {
|
|
1248
|
-
});
|
|
1249
|
-
const { publicKey } = await wallet.getPublicKey({ identityKey: true });
|
|
1250
|
-
return { wallet, payer: wallet, identityKey: publicKey, optionId: o.id, kind: o.kind };
|
|
1251
|
-
}
|
|
1252
|
-
function brc100Option(wallet, info) {
|
|
1253
|
-
const opt = {
|
|
1254
|
-
id: info.id,
|
|
1255
|
-
name: info.name,
|
|
1256
|
-
icon: info.icon,
|
|
1257
|
-
rdns: info.rdns,
|
|
1258
|
-
kind: "brc100",
|
|
1259
|
-
isBwalletX: info.isBwalletX ?? false,
|
|
1260
|
-
connect: () => brc100Connected(wallet, opt)
|
|
1261
|
-
};
|
|
1262
|
-
return opt;
|
|
1263
|
-
}
|
|
1264
|
-
function pairingOption(pairing) {
|
|
1265
|
-
const opt = {
|
|
1266
|
-
id: "bwalletx-pairing",
|
|
1267
|
-
name: "bWalletX on your phone",
|
|
1268
|
-
kind: "pairing",
|
|
1269
|
-
isBwalletX: true,
|
|
1270
|
-
async connect() {
|
|
1271
|
-
const p = typeof pairing === "function" ? pairing() : pairing;
|
|
1272
|
-
const w = await restorePairing(p) ?? await pairBwalletX(p);
|
|
1273
|
-
return brc100Connected(w, opt);
|
|
1274
|
-
}
|
|
1275
|
-
};
|
|
1276
|
-
return opt;
|
|
1277
|
-
}
|
|
1278
|
-
function handcashAdapter(o) {
|
|
1279
|
-
const opt = {
|
|
1280
|
-
id: "handcash",
|
|
1281
|
-
name: "HandCash",
|
|
1282
|
-
icon: o.icon,
|
|
1283
|
-
kind: "handcash",
|
|
1284
|
-
isBwalletX: false,
|
|
1285
|
-
async connect() {
|
|
1286
|
-
const p = await o.login();
|
|
1287
|
-
return {
|
|
1288
|
-
wallet: null,
|
|
1289
|
-
payer: o.pay ? { pay: o.pay } : null,
|
|
1290
|
-
identityKey: p.identityKey ?? null,
|
|
1291
|
-
handle: p.handle,
|
|
1292
|
-
paymail: p.paymail ?? `${p.handle.replace(/^\$/, "")}@handcash.io`,
|
|
1293
|
-
optionId: opt.id,
|
|
1294
|
-
kind: "handcash",
|
|
1295
|
-
transitional: true
|
|
1296
|
-
};
|
|
1297
|
-
}
|
|
1298
|
-
};
|
|
1299
|
-
return opt;
|
|
1300
|
-
}
|
|
1301
|
-
var NAMES = { [RDNS_EXTENSION]: "bWalletX", "space.bwallet.mobile": "bWalletX" };
|
|
1302
|
-
async function probe(substrate, ms) {
|
|
1303
|
-
try {
|
|
1304
|
-
const w = new import_sdk6.WalletClient(substrate);
|
|
1305
|
-
const ok = await Promise.race([w.isAuthenticated({}).then(() => true), new Promise((r) => setTimeout(() => r(false), ms))]);
|
|
1306
|
-
return ok ? w : null;
|
|
1307
|
-
} catch {
|
|
1308
|
-
return null;
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
async function discoverWallets(o = {}) {
|
|
1312
|
-
const out = [];
|
|
1313
|
-
const ms = o.ms ?? 1200;
|
|
1314
|
-
if (typeof window !== "undefined") {
|
|
1315
|
-
const seen = /* @__PURE__ */ new Map();
|
|
1316
|
-
const onAnnounce = (e) => {
|
|
1317
|
-
const d = e.detail;
|
|
1318
|
-
if (!d?.wallet) return;
|
|
1319
|
-
const rdns = d.info?.rdns ?? d.info?.uuid ?? `wallet-${seen.size}`;
|
|
1320
|
-
if (seen.has(rdns)) return;
|
|
1321
|
-
const isB = BWALLETX_RDNS.includes(rdns);
|
|
1322
|
-
seen.set(rdns, brc100Option(d.wallet, { id: rdns, rdns, name: NAMES[rdns] ?? d.info?.name ?? rdns, icon: d.info?.icon, isBwalletX: isB }));
|
|
1323
|
-
};
|
|
1324
|
-
window.addEventListener("brc100:announceWallet", onAnnounce);
|
|
1325
|
-
window.dispatchEvent(new Event("brc100:requestWallet"));
|
|
1326
|
-
const local = o.probeLocal ? Promise.all([probe("json-api", ms), probe("Cicada", ms)]) : Promise.resolve([null, null]);
|
|
1327
|
-
await new Promise((r) => setTimeout(r, ms));
|
|
1328
|
-
window.removeEventListener("brc100:announceWallet", onAnnounce);
|
|
1329
|
-
out.push(...seen.values());
|
|
1330
|
-
const cwi = window.CWI;
|
|
1331
|
-
if (cwi && ![...seen.values()].some((x) => x.rdns === "space.bwallet.mobile")) {
|
|
1332
|
-
out.push(brc100Option(cwi, { id: "window.CWI", name: "This app\u2019s wallet", isBwalletX: false }));
|
|
1333
|
-
}
|
|
1334
|
-
const [json, cicada] = await local;
|
|
1335
|
-
if (json) out.push(brc100Option(json, { id: "localhost:3321", name: "Desktop wallet (localhost:3321)" }));
|
|
1336
|
-
if (cicada) out.push(brc100Option(cicada, { id: "localhost:3301", name: "Desktop wallet (localhost:3301)" }));
|
|
1337
|
-
}
|
|
1338
|
-
if (o.pairing) out.push(pairingOption(o.pairing));
|
|
1339
|
-
out.push(...o.extra ?? []);
|
|
1340
|
-
const rank = (w) => w.isBwalletX ? 0 : w.kind === "handcash" ? 2 : 1;
|
|
1341
|
-
return out.map((w, i) => ({ w, i })).sort((a, b) => rank(a.w) - rank(b.w) || a.i - b.i).map((x) => x.w);
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
// src/modal.ts
|
|
1345
|
-
var import_qrcode_generator = __toESM(require("qrcode-generator"), 1);
|
|
1346
|
-
var CSS2 = `
|
|
1347
|
-
.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}
|
|
1348
|
-
.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)}
|
|
1349
|
-
.bwx-box h2{margin:0 0 6px;font-size:18px;color:#e8c766}
|
|
1350
|
-
.bwx-box p{margin:0 0 14px;color:#bdb6a3;font-size:14px}
|
|
1351
|
-
.bwx-qr{background:#fff;border-radius:12px;padding:12px;display:flex;justify-content:center}
|
|
1352
|
-
.bwx-qr svg{width:100%;height:auto;max-width:260px}
|
|
1353
|
-
.bwx-code{margin:14px 0 0;text-align:center;font-size:28px;letter-spacing:.3em;color:#e8c766;font-variant-numeric:tabular-nums}
|
|
1354
|
-
.bwx-row{display:flex;gap:8px;margin-top:14px}
|
|
1355
|
-
.bwx-btn{flex:1;border:1px solid #3a3220;background:#15130e;color:#f4f1e8;border-radius:10px;padding:10px 12px;font:inherit;cursor:pointer}
|
|
1356
|
-
.bwx-btn:hover{border-color:#e8c766}
|
|
1357
|
-
a.bwx-btn{display:block;text-align:center;text-decoration:none;box-sizing:border-box}
|
|
1358
|
-
.bwx-primary{background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;border:0;font-weight:700;min-height:48px;padding:13px 12px}
|
|
1359
|
-
.bwx-stack{display:flex;flex-direction:column;gap:8px}
|
|
1360
|
-
.bwx-list{display:flex;flex-direction:column;gap:8px}
|
|
1361
|
-
.bwx-opt{display:flex;align-items:center;gap:10px;text-align:left}
|
|
1362
|
-
.bwx-opt img{width:24px;height:24px;border-radius:6px}
|
|
1363
|
-
.bwx-tag{margin-left:auto;font-size:11px;color:#8d866f}
|
|
1364
|
-
`;
|
|
1365
|
-
function mount() {
|
|
1366
|
-
const root = document.createElement("div");
|
|
1367
|
-
root.className = "bwx-ov";
|
|
1368
|
-
root.setAttribute("role", "dialog");
|
|
1369
|
-
root.setAttribute("aria-modal", "true");
|
|
1370
|
-
const style = document.createElement("style");
|
|
1371
|
-
style.textContent = CSS2;
|
|
1372
|
-
const box = document.createElement("div");
|
|
1373
|
-
box.className = "bwx-box";
|
|
1374
|
-
root.append(style, box);
|
|
1375
|
-
document.body.appendChild(root);
|
|
1376
|
-
return { root, box, close: () => root.remove() };
|
|
1377
|
-
}
|
|
1378
|
-
function el(tag, cls, text) {
|
|
1379
|
-
const e = document.createElement(tag);
|
|
1380
|
-
if (cls) e.className = cls;
|
|
1381
|
-
if (text != null) e.textContent = text;
|
|
1382
|
-
return e;
|
|
1383
|
-
}
|
|
1384
|
-
function qrSvg(text) {
|
|
1385
|
-
const q = (0, import_qrcode_generator.default)(0, "M");
|
|
1386
|
-
q.addData(text);
|
|
1387
|
-
q.make();
|
|
1388
|
-
return q.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
|
|
1389
|
-
}
|
|
1390
|
-
function isTouchDevice(env = globalThis) {
|
|
1391
|
-
const nav = env.navigator;
|
|
1392
|
-
if (!nav) return false;
|
|
1393
|
-
const ua = nav.userAgent ?? "";
|
|
1394
|
-
if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
|
|
1395
|
-
if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
|
|
1396
|
-
try {
|
|
1397
|
-
return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
|
|
1398
|
-
} catch {
|
|
1399
|
-
return false;
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
function pairingModal(o = {}) {
|
|
1403
|
-
const ac = new AbortController();
|
|
1404
|
-
let ui = null;
|
|
1405
|
-
let codeEl = null;
|
|
1406
|
-
const close = () => {
|
|
1407
|
-
ui?.close();
|
|
1408
|
-
ui = null;
|
|
1409
|
-
};
|
|
1410
|
-
ac.signal.addEventListener("abort", close);
|
|
1411
|
-
return {
|
|
1412
|
-
signal: ac.signal,
|
|
1413
|
-
close,
|
|
1414
|
-
onLink(link) {
|
|
1415
|
-
if (typeof document === "undefined") return;
|
|
1416
|
-
close();
|
|
1417
|
-
ui = mount();
|
|
1418
|
-
const app = o.mode === "app" || (o.mode ?? "auto") === "auto" && isTouchDevice();
|
|
1419
|
-
const h = el("h2", "", o.title ?? "Connect bWalletX");
|
|
1420
|
-
const cancel = el("button", "bwx-btn", "Cancel");
|
|
1421
|
-
cancel.onclick = () => {
|
|
1422
|
-
o.onCancel?.();
|
|
1423
|
-
ac.abort();
|
|
1424
|
-
};
|
|
1425
|
-
codeEl = el("div", "bwx-code", "");
|
|
1426
|
-
codeEl.setAttribute("aria-live", "polite");
|
|
1427
|
-
if (app) {
|
|
1428
|
-
const p2 = el("p", "", "Open bWalletX, approve, then come back to this page.");
|
|
1429
|
-
const stack = el("div", "bwx-stack");
|
|
1430
|
-
const open = el("a", "bwx-btn bwx-primary", "Open bWalletX");
|
|
1431
|
-
open.href = link;
|
|
1432
|
-
open.rel = "noopener";
|
|
1433
|
-
const web = el("button", "bwx-btn", "Use web.bwalletx.com instead");
|
|
1434
|
-
web.onclick = () => {
|
|
1435
|
-
void navigator.clipboard?.writeText(link).catch(() => void 0);
|
|
1436
|
-
window.open(WEB_WALLET_URL, "_blank", "noopener");
|
|
1437
|
-
p2.textContent = "Link copied. In web.bwalletx.com tap Connect and paste it, then come back here.";
|
|
1438
|
-
};
|
|
1439
|
-
stack.append(open, web, cancel);
|
|
1440
|
-
ui.box.append(h, p2, codeEl, stack);
|
|
1441
|
-
return;
|
|
1442
|
-
}
|
|
1443
|
-
const p = el("p", "", "Scan with bWalletX on your phone, or paste the link into web.bwalletx.com.");
|
|
1444
|
-
const qr = el("div", "bwx-qr");
|
|
1445
|
-
qr.innerHTML = qrSvg(link);
|
|
1446
|
-
const row = el("div", "bwx-row");
|
|
1447
|
-
const copy = el("button", "bwx-btn", "Copy link");
|
|
1448
|
-
copy.onclick = () => void navigator.clipboard?.writeText(link).then(() => copy.textContent = "Copied");
|
|
1449
|
-
row.append(copy, cancel);
|
|
1450
|
-
ui.box.append(h, p, qr, codeEl, row);
|
|
1451
|
-
},
|
|
1452
|
-
onCode(code) {
|
|
1453
|
-
if (codeEl) {
|
|
1454
|
-
codeEl.textContent = code;
|
|
1455
|
-
const p = ui?.box.querySelector("p");
|
|
1456
|
-
if (p) p.textContent = "Check bWalletX shows the same code, then tap Connect.";
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
};
|
|
1460
|
-
}
|
|
1461
|
-
function pickWallet(options, o = {}) {
|
|
1462
|
-
if (typeof document === "undefined") return Promise.resolve(null);
|
|
1463
|
-
return new Promise((resolve) => {
|
|
1464
|
-
const ui = mount();
|
|
1465
|
-
const done = (w) => {
|
|
1466
|
-
ui.close();
|
|
1467
|
-
resolve(w);
|
|
1468
|
-
};
|
|
1469
|
-
ui.box.append(el("h2", "", o.title ?? "Choose a wallet"));
|
|
1470
|
-
if (!options.length) ui.box.append(el("p", "", "No wallet found. Get bWalletX at bwalletx.com."));
|
|
1471
|
-
const list = el("div", "bwx-list");
|
|
1472
|
-
for (const w of options) {
|
|
1473
|
-
const b = el("button", "bwx-btn bwx-opt");
|
|
1474
|
-
if (w.icon) {
|
|
1475
|
-
const img = el("img");
|
|
1476
|
-
img.src = w.icon;
|
|
1477
|
-
img.alt = "";
|
|
1478
|
-
b.append(img);
|
|
1479
|
-
}
|
|
1480
|
-
b.append(el("span", "", w.name));
|
|
1481
|
-
if (w.kind === "handcash") b.append(el("span", "bwx-tag", "until BRC-100"));
|
|
1482
|
-
b.onclick = () => done(w);
|
|
1483
|
-
list.append(b);
|
|
1484
|
-
}
|
|
1485
|
-
const cancel = el("button", "bwx-btn", "Cancel");
|
|
1486
|
-
cancel.onclick = () => done(null);
|
|
1487
|
-
ui.root.onclick = (e) => e.target === ui.root && done(null);
|
|
1488
|
-
ui.box.append(list, el("div", "bwx-row"));
|
|
1489
|
-
ui.box.lastChild.append(cancel);
|
|
1490
|
-
});
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
2489
|
// src/theme.ts
|
|
1494
2490
|
var WALLET_THEME_META = "bwalletx-theme";
|
|
1495
2491
|
var ID = /^[a-z0-9-]{1,32}$/;
|
|
@@ -1508,17 +2504,77 @@ function declareWalletTheme(theme) {
|
|
|
1508
2504
|
}
|
|
1509
2505
|
if (meta.content !== id) meta.content = id;
|
|
1510
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
|
+
}
|
|
1511
2555
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1512
2556
|
0 && (module.exports = {
|
|
1513
2557
|
ACCESS_ERROR_STATUS,
|
|
1514
2558
|
ACCESS_PROTOCOL,
|
|
1515
2559
|
ACCESS_VERSION,
|
|
1516
2560
|
AccessError,
|
|
2561
|
+
BAVATAR_URL,
|
|
1517
2562
|
BRC29_PROTOCOL,
|
|
2563
|
+
BUTTON_TAG,
|
|
1518
2564
|
BWALLETX_RDNS,
|
|
2565
|
+
BWALLETX_TYPE,
|
|
2566
|
+
CONNECTED_KEY,
|
|
2567
|
+
DEFAULT_DISCOVER_MS,
|
|
2568
|
+
DESKTOP_OPTION_ID,
|
|
1519
2569
|
GET_URL,
|
|
2570
|
+
IDENTITY_HINT_KEY,
|
|
1520
2571
|
LOGIN_PROTOCOL,
|
|
1521
2572
|
LOGO_URL,
|
|
2573
|
+
MENU_CSS,
|
|
2574
|
+
PAYMAIL_LOOKUP_URL,
|
|
2575
|
+
PENDING_LABEL,
|
|
2576
|
+
PENDING_LABEL_OTHER,
|
|
2577
|
+
PENDING_NOTE,
|
|
1522
2578
|
PROTOCOL_MEDIA,
|
|
1523
2579
|
PaymentError,
|
|
1524
2580
|
RDNS_EXTENSION,
|
|
@@ -1528,40 +2584,74 @@ function declareWalletTheme(theme) {
|
|
|
1528
2584
|
WALLET_THEME_META,
|
|
1529
2585
|
WEB_WALLET_URL,
|
|
1530
2586
|
accessErrorMessage,
|
|
2587
|
+
accountLabel,
|
|
1531
2588
|
amountInSats,
|
|
2589
|
+
attemptFor,
|
|
2590
|
+
autoChoice,
|
|
2591
|
+
avatarUrlFor,
|
|
2592
|
+
bindPrewarm,
|
|
1532
2593
|
brc100Option,
|
|
1533
2594
|
buildAuthorizeUrl,
|
|
1534
2595
|
buyToken,
|
|
1535
2596
|
canonicalJson,
|
|
2597
|
+
challengeWantsIdentityKey,
|
|
2598
|
+
chooseWallet,
|
|
2599
|
+
chooserOrder,
|
|
1536
2600
|
clearPairing,
|
|
1537
2601
|
connectBwalletX,
|
|
2602
|
+
cwiIsAnnounced,
|
|
2603
|
+
decideWallet,
|
|
1538
2604
|
declareWalletTheme,
|
|
2605
|
+
defineBwalletxButton,
|
|
1539
2606
|
defineBwalletxSignin,
|
|
2607
|
+
desktopOption,
|
|
1540
2608
|
discoverBwalletX,
|
|
1541
2609
|
discoverWallets,
|
|
2610
|
+
dropLegacyConnectedKey,
|
|
1542
2611
|
findBwalletX,
|
|
2612
|
+
getUrlFor,
|
|
1543
2613
|
handcashAdapter,
|
|
2614
|
+
handleLabel,
|
|
2615
|
+
hasBwalletX,
|
|
1544
2616
|
inOrder,
|
|
2617
|
+
isFramed,
|
|
1545
2618
|
isIdentityKeyHex,
|
|
1546
2619
|
isPaymail,
|
|
2620
|
+
isPresentBwalletX,
|
|
1547
2621
|
isTouchDevice,
|
|
2622
|
+
isUserRejection,
|
|
2623
|
+
lookupPaymail,
|
|
1548
2624
|
markSvg,
|
|
2625
|
+
otherDetected,
|
|
2626
|
+
outcomeOf,
|
|
1549
2627
|
pairBwalletX,
|
|
1550
2628
|
pairingModal,
|
|
1551
2629
|
pairingOption,
|
|
1552
2630
|
pay,
|
|
1553
2631
|
pickWallet,
|
|
2632
|
+
prewarmBwalletX,
|
|
1554
2633
|
protocolMediaUrl,
|
|
1555
2634
|
proveHoldings,
|
|
1556
2635
|
qrSvg,
|
|
2636
|
+
rememberIdentity,
|
|
2637
|
+
rememberedIdentity,
|
|
1557
2638
|
renderButton,
|
|
2639
|
+
reportAttempt,
|
|
1558
2640
|
requestAccess,
|
|
1559
2641
|
resolvePaymailP2P,
|
|
1560
2642
|
restorePairing,
|
|
2643
|
+
runConnect,
|
|
2644
|
+
shortKey,
|
|
2645
|
+
signInAndVerify,
|
|
2646
|
+
signInErrorText,
|
|
1561
2647
|
signInWithBwalletX,
|
|
1562
2648
|
signinText,
|
|
2649
|
+
stateFor,
|
|
2650
|
+
substrateOf,
|
|
1563
2651
|
throwIfAccessError,
|
|
1564
2652
|
tip,
|
|
1565
|
-
toAccessError
|
|
2653
|
+
toAccessError,
|
|
2654
|
+
waitIfLocked,
|
|
2655
|
+
walletTypeOf
|
|
1566
2656
|
});
|
|
1567
2657
|
//# sourceMappingURL=index.cjs.map
|