// NAC XIC Presale — Wallet Selector Component // Detects installed EVM wallets and shows connect/install buttons for each // v3: added mobile detection, DeepLink support for MetaMask/Trust/OKX App import { useState, useEffect, useCallback } from "react"; type Lang = "zh" | "en"; interface WalletInfo { id: string; name: string; icon: React.ReactNode; installUrl: string; mobileDeepLink?: string; // DeepLink to open current page in wallet's in-app browser isInstalled: () => boolean; connect: () => Promise; } interface WalletSelectorProps { lang: Lang; onAddressDetected: (address: string) => void; connectedAddress?: string; compact?: boolean; // compact mode for BSC/ETH panel } // ── Wallet Icons ────────────────────────────────────────────────────────────── const MetaMaskIcon = () => ( ); const TrustWalletIcon = () => ( ); const OKXIcon = () => ( ); const CoinbaseIcon = () => ( ); const TokenPocketIcon = () => ( ); const BitgetIcon = () => ( ); // ── Mobile detection ────────────────────────────────────────────────────────── function isMobileBrowser(): boolean { if (typeof window === "undefined") return false; return /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); } // Check if running inside a wallet's in-app browser function isInWalletBrowser(): boolean { if (typeof window === "undefined") return false; const ua = navigator.userAgent.toLowerCase(); const w = window as unknown as Record; const eth = w.ethereum as { isMetaMask?: boolean; isTrust?: boolean; isTrustWallet?: boolean; isOKExWallet?: boolean; isOkxWallet?: boolean } | undefined; return !!( eth?.isMetaMask || eth?.isTrust || eth?.isTrustWallet || eth?.isOKExWallet || eth?.isOkxWallet || ua.includes("metamask") || ua.includes("trust") || ua.includes("okex") || ua.includes("tokenpocket") || ua.includes("bitkeep") ); } // Build DeepLink URL for opening current page in wallet's in-app browser function buildDeepLink(walletScheme: string): string { const currentUrl = typeof window !== "undefined" ? window.location.href : "https://pre-sale.newassetchain.io"; // Remove protocol from URL for deeplink const urlWithoutProtocol = currentUrl.replace(/^https?:\/\//, ""); return `${walletScheme}${urlWithoutProtocol}`; } // ── Provider detection helpers ──────────────────────────────────────────────── type EthProvider = { isMetaMask?: boolean; isTrust?: boolean; isTrustWallet?: boolean; isOKExWallet?: boolean; isOkxWallet?: boolean; isCoinbaseWallet?: boolean; isTokenPocket?: boolean; isBitkeep?: boolean; isBitgetWallet?: boolean; providers?: EthProvider[]; request: (args: { method: string; params?: unknown[] }) => Promise; }; function getEth(): EthProvider | null { if (typeof window === "undefined") return null; return (window as unknown as { ethereum?: EthProvider }).ethereum ?? null; } function getOKX(): EthProvider | null { if (typeof window === "undefined") return null; return (window as unknown as { okxwallet?: EthProvider }).okxwallet ?? null; } function getBitget(): EthProvider | null { if (typeof window === "undefined") return null; const w = window as unknown as { bitkeep?: { ethereum?: EthProvider } }; return w.bitkeep?.ethereum ?? null; } // Find a specific provider from the providers array or direct injection function findProvider(predicate: (p: EthProvider) => boolean): EthProvider | null { const eth = getEth(); if (!eth) return null; if (eth.providers && Array.isArray(eth.providers)) { return eth.providers.find(predicate) ?? null; } return predicate(eth) ? eth : null; } async function requestAccounts(provider: EthProvider): Promise { try { const accounts = await provider.request({ method: "eth_requestAccounts" }) as string[]; return accounts?.[0] ?? null; } catch (err: unknown) { const error = err as { code?: number; message?: string }; // User rejected if (error?.code === 4001) throw new Error("user_rejected"); // MetaMask not initialized / locked if (error?.code === -32002) throw new Error("wallet_pending"); throw err; } } // ── Wallet definitions ──────────────────────────────────────────────────────── function buildWallets(): WalletInfo[] { return [ { id: "metamask", name: "MetaMask", icon: , installUrl: "https://metamask.io/download/", mobileDeepLink: buildDeepLink("https://metamask.app.link/dapp/"), isInstalled: () => !!findProvider(p => !!p.isMetaMask), connect: async () => { const p = findProvider(p => !!p.isMetaMask) ?? getEth(); return p ? requestAccounts(p) : null; }, }, { id: "trust", name: "Trust Wallet", icon: , installUrl: "https://trustwallet.com/download", mobileDeepLink: buildDeepLink("https://link.trustwallet.com/open_url?coin_id=60&url=https://"), isInstalled: () => !!findProvider(p => !!(p.isTrust || p.isTrustWallet)), connect: async () => { const p = findProvider(p => !!(p.isTrust || p.isTrustWallet)) ?? getEth(); return p ? requestAccounts(p) : null; }, }, { id: "okx", name: "OKX Wallet", icon: , installUrl: "https://www.okx.com/web3", mobileDeepLink: buildDeepLink("okx://wallet/dapp/url?dappUrl=https://"), isInstalled: () => !!(getOKX() || findProvider(p => !!(p.isOKExWallet || p.isOkxWallet))), connect: async () => { const p = getOKX() ?? findProvider(p => !!(p.isOKExWallet || p.isOkxWallet)); return p ? requestAccounts(p) : null; }, }, { id: "coinbase", name: "Coinbase Wallet", icon: , installUrl: "https://www.coinbase.com/wallet/downloads", isInstalled: () => !!findProvider(p => !!p.isCoinbaseWallet), connect: async () => { const p = findProvider(p => !!p.isCoinbaseWallet) ?? getEth(); return p ? requestAccounts(p) : null; }, }, { id: "tokenpocket", name: "TokenPocket", icon: , installUrl: "https://www.tokenpocket.pro/en/download/app", isInstalled: () => !!findProvider(p => !!p.isTokenPocket), connect: async () => { const p = findProvider(p => !!p.isTokenPocket) ?? getEth(); return p ? requestAccounts(p) : null; }, }, { id: "bitget", name: "Bitget Wallet", icon: , installUrl: "https://web3.bitget.com/en/wallet-download", isInstalled: () => !!(getBitget() || findProvider(p => !!(p.isBitkeep || p.isBitgetWallet))), connect: async () => { const p = getBitget() ?? findProvider(p => !!(p.isBitkeep || p.isBitgetWallet)); return p ? requestAccounts(p) : null; }, }, ]; } // Validate Ethereum address format function isValidEthAddress(addr: string): boolean { return /^0x[0-9a-fA-F]{40}$/.test(addr); } // ── Mobile DeepLink Panel ───────────────────────────────────────────────────── function MobileDeepLinkPanel({ lang }: { lang: Lang }) { const currentUrl = typeof window !== "undefined" ? window.location.href : "https://pre-sale.newassetchain.io"; const urlWithoutProtocol = currentUrl.replace(/^https?:\/\//, ""); const mobileWallets = [ { id: "metamask", name: "MetaMask", icon: , deepLink: `https://metamask.app.link/dapp/${urlWithoutProtocol}`, installUrl: "https://metamask.io/download/", color: "#E27625", }, { id: "trust", name: "Trust Wallet", icon: , deepLink: `https://link.trustwallet.com/open_url?coin_id=60&url=${encodeURIComponent(currentUrl)}`, installUrl: "https://trustwallet.com/download", color: "#3375BB", }, { id: "okx", name: "OKX Wallet", icon: , deepLink: `okx://wallet/dapp/url?dappUrl=${encodeURIComponent(currentUrl)}`, installUrl: "https://www.okx.com/web3", color: "#00F0FF", }, { id: "tokenpocket", name: "TokenPocket", icon: , deepLink: `tpoutside://pull?param=${encodeURIComponent(JSON.stringify({ url: currentUrl }))}`, installUrl: "https://www.tokenpocket.pro/en/download/app", color: "#2980FE", }, ]; return (
{/* Mobile guidance header */}
📱

