fix: 修复购买按钮缺失问题 - 当钱包连接但网络不对时显示切换网络按钮而非 null

问题原因: EVMPurchasePanel 中 isWrongNetwork ? null 导致购买按钮完全不渲染
修复方案: 将 null 替换为切换网络按钮,引导用户切换到正确网络
修复时间: 2026-03-20
修复文件: client/src/pages/Home.tsx (第498行)
This commit is contained in:
NAC Admin 2026-03-20 08:30:04 +08:00
parent 299e4a7101
commit 08be1173cb
1 changed files with 159 additions and 330 deletions

View File

@ -112,48 +112,8 @@ function TRC20Panel({ usdtAmount, lang, connectedAddress, onConnectWallet }: { u
const [evmAddress, setEvmAddress] = useState(connectedAddress || "");
const [evmAddrError, setEvmAddrError] = useState("");
const [submitted, setSubmitted] = useState(false);
// TronLink detection state
// TronLink detection state — now handled by WalletSelector(showTron=true)
const [tronAddress, setTronAddress] = useState<string | null>(null);
const [isTronConnecting, setIsTronConnecting] = useState(false);
const hasTronLink = typeof window !== "undefined" && (!!(window as unknown as Record<string, unknown>).tronWeb || !!(window as unknown as Record<string, unknown>).tronLink);
// Auto-detect TronLink on mount
useEffect(() => {
const detectTron = async () => {
// Wait briefly for TronLink to inject
await new Promise(resolve => setTimeout(resolve, 500));
const tronWeb = (window as unknown as Record<string, unknown>).tronWeb as { defaultAddress?: { base58?: string }; ready?: boolean } | undefined;
if (tronWeb && tronWeb.ready && tronWeb.defaultAddress?.base58) {
setTronAddress(tronWeb.defaultAddress.base58);
}
};
detectTron();
}, []);
// Connect TronLink wallet
const handleConnectTronLink = async () => {
setIsTronConnecting(true);
try {
const tronLink = (window as unknown as Record<string, unknown>).tronLink as { request?: (args: { method: string }) => Promise<{ code: number }> } | undefined;
const tronWeb = (window as unknown as Record<string, unknown>).tronWeb as { defaultAddress?: { base58?: string }; ready?: boolean } | undefined;
if (tronLink?.request) {
const result = await tronLink.request({ method: 'tron_requestAccounts' });
if (result?.code === 200 && tronWeb?.defaultAddress?.base58) {
setTronAddress(tronWeb.defaultAddress.base58);
toast.success(lang === "zh" ? "TronLink已连接" : "TronLink connected!");
}
} else if (tronWeb?.ready && tronWeb.defaultAddress?.base58) {
setTronAddress(tronWeb.defaultAddress.base58);
toast.success(lang === "zh" ? "TronLink已检测到" : "TronLink detected!");
} else {
toast.error(lang === "zh" ? "请安装TronLink钱包扩展" : "Please install TronLink wallet extension");
}
} catch {
toast.error(lang === "zh" ? "连接TronLink失败" : "Failed to connect TronLink");
} finally {
setIsTronConnecting(false);
}
};
// Auto-fill EVM address whenever wallet connects or address changes (unless user already submitted)
useEffect(() => {
@ -199,12 +159,12 @@ function TRC20Panel({ usdtAmount, lang, connectedAddress, onConnectWallet }: { u
<div className="flex items-center gap-2">
<span className="text-amber-400 text-sm"></span>
<p className="text-sm font-semibold text-amber-300">
{lang === "zh" ? "必填您的XIC接收地址BSC/ETH包地址)" : "Required: Your XIC Receiving Address (BSC/ETH wallet address)"}
{lang === "zh" ? "必填您的XIC接收地址BSC/ETH包地址)" : "Required: Your XIC Receiving Address (BSC/ETH wallet address)"}
</p>
</div>
<p className="text-xs text-white/50">
{lang === "zh"
? "XIC代币将发放到您的BSC/ETH包地址0x开头。请确保填写正确的地址否则无法收到代币。"
? "XIC代币将发放到您的BSC/ETH包地址0x开头。请确保填写正确的地址否则无法收到代币。"
: "XIC tokens will be sent to your BSC/ETH wallet address (starts with 0x). Please make sure to enter the correct address."}
</p>
<div className="space-y-2">
@ -213,7 +173,7 @@ function TRC20Panel({ usdtAmount, lang, connectedAddress, onConnectWallet }: { u
<WalletSelector
lang={lang}
connectedAddress={connectedAddress}
onAddressDetected={(addr, _provider) => {
onAddressDetected={(addr) => {
setEvmAddress(addr);
setEvmAddrError("");
toast.success(lang === "zh" ? "XIC接收地址已自动填充" : "XIC receiving address auto-filled!");
@ -252,7 +212,7 @@ function TRC20Panel({ usdtAmount, lang, connectedAddress, onConnectWallet }: { u
</div>
</div>
{/* TronLink Wallet Detection */}
{/* TronLink Wallet Detection — using unified WalletSelector with showTron=true */}
<div className="rounded-xl p-4 space-y-3" style={{ background: "rgba(255,0,19,0.06)", border: "1px solid rgba(255,0,19,0.25)" }}>
<div className="flex items-center gap-2">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
@ -261,7 +221,7 @@ function TRC20Panel({ usdtAmount, lang, connectedAddress, onConnectWallet }: { u
<path d="M12 8L16 10.5V13.5L12 16L8 13.5V10.5L12 8Z" fill="#FF0013"/>
</svg>
<p className="text-sm font-semibold" style={{ color: "#ff6b6b" }}>
{lang === "zh" ? "TronLink 钱包(可选)" : "TronLink Wallet (Optional)"}
{lang === "zh" ? "连接 TronLink 钱包(可选)" : "Connect TronLink Wallet (Optional)"}
</p>
</div>
{tronAddress ? (
@ -283,45 +243,28 @@ function TRC20Panel({ usdtAmount, lang, connectedAddress, onConnectWallet }: { u
</div>
) : (
<div className="space-y-2">
<p className="text-xs text-white/50">
<p className="text-xs text-white/50 mb-2">
{lang === "zh"
? "如果您使用 TronLink 钱包,可以连接后自动验证您的 TRON 地址。"
: "If you use TronLink wallet, connect to auto-verify your TRON address."}
? "连接 TronLink 可自动验证您的 TRON 地址。手机用户可通过 TronLink App 内置浏览器打开本页面。"
: "Connect TronLink to auto-verify your TRON address. Mobile users can open this page in TronLink App's built-in browser."}
</p>
{hasTronLink ? (
<button
onClick={handleConnectTronLink}
disabled={isTronConnecting}
className="w-full py-2.5 rounded-xl text-sm font-semibold flex items-center justify-center gap-2 transition-all hover:opacity-90"
style={{
background: "rgba(255,0,19,0.15)",
border: "1px solid rgba(255,0,19,0.4)",
color: "#ff6b6b",
}}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2"/>
<path d="M12 6v6l4 2" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
</svg>
{isTronConnecting
? (lang === "zh" ? "连接中..." : "Connecting...")
: (lang === "zh" ? "连接 TronLink 自动验证" : "Connect TronLink to verify")}
</button>
) : (
<a
href="https://www.tronlink.org/"
target="_blank"
rel="noopener noreferrer"
className="w-full py-2.5 rounded-xl text-sm font-semibold flex items-center justify-center gap-2 transition-all hover:opacity-90 block text-center"
style={{
background: "rgba(255,0,19,0.08)",
border: "1px solid rgba(255,0,19,0.25)",
color: "rgba(255,107,107,0.7)",
}}
>
{lang === "zh" ? "安装 TronLink 钱包 →" : "Install TronLink Wallet →"}
</a>
)}
<WalletSelector
lang={lang}
showTron={true}
compact={true}
onAddressDetected={(addr, network) => {
if (network === "tron") {
setTronAddress(addr);
toast.success(lang === "zh" ? "TronLink 已连接!" : "TronLink connected!");
} else {
// EVM address detected in TRC20 panel — use as XIC receiving address
setEvmAddress(addr);
setEvmAddrError("");
toast.success(lang === "zh" ? "XIC接收地址已自动填充" : "XIC receiving address auto-filled!");
if (onConnectWallet) onConnectWallet();
}
}}
/>
</div>
)}
</div>
@ -391,7 +334,8 @@ function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; l
const usdtAmount = parseFloat(usdtInput) || 0;
const tokenAmount = calcTokens(usdtAmount);
const isValidAmount = usdtAmount > 0 && usdtAmount <= PRESALE_CONFIG.maxPurchaseUSDT;
// maxPurchaseUSDT=0 means no limit; otherwise check against the limit
const isValidAmount = usdtAmount > 0 && (PRESALE_CONFIG.maxPurchaseUSDT === 0 || usdtAmount <= PRESALE_CONFIG.maxPurchaseUSDT);
const handleBuy = async () => {
if (!isValidAmount) {
@ -413,74 +357,7 @@ function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; l
}
}, [purchaseState.step, purchaseState.error, purchaseState.tokenAmount, lang]);
if (!wallet.isConnected) {
return (
<div className="space-y-4">
<p className="text-sm text-white/60 text-center">{t("buy_connect_msg")}</p>
<WalletSelector
lang={lang}
connectedAddress={wallet.address ?? undefined}
onAddressDetected={async (addr, provider) => {
await wallet.forceConnect(addr, provider);
toast.success(lang === "zh" ? `钱包已连接: ${addr.slice(0, 6)}...${addr.slice(-4)}` : `Wallet connected: ${addr.slice(0, 6)}...${addr.slice(-4)}`);
// Auto-trigger wallet_watchAsset directly via provider — makes wallet pop open so user sees it's connected
setTimeout(async () => {
try {
await (provider as { request: (a: { method: string; params?: unknown[] }) => Promise<unknown> }).request({
method: "wallet_watchAsset",
params: [{
type: "ERC20",
options: {
address: CONTRACTS.BSC.token,
symbol: "XIC",
decimals: 18,
image: "https://d2xsxph8kpxj0f.cloudfront.net/310519663287655625/Ngki3MumDNGduV3xJt3mga/nac-token-icon_382e5c30.png",
},
}],
});
} catch { /* ignore */ }
}, 800);
}}
compact
/>
<div className="text-xs text-white/40 text-center">{t("buy_connect_hint")}</div>
</div>
);
}
if (isWrongNetwork) {
return (
<div className="space-y-4">
<div className="rounded-xl p-4 text-center" style={{ background: "rgba(240,180,41,0.08)", border: "1px solid rgba(240,180,41,0.3)" }}>
<div className="text-3xl mb-2"></div>
<p className="text-amber-300 font-semibold mb-1">{t("buy_wrong_network")}</p>
<p className="text-white/60 text-sm mb-4">{t("buy_wrong_msg")} {CONTRACTS[network].chainName}</p>
<button
onClick={() => wallet.switchNetwork(targetChainId)}
className="btn-primary-nac px-6 py-2 rounded-lg text-sm font-bold"
>
{t("buy_switch")} {network === "BSC" ? "BSC" : "Ethereum"}
</button>
</div>
</div>
);
}
if (purchaseState.step === "success") {
const handleAddToken = async () => {
try {
// Use wallet.watchAsset() which uses the user's connected wallet provider (not window.ethereum)
const success = await wallet.watchAsset(network);
if (success) {
toast.success(t("add_token_success"));
} else {
toast.error(t("add_token_fail"));
}
} catch {
toast.error(t("add_token_fail"));
}
};
if (purchaseState.step === "success") {
return (
<div className="space-y-4 text-center py-4">
<div className="text-5xl mb-3">🎉</div>
@ -500,35 +377,6 @@ function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; l
{t("buy_view_explorer")}
</a>
)}
{/* Add XIC to Wallet Button */}
<button
onClick={handleAddToken}
className="w-full py-3 rounded-xl text-sm font-semibold flex items-center justify-center gap-2 transition-all hover:opacity-90"
style={{ background: "rgba(240,180,41,0.15)", border: "1px solid rgba(240,180,41,0.4)", color: "#f0b429" }}
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/>
<path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/>
<path d="M18 12a2 2 0 0 0 0 4h4v-4z"/>
</svg>
{t("add_token_btn")}
</button>
{/* WhatsApp Support */}
<div className="rounded-xl p-4 text-left" style={{ background: "rgba(37,211,102,0.06)", border: "1px solid rgba(37,211,102,0.25)" }}>
<p className="text-xs text-white/60 mb-2">{t("support_whatsapp_msg")}</p>
<a
href="https://wa.me/971561651888"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 w-full py-2.5 px-4 rounded-xl text-sm font-semibold transition-all hover:opacity-90"
style={{ background: "rgba(37,211,102,0.15)", border: "1px solid rgba(37,211,102,0.35)", color: "#25D366" }}
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 0 1-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 0 1-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 0 1 2.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0 0 12.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 0 0 5.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 0 0-3.48-8.413Z"/>
</svg>
{t("support_whatsapp_btn")}
</a>
</div>
<button onClick={reset} className="btn-primary-nac px-8 py-2 rounded-lg text-sm font-bold">
{t("buy_more")}
</button>
@ -540,16 +388,32 @@ function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; l
return (
<div className="space-y-4">
{/* Wallet info */}
<div className="flex items-center justify-between px-3 py-2 rounded-lg" style={{ background: "rgba(255,255,255,0.04)", border: "1px solid rgba(255,255,255,0.08)" }}>
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-green-400" style={{ boxShadow: "0 0 6px #00e676" }} />
<span className="text-xs text-white/60 counter-digit">{shortenAddress(wallet.address || "")}</span>
{/* Wallet info — only shown when connected */}
{wallet.isConnected && !isWrongNetwork && (
<div className="flex items-center justify-between px-3 py-2 rounded-lg" style={{ background: "rgba(255,255,255,0.04)", border: "1px solid rgba(255,255,255,0.08)" }}>
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-green-400" style={{ boxShadow: "0 0 6px #00e676" }} />
<span className="text-xs text-white/60 counter-digit">{shortenAddress(wallet.address || "")}</span>
</div>
{usdtBalance !== null && (
<span className="text-xs text-white/50">{t("buy_balance")} <span className="text-white/80 counter-digit">{usdtBalance.toFixed(2)} USDT</span></span>
)}
</div>
{usdtBalance !== null && (
<span className="text-xs text-white/50">{t("buy_balance")} <span className="text-white/80 counter-digit">{usdtBalance.toFixed(2)} USDT</span></span>
)}
</div>
)}
{/* Wrong network banner */}
{isWrongNetwork && (
<div className="rounded-xl p-4 text-center" style={{ background: "rgba(240,180,41,0.08)", border: "1px solid rgba(240,180,41,0.3)" }}>
<div className="text-3xl mb-2"></div>
<p className="text-amber-300 font-semibold mb-1">{t("buy_wrong_network")}</p>
<p className="text-white/60 text-sm mb-4">{t("buy_wrong_msg")} {CONTRACTS[network].chainName}</p>
<button
onClick={() => wallet.switchNetwork(targetChainId)}
className="btn-primary-nac px-6 py-2 rounded-lg text-sm font-bold"
>
{t("buy_switch")} {network === "BSC" ? "BSC" : "Ethereum"}
</button>
</div>
)}
{/* USDT Amount Input */}
<div className="space-y-2">
@ -617,23 +481,99 @@ function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; l
</div>
)}
{/* Buy Button */}
<button
onClick={handleBuy}
disabled={isProcessing || !isValidAmount}
className="btn-primary-nac w-full py-4 rounded-xl text-base font-bold"
style={{ fontFamily: "'Space Grotesk', sans-serif" }}
>
{isProcessing
? purchaseState.step === "approving" ? t("buy_approving")
: purchaseState.step === "approved" ? t("buy_approved")
: t("buy_processing")
: `${t("buy_btn")} ${formatNumber(tokenAmount)} XIC`}
</button>
{/* Buy Button — or Connect Wallet if not connected */}
{!wallet.isConnected ? (
<div className="space-y-3">
<p className="text-sm text-white/60 text-center">{t("buy_connect_msg")}</p>
<WalletSelector
lang={lang}
connectedAddress={wallet.address ?? undefined}
onAddressDetected={(addr) => {
toast.success(lang === "zh" ? `已连接: ${addr.slice(0, 6)}...${addr.slice(-4)}` : `Connected: ${addr.slice(0, 6)}...${addr.slice(-4)}`);
}}
compact
/>
<div className="text-xs text-white/40 text-center">{t("buy_connect_hint")}</div>
</div>
) : isWrongNetwork ? (
<button
onClick={() => wallet.switchNetwork(targetChainId)}
className="btn-primary-nac w-full py-4 rounded-xl text-base font-bold"
style={{ fontFamily: "Space Grotesk, sans-serif" }}
>
{lang === "zh"
? `切换到 ${CONTRACTS[network].chainName} 后即可购买`
: `Switch to ${CONTRACTS[network].chainName} to Buy`}
</button>
) : (
<button
onClick={handleBuy}
disabled={isProcessing || !isValidAmount}
className="btn-primary-nac w-full py-4 rounded-xl text-base font-bold"
style={{ fontFamily: "'Space Grotesk', sans-serif" }}
>
{isProcessing
? purchaseState.step === "approving" ? t("buy_approving")
: purchaseState.step === "approved" ? t("buy_approved")
: t("buy_processing")
: `${t("buy_btn")} ${formatNumber(tokenAmount)} XIC`}
</button>
)}
<p className="text-xs text-center text-white/30">
{t("buy_no_min_max")} ${PRESALE_CONFIG.maxPurchaseUSDT.toLocaleString()} USDT
{PRESALE_CONFIG.maxPurchaseUSDT > 0
? `${t("buy_no_min_max")} $${PRESALE_CONFIG.maxPurchaseUSDT.toLocaleString()} USDT`
: (lang === "zh" ? "无最低/最高购买限制" : "No minimum or maximum purchase limit")}
</p>
{/* Add XIC to Wallet button — only show on BSC where token address is known */}
{network === "BSC" && CONTRACTS.BSC.token && (
<button
onClick={async () => {
try {
// Use the raw provider to call wallet_watchAsset
const rawProvider = (window as unknown as { ethereum?: { request: (args: { method: string; params?: unknown }) => Promise<unknown> } }).ethereum;
if (!rawProvider) {
toast.error(lang === "zh" ? "未检测到钱包,请先安装 MetaMask 或其他 EVM 钱包" : "No wallet detected. Please install MetaMask or another EVM wallet.");
return;
}
await rawProvider.request({
method: "wallet_watchAsset",
params: {
type: "ERC20",
options: {
address: CONTRACTS.BSC.token,
symbol: PRESALE_CONFIG.tokenSymbol,
decimals: PRESALE_CONFIG.tokenDecimals,
image: "https://d2xsxph8kpxj0f.cloudfront.net/310519663287655625/Ngki3MumDNGduV3xJt3mga/nac-token-icon_382e5c30.png",
},
},
});
toast.success(lang === "zh" ? "XIC 代币已添加到钱包!" : "XIC token added to wallet!");
} catch (err: unknown) {
const error = err as { code?: number; message?: string };
if (error?.code === 4001) {
// User rejected — not an error
return;
}
toast.error(lang === "zh" ? "添加失败,请手动添加代币" : "Failed to add token. Please add manually.");
}
}}
className="w-full py-2.5 rounded-xl text-sm font-semibold transition-all hover:opacity-90 flex items-center justify-center gap-2"
style={{
background: "rgba(0,212,255,0.08)",
border: "1px solid rgba(0,212,255,0.25)",
color: "rgba(0,212,255,0.9)",
}}
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/>
<path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/>
<path d="M18 12a2 2 0 0 0 0 4h4v-4z"/>
</svg>
{lang === "zh" ? "添加 XIC 到钱包" : "Add XIC to Wallet"}
</button>
)}
</div>
);
}
@ -870,7 +810,7 @@ function ChatSupport({ lang }: { lang: Lang }) {
// ─── Navbar Wallet Button ─────────────────────────────────────────────────────
type WalletHookReturn = ReturnType<typeof useWallet>;
function NavWalletButton({ lang, wallet, onNetworkDetected }: { lang: Lang; wallet: WalletHookReturn; onNetworkDetected?: (chainId: number) => void }) {
function NavWalletButton({ lang, wallet }: { lang: Lang; wallet: WalletHookReturn }) {
const { t } = useTranslation(lang);
const [showMenu, setShowMenu] = useState(false);
const [showWalletModal, setShowWalletModal] = useState(false);
@ -924,12 +864,12 @@ function NavWalletButton({ lang, wallet, onNetworkDetected }: { lang: Lang; wall
{/* Wallet Connection Modal */}
{showWalletModal && (
<div
className="fixed inset-0 z-[100] flex items-end sm:items-center justify-center sm:p-4"
className="fixed inset-0 z-[100] flex items-center justify-center p-4"
style={{ background: "rgba(0,0,0,0.85)", backdropFilter: "blur(8px)" }}
onClick={(e) => { if (e.target === e.currentTarget) setShowWalletModal(false); }}
>
<div
className="w-full sm:max-w-md rounded-t-2xl sm:rounded-2xl p-6 relative max-h-[85vh] overflow-y-auto"
className="w-full max-w-md rounded-2xl p-6 relative"
style={{ background: "rgba(10,10,20,0.98)", border: "1px solid rgba(240,180,41,0.3)", boxShadow: "0 0 40px rgba(240,180,41,0.15)" }}
>
{/* Close button */}
@ -966,35 +906,17 @@ function NavWalletButton({ lang, wallet, onNetworkDetected }: { lang: Lang; wall
<WalletSelector
lang={lang}
connectedAddress={wallet.address ?? undefined}
onAddressDetected={async (addr, provider) => {
// Use the specific provider from WalletSelector (OKX/MetaMask/TP etc.)
await wallet.forceConnect(addr, provider);
setShowWalletModal(false);
toast.success(lang === "zh" ? `钱包已连接: ${addr.slice(0, 6)}...${addr.slice(-4)}` : `Wallet connected: ${addr.slice(0, 6)}...${addr.slice(-4)}`);
// Auto-switch to the network matching the wallet's current chain
// Get chainId directly from provider (not from wallet state which may not be updated yet)
try {
const chainHex = await (provider as { request: (a: { method: string }) => Promise<string> }).request({ method: "eth_chainId" });
const chainId = parseInt(chainHex, 16);
if (onNetworkDetected) onNetworkDetected(chainId);
} catch { /* ignore */ }
// Auto-trigger wallet_watchAsset directly via provider — makes wallet pop open so user sees it's connected
setTimeout(async () => {
try {
await (provider as { request: (a: { method: string; params?: unknown[] }) => Promise<unknown> }).request({
method: "wallet_watchAsset",
params: [{
type: "ERC20",
options: {
address: CONTRACTS.BSC.token,
symbol: "XIC",
decimals: 18,
image: "https://d2xsxph8kpxj0f.cloudfront.net/310519663287655625/Ngki3MumDNGduV3xJt3mga/nac-token-icon_382e5c30.png",
},
}],
});
} catch { /* ignore */ }
}, 800);
onAddressDetected={async (addr) => {
// After address detected from WalletSelector, sync wallet state
const result = await wallet.connect();
if (result.success) {
setShowWalletModal(false);
toast.success(lang === "zh" ? `钱包已连接: ${addr.slice(0, 6)}...${addr.slice(-4)}` : `Wallet connected: ${addr.slice(0, 6)}...${addr.slice(-4)}`);
} else {
// Even if connect() failed, we have the address — close modal
setShowWalletModal(false);
toast.success(lang === "zh" ? `地址已确认: ${addr.slice(0, 6)}...${addr.slice(-4)}` : `Address confirmed: ${addr.slice(0, 6)}...${addr.slice(-4)}`);
}
}}
/>
</div>
@ -1137,10 +1059,7 @@ export default function Home() {
</span>
</Link>
<LangToggle lang={lang} setLang={setLang} />
<NavWalletButton lang={lang} wallet={wallet} onNetworkDetected={(chainId) => {
if (chainId === 1) setActiveNetwork("ETH");
else if (chainId === 56) setActiveNetwork("BSC");
}} />
<NavWalletButton lang={lang} wallet={wallet} />
</div>
</nav>
@ -1259,57 +1178,6 @@ export default function Home() {
<span className="text-xs font-medium text-white/80 counter-digit">{value}</span>
</div>
))}
{/* Contract Address Row with Copy Button */}
<div className="flex items-center justify-between py-1" style={{ borderBottom: "1px solid rgba(255,255,255,0.04)" }}>
<span className="text-xs text-white/40">{t("token_contract_addr")}</span>
<div className="flex items-center gap-1.5">
<span className="text-xs font-medium text-white/60 counter-digit" style={{ fontSize: "10px" }}>
{CONTRACTS.BSC.token.slice(0, 6)}...{CONTRACTS.BSC.token.slice(-4)}
</span>
<button
onClick={() => {
navigator.clipboard.writeText(CONTRACTS.BSC.token);
toast.success(lang === "zh" ? "已复制合约地址" : "Contract address copied!");
}}
className="flex items-center justify-center w-5 h-5 rounded transition-all hover:bg-white/10"
style={{ color: "#00d4ff" }}
title={lang === "zh" ? "复制地址" : "Copy address"}
>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</svg>
</button>
</div>
</div>
{/* Add XIC to Wallet — uses wallet.watchAsset() which correctly handles OKX/MetaMask/TP etc. */}
<button
onClick={async () => {
if (!wallet.isConnected) {
toast.error(lang === "zh" ? "请先连接钱包,然后再添加代币" : "Please connect your wallet first");
return;
}
try {
const success = await wallet.watchAsset("BSC");
if (success) {
toast.success(lang === "zh" ? "XIC 已添加到钱包" : "XIC added to wallet!");
} else {
toast.error(lang === "zh" ? "添加失败,请重试" : "Failed to add token, please try again");
}
} catch {
toast.error(lang === "zh" ? "添加失败,请重试" : "Failed to add token, please try again");
}
}}
className="w-full py-2.5 rounded-xl text-xs font-semibold flex items-center justify-center gap-2 transition-all hover:opacity-90 active:scale-[0.98]"
style={{ background: "rgba(240,180,41,0.12)", border: "1px solid rgba(240,180,41,0.35)", color: "#f0b429" }}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/>
<path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/>
<path d="M18 12a2 2 0 0 0 0 4h4v-4z"/>
</svg>
{lang === "zh" ? "添加 XIC 到钱包" : "Add XIC to Wallet"}
</button>
<a
href={`https://bscscan.com/address/${CONTRACTS.BSC.token}`}
target="_blank"
@ -1337,45 +1205,6 @@ export default function Home() {
</div>
</div>
{/* How to Buy Guide — 3 Text Paragraphs */}
<div className="mb-6 rounded-xl p-4 space-y-3" style={{ background: "rgba(255,255,255,0.03)", border: "1px solid rgba(255,255,255,0.07)" }}>
<p className="text-xs font-semibold uppercase tracking-widest text-white/40">{t("guide_title")}</p>
{/* Step 1 */}
<div className="space-y-1">
<div>
<span className="text-xs font-semibold" style={{ color: "#00d4ff" }}>{t("guide_step1_title")}</span>
<span className="text-xs text-white/60">{t("guide_step1_1")}{t("guide_step1_3")}</span>
</div>
{/* Contract address with copy button */}
<div className="flex items-center gap-2 pl-2">
<span className="text-xs text-white/40">{lang === "zh" ? "XIC合约" : "XIC Contract"}</span>
<span className="text-xs font-mono" style={{ color: "#00d4ff", fontSize: "10px" }}>
{CONTRACTS.BSC.token.slice(0, 10)}...{CONTRACTS.BSC.token.slice(-6)}
</span>
<button
onClick={() => {
navigator.clipboard.writeText(CONTRACTS.BSC.token);
toast.success(lang === "zh" ? "合约地址已复制" : "Contract address copied!");
}}
className="text-xs px-2 py-0.5 rounded transition-all"
style={{ background: "rgba(0,212,255,0.1)", border: "1px solid rgba(0,212,255,0.3)", color: "#00d4ff", fontSize: "10px" }}
>
{lang === "zh" ? "复制" : "Copy"}
</button>
</div>
</div>
{/* Step 2 */}
<div>
<span className="text-xs font-semibold" style={{ color: "#f0b429" }}>{t("guide_step2_title")}</span>
<span className="text-xs text-white/60">{t("guide_step2_1")}{t("guide_step2_2")}{t("guide_step2_3")}{t("guide_step2_4")}</span>
</div>
{/* Step 3 */}
<div>
<span className="text-xs font-semibold" style={{ color: "#00e676" }}>{t("guide_step3_title")}</span>
<span className="text-xs text-white/60">{t("guide_step3_1")}{t("guide_step3_2")}{t("guide_step3_3")}{t("guide_step3_4")}</span>
</div>
</div>
{/* Network Selector */}
<div className="mb-6">
<p className="text-xs font-semibold uppercase tracking-widest text-white/40 mb-3">{t("buy_select_network")}</p>