UNPKG

@avalabs/avacloud-waas-react

Version:
1,541 lines (1,517 loc) 185 kB
// src/AvaCloudWalletProvider.tsx import { createContext as createContext4, useContext as useContext4, useEffect as useEffect5, useState as useState8, useCallback as useCallback6, useRef as useRef3 } from "react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { envs } from "@cubist-labs/cubesigner-sdk"; // src/types/vm.ts var VM = /* @__PURE__ */ ((VM2) => { VM2["EVM"] = "EVM"; VM2["AVM"] = "AVM"; VM2["PVM"] = "PVM"; return VM2; })(VM || {}); // src/utils/derivationPath.ts var getCoinIndexForVm = (vm) => { switch (vm) { case "EVM" /* EVM */: return 60; case "AVM" /* AVM */: case "PVM" /* PVM */: return 9e3; default: throw new Error(`Unknown coin index for vm: ${vm}`); } }; function getDerivationPath(vm, accountIndex) { if (accountIndex < 0 || Math.round(accountIndex) !== accountIndex) { throw new Error("Account index must be a non-negative integer"); } const coinIndex = getCoinIndexForVm(vm); return `m/44'/${coinIndex}'/0'/0/${accountIndex}`; } // src/hooks/usePenpalAuth.ts import { useCallback, useEffect, useRef, useState } from "react"; import { connect, WindowMessenger } from "penpal"; // ../avacloud-waas-common/dist/constants/storage.js var OIDC_TOKEN_KEY = "avacloud-auth-oidc-token"; var AUTH_TOKENS_KEY = "auth_tokens"; var AUTH0_STORAGE_KEYS = { IS_AUTHENTICATED: "auth0.is.authenticated", ACCESS_TOKEN: "auth0.access_token", ID_TOKEN: "auth0.id_token", EXPIRES_AT: "auth0.expires_at" }; var CUBIST_USER_ID_KEY = "cubist_user_id"; var ORG_CONFIG_CACHE_KEY = "avacloud-org-config-cache"; // src/hooks/usePenpalAuth.ts function usePenpalAuth({ authServiceUrl, orgId, environment, iframe, onAuthSuccess, onAuthError, onOidcReceived, onOrgConfigUpdate, setIsAuthenticated, setUser, setIsLoading, setIsIframeReady }) { const connectionRef = useRef(null); const [authService, setAuthService] = useState(null); const [isConnected, setIsConnected] = useState(false); const isConnectedRef = useRef(false); const hasCheckedAuthRef = useRef(false); const hasLoadedCacheRef = useRef(false); useEffect(() => { if (orgId && onOrgConfigUpdate && !hasLoadedCacheRef.current) { hasLoadedCacheRef.current = true; try { const cachedConfigKey = `${ORG_CONFIG_CACHE_KEY}-${orgId}-${environment}`; const cachedConfigJson = localStorage.getItem(cachedConfigKey); if (cachedConfigJson) { const cachedConfig = JSON.parse(cachedConfigJson); const timestamp = cachedConfig._timestamp || 0; const isExpired = Date.now() - timestamp > 30 * 60 * 1e3; if (!isExpired) { const { _timestamp, ...configWithoutTimestamp } = cachedConfig; onOrgConfigUpdate(configWithoutTimestamp); } } } catch (error) { } } }, [orgId, environment, onOrgConfigUpdate]); const parentMethods = { onReady: () => { console.log("[Penpal Parent] Iframe is ready"); setIsIframeReady(true); }, onAuthStatusChange: (status) => { var _a; console.log("[Penpal Parent] Auth status changed:", status); setIsAuthenticated(status.isAuthenticated); if (status.user) { const userInfo = { email: status.user.email, sub: status.user.sub, configured_mfa: [], displayName: status.user.nickname || status.user.name || ((_a = status.user.email) == null ? void 0 : _a.split("@")[0]) || "User", rawUserData: status.user }; setUser(userInfo); } else { setUser(null); } if (status.orgConfig) { onOrgConfigUpdate == null ? void 0 : onOrgConfigUpdate(status.orgConfig); if (orgId) { try { const cachedConfigKey = `${ORG_CONFIG_CACHE_KEY}-${orgId}-${environment}`; const configWithTimestamp = { ...status.orgConfig, _timestamp: Date.now() }; localStorage.setItem(cachedConfigKey, JSON.stringify(configWithTimestamp)); } catch (error) { } } } if (status.isAuthenticated) { onAuthSuccess == null ? void 0 : onAuthSuccess(); } setIsLoading(false); }, onError: (error) => { console.error("[Penpal Parent] Error from iframe:", error); if (error.message === "User not authenticated in iframe" || error.message === "Unknown error" || error.message.includes("OIDC user not found")) { return; } onAuthError == null ? void 0 : onAuthError(new Error(error.message)); setIsLoading(false); }, onWalletUpdate: (wallet) => { console.log("[Penpal Parent] Wallet update:", wallet); } }; useEffect(() => { if (!(iframe == null ? void 0 : iframe.contentWindow)) { return; } let isCancelled = false; const setupConnection = async (attempt = 1, maxAttempts = 3) => { if (isCancelled) return; if (isConnectedRef.current && connectionRef.current) { console.log("[Penpal Parent] Already connected, skipping setup"); return; } console.log(`[Penpal Parent] Setting up connection to iframe (attempt ${attempt}/${maxAttempts})...`); if (connectionRef.current) { connectionRef.current.destroy(); connectionRef.current = null; setAuthService(null); setIsConnected(false); isConnectedRef.current = false; } try { const authServiceOrigin = new URL(authServiceUrl).origin; const messenger = new WindowMessenger({ remoteWindow: iframe.contentWindow, allowedOrigins: [authServiceOrigin] }); const connection = connect({ messenger, methods: parentMethods, timeout: 1e4 // 10 second connection timeout per attempt }); connectionRef.current = connection; const remote = await connection.promise; if (isCancelled) { connection.destroy(); return; } console.log("[Penpal Parent] Connected to auth service"); isConnectedRef.current = true; setAuthService(remote); setIsConnected(true); setIsIframeReady(true); } catch (error) { if (isCancelled) return; console.error(`[Penpal Parent] Failed to connect (attempt ${attempt}):`, error); if (attempt < maxAttempts) { console.log(`[Penpal Parent] Retrying in 1 second...`); await new Promise((resolve) => setTimeout(resolve, 1e3)); if (!isCancelled) { await setupConnection(attempt + 1, maxAttempts); } } else { onAuthError == null ? void 0 : onAuthError(error instanceof Error ? error : new Error("Failed to connect to auth service")); } } }; setupConnection(); return () => { isCancelled = true; }; }, [iframe, authServiceUrl]); useEffect(() => { if (isConnected && authService && !hasCheckedAuthRef.current) { hasCheckedAuthRef.current = true; authService.checkAuthStatus(orgId, environment).then((status) => { console.log("[Penpal Parent] Initial auth status:", status); parentMethods.onAuthStatusChange(status); if (status.isAuthenticated) { return authService.getOidc(); } return null; }).then((oidcResult) => { if (oidcResult == null ? void 0 : oidcResult.idToken) { onOidcReceived == null ? void 0 : onOidcReceived(oidcResult.idToken); } }).catch((error) => { console.error("[Penpal Parent] Error checking auth status:", error); setIsLoading(false); }); } }, [isConnected, authService, orgId, environment]); const login = useCallback(async (params) => { var _a; if (!authService) { console.warn("[Penpal] Cannot login - not connected"); return; } try { const result = await authService.login(params || {}); setIsAuthenticated(true); if (result.user) { const userInfo = { email: result.user.email, sub: result.user.sub, configured_mfa: [], displayName: result.user.nickname || result.user.name || ((_a = result.user.email) == null ? void 0 : _a.split("@")[0]) || "User", rawUserData: result.user }; setUser(userInfo); } if (result.accessToken) { onOidcReceived == null ? void 0 : onOidcReceived(result.accessToken); } onAuthSuccess == null ? void 0 : onAuthSuccess(); return result; } catch (error) { onAuthError == null ? void 0 : onAuthError(error instanceof Error ? error : new Error("Login failed")); throw error; } }, [authService, setIsAuthenticated, setUser, onOidcReceived, onAuthSuccess, onAuthError]); const signup = useCallback(async (email, password) => { var _a; if (!authService) { console.warn("[Penpal] Cannot signup - not connected"); return; } try { const result = await authService.signup({ email, password }); setIsAuthenticated(true); if (result.user) { const userInfo = { email: result.user.email, sub: result.user.sub, configured_mfa: [], displayName: result.user.nickname || result.user.name || ((_a = result.user.email) == null ? void 0 : _a.split("@")[0]) || "User", rawUserData: result.user }; setUser(userInfo); } if (result.accessToken) { onOidcReceived == null ? void 0 : onOidcReceived(result.accessToken); } onAuthSuccess == null ? void 0 : onAuthSuccess(); return result; } catch (error) { onAuthError == null ? void 0 : onAuthError(error instanceof Error ? error : new Error("Signup failed")); throw error; } }, [authService, setIsAuthenticated, setUser, onOidcReceived, onAuthSuccess, onAuthError]); const logout = useCallback(async () => { if (!authService) { console.warn("[Penpal] Cannot logout - not connected"); return; } try { await authService.logout(); setIsAuthenticated(false); setUser(null); localStorage.removeItem(AUTH_TOKENS_KEY); localStorage.removeItem(AUTH0_STORAGE_KEYS.IS_AUTHENTICATED); localStorage.removeItem(AUTH0_STORAGE_KEYS.ACCESS_TOKEN); localStorage.removeItem(AUTH0_STORAGE_KEYS.ID_TOKEN); localStorage.removeItem(AUTH0_STORAGE_KEYS.EXPIRES_AT); localStorage.removeItem(CUBIST_USER_ID_KEY); sessionStorage.removeItem(OIDC_TOKEN_KEY); } catch (error) { onAuthError == null ? void 0 : onAuthError(error instanceof Error ? error : new Error("Logout failed")); throw error; } }, [authService, setIsAuthenticated, setUser, onAuthError]); const getOidc = useCallback(async () => { if (!authService) { console.warn("[Penpal] Cannot get OIDC - not connected"); return; } try { return await authService.getOidc(); } catch (error) { onAuthError == null ? void 0 : onAuthError(error instanceof Error ? error : new Error("Failed to get OIDC token")); throw error; } }, [authService, onAuthError]); const checkAuthStatus = useCallback(async () => { if (!authService) { console.warn("[Penpal] Cannot check auth status - not connected"); return; } try { return await authService.checkAuthStatus(orgId, environment); } catch (error) { onAuthError == null ? void 0 : onAuthError(error instanceof Error ? error : new Error("Failed to check auth status")); throw error; } }, [authService, orgId, environment, onAuthError]); return { authService, isConnected, login, signup, logout, getOidc, checkAuthStatus }; } // src/AuthModalContext.tsx import { createContext, useContext, useState as useState4, useCallback as useCallback3 } from "react"; import { Dialog as Dialog2, DialogContent as DialogContent2, DialogTitle } from "@avalabs/core-k2-components"; // src/components/Modal.tsx import { useState as useState3, useRef as useRef2, useCallback as useCallback2, useEffect as useEffect2 } from "react"; import { Dialog, DialogContent, IconButton as IconButton2, Stack as Stack3, XIcon } from "@avalabs/core-k2-components"; // src/components/SignInContent.tsx import { useState as useState2 } from "react"; import { Button, TextField, Typography as Typography2, Stack as Stack2, IconButton, Divider, GoogleIcon, AppleIcon, XTwitterIcon, FacebookIcon } from "@avalabs/core-k2-components"; // src/components/PoweredByAvaCloud.tsx import { Stack, Typography, AvaCloudConnectIcon } from "@avalabs/core-k2-components"; import { jsx, jsxs } from "react/jsx-runtime"; function PoweredByAvaCloud() { return /* @__PURE__ */ jsxs(Stack, { direction: "row", alignItems: "center", justifyContent: "center", spacing: 1, children: [ /* @__PURE__ */ jsx(Typography, { variant: "body2", children: "Powered by" }), /* @__PURE__ */ jsxs(Stack, { direction: "row", alignItems: "center", spacing: 0.5, children: [ /* @__PURE__ */ jsx(AvaCloudConnectIcon, {}), /* @__PURE__ */ jsx(Typography, { variant: "body2", children: "AvaCloud" }) ] }) ] }); } // src/constants/legal.ts var TERMS_OF_SERVICE_URL = "https://app.avacloud.io/legal/waas/user-terms"; var PRIVACY_POLICY_URL = "https://www.avalabs.org/privacy-policy"; // src/components/SignInContent.tsx import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime"; function SignInContent({ onEmailLogin, onEmailSignup, onProviderLogin, error, isSubmitting, socialLogins = ["google", "x", "apple"] }) { const [email, setEmail] = useState2(""); const [password, setPassword] = useState2(""); const [isPasswordStep, setIsPasswordStep] = useState2(false); const [isSignupMode, setIsSignupMode] = useState2(false); const providerConnectionMap = { "google": "google-oauth2", "x": "twitter", "twitter": "twitter", "facebook": "facebook", "apple": "apple" }; const renderSocialLoginButton = (loginType) => { if (!providerConnectionMap[loginType]) return null; const connectionName = providerConnectionMap[loginType]; let LoginIcon; switch (loginType) { case "google": LoginIcon = GoogleIcon; break; case "x": case "twitter": LoginIcon = XTwitterIcon; break; case "facebook": LoginIcon = FacebookIcon; break; case "apple": LoginIcon = AppleIcon; break; default: return null; } return /* @__PURE__ */ jsx2( IconButton, { color: "default", variant: "contained", onClick: () => onProviderLogin(connectionName), disabled: isSubmitting, sx: { display: "flex", padding: "16px", alignItems: "center", gap: "8px", borderRadius: "8px", background: "rgba(0, 0, 0, 0.02)", boxShadow: "2px 1px 4px 0px rgba(0, 0, 0, 0.20)", width: 72, height: 72, "&:hover": { background: "rgba(0, 0, 0, 0.04)" } }, children: /* @__PURE__ */ jsx2(LoginIcon, { sx: { width: 24, height: 24 } }) }, loginType ); }; const handleEmailSubmit = (e) => { e.preventDefault(); if (!email) return; setIsPasswordStep(true); }; const handlePasswordSubmit = (e) => { e.preventDefault(); if (!password) return; onEmailLogin(email, password); }; const handleSignupSubmit = (e) => { e.preventDefault(); if (!email || !password) return; onEmailSignup(email, password); }; const handleForgotPassword = () => { }; const handleToggleSignup = () => { setIsSignupMode(!isSignupMode); setIsPasswordStep(false); }; const titleTypography = { alignSelf: "stretch", color: (theme) => theme.palette.mode === "dark" ? "#FFFFFF" : "rgba(0, 0, 0, 0.80)", fontFamily: "Inter", fontSize: "16px", fontStyle: "normal", fontWeight: 700, lineHeight: "150%" }; if (isSignupMode) { return /* @__PURE__ */ jsxs2(Stack2, { gap: 3, children: [ /* @__PURE__ */ jsx2(Typography2, { variant: "h6", sx: titleTypography, children: "Sign up with" }), /* @__PURE__ */ jsx2(Stack2, { direction: "row", spacing: 3, sx: { justifyContent: "center" }, children: socialLogins.map((loginType) => renderSocialLoginButton(loginType)) }), /* @__PURE__ */ jsx2(Divider, { children: /* @__PURE__ */ jsx2(Typography2, { variant: "body2", color: "text.secondary", children: "or" }) }), /* @__PURE__ */ jsx2("form", { onSubmit: handleSignupSubmit, children: /* @__PURE__ */ jsxs2(Stack2, { gap: 2, children: [ /* @__PURE__ */ jsx2( TextField, { placeholder: "Enter email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), fullWidth: true, required: true, disabled: isSubmitting, error: !!error, helperText: error, sx: { "& .MuiOutlinedInput-root": { backgroundColor: (theme) => theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.05)" : "grey.50" } } } ), /* @__PURE__ */ jsx2( TextField, { placeholder: "Create password", type: "password", value: password, onChange: (e) => setPassword(e.target.value), fullWidth: true, required: true, disabled: isSubmitting, sx: { "& .MuiOutlinedInput-root": { backgroundColor: (theme) => theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.05)" : "grey.50" } } } ), /* @__PURE__ */ jsx2( Button, { type: "submit", variant: "contained", fullWidth: true, disabled: isSubmitting || !email || !password, sx: { height: 48, backgroundColor: "#3A65FF", borderRadius: 24, textTransform: "none", "&:hover": { backgroundColor: "#2952E6" } }, children: "Sign up" } ), /* @__PURE__ */ jsxs2( Typography2, { variant: "body2", sx: { textAlign: "center", "& a": { color: "#3A65FF", textDecoration: "none", cursor: "pointer", "&:hover": { textDecoration: "underline" } } }, children: [ "Already have an account?", " ", /* @__PURE__ */ jsx2( Button, { variant: "text", onClick: handleToggleSignup, sx: { color: "#3A65FF", textTransform: "none", padding: "0 4px", minWidth: "auto", fontWeight: 600, "&:hover": { textDecoration: "underline", backgroundColor: "transparent" } }, children: "Sign in" } ) ] } ) ] }) }), /* @__PURE__ */ jsx2(PoweredByAvaCloud, {}), /* @__PURE__ */ jsxs2( Typography2, { variant: "body2", sx: { textAlign: "center", color: "text.secondary", fontSize: "0.75rem", "& a": { color: "inherit", textDecoration: "none", "&:hover": { textDecoration: "underline" } } }, children: [ "By connecting, you agree to the", " ", /* @__PURE__ */ jsx2( Button, { variant: "text", component: "a", href: TERMS_OF_SERVICE_URL, target: "_blank", rel: "noopener noreferrer", sx: { color: "inherit", p: 0, minWidth: "auto", textTransform: "none", fontSize: "inherit", fontWeight: "inherit", "&:hover": { textDecoration: "underline", backgroundColor: "transparent" } }, children: "Terms of Service" } ), " ", "and", " ", /* @__PURE__ */ jsx2( Button, { variant: "text", component: "a", href: PRIVACY_POLICY_URL, target: "_blank", rel: "noopener noreferrer", sx: { color: "inherit", p: 0, minWidth: "auto", textTransform: "none", fontSize: "inherit", fontWeight: "inherit", "&:hover": { textDecoration: "underline", backgroundColor: "transparent" } }, children: "Privacy Policy" } ) ] } ) ] }); } if (isPasswordStep) { return /* @__PURE__ */ jsxs2(Stack2, { gap: 3, children: [ /* @__PURE__ */ jsx2(Typography2, { variant: "h6", sx: titleTypography, children: "Sign in with" }), /* @__PURE__ */ jsx2("form", { onSubmit: handlePasswordSubmit, children: /* @__PURE__ */ jsxs2(Stack2, { gap: 2, children: [ /* @__PURE__ */ jsx2( TextField, { placeholder: "Enter password", type: "password", value: password, onChange: (e) => setPassword(e.target.value), fullWidth: true, required: true, disabled: isSubmitting, error: !!error, helperText: error, sx: { "& .MuiOutlinedInput-root": { backgroundColor: (theme) => theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.05)" : "grey.50" } } } ), /* @__PURE__ */ jsx2( Button, { type: "submit", variant: "contained", fullWidth: true, disabled: isSubmitting || !password, sx: { height: 48, backgroundColor: "#3A65FF", borderRadius: 24, textTransform: "none", "&:hover": { backgroundColor: "#2952E6" } }, children: "Continue" } ), /* @__PURE__ */ jsx2( Typography2, { variant: "body2", sx: { textAlign: "center", "& a": { color: "#3A65FF", textDecoration: "none", cursor: "pointer", "&:hover": { textDecoration: "underline" } } }, children: /* @__PURE__ */ jsx2( Button, { variant: "text", onClick: handleForgotPassword, sx: { color: "#3A65FF", textTransform: "none", "&:hover": { textDecoration: "underline", backgroundColor: "transparent" } }, children: "Forget password?" } ) } ) ] }) }), /* @__PURE__ */ jsx2(PoweredByAvaCloud, {}), /* @__PURE__ */ jsxs2( Typography2, { variant: "body2", sx: { textAlign: "center", color: "text.secondary", fontSize: "0.75rem", "& a": { color: "inherit", textDecoration: "none", "&:hover": { textDecoration: "underline" } } }, children: [ "By continuing, you agree to our", " ", /* @__PURE__ */ jsx2( Button, { variant: "text", component: "a", href: TERMS_OF_SERVICE_URL, target: "_blank", rel: "noopener noreferrer", sx: { color: "inherit", p: 0, minWidth: "auto", textTransform: "none", fontSize: "inherit", fontWeight: "inherit", "&:hover": { textDecoration: "underline", backgroundColor: "transparent" } }, children: "Terms of Use" } ), " ", "and", " ", /* @__PURE__ */ jsx2( Button, { variant: "text", component: "a", href: PRIVACY_POLICY_URL, target: "_blank", rel: "noopener noreferrer", sx: { color: "inherit", p: 0, minWidth: "auto", textTransform: "none", fontSize: "inherit", fontWeight: "inherit", "&:hover": { textDecoration: "underline", backgroundColor: "transparent" } }, children: "Privacy Policy" } ) ] } ) ] }); } return /* @__PURE__ */ jsxs2(Stack2, { gap: 3, children: [ /* @__PURE__ */ jsx2(Typography2, { variant: "h6", sx: titleTypography, children: "Sign in with" }), /* @__PURE__ */ jsx2(Stack2, { direction: "row", spacing: 3, sx: { justifyContent: "center" }, children: socialLogins.map((loginType) => renderSocialLoginButton(loginType)) }), /* @__PURE__ */ jsx2(Divider, { children: /* @__PURE__ */ jsx2(Typography2, { variant: "body2", color: "text.secondary", children: "or" }) }), /* @__PURE__ */ jsx2("form", { onSubmit: handleEmailSubmit, children: /* @__PURE__ */ jsxs2(Stack2, { gap: 2, children: [ /* @__PURE__ */ jsx2( TextField, { placeholder: "Enter email", type: "email", value: email, onChange: (e) => setEmail(e.target.value), fullWidth: true, required: true, disabled: isSubmitting, error: !!error, helperText: error, sx: { "& .MuiOutlinedInput-root": { backgroundColor: (theme) => theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.05)" : "grey.50" } } } ), /* @__PURE__ */ jsx2( Button, { type: "submit", variant: "contained", fullWidth: true, disabled: isSubmitting || !email, sx: { height: 48, backgroundColor: "#3A65FF", borderRadius: 24, textTransform: "none", "&:hover": { backgroundColor: "#2952E6" } }, children: "Continue" } ), /* @__PURE__ */ jsxs2( Typography2, { variant: "body2", sx: { textAlign: "center", "& a": { color: "#3A65FF", textDecoration: "none", cursor: "pointer", "&:hover": { textDecoration: "underline" } } }, children: [ "Don't have an account?", " ", /* @__PURE__ */ jsx2( Button, { variant: "text", onClick: handleToggleSignup, sx: { color: "#3A65FF", textTransform: "none", padding: "0 4px", minWidth: "auto", fontWeight: 600, "&:hover": { textDecoration: "underline", backgroundColor: "transparent" } }, children: "Sign up" } ) ] } ) ] }) }), /* @__PURE__ */ jsx2(PoweredByAvaCloud, {}), /* @__PURE__ */ jsxs2( Typography2, { variant: "body2", sx: { textAlign: "center", color: "text.secondary", fontSize: "0.75rem", "& a": { color: "inherit", textDecoration: "none", "&:hover": { textDecoration: "underline" } } }, children: [ "By connecting, you agree to the", " ", /* @__PURE__ */ jsx2( Button, { variant: "text", component: "a", href: TERMS_OF_SERVICE_URL, target: "_blank", rel: "noopener noreferrer", sx: { color: "inherit", p: 0, minWidth: "auto", textTransform: "none", fontSize: "inherit", fontWeight: "inherit", "&:hover": { textDecoration: "underline", backgroundColor: "transparent" } }, children: "Terms of Service" } ), " ", "and", " ", /* @__PURE__ */ jsx2( Button, { variant: "text", component: "a", href: PRIVACY_POLICY_URL, target: "_blank", rel: "noopener noreferrer", sx: { color: "inherit", p: 0, minWidth: "auto", textTransform: "none", fontSize: "inherit", fontWeight: "inherit", "&:hover": { textDecoration: "underline", backgroundColor: "transparent" } }, children: "Privacy Policy" } ) ] } ) ] }); } // src/components/Modal.tsx import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime"; function LoginModal({ open, onClose }) { var _a; const { orgConfig, signup, login, isAuthenticated } = useAvaCloudWallet(); const [isSubmitting, setIsSubmitting] = useState3(false); const [error, setError] = useState3(""); const timeoutIdRef = useRef2(null); const socialLogins = (_a = orgConfig == null ? void 0 : orgConfig.adminPortalSettings) == null ? void 0 : _a.socialLogins; useEffect2(() => { if (isAuthenticated && open) { setIsSubmitting(false); if (timeoutIdRef.current) { clearTimeout(timeoutIdRef.current); timeoutIdRef.current = null; } onClose(); } }, [isAuthenticated, open, onClose]); useEffect2(() => { if (!open) { setIsSubmitting(false); setError(""); if (timeoutIdRef.current) { clearTimeout(timeoutIdRef.current); timeoutIdRef.current = null; } } }, [open]); useEffect2(() => { return () => { if (timeoutIdRef.current) { clearTimeout(timeoutIdRef.current); timeoutIdRef.current = null; } }; }, []); const handleProviderLogin = useCallback2((provider) => { setError(""); setIsSubmitting(true); login({ connection: provider }); }, [login]); const handleEmailLogin = useCallback2((email, password) => { setError(""); setIsSubmitting(true); if (timeoutIdRef.current) { clearTimeout(timeoutIdRef.current); timeoutIdRef.current = null; } login({ email, password }); timeoutIdRef.current = setTimeout(() => { setError("Authentication service timed out"); setIsSubmitting(false); timeoutIdRef.current = null; }, 1e4); }, [login]); const handleEmailSignup = useCallback2((email, password) => { setError(""); setIsSubmitting(true); if (timeoutIdRef.current) { clearTimeout(timeoutIdRef.current); timeoutIdRef.current = null; } signup(email, password); timeoutIdRef.current = setTimeout(() => { setError("Authentication service timed out"); setIsSubmitting(false); timeoutIdRef.current = null; }, 1e4); }, [signup]); return /* @__PURE__ */ jsxs3( Dialog, { open, maxWidth: "xs", PaperProps: { sx: { borderRadius: "8px", width: "100%", maxWidth: 400, maxHeight: "calc(100% - 64px)", m: 2, overflow: "visible", bgcolor: (theme) => theme.palette.mode === "dark" ? "#1A1A1A" : "#ffffff", backgroundImage: "none", display: "flex", flexDirection: "column", position: "relative", boxShadow: (theme) => theme.palette.mode === "dark" ? "0px 4px 6px 0px rgba(0, 0, 0, 0.3), 0px 15px 15px 0px rgba(0, 0, 0, 0.25)" : "0px 4px 6px 0px rgba(0, 0, 0, 0.16), 0px 15px 15px 0px rgba(0, 0, 0, 0.15)", transition: "all 0.2s ease-in-out" } }, children: [ /* @__PURE__ */ jsxs3( Stack3, { direction: "row", sx: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", alignSelf: "stretch", p: 3, pb: 0 }, children: [ /* @__PURE__ */ jsx3( "img", { src: "https://images.ctfassets.net/9bazykntljf6/58QaXZf2yQ7MqI9A8MrKiX/d8f986355c6e321e1dee79f6e91575ec/avacloud.png", alt: "AvaCloud", style: { height: 24, objectFit: "contain" } } ), /* @__PURE__ */ jsx3( IconButton2, { onClick: onClose, size: "small", sx: { color: (theme) => theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.54)" : "rgba(0, 0, 0, 0.54)", padding: "4px", marginTop: "-4px", marginRight: "-4px", "&:hover": { backgroundColor: (theme) => theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.04)" : "rgba(0, 0, 0, 0.04)" } }, children: /* @__PURE__ */ jsx3(XIcon, { sx: { fontSize: 20 } }) } ) ] } ), /* @__PURE__ */ jsx3(DialogContent, { sx: { p: 3 }, children: /* @__PURE__ */ jsx3( SignInContent, { onEmailLogin: handleEmailLogin, onEmailSignup: handleEmailSignup, onProviderLogin: handleProviderLogin, error, isSubmitting, socialLogins } ) }) ] } ); } // src/AuthModalContext.tsx import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime"; var AuthModalContext = createContext(void 0); function AuthModalProvider({ children }) { const [isModalOpen, setIsModalOpen] = useState4(false); const openLoginModal = useCallback3(() => setIsModalOpen(true), []); const closeLoginModal = useCallback3(() => setIsModalOpen(false), []); return /* @__PURE__ */ jsxs4(AuthModalContext.Provider, { value: { openLoginModal, closeLoginModal, isModalOpen }, children: [ children, /* @__PURE__ */ jsxs4( Dialog2, { open: isModalOpen, onClose: closeLoginModal, maxWidth: "xs", children: [ /* @__PURE__ */ jsx4(DialogTitle, { children: /* @__PURE__ */ jsx4("img", { src: "", alt: "AvaCloud Connect", style: { height: "32px" } }) }), /* @__PURE__ */ jsx4(DialogContent2, { sx: { padding: 4 }, children: /* @__PURE__ */ jsx4( LoginModal, { open: isModalOpen, onClose: closeLoginModal } ) }) ] } ) ] }); } function useAuthModal() { const context = useContext(AuthModalContext); if (context === void 0) { throw new Error("useAuthModal must be used within an AuthModalProvider"); } return context; } // src/AvaCloudWalletProvider.tsx import { CubeSignerClient } from "@cubist-labs/cubesigner-sdk"; // src/providers/ViemContext.tsx import { createContext as createContext2, useContext as useContext2, useState as useState5, useEffect as useEffect3 } from "react"; import { createPublicClient, createWalletClient, http } from "viem"; // src/hooks/useAuth.ts import { useCallback as useCallback4 } from "react"; function useAuth() { const { isAuthenticated, isLoading, user, wallet, logout, loginWithCubist, cubistClient, cubistError } = useAvaCloudWallet(); const { openLoginModal } = useAuthModal(); const login = useCallback4(() => { openLoginModal(); }, [openLoginModal]); return { isAuthenticated, isLoading, user, wallet, login, logout, loginWithCubist, cubistClient, cubistError }; } // src/providers/ViemContext.tsx import { jsx as jsx5 } from "react/jsx-runtime"; var ViemContext = createContext2(null); function ViemProvider({ children, rpcUrl, chainId, explorerUrl }) { var _a, _b; const [publicClient, setPublicClient] = useState5(null); const [walletClient, setWalletClient] = useState5(null); const [isConnected, setIsConnected] = useState5(false); const [error, setError] = useState5(null); const { cubistClient, wallet: authWallet } = useAuth(); useEffect3(() => { const initClient = async () => { var _a2; try { const transport = http(rpcUrl); const customChain = { id: chainId, name: `Chain ${chainId}`, nativeCurrency: { name: "AVAX", symbol: "AVAX", decimals: 18 }, rpcUrls: { default: { http: [rpcUrl] }, public: { http: [rpcUrl] } }, blockExplorers: explorerUrl ? { default: { name: "Explorer", url: explorerUrl } } : void 0 }; const client = createPublicClient({ transport, chain: customChain }); if ((_a2 = authWallet == null ? void 0 : authWallet.cubistWallet) == null ? void 0 : _a2.address) { const walletInstance = createWalletClient({ transport, chain: customChain, account: authWallet.cubistWallet.address }); setWalletClient(walletInstance); } await client.getBlockNumber(); setPublicClient(client); setIsConnected(true); setError(null); } catch (err) { setError(err instanceof Error ? err : new Error("Failed to connect")); setIsConnected(false); } }; initClient(); }, [rpcUrl, chainId, explorerUrl, (_a = authWallet == null ? void 0 : authWallet.cubistWallet) == null ? void 0 : _a.address]); useEffect3(() => { var _a2; if (((_a2 = authWallet == null ? void 0 : authWallet.cubistWallet) == null ? void 0 : _a2.address) && publicClient && walletClient) { setIsConnected(true); } else { setIsConnected(false); } }, [(_b = authWallet == null ? void 0 : authWallet.cubistWallet) == null ? void 0 : _b.address, publicClient, walletClient]); const clearError = () => setError(null); return /* @__PURE__ */ jsx5( ViemContext.Provider, { value: { publicClient, walletClient, setPublicClient, setWalletClient, chainId, explorerUrl, isConnected, error, clearError }, children } ); } function useViem() { const context = useContext2(ViemContext); if (!context) { throw new Error("useViem must be used within a ViemProvider"); } return context; } // src/hooks/useGlacier.ts import { useQuery } from "@tanstack/react-query"; // src/services/glacier/client.ts var GLACIER_API_BASE_URL = "https://glacier-api.avax.network"; var GlacierApiClient = class { constructor() { this.baseUrl = GLACIER_API_BASE_URL; } async getBlockchains() { const response = await fetch(`${this.baseUrl}/v1/chains`); if (!response.ok) { throw new Error("Failed to fetch blockchains"); } return response.json(); } async getValidators(subnetId) { const response = await fetch(`${this.baseUrl}/v1/subnets/${subnetId}/validators`); if (!response.ok) { throw new Error("Failed to fetch validators"); } return response.json(); } async getSubnets() { const response = await fetch(`${this.baseUrl}/v1/subnets`); if (!response.ok) { throw new Error("Failed to fetch subnets"); } return response.json(); } async getBlockchain(chainId) { const response = await fetch(`${this.baseUrl}/v1/chains/${chainId}`); if (!response.ok) { throw new Error("Failed to fetch blockchain"); } return response.json(); } async getBalance(address, chainId) { const chain = await this.getBlockchain(chainId); const response = await fetch(chain.rpcUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_getBalance", params: [address, "latest"] }) }); if (!response.ok) { throw new Error("Failed to fetch balance"); } const data = await response.json(); return data.result; } async getERC20Balances(address, chainId) { const response = await fetch(`${this.baseUrl}/v1/chains/${chainId}/addresses/${address}/balances:listErc20`); if (!response.ok) { throw new Error("Failed to fetch ERC20 balances"); } return response.json(); } async getERC721Balances(address, chainId) { const response = await fetch(`${this.baseUrl}/v1/chains/${chainId}/addresses/${address}/balances:listErc721`); if (!response.ok) { throw new Error("Failed to fetch ERC721 balances"); } return response.json(); } async getERC1155Balances(address, chainId) { const response = await fetch(`${this.baseUrl}/v1/chains/${chainId}/addresses/${address}/balances:listErc1155`); if (!response.ok) { throw new Error("Failed to fetch ERC1155 balances"); } return response.json(); } }; var glacierApi = new GlacierApiClient(); // src/hooks/useChainId.ts import { useState as useState6 } from "react"; var CHAIN_ID_STORAGE_KEY = "avalanche-chain-id"; var DEFAULT_CHAIN_ID = 43113; function useChainId() { const [chainId, setChainIdState] = useState6(() => { const storedChainId = localStorage.getItem(CHAIN_ID_STORAGE_KEY); return storedChainId ? Number.parseInt(storedChainId, 10) : DEFAULT_CHAIN_ID; }); const setChainId = (newChainId) => { localStorage.setItem(CHAIN_ID_STORAGE_KEY, newChainId.toString()); setChainIdState(newChainId); }; return { chainId, setChainId }; } // src/hooks/useGlacier.ts var glacierKeys = { all: ["glacier"], blockchains: () => [...glacierKeys.all, "blockchains"], blockchain: (chainId) => [...glacierKeys.blockchains(), chainId], validators: (subnetId) => [...glacierKeys.all, "validators", subnetId], subnets: () => [...glacierKeys.all, "subnets"], balance: (address, chainId) => [...glacierKeys.all, "balance", address, chainId], erc20Balances: (address, chainId) => [...glacierKeys.all, "erc20Balances", address, chainId], erc721Balances: (address, chainId) => [...glacierKeys.all, "erc721Balances", address, chainId], erc1155Balances: (address, chainId) => [...glacierKeys.all, "erc1155Balances", address, chainId] }; function useGlacier() { var _a; const { wallet } = useAvaCloudWallet(); const { chainId } = useChainId(); const { data: blockchain } = useBlockchain(chainId.toString()); const { data: balance, isLoading: isLoadingBalance } = useQuery({ queryKey: glacierKeys.balance(wallet.address || "", chainId.toString()), queryFn: () => glacierApi.getBalance(wallet.address || "", chainId.toString()), enabled: !!wallet.address, refetchInterval: 3e3 // Refetch every 3 seconds }); return { balance: balance ? (Number.parseInt(balance, 16) / 1e18).toString() : "0", isLoadingBalance, currencySymbol: ((_a = blockchain == null ? void 0 : blockchain.networkToken) == null ? void 0 : _a.symbol) || "AVAX", blockchain }; } function useBlockchain(chainId) { return useQuery({ queryKey: glacierKeys.blockchain(chainId), queryFn: () => glacierApi.getBlockchain(chainId), enabled: !!chainId, staleTime: Number.POSITIVE_INFINITY }); } function useERC20Balances(address, chainId) { return useQuery({ queryKey: glacierKeys.erc20Balances(address || "", chainId || ""), queryFn: () => glacierApi.getERC20Balances(address || "", chainId || ""), enabled: !!address && !!chainId, refetchInterval: 3e3 // Refetch every 3 seconds }); } function useERC721Balances(address, chainId) { return useQuery({ queryKey: glacierKeys.erc721Balances(address || "", chainId || ""), queryFn: () => glacierApi.getERC721Balances(address || "", chainId || ""), enabled: !!address && !!chainId, refetchInterval: 3e3 // Refetch every 3 seconds }); } function useERC1155Balances(address, chainId) { return useQuery({ queryKey: glacierKeys.erc1155Balances(address || "", chainId || ""), queryFn: () => glacierApi.getERC1155Balances(address || "", chainId || ""), enabled: !!address && !!chainId, refetchInterval: 3e3 // Refetch every 3 seconds }); } // src/providers/ThemeProvider.tsx import { ThemeProvider as K2ThemeProvider, createTheme } from "@avalabs/core-k2-components"; import { createContext as createContext3, useContext as useContext3, useState as useState7, useCallback as useCallback5, useEffect as useEffect4 } from "react"; import { jsx as jsx6 } from "react/jsx-runtime"; var ThemeContext = createContext3({ isDarkMode: false, toggleTheme: () => { } }); var useThemeMode = () => useContext3(ThemeContext); var lightTheme = createTheme({ typography: { fontFamily: "Inter, sans-serif" } }); var darkTheme = createTheme({ typography: { fontFamily: "Inter, sans-serif" }, palette: { mode: "dark", background: { default: "#1A1A1A", paper: "#1A1A1A" }, text: { primary: "#FFFFFF", secondary: "rgba(255, 255, 255, 0.7)" }, primary: { main: "#4B9FFF", light: "#73B5FF", dark: "#3B7FCC", contrastText: "#FFFFFF" }, divider: "rgba(255, 255, 255, 0.12)" }, components: { MuiDialog: { styleOverrides: { paper: { backgroundColor: "#1A1A1A" } } } } }); function ThemeProvider({ children, darkMode, onDarkModeChange }) { const [isDarkMode, setIsDarkMode] = useState7(darkMode != null ? darkMode : false); useEffect4(() => { if (darkMode !== void 0 && darkMode !== isDarkMode) { setIsDarkMode(darkMode); } }, [darkMode, isDarkMode]); const toggleTheme = useCallback5(() => { const newDarkMode = !isDarkMode; setIsDarkMode(newDarkMode); onDarkModeChange == null ? void 0 : onDarkModeChange(newDarkMode); }, [isDarkMode, onDarkModeChange]); return /* @__PURE__ */ jsx6(ThemeContext.Provider, { value: { isDarkMode, toggleTheme }, children: /* @__PURE__ */ jsx6(K2ThemeProvider, { theme: isDarkMode ? darkTheme : lightTheme, children }) }); } // src/constants/storage.ts var OIDC_TOKEN_KEY2 = "avacloud-auth-oidc-token"; var AUTH_TOKENS_KEY2 = "auth_tokens"; var AUTH0_STORAGE_KEYS2 = { IS_AUTHENTICATED: "auth0.is.authenticated", ACCESS_TOKEN: "auth0.access_token", ID_TOKEN: "auth0.id_token", EXPIRES_AT: "auth0.expires_at" }; var CUBIST_USER_ID_KEY2 = "cubist_user_id"; var ORG_CONFIG_CACHE_KEY2 = "avacloud-org-config-cache"; // src/AvaCloudWalletProvider.tsx import { Fragment, jsx as jsx7 } from "react/jsx-runtime"; var AvaCloudWalletContext = createContext4(void 0); var queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1e3 * 60 * 5, // 5 minutes retry: 2 } } }); function getCubistEnv(environment) { return environment === "production" ? envs.prod : envs.gamma; } function ViemProviderWrapper({ children, chainId }) { const { data: blockchain } = useBlockchain(chainId.toString()); if (!(blockchain == null ? void 0 : blockchain.rpcUrl)) { return /* @__PURE__ */ jsx7(Fragment, { children }); } return /* @__PURE__ */ jsx7( ViemProvider, { chainId, rpcUrl: blockchain.rpcUrl, explorerUrl: blockchain.explorerUrl, children } ); } function normalizeOrgConfig(config) { if (!config) { return null; } const typedConfig = config; const ex