@bwalletx/connect 0.5.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +97 -9
- package/dist/bwalletx-connect.global.js +97 -25
- package/dist/bwalletx-connect.global.js.map +1 -1
- package/dist/choice-B97AjqF4.d.cts +442 -0
- package/dist/choice-B97AjqF4.d.ts +442 -0
- package/dist/chunk-6EFFOIHK.js +2441 -0
- package/dist/chunk-6EFFOIHK.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-DIKJFKND.js +239 -0
- package/dist/chunk-DIKJFKND.js.map +1 -0
- package/dist/chunk-TNRI7ARM.js +206 -0
- package/dist/chunk-TNRI7ARM.js.map +1 -0
- package/dist/element-BXEoOdXY.d.cts +53 -0
- package/dist/element-BXEoOdXY.d.ts +53 -0
- package/dist/element.cjs +1576 -60
- package/dist/element.cjs.map +1 -1
- package/dist/element.d.cts +26 -26
- package/dist/element.d.ts +26 -26
- package/dist/element.js +35 -2
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +2046 -454
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +323 -16
- package/dist/index.d.ts +323 -16
- package/dist/index.js +186 -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 +1601 -210
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +115 -9
- package/dist/react.d.ts +115 -9
- package/dist/react.js +262 -46
- 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/package.json +10 -9
- package/dist/chunk-EXRQJTXF.js +0 -910
- package/dist/chunk-EXRQJTXF.js.map +0 -1
- package/dist/chunk-QRXFL66W.js.map +0 -1
- package/dist/chunk-RIOHG5JH.js +0 -459
- package/dist/chunk-RIOHG5JH.js.map +0 -1
- package/dist/wallets-CbzMtxrf.d.cts +0 -250
- package/dist/wallets-CbzMtxrf.d.ts +0 -250
package/dist/index.cjs
CHANGED
|
@@ -34,58 +34,144 @@ __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
|
+
CONTINUE_OTHER_LABEL: () => CONTINUE_OTHER_LABEL,
|
|
39
44
|
DEFAULT_DISCOVER_MS: () => DEFAULT_DISCOVER_MS,
|
|
45
|
+
DESKTOP_OPTION_ID: () => DESKTOP_OPTION_ID,
|
|
40
46
|
GET_URL: () => GET_URL,
|
|
47
|
+
IDENTITY_HINT_KEY: () => IDENTITY_HINT_KEY,
|
|
48
|
+
KNOWN_KEYS_KEY: () => KNOWN_KEYS_KEY,
|
|
41
49
|
LOGIN_PROTOCOL: () => LOGIN_PROTOCOL,
|
|
42
50
|
LOGO_URL: () => LOGO_URL,
|
|
51
|
+
MENU_CSS: () => MENU_CSS,
|
|
52
|
+
METANET_ANSWER_MS: () => METANET_ANSWER_MS,
|
|
53
|
+
METANET_JSON_API: () => METANET_JSON_API,
|
|
54
|
+
METANET_LABEL: () => METANET_LABEL,
|
|
55
|
+
METANET_NOTE: () => METANET_NOTE,
|
|
56
|
+
METANET_OPTION_ID: () => METANET_OPTION_ID,
|
|
57
|
+
METANET_REMEMBERED_NOTE: () => METANET_REMEMBERED_NOTE,
|
|
58
|
+
METANET_SILENT_MS: () => METANET_SILENT_MS,
|
|
59
|
+
METANET_TYPE: () => METANET_TYPE,
|
|
60
|
+
METANET_UNREACHABLE: () => METANET_UNREACHABLE,
|
|
61
|
+
MetanetUnreachableError: () => MetanetUnreachableError,
|
|
62
|
+
NOT_YOU_LABEL: () => NOT_YOU_LABEL,
|
|
63
|
+
NO_COINS_TEXT: () => NO_COINS_TEXT,
|
|
64
|
+
PAYMAIL_LOOKUP_URL: () => PAYMAIL_LOOKUP_URL,
|
|
65
|
+
PENDING_LABEL: () => PENDING_LABEL,
|
|
66
|
+
PENDING_LABEL_OTHER: () => PENDING_LABEL_OTHER,
|
|
67
|
+
PENDING_NOTE: () => PENDING_NOTE,
|
|
43
68
|
PROTOCOL_MEDIA: () => PROTOCOL_MEDIA,
|
|
44
69
|
PaymentError: () => PaymentError,
|
|
45
70
|
RDNS_EXTENSION: () => RDNS_EXTENSION,
|
|
46
71
|
RDNS_MOBILE: () => RDNS_MOBILE,
|
|
47
72
|
SIGNIN_CSS: () => SIGNIN_CSS,
|
|
48
73
|
SIGNIN_TAG: () => SIGNIN_TAG,
|
|
74
|
+
SWITCH_NOTE: () => SWITCH_NOTE,
|
|
75
|
+
WALLET_CHOICE_KEY: () => WALLET_CHOICE_KEY,
|
|
49
76
|
WALLET_THEME_META: () => WALLET_THEME_META,
|
|
50
77
|
WEB_WALLET_URL: () => WEB_WALLET_URL,
|
|
51
78
|
accessErrorMessage: () => accessErrorMessage,
|
|
79
|
+
accountLabel: () => accountLabel,
|
|
52
80
|
amountInSats: () => amountInSats,
|
|
81
|
+
attachRemembered: () => attachRemembered,
|
|
82
|
+
attemptFor: () => attemptFor,
|
|
83
|
+
autoChoice: () => autoChoice,
|
|
84
|
+
avatarUrlFor: () => avatarUrlFor,
|
|
53
85
|
bindPrewarm: () => bindPrewarm,
|
|
54
86
|
brc100Option: () => brc100Option,
|
|
55
87
|
buildAuthorizeUrl: () => buildAuthorizeUrl,
|
|
56
88
|
buyToken: () => buyToken,
|
|
57
89
|
canonicalJson: () => canonicalJson,
|
|
90
|
+
challengeWantsIdentityKey: () => challengeWantsIdentityKey,
|
|
91
|
+
chooseWallet: () => chooseWallet,
|
|
92
|
+
chooserOrder: () => chooserOrder,
|
|
93
|
+
claimSilentAttempt: () => claimSilentAttempt,
|
|
58
94
|
clearPairing: () => clearPairing,
|
|
59
95
|
connectBwalletX: () => connectBwalletX,
|
|
96
|
+
connectMetanetDesktop: () => connectMetanetDesktop,
|
|
97
|
+
cwiIsAnnounced: () => cwiIsAnnounced,
|
|
98
|
+
decideWallet: () => decideWallet,
|
|
60
99
|
declareWalletTheme: () => declareWalletTheme,
|
|
100
|
+
defineBwalletxButton: () => defineBwalletxButton,
|
|
61
101
|
defineBwalletxSignin: () => defineBwalletxSignin,
|
|
102
|
+
desktopOption: () => desktopOption,
|
|
62
103
|
discoverBwalletX: () => discoverBwalletX,
|
|
63
104
|
discoverWallets: () => discoverWallets,
|
|
105
|
+
dropLegacyConnectedKey: () => dropLegacyConnectedKey,
|
|
64
106
|
findBwalletX: () => findBwalletX,
|
|
107
|
+
forgetWalletChoice: () => forgetWalletChoice,
|
|
108
|
+
getUrlFor: () => getUrlFor,
|
|
65
109
|
handcashAdapter: () => handcashAdapter,
|
|
110
|
+
handleLabel: () => handleLabel,
|
|
111
|
+
hasBwalletX: () => hasBwalletX,
|
|
112
|
+
inAnyFrame: () => inAnyFrame,
|
|
66
113
|
inOrder: () => inOrder,
|
|
114
|
+
isExplicitPick: () => isExplicitPick,
|
|
115
|
+
isFramed: () => isFramed,
|
|
67
116
|
isIdentityKeyHex: () => isIdentityKeyHex,
|
|
117
|
+
isInsufficientFunds: () => isInsufficientFunds,
|
|
118
|
+
isKnownIdentityKey: () => isKnownIdentityKey,
|
|
119
|
+
isLocalDesktop: () => isLocalDesktop,
|
|
120
|
+
isLocalDesktopType: () => isLocalDesktopType,
|
|
68
121
|
isPaymail: () => isPaymail,
|
|
122
|
+
isPresentBwalletX: () => isPresentBwalletX,
|
|
123
|
+
isRememberable: () => isRememberable,
|
|
69
124
|
isTouchDevice: () => isTouchDevice,
|
|
125
|
+
isUserRejection: () => isUserRejection,
|
|
126
|
+
knownIdentityKeys: () => knownIdentityKeys,
|
|
127
|
+
lookupPaymail: () => lookupPaymail,
|
|
70
128
|
markSvg: () => markSvg,
|
|
129
|
+
matchRemembered: () => matchRemembered,
|
|
130
|
+
metanetOffered: () => metanetOffered,
|
|
131
|
+
metanetOption: () => metanetOption,
|
|
132
|
+
migrateConnectedKey: () => migrateConnectedKey,
|
|
133
|
+
needsBwalletX: () => needsBwalletX,
|
|
134
|
+
otherDetected: () => otherDetected,
|
|
135
|
+
outcomeOf: () => outcomeOf,
|
|
71
136
|
pairBwalletX: () => pairBwalletX,
|
|
72
137
|
pairingModal: () => pairingModal,
|
|
73
138
|
pairingOption: () => pairingOption,
|
|
74
139
|
pay: () => pay,
|
|
75
140
|
pickWallet: () => pickWallet,
|
|
141
|
+
planAutoConnect: () => planAutoConnect,
|
|
76
142
|
prewarmBwalletX: () => prewarmBwalletX,
|
|
77
143
|
protocolMediaUrl: () => protocolMediaUrl,
|
|
78
144
|
proveHoldings: () => proveHoldings,
|
|
79
145
|
qrSvg: () => qrSvg,
|
|
146
|
+
rememberIdentity: () => rememberIdentity,
|
|
147
|
+
rememberKnownIdentityKey: () => rememberKnownIdentityKey,
|
|
148
|
+
rememberWalletChoice: () => rememberWalletChoice,
|
|
149
|
+
rememberedIdentity: () => rememberedIdentity,
|
|
150
|
+
rememberedWalletChoice: () => rememberedWalletChoice,
|
|
80
151
|
renderButton: () => renderButton,
|
|
152
|
+
reportAttempt: () => reportAttempt,
|
|
81
153
|
requestAccess: () => requestAccess,
|
|
154
|
+
resetSilentAttempt: () => resetSilentAttempt,
|
|
82
155
|
resolvePaymailP2P: () => resolvePaymailP2P,
|
|
83
156
|
restorePairing: () => restorePairing,
|
|
157
|
+
runConnect: () => runConnect,
|
|
158
|
+
runSilentMetanet: () => runSilentMetanet,
|
|
159
|
+
shortKey: () => shortKey,
|
|
160
|
+
signInAndVerify: () => signInAndVerify,
|
|
161
|
+
signInErrorText: () => signInErrorText,
|
|
84
162
|
signInWithBwalletX: () => signInWithBwalletX,
|
|
85
163
|
signinText: () => signinText,
|
|
164
|
+
silentAttemptUsed: () => silentAttemptUsed,
|
|
165
|
+
stateFor: () => stateFor,
|
|
166
|
+
substrateOf: () => substrateOf,
|
|
167
|
+
tapGoesElsewhere: () => tapGoesElsewhere,
|
|
86
168
|
throwIfAccessError: () => throwIfAccessError,
|
|
87
169
|
tip: () => tip,
|
|
88
|
-
toAccessError: () => toAccessError
|
|
170
|
+
toAccessError: () => toAccessError,
|
|
171
|
+
trySilentMetanet: () => trySilentMetanet,
|
|
172
|
+
waitIfLocked: () => waitIfLocked,
|
|
173
|
+
walletTypeOf: () => walletTypeOf,
|
|
174
|
+
warnIfNoLogout: () => warnIfNoLogout
|
|
89
175
|
});
|
|
90
176
|
module.exports = __toCommonJS(src_exports);
|
|
91
177
|
|
|
@@ -100,7 +186,7 @@ function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
|
|
|
100
186
|
return new Promise((resolve) => {
|
|
101
187
|
let done = false;
|
|
102
188
|
let timer;
|
|
103
|
-
const
|
|
189
|
+
const finish2 = (r) => {
|
|
104
190
|
if (done) return;
|
|
105
191
|
done = true;
|
|
106
192
|
if (timer !== void 0) clearTimeout(timer);
|
|
@@ -111,24 +197,19 @@ function discoverBwalletX(ms = DEFAULT_DISCOVER_MS) {
|
|
|
111
197
|
const d = e.detail;
|
|
112
198
|
const rdns = d?.info?.rdns ?? "";
|
|
113
199
|
if (d?.wallet && BWALLETX_RDNS.includes(rdns)) {
|
|
114
|
-
|
|
200
|
+
finish2({ wallet: d.wallet, method: rdns === RDNS_EXTENSION ? "extension" : "in-app", rdns });
|
|
115
201
|
}
|
|
116
202
|
};
|
|
117
|
-
const cwiNow = () => {
|
|
118
|
-
const cwi = window.CWI;
|
|
119
|
-
if (cwi) finish({ wallet: cwi, method: "in-app", rdns: null });
|
|
120
|
-
return !!cwi;
|
|
121
|
-
};
|
|
122
203
|
window.addEventListener("brc100:announceWallet", onAnnounce);
|
|
123
204
|
window.dispatchEvent(new Event("brc100:requestWallet"));
|
|
124
|
-
if (done
|
|
205
|
+
if (done) return;
|
|
125
206
|
if (isFramed()) {
|
|
126
|
-
void probeXdm(Math.max(ms, 1200)).then((w) =>
|
|
207
|
+
void probeXdm(Math.max(ms, 1200)).then((w) => finish2(w ? { wallet: w, method: "in-app", rdns: null } : null));
|
|
127
208
|
return;
|
|
128
209
|
}
|
|
129
210
|
timer = setTimeout(() => {
|
|
130
211
|
timer = void 0;
|
|
131
|
-
|
|
212
|
+
finish2(null);
|
|
132
213
|
}, ms);
|
|
133
214
|
});
|
|
134
215
|
}
|
|
@@ -618,27 +699,68 @@ async function connectBwalletX(opts = {}) {
|
|
|
618
699
|
status("pairing");
|
|
619
700
|
return { wallet: await pairBwalletX(opts.pairing), method: "pairing" };
|
|
620
701
|
}
|
|
702
|
+
async function waitIfLocked(wallet) {
|
|
703
|
+
let ok = false;
|
|
704
|
+
try {
|
|
705
|
+
ok = !!(await wallet.isAuthenticated({}))?.authenticated;
|
|
706
|
+
} catch {
|
|
707
|
+
}
|
|
708
|
+
if (!ok) await Promise.resolve().then(() => wallet.waitForAuthentication({})).catch(() => {
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
function challengeWantsIdentityKey(e) {
|
|
712
|
+
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
713
|
+
return /identityKey/i.test(msg);
|
|
714
|
+
}
|
|
621
715
|
async function signInWithBwalletX(opts) {
|
|
622
716
|
const { wallet, method } = await connectBwalletX(opts);
|
|
623
|
-
if (method !== "pairing") await wallet
|
|
717
|
+
if (method !== "pairing" && !opts.skipWaitForAuthentication) await waitIfLocked(wallet);
|
|
718
|
+
const status = () => {
|
|
719
|
+
try {
|
|
720
|
+
opts.onStatus?.("signing");
|
|
721
|
+
} catch {
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
const sign = (c2) => wallet.createSignature({
|
|
725
|
+
protocolID: c2.protocolID ?? LOGIN_PROTOCOL,
|
|
726
|
+
keyID: c2.keyID,
|
|
727
|
+
counterparty: "anyone",
|
|
728
|
+
data: Array.from(new TextEncoder().encode(c2.message))
|
|
624
729
|
});
|
|
625
|
-
const
|
|
730
|
+
const keyFirst = async () => {
|
|
731
|
+
const { publicKey: identityKey2 } = await atStage("identity", () => wallet.getPublicKey({ identityKey: true }));
|
|
732
|
+
status();
|
|
733
|
+
const c2 = await atStage("challenge", () => opts.challenge(identityKey2));
|
|
734
|
+
const { signature: signature2 } = await atStage("signature", () => sign(c2));
|
|
735
|
+
return { identityKey: identityKey2, signature: Array.from(signature2), nonce: c2.nonce, method, wallet };
|
|
736
|
+
};
|
|
737
|
+
if (opts.challengeNeedsIdentityKey && !opts.identityKey) return keyFirst();
|
|
738
|
+
let c;
|
|
626
739
|
try {
|
|
627
|
-
|
|
628
|
-
} catch {
|
|
740
|
+
c = await atStage("challenge", () => opts.challenge(opts.identityKey));
|
|
741
|
+
} catch (e) {
|
|
742
|
+
if (!opts.identityKey && challengeWantsIdentityKey(e)) return keyFirst();
|
|
743
|
+
throw e;
|
|
629
744
|
}
|
|
630
|
-
|
|
631
|
-
const { signature } = await
|
|
632
|
-
"
|
|
633
|
-
() =>
|
|
634
|
-
|
|
635
|
-
keyID: c.keyID,
|
|
636
|
-
counterparty: "anyone",
|
|
637
|
-
data: Array.from(new TextEncoder().encode(c.message))
|
|
638
|
-
})
|
|
639
|
-
);
|
|
745
|
+
status();
|
|
746
|
+
const [{ publicKey: identityKey }, { signature }] = await Promise.all([
|
|
747
|
+
opts.identityKey ? Promise.resolve({ publicKey: opts.identityKey }) : atStage("identity", () => wallet.getPublicKey({ identityKey: true })),
|
|
748
|
+
atStage("signature", () => sign(c))
|
|
749
|
+
]);
|
|
640
750
|
return { identityKey, signature: Array.from(signature), nonce: c.nonce, method, wallet };
|
|
641
751
|
}
|
|
752
|
+
async function signInAndVerify(opts) {
|
|
753
|
+
const hinted = !opts.identityKey && !!opts.identityKeyHint;
|
|
754
|
+
const r = await signInWithBwalletX({ ...opts, identityKey: opts.identityKey ?? opts.identityKeyHint });
|
|
755
|
+
try {
|
|
756
|
+
return { result: r, verified: await atStage("verify", () => opts.verify(r)) };
|
|
757
|
+
} catch (e) {
|
|
758
|
+
const status = e?.status;
|
|
759
|
+
if (!hinted || status !== 401) throw e;
|
|
760
|
+
const again = await signInWithBwalletX({ ...opts, wallet: r.wallet, identityKey: void 0, skipWaitForAuthentication: true });
|
|
761
|
+
return { result: again, verified: await atStage("verify", () => opts.verify(again)) };
|
|
762
|
+
}
|
|
763
|
+
}
|
|
642
764
|
async function atStage(stage, run) {
|
|
643
765
|
try {
|
|
644
766
|
return await run();
|
|
@@ -693,156 +815,924 @@ function renderButton(el2, opts = {}) {
|
|
|
693
815
|
return b;
|
|
694
816
|
}
|
|
695
817
|
|
|
696
|
-
// src/
|
|
818
|
+
// src/wallets.ts
|
|
697
819
|
var import_sdk4 = require("@bsv/sdk");
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
var ACCESS_ERROR_STATUS = {
|
|
704
|
-
not_installed: 424,
|
|
705
|
-
user_rejected: 403,
|
|
706
|
-
wrong_account: 409,
|
|
707
|
-
insufficient_tokens: 403,
|
|
708
|
-
identity_level_too_low: 403,
|
|
709
|
-
kyc_required: 403,
|
|
710
|
-
indexer_unavailable: 503,
|
|
711
|
-
expired: 401,
|
|
712
|
-
replay: 401,
|
|
713
|
-
invalid_signature: 401,
|
|
714
|
-
bad_request: 400
|
|
715
|
-
};
|
|
716
|
-
var AccessError = class extends Error {
|
|
717
|
-
code;
|
|
718
|
-
have;
|
|
719
|
-
need;
|
|
720
|
-
constructor(code, message, extra = {}) {
|
|
721
|
-
super(message ?? code);
|
|
722
|
-
this.name = "AccessError";
|
|
723
|
-
this.code = code;
|
|
724
|
-
this.have = extra.have;
|
|
725
|
-
this.need = extra.need;
|
|
726
|
-
}
|
|
727
|
-
toJSON() {
|
|
728
|
-
const b = { error: this.code, message: this.message };
|
|
729
|
-
if (this.have !== void 0) b.have = this.have;
|
|
730
|
-
if (this.need !== void 0) b.need = this.need;
|
|
731
|
-
return b;
|
|
732
|
-
}
|
|
733
|
-
};
|
|
734
|
-
var PROTOCOL_MEDIA = {
|
|
735
|
-
"401": { name: "Identity", color: "#22c55e", video: "assets/protocol/401.mp4", poster: "assets/protocol/401.jpg" },
|
|
736
|
-
"402": { name: "Payment", color: "#3b82f6", video: "assets/protocol/402.mp4", poster: "assets/protocol/402.jpg" },
|
|
737
|
-
"403": { name: "Access", color: "#ef4444", video: "assets/protocol/403.mp4", poster: "assets/protocol/403.jpg" }
|
|
738
|
-
};
|
|
739
|
-
function protocolMediaUrl(kind, which, version = "latest") {
|
|
740
|
-
return `https://unpkg.com/@bwalletx/connect@${version}/${PROTOCOL_MEDIA[kind][which]}`;
|
|
820
|
+
async function brc100Connected(wallet, o, c = {}) {
|
|
821
|
+
if (c.identity === false) return { wallet, payer: wallet, identityKey: null, optionId: o.id, kind: o.kind };
|
|
822
|
+
await waitIfLocked(wallet);
|
|
823
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true });
|
|
824
|
+
return { wallet, payer: wallet, identityKey: publicKey, optionId: o.id, kind: o.kind };
|
|
741
825
|
}
|
|
742
|
-
function
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
throw new Error(`canonicalJson: unsupported ${typeof v}`);
|
|
826
|
+
function brc100Option(wallet, info) {
|
|
827
|
+
const opt = {
|
|
828
|
+
id: info.id,
|
|
829
|
+
name: info.name,
|
|
830
|
+
icon: info.icon,
|
|
831
|
+
rdns: info.rdns,
|
|
832
|
+
kind: "brc100",
|
|
833
|
+
isBwalletX: info.isBwalletX ?? false,
|
|
834
|
+
substrate: info.substrate,
|
|
835
|
+
wallet,
|
|
836
|
+
connect: (c) => brc100Connected(wallet, opt, c)
|
|
837
|
+
};
|
|
838
|
+
return opt;
|
|
756
839
|
}
|
|
757
|
-
function
|
|
758
|
-
|
|
840
|
+
function pairingOption(pairing) {
|
|
841
|
+
const opt = {
|
|
842
|
+
id: "bwalletx-pairing",
|
|
843
|
+
name: "bWalletX on your phone",
|
|
844
|
+
kind: "pairing",
|
|
845
|
+
isBwalletX: true,
|
|
846
|
+
substrate: "pairing",
|
|
847
|
+
async connect(c) {
|
|
848
|
+
const p = typeof pairing === "function" ? pairing() : pairing;
|
|
849
|
+
const w = await restorePairing(p) ?? await pairBwalletX(p);
|
|
850
|
+
return brc100Connected(w, opt, c);
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
return opt;
|
|
759
854
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
throw new AccessError("bad_request", "require.token.minBps must be an integer 1..10000");
|
|
781
|
-
}
|
|
782
|
-
out.token.minBps = t.minBps;
|
|
855
|
+
function handcashAdapter(o) {
|
|
856
|
+
const opt = {
|
|
857
|
+
id: "handcash",
|
|
858
|
+
name: "HandCash",
|
|
859
|
+
icon: o.icon,
|
|
860
|
+
kind: "handcash",
|
|
861
|
+
isBwalletX: false,
|
|
862
|
+
substrate: "adapter",
|
|
863
|
+
async connect() {
|
|
864
|
+
const p = await o.login();
|
|
865
|
+
return {
|
|
866
|
+
wallet: null,
|
|
867
|
+
payer: o.pay ? { pay: o.pay } : null,
|
|
868
|
+
identityKey: p.identityKey ?? null,
|
|
869
|
+
handle: p.handle,
|
|
870
|
+
paymail: p.paymail ?? `${p.handle.replace(/^\$/, "")}@handcash.io`,
|
|
871
|
+
optionId: opt.id,
|
|
872
|
+
kind: "handcash",
|
|
873
|
+
transitional: true
|
|
874
|
+
};
|
|
783
875
|
}
|
|
876
|
+
};
|
|
877
|
+
return opt;
|
|
878
|
+
}
|
|
879
|
+
var NAMES = { [RDNS_EXTENSION]: "bWalletX", "space.bwallet.mobile": "bWalletX" };
|
|
880
|
+
async function probe(substrate, ms) {
|
|
881
|
+
try {
|
|
882
|
+
const w = new import_sdk4.WalletClient(substrate);
|
|
883
|
+
const ok = await Promise.race([w.isAuthenticated({}).then(() => true), new Promise((r) => setTimeout(() => r(false), ms))]);
|
|
884
|
+
return ok ? w : null;
|
|
885
|
+
} catch {
|
|
886
|
+
return null;
|
|
784
887
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
888
|
+
}
|
|
889
|
+
function cwiIsAnnounced(cwi, announced) {
|
|
890
|
+
return announced.some((o) => o.wallet === cwi || o.rdns === RDNS_MOBILE);
|
|
891
|
+
}
|
|
892
|
+
async function discoverWallets(o = {}) {
|
|
893
|
+
const out = [];
|
|
894
|
+
const ms = o.ms ?? 300;
|
|
895
|
+
if (typeof window !== "undefined") {
|
|
896
|
+
const seen = /* @__PURE__ */ new Map();
|
|
897
|
+
const onAnnounce = (e) => {
|
|
898
|
+
const d = e.detail;
|
|
899
|
+
if (!d?.wallet) return;
|
|
900
|
+
const rdns = d.info?.rdns ?? d.info?.uuid ?? `wallet-${seen.size}`;
|
|
901
|
+
if (seen.has(rdns)) return;
|
|
902
|
+
const isB = BWALLETX_RDNS.includes(rdns);
|
|
903
|
+
const substrate = rdns === RDNS_EXTENSION ? "extension" : rdns === RDNS_MOBILE ? "in-app" : "injected";
|
|
904
|
+
seen.set(rdns, brc100Option(d.wallet, { id: rdns, rdns, name: NAMES[rdns] ?? d.info?.name ?? rdns, icon: d.info?.icon, isBwalletX: isB, substrate }));
|
|
905
|
+
};
|
|
906
|
+
window.addEventListener("brc100:announceWallet", onAnnounce);
|
|
907
|
+
window.dispatchEvent(new Event("brc100:requestWallet"));
|
|
908
|
+
const local = o.probeLocal ? Promise.all([probe("json-api", ms), probe("Cicada", ms)]) : Promise.resolve([null, null]);
|
|
909
|
+
await new Promise((r) => setTimeout(r, seen.size > 0 || window.CWI ? Math.min(ms, 50) : ms));
|
|
910
|
+
window.removeEventListener("brc100:announceWallet", onAnnounce);
|
|
911
|
+
out.push(...seen.values());
|
|
912
|
+
if (![...seen.values()].some((w) => w.isBwalletX) && isFramed()) {
|
|
913
|
+
const x = await probeXdm(Math.max(ms, 1200));
|
|
914
|
+
if (x) out.push(brc100Option(x, { id: "bwalletx-xdm", name: "bWalletX", isBwalletX: true, substrate: "xdm" }));
|
|
788
915
|
}
|
|
789
|
-
|
|
916
|
+
const cwi = window.CWI;
|
|
917
|
+
if (cwi && !cwiIsAnnounced(cwi, [...seen.values()])) {
|
|
918
|
+
out.push(brc100Option(cwi, { id: "window.CWI", name: seen.size ? "Another browser wallet" : "Browser wallet", isBwalletX: false, substrate: "window.CWI" }));
|
|
919
|
+
}
|
|
920
|
+
const [json, cicada] = await local;
|
|
921
|
+
if (json) out.push(brc100Option(json, { id: "localhost:3321", name: "Desktop wallet (localhost:3321)", substrate: "json-api" }));
|
|
922
|
+
if (cicada) out.push(brc100Option(cicada, { id: "localhost:3301", name: "Desktop wallet (localhost:3301)", substrate: "cicada" }));
|
|
790
923
|
}
|
|
791
|
-
if (
|
|
792
|
-
|
|
793
|
-
|
|
924
|
+
if (o.pairing) out.push(pairingOption(o.pairing));
|
|
925
|
+
out.push(...o.extra ?? []);
|
|
926
|
+
const rank = (w) => w.isBwalletX ? 0 : w.kind === "handcash" ? 2 : 1;
|
|
927
|
+
return out.map((w, i) => ({ w, i })).sort((a, b) => rank(a.w) - rank(b.w) || a.i - b.i).map((x) => x.w);
|
|
928
|
+
}
|
|
929
|
+
var DESKTOP_OPTION_ID = "desktop";
|
|
930
|
+
function desktopOption(ms = 1500) {
|
|
931
|
+
const opt = {
|
|
932
|
+
id: DESKTOP_OPTION_ID,
|
|
933
|
+
name: "Desktop wallet",
|
|
934
|
+
kind: "brc100",
|
|
935
|
+
isBwalletX: false,
|
|
936
|
+
substrate: "json-api",
|
|
937
|
+
async connect(c) {
|
|
938
|
+
const [json, cicada] = await Promise.all([probe("json-api", ms), probe("Cicada", ms)]);
|
|
939
|
+
const w = json ?? cicada;
|
|
940
|
+
if (!w) throw Object.assign(new Error("No desktop wallet answered on this computer"), { code: "NOT_FOUND" });
|
|
941
|
+
opt.wallet = w;
|
|
942
|
+
opt.substrate = json ? "json-api" : "cicada";
|
|
943
|
+
return brc100Connected(w, opt, c);
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
return opt;
|
|
947
|
+
}
|
|
948
|
+
function autoChoice(list, force = false) {
|
|
949
|
+
const auto = list.filter((w) => !isLocalDesktop(w));
|
|
950
|
+
const detected = auto.filter((w) => w.kind === "brc100");
|
|
951
|
+
const others = auto.filter((w) => w.kind === "handcash" || w.kind === "custom");
|
|
952
|
+
const hasLocal = auto.length !== list.length;
|
|
953
|
+
if (force) return detected[0] ?? auto.find((w) => w.kind !== "pairing") ?? auto[0] ?? null;
|
|
954
|
+
if (!list.length) return null;
|
|
955
|
+
if (list.length === 1) return hasLocal ? void 0 : list[0];
|
|
956
|
+
if (detected.length === 1 && others.length === 0 && !hasLocal) return detected[0];
|
|
957
|
+
if (detected.length === 0 && others.length === 0) return auto[0] ?? void 0;
|
|
958
|
+
return void 0;
|
|
959
|
+
}
|
|
960
|
+
function isLocalDesktop(w) {
|
|
961
|
+
return w.substrate === "json-api" || w.substrate === "cicada" || w.id === "localhost:3321" || w.id === "localhost:3301" || w.id === DESKTOP_OPTION_ID;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// src/account.ts
|
|
965
|
+
var PAYMAIL_LOOKUP_URL = "https://pay.bwallet.space/api/paymail/lookup?key=";
|
|
966
|
+
var BAVATAR_URL = "https://chat.bwalletx.com/api/bavatar/";
|
|
967
|
+
function shortKey(key) {
|
|
968
|
+
return key.length > 12 ? `${key.slice(0, 6)}\u2026${key.slice(-4)}` : key;
|
|
969
|
+
}
|
|
970
|
+
function accountLabel(a) {
|
|
971
|
+
if (a.paymail && /@bwalletx\.com$/i.test(a.paymail)) return "$" + a.paymail.split("@")[0];
|
|
972
|
+
if (a.handle) return a.handle;
|
|
973
|
+
if (a.paymail) return a.paymail;
|
|
974
|
+
return a.identityKey ? shortKey(a.identityKey) : "";
|
|
975
|
+
}
|
|
976
|
+
function avatarUrlFor(identityKey) {
|
|
977
|
+
return identityKey && /^0[23][0-9a-fA-F]{64}$/.test(identityKey) ? `${BAVATAR_URL}${identityKey.toLowerCase()}.png` : null;
|
|
978
|
+
}
|
|
979
|
+
var cache = /* @__PURE__ */ new Map();
|
|
980
|
+
function lookupPaymail(identityKey, url = PAYMAIL_LOOKUP_URL) {
|
|
981
|
+
if (url === "off" || typeof fetch === "undefined") return Promise.resolve(null);
|
|
982
|
+
const k = `${url}|${identityKey}`;
|
|
983
|
+
let p = cache.get(k);
|
|
984
|
+
if (!p) {
|
|
985
|
+
p = fetch(url + encodeURIComponent(identityKey)).then((r) => r.ok ? r.json() : null).then((j) => j && typeof j.paymail === "string" ? j.paymail : null).catch(() => null);
|
|
986
|
+
cache.set(k, p);
|
|
794
987
|
}
|
|
795
|
-
|
|
796
|
-
return out;
|
|
988
|
+
return p;
|
|
797
989
|
}
|
|
798
|
-
function
|
|
799
|
-
|
|
800
|
-
|
|
990
|
+
function isUserRejection(e) {
|
|
991
|
+
const code = e?.code;
|
|
992
|
+
if (code === "ERR_PERMISSION_DENIED" || code === 4001) return true;
|
|
993
|
+
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
994
|
+
return /denied|reject|cancel|declin|refused|not now|user closed/i.test(msg);
|
|
995
|
+
}
|
|
996
|
+
function signInErrorText(e) {
|
|
997
|
+
if (isUserRejection(e)) return "Sign-in cancelled";
|
|
998
|
+
if (e?.name === "MetanetUnreachableError") return e.message;
|
|
999
|
+
const code = e?.code;
|
|
1000
|
+
if (code === "NOT_FOUND") return "bWalletX was not found in this browser";
|
|
1001
|
+
const msg = e instanceof Error ? e.message : String(e ?? "");
|
|
1002
|
+
if (/lock/i.test(msg)) return "Your wallet is locked. Unlock it and try again";
|
|
1003
|
+
if (/timeout|timed out|expired/i.test(msg)) return "The wallet did not answer in time";
|
|
1004
|
+
if (/origin/i.test(msg)) return "This page is not on the site\u2019s configured origin";
|
|
1005
|
+
return "Sign-in failed";
|
|
1006
|
+
}
|
|
1007
|
+
async function copyText(text) {
|
|
801
1008
|
try {
|
|
802
|
-
|
|
1009
|
+
await navigator.clipboard.writeText(text);
|
|
1010
|
+
return true;
|
|
803
1011
|
} catch {
|
|
804
|
-
|
|
1012
|
+
return false;
|
|
805
1013
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1014
|
+
}
|
|
1015
|
+
var IDENTITY_HINT_KEY = "bwx:identityKey";
|
|
1016
|
+
function rememberedIdentity() {
|
|
1017
|
+
try {
|
|
1018
|
+
const k = globalThis.localStorage?.getItem(IDENTITY_HINT_KEY) ?? "";
|
|
1019
|
+
return /^0[23][0-9a-f]{64}$/i.test(k) ? k : void 0;
|
|
1020
|
+
} catch {
|
|
1021
|
+
return void 0;
|
|
810
1022
|
}
|
|
811
|
-
if (!Number.isInteger(c.iat) || !Number.isInteger(c.exp) || c.exp <= c.iat) throw new AccessError("bad_request", "bad iat/exp");
|
|
812
|
-
if (c.sub !== void 0 && (typeof c.sub !== "string" || !/^0[23][0-9a-f]{64}$/.test(c.sub))) throw new AccessError("bad_request", "bad sub");
|
|
813
|
-
c.require = normaliseRequirement(c.require);
|
|
814
|
-
if (canonicalJson(c) !== s) throw new AccessError("bad_request", "challenge is not canonical");
|
|
815
|
-
return c;
|
|
816
1023
|
}
|
|
817
|
-
function
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
return "The request was declined in the wallet.";
|
|
823
|
-
case "wrong_account":
|
|
824
|
-
return "The wallet is signed in to a different account than this site expects.";
|
|
825
|
-
case "insufficient_tokens":
|
|
826
|
-
return `Not enough tokens: holding ${e.have ?? 0}, need ${e.need ?? "?"}.`;
|
|
827
|
-
case "identity_level_too_low":
|
|
828
|
-
return `Identity level ${e.have ?? 0} is below the required level ${e.need ?? "?"}.`;
|
|
829
|
-
case "kyc_required":
|
|
830
|
-
return "A verified identity (KYC) is required.";
|
|
831
|
-
case "indexer_unavailable":
|
|
832
|
-
return "Holdings could not be checked right now. Try again shortly.";
|
|
833
|
-
case "expired":
|
|
834
|
-
return "The request expired. Try again.";
|
|
835
|
-
case "replay":
|
|
836
|
-
return "That request was already used. Try again.";
|
|
837
|
-
case "invalid_signature":
|
|
838
|
-
return "The wallet signature did not verify.";
|
|
839
|
-
case "bad_request":
|
|
840
|
-
return "The request was malformed.";
|
|
1024
|
+
function rememberIdentity(key) {
|
|
1025
|
+
try {
|
|
1026
|
+
if (key) globalThis.localStorage?.setItem(IDENTITY_HINT_KEY, key);
|
|
1027
|
+
else globalThis.localStorage?.removeItem(IDENTITY_HINT_KEY);
|
|
1028
|
+
} catch {
|
|
841
1029
|
}
|
|
842
1030
|
}
|
|
843
1031
|
|
|
844
|
-
// src/
|
|
845
|
-
var
|
|
1032
|
+
// src/metanet.ts
|
|
1033
|
+
var import_sdk5 = require("@bsv/sdk");
|
|
1034
|
+
|
|
1035
|
+
// src/device.ts
|
|
1036
|
+
function isTouchDevice(env = globalThis) {
|
|
1037
|
+
const nav = env.navigator;
|
|
1038
|
+
if (!nav) return false;
|
|
1039
|
+
const ua = nav.userAgent ?? "";
|
|
1040
|
+
if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
|
|
1041
|
+
if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
|
|
1042
|
+
try {
|
|
1043
|
+
return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
|
|
1044
|
+
} catch {
|
|
1045
|
+
return false;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// src/metanet.ts
|
|
1050
|
+
var METANET_TYPE = "metanet";
|
|
1051
|
+
var METANET_OPTION_ID = METANET_TYPE;
|
|
1052
|
+
var METANET_JSON_API = "http://localhost:3321";
|
|
1053
|
+
var METANET_LABEL = "Use Metanet Desktop or another BRC-100 wallet";
|
|
1054
|
+
var METANET_NOTE = "Metanet Desktop must be running on this computer";
|
|
1055
|
+
var METANET_REMEMBERED_NOTE = "Signs in with Metanet Desktop on this computer.";
|
|
1056
|
+
var NOT_YOU_LABEL = "Not you? Use another wallet";
|
|
1057
|
+
var METANET_UNREACHABLE = "We couldn\u2019t reach Metanet Desktop. Open it, then try again.";
|
|
1058
|
+
var METANET_ANSWER_MS = 4e3;
|
|
1059
|
+
var METANET_SILENT_MS = 2e3;
|
|
1060
|
+
var MetanetUnreachableError = class extends Error {
|
|
1061
|
+
code = "NOT_FOUND";
|
|
1062
|
+
constructor() {
|
|
1063
|
+
super(METANET_UNREACHABLE);
|
|
1064
|
+
this.name = "MetanetUnreachableError";
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
function inAnyFrame(win = typeof window === "undefined" ? void 0 : window) {
|
|
1068
|
+
if (!win) return false;
|
|
1069
|
+
try {
|
|
1070
|
+
return win.top !== win.self;
|
|
1071
|
+
} catch {
|
|
1072
|
+
return true;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
function metanetOffered(env = {}) {
|
|
1076
|
+
const win = env.window ?? (typeof window === "undefined" ? void 0 : window);
|
|
1077
|
+
if (!win) return false;
|
|
1078
|
+
const nav = env.navigator ?? (typeof navigator === "undefined" ? void 0 : navigator);
|
|
1079
|
+
const mm = env.matchMedia ?? (typeof matchMedia === "undefined" ? void 0 : (q) => matchMedia(q));
|
|
1080
|
+
return !isTouchDevice({ navigator: nav, matchMedia: mm }) && !inAnyFrame(win);
|
|
1081
|
+
}
|
|
1082
|
+
function client(o) {
|
|
1083
|
+
if (o.make) return o.make();
|
|
1084
|
+
const originator = o.originator ?? (typeof location !== "undefined" && location.host ? location.host : void 0);
|
|
1085
|
+
return new import_sdk5.WalletClient("json-api", originator);
|
|
1086
|
+
}
|
|
1087
|
+
async function answers(wallet) {
|
|
1088
|
+
return Promise.resolve().then(() => wallet.getVersion({})).then((v) => typeof v?.version === "string", () => false);
|
|
1089
|
+
}
|
|
1090
|
+
async function connectMetanetDesktop(o = {}) {
|
|
1091
|
+
let wallet;
|
|
1092
|
+
try {
|
|
1093
|
+
wallet = client(o);
|
|
1094
|
+
} catch {
|
|
1095
|
+
throw new MetanetUnreachableError();
|
|
1096
|
+
}
|
|
1097
|
+
let timer;
|
|
1098
|
+
const ok = await Promise.race([answers(wallet), new Promise((r) => timer = setTimeout(() => r(false), o.timeoutMs ?? METANET_ANSWER_MS))]);
|
|
1099
|
+
clearTimeout(timer);
|
|
1100
|
+
if (!ok) throw new MetanetUnreachableError();
|
|
1101
|
+
return wallet;
|
|
1102
|
+
}
|
|
1103
|
+
function metanetOption(o = {}) {
|
|
1104
|
+
const opt = {
|
|
1105
|
+
id: METANET_OPTION_ID,
|
|
1106
|
+
name: METANET_LABEL,
|
|
1107
|
+
kind: "brc100",
|
|
1108
|
+
isBwalletX: false,
|
|
1109
|
+
substrate: "json-api",
|
|
1110
|
+
async connect() {
|
|
1111
|
+
const wallet = await connectMetanetDesktop(o);
|
|
1112
|
+
opt.wallet = wallet;
|
|
1113
|
+
await waitIfLocked(wallet);
|
|
1114
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true });
|
|
1115
|
+
return { wallet, payer: wallet, identityKey: publicKey, optionId: opt.id, kind: "brc100" };
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
return opt;
|
|
1119
|
+
}
|
|
1120
|
+
var KNOWN_KEYS_KEY = "bwx:known-keys";
|
|
1121
|
+
var KEY_RE = /^0[23][0-9a-f]{64}$/;
|
|
1122
|
+
var MAX_KNOWN = 8;
|
|
1123
|
+
function knownIdentityKeys() {
|
|
1124
|
+
try {
|
|
1125
|
+
const v = JSON.parse(globalThis.localStorage?.getItem(KNOWN_KEYS_KEY) || "[]");
|
|
1126
|
+
return Array.isArray(v) ? v.filter((k) => typeof k === "string" && KEY_RE.test(k)) : [];
|
|
1127
|
+
} catch {
|
|
1128
|
+
return [];
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
function isKnownIdentityKey(key) {
|
|
1132
|
+
if (!key) return false;
|
|
1133
|
+
const k = key.toLowerCase();
|
|
1134
|
+
if (!KEY_RE.test(k)) return false;
|
|
1135
|
+
return knownIdentityKeys().includes(k) || rememberedIdentity()?.toLowerCase() === k;
|
|
1136
|
+
}
|
|
1137
|
+
function rememberKnownIdentityKey(key) {
|
|
1138
|
+
const k = (key ?? "").toLowerCase();
|
|
1139
|
+
if (!KEY_RE.test(k)) return;
|
|
1140
|
+
try {
|
|
1141
|
+
const list = [k, ...knownIdentityKeys().filter((x) => x !== k)].slice(0, MAX_KNOWN);
|
|
1142
|
+
globalThis.localStorage?.setItem(KNOWN_KEYS_KEY, JSON.stringify(list));
|
|
1143
|
+
} catch {
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
var triedThisLoad = false;
|
|
1147
|
+
function silentAttemptUsed() {
|
|
1148
|
+
return triedThisLoad;
|
|
1149
|
+
}
|
|
1150
|
+
function claimSilentAttempt() {
|
|
1151
|
+
if (triedThisLoad) return false;
|
|
1152
|
+
triedThisLoad = true;
|
|
1153
|
+
return true;
|
|
1154
|
+
}
|
|
1155
|
+
function resetSilentAttempt() {
|
|
1156
|
+
triedThisLoad = false;
|
|
1157
|
+
}
|
|
1158
|
+
async function trySilentMetanet(o = {}) {
|
|
1159
|
+
if (!metanetOffered()) return { ok: false, stop: "not-here" };
|
|
1160
|
+
if (!claimSilentAttempt()) return { ok: false, stop: "used" };
|
|
1161
|
+
const run = async () => {
|
|
1162
|
+
let wallet;
|
|
1163
|
+
try {
|
|
1164
|
+
wallet = client(o);
|
|
1165
|
+
} catch {
|
|
1166
|
+
return { ok: false, stop: "unreachable" };
|
|
1167
|
+
}
|
|
1168
|
+
if (!await answers(wallet)) return { ok: false, stop: "unreachable" };
|
|
1169
|
+
const unlocked = await Promise.resolve().then(() => wallet.isAuthenticated({})).then((r2) => r2?.authenticated === true, () => false);
|
|
1170
|
+
if (!unlocked) return { ok: false, stop: "locked" };
|
|
1171
|
+
const identityKey = await Promise.resolve().then(() => wallet.getPublicKey({ identityKey: true })).then((r2) => r2.publicKey, () => null);
|
|
1172
|
+
if (!identityKey) return { ok: false, stop: "unreachable" };
|
|
1173
|
+
if (!isKnownIdentityKey(identityKey)) return { ok: false, stop: "unknown-key" };
|
|
1174
|
+
return { ok: true, wallet, identityKey };
|
|
1175
|
+
};
|
|
1176
|
+
let timer;
|
|
1177
|
+
const r = await Promise.race([
|
|
1178
|
+
run().catch(() => ({ ok: false, stop: "unreachable" })),
|
|
1179
|
+
new Promise((res) => timer = setTimeout(() => res({ ok: false, stop: "slow" }), o.windowMs ?? METANET_SILENT_MS))
|
|
1180
|
+
]);
|
|
1181
|
+
clearTimeout(timer);
|
|
1182
|
+
return r;
|
|
1183
|
+
}
|
|
1184
|
+
var warnedLogout = false;
|
|
1185
|
+
var isProduction = () => {
|
|
1186
|
+
try {
|
|
1187
|
+
return globalThis.process?.env?.NODE_ENV === "production";
|
|
1188
|
+
} catch {
|
|
1189
|
+
return false;
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
function warnIfNoLogout(o) {
|
|
1193
|
+
if (!o.verifyUrl || o.logoutUrl || warnedLogout || isProduction() || typeof console === "undefined") return false;
|
|
1194
|
+
warnedLogout = true;
|
|
1195
|
+
console.warn(
|
|
1196
|
+
`@bwalletx/connect: ${o.where} has a verify URL but no logout URL. Set it: "Not you? Use another wallet" and Disconnect must be able to end a session the verify route created.`
|
|
1197
|
+
);
|
|
1198
|
+
return true;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// src/choice.ts
|
|
1202
|
+
var BWALLETX_TYPE = "bwalletx";
|
|
1203
|
+
function substrateOf(o) {
|
|
1204
|
+
if (o.substrate) return o.substrate;
|
|
1205
|
+
if (o.kind === "pairing") return "pairing";
|
|
1206
|
+
if (o.isBwalletX && o.kind === "brc100") return o.rdns === RDNS_MOBILE ? "in-app" : o.id === "bwalletx-xdm" ? "xdm" : "extension";
|
|
1207
|
+
if (o.kind === "handcash" || o.kind === "custom") return "adapter";
|
|
1208
|
+
if (o.rdns === RDNS_EXTENSION) return "extension";
|
|
1209
|
+
if (o.rdns === RDNS_MOBILE) return "in-app";
|
|
1210
|
+
if (o.id === "window.CWI") return "window.CWI";
|
|
1211
|
+
if (o.id === "localhost:3321" || o.id === DESKTOP_OPTION_ID) return "json-api";
|
|
1212
|
+
if (o.id === "localhost:3301") return "cicada";
|
|
1213
|
+
return "injected";
|
|
1214
|
+
}
|
|
1215
|
+
function walletTypeOf(o) {
|
|
1216
|
+
return o.isBwalletX ? BWALLETX_TYPE : o.rdns ?? o.id;
|
|
1217
|
+
}
|
|
1218
|
+
var OWN_MARKERS = ["extension", "in-app", "xdm"];
|
|
1219
|
+
function isPresentBwalletX(o) {
|
|
1220
|
+
return o.isBwalletX && o.kind === "brc100" && !!o.wallet && OWN_MARKERS.includes(substrateOf(o));
|
|
1221
|
+
}
|
|
1222
|
+
function hasBwalletX(list) {
|
|
1223
|
+
return list.some(isPresentBwalletX);
|
|
1224
|
+
}
|
|
1225
|
+
function otherDetected(list) {
|
|
1226
|
+
return list.filter((o) => !o.isBwalletX && o.kind === "brc100" && !!o.wallet);
|
|
1227
|
+
}
|
|
1228
|
+
function chooserOrder(list) {
|
|
1229
|
+
const rank = (o) => isPresentBwalletX(o) ? 0 : o.isBwalletX ? 1 : o.kind === "brc100" ? 2 : o.kind === "custom" ? 3 : 4;
|
|
1230
|
+
return list.map((o, i) => ({ o, i })).sort((a, b) => rank(a.o) - rank(b.o) || a.i - b.i).map((x) => x.o);
|
|
1231
|
+
}
|
|
1232
|
+
function decideWallet(list, o = {}) {
|
|
1233
|
+
if (o.walletId) {
|
|
1234
|
+
const w = list.find((x) => x.id === o.walletId);
|
|
1235
|
+
if (w) return { option: w, reason: "requested" };
|
|
1236
|
+
}
|
|
1237
|
+
if (o.chooser) return { option: null, reason: list.length || o.picker !== false ? "chooser" : "none" };
|
|
1238
|
+
if (o.fastPath === false) {
|
|
1239
|
+
const a = autoChoice([...list], o.picker === false);
|
|
1240
|
+
return a === void 0 ? { option: null, reason: "chooser" } : a === null ? { option: null, reason: "none" } : { option: a, reason: "legacy" };
|
|
1241
|
+
}
|
|
1242
|
+
if (o.remembered && !o.preferBwalletX) {
|
|
1243
|
+
const m = matchRemembered(list, o.remembered);
|
|
1244
|
+
if (m) return { option: m, reason: "remembered" };
|
|
1245
|
+
}
|
|
1246
|
+
const b = list.find(isPresentBwalletX);
|
|
1247
|
+
if (b) return { option: b, reason: "bwalletx" };
|
|
1248
|
+
if (o.picker === false) {
|
|
1249
|
+
const phone = list.find((x) => x.isBwalletX);
|
|
1250
|
+
return phone ? { option: phone, reason: "bwalletx-phone" } : { option: null, reason: "none" };
|
|
1251
|
+
}
|
|
1252
|
+
return list.length ? { option: null, reason: "chooser" } : { option: null, reason: "none" };
|
|
1253
|
+
}
|
|
1254
|
+
function outcomeOf(e) {
|
|
1255
|
+
const err = e;
|
|
1256
|
+
if (err?.code === "NOT_FOUND") return "not-found";
|
|
1257
|
+
if (err?.name === "AbortError" || err?.message === "cancelled") return "cancelled";
|
|
1258
|
+
if (isUserRejection(e)) return "rejected";
|
|
1259
|
+
return "error";
|
|
1260
|
+
}
|
|
1261
|
+
function attemptFor(option, outcome, startedAt, now = Date.now()) {
|
|
1262
|
+
return {
|
|
1263
|
+
walletType: option ? walletTypeOf(option) : null,
|
|
1264
|
+
substrate: option ? substrateOf(option) : null,
|
|
1265
|
+
outcome,
|
|
1266
|
+
ms: Math.max(0, Math.round(now - startedAt))
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
function reportAttempt(hook, a) {
|
|
1270
|
+
if (!hook) return;
|
|
1271
|
+
try {
|
|
1272
|
+
hook(Object.freeze({ walletType: a.walletType, substrate: a.substrate, outcome: a.outcome, ms: a.ms }));
|
|
1273
|
+
} catch {
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
var WALLET_CHOICE_KEY = "bwx:wallet";
|
|
1277
|
+
var SUBSTRATES = ["extension", "in-app", "xdm", "injected", "window.CWI", "json-api", "cicada", "pairing", "adapter"];
|
|
1278
|
+
var TYPE_RE = /^[A-Za-z0-9._:-]{1,64}$/;
|
|
1279
|
+
var KEY_LIKE = /^(0[23][0-9a-f]{64}|04[0-9a-f]{128}|[13][1-9A-HJ-NP-Za-km-z]{25,34})$/i;
|
|
1280
|
+
function isRememberable(o) {
|
|
1281
|
+
const t = walletTypeOf(o);
|
|
1282
|
+
return substrateOf(o) !== "window.CWI" && TYPE_RE.test(t) && !KEY_LIKE.test(t);
|
|
1283
|
+
}
|
|
1284
|
+
function isExplicitPick(o, reason) {
|
|
1285
|
+
return o.isBwalletX || reason === "chooser" || reason === "requested" || reason === "remembered";
|
|
1286
|
+
}
|
|
1287
|
+
function rememberWalletChoice(o) {
|
|
1288
|
+
try {
|
|
1289
|
+
if (!o) return void globalThis.localStorage?.removeItem(WALLET_CHOICE_KEY);
|
|
1290
|
+
if (!isRememberable(o)) return;
|
|
1291
|
+
const v = { type: walletTypeOf(o), substrate: substrateOf(o), v: 1 };
|
|
1292
|
+
globalThis.localStorage?.setItem(WALLET_CHOICE_KEY, JSON.stringify(v));
|
|
1293
|
+
} catch {
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
function rememberedWalletChoice() {
|
|
1297
|
+
try {
|
|
1298
|
+
const raw = globalThis.localStorage?.getItem(WALLET_CHOICE_KEY);
|
|
1299
|
+
if (!raw) return null;
|
|
1300
|
+
const j = JSON.parse(raw);
|
|
1301
|
+
if (typeof j.type !== "string" || !TYPE_RE.test(j.type) || KEY_LIKE.test(j.type)) return null;
|
|
1302
|
+
if (!SUBSTRATES.includes(j.substrate) || j.substrate === "window.CWI") return null;
|
|
1303
|
+
return { type: j.type, substrate: j.substrate };
|
|
1304
|
+
} catch {
|
|
1305
|
+
return null;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
function forgetWalletChoice() {
|
|
1309
|
+
rememberWalletChoice(null);
|
|
1310
|
+
}
|
|
1311
|
+
function needsBwalletX(type) {
|
|
1312
|
+
return !!type && type !== BWALLETX_TYPE;
|
|
1313
|
+
}
|
|
1314
|
+
function tapGoesElsewhere(type) {
|
|
1315
|
+
return needsBwalletX(type) && type !== METANET_TYPE && type !== DESKTOP_OPTION_ID;
|
|
1316
|
+
}
|
|
1317
|
+
function isLocalDesktopType(r) {
|
|
1318
|
+
return r.type === METANET_TYPE || r.type === DESKTOP_OPTION_ID || r.substrate === "json-api" || r.substrate === "cicada";
|
|
1319
|
+
}
|
|
1320
|
+
function matchRemembered(list, r) {
|
|
1321
|
+
if (isLocalDesktopType(r)) return null;
|
|
1322
|
+
if (r.type === BWALLETX_TYPE) {
|
|
1323
|
+
return (r.substrate === "pairing" ? list.find((o) => o.kind === "pairing") : list.find(isPresentBwalletX)) ?? null;
|
|
1324
|
+
}
|
|
1325
|
+
return list.find((o) => !o.isBwalletX && !isLocalDesktop(o) && isRememberable(o) && walletTypeOf(o) === r.type) ?? null;
|
|
1326
|
+
}
|
|
1327
|
+
var ATTACHABLE = ["extension", "in-app", "xdm", "injected"];
|
|
1328
|
+
function planAutoConnect(o) {
|
|
1329
|
+
const r = o.remembered;
|
|
1330
|
+
if (!r || o.enabled === false) return { action: "none", remembered: r, needsBwalletX: false };
|
|
1331
|
+
if (r.type === METANET_TYPE) {
|
|
1332
|
+
return { action: o.metanetHere ?? metanetOffered() ? "silent-metanet" : "none", remembered: r, needsBwalletX: false };
|
|
1333
|
+
}
|
|
1334
|
+
if (isLocalDesktopType(r)) return { action: "none", remembered: r, needsBwalletX: false };
|
|
1335
|
+
const action = o.signedIn && ATTACHABLE.includes(r.substrate) ? "attach" : "wait-for-tap";
|
|
1336
|
+
return { action, remembered: r, needsBwalletX: needsBwalletX(r.type) };
|
|
1337
|
+
}
|
|
1338
|
+
async function attachRemembered(r, o = {}) {
|
|
1339
|
+
if (!ATTACHABLE.includes(r.substrate)) return null;
|
|
1340
|
+
const list = await discoverWallets({ ms: o.ms });
|
|
1341
|
+
const m = matchRemembered(list, r);
|
|
1342
|
+
return m && m.kind === "brc100" && m.wallet && ATTACHABLE.includes(substrateOf(m)) ? m : null;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// src/modal.ts
|
|
1346
|
+
var import_qrcode_generator = __toESM(require("qrcode-generator"), 1);
|
|
1347
|
+
var CSS2 = `
|
|
1348
|
+
.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}
|
|
1349
|
+
.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)}
|
|
1350
|
+
.bwx-box h2{margin:0 0 6px;font-size:18px;color:#e8c766}
|
|
1351
|
+
.bwx-box p{margin:0 0 14px;color:#bdb6a3;font-size:14px}
|
|
1352
|
+
.bwx-qr{background:#fff;border-radius:12px;padding:12px;display:flex;justify-content:center}
|
|
1353
|
+
.bwx-qr svg{width:100%;height:auto;max-width:260px}
|
|
1354
|
+
.bwx-code{margin:14px 0 0;text-align:center;font-size:28px;letter-spacing:.3em;color:#e8c766;font-variant-numeric:tabular-nums}
|
|
1355
|
+
.bwx-row{display:flex;gap:8px;margin-top:14px}
|
|
1356
|
+
.bwx-btn{flex:1;border:1px solid #3a3220;background:#15130e;color:#f4f1e8;border-radius:10px;padding:10px 12px;font:inherit;cursor:pointer}
|
|
1357
|
+
.bwx-btn:hover{border-color:#e8c766}
|
|
1358
|
+
a.bwx-btn{display:block;text-align:center;text-decoration:none;box-sizing:border-box}
|
|
1359
|
+
.bwx-primary{background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;border:0;font-weight:700;min-height:48px;padding:13px 12px}
|
|
1360
|
+
.bwx-stack{display:flex;flex-direction:column;gap:8px}
|
|
1361
|
+
.bwx-list{display:flex;flex-direction:column;gap:8px}
|
|
1362
|
+
.bwx-opt{display:flex;align-items:center;gap:10px;text-align:left}
|
|
1363
|
+
.bwx-opt img{width:24px;height:24px;border-radius:6px}
|
|
1364
|
+
.bwx-tag{margin-left:auto;font-size:11px;color:#8d866f;white-space:nowrap}
|
|
1365
|
+
.bwx-opt{min-height:48px;box-sizing:border-box}
|
|
1366
|
+
a.bwx-btn.bwx-opt{display:flex;text-align:left}
|
|
1367
|
+
.bwx-btn:focus{outline:none}
|
|
1368
|
+
.bwx-btn:focus-visible{outline:2px solid #e8c766;outline-offset:2px}
|
|
1369
|
+
.bwx-mk{display:inline-flex;align-items:center;justify-content:center;flex:none;width:28px;height:28px;border-radius:50%;background:#000}
|
|
1370
|
+
.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}
|
|
1371
|
+
.bwx-opt img{width:28px;height:28px;border-radius:8px;flex:none}
|
|
1372
|
+
.bwx-txt{display:flex;flex-direction:column;min-width:0;gap:1px}
|
|
1373
|
+
.bwx-txt b{font-weight:600}
|
|
1374
|
+
.bwx-txt small{font-size:12px;color:#8d866f;font-weight:400}
|
|
1375
|
+
.bwx-primary .bwx-txt small{color:rgba(0,0,0,.68)}
|
|
1376
|
+
.bwx-primary .bwx-tag{color:rgba(0,0,0,.6)}
|
|
1377
|
+
.bwx-sep{font-size:12px;color:#8d866f;margin:6px 2px 0}
|
|
1378
|
+
.bwx-foot{font-size:12px;color:#8d866f;margin:12px 0 0}
|
|
1379
|
+
`;
|
|
1380
|
+
function mount() {
|
|
1381
|
+
const root = document.createElement("div");
|
|
1382
|
+
root.className = "bwx-ov";
|
|
1383
|
+
root.setAttribute("role", "dialog");
|
|
1384
|
+
root.setAttribute("aria-modal", "true");
|
|
1385
|
+
const style = document.createElement("style");
|
|
1386
|
+
style.textContent = CSS2;
|
|
1387
|
+
const box = document.createElement("div");
|
|
1388
|
+
box.className = "bwx-box";
|
|
1389
|
+
root.append(style, box);
|
|
1390
|
+
document.body.appendChild(root);
|
|
1391
|
+
return { root, box, close: () => root.remove() };
|
|
1392
|
+
}
|
|
1393
|
+
function el(tag, cls, text) {
|
|
1394
|
+
const e = document.createElement(tag);
|
|
1395
|
+
if (cls) e.className = cls;
|
|
1396
|
+
if (text != null) e.textContent = text;
|
|
1397
|
+
return e;
|
|
1398
|
+
}
|
|
1399
|
+
function qrSvg(text) {
|
|
1400
|
+
const q = (0, import_qrcode_generator.default)(0, "M");
|
|
1401
|
+
q.addData(text);
|
|
1402
|
+
q.make();
|
|
1403
|
+
return q.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
|
|
1404
|
+
}
|
|
1405
|
+
function pairingModal(o = {}) {
|
|
1406
|
+
const ac = new AbortController();
|
|
1407
|
+
let ui = null;
|
|
1408
|
+
let codeEl = null;
|
|
1409
|
+
const close = () => {
|
|
1410
|
+
ui?.close();
|
|
1411
|
+
ui = null;
|
|
1412
|
+
};
|
|
1413
|
+
ac.signal.addEventListener("abort", close);
|
|
1414
|
+
return {
|
|
1415
|
+
signal: ac.signal,
|
|
1416
|
+
close,
|
|
1417
|
+
onLink(link) {
|
|
1418
|
+
if (typeof document === "undefined") return;
|
|
1419
|
+
close();
|
|
1420
|
+
ui = mount();
|
|
1421
|
+
const app = o.mode === "app" || (o.mode ?? "auto") === "auto" && isTouchDevice();
|
|
1422
|
+
const h = el("h2", "", o.title ?? "Connect bWalletX");
|
|
1423
|
+
const cancel = el("button", "bwx-btn", "Cancel");
|
|
1424
|
+
cancel.onclick = () => {
|
|
1425
|
+
o.onCancel?.();
|
|
1426
|
+
ac.abort();
|
|
1427
|
+
};
|
|
1428
|
+
codeEl = el("div", "bwx-code", "");
|
|
1429
|
+
codeEl.setAttribute("aria-live", "polite");
|
|
1430
|
+
if (app) {
|
|
1431
|
+
const p2 = el("p", "", "Open bWalletX, approve, then come back to this page.");
|
|
1432
|
+
const stack = el("div", "bwx-stack");
|
|
1433
|
+
const open = el("a", "bwx-btn bwx-primary", "Open bWalletX");
|
|
1434
|
+
open.href = link;
|
|
1435
|
+
open.rel = "noopener";
|
|
1436
|
+
const web = el("button", "bwx-btn", "Use web.bwalletx.com instead");
|
|
1437
|
+
web.onclick = () => {
|
|
1438
|
+
void navigator.clipboard?.writeText(link).catch(() => void 0);
|
|
1439
|
+
window.open(WEB_WALLET_URL, "_blank", "noopener");
|
|
1440
|
+
p2.textContent = "Link copied. In web.bwalletx.com tap Connect and paste it, then come back here.";
|
|
1441
|
+
};
|
|
1442
|
+
stack.append(open, web, cancel);
|
|
1443
|
+
ui.box.append(h, p2, codeEl, stack);
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
const p = el("p", "", "Scan with bWalletX on your phone, or paste the link into web.bwalletx.com.");
|
|
1447
|
+
const qr = el("div", "bwx-qr");
|
|
1448
|
+
qr.innerHTML = qrSvg(link);
|
|
1449
|
+
const row = el("div", "bwx-row");
|
|
1450
|
+
const copy = el("button", "bwx-btn", "Copy link");
|
|
1451
|
+
copy.onclick = () => void navigator.clipboard?.writeText(link).then(() => copy.textContent = "Copied");
|
|
1452
|
+
row.append(copy, cancel);
|
|
1453
|
+
ui.box.append(h, p, qr, codeEl, row);
|
|
1454
|
+
},
|
|
1455
|
+
onCode(code) {
|
|
1456
|
+
if (codeEl) {
|
|
1457
|
+
codeEl.textContent = code;
|
|
1458
|
+
const p = ui?.box.querySelector("p");
|
|
1459
|
+
if (p) p.textContent = "Check bWalletX shows the same code, then tap Connect.";
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
function pickWallet(options, o = {}) {
|
|
1465
|
+
if (typeof document === "undefined") return Promise.resolve(null);
|
|
1466
|
+
return new Promise((resolve) => {
|
|
1467
|
+
const ui = mount();
|
|
1468
|
+
const done = (w) => {
|
|
1469
|
+
ui.close();
|
|
1470
|
+
resolve(w);
|
|
1471
|
+
};
|
|
1472
|
+
ui.box.append(el("h2", "", o.title ?? "Choose a wallet"));
|
|
1473
|
+
if (!options.length) ui.box.append(el("p", "", "No wallet found. Get bWalletX at bwalletx.com."));
|
|
1474
|
+
const list = el("div", "bwx-list");
|
|
1475
|
+
for (const w of options) {
|
|
1476
|
+
const b = el("button", "bwx-btn bwx-opt");
|
|
1477
|
+
if (w.icon) {
|
|
1478
|
+
const img = el("img");
|
|
1479
|
+
img.src = w.icon;
|
|
1480
|
+
img.alt = "";
|
|
1481
|
+
b.append(img);
|
|
1482
|
+
}
|
|
1483
|
+
b.append(el("span", "", w.name));
|
|
1484
|
+
if (w.kind === "handcash") b.append(el("span", "bwx-tag", "until BRC-100"));
|
|
1485
|
+
b.onclick = () => done(w);
|
|
1486
|
+
list.append(b);
|
|
1487
|
+
}
|
|
1488
|
+
const cancel = el("button", "bwx-btn", "Cancel");
|
|
1489
|
+
cancel.onclick = () => done(null);
|
|
1490
|
+
ui.root.onclick = (e) => e.target === ui.root && done(null);
|
|
1491
|
+
ui.box.append(list, el("div", "bwx-row"));
|
|
1492
|
+
ui.box.lastChild.append(cancel);
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
function optionRow(w, primary, sub, tag) {
|
|
1496
|
+
const b = el("button", `bwx-btn bwx-opt${primary ? " bwx-primary" : ""}`);
|
|
1497
|
+
b.type = "button";
|
|
1498
|
+
b.dataset.wallet = w.id;
|
|
1499
|
+
if (w.isBwalletX) {
|
|
1500
|
+
const mk = el("span", "bwx-mk");
|
|
1501
|
+
mk.innerHTML = markSvg(18);
|
|
1502
|
+
b.append(mk);
|
|
1503
|
+
} else if (w.icon) {
|
|
1504
|
+
const img = el("img");
|
|
1505
|
+
img.src = w.icon;
|
|
1506
|
+
img.alt = "";
|
|
1507
|
+
b.append(img);
|
|
1508
|
+
} else {
|
|
1509
|
+
b.append(el("span", "bwx-ico", (w.name.trim().charAt(0) || "W").toUpperCase()));
|
|
1510
|
+
}
|
|
1511
|
+
const t = el("span", "bwx-txt");
|
|
1512
|
+
t.append(el("b", "", w.name));
|
|
1513
|
+
if (sub) t.append(el("small", "", sub));
|
|
1514
|
+
b.append(t);
|
|
1515
|
+
if (tag) b.append(el("span", "bwx-tag", tag));
|
|
1516
|
+
return b;
|
|
1517
|
+
}
|
|
1518
|
+
function chooseWallet(options, o = {}) {
|
|
1519
|
+
if (typeof document === "undefined") return Promise.resolve(null);
|
|
1520
|
+
return new Promise((resolve) => {
|
|
1521
|
+
const ui = mount();
|
|
1522
|
+
ui.root.dataset.bwxChooser = "";
|
|
1523
|
+
const done = (w) => {
|
|
1524
|
+
document.removeEventListener("keydown", onKey, true);
|
|
1525
|
+
ui.close();
|
|
1526
|
+
resolve(w);
|
|
1527
|
+
};
|
|
1528
|
+
const onKey = (e) => {
|
|
1529
|
+
if (e.key === "Escape") {
|
|
1530
|
+
e.preventDefault();
|
|
1531
|
+
done(null);
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
document.addEventListener("keydown", onKey, true);
|
|
1535
|
+
ui.box.append(el("h2", "", o.title ?? "Choose a wallet"));
|
|
1536
|
+
const list = el("div", "bwx-list");
|
|
1537
|
+
const here = hasBwalletX(options);
|
|
1538
|
+
if (!here) {
|
|
1539
|
+
const get = el("a", "bwx-btn bwx-opt bwx-primary");
|
|
1540
|
+
get.href = o.getUrl ?? GET_URL;
|
|
1541
|
+
get.target = "_blank";
|
|
1542
|
+
get.rel = "noopener";
|
|
1543
|
+
get.dataset.wallet = "get-bwalletx";
|
|
1544
|
+
const mk = el("span", "bwx-mk");
|
|
1545
|
+
mk.innerHTML = markSvg(18);
|
|
1546
|
+
const t = el("span", "bwx-txt");
|
|
1547
|
+
t.append(el("b", "", "Get bWalletX"), el("small", "", "Free. Every feature on this site"));
|
|
1548
|
+
get.append(mk, t);
|
|
1549
|
+
list.append(get);
|
|
1550
|
+
}
|
|
1551
|
+
const ordered = chooserOrder(options);
|
|
1552
|
+
const touch = isTouchDevice();
|
|
1553
|
+
let sep = false;
|
|
1554
|
+
for (const w of ordered) {
|
|
1555
|
+
if (!here && !sep) {
|
|
1556
|
+
list.append(el("p", "bwx-sep", "Or use"));
|
|
1557
|
+
sep = true;
|
|
1558
|
+
}
|
|
1559
|
+
const sub = isPresentBwalletX(w) ? "Recommended" : w.kind === "pairing" ? touch ? "Open the bWalletX app" : "Scan a QR with your phone" : void 0;
|
|
1560
|
+
const tag = w.kind === "handcash" ? "until BRC-100" : !w.isBwalletX && w.kind === "brc100" ? "Fewer features" : void 0;
|
|
1561
|
+
const row2 = optionRow(w, isPresentBwalletX(w), sub, tag);
|
|
1562
|
+
row2.onclick = () => done(w);
|
|
1563
|
+
list.append(row2);
|
|
1564
|
+
}
|
|
1565
|
+
if (o.desktop !== false && metanetOffered() && !options.some((w) => w.substrate === "json-api" || w.substrate === "cicada")) {
|
|
1566
|
+
if (!here && !sep) list.append(el("p", "bwx-sep", "Or use"));
|
|
1567
|
+
const d = metanetOption();
|
|
1568
|
+
const row2 = optionRow(d, false, METANET_NOTE);
|
|
1569
|
+
row2.title = "Connects to Metanet Desktop on this computer, never to a browser extension.";
|
|
1570
|
+
row2.onclick = () => done(d);
|
|
1571
|
+
list.append(row2);
|
|
1572
|
+
}
|
|
1573
|
+
ui.box.append(list);
|
|
1574
|
+
if (otherDetected(options).length || !here) ui.box.append(el("p", "bwx-foot", "Some features need bWalletX."));
|
|
1575
|
+
const row = el("div", "bwx-row");
|
|
1576
|
+
const cancel = el("button", "bwx-btn", "Cancel");
|
|
1577
|
+
cancel.type = "button";
|
|
1578
|
+
cancel.onclick = () => done(null);
|
|
1579
|
+
row.append(cancel);
|
|
1580
|
+
ui.box.append(row);
|
|
1581
|
+
ui.root.onclick = (e) => e.target === ui.root && done(null);
|
|
1582
|
+
list.querySelector("a,button")?.focus();
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// src/access-client.ts
|
|
1587
|
+
var import_sdk6 = require("@bsv/sdk");
|
|
1588
|
+
|
|
1589
|
+
// src/access.ts
|
|
1590
|
+
var ACCESS_VERSION = 1;
|
|
1591
|
+
var ACCESS_PROTOCOL = [2, "bwalletx access"];
|
|
1592
|
+
var MAX_HOLDING_PROOFS = 50;
|
|
1593
|
+
var ACCESS_ERROR_STATUS = {
|
|
1594
|
+
not_installed: 424,
|
|
1595
|
+
user_rejected: 403,
|
|
1596
|
+
wrong_account: 409,
|
|
1597
|
+
insufficient_tokens: 403,
|
|
1598
|
+
identity_level_too_low: 403,
|
|
1599
|
+
kyc_required: 403,
|
|
1600
|
+
indexer_unavailable: 503,
|
|
1601
|
+
expired: 401,
|
|
1602
|
+
replay: 401,
|
|
1603
|
+
invalid_signature: 401,
|
|
1604
|
+
bad_request: 400
|
|
1605
|
+
};
|
|
1606
|
+
var AccessError = class extends Error {
|
|
1607
|
+
code;
|
|
1608
|
+
have;
|
|
1609
|
+
need;
|
|
1610
|
+
constructor(code, message, extra = {}) {
|
|
1611
|
+
super(message ?? code);
|
|
1612
|
+
this.name = "AccessError";
|
|
1613
|
+
this.code = code;
|
|
1614
|
+
this.have = extra.have;
|
|
1615
|
+
this.need = extra.need;
|
|
1616
|
+
}
|
|
1617
|
+
toJSON() {
|
|
1618
|
+
const b = { error: this.code, message: this.message };
|
|
1619
|
+
if (this.have !== void 0) b.have = this.have;
|
|
1620
|
+
if (this.need !== void 0) b.need = this.need;
|
|
1621
|
+
return b;
|
|
1622
|
+
}
|
|
1623
|
+
};
|
|
1624
|
+
var PROTOCOL_MEDIA = {
|
|
1625
|
+
"401": { name: "Identity", color: "#22c55e", video: "assets/protocol/401.mp4", poster: "assets/protocol/401.jpg" },
|
|
1626
|
+
"402": { name: "Payment", color: "#3b82f6", video: "assets/protocol/402.mp4", poster: "assets/protocol/402.jpg" },
|
|
1627
|
+
"403": { name: "Access", color: "#ef4444", video: "assets/protocol/403.mp4", poster: "assets/protocol/403.jpg" }
|
|
1628
|
+
};
|
|
1629
|
+
function protocolMediaUrl(kind, which, version = "latest") {
|
|
1630
|
+
return `https://unpkg.com/@bwalletx/connect@${version}/${PROTOCOL_MEDIA[kind][which]}`;
|
|
1631
|
+
}
|
|
1632
|
+
function canonicalJson(v) {
|
|
1633
|
+
if (v === null) return "null";
|
|
1634
|
+
if (typeof v === "string" || typeof v === "boolean") return JSON.stringify(v);
|
|
1635
|
+
if (typeof v === "number") {
|
|
1636
|
+
if (!Number.isFinite(v)) throw new Error("canonicalJson: non-finite number");
|
|
1637
|
+
return JSON.stringify(v);
|
|
1638
|
+
}
|
|
1639
|
+
if (Array.isArray(v)) return `[${v.map((x) => canonicalJson(x)).join(",")}]`;
|
|
1640
|
+
if (typeof v === "object") {
|
|
1641
|
+
const o = v;
|
|
1642
|
+
const keys = Object.keys(o).filter((k) => o[k] !== void 0).sort();
|
|
1643
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(o[k])}`).join(",")}}`;
|
|
1644
|
+
}
|
|
1645
|
+
throw new Error(`canonicalJson: unsupported ${typeof v}`);
|
|
1646
|
+
}
|
|
1647
|
+
function challengeBytes(challenge) {
|
|
1648
|
+
return Array.from(new TextEncoder().encode(challenge));
|
|
1649
|
+
}
|
|
1650
|
+
var TOKEN_ID = /^bsv21:[0-9a-f]{64}_\d{1,6}$/;
|
|
1651
|
+
var RAW = /^[1-9]\d{0,38}$/;
|
|
1652
|
+
function normaliseRequirement(r) {
|
|
1653
|
+
if (!r || typeof r !== "object") throw new AccessError("bad_request", "require must be an object");
|
|
1654
|
+
const req = r;
|
|
1655
|
+
const out = {};
|
|
1656
|
+
if (req.token !== void 0) {
|
|
1657
|
+
const t = req.token;
|
|
1658
|
+
if (!t || typeof t !== "object" || typeof t.id !== "string") throw new AccessError("bad_request", "require.token.id is required");
|
|
1659
|
+
const id = t.id.trim().toLowerCase();
|
|
1660
|
+
if (!TOKEN_ID.test(id)) throw new AccessError("bad_request", "require.token.id must be 'bsv21:<txid>_<vout>'");
|
|
1661
|
+
const hasMin = t.min !== void 0;
|
|
1662
|
+
const hasBps = t.minBps !== void 0;
|
|
1663
|
+
if (hasMin === hasBps) throw new AccessError("bad_request", "require.token needs exactly one of min or minBps");
|
|
1664
|
+
out.token = { id };
|
|
1665
|
+
if (hasMin) {
|
|
1666
|
+
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)");
|
|
1667
|
+
out.token.min = t.min;
|
|
1668
|
+
} else {
|
|
1669
|
+
if (!Number.isInteger(t.minBps) || t.minBps < 1 || t.minBps > 1e4) {
|
|
1670
|
+
throw new AccessError("bad_request", "require.token.minBps must be an integer 1..10000");
|
|
1671
|
+
}
|
|
1672
|
+
out.token.minBps = t.minBps;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
if (req.identityLevel !== void 0) {
|
|
1676
|
+
if (!Number.isInteger(req.identityLevel) || req.identityLevel < 1 || req.identityLevel > 10) {
|
|
1677
|
+
throw new AccessError("bad_request", "require.identityLevel must be an integer 1..10");
|
|
1678
|
+
}
|
|
1679
|
+
out.identityLevel = req.identityLevel;
|
|
1680
|
+
}
|
|
1681
|
+
if (req.kyc !== void 0) {
|
|
1682
|
+
if (typeof req.kyc !== "boolean") throw new AccessError("bad_request", "require.kyc must be a boolean");
|
|
1683
|
+
if (req.kyc) out.kyc = true;
|
|
1684
|
+
}
|
|
1685
|
+
if (!out.token && !out.identityLevel && !out.kyc) throw new AccessError("bad_request", "require is empty");
|
|
1686
|
+
return out;
|
|
1687
|
+
}
|
|
1688
|
+
function parseAccessChallenge(s) {
|
|
1689
|
+
if (typeof s !== "string" || s.length > 4096) throw new AccessError("bad_request", "challenge must be a string");
|
|
1690
|
+
let c;
|
|
1691
|
+
try {
|
|
1692
|
+
c = JSON.parse(s);
|
|
1693
|
+
} catch {
|
|
1694
|
+
throw new AccessError("bad_request", "challenge is not JSON");
|
|
1695
|
+
}
|
|
1696
|
+
if (!c || typeof c !== "object" || c.v !== 1) throw new AccessError("bad_request", "unsupported challenge version");
|
|
1697
|
+
if (c.kind !== "401" && c.kind !== "402" && c.kind !== "403") throw new AccessError("bad_request", "bad kind");
|
|
1698
|
+
if (typeof c.origin !== "string" || typeof c.nonce !== "string" || !/^[A-Za-z0-9_-]{16,64}$/.test(c.nonce)) {
|
|
1699
|
+
throw new AccessError("bad_request", "bad origin or nonce");
|
|
1700
|
+
}
|
|
1701
|
+
if (!Number.isInteger(c.iat) || !Number.isInteger(c.exp) || c.exp <= c.iat) throw new AccessError("bad_request", "bad iat/exp");
|
|
1702
|
+
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");
|
|
1703
|
+
c.require = normaliseRequirement(c.require);
|
|
1704
|
+
if (canonicalJson(c) !== s) throw new AccessError("bad_request", "challenge is not canonical");
|
|
1705
|
+
return c;
|
|
1706
|
+
}
|
|
1707
|
+
function accessErrorMessage(code, e = {}) {
|
|
1708
|
+
switch (code) {
|
|
1709
|
+
case "not_installed":
|
|
1710
|
+
return "No bWalletX (or other BRC-100 wallet) was found.";
|
|
1711
|
+
case "user_rejected":
|
|
1712
|
+
return "The request was declined in the wallet.";
|
|
1713
|
+
case "wrong_account":
|
|
1714
|
+
return "The wallet is signed in to a different account than this site expects.";
|
|
1715
|
+
case "insufficient_tokens":
|
|
1716
|
+
return `Not enough tokens: holding ${e.have ?? 0}, need ${e.need ?? "?"}.`;
|
|
1717
|
+
case "identity_level_too_low":
|
|
1718
|
+
return `Identity level ${e.have ?? 0} is below the required level ${e.need ?? "?"}.`;
|
|
1719
|
+
case "kyc_required":
|
|
1720
|
+
return "A verified identity (KYC) is required.";
|
|
1721
|
+
case "indexer_unavailable":
|
|
1722
|
+
return "Holdings could not be checked right now. Try again shortly.";
|
|
1723
|
+
case "expired":
|
|
1724
|
+
return "The request expired. Try again.";
|
|
1725
|
+
case "replay":
|
|
1726
|
+
return "That request was already used. Try again.";
|
|
1727
|
+
case "invalid_signature":
|
|
1728
|
+
return "The wallet signature did not verify.";
|
|
1729
|
+
case "bad_request":
|
|
1730
|
+
return "The request was malformed.";
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// src/access-client.ts
|
|
1735
|
+
var ONESAT = [0, "onesat"];
|
|
846
1736
|
var LEGACY_ONESAT = [0, "p 1sat"];
|
|
847
1737
|
function toAccessError(e) {
|
|
848
1738
|
if (e instanceof AccessError) return e;
|
|
@@ -880,7 +1770,7 @@ async function proveHoldings(wallet, challenge, tokenId, basket = "bsv21") {
|
|
|
880
1770
|
const counterparty = ci.counterparty ?? "self";
|
|
881
1771
|
for (const protocolID of ci.protocolID ? [ci.protocolID] : [ONESAT, LEGACY_ONESAT]) {
|
|
882
1772
|
const { publicKey } = await wallet.getPublicKey({ protocolID, keyID: ci.keyID, counterparty });
|
|
883
|
-
const h =
|
|
1773
|
+
const h = import_sdk6.Utils.toHex(import_sdk6.Hash.hash160(import_sdk6.PublicKey.fromString(publicKey).encode(true)));
|
|
884
1774
|
if (want && h !== want) continue;
|
|
885
1775
|
const existing = byKey.get(publicKey);
|
|
886
1776
|
if (existing) {
|
|
@@ -926,11 +1816,119 @@ async function requestAccess(o) {
|
|
|
926
1816
|
}
|
|
927
1817
|
}
|
|
928
1818
|
|
|
1819
|
+
// src/connect-flow.ts
|
|
1820
|
+
async function runConnect(o) {
|
|
1821
|
+
const started = Date.now();
|
|
1822
|
+
const { list } = o;
|
|
1823
|
+
let choice = null;
|
|
1824
|
+
let reason = "none";
|
|
1825
|
+
try {
|
|
1826
|
+
if (o.metanet) {
|
|
1827
|
+
if (!metanetOffered()) throw Object.assign(new Error("Metanet Desktop runs on a computer, outside any frame."), { code: "NOT_SUPPORTED" });
|
|
1828
|
+
choice = metanetOption(typeof o.metanet === "object" ? o.metanet : {});
|
|
1829
|
+
reason = "requested";
|
|
1830
|
+
return await finish(o, choice, reason, started);
|
|
1831
|
+
}
|
|
1832
|
+
const d = decideWallet(list, {
|
|
1833
|
+
walletId: o.walletId,
|
|
1834
|
+
chooser: o.chooser,
|
|
1835
|
+
picker: o.picker,
|
|
1836
|
+
fastPath: o.fastPath,
|
|
1837
|
+
remembered: o.remembered,
|
|
1838
|
+
preferBwalletX: o.preferBwalletX
|
|
1839
|
+
});
|
|
1840
|
+
choice = d.option;
|
|
1841
|
+
reason = d.reason;
|
|
1842
|
+
if (!choice && d.reason === "chooser") choice = await o.pick(list);
|
|
1843
|
+
if (!choice) {
|
|
1844
|
+
if (d.reason === "none" || !list.length) throw Object.assign(new Error("No wallet found. Get bWalletX at bwalletx.com."), { code: "NOT_FOUND" });
|
|
1845
|
+
reportAttempt(o.onAttempt, attemptFor(null, "cancelled", started));
|
|
1846
|
+
return null;
|
|
1847
|
+
}
|
|
1848
|
+
return await finish(o, choice, reason, started);
|
|
1849
|
+
} catch (e) {
|
|
1850
|
+
reportAttempt(o.onAttempt, attemptFor(choice, outcomeOf(e), started));
|
|
1851
|
+
throw e;
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
async function finish(o, choice, reason, started) {
|
|
1855
|
+
const signIn = !!(o.challengeUrl && o.verifyUrl) && choice.kind !== "handcash";
|
|
1856
|
+
const cw = await choice.connect(signIn ? { identity: false } : void 0);
|
|
1857
|
+
let handle = cw.handle ?? null;
|
|
1858
|
+
let paymail = cw.paymail ?? null;
|
|
1859
|
+
let identityKey = cw.identityKey;
|
|
1860
|
+
let signedIn = false;
|
|
1861
|
+
if (cw.wallet && signIn) {
|
|
1862
|
+
const { result, verified } = await signInAndVerify({
|
|
1863
|
+
wallet: cw.wallet,
|
|
1864
|
+
identityKey: cw.identityKey ?? void 0,
|
|
1865
|
+
// Metanet's connect() just checked the lock: don't ask again (rule 7, the minimum).
|
|
1866
|
+
skipWaitForAuthentication: choice.id === METANET_OPTION_ID,
|
|
1867
|
+
// The key that signed in here last time: a repeat sign-in is then the signature alone.
|
|
1868
|
+
identityKeyHint: rememberedIdentity(),
|
|
1869
|
+
challengeNeedsIdentityKey: o.challengeNeedsIdentityKey,
|
|
1870
|
+
challenge: (key) => o.post(o.challengeUrl, key ? { identityKey: key } : {}),
|
|
1871
|
+
verify: (r) => o.post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
|
|
1872
|
+
});
|
|
1873
|
+
rememberIdentity(result.identityKey);
|
|
1874
|
+
identityKey = result.identityKey;
|
|
1875
|
+
handle = verified.handle ?? handle;
|
|
1876
|
+
paymail = verified.paymail ?? paymail;
|
|
1877
|
+
signedIn = true;
|
|
1878
|
+
} else if (cw.wallet && !identityKey) {
|
|
1879
|
+
identityKey = (await cw.wallet.getPublicKey({ identityKey: true })).publicKey;
|
|
1880
|
+
}
|
|
1881
|
+
if (o.remember && isExplicitPick(choice, reason)) rememberWalletChoice(choice);
|
|
1882
|
+
if (o.remember && signedIn) rememberKnownIdentityKey(identityKey);
|
|
1883
|
+
reportAttempt(o.onAttempt, attemptFor(choice, signedIn ? "signed-in" : "connected", started));
|
|
1884
|
+
return { connected: cw, identityKey, handle, paymail, signedIn, walletType: walletTypeOf(choice), substrate: substrateOf(choice), reason };
|
|
1885
|
+
}
|
|
1886
|
+
async function runSilentMetanet(o = {}) {
|
|
1887
|
+
const s = await trySilentMetanet(o);
|
|
1888
|
+
if (!s.ok || o.isAbandoned?.()) return null;
|
|
1889
|
+
const started = Date.now();
|
|
1890
|
+
const opt = metanetOption(o);
|
|
1891
|
+
opt.wallet = s.wallet;
|
|
1892
|
+
const cw = { wallet: s.wallet, payer: s.wallet, identityKey: s.identityKey, optionId: METANET_OPTION_ID, kind: "brc100" };
|
|
1893
|
+
const done = (signedIn, handle = null, paymail = null) => {
|
|
1894
|
+
rememberWalletChoice(opt);
|
|
1895
|
+
if (signedIn) rememberKnownIdentityKey(s.identityKey);
|
|
1896
|
+
reportAttempt(o.onAttempt, attemptFor(opt, signedIn ? "signed-in" : "connected", started));
|
|
1897
|
+
return { connected: cw, identityKey: s.identityKey, handle, paymail, signedIn, walletType: walletTypeOf(opt), substrate: "json-api", reason: "remembered" };
|
|
1898
|
+
};
|
|
1899
|
+
if (o.sessionKey !== void 0 && o.sessionKey !== null) {
|
|
1900
|
+
return o.sessionKey.toLowerCase() === s.identityKey.toLowerCase() ? done(true) : null;
|
|
1901
|
+
}
|
|
1902
|
+
if (!o.challengeUrl || !o.verifyUrl || !o.post) return done(false);
|
|
1903
|
+
if (!isKnownIdentityKey(s.identityKey)) return null;
|
|
1904
|
+
try {
|
|
1905
|
+
o.onSignIn?.();
|
|
1906
|
+
const post = o.post;
|
|
1907
|
+
const { result, verified } = await signInAndVerify({
|
|
1908
|
+
wallet: s.wallet,
|
|
1909
|
+
identityKey: s.identityKey,
|
|
1910
|
+
skipWaitForAuthentication: true,
|
|
1911
|
+
// trySilentMetanet just saw it unlocked
|
|
1912
|
+
challenge: (key) => post(o.challengeUrl, key ? { identityKey: key } : {}),
|
|
1913
|
+
verify: (r) => post(o.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature })
|
|
1914
|
+
});
|
|
1915
|
+
if (o.isAbandoned?.()) {
|
|
1916
|
+
await Promise.resolve(o.logout?.()).catch(() => void 0);
|
|
1917
|
+
return null;
|
|
1918
|
+
}
|
|
1919
|
+
rememberIdentity(result.identityKey);
|
|
1920
|
+
return done(true, verified.handle ?? null, verified.paymail ?? null);
|
|
1921
|
+
} catch (e) {
|
|
1922
|
+
reportAttempt(o.onAttempt, attemptFor(opt, outcomeOf(e), started));
|
|
1923
|
+
return null;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
|
|
929
1927
|
// src/element.ts
|
|
930
1928
|
var SIGNIN_TAG = "bwalletx-signin";
|
|
931
1929
|
var GET_URL = "https://bwalletx.com/get";
|
|
932
1930
|
var WEB_WALLET_URL = "https://web.bwalletx.com";
|
|
933
|
-
var VERBS = { signin: "Sign in with", continue: "Continue with", connect: "Connect" };
|
|
1931
|
+
var VERBS = { signin: "Sign in with", continue: "Continue with", connect: "Connect", get: "Get", play: "Play with" };
|
|
934
1932
|
function signinText(label = "signin", brand = "bWalletX") {
|
|
935
1933
|
return `${VERBS[label] ?? VERBS.signin} ${brand}`;
|
|
936
1934
|
}
|
|
@@ -985,7 +1983,60 @@ button[aria-busy=true]{cursor:wait}
|
|
|
985
1983
|
.nf a:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
|
|
986
1984
|
:host([theme=dark]) .nf,:host-context(.dark) .nf{color:#a1a1aa}
|
|
987
1985
|
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
|
1986
|
+
/* pending: one plain line under the button */
|
|
1987
|
+
.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}
|
|
1988
|
+
:host([theme=dark]) .note,:host-context(.dark) .note{color:#a1a1aa}
|
|
1989
|
+
/* "Other wallets" (0.5.6): one small link under the button */
|
|
1990
|
+
.ow{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
|
|
1991
|
+
:host([theme=dark]) .ow,:host-context(.dark) .ow{color:#a1a1aa}
|
|
1992
|
+
.sw{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#a16207;text-align:center}
|
|
1993
|
+
:host([theme=dark]) .sw,:host-context(.dark) .sw{color:#fcd34d}
|
|
1994
|
+
.mn{margin:8px 0 0;width:0;min-width:100%;font-size:12px;line-height:1.4;color:#71717a;text-align:center}
|
|
1995
|
+
.mn small{display:block;font-size:11px;opacity:.85}
|
|
1996
|
+
:host([theme=dark]) .mn,:host-context(.dark) .mn{color:#a1a1aa}
|
|
1997
|
+
.lnk{all:unset;cursor:pointer;color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px}
|
|
1998
|
+
.lnk:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}
|
|
1999
|
+
/* error: one line plus Retry */
|
|
2000
|
+
.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}
|
|
2001
|
+
:host([theme=dark]) .err,:host-context(.dark) .err{color:#fca5a5}
|
|
2002
|
+
/* signed in: the badge */
|
|
2003
|
+
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;
|
|
2004
|
+
background:#0b0b0c;color:#ffd24d;border:1px solid rgba(255,210,77,.55);box-shadow:none;font-size:15px}
|
|
2005
|
+
button.chip:hover{background:#151517;border-color:#ffd24d}
|
|
2006
|
+
button.chip:active{background:#000;transform:none}
|
|
2007
|
+
button.chip:focus-visible{outline:2px solid #ffd24d;outline-offset:3px}
|
|
2008
|
+
:host([theme=light]) button.chip{background:#fff;color:#09090b;border-color:#f59e0b}
|
|
2009
|
+
:host([theme=light]) button.chip:hover{background:#fffbeb}
|
|
2010
|
+
:host([full-width]) button.chip{width:100%}
|
|
2011
|
+
.av{position:relative;flex:none;display:inline-grid;place-items:center;width:30px;height:30px;border-radius:50%;overflow:hidden;
|
|
2012
|
+
background:radial-gradient(circle at 30% 30%,#ffe27a,#f5b800 55%,#b88600);color:#000;font-weight:800;font-size:14px}
|
|
2013
|
+
.av img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
|
|
2014
|
+
.name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:min(52vw,260px)}
|
|
2015
|
+
.dot{flex:none;width:8px;height:8px;border-radius:50%;background:#ffd24d;box-shadow:0 0 0 3px rgba(255,210,77,.22)}
|
|
2016
|
+
:host([size=sm]) button.chip{font-size:13px;padding:3px 12px 3px 3px;gap:8px}
|
|
2017
|
+
:host([size=sm]) .av{width:26px;height:26px}
|
|
2018
|
+
:host([size=icon]) button.chip{width:48px;height:48px;padding:0;justify-content:center}
|
|
2019
|
+
:host([size=icon]) .name,:host([size=icon]) .dot{display:none}
|
|
2020
|
+
:host([size=icon]) .av{width:40px;height:40px}
|
|
2021
|
+
`;
|
|
2022
|
+
var MENU_CSS = `
|
|
2023
|
+
:host{all:initial}
|
|
2024
|
+
.menu{position:fixed;z-index:2147483600;box-sizing:border-box;min-width:200px;padding:6px;border-radius:14px;background:#0b0b0c;color:#ffd24d;
|
|
2025
|
+
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}
|
|
2026
|
+
.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}
|
|
2027
|
+
.item{all:unset;box-sizing:border-box;display:block;width:100%;padding:11px 10px;border-radius:10px;cursor:pointer;color:#ffd24d}
|
|
2028
|
+
.item:hover,.item:focus-visible{background:#ffd24d;color:#000;outline:none}
|
|
2029
|
+
.tip{position:fixed;z-index:2147483600;box-sizing:border-box;padding:8px 12px;border-radius:10px;background:#0b0b0c;color:#e7d9a8;
|
|
2030
|
+
border:1px solid rgba(255,210,77,.4);box-shadow:0 8px 24px rgba(0,0,0,.35);font:500 12px/1.4 Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
|
|
988
2031
|
`;
|
|
2032
|
+
var PENDING_NOTE = "Sign in: shares only your public identity key, moves no money";
|
|
2033
|
+
var PENDING_LABEL = "Approve in bWalletX\u2026";
|
|
2034
|
+
var PENDING_LABEL_OTHER = "Approve in your wallet\u2026";
|
|
2035
|
+
var SWITCH_NOTE = "Some features need bWalletX";
|
|
2036
|
+
var CONTINUE_OTHER_LABEL = "Continue with your wallet";
|
|
2037
|
+
function walletGlyph(px) {
|
|
2038
|
+
return `<svg viewBox="0 0 24 24" width="${px}" height="${px}" aria-hidden="true" focusable="false" fill="none" stroke="#FFD24D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 7V5a1 1 0 0 0-1-1H5a2 2 0 0 0 0 4h15a1 1 0 0 1 1 1v4h-3a2 2 0 0 0 0 4h3a1 1 0 0 0 1-1v-2"/><path d="M3 6v12a2 2 0 0 0 2 2h15a1 1 0 0 0 1-1v-3"/></svg>`;
|
|
2039
|
+
}
|
|
989
2040
|
function b64url2(bytes) {
|
|
990
2041
|
let s = "";
|
|
991
2042
|
for (const b of bytes) s += String.fromCharCode(b);
|
|
@@ -1018,14 +2069,15 @@ async function buildAuthorizeUrl(p) {
|
|
|
1018
2069
|
async function postJson(url, body) {
|
|
1019
2070
|
const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
1020
2071
|
const data = await res.json().catch(() => ({}));
|
|
1021
|
-
if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);
|
|
2072
|
+
if (!res.ok) throw Object.assign(new Error(data.error ?? `HTTP ${res.status}`), { status: res.status });
|
|
1022
2073
|
return data;
|
|
1023
2074
|
}
|
|
1024
2075
|
async function postAccess(url, body) {
|
|
1025
2076
|
const res = await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
|
|
1026
2077
|
return res.json().catch(() => ({ error: "bad_request", message: `HTTP ${res.status}` }));
|
|
1027
2078
|
}
|
|
1028
|
-
var OBSERVED = ["size", "theme", "label", "brand", "subtitle", "loading", "disabled", "full-width", "aria-label", "get-url", "web-url"];
|
|
2079
|
+
var OBSERVED = ["size", "theme", "label", "brand", "subtitle", "loading", "disabled", "full-width", "aria-label", "get-url", "web-url", "state", "identity-key", "paymail", "handle", "avatar-url", "error", "other-wallets", "challenge-url", "wallet-type", "switch-note", "remember", "metanet"];
|
|
2080
|
+
var STATES = ["signed-out", "restoring", "pending", "signed-in", "error"];
|
|
1029
2081
|
function createClass() {
|
|
1030
2082
|
return class BwalletxSigninElement extends HTMLElement {
|
|
1031
2083
|
static get observedAttributes() {
|
|
@@ -1034,12 +2086,30 @@ function createClass() {
|
|
|
1034
2086
|
#root;
|
|
1035
2087
|
#notFound = false;
|
|
1036
2088
|
#busy = false;
|
|
2089
|
+
#phase = "signed-out";
|
|
2090
|
+
#account = null;
|
|
2091
|
+
#error = "";
|
|
2092
|
+
#restored = false;
|
|
2093
|
+
#pendingLabel = PENDING_LABEL;
|
|
2094
|
+
/** The wallet type signed in with here (0.6.0), for the Switch note. */
|
|
2095
|
+
#signedType = null;
|
|
2096
|
+
#menu = null;
|
|
2097
|
+
/** (0.7.0) A Metanet sign-in (tapped or silent) is in flight. */
|
|
2098
|
+
#viaMetanet = false;
|
|
2099
|
+
/** (0.7.0) Bumped by "Not you?": a Metanet sign-in started before it lands nobody. */
|
|
2100
|
+
#run = 0;
|
|
1037
2101
|
constructor() {
|
|
1038
2102
|
super();
|
|
1039
2103
|
this.#root = this.attachShadow({ mode: "open" });
|
|
1040
2104
|
}
|
|
1041
2105
|
connectedCallback() {
|
|
2106
|
+
if (!this.#controlled && this.#stateful) warnIfNoLogout({ verifyUrl: this.#attr("verify-url"), logoutUrl: this.#attr("logout-url"), where: "<bwalletx-signin>" });
|
|
1042
2107
|
this.#render();
|
|
2108
|
+
void this.#restore().then(() => this.#silentMetanet());
|
|
2109
|
+
}
|
|
2110
|
+
disconnectedCallback() {
|
|
2111
|
+
this.#menu?.close(false);
|
|
2112
|
+
this.#hideTip();
|
|
1043
2113
|
}
|
|
1044
2114
|
attributeChangedCallback() {
|
|
1045
2115
|
if (this.isConnected) this.#render();
|
|
@@ -1051,109 +2121,793 @@ function createClass() {
|
|
|
1051
2121
|
get notFound() {
|
|
1052
2122
|
return this.#notFound;
|
|
1053
2123
|
}
|
|
2124
|
+
/** The state shown now (the `state` attribute when set, else the element's own). */
|
|
2125
|
+
get state() {
|
|
2126
|
+
const a = this.#attr("state");
|
|
2127
|
+
return a && STATES.includes(a) ? a : this.#phase;
|
|
2128
|
+
}
|
|
2129
|
+
/** The signed-in account, or null. */
|
|
2130
|
+
get account() {
|
|
2131
|
+
if (!this.#controlled) return this.#account;
|
|
2132
|
+
const identityKey = this.#attr("identity-key");
|
|
2133
|
+
return identityKey ? { identityKey, paymail: this.#attr("paymail"), handle: this.#attr("handle"), avatarUrl: this.#attr("avatar-url") } : null;
|
|
2134
|
+
}
|
|
2135
|
+
/** True while the menu is open (tests). */
|
|
2136
|
+
get menuOpen() {
|
|
2137
|
+
return !!this.#menu;
|
|
2138
|
+
}
|
|
1054
2139
|
focus(opts) {
|
|
1055
2140
|
this.button?.focus(opts);
|
|
1056
2141
|
}
|
|
2142
|
+
get #controlled() {
|
|
2143
|
+
return this.hasAttribute("state");
|
|
2144
|
+
}
|
|
2145
|
+
get #stateful() {
|
|
2146
|
+
return !this.hasAttribute("stateless");
|
|
2147
|
+
}
|
|
1057
2148
|
#attr(name) {
|
|
1058
2149
|
return this.getAttribute(name);
|
|
1059
2150
|
}
|
|
2151
|
+
/** Restore the badge from session-url once, with no wallet call. */
|
|
2152
|
+
async #restore() {
|
|
2153
|
+
const url = this.#attr("session-url");
|
|
2154
|
+
if (this.#restored || !url || this.#controlled || !this.#stateful) return;
|
|
2155
|
+
this.#restored = true;
|
|
2156
|
+
this.#set("restoring");
|
|
2157
|
+
try {
|
|
2158
|
+
const r = await fetch(url, { credentials: "include", headers: { accept: "application/json" } });
|
|
2159
|
+
const j = await r.json().catch(() => ({}));
|
|
2160
|
+
if (this.#phase !== "restoring") return;
|
|
2161
|
+
if (j.identityKey) this.#signedIn({ identityKey: j.identityKey, handle: j.handle ?? null, paymail: j.paymail ?? null });
|
|
2162
|
+
else this.#set("signed-out");
|
|
2163
|
+
} catch {
|
|
2164
|
+
if (this.#phase === "restoring") this.#set("signed-out");
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
#set(p) {
|
|
2168
|
+
this.#phase = p;
|
|
2169
|
+
if (p !== "signed-in") this.#menu?.close(false);
|
|
2170
|
+
this.#render();
|
|
2171
|
+
}
|
|
2172
|
+
#signedIn(a) {
|
|
2173
|
+
this.#account = a;
|
|
2174
|
+
this.#error = "";
|
|
2175
|
+
this.#set("signed-in");
|
|
2176
|
+
const lookup = this.#attr("lookup-url");
|
|
2177
|
+
if (!a.paymail && lookup !== "off") {
|
|
2178
|
+
void lookupPaymail(a.identityKey, lookup ?? void 0).then((pm) => {
|
|
2179
|
+
if (pm && this.#account?.identityKey === a.identityKey && !this.#account.paymail) {
|
|
2180
|
+
this.#account = { ...this.#account, paymail: pm };
|
|
2181
|
+
this.#render();
|
|
2182
|
+
}
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
1060
2186
|
#render() {
|
|
1061
2187
|
const size = this.#attr("size") ?? "lg";
|
|
1062
2188
|
const brand = this.#attr("brand") === "bWallet" ? "bWallet" : "bWalletX";
|
|
1063
|
-
const
|
|
2189
|
+
const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
2190
|
+
const state = this.state;
|
|
2191
|
+
const acct = this.account;
|
|
2192
|
+
if (state === "signed-in" && acct) {
|
|
2193
|
+
this.#hideTip();
|
|
2194
|
+
const name = accountLabel(acct);
|
|
2195
|
+
const avatar = acct.avatarUrl ?? avatarUrlFor(acct.identityKey);
|
|
2196
|
+
const initial = (name.replace(/^\$/, "").charAt(0) || "b").toUpperCase();
|
|
2197
|
+
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>`;
|
|
2198
|
+
const img = this.#root.querySelector("img");
|
|
2199
|
+
if (img) img.addEventListener("error", () => img.remove(), { once: true });
|
|
2200
|
+
const chip = this.button;
|
|
2201
|
+
chip.addEventListener("click", () => this.#menu ? this.#menu.close(true) : this.#openMenu());
|
|
2202
|
+
chip.addEventListener("keydown", (e) => {
|
|
2203
|
+
if ((e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") && !this.#menu) {
|
|
2204
|
+
e.preventDefault();
|
|
2205
|
+
this.#openMenu();
|
|
2206
|
+
}
|
|
2207
|
+
});
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
const pending = state === "pending";
|
|
2211
|
+
const restoring = state === "restoring";
|
|
2212
|
+
const error = state === "error";
|
|
2213
|
+
const pendingLabel = this.#controlled ? PENDING_LABEL : this.#pendingLabel;
|
|
2214
|
+
const elsewhere = state === "signed-out" && tapGoesElsewhere(this.#walletType());
|
|
2215
|
+
const text = pending ? pendingLabel : error ? "Retry" : elsewhere ? CONTINUE_OTHER_LABEL : signinText(this.#attr("label") ?? "signin", brand);
|
|
1064
2216
|
const subAttr = this.#attr("subtitle");
|
|
1065
2217
|
const subDefault = brand === "bWalletX" && (size === "lg" || size === "md") ? "also works with bWallet" : null;
|
|
1066
|
-
const subtitle = subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
|
|
1067
|
-
const loading = this.hasAttribute("loading") || this.#busy;
|
|
2218
|
+
const subtitle = pending || error || elsewhere ? null : subAttr === null || subAttr === "" || subAttr === "on" ? subAttr === "on" ? "also works with bWallet" : subDefault : subAttr === "off" ? null : subAttr;
|
|
2219
|
+
const loading = this.hasAttribute("loading") || this.#busy || pending || restoring;
|
|
1068
2220
|
const disabled = this.hasAttribute("disabled");
|
|
1069
2221
|
const markPx = size === "icon" ? 24 : size === "sm" ? 15 : size === "md" ? 17 : 20;
|
|
1070
|
-
const aria = this.#attr("aria-label") ?? (subtitle && size !== "icon" ? `${text}, ${subtitle}` : text);
|
|
2222
|
+
const aria = pending ? pendingLabel : this.#attr("aria-label") ?? (subtitle && size !== "icon" ? `${text}, ${subtitle}` : text);
|
|
1071
2223
|
const getUrl = this.#attr("get-url") ?? GET_URL;
|
|
1072
2224
|
const webUrl = this.#attr("web-url") ?? WEB_WALLET_URL;
|
|
1073
|
-
const
|
|
2225
|
+
const errText = error ? (this.#controlled ? this.#attr("error") : this.#error) || "Sign-in failed" : "";
|
|
1074
2226
|
const label = size === "icon" ? "" : `<span class="txt" part="text"><b part="label">${esc(text)}</b>${subtitle ? `<small part="subtitle">${esc(subtitle)}</small>` : ""}</span>`;
|
|
1075
|
-
const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : markSvg(markPx);
|
|
1076
|
-
const
|
|
1077
|
-
|
|
2227
|
+
const mark = loading ? '<span class="spin" aria-hidden="true"></span>' : elsewhere ? walletGlyph(markPx - 4) : markSvg(markPx);
|
|
2228
|
+
const otherOff = this.#attr("other-wallets") === "off";
|
|
2229
|
+
const otherLink = '<button type="button" class="lnk" data-act="other" part="other-wallets-link">Other wallets</button>';
|
|
2230
|
+
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>` : "";
|
|
2231
|
+
const ow = !this.#notFound && !pending && !restoring && this.#otherWalletsShown(size) ? `<p class="ow" part="other-wallets">${otherLink}</p>` : "";
|
|
2232
|
+
const sw = !pending && !restoring && this.#switchNoteShown(size) ? `<p class="sw" part="switch-note">${esc(SWITCH_NOTE)} \xB7 <button type="button" class="lnk" data-act="switch" part="switch-link">Switch</button></p>` : "";
|
|
2233
|
+
const err = error ? `<p class="err" part="error" role="alert">${esc(errText)}</p>` : "";
|
|
2234
|
+
const mnHere = this.#metanetShown(size);
|
|
2235
|
+
const mnRemembered = mnHere && (pending ? this.#metanetPending() : !restoring && this.#walletType() === METANET_TYPE);
|
|
2236
|
+
const notYou = `<button type="button" class="lnk" data-act="not-you" part="not-you-link">${esc(NOT_YOU_LABEL)}</button>`;
|
|
2237
|
+
const mn = !mnHere ? "" : pending ? mnRemembered ? `<p class="mn" part="metanet">${esc(METANET_REMEMBERED_NOTE)} ${notYou}</p>` : "" : restoring ? "" : `<p class="mn" part="metanet"><button type="button" class="lnk" data-act="metanet" part="metanet-link" title="Connects to Metanet Desktop on this computer, never to a browser extension.">${esc(METANET_LABEL)}</button><small>${esc(METANET_NOTE)}</small>${mnRemembered ? `<small>${esc(METANET_REMEMBERED_NOTE)} ${notYou}</small>` : ""}</p>`;
|
|
2238
|
+
const compact = size === "sm" || size === "icon";
|
|
2239
|
+
const note = pending && !compact ? `<p class="note" part="note">${esc(PENDING_NOTE)}</p>` : "";
|
|
2240
|
+
const status = pending ? `<span class="sr" role="status">${esc(pendingLabel)}</span>` : loading ? '<span class="sr" role="status">Connecting to bWalletX\u2026</span>' : "";
|
|
2241
|
+
this.#root.innerHTML = `<style>${SIGNIN_CSS}</style>${err}<button type="button" part="button" aria-label="${esc(aria)}" title="${esc(subtitle ? `${text} (${subtitle})` : text)}"${disabled ? " disabled" : ""}${loading ? ' aria-busy="true"' : ""}><span class="mark" part="mark">${mark}</span>${label}</button>${status}${note}${sw}${mn}${ow}${nf}`;
|
|
1078
2242
|
this.button.addEventListener("click", () => void this.#click());
|
|
2243
|
+
for (const l of Array.from(this.#root.querySelectorAll("[data-act=other]"))) l.addEventListener("click", () => void this.otherWallets());
|
|
2244
|
+
this.#root.querySelector("[data-act=switch]")?.addEventListener("click", () => void this.switchToBwalletX());
|
|
2245
|
+
this.#root.querySelector("[data-act=metanet]")?.addEventListener("click", () => void this.useMetanet());
|
|
2246
|
+
this.#root.querySelector("[data-act=not-you]")?.addEventListener("click", () => this.notYou());
|
|
1079
2247
|
bindPrewarm(this.button);
|
|
2248
|
+
if (pending && compact) this.#showTip(PENDING_NOTE);
|
|
2249
|
+
else this.#hideTip();
|
|
2250
|
+
}
|
|
2251
|
+
#tip = null;
|
|
2252
|
+
#showTip(text) {
|
|
2253
|
+
const b = this.button;
|
|
2254
|
+
if (!b || typeof document === "undefined") return;
|
|
2255
|
+
if (!this.#tip) {
|
|
2256
|
+
const host = document.createElement("div");
|
|
2257
|
+
host.setAttribute("data-bwalletx-tip", "");
|
|
2258
|
+
const root = host.attachShadow({ mode: "open" });
|
|
2259
|
+
root.innerHTML = `<style>${MENU_CSS}</style><div class="tip" role="note"></div>`;
|
|
2260
|
+
document.body.appendChild(host);
|
|
2261
|
+
this.#tip = host;
|
|
2262
|
+
}
|
|
2263
|
+
const tip2 = this.#tip.shadowRoot.querySelector(".tip");
|
|
2264
|
+
tip2.textContent = text;
|
|
2265
|
+
const r = b.getBoundingClientRect();
|
|
2266
|
+
const vw = window.innerWidth || document.documentElement.clientWidth || 360;
|
|
2267
|
+
const width = Math.min(280, vw - 32);
|
|
2268
|
+
tip2.style.width = `${width}px`;
|
|
2269
|
+
tip2.style.left = `${Math.max(16, Math.min(r.right - width, vw - width - 16))}px`;
|
|
2270
|
+
tip2.style.top = `${r.bottom + 8}px`;
|
|
2271
|
+
}
|
|
2272
|
+
#hideTip() {
|
|
2273
|
+
this.#tip?.remove();
|
|
2274
|
+
this.#tip = null;
|
|
2275
|
+
}
|
|
2276
|
+
#openMenu() {
|
|
2277
|
+
const acct = this.account;
|
|
2278
|
+
const chip = this.button;
|
|
2279
|
+
if (!acct || !chip || typeof document === "undefined") return;
|
|
2280
|
+
const name = accountLabel(acct);
|
|
2281
|
+
const host = document.createElement("div");
|
|
2282
|
+
host.setAttribute("data-bwalletx-menu", "");
|
|
2283
|
+
const root = host.attachShadow({ mode: "open" });
|
|
2284
|
+
const style = document.createElement("style");
|
|
2285
|
+
style.textContent = MENU_CSS;
|
|
2286
|
+
const menu = document.createElement("div");
|
|
2287
|
+
menu.className = "menu";
|
|
2288
|
+
menu.setAttribute("role", "menu");
|
|
2289
|
+
menu.setAttribute("aria-label", "bWalletX account");
|
|
2290
|
+
const who = document.createElement("div");
|
|
2291
|
+
who.className = "who";
|
|
2292
|
+
who.textContent = `Signed in as ${acct.paymail || name}`;
|
|
2293
|
+
const item = (text, run) => {
|
|
2294
|
+
const b = document.createElement("button");
|
|
2295
|
+
b.type = "button";
|
|
2296
|
+
b.className = "item";
|
|
2297
|
+
b.setAttribute("role", "menuitem");
|
|
2298
|
+
b.tabIndex = -1;
|
|
2299
|
+
b.textContent = text;
|
|
2300
|
+
b.addEventListener("click", () => run(b));
|
|
2301
|
+
return b;
|
|
2302
|
+
};
|
|
2303
|
+
const copyWhat = acct.paymail ? "paymail" : "key";
|
|
2304
|
+
const copy = item(`Copy ${copyWhat}`, (b) => {
|
|
2305
|
+
void copyText(acct.paymail || acct.identityKey).then((ok) => {
|
|
2306
|
+
b.textContent = ok ? "Copied" : `Copy ${copyWhat}`;
|
|
2307
|
+
setTimeout(() => close(true), ok ? 600 : 0);
|
|
2308
|
+
});
|
|
2309
|
+
});
|
|
2310
|
+
const out = item("Disconnect", () => {
|
|
2311
|
+
close(false);
|
|
2312
|
+
void this.signOut();
|
|
2313
|
+
});
|
|
2314
|
+
const items = [copy, out];
|
|
2315
|
+
menu.append(who);
|
|
2316
|
+
if (needsBwalletX(this.#walletType())) {
|
|
2317
|
+
const sw = item("Switch to bWalletX", () => {
|
|
2318
|
+
close(false);
|
|
2319
|
+
void this.switchToBwalletX();
|
|
2320
|
+
});
|
|
2321
|
+
const line = document.createElement("div");
|
|
2322
|
+
line.className = "who";
|
|
2323
|
+
line.textContent = SWITCH_NOTE;
|
|
2324
|
+
menu.append(line, sw);
|
|
2325
|
+
items.unshift(sw);
|
|
2326
|
+
}
|
|
2327
|
+
menu.append(copy, out);
|
|
2328
|
+
root.append(style, menu);
|
|
2329
|
+
document.body.appendChild(host);
|
|
2330
|
+
const place = () => {
|
|
2331
|
+
const r = chip.getBoundingClientRect();
|
|
2332
|
+
const vw = window.innerWidth || document.documentElement.clientWidth || 360;
|
|
2333
|
+
const vh = window.innerHeight || document.documentElement.clientHeight || 640;
|
|
2334
|
+
const width = Math.min(260, vw - 32);
|
|
2335
|
+
menu.style.width = `${width}px`;
|
|
2336
|
+
const left = Math.max(16, Math.min(r.right - width, vw - width - 16));
|
|
2337
|
+
menu.style.left = `${left}px`;
|
|
2338
|
+
const h = menu.offsetHeight || 150;
|
|
2339
|
+
const below = r.bottom + 8;
|
|
2340
|
+
menu.style.top = `${below + h > vh && r.top - 8 - h > 0 ? r.top - 8 - h : below}px`;
|
|
2341
|
+
};
|
|
2342
|
+
place();
|
|
2343
|
+
const focusAt = (i) => items[(i + items.length) % items.length].focus();
|
|
2344
|
+
const onKey = (e) => {
|
|
2345
|
+
const i = items.indexOf(root.activeElement);
|
|
2346
|
+
if (e.key === "Escape") {
|
|
2347
|
+
e.preventDefault();
|
|
2348
|
+
close(true);
|
|
2349
|
+
} else if (e.key === "ArrowDown") {
|
|
2350
|
+
e.preventDefault();
|
|
2351
|
+
focusAt(i + 1);
|
|
2352
|
+
} else if (e.key === "ArrowUp") {
|
|
2353
|
+
e.preventDefault();
|
|
2354
|
+
focusAt(i - 1);
|
|
2355
|
+
} else if (e.key === "Home") {
|
|
2356
|
+
e.preventDefault();
|
|
2357
|
+
focusAt(0);
|
|
2358
|
+
} else if (e.key === "End") {
|
|
2359
|
+
e.preventDefault();
|
|
2360
|
+
focusAt(items.length - 1);
|
|
2361
|
+
} else if (e.key === "Tab") close(false);
|
|
2362
|
+
};
|
|
2363
|
+
const onDown = (e) => {
|
|
2364
|
+
const path = e.composedPath?.() ?? [];
|
|
2365
|
+
if (!path.includes(host) && !path.includes(this)) close(false);
|
|
2366
|
+
};
|
|
2367
|
+
const onMove = () => close(false);
|
|
2368
|
+
menu.addEventListener("keydown", onKey);
|
|
2369
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
2370
|
+
window.addEventListener("resize", onMove);
|
|
2371
|
+
window.addEventListener("scroll", place, true);
|
|
2372
|
+
const close = (focusChip = false) => {
|
|
2373
|
+
if (this.#menu?.host !== host) return;
|
|
2374
|
+
this.#menu = null;
|
|
2375
|
+
menu.removeEventListener("keydown", onKey);
|
|
2376
|
+
document.removeEventListener("pointerdown", onDown, true);
|
|
2377
|
+
window.removeEventListener("resize", onMove);
|
|
2378
|
+
window.removeEventListener("scroll", place, true);
|
|
2379
|
+
host.remove();
|
|
2380
|
+
this.button?.setAttribute("aria-expanded", "false");
|
|
2381
|
+
if (focusChip) this.button?.focus();
|
|
2382
|
+
};
|
|
2383
|
+
this.#menu = { host, close };
|
|
2384
|
+
chip.setAttribute("aria-expanded", "true");
|
|
2385
|
+
focusAt(0);
|
|
2386
|
+
}
|
|
2387
|
+
/**
|
|
2388
|
+
* Disconnect: emits bwalletx-disconnect (cancelable). Unless a listener cancels it (BwalletXProvider signs out
|
|
2389
|
+
* itself) or the element is driven by `state`, posts logout-url and shows signed out.
|
|
2390
|
+
*/
|
|
2391
|
+
async signOut() {
|
|
2392
|
+
this.#menu?.close(false);
|
|
2393
|
+
if (!this.#emit("bwalletx-disconnect", { element: this, account: this.account }, true)) return;
|
|
2394
|
+
if (this.#controlled) return;
|
|
2395
|
+
const url = this.#attr("logout-url");
|
|
2396
|
+
if (url) await fetch(url, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
|
|
2397
|
+
if (this.#remember) forgetWalletChoice();
|
|
2398
|
+
this.#signedType = null;
|
|
2399
|
+
this.#account = null;
|
|
2400
|
+
this.#set("signed-out");
|
|
2401
|
+
this.#emit("bwalletx-signed-out");
|
|
2402
|
+
this.button?.focus();
|
|
1080
2403
|
}
|
|
1081
2404
|
#emit(type, detail, cancelable = false) {
|
|
1082
2405
|
return this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable }));
|
|
1083
2406
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
2407
|
+
/** "Other wallets" is offered under the button: other-wallets on, or auto at lg/md when this element signs in. */
|
|
2408
|
+
#otherWalletsShown(size) {
|
|
2409
|
+
const a = this.#attr("other-wallets");
|
|
2410
|
+
if (a === "off") return false;
|
|
2411
|
+
if (a === "on") return true;
|
|
2412
|
+
return (size === "lg" || size === "md") && (this.#controlled || !!this.#attr("challenge-url"));
|
|
2413
|
+
}
|
|
2414
|
+
/** Open the wallet chooser (the "Other wallets" link): phone QR, other browser wallets, desktop wallets. */
|
|
2415
|
+
otherWallets() {
|
|
2416
|
+
return this.#click("chooser");
|
|
2417
|
+
}
|
|
2418
|
+
/** (0.6.0) "Switch": sign in with bWalletX (its sheet when here, else the chooser with Get bWalletX first). */
|
|
2419
|
+
switchToBwalletX() {
|
|
2420
|
+
return this.#click("bwalletx");
|
|
2421
|
+
}
|
|
2422
|
+
get #remember() {
|
|
2423
|
+
return this.#attr("remember") !== "off";
|
|
2424
|
+
}
|
|
2425
|
+
/**
|
|
2426
|
+
* (0.7.0) "Use Metanet Desktop or another BRC-100 wallet" is offered: metanet on, or auto at lg/md when this
|
|
2427
|
+
* element signs in or follows a provider. Never on a phone or inside a frame.
|
|
2428
|
+
*/
|
|
2429
|
+
#metanetShown(size) {
|
|
2430
|
+
const a = this.#attr("metanet");
|
|
2431
|
+
if (a === "off" || !metanetOffered()) return false;
|
|
2432
|
+
if (a === "on") return true;
|
|
2433
|
+
return (size === "lg" || size === "md") && (this.#controlled || !!this.#attr("challenge-url"));
|
|
2434
|
+
}
|
|
2435
|
+
/** A Metanet sign-in is on screen: our own, or (driven) the provider's with Metanet remembered. */
|
|
2436
|
+
#metanetPending() {
|
|
2437
|
+
return this.#controlled ? this.#attr("wallet-type") === METANET_TYPE : this.#viaMetanet;
|
|
2438
|
+
}
|
|
2439
|
+
/** (0.7.0) The Metanet link: connect to Metanet Desktop on localhost:3321 (after this tap, never before). */
|
|
2440
|
+
useMetanet() {
|
|
2441
|
+
return this.#click("metanet");
|
|
2442
|
+
}
|
|
2443
|
+
/**
|
|
2444
|
+
* (0.7.0) "Not you? Use another wallet": forget the remembered wallet here, and drop a Metanet sign-in in flight
|
|
2445
|
+
* (if its verify already set a session, logout-url ends it). Emits bwalletx-not-you (cancelable).
|
|
2446
|
+
*/
|
|
2447
|
+
notYou() {
|
|
2448
|
+
if (!this.#emit("bwalletx-not-you", { element: this }, true)) return;
|
|
2449
|
+
this.#run++;
|
|
2450
|
+
if (this.#remember) forgetWalletChoice();
|
|
2451
|
+
this.#signedType = null;
|
|
2452
|
+
if (this.#viaMetanet && !this.#controlled) {
|
|
2453
|
+
this.#viaMetanet = false;
|
|
2454
|
+
this.#busy = false;
|
|
2455
|
+
if (this.#phase === "pending") this.#phase = "signed-out";
|
|
2456
|
+
}
|
|
1086
2457
|
this.#render();
|
|
1087
2458
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
if (
|
|
1094
|
-
|
|
2459
|
+
/**
|
|
2460
|
+
* (0.7.0) Rule 4: back with Metanet remembered and no session, ONE silent attempt per page load (2 s, a known
|
|
2461
|
+
* identity key only), then the signature alone. Any stop or failure is silent: the normal button stays.
|
|
2462
|
+
*/
|
|
2463
|
+
async #silentMetanet() {
|
|
2464
|
+
if (this.#controlled || !this.#stateful || !this.#remember || this.#attr("require-token")) return;
|
|
2465
|
+
if (!this.#metanetShown(this.#attr("size") ?? "lg")) return;
|
|
2466
|
+
const challengeUrl = this.#attr("challenge-url");
|
|
2467
|
+
const verifyUrl = this.#attr("verify-url");
|
|
2468
|
+
if (!challengeUrl || !verifyUrl || this.#phase !== "signed-out" || this.#busy || silentAttemptUsed()) return;
|
|
2469
|
+
if (rememberedWalletChoice()?.type !== METANET_TYPE || !metanetOffered()) return;
|
|
2470
|
+
const run = ++this.#run;
|
|
2471
|
+
const live2 = () => run === this.#run;
|
|
2472
|
+
const started = Date.now();
|
|
2473
|
+
const r = await runSilentMetanet({
|
|
2474
|
+
challengeUrl,
|
|
2475
|
+
verifyUrl,
|
|
2476
|
+
post: (url, body) => postJson(url, body),
|
|
2477
|
+
isAbandoned: () => !live2(),
|
|
2478
|
+
logout: async () => {
|
|
2479
|
+
const out = this.#attr("logout-url");
|
|
2480
|
+
if (out) await fetch(out, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
|
|
2481
|
+
},
|
|
2482
|
+
onSignIn: () => {
|
|
2483
|
+
if (!live2() || this.#phase !== "signed-out" || this.#busy) return;
|
|
2484
|
+
this.#viaMetanet = true;
|
|
2485
|
+
this.#busy = true;
|
|
2486
|
+
this.#pendingLabel = PENDING_LABEL_OTHER;
|
|
2487
|
+
this.#set("pending");
|
|
2488
|
+
},
|
|
2489
|
+
onAttempt: (a) => this.#emit("bwalletx-attempt", a)
|
|
2490
|
+
});
|
|
2491
|
+
if (!live2()) return;
|
|
2492
|
+
this.#viaMetanet = false;
|
|
2493
|
+
this.#busy = false;
|
|
2494
|
+
if (!r) {
|
|
2495
|
+
if (this.#phase === "pending") this.#set("signed-out");
|
|
1095
2496
|
return;
|
|
1096
2497
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
2498
|
+
this.#signedType = r.walletType;
|
|
2499
|
+
this.#signedIn({ identityKey: r.identityKey, handle: r.handle, paymail: r.paymail });
|
|
2500
|
+
this.#emit("bwalletx-signed-in", { identityKey: r.identityKey, method: "wallet", nonce: null, signature: null, response: { handle: r.handle, paymail: r.paymail }, silent: true, ms: Date.now() - started });
|
|
2501
|
+
}
|
|
2502
|
+
/** The wallet type the Switch note judges: the provider's, else what signed in here, else the remembered one. */
|
|
2503
|
+
#walletType() {
|
|
2504
|
+
if (this.#controlled) return this.#attr("wallet-type");
|
|
2505
|
+
return this.#signedType ?? (this.#remember ? rememberedWalletChoice()?.type ?? null : null);
|
|
2506
|
+
}
|
|
2507
|
+
#switchNoteShown(size) {
|
|
2508
|
+
const a = this.#attr("switch-note");
|
|
2509
|
+
if (a === "off" || !tapGoesElsewhere(this.#walletType())) return false;
|
|
2510
|
+
return a === "on" || size === "lg" || size === "md";
|
|
2511
|
+
}
|
|
2512
|
+
/**
|
|
2513
|
+
* Signed in: note the wallet type, and remember it for this origin when the user picked it (0.6.0), plus the
|
|
2514
|
+
* identity key as known here (0.7.0, public keys only: Metanet's silent attempt goes on only for these). Pass a key
|
|
2515
|
+
* only after the site's verify route accepted it.
|
|
2516
|
+
*/
|
|
2517
|
+
#picked(option, reason, identityKey) {
|
|
2518
|
+
this.#signedType = walletTypeOf(option);
|
|
2519
|
+
if (this.#remember && isExplicitPick(option, reason)) rememberWalletChoice(option);
|
|
2520
|
+
if (this.#remember) rememberKnownIdentityKey(identityKey);
|
|
2521
|
+
}
|
|
2522
|
+
#attempt(option, outcome, started) {
|
|
2523
|
+
this.#emit("bwalletx-attempt", attemptFor(option, outcome, started));
|
|
2524
|
+
}
|
|
2525
|
+
/**
|
|
2526
|
+
* Which wallet: bWalletX by its own marker goes straight to its sheet; otherwise the chooser ("Get bWalletX" first)
|
|
2527
|
+
* when another wallet is here or the user asked; otherwise 'not-found'. null: the chooser was closed.
|
|
2528
|
+
*/
|
|
2529
|
+
async #choose(mode, ms, pairing) {
|
|
2530
|
+
if (mode === "metanet") return { option: metanetOption(), reason: "requested" };
|
|
2531
|
+
const r = mode === "signin" && this.#remember ? rememberedWalletChoice() : null;
|
|
2532
|
+
if (r && r.type !== METANET_TYPE && !(r.type === BWALLETX_TYPE && r.substrate !== "pairing")) {
|
|
2533
|
+
const m = matchRemembered(await discoverWallets({ ms, pairing: () => pairing }), r);
|
|
2534
|
+
if (m) return { option: m, reason: "remembered" };
|
|
2535
|
+
}
|
|
2536
|
+
if (mode !== "chooser") {
|
|
2537
|
+
const f = await discoverBwalletX(ms);
|
|
2538
|
+
if (f) {
|
|
2539
|
+
const substrate = f.rdns === RDNS_EXTENSION ? "extension" : f.rdns === RDNS_MOBILE ? "in-app" : "xdm";
|
|
2540
|
+
const option = brc100Option(f.wallet, { id: f.rdns ?? "bwalletx-xdm", rdns: f.rdns ?? void 0, name: "bWalletX", isBwalletX: true, substrate });
|
|
2541
|
+
return { option, reason: "bwalletx" };
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
const list = await discoverWallets({ ms: 50, pairing: () => pairing });
|
|
2545
|
+
if (mode !== "chooser" && !hasBwalletX(list) && !otherDetected(list).length) return "not-found";
|
|
2546
|
+
const picked = await chooseWallet(list, { getUrl: this.#attr("get-url") ?? void 0 });
|
|
2547
|
+
return picked ? { option: picked, reason: "chooser" } : null;
|
|
2548
|
+
}
|
|
2549
|
+
async #click(mode = "signin") {
|
|
2550
|
+
if (this.hasAttribute("disabled") || this.hasAttribute("loading") || this.#busy || this.state === "pending") return;
|
|
2551
|
+
if (!this.#emit("bwalletx-signin", { element: this, mode }, true)) return;
|
|
2552
|
+
if (mode === "signin") {
|
|
2553
|
+
const href = this.#attr("href");
|
|
2554
|
+
const authorizeUrl = this.#attr("authorize-url");
|
|
2555
|
+
if (authorizeUrl) {
|
|
2556
|
+
location.assign(await buildAuthorizeUrl({ authorizeUrl, clientId: this.#attr("client-id"), redirectUri: this.#attr("redirect-uri"), scope: this.#attr("scope") }));
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
if (href) {
|
|
2560
|
+
location.assign(href);
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
1100
2563
|
}
|
|
1101
2564
|
const challengeUrl = this.#attr("challenge-url");
|
|
1102
2565
|
if (!challengeUrl) return;
|
|
2566
|
+
const own = this.#stateful && !this.#controlled;
|
|
2567
|
+
const prev = this.#phase === "signed-in" ? "signed-in" : "signed-out";
|
|
2568
|
+
const started = Date.now();
|
|
2569
|
+
const pairing = pairingModal();
|
|
2570
|
+
let option = null;
|
|
2571
|
+
const run = ++this.#run;
|
|
2572
|
+
const live2 = () => run === this.#run;
|
|
1103
2573
|
this.#notFound = false;
|
|
1104
|
-
this.#
|
|
2574
|
+
this.#busy = true;
|
|
2575
|
+
this.#viaMetanet = mode === "metanet";
|
|
2576
|
+
this.#pendingLabel = mode === "metanet" ? PENDING_LABEL_OTHER : PENDING_LABEL;
|
|
2577
|
+
if (own) this.#set("pending");
|
|
2578
|
+
else this.#render();
|
|
1105
2579
|
try {
|
|
1106
2580
|
const ms = Number(this.#attr("discover-ms")) || DEFAULT_DISCOVER_MS;
|
|
1107
|
-
const
|
|
1108
|
-
if (
|
|
2581
|
+
const picked = await this.#choose(mode, ms, pairing);
|
|
2582
|
+
if (picked === "not-found") {
|
|
1109
2583
|
this.#notFound = true;
|
|
2584
|
+
if (own) this.#phase = "signed-out";
|
|
2585
|
+
this.#attempt(null, "not-found", started);
|
|
1110
2586
|
this.#emit("bwalletx-not-found");
|
|
1111
2587
|
if (this.#attr("require-token")) this.#emit("bwalletx-access-denied", { code: "not_installed", message: "No wallet found" });
|
|
1112
2588
|
return;
|
|
1113
2589
|
}
|
|
2590
|
+
if (!picked) {
|
|
2591
|
+
if (own) this.#phase = prev;
|
|
2592
|
+
this.#attempt(null, "cancelled", started);
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
2595
|
+
option = picked.option;
|
|
2596
|
+
const reason = picked.reason;
|
|
2597
|
+
if (!option.isBwalletX) {
|
|
2598
|
+
this.#pendingLabel = PENDING_LABEL_OTHER;
|
|
2599
|
+
if (own) this.#render();
|
|
2600
|
+
}
|
|
2601
|
+
const cw = await option.connect({ identity: false });
|
|
2602
|
+
if (!live2()) return;
|
|
2603
|
+
const wallet = cw.wallet;
|
|
2604
|
+
if (!wallet) throw Object.assign(new Error("This wallet cannot sign in here"), { code: "NOT_SUPPORTED" });
|
|
2605
|
+
const sub = substrateOf(option);
|
|
2606
|
+
const method = sub === "extension" ? "extension" : sub === "in-app" || sub === "xdm" ? "in-app" : sub === "pairing" ? "pairing" : "wallet";
|
|
1114
2607
|
const requireToken = this.#attr("require-token");
|
|
1115
2608
|
if (requireToken) {
|
|
2609
|
+
if (own) this.#phase = "signed-out";
|
|
1116
2610
|
const verifyUrl2 = this.#attr("verify-url");
|
|
1117
2611
|
const min = this.#attr("min");
|
|
1118
2612
|
const minBps = this.#attr("min-bps");
|
|
1119
2613
|
const hint = { token: { id: requireToken, ...minBps ? { minBps: Number(minBps) } : { min: min ?? "1" } } };
|
|
1120
2614
|
try {
|
|
1121
2615
|
const a = await requestAccess({
|
|
1122
|
-
wallet
|
|
2616
|
+
wallet,
|
|
1123
2617
|
challenge: (identityKey) => postJson(challengeUrl, { identityKey, require: hint }),
|
|
1124
2618
|
verify: verifyUrl2 ? (response2) => postAccess(verifyUrl2, response2) : void 0
|
|
1125
2619
|
});
|
|
2620
|
+
this.#picked(option, reason, verifyUrl2 ? a.response.identityKey : null);
|
|
2621
|
+
this.#attempt(option, "signed-in", started);
|
|
1126
2622
|
this.#emit("bwalletx-access-granted", { identityKey: a.response.identityKey, response: a.verified });
|
|
1127
2623
|
} catch (e) {
|
|
1128
2624
|
const err = e instanceof AccessError ? e : new AccessError("bad_request", String(e));
|
|
2625
|
+
this.#attempt(option, outcomeOf(e), started);
|
|
1129
2626
|
this.#emit("bwalletx-access-denied", { code: err.code, message: err.message, have: err.have, need: err.need });
|
|
1130
2627
|
}
|
|
1131
2628
|
return;
|
|
1132
2629
|
}
|
|
1133
|
-
const r = await signInWithBwalletX({ wallet: found.wallet, challenge: (identityKey) => postJson(challengeUrl, { identityKey }) });
|
|
1134
2630
|
const verifyUrl = this.#attr("verify-url");
|
|
1135
|
-
const
|
|
1136
|
-
|
|
2631
|
+
const { result: r, verified } = await signInAndVerify({
|
|
2632
|
+
wallet,
|
|
2633
|
+
// Metanet hands over its key on connect (one request at a time), so its sign-in is the signature alone.
|
|
2634
|
+
identityKey: cw.identityKey ?? void 0,
|
|
2635
|
+
skipWaitForAuthentication: option.id === METANET_OPTION_ID,
|
|
2636
|
+
// its connect() just checked the lock
|
|
2637
|
+
identityKeyHint: verifyUrl ? rememberedIdentity() : void 0,
|
|
2638
|
+
challenge: (identityKey) => postJson(challengeUrl, identityKey ? { identityKey } : {}),
|
|
2639
|
+
verify: async (res) => verifyUrl ? await postJson(verifyUrl, { identityKey: res.identityKey, nonce: res.nonce, signature: res.signature }) : {}
|
|
2640
|
+
});
|
|
2641
|
+
if (!live2()) {
|
|
2642
|
+
const out = this.#attr("logout-url");
|
|
2643
|
+
if (verifyUrl && out) await fetch(out, { method: "POST", credentials: "include", headers: { "content-type": "application/json" }, body: "{}" }).catch(() => void 0);
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
if (verifyUrl) rememberIdentity(r.identityKey);
|
|
2647
|
+
const response = verifyUrl ? verified : null;
|
|
2648
|
+
this.#picked(option, reason, verifyUrl ? r.identityKey : null);
|
|
2649
|
+
if (own) this.#signedIn({ identityKey: r.identityKey, handle: response?.handle ?? null, paymail: response?.paymail ?? null });
|
|
2650
|
+
this.#attempt(option, "signed-in", started);
|
|
2651
|
+
this.#emit("bwalletx-signed-in", { identityKey: r.identityKey, method, nonce: r.nonce, signature: r.signature, response });
|
|
1137
2652
|
} catch (error) {
|
|
1138
|
-
|
|
2653
|
+
const outcome = outcomeOf(error);
|
|
2654
|
+
this.#attempt(option, outcome, started);
|
|
2655
|
+
if (!live2()) return;
|
|
2656
|
+
const closedQr = outcome === "cancelled" && option?.kind === "pairing";
|
|
2657
|
+
if (own) {
|
|
2658
|
+
if (closedQr) this.#phase = prev;
|
|
2659
|
+
else {
|
|
2660
|
+
this.#error = signInErrorText(error);
|
|
2661
|
+
this.#phase = "error";
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
if (!closedQr) this.#emit("bwalletx-error", { error });
|
|
2665
|
+
} finally {
|
|
2666
|
+
try {
|
|
2667
|
+
pairing.close();
|
|
2668
|
+
} catch {
|
|
2669
|
+
}
|
|
2670
|
+
if (live2()) {
|
|
2671
|
+
this.#busy = false;
|
|
2672
|
+
this.#viaMetanet = false;
|
|
2673
|
+
this.#render();
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2679
|
+
function defineBwalletxSignin(tag = SIGNIN_TAG) {
|
|
2680
|
+
if (typeof window === "undefined" || typeof customElements === "undefined") return void 0;
|
|
2681
|
+
const existing = customElements.get(tag);
|
|
2682
|
+
if (existing) return existing;
|
|
2683
|
+
const C = createClass();
|
|
2684
|
+
customElements.define(tag, C);
|
|
2685
|
+
return C;
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
// src/wallet-button.ts
|
|
2689
|
+
var BUTTON_TAG = "bwalletx-button";
|
|
2690
|
+
var CONNECTED_KEY = "bwx:connected";
|
|
2691
|
+
function migrateConnectedKey() {
|
|
2692
|
+
const had = dropLegacyConnectedKey();
|
|
2693
|
+
if (had && !rememberedWalletChoice()) rememberBwalletX("extension");
|
|
2694
|
+
return had;
|
|
2695
|
+
}
|
|
2696
|
+
function rememberBwalletX(substrate) {
|
|
2697
|
+
rememberWalletChoice({ id: "bwalletx", name: "bWalletX", kind: substrate === "pairing" ? "pairing" : "brc100", isBwalletX: true, substrate, connect: async () => {
|
|
2698
|
+
throw new Error("unused");
|
|
2699
|
+
} });
|
|
2700
|
+
}
|
|
2701
|
+
function dropLegacyConnectedKey() {
|
|
2702
|
+
try {
|
|
2703
|
+
const had = globalThis.localStorage?.getItem(CONNECTED_KEY) != null;
|
|
2704
|
+
if (had) globalThis.localStorage?.removeItem(CONNECTED_KEY);
|
|
2705
|
+
return had;
|
|
2706
|
+
} catch {
|
|
2707
|
+
return false;
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
var LOOKUP = "https://pay.bwallet.space/api/paymail/lookup?key=";
|
|
2711
|
+
var LEGACY = '[data-bwx-connect], .nav-connect, .nav-acct, a[href^="https://bwalletx.com/get"], a[href^="https://www.bwalletx.com/get"]';
|
|
2712
|
+
function getUrlFor(ua, touchMac = false) {
|
|
2713
|
+
if (/iPhone|iPad|iPod/i.test(ua) || touchMac && /Macintosh/.test(ua)) return "https://bwalletx.com/iphone";
|
|
2714
|
+
if (/Android/i.test(ua)) return "https://bwalletx.com/android";
|
|
2715
|
+
if (/Chrome\//.test(ua) && !/Edg\/|OPR\//.test(ua)) return "https://bwalletx.com/extension";
|
|
2716
|
+
return "https://bwalletx.com/get";
|
|
2717
|
+
}
|
|
2718
|
+
function stateFor(o) {
|
|
2719
|
+
if (o.connected) return "connected";
|
|
2720
|
+
if (o.found === null) return o.phone ? "available" : "checking";
|
|
2721
|
+
return o.found || o.phone ? "available" : "none";
|
|
2722
|
+
}
|
|
2723
|
+
function handleLabel(key, paymail) {
|
|
2724
|
+
if (paymail && /@bwalletx\.com$/i.test(paymail)) return "$" + paymail.split("@")[0];
|
|
2725
|
+
if (paymail) return paymail;
|
|
2726
|
+
return key.slice(0, 6) + "\u2026" + key.slice(-4);
|
|
2727
|
+
}
|
|
2728
|
+
var CHIP_CSS = `
|
|
2729
|
+
a.b,button.b{all:unset;box-sizing:border-box}
|
|
2730
|
+
.chip{background:#0b0b0c;color:#ffd24d;border:1px solid rgba(255,210,77,.45);box-shadow:none}
|
|
2731
|
+
.chip:hover{background:#151517;border-color:#ffd24d}
|
|
2732
|
+
.chip .dot{width:8px;height:8px;border-radius:50%;background:#34d399;flex:none}
|
|
2733
|
+
.txt b{overflow:hidden;text-overflow:ellipsis;max-width:min(40vw,220px)}
|
|
2734
|
+
: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}
|
|
2735
|
+
:host(:not([size])) .mark,:host([size=sm]) a.btn .mark{width:22px;height:22px}
|
|
2736
|
+
a.btn{all:unset;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:12px;cursor:pointer;
|
|
2737
|
+
padding:16px 20px;border-radius:12px;font-size:16px;font-weight:700;line-height:1.2;background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;
|
|
2738
|
+
box-shadow:0 10px 15px -3px rgba(245,158,11,.2),0 4px 6px -4px rgba(245,158,11,.2)}
|
|
2739
|
+
a.btn:hover{background:linear-gradient(180deg,#fde68a,#fbbf24)}
|
|
2740
|
+
a.btn:focus-visible{outline:2px solid #b45309;outline-offset:3px}
|
|
2741
|
+
:host([size=md]) a.btn{padding:10px 16px;border-radius:10px;font-size:15px;gap:10px}
|
|
2742
|
+
`;
|
|
2743
|
+
var live = /* @__PURE__ */ new Set();
|
|
2744
|
+
function createClass2() {
|
|
2745
|
+
return class BwalletxButtonElement extends HTMLElement {
|
|
2746
|
+
#root;
|
|
2747
|
+
#state = "checking";
|
|
2748
|
+
#key = null;
|
|
2749
|
+
#paymail = null;
|
|
2750
|
+
#duplicate = false;
|
|
2751
|
+
constructor() {
|
|
2752
|
+
super();
|
|
2753
|
+
this.#root = this.attachShadow({ mode: "open" });
|
|
2754
|
+
}
|
|
2755
|
+
get state() {
|
|
2756
|
+
return this.#state;
|
|
2757
|
+
}
|
|
2758
|
+
get identityKey() {
|
|
2759
|
+
return this.#key;
|
|
2760
|
+
}
|
|
2761
|
+
connectedCallback() {
|
|
2762
|
+
if (live.size && ![...live].includes(this)) {
|
|
2763
|
+
this.#duplicate = true;
|
|
2764
|
+
this.hidden = true;
|
|
2765
|
+
console.warn("[bwalletx] A second <bwalletx-button> is on this page; it is hidden. Use one per page.");
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2768
|
+
live.add(this);
|
|
2769
|
+
this.#hideLegacy();
|
|
2770
|
+
migrateConnectedKey();
|
|
2771
|
+
const phone = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
|
2772
|
+
this.#set(stateFor({ connected: false, found: null, phone }));
|
|
2773
|
+
const ms = Number(this.getAttribute("discover-ms")) || 800;
|
|
2774
|
+
void discoverBwalletX(ms).then((f) => {
|
|
2775
|
+
if (!this.isConnected) return;
|
|
2776
|
+
if (this.#state !== "connected" && this.#state !== "connecting") this.#set(stateFor({ connected: false, found: !!f, phone }));
|
|
2777
|
+
}).catch(() => {
|
|
2778
|
+
if (this.#state === "checking") this.#set("none");
|
|
2779
|
+
});
|
|
2780
|
+
void this.#restoreSession();
|
|
2781
|
+
}
|
|
2782
|
+
/** A live session your server reports (session-url): the chip, with no wallet call. */
|
|
2783
|
+
async #restoreSession() {
|
|
2784
|
+
const url = this.getAttribute("session-url");
|
|
2785
|
+
if (!url) return;
|
|
2786
|
+
try {
|
|
2787
|
+
const r = await fetch(url, { credentials: "include", headers: { accept: "application/json" } });
|
|
2788
|
+
const j = await r.json().catch(() => ({}));
|
|
2789
|
+
if (j.identityKey && this.isConnected && this.#state !== "connected" && this.#state !== "connecting") {
|
|
2790
|
+
this.#connected(j.identityKey);
|
|
2791
|
+
if (j.paymail) {
|
|
2792
|
+
this.#paymail = j.paymail;
|
|
2793
|
+
this.#render();
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
} catch {
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
disconnectedCallback() {
|
|
2800
|
+
if (!this.#duplicate) live.delete(this);
|
|
2801
|
+
}
|
|
2802
|
+
#hideLegacy() {
|
|
2803
|
+
const scope = this.closest("header, nav") ?? document;
|
|
2804
|
+
for (const el2 of Array.from(scope.querySelectorAll(LEGACY))) {
|
|
2805
|
+
if (el2 === this || el2.contains(this)) continue;
|
|
2806
|
+
el2.hidden = true;
|
|
2807
|
+
el2.style.setProperty("display", "none", "important");
|
|
2808
|
+
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.");
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
#emit(type, detail) {
|
|
2812
|
+
this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
|
|
2813
|
+
}
|
|
2814
|
+
#set(s) {
|
|
2815
|
+
this.#state = s;
|
|
2816
|
+
this.setAttribute("state", s);
|
|
2817
|
+
this.#render();
|
|
2818
|
+
}
|
|
2819
|
+
#connected(key) {
|
|
2820
|
+
this.#key = key;
|
|
2821
|
+
this.#set("connected");
|
|
2822
|
+
this.#emit("bwalletx-connected", { identityKey: key });
|
|
2823
|
+
const lookup = this.getAttribute("lookup-url");
|
|
2824
|
+
if (lookup === "off") return;
|
|
2825
|
+
void fetch((lookup ?? LOOKUP) + encodeURIComponent(key)).then((r) => r.ok ? r.json() : null).then((j) => {
|
|
2826
|
+
if (j && typeof j.paymail === "string" && this.#key === key) {
|
|
2827
|
+
this.#paymail = j.paymail;
|
|
2828
|
+
this.#render();
|
|
2829
|
+
}
|
|
2830
|
+
}).catch(() => void 0);
|
|
2831
|
+
}
|
|
2832
|
+
async connect() {
|
|
2833
|
+
if (this.#state === "connecting" || this.#state === "connected") return;
|
|
2834
|
+
const modal = pairingModal({ title: "Connect bWalletX" });
|
|
2835
|
+
this.#set("connecting");
|
|
2836
|
+
try {
|
|
2837
|
+
const { wallet, method } = await connectBwalletX({ pairing: modal });
|
|
2838
|
+
const { publicKey } = await wallet.getPublicKey({ identityKey: true });
|
|
2839
|
+
rememberBwalletX(method === "pairing" ? "pairing" : method === "in-app" ? "in-app" : "extension");
|
|
2840
|
+
this.#connected(publicKey);
|
|
2841
|
+
} catch (e) {
|
|
2842
|
+
if (e?.code === "NOT_FOUND") this.#set("none");
|
|
2843
|
+
else {
|
|
2844
|
+
this.#set("available");
|
|
2845
|
+
if (e?.name !== "AbortError") console.warn("[bwalletx] connect:", e);
|
|
2846
|
+
}
|
|
1139
2847
|
} finally {
|
|
1140
|
-
|
|
2848
|
+
try {
|
|
2849
|
+
modal.close();
|
|
2850
|
+
} catch {
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
disconnect() {
|
|
2855
|
+
try {
|
|
2856
|
+
clearPairing();
|
|
2857
|
+
} catch {
|
|
2858
|
+
}
|
|
2859
|
+
try {
|
|
2860
|
+
window.postMessage({ type: "bwallet:disconnect" }, location.origin);
|
|
2861
|
+
} catch {
|
|
2862
|
+
}
|
|
2863
|
+
this.#key = null;
|
|
2864
|
+
this.#paymail = null;
|
|
2865
|
+
dropLegacyConnectedKey();
|
|
2866
|
+
if (rememberedWalletChoice()?.type === BWALLETX_TYPE) forgetWalletChoice();
|
|
2867
|
+
this.#set("available");
|
|
2868
|
+
this.#emit("bwalletx-disconnected");
|
|
2869
|
+
}
|
|
2870
|
+
#render() {
|
|
2871
|
+
const esc = (s2) => s2.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
2872
|
+
const size = this.getAttribute("size") ?? "sm";
|
|
2873
|
+
const px = size === "lg" ? 20 : size === "md" ? 17 : 15;
|
|
2874
|
+
const mark = `<span class="mark" part="mark">${markSvg(px)}</span>`;
|
|
2875
|
+
const css = `<style>${SIGNIN_CSS}${CHIP_CSS}</style>`;
|
|
2876
|
+
const s = this.#state;
|
|
2877
|
+
if (s === "none") {
|
|
2878
|
+
const href = this.getAttribute("get-url") ?? getUrlFor(navigator.userAgent, (navigator.maxTouchPoints ?? 0) > 1);
|
|
2879
|
+
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>`;
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
if (s === "connected" && this.#key) {
|
|
2883
|
+
const name = handleLabel(this.#key, this.#paymail);
|
|
2884
|
+
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>`;
|
|
2885
|
+
this.#root.querySelector("button").onclick = () => this.disconnect();
|
|
2886
|
+
return;
|
|
1141
2887
|
}
|
|
2888
|
+
const busy = s === "connecting" || s === "checking";
|
|
2889
|
+
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>`;
|
|
2890
|
+
this.#root.querySelector("button").onclick = () => void this.connect();
|
|
1142
2891
|
}
|
|
1143
2892
|
};
|
|
1144
2893
|
}
|
|
1145
|
-
function
|
|
1146
|
-
if (typeof
|
|
1147
|
-
|
|
1148
|
-
if (existing) return existing;
|
|
1149
|
-
const C = createClass();
|
|
1150
|
-
customElements.define(tag, C);
|
|
1151
|
-
return C;
|
|
2894
|
+
function defineBwalletxButton(tag = BUTTON_TAG) {
|
|
2895
|
+
if (typeof customElements === "undefined" || customElements.get(tag)) return;
|
|
2896
|
+
customElements.define(tag, createClass2());
|
|
1152
2897
|
}
|
|
1153
2898
|
|
|
1154
2899
|
// src/pay.ts
|
|
1155
|
-
var
|
|
2900
|
+
var import_sdk7 = require("@bsv/sdk");
|
|
1156
2901
|
var BRC29_PROTOCOL = [2, "3241645161d8"];
|
|
2902
|
+
var NO_COINS_TEXT = "Your wallet doesn't have enough to cover this payment and its small network fee. Add a little and try again.";
|
|
2903
|
+
function isInsufficientFunds(e) {
|
|
2904
|
+
const err = e;
|
|
2905
|
+
if (!err) return false;
|
|
2906
|
+
if (err.name === "WERR_INSUFFICIENT_FUNDS" || err.code === "WERR_INSUFFICIENT_FUNDS" || err.code === "ERR_INSUFFICIENT_FUNDS") return true;
|
|
2907
|
+
if (err.isError === true && err.code === 7) return true;
|
|
2908
|
+
const msg = typeof err.message === "string" ? err.message : String(e);
|
|
2909
|
+
return /insufficient (funds|balance)|not enough (funds|coins|satoshis|sats)|more satoshis are needed/i.test(msg);
|
|
2910
|
+
}
|
|
1157
2911
|
var PaymentError = class extends Error {
|
|
1158
2912
|
constructor(code, message, maybeSent = false, cause) {
|
|
1159
2913
|
super(message);
|
|
@@ -1172,13 +2926,13 @@ var isPaymail = (s) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
|
|
|
1172
2926
|
function randomB64(n) {
|
|
1173
2927
|
const b = new Uint8Array(n);
|
|
1174
2928
|
globalThis.crypto.getRandomValues(b);
|
|
1175
|
-
return
|
|
2929
|
+
return import_sdk7.Utils.toBase64(Array.from(b));
|
|
1176
2930
|
}
|
|
1177
2931
|
function opReturn(text) {
|
|
1178
|
-
const d =
|
|
2932
|
+
const d = import_sdk7.Utils.toArray(text, "utf8");
|
|
1179
2933
|
const len = d.length;
|
|
1180
2934
|
const push = len < 76 ? [len] : len <= 255 ? [76, len] : [77, len & 255, len >> 8];
|
|
1181
|
-
return
|
|
2935
|
+
return import_sdk7.Utils.toHex([0, 106, ...push, ...d]);
|
|
1182
2936
|
}
|
|
1183
2937
|
var clip = (s, n) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
|
|
1184
2938
|
var describe = (s) => clip(s.length < 5 ? `${s} payment`.trim() : s, 50);
|
|
@@ -1249,6 +3003,7 @@ async function runAction(wallet, description, outputs, labels) {
|
|
|
1249
3003
|
options: { randomizeOutputs: false }
|
|
1250
3004
|
});
|
|
1251
3005
|
} catch (e) {
|
|
3006
|
+
if (isInsufficientFunds(e)) throw new PaymentError("INSUFFICIENT_FUNDS", NO_COINS_TEXT, false, e);
|
|
1252
3007
|
const msg = e?.message ?? String(e);
|
|
1253
3008
|
const rejected = /denied|reject|cancel|abort|user/i.test(msg) || e?.code === 4001;
|
|
1254
3009
|
throw new PaymentError(rejected ? "WALLET_REJECTED" : "WALLET_ERROR", rejected ? "The payment was declined in the wallet." : `The wallet could not make the payment: ${msg}`, !rejected, e);
|
|
@@ -1274,7 +3029,7 @@ async function pay(source, req, o = {}) {
|
|
|
1274
3029
|
} catch (e) {
|
|
1275
3030
|
throw new PaymentError("WALLET_ERROR", `The wallet could not derive the payment key: ${e.message}`, false, e);
|
|
1276
3031
|
}
|
|
1277
|
-
outputs = [{ script: new
|
|
3032
|
+
outputs = [{ script: new import_sdk7.P2PKH().lock(import_sdk7.PublicKey.fromString(pk).toHash()).toHex(), satoshis: sats, description: "payment" }];
|
|
1278
3033
|
remittance = { senderIdentityKey: me, derivationPrefix, derivationSuffix, outputIndex: 0 };
|
|
1279
3034
|
} else if (isPaymail(to)) {
|
|
1280
3035
|
const r2 = await (o.resolvePaymail ?? ((p, s) => resolvePaymailP2P(p, s, o.fetch ?? fetch)))(to, sats);
|
|
@@ -1282,7 +3037,7 @@ async function pay(source, req, o = {}) {
|
|
|
1282
3037
|
submit = r2.submit;
|
|
1283
3038
|
} else {
|
|
1284
3039
|
try {
|
|
1285
|
-
outputs = [{ script: new
|
|
3040
|
+
outputs = [{ script: new import_sdk7.P2PKH().lock(to).toHex(), satoshis: sats, description: "payment" }];
|
|
1286
3041
|
} catch {
|
|
1287
3042
|
throw new PaymentError("BAD_REQUEST", `Not a paymail, address or identity key: ${to}`);
|
|
1288
3043
|
}
|
|
@@ -1292,7 +3047,7 @@ async function pay(source, req, o = {}) {
|
|
|
1292
3047
|
if (!r.txid) throw new PaymentError("WALLET_ERROR", "The wallet did not return a txid", true);
|
|
1293
3048
|
const tx = r.tx ? Array.from(r.tx) : void 0;
|
|
1294
3049
|
if (submit && tx) {
|
|
1295
|
-
await submit(
|
|
3050
|
+
await submit(import_sdk7.Transaction.fromAtomicBEEF(tx).toHex()).catch(() => {
|
|
1296
3051
|
});
|
|
1297
3052
|
}
|
|
1298
3053
|
return { txid: r.txid, tx, sats, outputs, remittance };
|
|
@@ -1312,255 +3067,6 @@ async function buyToken(source, req) {
|
|
|
1312
3067
|
return res;
|
|
1313
3068
|
}
|
|
1314
3069
|
|
|
1315
|
-
// src/wallets.ts
|
|
1316
|
-
var import_sdk6 = require("@bsv/sdk");
|
|
1317
|
-
async function brc100Connected(wallet, o) {
|
|
1318
|
-
await wallet.waitForAuthentication({}).catch(() => {
|
|
1319
|
-
});
|
|
1320
|
-
const { publicKey } = await wallet.getPublicKey({ identityKey: true });
|
|
1321
|
-
return { wallet, payer: wallet, identityKey: publicKey, optionId: o.id, kind: o.kind };
|
|
1322
|
-
}
|
|
1323
|
-
function brc100Option(wallet, info) {
|
|
1324
|
-
const opt = {
|
|
1325
|
-
id: info.id,
|
|
1326
|
-
name: info.name,
|
|
1327
|
-
icon: info.icon,
|
|
1328
|
-
rdns: info.rdns,
|
|
1329
|
-
kind: "brc100",
|
|
1330
|
-
isBwalletX: info.isBwalletX ?? false,
|
|
1331
|
-
connect: () => brc100Connected(wallet, opt)
|
|
1332
|
-
};
|
|
1333
|
-
return opt;
|
|
1334
|
-
}
|
|
1335
|
-
function pairingOption(pairing) {
|
|
1336
|
-
const opt = {
|
|
1337
|
-
id: "bwalletx-pairing",
|
|
1338
|
-
name: "bWalletX on your phone",
|
|
1339
|
-
kind: "pairing",
|
|
1340
|
-
isBwalletX: true,
|
|
1341
|
-
async connect() {
|
|
1342
|
-
const p = typeof pairing === "function" ? pairing() : pairing;
|
|
1343
|
-
const w = await restorePairing(p) ?? await pairBwalletX(p);
|
|
1344
|
-
return brc100Connected(w, opt);
|
|
1345
|
-
}
|
|
1346
|
-
};
|
|
1347
|
-
return opt;
|
|
1348
|
-
}
|
|
1349
|
-
function handcashAdapter(o) {
|
|
1350
|
-
const opt = {
|
|
1351
|
-
id: "handcash",
|
|
1352
|
-
name: "HandCash",
|
|
1353
|
-
icon: o.icon,
|
|
1354
|
-
kind: "handcash",
|
|
1355
|
-
isBwalletX: false,
|
|
1356
|
-
async connect() {
|
|
1357
|
-
const p = await o.login();
|
|
1358
|
-
return {
|
|
1359
|
-
wallet: null,
|
|
1360
|
-
payer: o.pay ? { pay: o.pay } : null,
|
|
1361
|
-
identityKey: p.identityKey ?? null,
|
|
1362
|
-
handle: p.handle,
|
|
1363
|
-
paymail: p.paymail ?? `${p.handle.replace(/^\$/, "")}@handcash.io`,
|
|
1364
|
-
optionId: opt.id,
|
|
1365
|
-
kind: "handcash",
|
|
1366
|
-
transitional: true
|
|
1367
|
-
};
|
|
1368
|
-
}
|
|
1369
|
-
};
|
|
1370
|
-
return opt;
|
|
1371
|
-
}
|
|
1372
|
-
var NAMES = { [RDNS_EXTENSION]: "bWalletX", "space.bwallet.mobile": "bWalletX" };
|
|
1373
|
-
async function probe(substrate, ms) {
|
|
1374
|
-
try {
|
|
1375
|
-
const w = new import_sdk6.WalletClient(substrate);
|
|
1376
|
-
const ok = await Promise.race([w.isAuthenticated({}).then(() => true), new Promise((r) => setTimeout(() => r(false), ms))]);
|
|
1377
|
-
return ok ? w : null;
|
|
1378
|
-
} catch {
|
|
1379
|
-
return null;
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
async function discoverWallets(o = {}) {
|
|
1383
|
-
const out = [];
|
|
1384
|
-
const ms = o.ms ?? 300;
|
|
1385
|
-
if (typeof window !== "undefined") {
|
|
1386
|
-
const seen = /* @__PURE__ */ new Map();
|
|
1387
|
-
const onAnnounce = (e) => {
|
|
1388
|
-
const d = e.detail;
|
|
1389
|
-
if (!d?.wallet) return;
|
|
1390
|
-
const rdns = d.info?.rdns ?? d.info?.uuid ?? `wallet-${seen.size}`;
|
|
1391
|
-
if (seen.has(rdns)) return;
|
|
1392
|
-
const isB = BWALLETX_RDNS.includes(rdns);
|
|
1393
|
-
seen.set(rdns, brc100Option(d.wallet, { id: rdns, rdns, name: NAMES[rdns] ?? d.info?.name ?? rdns, icon: d.info?.icon, isBwalletX: isB }));
|
|
1394
|
-
};
|
|
1395
|
-
window.addEventListener("brc100:announceWallet", onAnnounce);
|
|
1396
|
-
window.dispatchEvent(new Event("brc100:requestWallet"));
|
|
1397
|
-
const local = o.probeLocal ? Promise.all([probe("json-api", ms), probe("Cicada", ms)]) : Promise.resolve([null, null]);
|
|
1398
|
-
await new Promise((r) => setTimeout(r, seen.size > 0 || window.CWI ? Math.min(ms, 50) : ms));
|
|
1399
|
-
window.removeEventListener("brc100:announceWallet", onAnnounce);
|
|
1400
|
-
out.push(...seen.values());
|
|
1401
|
-
const cwi = window.CWI;
|
|
1402
|
-
if (cwi && ![...seen.values()].some((x) => x.rdns === "space.bwallet.mobile")) {
|
|
1403
|
-
out.push(brc100Option(cwi, { id: "window.CWI", name: "This app\u2019s wallet", isBwalletX: false }));
|
|
1404
|
-
}
|
|
1405
|
-
const [json, cicada] = await local;
|
|
1406
|
-
if (json) out.push(brc100Option(json, { id: "localhost:3321", name: "Desktop wallet (localhost:3321)" }));
|
|
1407
|
-
if (cicada) out.push(brc100Option(cicada, { id: "localhost:3301", name: "Desktop wallet (localhost:3301)" }));
|
|
1408
|
-
}
|
|
1409
|
-
if (o.pairing) out.push(pairingOption(o.pairing));
|
|
1410
|
-
out.push(...o.extra ?? []);
|
|
1411
|
-
const rank = (w) => w.isBwalletX ? 0 : w.kind === "handcash" ? 2 : 1;
|
|
1412
|
-
return out.map((w, i) => ({ w, i })).sort((a, b) => rank(a.w) - rank(b.w) || a.i - b.i).map((x) => x.w);
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
// src/modal.ts
|
|
1416
|
-
var import_qrcode_generator = __toESM(require("qrcode-generator"), 1);
|
|
1417
|
-
var CSS2 = `
|
|
1418
|
-
.bwx-ov{position:fixed;inset:0;z-index:2147483000;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;padding:16px;font:15px/1.4 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}
|
|
1419
|
-
.bwx-box{background:#0b0b0b;color:#f4f1e8;border:1px solid #3a3220;border-radius:16px;width:min(360px,100%);padding:20px;box-shadow:0 20px 60px rgba(0,0,0,.5)}
|
|
1420
|
-
.bwx-box h2{margin:0 0 6px;font-size:18px;color:#e8c766}
|
|
1421
|
-
.bwx-box p{margin:0 0 14px;color:#bdb6a3;font-size:14px}
|
|
1422
|
-
.bwx-qr{background:#fff;border-radius:12px;padding:12px;display:flex;justify-content:center}
|
|
1423
|
-
.bwx-qr svg{width:100%;height:auto;max-width:260px}
|
|
1424
|
-
.bwx-code{margin:14px 0 0;text-align:center;font-size:28px;letter-spacing:.3em;color:#e8c766;font-variant-numeric:tabular-nums}
|
|
1425
|
-
.bwx-row{display:flex;gap:8px;margin-top:14px}
|
|
1426
|
-
.bwx-btn{flex:1;border:1px solid #3a3220;background:#15130e;color:#f4f1e8;border-radius:10px;padding:10px 12px;font:inherit;cursor:pointer}
|
|
1427
|
-
.bwx-btn:hover{border-color:#e8c766}
|
|
1428
|
-
a.bwx-btn{display:block;text-align:center;text-decoration:none;box-sizing:border-box}
|
|
1429
|
-
.bwx-primary{background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;border:0;font-weight:700;min-height:48px;padding:13px 12px}
|
|
1430
|
-
.bwx-stack{display:flex;flex-direction:column;gap:8px}
|
|
1431
|
-
.bwx-list{display:flex;flex-direction:column;gap:8px}
|
|
1432
|
-
.bwx-opt{display:flex;align-items:center;gap:10px;text-align:left}
|
|
1433
|
-
.bwx-opt img{width:24px;height:24px;border-radius:6px}
|
|
1434
|
-
.bwx-tag{margin-left:auto;font-size:11px;color:#8d866f}
|
|
1435
|
-
`;
|
|
1436
|
-
function mount() {
|
|
1437
|
-
const root = document.createElement("div");
|
|
1438
|
-
root.className = "bwx-ov";
|
|
1439
|
-
root.setAttribute("role", "dialog");
|
|
1440
|
-
root.setAttribute("aria-modal", "true");
|
|
1441
|
-
const style = document.createElement("style");
|
|
1442
|
-
style.textContent = CSS2;
|
|
1443
|
-
const box = document.createElement("div");
|
|
1444
|
-
box.className = "bwx-box";
|
|
1445
|
-
root.append(style, box);
|
|
1446
|
-
document.body.appendChild(root);
|
|
1447
|
-
return { root, box, close: () => root.remove() };
|
|
1448
|
-
}
|
|
1449
|
-
function el(tag, cls, text) {
|
|
1450
|
-
const e = document.createElement(tag);
|
|
1451
|
-
if (cls) e.className = cls;
|
|
1452
|
-
if (text != null) e.textContent = text;
|
|
1453
|
-
return e;
|
|
1454
|
-
}
|
|
1455
|
-
function qrSvg(text) {
|
|
1456
|
-
const q = (0, import_qrcode_generator.default)(0, "M");
|
|
1457
|
-
q.addData(text);
|
|
1458
|
-
q.make();
|
|
1459
|
-
return q.createSvgTag({ cellSize: 4, margin: 2, scalable: true });
|
|
1460
|
-
}
|
|
1461
|
-
function isTouchDevice(env = globalThis) {
|
|
1462
|
-
const nav = env.navigator;
|
|
1463
|
-
if (!nav) return false;
|
|
1464
|
-
const ua = nav.userAgent ?? "";
|
|
1465
|
-
if (/Android|iPhone|iPad|iPod|Mobile|Silk|Kindle/i.test(ua)) return true;
|
|
1466
|
-
if (/Macintosh/.test(ua) && (nav.maxTouchPoints ?? 0) > 1) return true;
|
|
1467
|
-
try {
|
|
1468
|
-
return !!env.matchMedia?.("(pointer: coarse)").matches && !env.matchMedia?.("(hover: hover)").matches;
|
|
1469
|
-
} catch {
|
|
1470
|
-
return false;
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
function pairingModal(o = {}) {
|
|
1474
|
-
const ac = new AbortController();
|
|
1475
|
-
let ui = null;
|
|
1476
|
-
let codeEl = null;
|
|
1477
|
-
const close = () => {
|
|
1478
|
-
ui?.close();
|
|
1479
|
-
ui = null;
|
|
1480
|
-
};
|
|
1481
|
-
ac.signal.addEventListener("abort", close);
|
|
1482
|
-
return {
|
|
1483
|
-
signal: ac.signal,
|
|
1484
|
-
close,
|
|
1485
|
-
onLink(link) {
|
|
1486
|
-
if (typeof document === "undefined") return;
|
|
1487
|
-
close();
|
|
1488
|
-
ui = mount();
|
|
1489
|
-
const app = o.mode === "app" || (o.mode ?? "auto") === "auto" && isTouchDevice();
|
|
1490
|
-
const h = el("h2", "", o.title ?? "Connect bWalletX");
|
|
1491
|
-
const cancel = el("button", "bwx-btn", "Cancel");
|
|
1492
|
-
cancel.onclick = () => {
|
|
1493
|
-
o.onCancel?.();
|
|
1494
|
-
ac.abort();
|
|
1495
|
-
};
|
|
1496
|
-
codeEl = el("div", "bwx-code", "");
|
|
1497
|
-
codeEl.setAttribute("aria-live", "polite");
|
|
1498
|
-
if (app) {
|
|
1499
|
-
const p2 = el("p", "", "Open bWalletX, approve, then come back to this page.");
|
|
1500
|
-
const stack = el("div", "bwx-stack");
|
|
1501
|
-
const open = el("a", "bwx-btn bwx-primary", "Open bWalletX");
|
|
1502
|
-
open.href = link;
|
|
1503
|
-
open.rel = "noopener";
|
|
1504
|
-
const web = el("button", "bwx-btn", "Use web.bwalletx.com instead");
|
|
1505
|
-
web.onclick = () => {
|
|
1506
|
-
void navigator.clipboard?.writeText(link).catch(() => void 0);
|
|
1507
|
-
window.open(WEB_WALLET_URL, "_blank", "noopener");
|
|
1508
|
-
p2.textContent = "Link copied. In web.bwalletx.com tap Connect and paste it, then come back here.";
|
|
1509
|
-
};
|
|
1510
|
-
stack.append(open, web, cancel);
|
|
1511
|
-
ui.box.append(h, p2, codeEl, stack);
|
|
1512
|
-
return;
|
|
1513
|
-
}
|
|
1514
|
-
const p = el("p", "", "Scan with bWalletX on your phone, or paste the link into web.bwalletx.com.");
|
|
1515
|
-
const qr = el("div", "bwx-qr");
|
|
1516
|
-
qr.innerHTML = qrSvg(link);
|
|
1517
|
-
const row = el("div", "bwx-row");
|
|
1518
|
-
const copy = el("button", "bwx-btn", "Copy link");
|
|
1519
|
-
copy.onclick = () => void navigator.clipboard?.writeText(link).then(() => copy.textContent = "Copied");
|
|
1520
|
-
row.append(copy, cancel);
|
|
1521
|
-
ui.box.append(h, p, qr, codeEl, row);
|
|
1522
|
-
},
|
|
1523
|
-
onCode(code) {
|
|
1524
|
-
if (codeEl) {
|
|
1525
|
-
codeEl.textContent = code;
|
|
1526
|
-
const p = ui?.box.querySelector("p");
|
|
1527
|
-
if (p) p.textContent = "Check bWalletX shows the same code, then tap Connect.";
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
};
|
|
1531
|
-
}
|
|
1532
|
-
function pickWallet(options, o = {}) {
|
|
1533
|
-
if (typeof document === "undefined") return Promise.resolve(null);
|
|
1534
|
-
return new Promise((resolve) => {
|
|
1535
|
-
const ui = mount();
|
|
1536
|
-
const done = (w) => {
|
|
1537
|
-
ui.close();
|
|
1538
|
-
resolve(w);
|
|
1539
|
-
};
|
|
1540
|
-
ui.box.append(el("h2", "", o.title ?? "Choose a wallet"));
|
|
1541
|
-
if (!options.length) ui.box.append(el("p", "", "No wallet found. Get bWalletX at bwalletx.com."));
|
|
1542
|
-
const list = el("div", "bwx-list");
|
|
1543
|
-
for (const w of options) {
|
|
1544
|
-
const b = el("button", "bwx-btn bwx-opt");
|
|
1545
|
-
if (w.icon) {
|
|
1546
|
-
const img = el("img");
|
|
1547
|
-
img.src = w.icon;
|
|
1548
|
-
img.alt = "";
|
|
1549
|
-
b.append(img);
|
|
1550
|
-
}
|
|
1551
|
-
b.append(el("span", "", w.name));
|
|
1552
|
-
if (w.kind === "handcash") b.append(el("span", "bwx-tag", "until BRC-100"));
|
|
1553
|
-
b.onclick = () => done(w);
|
|
1554
|
-
list.append(b);
|
|
1555
|
-
}
|
|
1556
|
-
const cancel = el("button", "bwx-btn", "Cancel");
|
|
1557
|
-
cancel.onclick = () => done(null);
|
|
1558
|
-
ui.root.onclick = (e) => e.target === ui.root && done(null);
|
|
1559
|
-
ui.box.append(list, el("div", "bwx-row"));
|
|
1560
|
-
ui.box.lastChild.append(cancel);
|
|
1561
|
-
});
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
3070
|
// src/theme.ts
|
|
1565
3071
|
var WALLET_THEME_META = "bwalletx-theme";
|
|
1566
3072
|
var ID = /^[a-z0-9-]{1,32}$/;
|
|
@@ -1585,57 +3091,143 @@ function declareWalletTheme(theme) {
|
|
|
1585
3091
|
ACCESS_PROTOCOL,
|
|
1586
3092
|
ACCESS_VERSION,
|
|
1587
3093
|
AccessError,
|
|
3094
|
+
BAVATAR_URL,
|
|
1588
3095
|
BRC29_PROTOCOL,
|
|
3096
|
+
BUTTON_TAG,
|
|
1589
3097
|
BWALLETX_RDNS,
|
|
3098
|
+
BWALLETX_TYPE,
|
|
3099
|
+
CONNECTED_KEY,
|
|
3100
|
+
CONTINUE_OTHER_LABEL,
|
|
1590
3101
|
DEFAULT_DISCOVER_MS,
|
|
3102
|
+
DESKTOP_OPTION_ID,
|
|
1591
3103
|
GET_URL,
|
|
3104
|
+
IDENTITY_HINT_KEY,
|
|
3105
|
+
KNOWN_KEYS_KEY,
|
|
1592
3106
|
LOGIN_PROTOCOL,
|
|
1593
3107
|
LOGO_URL,
|
|
3108
|
+
MENU_CSS,
|
|
3109
|
+
METANET_ANSWER_MS,
|
|
3110
|
+
METANET_JSON_API,
|
|
3111
|
+
METANET_LABEL,
|
|
3112
|
+
METANET_NOTE,
|
|
3113
|
+
METANET_OPTION_ID,
|
|
3114
|
+
METANET_REMEMBERED_NOTE,
|
|
3115
|
+
METANET_SILENT_MS,
|
|
3116
|
+
METANET_TYPE,
|
|
3117
|
+
METANET_UNREACHABLE,
|
|
3118
|
+
MetanetUnreachableError,
|
|
3119
|
+
NOT_YOU_LABEL,
|
|
3120
|
+
NO_COINS_TEXT,
|
|
3121
|
+
PAYMAIL_LOOKUP_URL,
|
|
3122
|
+
PENDING_LABEL,
|
|
3123
|
+
PENDING_LABEL_OTHER,
|
|
3124
|
+
PENDING_NOTE,
|
|
1594
3125
|
PROTOCOL_MEDIA,
|
|
1595
3126
|
PaymentError,
|
|
1596
3127
|
RDNS_EXTENSION,
|
|
1597
3128
|
RDNS_MOBILE,
|
|
1598
3129
|
SIGNIN_CSS,
|
|
1599
3130
|
SIGNIN_TAG,
|
|
3131
|
+
SWITCH_NOTE,
|
|
3132
|
+
WALLET_CHOICE_KEY,
|
|
1600
3133
|
WALLET_THEME_META,
|
|
1601
3134
|
WEB_WALLET_URL,
|
|
1602
3135
|
accessErrorMessage,
|
|
3136
|
+
accountLabel,
|
|
1603
3137
|
amountInSats,
|
|
3138
|
+
attachRemembered,
|
|
3139
|
+
attemptFor,
|
|
3140
|
+
autoChoice,
|
|
3141
|
+
avatarUrlFor,
|
|
1604
3142
|
bindPrewarm,
|
|
1605
3143
|
brc100Option,
|
|
1606
3144
|
buildAuthorizeUrl,
|
|
1607
3145
|
buyToken,
|
|
1608
3146
|
canonicalJson,
|
|
3147
|
+
challengeWantsIdentityKey,
|
|
3148
|
+
chooseWallet,
|
|
3149
|
+
chooserOrder,
|
|
3150
|
+
claimSilentAttempt,
|
|
1609
3151
|
clearPairing,
|
|
1610
3152
|
connectBwalletX,
|
|
3153
|
+
connectMetanetDesktop,
|
|
3154
|
+
cwiIsAnnounced,
|
|
3155
|
+
decideWallet,
|
|
1611
3156
|
declareWalletTheme,
|
|
3157
|
+
defineBwalletxButton,
|
|
1612
3158
|
defineBwalletxSignin,
|
|
3159
|
+
desktopOption,
|
|
1613
3160
|
discoverBwalletX,
|
|
1614
3161
|
discoverWallets,
|
|
3162
|
+
dropLegacyConnectedKey,
|
|
1615
3163
|
findBwalletX,
|
|
3164
|
+
forgetWalletChoice,
|
|
3165
|
+
getUrlFor,
|
|
1616
3166
|
handcashAdapter,
|
|
3167
|
+
handleLabel,
|
|
3168
|
+
hasBwalletX,
|
|
3169
|
+
inAnyFrame,
|
|
1617
3170
|
inOrder,
|
|
3171
|
+
isExplicitPick,
|
|
3172
|
+
isFramed,
|
|
1618
3173
|
isIdentityKeyHex,
|
|
3174
|
+
isInsufficientFunds,
|
|
3175
|
+
isKnownIdentityKey,
|
|
3176
|
+
isLocalDesktop,
|
|
3177
|
+
isLocalDesktopType,
|
|
1619
3178
|
isPaymail,
|
|
3179
|
+
isPresentBwalletX,
|
|
3180
|
+
isRememberable,
|
|
1620
3181
|
isTouchDevice,
|
|
3182
|
+
isUserRejection,
|
|
3183
|
+
knownIdentityKeys,
|
|
3184
|
+
lookupPaymail,
|
|
1621
3185
|
markSvg,
|
|
3186
|
+
matchRemembered,
|
|
3187
|
+
metanetOffered,
|
|
3188
|
+
metanetOption,
|
|
3189
|
+
migrateConnectedKey,
|
|
3190
|
+
needsBwalletX,
|
|
3191
|
+
otherDetected,
|
|
3192
|
+
outcomeOf,
|
|
1622
3193
|
pairBwalletX,
|
|
1623
3194
|
pairingModal,
|
|
1624
3195
|
pairingOption,
|
|
1625
3196
|
pay,
|
|
1626
3197
|
pickWallet,
|
|
3198
|
+
planAutoConnect,
|
|
1627
3199
|
prewarmBwalletX,
|
|
1628
3200
|
protocolMediaUrl,
|
|
1629
3201
|
proveHoldings,
|
|
1630
3202
|
qrSvg,
|
|
3203
|
+
rememberIdentity,
|
|
3204
|
+
rememberKnownIdentityKey,
|
|
3205
|
+
rememberWalletChoice,
|
|
3206
|
+
rememberedIdentity,
|
|
3207
|
+
rememberedWalletChoice,
|
|
1631
3208
|
renderButton,
|
|
3209
|
+
reportAttempt,
|
|
1632
3210
|
requestAccess,
|
|
3211
|
+
resetSilentAttempt,
|
|
1633
3212
|
resolvePaymailP2P,
|
|
1634
3213
|
restorePairing,
|
|
3214
|
+
runConnect,
|
|
3215
|
+
runSilentMetanet,
|
|
3216
|
+
shortKey,
|
|
3217
|
+
signInAndVerify,
|
|
3218
|
+
signInErrorText,
|
|
1635
3219
|
signInWithBwalletX,
|
|
1636
3220
|
signinText,
|
|
3221
|
+
silentAttemptUsed,
|
|
3222
|
+
stateFor,
|
|
3223
|
+
substrateOf,
|
|
3224
|
+
tapGoesElsewhere,
|
|
1637
3225
|
throwIfAccessError,
|
|
1638
3226
|
tip,
|
|
1639
|
-
toAccessError
|
|
3227
|
+
toAccessError,
|
|
3228
|
+
trySilentMetanet,
|
|
3229
|
+
waitIfLocked,
|
|
3230
|
+
walletTypeOf,
|
|
3231
|
+
warnIfNoLogout
|
|
1640
3232
|
});
|
|
1641
3233
|
//# sourceMappingURL=index.cjs.map
|