@abstraxn/signer-react 2.1.4 → 2.2.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 +17 -0
- package/dist/src/WalletModal.css +0 -1
- package/dist/src/WalletModal.js +10 -4
- package/dist/src/WalletModal.js.map +1 -1
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderInner.js +557 -81
- package/dist/src/components/AbstraxnProvider/AbstraxnProviderInner.js.map +1 -1
- package/dist/src/components/AbstraxnProvider/useOAuthCallbacks.d.ts +2 -1
- package/dist/src/components/AbstraxnProvider/useOAuthCallbacks.js +72 -8
- package/dist/src/components/AbstraxnProvider/useOAuthCallbacks.js.map +1 -1
- package/dist/src/components/AbstraxnProvider/useWalletInitialization.js +16 -11
- package/dist/src/components/AbstraxnProvider/useWalletInitialization.js.map +1 -1
- package/dist/src/components/AbstraxnProvider/utils.d.ts +5 -0
- package/dist/src/components/AbstraxnProvider/utils.js +9 -0
- package/dist/src/components/AbstraxnProvider/utils.js.map +1 -1
- package/dist/src/components/OnboardingUI/OnboardingUI.css +186 -6
- package/dist/src/components/OnboardingUI/OnboardingUIReact.js +18 -4
- package/dist/src/components/OnboardingUI/OnboardingUIReact.js.map +1 -1
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.d.ts +21 -0
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js +528 -20
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js.map +1 -1
- package/dist/src/components/OnboardingUI/components/EmailForm.js +2 -1
- package/dist/src/components/OnboardingUI/components/EmailForm.js.map +1 -1
- package/dist/src/components/OnboardingUI/components/MfaForm.d.ts +17 -0
- package/dist/src/components/OnboardingUI/components/MfaForm.js +81 -0
- package/dist/src/components/OnboardingUI/components/MfaForm.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/index.d.ts +2 -0
- package/dist/src/components/OnboardingUI/components/index.js +1 -0
- package/dist/src/components/OnboardingUI/components/index.js.map +1 -1
- package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js +3 -0
- package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js.map +1 -1
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.d.ts +4 -1
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.js +65 -2
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.js.map +1 -1
- package/dist/src/components/WalletModal/components/ManageWalletModal.css +1443 -2
- package/dist/src/components/WalletModal/components/ManageWalletModal.js +737 -23
- package/dist/src/components/WalletModal/components/ManageWalletModal.js.map +1 -1
- package/dist/src/components/WalletModal/components/ReceiveModal.css +0 -1
- package/dist/src/components/WalletModal/hooks/useSendTransaction.js +67 -12
- package/dist/src/components/WalletModal/hooks/useSendTransaction.js.map +1 -1
- package/dist/src/types.d.ts +16 -0
- package/dist/src/wagmiConfig.js +1 -0
- package/dist/src/wagmiConfig.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
|
@@ -11,6 +11,7 @@ import { parseEther, createPublicClient, http } from "viem";
|
|
|
11
11
|
import { ExternalWalletButtons } from "../../ExternalWalletButtons";
|
|
12
12
|
import { EVM_CHAINS, SOLANA_CHAINS, getChainById, toCoreChain, } from "../../chains";
|
|
13
13
|
import { AbstraxnContext } from "./context";
|
|
14
|
+
import { isLinkAuthCallback } from "./utils";
|
|
14
15
|
/** Syncs wagmi disconnect to external wallet state; only rendered when wagmi is available (under WagmiProvider). */
|
|
15
16
|
function WagmiConnectionEffectSync({ onDisconnect, }) {
|
|
16
17
|
useConnectionEffect({ onDisconnect });
|
|
@@ -47,6 +48,72 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
47
48
|
useEffect(() => {
|
|
48
49
|
isExternalWalletConnectedRef.current = isExternalWalletConnected;
|
|
49
50
|
}, [isExternalWalletConnected]);
|
|
51
|
+
// Detect link-auth error in popup: notify opener and close (run first so popup closes on error)
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (typeof window === "undefined" || !window.opener)
|
|
54
|
+
return;
|
|
55
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
56
|
+
const errorParam = urlParams.get("error");
|
|
57
|
+
if (!errorParam)
|
|
58
|
+
return;
|
|
59
|
+
let provider = "google";
|
|
60
|
+
try {
|
|
61
|
+
const linkingMetadataRaw = urlParams.get("linkingMetadata");
|
|
62
|
+
if (linkingMetadataRaw) {
|
|
63
|
+
const linkingMetadata = JSON.parse(decodeURIComponent(linkingMetadataRaw));
|
|
64
|
+
const authProvider = linkingMetadata?.authProvider;
|
|
65
|
+
if (authProvider) {
|
|
66
|
+
provider = String(authProvider).toLowerCase();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// keep default provider
|
|
72
|
+
}
|
|
73
|
+
let errorMessage;
|
|
74
|
+
try {
|
|
75
|
+
errorMessage = decodeURIComponent(errorParam);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
errorMessage = errorParam;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
window.opener.postMessage({ type: "abstraxn-auth-link-error", provider, error: errorMessage }, window.location.origin);
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
window.close();
|
|
85
|
+
}
|
|
86
|
+
}, []);
|
|
87
|
+
// Detect link-auth callback in popup: notify opener and close (run early, no wallet dependency)
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (typeof window === "undefined" || !window.opener)
|
|
90
|
+
return;
|
|
91
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
92
|
+
const stampingRequired = urlParams.get("stampingRequired") === "true";
|
|
93
|
+
const activityBody = urlParams.get("activityBody");
|
|
94
|
+
if (!stampingRequired || !activityBody)
|
|
95
|
+
return;
|
|
96
|
+
let provider = "google";
|
|
97
|
+
try {
|
|
98
|
+
const linkingMetadataRaw = urlParams.get("linkingMetadata");
|
|
99
|
+
if (linkingMetadataRaw) {
|
|
100
|
+
const linkingMetadata = JSON.parse(decodeURIComponent(linkingMetadataRaw));
|
|
101
|
+
const authProvider = linkingMetadata?.authProvider;
|
|
102
|
+
if (authProvider) {
|
|
103
|
+
provider = String(authProvider).toLowerCase();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// keep default provider
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
window.opener.postMessage({ type: "abstraxn-auth-link-done", provider, activityBody }, window.location.origin);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
window.close();
|
|
115
|
+
}
|
|
116
|
+
}, []);
|
|
50
117
|
// Track Solana state changes
|
|
51
118
|
useEffect(() => {
|
|
52
119
|
if (solana?.wallet?.connected && solana?.wallet?.publicKey) {
|
|
@@ -156,20 +223,37 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
156
223
|
return;
|
|
157
224
|
requestAnimationFrame(() => {
|
|
158
225
|
if (externalWalletsEnabled) {
|
|
159
|
-
//
|
|
160
|
-
|
|
226
|
+
// Don't show external wallet on OTP or MFA (auth code) screen
|
|
227
|
+
const isOtpScreen = onboardingAny.otpVerificationScreen &&
|
|
228
|
+
onboardingAny.otpVerificationScreen.parentElement &&
|
|
229
|
+
onboardingAny.otpVerificationScreen.offsetParent !== null;
|
|
230
|
+
const isMfaScreen = onboardingAny.mfaVerificationScreen &&
|
|
231
|
+
onboardingAny.mfaVerificationScreen.parentElement &&
|
|
232
|
+
onboardingAny.mfaVerificationScreen.offsetParent !== null;
|
|
233
|
+
if (!isOtpScreen && !isMfaScreen && onboardingAny.externalWalletContainer) {
|
|
161
234
|
onboardingAny.externalWalletContainer.style.display = "";
|
|
162
235
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
236
|
+
else if (onboardingAny.externalWalletContainer) {
|
|
237
|
+
onboardingAny.externalWalletContainer.style.display = "none";
|
|
238
|
+
}
|
|
239
|
+
// Show divider only if email/Google are also visible (and not on OTP/MFA)
|
|
240
|
+
if (!isOtpScreen && !isMfaScreen) {
|
|
241
|
+
const authMethods = onboardingAny.config?.authMethods || [
|
|
242
|
+
"otp",
|
|
243
|
+
"google",
|
|
244
|
+
];
|
|
245
|
+
const showEmail = authMethods.includes("otp");
|
|
246
|
+
const showGoogle = authMethods.includes("google");
|
|
247
|
+
const hasEmailOrGoogle = showEmail || showGoogle;
|
|
248
|
+
if (onboardingAny.externalWalletDivider && hasEmailOrGoogle) {
|
|
249
|
+
onboardingAny.externalWalletDivider.style.display = "";
|
|
250
|
+
}
|
|
251
|
+
else if (onboardingAny.externalWalletDivider) {
|
|
252
|
+
onboardingAny.externalWalletDivider.style.display = "none";
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else if (onboardingAny.externalWalletDivider) {
|
|
256
|
+
onboardingAny.externalWalletDivider.style.display = "none";
|
|
173
257
|
}
|
|
174
258
|
}
|
|
175
259
|
else {
|
|
@@ -513,6 +597,12 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
513
597
|
const authManager = walletInstance.getAuthManager();
|
|
514
598
|
const user = await authManager.verifyOTP(otpIdRef.current, otp);
|
|
515
599
|
otpIdRef.current = null;
|
|
600
|
+
const lastAuthState = authManager.getLastAuthState();
|
|
601
|
+
if (lastAuthState?.mfaRequired === true) {
|
|
602
|
+
// Show MFA verification screen; do not connect until MFA is verified
|
|
603
|
+
setError(null);
|
|
604
|
+
return { success: true, user, mfaRequired: true };
|
|
605
|
+
}
|
|
516
606
|
// Connect wallet after successful authentication
|
|
517
607
|
await walletInstance.connect();
|
|
518
608
|
// Clear any previous errors on successful verification
|
|
@@ -526,6 +616,51 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
526
616
|
return { success: false, error: errorMessage };
|
|
527
617
|
}
|
|
528
618
|
},
|
|
619
|
+
onMfaVerify: async (code) => {
|
|
620
|
+
try {
|
|
621
|
+
if (!walletInstance)
|
|
622
|
+
throw new Error("Wallet not initialized");
|
|
623
|
+
await walletInstance.verifyMfa(code);
|
|
624
|
+
await walletInstance.connect();
|
|
625
|
+
setError(null);
|
|
626
|
+
// Explicitly refresh provider state so OnboardingUI / app re-renders as connected without relying on connect event
|
|
627
|
+
try {
|
|
628
|
+
const whoamiInfo = await walletInstance.getWhoami();
|
|
629
|
+
if (whoamiInfo) {
|
|
630
|
+
setIsConnected(true);
|
|
631
|
+
const userInfo = await walletInstance.getUserInfo();
|
|
632
|
+
setUser(userInfo);
|
|
633
|
+
setWhoami(whoamiInfo);
|
|
634
|
+
try {
|
|
635
|
+
const addr = await walletInstance.getAddress();
|
|
636
|
+
setAddress(addr);
|
|
637
|
+
}
|
|
638
|
+
catch (_e) { /* optional */ }
|
|
639
|
+
try {
|
|
640
|
+
const cid = await walletInstance.getChainId();
|
|
641
|
+
setChainId(cid);
|
|
642
|
+
}
|
|
643
|
+
catch (_e) { /* optional */ }
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
catch (refreshErr) {
|
|
647
|
+
console.warn("Post-MFA state refresh:", refreshErr);
|
|
648
|
+
}
|
|
649
|
+
// Clear OAuth callback params from URL so app shows connected state without refresh
|
|
650
|
+
if (typeof window !== "undefined") {
|
|
651
|
+
const url = new URL(window.location.href);
|
|
652
|
+
const keys = ["success", "mfaRequired", "user", "accessToken", "refreshToken", "turnkeyPublicKey", "error", "provider", "authProvider"];
|
|
653
|
+
keys.forEach((k) => url.searchParams.delete(k));
|
|
654
|
+
window.history.replaceState({}, document.title, url.pathname + url.search);
|
|
655
|
+
}
|
|
656
|
+
return { success: true };
|
|
657
|
+
}
|
|
658
|
+
catch (err) {
|
|
659
|
+
console.error("MFA Verify Error:", err);
|
|
660
|
+
const errorMessage = err instanceof Error ? err.message : "MFA verification failed";
|
|
661
|
+
return { success: false, error: errorMessage };
|
|
662
|
+
}
|
|
663
|
+
},
|
|
529
664
|
onGoogleLogin: async () => {
|
|
530
665
|
if (!walletInstance)
|
|
531
666
|
throw new Error("Wallet not initialized");
|
|
@@ -535,6 +670,7 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
535
670
|
try {
|
|
536
671
|
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
537
672
|
localStorage.setItem("abstraxn_oauth_pending", "google");
|
|
673
|
+
localStorage.setItem("abstraxn_oauth_ui", "modal");
|
|
538
674
|
}
|
|
539
675
|
catch (e) {
|
|
540
676
|
// Ignore localStorage errors
|
|
@@ -563,6 +699,7 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
563
699
|
try {
|
|
564
700
|
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
565
701
|
localStorage.setItem("abstraxn_oauth_pending", "twitter");
|
|
702
|
+
localStorage.setItem("abstraxn_oauth_ui", "modal");
|
|
566
703
|
}
|
|
567
704
|
catch (e) {
|
|
568
705
|
// Ignore localStorage errors
|
|
@@ -591,6 +728,7 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
591
728
|
try {
|
|
592
729
|
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
593
730
|
localStorage.setItem("abstraxn_oauth_pending", "discord");
|
|
731
|
+
localStorage.setItem("abstraxn_oauth_ui", "modal");
|
|
594
732
|
}
|
|
595
733
|
catch (e) {
|
|
596
734
|
// Ignore localStorage errors
|
|
@@ -651,6 +789,16 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
651
789
|
onLoginSuccess: async (_data) => {
|
|
652
790
|
// Clear any previous errors on successful login
|
|
653
791
|
setError(null);
|
|
792
|
+
// Clear URL params (e.g. after OAuth MFA verification so success/user/accessToken are removed)
|
|
793
|
+
if (typeof window !== "undefined") {
|
|
794
|
+
window.history.replaceState({}, document.title, window.location.pathname);
|
|
795
|
+
}
|
|
796
|
+
// Set user from wallet (covers OAuth MFA path where we did not call setUser in the callback)
|
|
797
|
+
if (walletInstance) {
|
|
798
|
+
const currentUser = walletInstance.getAuthManager().getCurrentUser();
|
|
799
|
+
if (currentUser)
|
|
800
|
+
setUser(currentUser);
|
|
801
|
+
}
|
|
654
802
|
// Restore connection type from localStorage for OAuth logins (Google, X, Discord)
|
|
655
803
|
// This ensures connectionType is set after OAuth redirects
|
|
656
804
|
try {
|
|
@@ -804,16 +952,18 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
804
952
|
onboardingAny.modalOverlay.style.display = "none";
|
|
805
953
|
document.body.style.overflow = "";
|
|
806
954
|
}
|
|
807
|
-
else if (hasSuccess) {
|
|
808
|
-
// If success=true is in URL, show loading modal
|
|
809
|
-
//
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
onboardingInstance.showLoadingModal
|
|
815
|
-
|
|
816
|
-
|
|
955
|
+
else if (hasSuccess && !isLinkAuthCallback(urlParams)) {
|
|
956
|
+
// If success=true is in URL (login flow only), show loading modal. Skip for link-auth callback (popup handles it).
|
|
957
|
+
// Skip when mfaRequired=true so MFA verification screen can show instead.
|
|
958
|
+
const mfaRequired = urlParams.get("mfaRequired") === "true";
|
|
959
|
+
if (!mfaRequired) {
|
|
960
|
+
setTimeout(() => {
|
|
961
|
+
const onboardingInstance = onboardingRef.current;
|
|
962
|
+
if (onboardingInstance && onboardingInstance.showLoadingModal) {
|
|
963
|
+
onboardingInstance.showLoadingModal();
|
|
964
|
+
}
|
|
965
|
+
}, 150);
|
|
966
|
+
}
|
|
817
967
|
}
|
|
818
968
|
onboardingRef.current = onboarding;
|
|
819
969
|
// Handle Google OAuth callback (only in useEffect, not exposed)
|
|
@@ -821,6 +971,8 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
821
971
|
if (googleCallbackHandledRef.current)
|
|
822
972
|
return;
|
|
823
973
|
const urlParams = new URLSearchParams(window.location.search);
|
|
974
|
+
if (isLinkAuthCallback(urlParams))
|
|
975
|
+
return;
|
|
824
976
|
if (!hasAuthParams(urlParams) ||
|
|
825
977
|
matchesProvider("twitter", urlParams) ||
|
|
826
978
|
matchesProvider("discord", urlParams)) {
|
|
@@ -839,9 +991,9 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
839
991
|
localStorage.removeItem("abstraxn_oauth_pending");
|
|
840
992
|
}
|
|
841
993
|
catch (e) { }
|
|
842
|
-
// Show loading modal if success=true is in URL
|
|
843
994
|
const hasSuccess = urlParams.get("success") === "true";
|
|
844
|
-
|
|
995
|
+
const mfaRequired = urlParams.get("mfaRequired") === "true";
|
|
996
|
+
if (hasSuccess && !mfaRequired && onboardingRef.current) {
|
|
845
997
|
const onboardingAny = onboardingRef.current;
|
|
846
998
|
if (onboardingAny.showLoadingScreen) {
|
|
847
999
|
onboardingAny.showLoadingScreen();
|
|
@@ -854,6 +1006,24 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
854
1006
|
try {
|
|
855
1007
|
const user = await walletInstance.handleGoogleCallback();
|
|
856
1008
|
if (user) {
|
|
1009
|
+
const lastAuthState = walletInstance.getAuthManager().getLastAuthState();
|
|
1010
|
+
if (lastAuthState?.mfaRequired === true) {
|
|
1011
|
+
try {
|
|
1012
|
+
if (localStorage.getItem("abstraxn_oauth_ui") !== "inline") {
|
|
1013
|
+
showOnboarding();
|
|
1014
|
+
if (onboardingRef.current && typeof onboardingRef.current.showMfaVerificationScreenForOAuth === "function") {
|
|
1015
|
+
onboardingRef.current.showMfaVerificationScreenForOAuth();
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
finally {
|
|
1020
|
+
try {
|
|
1021
|
+
localStorage.removeItem("abstraxn_oauth_ui");
|
|
1022
|
+
}
|
|
1023
|
+
catch (e) { }
|
|
1024
|
+
}
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
857
1027
|
setUser(user);
|
|
858
1028
|
window.history.replaceState({}, document.title, window.location.pathname);
|
|
859
1029
|
// Hide loading modal and close modal on success
|
|
@@ -895,6 +1065,8 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
895
1065
|
if (discordCallbackHandledRef.current)
|
|
896
1066
|
return;
|
|
897
1067
|
const urlParams = new URLSearchParams(window.location.search);
|
|
1068
|
+
if (isLinkAuthCallback(urlParams))
|
|
1069
|
+
return;
|
|
898
1070
|
if (!hasAuthParams(urlParams) || !matchesProvider("discord", urlParams)) {
|
|
899
1071
|
return;
|
|
900
1072
|
}
|
|
@@ -911,9 +1083,10 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
911
1083
|
localStorage.removeItem("abstraxn_oauth_pending");
|
|
912
1084
|
}
|
|
913
1085
|
catch (e) { }
|
|
914
|
-
// Show loading modal if success=true is in URL
|
|
1086
|
+
// Show loading modal if success=true is in URL (skip when mfaRequired so MFA screen can show)
|
|
915
1087
|
const hasSuccess = urlParams.get("success") === "true";
|
|
916
|
-
|
|
1088
|
+
const mfaRequired = urlParams.get("mfaRequired") === "true";
|
|
1089
|
+
if (hasSuccess && !mfaRequired && onboardingRef.current) {
|
|
917
1090
|
const onboardingAny = onboardingRef.current;
|
|
918
1091
|
if (onboardingAny.showLoadingScreen) {
|
|
919
1092
|
onboardingAny.showLoadingScreen();
|
|
@@ -926,6 +1099,24 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
926
1099
|
try {
|
|
927
1100
|
const user = await walletInstance.handleDiscordCallback();
|
|
928
1101
|
if (user) {
|
|
1102
|
+
const lastAuthState = walletInstance.getAuthManager().getLastAuthState();
|
|
1103
|
+
if (lastAuthState?.mfaRequired === true) {
|
|
1104
|
+
try {
|
|
1105
|
+
if (localStorage.getItem("abstraxn_oauth_ui") !== "inline") {
|
|
1106
|
+
showOnboarding();
|
|
1107
|
+
if (onboardingRef.current && typeof onboardingRef.current.showMfaVerificationScreenForOAuth === "function") {
|
|
1108
|
+
onboardingRef.current.showMfaVerificationScreenForOAuth();
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
finally {
|
|
1113
|
+
try {
|
|
1114
|
+
localStorage.removeItem("abstraxn_oauth_ui");
|
|
1115
|
+
}
|
|
1116
|
+
catch (e) { }
|
|
1117
|
+
}
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
929
1120
|
setUser(user);
|
|
930
1121
|
window.history.replaceState({}, document.title, window.location.pathname);
|
|
931
1122
|
// Hide loading modal and close modal on success
|
|
@@ -969,6 +1160,8 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
969
1160
|
if (twitterCallbackHandledRef.current)
|
|
970
1161
|
return;
|
|
971
1162
|
const urlParams = new URLSearchParams(window.location.search);
|
|
1163
|
+
if (isLinkAuthCallback(urlParams))
|
|
1164
|
+
return;
|
|
972
1165
|
if (!hasAuthParams(urlParams) || !matchesProvider("twitter", urlParams)) {
|
|
973
1166
|
return;
|
|
974
1167
|
}
|
|
@@ -985,9 +1178,10 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
985
1178
|
localStorage.removeItem("abstraxn_oauth_pending");
|
|
986
1179
|
}
|
|
987
1180
|
catch (e) { }
|
|
988
|
-
// Show loading modal if success=true is in URL
|
|
1181
|
+
// Show loading modal if success=true is in URL (skip when mfaRequired so MFA screen can show)
|
|
989
1182
|
const hasSuccess = urlParams.get("success") === "true";
|
|
990
|
-
|
|
1183
|
+
const mfaRequired = urlParams.get("mfaRequired") === "true";
|
|
1184
|
+
if (hasSuccess && !mfaRequired && onboardingRef.current) {
|
|
991
1185
|
const onboardingAny = onboardingRef.current;
|
|
992
1186
|
if (onboardingAny.showLoadingScreen) {
|
|
993
1187
|
onboardingAny.showLoadingScreen();
|
|
@@ -1000,6 +1194,24 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
1000
1194
|
try {
|
|
1001
1195
|
const user = await walletInstance.handleTwitterCallback();
|
|
1002
1196
|
if (user) {
|
|
1197
|
+
const lastAuthState = walletInstance.getAuthManager().getLastAuthState();
|
|
1198
|
+
if (lastAuthState?.mfaRequired === true) {
|
|
1199
|
+
try {
|
|
1200
|
+
if (localStorage.getItem("abstraxn_oauth_ui") !== "inline") {
|
|
1201
|
+
showOnboarding();
|
|
1202
|
+
if (onboardingRef.current && typeof onboardingRef.current.showMfaVerificationScreenForOAuth === "function") {
|
|
1203
|
+
onboardingRef.current.showMfaVerificationScreenForOAuth();
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
finally {
|
|
1208
|
+
try {
|
|
1209
|
+
localStorage.removeItem("abstraxn_oauth_ui");
|
|
1210
|
+
}
|
|
1211
|
+
catch (e) { }
|
|
1212
|
+
}
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1003
1215
|
setUser(user);
|
|
1004
1216
|
window.history.replaceState({}, document.title, window.location.pathname);
|
|
1005
1217
|
// Hide loading modal and close modal on success
|
|
@@ -1133,13 +1345,15 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
1133
1345
|
// Ensure external wallet container is visible when modal opens
|
|
1134
1346
|
if (externalWalletsEnabledRef.current &&
|
|
1135
1347
|
onboarding.externalWalletContainer) {
|
|
1136
|
-
// Check if we're on OTP screen - if so, don't show external wallets
|
|
1137
|
-
// Check if OTP screen exists AND is actually visible in the DOM
|
|
1348
|
+
// Check if we're on OTP or MFA screen - if so, don't show external wallets
|
|
1138
1349
|
const isOtpScreen = onboarding.otpVerificationScreen &&
|
|
1139
1350
|
onboarding.otpVerificationScreen.parentElement &&
|
|
1140
1351
|
onboarding.otpVerificationScreen.offsetParent !== null;
|
|
1141
|
-
|
|
1142
|
-
|
|
1352
|
+
const isMfaScreen = onboarding.mfaVerificationScreen &&
|
|
1353
|
+
onboarding.mfaVerificationScreen.parentElement &&
|
|
1354
|
+
onboarding.mfaVerificationScreen.offsetParent !== null;
|
|
1355
|
+
if (!isOtpScreen && !isMfaScreen) {
|
|
1356
|
+
// Only show external wallets if not on OTP or MFA (auth code) screen
|
|
1143
1357
|
onboarding.externalWalletContainer.style.display = "";
|
|
1144
1358
|
// Show divider only if both email/Google AND external wallets are visible
|
|
1145
1359
|
const authMethods = onboarding.config?.authMethods || [
|
|
@@ -1157,7 +1371,7 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
1157
1371
|
}
|
|
1158
1372
|
}
|
|
1159
1373
|
else {
|
|
1160
|
-
// On OTP screen - hide external wallets
|
|
1374
|
+
// On OTP or MFA screen - hide external wallets
|
|
1161
1375
|
onboarding.externalWalletContainer.style.display = "none";
|
|
1162
1376
|
if (onboarding.externalWalletDivider) {
|
|
1163
1377
|
onboarding.externalWalletDivider.style.display = "none";
|
|
@@ -1181,10 +1395,13 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
1181
1395
|
// Modal already exists, just show it
|
|
1182
1396
|
// Use requestAnimationFrame to prevent blinking
|
|
1183
1397
|
requestAnimationFrame(() => {
|
|
1184
|
-
// Check if we're on OTP screen - if so, ensure social buttons stay hidden
|
|
1398
|
+
// Check if we're on OTP or MFA screen - if so, ensure social buttons stay hidden
|
|
1185
1399
|
const isOtpScreen = onboarding.otpVerificationScreen &&
|
|
1186
1400
|
onboarding.otpVerificationScreen.parentElement &&
|
|
1187
1401
|
onboarding.otpVerificationScreen.offsetParent !== null;
|
|
1402
|
+
const isMfaScreen = onboarding.mfaVerificationScreen &&
|
|
1403
|
+
onboarding.mfaVerificationScreen.parentElement &&
|
|
1404
|
+
onboarding.mfaVerificationScreen.offsetParent !== null;
|
|
1188
1405
|
onboarding.modalOverlay.classList.remove("onboarding-modal-closing");
|
|
1189
1406
|
onboarding.modalOverlay.classList.add("onboarding-modal-open");
|
|
1190
1407
|
onboarding.modalOverlay.style.display = "flex";
|
|
@@ -1195,8 +1412,8 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
1195
1412
|
if (emailForOtp && typeof onboarding.setPreFillEmail === "function") {
|
|
1196
1413
|
onboarding.setPreFillEmail(emailForOtp, { readOnly: true });
|
|
1197
1414
|
}
|
|
1198
|
-
// If on OTP screen, ensure all login elements (social buttons, etc.) are hidden
|
|
1199
|
-
if (isOtpScreen) {
|
|
1415
|
+
// If on OTP or MFA screen, ensure all login elements (social buttons, etc.) are hidden
|
|
1416
|
+
if (isOtpScreen || isMfaScreen) {
|
|
1200
1417
|
// On OTP screen - ensure all login elements are hidden
|
|
1201
1418
|
if (onboarding.hideLoginElements &&
|
|
1202
1419
|
typeof onboarding.hideLoginElements === "function") {
|
|
@@ -1229,9 +1446,9 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
1229
1446
|
// CRITICAL: Always ensure external wallets are mounted when modal reopens
|
|
1230
1447
|
if (externalWalletsEnabledRef.current &&
|
|
1231
1448
|
onboarding.externalWalletContainer) {
|
|
1232
|
-
// Check if we're on OTP screen - if so, don't show external wallets
|
|
1233
|
-
if (!isOtpScreen) {
|
|
1234
|
-
// Only show external wallets if not on OTP screen
|
|
1449
|
+
// Check if we're on OTP or MFA screen - if so, don't show external wallets
|
|
1450
|
+
if (!isOtpScreen && !isMfaScreen) {
|
|
1451
|
+
// Only show external wallets if not on OTP or MFA (auth code) screen
|
|
1235
1452
|
onboarding.externalWalletContainer.style.display = "";
|
|
1236
1453
|
// Show divider only if both email/Google AND external wallets are visible
|
|
1237
1454
|
const authMethods = onboarding.config?.authMethods || [
|
|
@@ -1249,7 +1466,7 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
1249
1466
|
}
|
|
1250
1467
|
}
|
|
1251
1468
|
else {
|
|
1252
|
-
// On OTP screen - hide external wallets
|
|
1469
|
+
// On OTP or MFA screen - hide external wallets
|
|
1253
1470
|
onboarding.externalWalletContainer.style.display = "none";
|
|
1254
1471
|
if (onboarding.externalWalletDivider) {
|
|
1255
1472
|
onboarding.externalWalletDivider.style.display = "none";
|
|
@@ -2130,6 +2347,138 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
2130
2347
|
setLoading(false);
|
|
2131
2348
|
}
|
|
2132
2349
|
}, [isExternalWalletConnected]);
|
|
2350
|
+
const getSupportedAuthMethods = useCallback(async () => {
|
|
2351
|
+
if (!walletRef.current?.isConnected) {
|
|
2352
|
+
throw new Error("Wallet not connected");
|
|
2353
|
+
}
|
|
2354
|
+
return await walletRef.current.getSupportedAuthMethods();
|
|
2355
|
+
}, []);
|
|
2356
|
+
const getLinkedAuthMethods = useCallback(async () => {
|
|
2357
|
+
if (!walletRef.current?.isConnected) {
|
|
2358
|
+
throw new Error("Wallet not connected");
|
|
2359
|
+
}
|
|
2360
|
+
return await walletRef.current.getLinkedAuthMethods();
|
|
2361
|
+
}, []);
|
|
2362
|
+
const linkAuthMethod = useCallback(async (provider) => {
|
|
2363
|
+
if (!walletRef.current?.isConnected) {
|
|
2364
|
+
throw new Error("Wallet not connected");
|
|
2365
|
+
}
|
|
2366
|
+
if (provider.toLowerCase() === "passkey") {
|
|
2367
|
+
await walletRef.current.linkPasskey();
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
if (provider.toLowerCase() === "email") {
|
|
2371
|
+
throw new Error("Email linking uses the in-page form. Use the Auth Methods screen and click Connect for Email, then enter your email and OTP.");
|
|
2372
|
+
}
|
|
2373
|
+
const { authUrl } = await walletRef.current.initiateLinkAuth(provider);
|
|
2374
|
+
const width = 500;
|
|
2375
|
+
const height = 600;
|
|
2376
|
+
const left = Math.round((window.screen.width - width) / 2);
|
|
2377
|
+
const top = Math.round((window.screen.height - height) / 2);
|
|
2378
|
+
const popup = window.open(authUrl, "abstraxn-auth-link", `width=${width},height=${height},left=${left},top=${top},scrollbars=yes,resizable=yes`);
|
|
2379
|
+
if (!popup) {
|
|
2380
|
+
throw new Error("Popup blocked. Please allow popups for this site.");
|
|
2381
|
+
}
|
|
2382
|
+
return new Promise((resolve, reject) => {
|
|
2383
|
+
let settled = false;
|
|
2384
|
+
const maybeSubmit = (data) => {
|
|
2385
|
+
if (settled)
|
|
2386
|
+
return;
|
|
2387
|
+
settled = true;
|
|
2388
|
+
cleanup();
|
|
2389
|
+
if (data?.activityBody) {
|
|
2390
|
+
walletRef.current
|
|
2391
|
+
?.submitLinkAuthStamped(provider, data.activityBody)
|
|
2392
|
+
.then(resolve)
|
|
2393
|
+
.catch((err) => {
|
|
2394
|
+
settled = false;
|
|
2395
|
+
reject(err);
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
2398
|
+
else {
|
|
2399
|
+
reject(new Error("Linking was cancelled"));
|
|
2400
|
+
}
|
|
2401
|
+
};
|
|
2402
|
+
const handleMessage = (event) => {
|
|
2403
|
+
if (event.data?.type === "abstraxn-auth-link-done" &&
|
|
2404
|
+
event.data?.provider === provider) {
|
|
2405
|
+
maybeSubmit(event.data);
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
if (event.data?.type === "abstraxn-auth-link-error") {
|
|
2409
|
+
if (settled)
|
|
2410
|
+
return;
|
|
2411
|
+
settled = true;
|
|
2412
|
+
cleanup();
|
|
2413
|
+
reject(new Error(event.data.error));
|
|
2414
|
+
}
|
|
2415
|
+
};
|
|
2416
|
+
let intervalId = null;
|
|
2417
|
+
const cleanup = () => {
|
|
2418
|
+
window.removeEventListener("message", handleMessage);
|
|
2419
|
+
if (intervalId)
|
|
2420
|
+
clearInterval(intervalId);
|
|
2421
|
+
};
|
|
2422
|
+
window.addEventListener("message", handleMessage);
|
|
2423
|
+
intervalId = setInterval(() => {
|
|
2424
|
+
if (popup.closed) {
|
|
2425
|
+
cleanup();
|
|
2426
|
+
if (!settled) {
|
|
2427
|
+
reject(new Error("Linking was cancelled"));
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
}, 200);
|
|
2431
|
+
});
|
|
2432
|
+
}, []);
|
|
2433
|
+
const unlinkAuthMethod = useCallback(async (provider) => {
|
|
2434
|
+
if (!walletRef.current?.isConnected) {
|
|
2435
|
+
throw new Error("Wallet not connected");
|
|
2436
|
+
}
|
|
2437
|
+
await walletRef.current.unlinkAuthMethod(provider);
|
|
2438
|
+
}, []);
|
|
2439
|
+
const requestOtpForEmailLink = useCallback(async (email) => {
|
|
2440
|
+
if (!walletRef.current?.isConnected) {
|
|
2441
|
+
throw new Error("Wallet not connected");
|
|
2442
|
+
}
|
|
2443
|
+
return await walletRef.current.requestOtpForEmailLink(email);
|
|
2444
|
+
}, []);
|
|
2445
|
+
const linkEmail = useCallback(async (otpId, otpCode) => {
|
|
2446
|
+
if (!walletRef.current?.isConnected) {
|
|
2447
|
+
throw new Error("Wallet not connected");
|
|
2448
|
+
}
|
|
2449
|
+
await walletRef.current.linkEmail(otpId, otpCode);
|
|
2450
|
+
}, []);
|
|
2451
|
+
// MFA helpers (delegating to core wallet/AuthManager)
|
|
2452
|
+
const getMfaStatus = useCallback(async () => {
|
|
2453
|
+
if (!walletRef.current?.isConnected) {
|
|
2454
|
+
throw new Error("Wallet not connected");
|
|
2455
|
+
}
|
|
2456
|
+
return await walletRef.current.getMfaStatus();
|
|
2457
|
+
}, []);
|
|
2458
|
+
const enableMfa = useCallback(async () => {
|
|
2459
|
+
if (!walletRef.current?.isConnected) {
|
|
2460
|
+
throw new Error("Wallet not connected");
|
|
2461
|
+
}
|
|
2462
|
+
return await walletRef.current.enableMfa();
|
|
2463
|
+
}, []);
|
|
2464
|
+
const verifySetupMfa = useCallback(async (code) => {
|
|
2465
|
+
if (!walletRef.current?.isConnected) {
|
|
2466
|
+
throw new Error("Wallet not connected");
|
|
2467
|
+
}
|
|
2468
|
+
return await walletRef.current.verifySetupMfa(code);
|
|
2469
|
+
}, []);
|
|
2470
|
+
const verifyMfa = useCallback(async (code) => {
|
|
2471
|
+
if (!walletRef.current?.isConnected) {
|
|
2472
|
+
throw new Error("Wallet not connected");
|
|
2473
|
+
}
|
|
2474
|
+
return await walletRef.current.verifyMfa(code);
|
|
2475
|
+
}, []);
|
|
2476
|
+
const disableMfaWithSignedPayload = useCallback(async () => {
|
|
2477
|
+
if (!walletRef.current?.isConnected) {
|
|
2478
|
+
throw new Error("Wallet not connected");
|
|
2479
|
+
}
|
|
2480
|
+
return await walletRef.current.disableMfaWithSignedPayload();
|
|
2481
|
+
}, []);
|
|
2133
2482
|
// Sync external wallet state from wagmi
|
|
2134
2483
|
useEffect(() => {
|
|
2135
2484
|
let checkAddressTimeout = null;
|
|
@@ -2864,44 +3213,52 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
2864
3213
|
const currentChainId = isExternalWalletConnected && externalWalletChainId
|
|
2865
3214
|
? externalWalletChainId
|
|
2866
3215
|
: chainId;
|
|
2867
|
-
// Fetch balance for Abstraxn wallet (not external wallet)
|
|
2868
|
-
|
|
3216
|
+
// Fetch balance for Abstraxn wallet (not external wallet) - reusable for on-demand refetch
|
|
3217
|
+
const refetchWalletBalance = useCallback(async () => {
|
|
2869
3218
|
if (isExternalWalletConnected || !address || !currentChainId) {
|
|
2870
3219
|
setWalletBalance(null);
|
|
2871
3220
|
return;
|
|
2872
3221
|
}
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
if (!currentChain || currentChain.type !== "evm") {
|
|
2877
|
-
setWalletBalance(null);
|
|
2878
|
-
return;
|
|
2879
|
-
}
|
|
2880
|
-
const publicClient = createPublicClient({
|
|
2881
|
-
chain: {
|
|
2882
|
-
id: currentChain.id,
|
|
2883
|
-
name: currentChain.name,
|
|
2884
|
-
nativeCurrency: currentChain.nativeCurrency,
|
|
2885
|
-
rpcUrls: {
|
|
2886
|
-
default: {
|
|
2887
|
-
http: [currentChain.rpcUrl],
|
|
2888
|
-
},
|
|
2889
|
-
},
|
|
2890
|
-
},
|
|
2891
|
-
transport: http(currentChain.rpcUrl),
|
|
2892
|
-
});
|
|
2893
|
-
const balance = await publicClient.getBalance({
|
|
2894
|
-
address: address,
|
|
2895
|
-
});
|
|
2896
|
-
setWalletBalance(balance);
|
|
2897
|
-
}
|
|
2898
|
-
catch (error) {
|
|
2899
|
-
console.error("Failed to fetch balance:", error);
|
|
3222
|
+
try {
|
|
3223
|
+
const currentChain = getChainById(currentChainId);
|
|
3224
|
+
if (!currentChain || currentChain.type !== "evm") {
|
|
2900
3225
|
setWalletBalance(null);
|
|
3226
|
+
return;
|
|
2901
3227
|
}
|
|
2902
|
-
|
|
2903
|
-
|
|
3228
|
+
const publicClient = createPublicClient({
|
|
3229
|
+
chain: {
|
|
3230
|
+
id: currentChain.id,
|
|
3231
|
+
name: currentChain.name,
|
|
3232
|
+
nativeCurrency: currentChain.nativeCurrency,
|
|
3233
|
+
rpcUrls: {
|
|
3234
|
+
default: {
|
|
3235
|
+
http: [currentChain.rpcUrl],
|
|
3236
|
+
},
|
|
3237
|
+
},
|
|
3238
|
+
},
|
|
3239
|
+
transport: http(currentChain.rpcUrl),
|
|
3240
|
+
});
|
|
3241
|
+
const balance = await publicClient.getBalance({
|
|
3242
|
+
address: address,
|
|
3243
|
+
});
|
|
3244
|
+
setWalletBalance(balance);
|
|
3245
|
+
}
|
|
3246
|
+
catch (error) {
|
|
3247
|
+
console.error("Failed to fetch balance:", error);
|
|
3248
|
+
setWalletBalance(null);
|
|
3249
|
+
}
|
|
2904
3250
|
}, [address, currentChainId, isExternalWalletConnected]);
|
|
3251
|
+
useEffect(() => {
|
|
3252
|
+
refetchWalletBalance();
|
|
3253
|
+
}, [refetchWalletBalance]);
|
|
3254
|
+
// Single refetch for UI: Abstraxn wallet = RPC call, external wallet = wagmi refetch
|
|
3255
|
+
const refetchBalance = useCallback(() => {
|
|
3256
|
+
if (isExternalWalletConnected && wagmiBalance?.refetch) {
|
|
3257
|
+
void wagmiBalance.refetch();
|
|
3258
|
+
return;
|
|
3259
|
+
}
|
|
3260
|
+
void refetchWalletBalance();
|
|
3261
|
+
}, [isExternalWalletConnected, wagmiBalance, refetchWalletBalance]);
|
|
2905
3262
|
// Compute available chains from config - supports both legacy and new format
|
|
2906
3263
|
const availableChains = useMemo(() => {
|
|
2907
3264
|
const chains = [];
|
|
@@ -3042,6 +3399,109 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
3042
3399
|
const setEmailForOtp = useCallback((email) => {
|
|
3043
3400
|
setEmailForOtpState(email);
|
|
3044
3401
|
}, []);
|
|
3402
|
+
// Enriched uiConfig with auth callbacks so <OnboardingUI /> (OnboardingUIReact) gets full flow including MFA
|
|
3403
|
+
const enrichedUiConfig = useMemo(() => ({
|
|
3404
|
+
...config.ui,
|
|
3405
|
+
onEmailOtpInitiate: async (email) => {
|
|
3406
|
+
try {
|
|
3407
|
+
if (!walletRef.current)
|
|
3408
|
+
throw new Error("Wallet not initialized");
|
|
3409
|
+
const authManager = walletRef.current.getAuthManager();
|
|
3410
|
+
const connectionTypeValue = "email";
|
|
3411
|
+
setConnectionType(connectionTypeValue);
|
|
3412
|
+
try {
|
|
3413
|
+
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
3414
|
+
}
|
|
3415
|
+
catch (e) {
|
|
3416
|
+
// Ignore
|
|
3417
|
+
}
|
|
3418
|
+
const result = await authManager.loginWithOTP(email);
|
|
3419
|
+
otpIdRef.current = result.otpId;
|
|
3420
|
+
}
|
|
3421
|
+
catch (err) {
|
|
3422
|
+
console.error("OTP Init Error:", err);
|
|
3423
|
+
throw err;
|
|
3424
|
+
}
|
|
3425
|
+
},
|
|
3426
|
+
onEmailOtpVerify: async (_email, otp) => {
|
|
3427
|
+
try {
|
|
3428
|
+
if (!walletRef.current)
|
|
3429
|
+
throw new Error("Wallet not initialized");
|
|
3430
|
+
if (!otpIdRef.current)
|
|
3431
|
+
throw new Error("OTP ID not found");
|
|
3432
|
+
const connectionTypeValue = "email";
|
|
3433
|
+
setConnectionType(connectionTypeValue);
|
|
3434
|
+
try {
|
|
3435
|
+
localStorage.setItem("abstraxn_connection_type", connectionTypeValue);
|
|
3436
|
+
}
|
|
3437
|
+
catch (e) {
|
|
3438
|
+
// Ignore
|
|
3439
|
+
}
|
|
3440
|
+
const authManager = walletRef.current.getAuthManager();
|
|
3441
|
+
const user = await authManager.verifyOTP(otpIdRef.current, otp);
|
|
3442
|
+
otpIdRef.current = null;
|
|
3443
|
+
const lastAuthState = authManager.getLastAuthState();
|
|
3444
|
+
if (lastAuthState?.mfaRequired === true) {
|
|
3445
|
+
setError(null);
|
|
3446
|
+
return { success: true, user, mfaRequired: true };
|
|
3447
|
+
}
|
|
3448
|
+
await walletRef.current.connect();
|
|
3449
|
+
setError(null);
|
|
3450
|
+
return { success: true, user };
|
|
3451
|
+
}
|
|
3452
|
+
catch (err) {
|
|
3453
|
+
console.error("OTP Verify Error:", err);
|
|
3454
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to verify OTP";
|
|
3455
|
+
return { success: false, error: errorMessage };
|
|
3456
|
+
}
|
|
3457
|
+
},
|
|
3458
|
+
onMfaVerify: async (code) => {
|
|
3459
|
+
try {
|
|
3460
|
+
if (!walletRef.current)
|
|
3461
|
+
throw new Error("Wallet not initialized");
|
|
3462
|
+
const wallet = walletRef.current;
|
|
3463
|
+
await wallet.verifyMfa(code);
|
|
3464
|
+
await wallet.connect();
|
|
3465
|
+
setError(null);
|
|
3466
|
+
// Explicitly refresh provider state so OnboardingUI / app re-renders as connected without relying on connect event
|
|
3467
|
+
try {
|
|
3468
|
+
const whoamiInfo = await wallet.getWhoami();
|
|
3469
|
+
if (whoamiInfo) {
|
|
3470
|
+
setIsConnected(true);
|
|
3471
|
+
const userInfo = await wallet.getUserInfo();
|
|
3472
|
+
setUser(userInfo);
|
|
3473
|
+
setWhoami(whoamiInfo);
|
|
3474
|
+
try {
|
|
3475
|
+
const addr = await wallet.getAddress();
|
|
3476
|
+
setAddress(addr);
|
|
3477
|
+
}
|
|
3478
|
+
catch (_e) { /* optional */ }
|
|
3479
|
+
try {
|
|
3480
|
+
const cid = await wallet.getChainId();
|
|
3481
|
+
setChainId(cid);
|
|
3482
|
+
}
|
|
3483
|
+
catch (_e) { /* optional */ }
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
catch (refreshErr) {
|
|
3487
|
+
console.warn("Post-MFA state refresh:", refreshErr);
|
|
3488
|
+
}
|
|
3489
|
+
// Clear OAuth callback params from URL so app shows connected state without refresh
|
|
3490
|
+
if (typeof window !== "undefined") {
|
|
3491
|
+
const url = new URL(window.location.href);
|
|
3492
|
+
const keys = ["success", "mfaRequired", "user", "accessToken", "refreshToken", "turnkeyPublicKey", "error", "provider", "authProvider"];
|
|
3493
|
+
keys.forEach((k) => url.searchParams.delete(k));
|
|
3494
|
+
window.history.replaceState({}, document.title, url.pathname + url.search);
|
|
3495
|
+
}
|
|
3496
|
+
return { success: true };
|
|
3497
|
+
}
|
|
3498
|
+
catch (err) {
|
|
3499
|
+
console.error("MFA Verify Error:", err);
|
|
3500
|
+
const errorMessage = err instanceof Error ? err.message : "MFA verification failed";
|
|
3501
|
+
return { success: false, error: errorMessage };
|
|
3502
|
+
}
|
|
3503
|
+
},
|
|
3504
|
+
}), [config.ui]);
|
|
3045
3505
|
const value = {
|
|
3046
3506
|
wallet: walletRef.current,
|
|
3047
3507
|
isInitialized,
|
|
@@ -3076,7 +3536,18 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
3076
3536
|
loginWithGoogle,
|
|
3077
3537
|
handleGoogleCallback: () => handleGoogleCallback(true),
|
|
3078
3538
|
refreshWhoami,
|
|
3079
|
-
|
|
3539
|
+
getMfaStatus,
|
|
3540
|
+
enableMfa,
|
|
3541
|
+
verifySetupMfa,
|
|
3542
|
+
verifyMfa,
|
|
3543
|
+
disableMfaWithSignedPayload,
|
|
3544
|
+
getSupportedAuthMethods,
|
|
3545
|
+
getLinkedAuthMethods,
|
|
3546
|
+
linkAuthMethod,
|
|
3547
|
+
requestOtpForEmailLink,
|
|
3548
|
+
linkEmail,
|
|
3549
|
+
unlinkAuthMethod,
|
|
3550
|
+
uiConfig: enrichedUiConfig,
|
|
3080
3551
|
// External wallet methods
|
|
3081
3552
|
connectExternalWallet: externalWalletsEnabled
|
|
3082
3553
|
? connectExternalWallet
|
|
@@ -3115,6 +3586,7 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
3115
3586
|
availableChains,
|
|
3116
3587
|
// Balance (for Abstraxn wallet)
|
|
3117
3588
|
walletBalance: !isExternalWalletConnected ? walletBalance : undefined,
|
|
3589
|
+
refetchBalance,
|
|
3118
3590
|
// Connection type
|
|
3119
3591
|
connectionType,
|
|
3120
3592
|
// Pre-fill email for email-OTP flow
|
|
@@ -3142,12 +3614,15 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
3142
3614
|
if (!container) {
|
|
3143
3615
|
return;
|
|
3144
3616
|
}
|
|
3145
|
-
// Check if we're on OTP screen - if so, don't show external wallets
|
|
3617
|
+
// Check if we're on OTP or MFA screen - if so, don't show external wallets
|
|
3146
3618
|
const isOtpScreen = onboardingAny.otpVerificationScreen &&
|
|
3147
3619
|
onboardingAny.otpVerificationScreen.parentElement &&
|
|
3148
3620
|
onboardingAny.otpVerificationScreen.offsetParent !== null;
|
|
3149
|
-
|
|
3150
|
-
|
|
3621
|
+
const isMfaScreen = onboardingAny.mfaVerificationScreen &&
|
|
3622
|
+
onboardingAny.mfaVerificationScreen.parentElement &&
|
|
3623
|
+
onboardingAny.mfaVerificationScreen.offsetParent !== null;
|
|
3624
|
+
if (!isOtpScreen && !isMfaScreen) {
|
|
3625
|
+
// Only show external wallets if not on OTP or MFA (auth code) screen
|
|
3151
3626
|
container.style.display = "";
|
|
3152
3627
|
// Show divider only if both email/Google AND external wallets are visible
|
|
3153
3628
|
const authMethods = onboardingAny.config?.authMethods || [
|
|
@@ -3165,7 +3640,7 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
3165
3640
|
}
|
|
3166
3641
|
}
|
|
3167
3642
|
else {
|
|
3168
|
-
// On OTP screen - hide external wallets
|
|
3643
|
+
// On OTP or MFA screen - hide external wallets
|
|
3169
3644
|
container.style.display = "none";
|
|
3170
3645
|
if (onboardingAny.externalWalletDivider) {
|
|
3171
3646
|
onboardingAny.externalWalletDivider.style.display = "none";
|
|
@@ -3330,14 +3805,15 @@ export function AbstraxnProviderInner({ config, children, base, wagmi, solana, }
|
|
|
3330
3805
|
console.warn("External wallet container is null");
|
|
3331
3806
|
return;
|
|
3332
3807
|
}
|
|
3333
|
-
// Check if we're on OTP screen - if so, don't show external wallets
|
|
3334
|
-
// Check if OTP screen exists AND is actually visible in the DOM
|
|
3808
|
+
// Check if we're on OTP or MFA screen - if so, don't show external wallets
|
|
3335
3809
|
const isOtpScreen = onboardingAny.otpVerificationScreen &&
|
|
3336
3810
|
onboardingAny.otpVerificationScreen.parentElement &&
|
|
3337
3811
|
onboardingAny.otpVerificationScreen.offsetParent !== null;
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3812
|
+
const isMfaScreen = onboardingAny.mfaVerificationScreen &&
|
|
3813
|
+
onboardingAny.mfaVerificationScreen.parentElement &&
|
|
3814
|
+
onboardingAny.mfaVerificationScreen.offsetParent !== null;
|
|
3815
|
+
if (!isOtpScreen && !isMfaScreen) {
|
|
3816
|
+
// Only show external wallets if not on OTP or MFA (auth code) screen
|
|
3341
3817
|
container.style.display = "";
|
|
3342
3818
|
// Show divider only if both email/Google AND external wallets are visible
|
|
3343
3819
|
const authMethods = onboardingAny.config?.authMethods || [
|