{lang === "zh" ? "手机端连接钱包" : "Connect Wallet on Mobile"}

{lang === "zh" ? "手机浏览器不支持钱包扩展。请选择以下任一钱包 App,在其内置浏览器中打开本页面即可连接钱包。" : "Mobile browsers don't support wallet extensions. Open this page in a wallet app's built-in browser to connect."}

{/* Wallet DeepLink buttons */}

{lang === "zh" ? "选择钱包 App 打开本页面" : "Choose a wallet app to open this page"}

{mobileWallets.map(wallet => ( {wallet.icon} {wallet.name} {lang === "zh" ? "在 App 中打开" : "Open in App"} ))}
{/* Step guide */}

{lang === "zh" ? "操作步骤" : "How it works"}

{[ lang === "zh" ? "1. 点击上方任一钱包 App 按钮" : "1. Tap any wallet app button above", lang === "zh" ? "2. 在钱包 App 的内置浏览器中打开本页面" : "2. Page opens in the wallet app's browser", lang === "zh" ? "3. 点击「连接钱包」即可自动连接" : "3. Tap 'Connect Wallet' to connect automatically", ].map((step, i) => (

{step}

))}
); } // ── WalletSelector Component ────────────────────────────────────────────────── export function WalletSelector({ lang, onAddressDetected, connectedAddress, compact = false }: WalletSelectorProps) { const [wallets, setWallets] = useState([]); const [connecting, setConnecting] = useState(null); const [error, setError] = useState(null); const [detecting, setDetecting] = useState(true); const [showManual, setShowManual] = useState(false); const [manualAddress, setManualAddress] = useState(""); const [manualError, setManualError] = useState(null); const [isMobile] = useState(() => isMobileBrowser()); const [inWalletBrowser] = useState(() => isInWalletBrowser()); const detectWallets = useCallback(() => { setDetecting(true); setError(null); // Wait for wallet extensions to fully inject (up to 1500ms) const timer = setTimeout(() => { setWallets(buildWallets()); setDetecting(false); }, 1500); return () => clearTimeout(timer); }, []); useEffect(() => { const cleanup = detectWallets(); return cleanup; }, [detectWallets]); const handleConnect = async (wallet: WalletInfo) => { setConnecting(wallet.id); setError(null); try { const address = await wallet.connect(); if (address) { onAddressDetected(address); } else { setError(lang === "zh" ? "未获取到地址,请重试" : "No address returned, please try again"); } } catch (err: unknown) { const error = err as Error; if (error.message === "user_rejected") { setError(lang === "zh" ? "已取消连接" : "Connection cancelled"); } else if (error.message === "wallet_pending") { setError(lang === "zh" ? "钱包请求处理中,请检查钱包弹窗" : "Wallet request pending, please check your wallet popup"); } else if (error.message?.includes("not initialized") || error.message?.includes("setup")) { setError(lang === "zh" ? "请先完成钱包初始化设置,然后刷新页面重试" : "Please complete wallet setup first, then refresh the page"); } else { setError(lang === "zh" ? "连接失败,请重试" : "Connection failed, please try again"); } } finally { setConnecting(null); } }; const handleManualSubmit = () => { const addr = manualAddress.trim(); if (!addr) { setManualError(lang === "zh" ? "请输入钱包地址" : "Please enter wallet address"); return; } if (!isValidEthAddress(addr)) { setManualError(lang === "zh" ? "地址格式无效,请输入正确的以太坊地址(0x开头,42位)" : "Invalid address format. Must be 0x followed by 40 hex characters"); return; } setManualError(null); onAddressDetected(addr); }; const installedWallets = wallets.filter(w => w.isInstalled()); const notInstalledWallets = wallets.filter(w => !w.isInstalled()); // If connected address is already set, show compact confirmation if (connectedAddress) { return (

