-
Notifications
You must be signed in to change notification settings - Fork 3
[B3-2843] [WIP] TK EOA Auth #407
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,37 @@ | ||
| import React, { useEffect, useRef, useState } from "react"; | ||
| import { useTurnkeyAuth } from "../hooks/useTurnkeyAuth"; | ||
|
|
||
| type ModalStep = "email" | "otp" | "success"; | ||
| type ModalStep = "method" | "email" | "wallet" | "otp" | "success"; | ||
|
|
||
| interface WalletProvider { | ||
| name: string; | ||
| icon?: string; | ||
| address: string; | ||
| chain: string; | ||
| } | ||
|
|
||
| interface TurnkeyAuthModalProps { | ||
| onClose: () => void; | ||
| onSuccess: (_user: any) => void; | ||
| initialEmail?: string; | ||
| skipToOtp?: boolean; | ||
| enableWalletAuth?: boolean; // New prop to enable wallet authentication | ||
| } | ||
|
|
||
| export function TurnkeyAuthModal({ onClose, onSuccess, initialEmail = "", skipToOtp = false }: TurnkeyAuthModalProps) { | ||
| const [step, setStep] = useState<ModalStep>(skipToOtp ? "otp" : "email"); | ||
| export function TurnkeyAuthModal({ | ||
| onClose, | ||
| onSuccess, | ||
| initialEmail = "", | ||
| skipToOtp = false, | ||
| enableWalletAuth = false, | ||
| }: TurnkeyAuthModalProps) { | ||
| const [step, setStep] = useState<ModalStep>(skipToOtp ? "otp" : enableWalletAuth ? "method" : "email"); | ||
| const [email, setEmail] = useState(initialEmail); | ||
| const [otpCode, setOtpCode] = useState(""); | ||
| const [otpId, setOtpId] = useState(""); | ||
| const [walletProviders, setWalletProviders] = useState<WalletProvider[]>([]); | ||
| const [selectedWallet, setSelectedWallet] = useState<WalletProvider | null>(null); | ||
| const [walletError, setWalletError] = useState<string | null>(null); | ||
| const autoSubmitTriggeredRef = useRef(false); | ||
|
|
||
| const { initiateLogin, verifyOtp, isLoading, error, clearError } = useTurnkeyAuth(); | ||
|
|
@@ -94,11 +111,158 @@ export function TurnkeyAuthModal({ onClose, onSuccess, initialEmail = "", skipTo | |
| } | ||
| }; | ||
|
|
||
| // Fetch available wallet providers when wallet step is shown | ||
| const fetchWallets = async () => { | ||
| try { | ||
| setWalletError(null); | ||
| // Check if user has MetaMask or other injected wallets | ||
| if (typeof window !== "undefined" && (window as any).ethereum) { | ||
| const ethereum = (window as any).ethereum; | ||
| const accounts = await ethereum.request({ method: "eth_requestAccounts" }); | ||
|
|
||
| if (accounts && accounts.length > 0) { | ||
| setWalletProviders([ | ||
| { | ||
| name: ethereum.isMetaMask ? "MetaMask" : "Ethereum Wallet", | ||
| address: accounts[0], | ||
| chain: "ethereum", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| }, | ||
| ]); | ||
| } else { | ||
| setWalletError("No wallet accounts found. Please connect your wallet."); | ||
| } | ||
| } else { | ||
| setWalletError("No Ethereum wallet detected. Please install MetaMask or another Web3 wallet."); | ||
| } | ||
| } catch (err: any) { | ||
| console.error("Failed to fetch wallets:", err); | ||
| setWalletError(err.message || "Failed to connect to wallet"); | ||
| } | ||
| }; | ||
|
|
||
| // Handle wallet authentication | ||
| const handleWalletAuth = async (provider: WalletProvider) => { | ||
| try { | ||
| setWalletError(null); | ||
| setSelectedWallet(provider); | ||
|
|
||
| // For now, we'll need to implement the actual Turnkey wallet auth | ||
| // This would involve signing a message with the wallet and sending it to the backend | ||
| // TODO: Implement full wallet authentication flow with Turnkey | ||
| setWalletError("Wallet authentication coming soon. Please use email authentication for now."); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Given this is a |
||
| } catch (err: any) { | ||
| console.error("Failed to authenticate with wallet:", err); | ||
| setWalletError(err.message || "Failed to authenticate with wallet"); | ||
| } | ||
| }; | ||
|
|
||
| const isTurnkeyPrimary = process.env.NEXT_PUBLIC_TURNKEY_PRIMARY === "true"; | ||
| const walletBrand = isTurnkeyPrimary ? "Smart Wallet" : "AnySpend Wallet"; | ||
|
|
||
| return ( | ||
| <div className="font-neue-montreal p-8"> | ||
| {/* Method Selection Step */} | ||
| {step === "method" && ( | ||
| <> | ||
| <h2 className="mb-6 text-center text-2xl font-bold text-gray-900 dark:text-white"> | ||
| Setup your {walletBrand} | ||
| </h2> | ||
| <div className="mb-6 space-y-3 text-center text-sm text-gray-600 dark:text-gray-400"> | ||
| <p>Choose your authentication method</p> | ||
| </div> | ||
|
|
||
| <div className="space-y-3"> | ||
| <button | ||
| onClick={() => setStep("email")} | ||
| className="w-full rounded-lg border-2 border-gray-300 bg-white px-6 py-4 font-semibold text-gray-900 transition-all duration-200 hover:border-blue-500 hover:bg-blue-50 dark:border-gray-700 dark:bg-gray-800 dark:text-white dark:hover:border-blue-500 dark:hover:bg-gray-700" | ||
| > | ||
| <div className="flex items-center justify-center gap-3"> | ||
| <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | ||
| <path | ||
| strokeLinecap="round" | ||
| strokeLinejoin="round" | ||
| strokeWidth={2} | ||
| d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" | ||
| /> | ||
| </svg> | ||
| Email + OTP | ||
| </div> | ||
| </button> | ||
|
|
||
| <button | ||
| onClick={() => { | ||
| setStep("wallet"); | ||
| fetchWallets(); | ||
| }} | ||
| className="w-full rounded-lg border-2 border-gray-300 bg-white px-6 py-4 font-semibold text-gray-900 transition-all duration-200 hover:border-blue-500 hover:bg-blue-50 dark:border-gray-700 dark:bg-gray-800 dark:text-white dark:hover:border-blue-500 dark:hover:bg-gray-700" | ||
| > | ||
| <div className="flex items-center justify-center gap-3"> | ||
| <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | ||
| <path | ||
| strokeLinecap="round" | ||
| strokeLinejoin="round" | ||
| strokeWidth={2} | ||
| d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z" | ||
| /> | ||
| </svg> | ||
| External Wallet | ||
| </div> | ||
| </button> | ||
| </div> | ||
| </> | ||
| )} | ||
|
|
||
| {/* Wallet Selection Step */} | ||
| {step === "wallet" && ( | ||
| <> | ||
| <h2 className="mb-6 text-center text-2xl font-bold text-gray-900 dark:text-white">Connect Wallet</h2> | ||
| <div className="mb-6 space-y-3 text-center text-sm text-gray-600 dark:text-gray-400"> | ||
| <p>Select a wallet to authenticate</p> | ||
| </div> | ||
|
|
||
| {walletError && ( | ||
| <div className="mb-4 rounded-md bg-red-50 p-3 text-sm text-red-800 dark:bg-red-900/20 dark:text-red-400"> | ||
| {walletError} | ||
| </div> | ||
| )} | ||
|
|
||
| <div className="space-y-3"> | ||
| {walletProviders.length > 0 ? ( | ||
| walletProviders.map((provider, index) => ( | ||
| <button | ||
| key={index} | ||
| onClick={() => handleWalletAuth(provider)} | ||
| className="w-full rounded-lg border-2 border-gray-300 bg-white px-6 py-4 font-semibold text-gray-900 transition-all duration-200 hover:border-blue-500 hover:bg-blue-50 dark:border-gray-700 dark:bg-gray-800 dark:text-white dark:hover:border-blue-500 dark:hover:bg-gray-700" | ||
| > | ||
| <div className="flex items-center justify-between"> | ||
| <div className="flex items-center gap-3"> | ||
| {provider.icon && <img src={provider.icon} alt={provider.name} className="h-8 w-8" />} | ||
| <div className="text-left"> | ||
| <div className="font-semibold">{provider.name}</div> | ||
| <div className="text-xs text-gray-500 dark:text-gray-400"> | ||
| {provider.address.slice(0, 6)}...{provider.address.slice(-4)} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </button> | ||
| )) | ||
| ) : ( | ||
| <div className="flex min-h-[200px] items-center justify-center"> | ||
| <div className="h-8 w-8 animate-spin rounded-full border-4 border-blue-600 border-t-transparent"></div> | ||
| </div> | ||
| )} | ||
| </div> | ||
|
|
||
| <button | ||
| onClick={() => setStep("method")} | ||
| className="mt-4 w-full text-sm text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200" | ||
| > | ||
| ← Back to methods | ||
| </button> | ||
| </> | ||
| )} | ||
|
|
||
| {/* Email Step */} | ||
| {step === "email" && ( | ||
| <> | ||
|
|
@@ -152,6 +316,15 @@ export function TurnkeyAuthModal({ onClose, onSuccess, initialEmail = "", skipTo | |
| )} | ||
| </button> | ||
| </form> | ||
|
|
||
| {enableWalletAuth && ( | ||
| <button | ||
| onClick={() => setStep("method")} | ||
| className="mt-4 w-full text-sm text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200" | ||
| > | ||
| ← Back to methods | ||
| </button> | ||
| )} | ||
| </> | ||
| )} | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Directly accessing
window.ethereumcan be less robust than usingthirdweb's wallet connectors, which are already integrated into the SDK. Leveragingthirdweb's utilities for wallet detection and connection would provide a more consistent and resilient experience, handling various wallet injection scenarios and potential edge cases more gracefully.