{lang === "zh" ? "钱包已连接" : "Wallet Connected"}

{connectedAddress}

); } // ── Mobile browser (not in wallet app) — show DeepLink guide ────────────── if (isMobile && !inWalletBrowser && !detecting) { const hasInstalledWallet = installedWallets.length > 0; if (!hasInstalledWallet) { return (
{/* Manual address fallback */}
{showManual && (

{lang === "zh" ? "直接输入您的 EVM 钱包地址(0x 开头)" : "Enter your EVM wallet address (starts with 0x)"}

{ setManualAddress(e.target.value); setManualError(null); }} placeholder={lang === "zh" ? "0x..." : "0x..."} className="flex-1 px-3 py-2 rounded-lg text-xs font-mono text-white/80 outline-none focus:ring-1" style={{ background: "rgba(255,255,255,0.06)", border: manualError ? "1px solid rgba(255,80,80,0.5)" : "1px solid rgba(255,255,255,0.12)", }} onKeyDown={e => e.key === "Enter" && handleManualSubmit()} />
{manualError && (

{manualError}

)}
)}
); } } // ── Loading state ───────────────────────────────────────────────────────── if (detecting) { return (

{lang === "zh" ? "选择钱包自动填充地址" : "Select wallet to auto-fill address"}

{lang === "zh" ? "正在检测钱包..." : "Detecting wallets..."}
); } return (

{lang === "zh" ? "选择钱包自动填充地址" : "Select wallet to auto-fill address"}

{/* Refresh detection button */}
{/* Installed wallets */} {installedWallets.length > 0 && (
{installedWallets.map(wallet => ( ))}
)} {/* No wallets installed — desktop */} {installedWallets.length === 0 && (

{lang === "zh" ? "未检测到 EVM 钱包" : "No EVM wallet detected"}

{lang === "zh" ? "请安装以下任一钱包,完成设置后点击上方「刷新」按钮" : "Install any wallet below, then click Refresh above after setup"}

{lang === "zh" ? "💡 已安装MetaMask?请先完成钱包初始化(创建或导入钱包),再点击刷新" : "💡 Have MetaMask? Complete wallet setup (create or import) first, then click Refresh"}

)} {/* Not-installed wallets — show install links */} {!compact && notInstalledWallets.length > 0 && (

{lang === "zh" ? "未安装(点击安装)" : "Not installed (click to install)"}

{notInstalledWallets.map(wallet => ( {wallet.icon} {wallet.name} ))}
)} {/* In compact mode, show install links inline */} {compact && notInstalledWallets.length > 0 && installedWallets.length === 0 && ( )} {error && (

{error}

)} {/* Manual address input — divider */}
{showManual && (

{lang === "zh" ? "直接输入您的 EVM 钱包地址(0x 开头)" : "Enter your EVM wallet address (starts with 0x)"}

{ setManualAddress(e.target.value); setManualError(null); }} placeholder={lang === "zh" ? "0x..." : "0x..."} className="flex-1 px-3 py-2 rounded-lg text-xs font-mono text-white/80 outline-none focus:ring-1" style={{ background: "rgba(255,255,255,0.06)", border: manualError ? "1px solid rgba(255,80,80,0.5)" : "1px solid rgba(255,255,255,0.12)", }} onKeyDown={e => e.key === "Enter" && handleManualSubmit()} />
{manualError && (

{manualError}

)}
)}
); }