Checkpoint: NAC XIC Token Presale v1.0 — 完整实现:BSC/ETH USDT 购买(ethers.js v6 + MetaMask)、TRC20 手动转账、暗黑科技设计、倒计时、进度条、合约验证链接。TypeScript 零错误。

This commit is contained in:
Manus 2026-03-07 20:41:27 -05:00
parent f871d3b777
commit f8b007a9eb
11 changed files with 4918 additions and 140 deletions

0
.gitkeep Normal file
View File

View File

@ -1,19 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<title>NAC XIC Token Presale</title>
<!-- THIS IS THE START OF A COMMENT BLOCK, BLOCK TO BE DELETED: Google Fonts here, example:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<title>XIC Token Presale — New AssetChain</title>
<meta name="description" content="Join the New AssetChain (NAC) XIC Token Presale. Buy XIC at $0.02 with USDT on BSC, ETH, or TRC20. The next-generation RWA blockchain." />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
THIS IS THE END OF A COMMENT BLOCK, BLOCK TO BE DELETED -->
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
@ -22,5 +17,4 @@
src="%VITE_ANALYTICS_ENDPOINT%/umami"
data-website-id="%VITE_ANALYTICS_WEBSITE_ID%"></script>
</body>
</html>

View File

@ -6,30 +6,20 @@ import ErrorBoundary from "./components/ErrorBoundary";
import { ThemeProvider } from "./contexts/ThemeContext";
import Home from "./pages/Home";
function Router() {
return (
<Switch>
<Route path={"/"} component={Home} />
<Route path={"/404"} component={NotFound} />
{/* Final fallback route */}
<Route component={NotFound} />
</Switch>
);
}
// NOTE: About Theme
// - First choose a default theme according to your design style (dark or light bg), than change color palette in index.css
// to keep consistent foreground/background color across components
// - If you want to make theme switchable, pass `switchable` ThemeProvider and use `useTheme` hook
function App() {
return (
<ErrorBoundary>
<ThemeProvider
defaultTheme="light"
// switchable
>
<ThemeProvider defaultTheme="dark">
<TooltipProvider>
<Toaster />
<Router />

View File

@ -0,0 +1,110 @@
// NAC XIC Presale — Purchase Logic Hook
// Handles BSC USDT, ETH USDT purchase flows
import { useState, useCallback } from "react";
import { Contract, parseUnits, formatUnits } from "ethers";
import { CONTRACTS, PRESALE_ABI, ERC20_ABI, PRESALE_CONFIG } from "@/lib/contracts";
import { WalletState } from "./useWallet";
export type PurchaseStep =
| "idle"
| "approving"
| "approved"
| "purchasing"
| "success"
| "error";
// All 6 steps are valid
export interface PurchaseState {
step: PurchaseStep;
txHash: string | null;
error: string | null;
tokenAmount: number;
}
export function usePresale(wallet: WalletState, network: "BSC" | "ETH") {
const [purchaseState, setPurchaseState] = useState<PurchaseState>({
step: "idle",
txHash: null,
error: null,
tokenAmount: 0,
});
const networkConfig = CONTRACTS[network];
const buyWithUSDT = useCallback(
async (usdtAmount: number) => {
if (!wallet.signer || !wallet.address) {
setPurchaseState(s => ({ ...s, step: "error", error: "Please connect your wallet first." }));
return;
}
const tokenAmount = usdtAmount / PRESALE_CONFIG.tokenPrice;
setPurchaseState({ step: "approving", txHash: null, error: null, tokenAmount });
try {
// USDT on BSC has 18 decimals, on ETH has 6 decimals
const usdtDecimals = network === "ETH" ? 6 : 18;
const usdtAmountWei = parseUnits(usdtAmount.toString(), usdtDecimals);
// Step 1: Approve USDT spending
const usdtContract = new Contract(networkConfig.usdt, ERC20_ABI, wallet.signer);
const presaleAddress = networkConfig.presale;
// Check current allowance
const currentAllowance = await usdtContract.allowance(wallet.address, presaleAddress);
if (currentAllowance < usdtAmountWei) {
const approveTx = await usdtContract.approve(presaleAddress, usdtAmountWei);
await approveTx.wait();
}
setPurchaseState(s => ({ ...s, step: "approved" }));
// Step 2: Buy tokens
const presaleContract = new Contract(presaleAddress, PRESALE_ABI, wallet.signer);
const buyTx = await presaleContract.buyTokensWithUSDT(usdtAmountWei);
setPurchaseState(s => ({ ...s, step: "purchasing", txHash: buyTx.hash }));
await buyTx.wait();
setPurchaseState(s => ({ ...s, step: "success" }));
} catch (err: unknown) {
const errMsg = (err as { reason?: string; message?: string }).reason
|| (err as Error).message
|| "Transaction failed";
setPurchaseState(s => ({ ...s, step: "error", error: errMsg }));
}
},
[wallet, network, networkConfig]
);
const reset = useCallback(() => {
setPurchaseState({ step: "idle", txHash: null, error: null, tokenAmount: 0 });
}, []);
// Calculate token amount from USDT input
const calcTokens = (usdtAmount: number): number => {
return usdtAmount / PRESALE_CONFIG.tokenPrice;
};
// Get user's USDT balance
const getUsdtBalance = useCallback(async (): Promise<number> => {
if (!wallet.provider || !wallet.address) return 0;
try {
const usdtDecimals = network === "ETH" ? 6 : 18;
const usdtContract = new Contract(networkConfig.usdt, ERC20_ABI, wallet.provider);
const balance = await usdtContract.balanceOf(wallet.address);
return parseFloat(formatUnits(balance, usdtDecimals));
} catch {
return 0;
}
}, [wallet, network, networkConfig]);
return {
purchaseState,
buyWithUSDT,
reset,
calcTokens,
getUsdtBalance,
};
}

View File

@ -0,0 +1,117 @@
// NAC XIC Presale — Wallet Connection Hook
// Supports MetaMask / any EVM-compatible wallet (BSC + ETH)
import { useState, useEffect, useCallback } from "react";
import { BrowserProvider, JsonRpcSigner, Eip1193Provider } from "ethers";
import { shortenAddress, switchToNetwork } from "@/lib/contracts";
export type NetworkType = "BSC" | "ETH" | "TRON";
export interface WalletState {
address: string | null;
shortAddress: string;
isConnected: boolean;
chainId: number | null;
provider: BrowserProvider | null;
signer: JsonRpcSigner | null;
isConnecting: boolean;
error: string | null;
}
const INITIAL_STATE: WalletState = {
address: null,
shortAddress: "",
isConnected: false,
chainId: null,
provider: null,
signer: null,
isConnecting: false,
error: null,
};
export function useWallet() {
const [state, setState] = useState<WalletState>(INITIAL_STATE);
const connect = useCallback(async () => {
if (!window.ethereum) {
setState(s => ({ ...s, error: "Please install MetaMask or a compatible wallet." }));
return;
}
setState(s => ({ ...s, isConnecting: true, error: null }));
try {
const provider = new BrowserProvider(window.ethereum as Eip1193Provider);
const accounts = await provider.send("eth_requestAccounts", []);
const network = await provider.getNetwork();
const signer = await provider.getSigner();
const address = accounts[0] as string;
setState({
address,
shortAddress: shortenAddress(address),
isConnected: true,
chainId: Number(network.chainId),
provider,
signer,
isConnecting: false,
error: null,
});
} catch (err: unknown) {
setState(s => ({
...s,
isConnecting: false,
error: (err as Error).message || "Failed to connect wallet",
}));
}
}, []);
const disconnect = useCallback(() => {
setState(INITIAL_STATE);
}, []);
const switchNetwork = useCallback(async (chainId: number) => {
try {
await switchToNetwork(chainId);
if (window.ethereum) {
const provider = new BrowserProvider(window.ethereum as Eip1193Provider);
const network = await provider.getNetwork();
const signer = await provider.getSigner();
setState(s => ({
...s,
chainId: Number(network.chainId),
provider,
signer,
error: null,
}));
}
} catch (err: unknown) {
setState(s => ({ ...s, error: (err as Error).message }));
}
}, []);
// Listen for account/chain changes
useEffect(() => {
if (!window.ethereum) return;
const handleAccountsChanged = (accounts: unknown) => {
const accs = accounts as string[];
if (accs.length === 0) {
setState(INITIAL_STATE);
} else {
setState(s => ({
...s,
address: accs[0],
shortAddress: shortenAddress(accs[0]),
}));
}
};
const handleChainChanged = () => {
window.location.reload();
};
window.ethereum.on("accountsChanged", handleAccountsChanged);
window.ethereum.on("chainChanged", handleChainChanged);
return () => {
window.ethereum?.removeListener("accountsChanged", handleAccountsChanged);
window.ethereum?.removeListener("chainChanged", handleChainChanged);
};
}, []);
return { ...state, connect, disconnect, switchNetwork };
}

View File

@ -3,6 +3,13 @@
@custom-variant dark (&:is(.dark *));
/* ============================================================
NAC XIC Token Presale Global Theme
Design: Dark Cyberpunk / Quantum Finance
Colors: Amber Gold #f0b429 | Quantum Blue #00d4ff | Deep Black #0a0a0f
Fonts: Space Grotesk (headings) | JetBrains Mono (numbers) | DM Sans (body)
============================================================ */
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
@ -42,136 +49,286 @@
--color-sidebar-ring: var(--sidebar-ring);
}
/* Dark theme — NAC Cyberpunk palette */
:root {
--primary: var(--color-blue-700);
--primary-foreground: var(--color-blue-50);
--sidebar-primary: var(--color-blue-600);
--sidebar-primary-foreground: var(--color-blue-50);
--chart-1: var(--color-blue-300);
--chart-2: var(--color-blue-500);
--chart-3: var(--color-blue-600);
--chart-4: var(--color-blue-700);
--chart-5: var(--color-blue-800);
--radius: 0.65rem;
--background: oklch(1 0 0);
--foreground: oklch(0.235 0.015 65);
--card: oklch(1 0 0);
--card-foreground: oklch(0.235 0.015 65);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.235 0.015 65);
--secondary: oklch(0.98 0.001 286.375);
--secondary-foreground: oklch(0.4 0.015 65);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.141 0.005 285.823);
--destructive: oklch(0.577 0.245 27.325);
--radius: 0.75rem;
--background: oklch(0.08 0.005 280);
--foreground: oklch(0.92 0.005 80);
--card: oklch(0.11 0.006 280);
--card-foreground: oklch(0.92 0.005 80);
--popover: oklch(0.11 0.006 280);
--popover-foreground: oklch(0.92 0.005 80);
--primary: oklch(0.78 0.18 75); /* Amber Gold */
--primary-foreground: oklch(0.08 0.005 280);
--secondary: oklch(0.15 0.006 280);
--secondary-foreground: oklch(0.75 0.005 80);
--muted: oklch(0.15 0.006 280);
--muted-foreground: oklch(0.55 0.01 280);
--accent: oklch(0.7 0.2 210); /* Quantum Blue */
--accent-foreground: oklch(0.08 0.005 280);
--destructive: oklch(0.65 0.22 25);
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.623 0.214 259.815);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.235 0.015 65);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.141 0.005 285.823);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.623 0.214 259.815);
--border: oklch(1 0 0 / 8%);
--input: oklch(1 0 0 / 10%);
--ring: oklch(0.78 0.18 75);
--chart-1: oklch(0.78 0.18 75);
--chart-2: oklch(0.7 0.2 210);
--chart-3: oklch(0.65 0.22 25);
--chart-4: oklch(0.75 0.15 150);
--chart-5: oklch(0.7 0.18 300);
--sidebar: oklch(0.11 0.006 280);
--sidebar-foreground: oklch(0.92 0.005 80);
--sidebar-primary: oklch(0.78 0.18 75);
--sidebar-primary-foreground: oklch(0.08 0.005 280);
--sidebar-accent: oklch(0.15 0.006 280);
--sidebar-accent-foreground: oklch(0.92 0.005 80);
--sidebar-border: oklch(1 0 0 / 8%);
--sidebar-ring: oklch(0.78 0.18 75);
}
/* Force dark mode globally */
.dark {
--primary: var(--color-blue-700);
--primary-foreground: var(--color-blue-50);
--sidebar-primary: var(--color-blue-500);
--sidebar-primary-foreground: var(--color-blue-50);
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.85 0.005 65);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.85 0.005 65);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.85 0.005 65);
--secondary: oklch(0.24 0.006 286.033);
--secondary-foreground: oklch(0.7 0.005 65);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.92 0.005 65);
--destructive: oklch(0.704 0.191 22.216);
--destructive-foreground: oklch(0.985 0 0);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.488 0.243 264.376);
--chart-1: var(--color-blue-300);
--chart-2: var(--color-blue-500);
--chart-3: var(--color-blue-600);
--chart-4: var(--color-blue-700);
--chart-5: var(--color-blue-800);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.85 0.005 65);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.488 0.243 264.376);
--background: oklch(0.08 0.005 280);
--foreground: oklch(0.92 0.005 80);
--card: oklch(0.11 0.006 280);
--card-foreground: oklch(0.92 0.005 80);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
html {
color-scheme: dark;
}
body {
@apply bg-background text-foreground;
font-family: 'DM Sans', system-ui, sans-serif;
background-color: #0a0a0f;
color: rgba(255, 255, 255, 0.87);
-webkit-font-smoothing: antialiased;
}
button:not(:disabled),
[role="button"]:not([aria-disabled="true"]),
[type="button"]:not(:disabled),
[type="submit"]:not(:disabled),
[type="reset"]:not(:disabled),
a[href],
select:not(:disabled),
input[type="checkbox"]:not(:disabled),
input[type="radio"]:not(:disabled) {
a[href] {
@apply cursor-pointer;
}
}
@layer components {
/**
* Custom container utility that centers content and adds responsive padding.
*
* This overrides Tailwind's default container behavior to:
* - Auto-center content (mx-auto)
* - Add responsive horizontal padding
* - Set max-width for large screens
*
* Usage: <div className="container">...</div>
*
* For custom widths, use max-w-* utilities directly:
* <div className="max-w-6xl mx-auto px-4">...</div>
*/
.container {
width: 100%;
margin-left: auto;
margin-right: auto;
padding-left: 1rem; /* 16px - mobile padding */
padding-left: 1rem;
padding-right: 1rem;
}
.flex {
min-height: 0;
min-width: 0;
}
@media (min-width: 640px) {
.container {
padding-left: 1.5rem; /* 24px - tablet padding */
padding-right: 1.5rem;
}
.container { padding-left: 1.5rem; padding-right: 1.5rem; }
}
@media (min-width: 1024px) {
.container { padding-left: 2rem; padding-right: 2rem; max-width: 1280px; }
}
@media (min-width: 1024px) {
.container {
padding-left: 2rem; /* 32px - desktop padding */
padding-right: 2rem;
max-width: 1280px; /* Standard content width */
}
/* ── NAC Card ── */
.nac-card {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.07);
backdrop-filter: blur(8px);
transition: border-color 0.2s ease;
}
.nac-card:hover {
border-color: rgba(240, 180, 41, 0.15);
}
/* ── NAC Card Blue ── */
.nac-card-blue {
background: rgba(0, 212, 255, 0.04);
border: 1px solid rgba(0, 212, 255, 0.15);
}
/* ── Amber Glow Effect ── */
.amber-glow {
box-shadow: 0 0 40px rgba(240, 180, 41, 0.06), 0 0 80px rgba(240, 180, 41, 0.03);
}
/* ── Amber Text Glow ── */
.amber-text-glow {
text-shadow: 0 0 20px rgba(240, 180, 41, 0.4);
}
/* ── Counter / Monospace Numbers ── */
.counter-digit {
font-family: 'JetBrains Mono', 'Courier New', monospace;
font-variant-numeric: tabular-nums;
}
/* ── TRC20 Address Display ── */
.trc20-address {
font-family: 'JetBrains Mono', monospace;
font-size: 0.7rem;
word-break: break-all;
color: rgba(0, 212, 255, 0.9);
line-height: 1.6;
}
/* ── Primary Button ── */
.btn-primary-nac {
background: linear-gradient(135deg, #f0b429 0%, #ffd700 50%, #f0b429 100%);
color: #0a0a0f;
font-weight: 700;
border: none;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
.btn-primary-nac::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.15) 0%, transparent 100%);
opacity: 0;
transition: opacity 0.2s;
}
.btn-primary-nac:hover:not(:disabled)::before { opacity: 1; }
.btn-primary-nac:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 8px 24px rgba(240, 180, 41, 0.35);
}
.btn-primary-nac:active:not(:disabled) { transform: translateY(0); }
.btn-primary-nac:disabled {
opacity: 0.4;
cursor: not-allowed;
}
/* ── Secondary Button ── */
.btn-secondary-nac {
background: rgba(240, 180, 41, 0.1);
color: #f0b429;
border: 1px solid rgba(240, 180, 41, 0.3);
font-weight: 600;
transition: all 0.2s ease;
}
.btn-secondary-nac:hover {
background: rgba(240, 180, 41, 0.18);
border-color: rgba(240, 180, 41, 0.5);
}
/* ── Input Field ── */
.input-nac {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.9);
outline: none;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.input-nac:focus {
border-color: rgba(240, 180, 41, 0.5);
box-shadow: 0 0 0 3px rgba(240, 180, 41, 0.08);
}
.input-nac::placeholder { color: rgba(255, 255, 255, 0.25); }
.input-nac::-webkit-inner-spin-button,
.input-nac::-webkit-outer-spin-button { -webkit-appearance: none; }
/* ── Network Tab ── */
.network-tab {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.07);
color: rgba(255, 255, 255, 0.55);
transition: all 0.2s ease;
cursor: pointer;
}
.network-tab:hover {
background: rgba(240, 180, 41, 0.06);
border-color: rgba(240, 180, 41, 0.2);
color: rgba(255, 255, 255, 0.8);
}
.network-tab.active {
background: rgba(240, 180, 41, 0.1);
border-color: rgba(240, 180, 41, 0.4);
color: #f0b429;
box-shadow: 0 0 16px rgba(240, 180, 41, 0.12);
}
/* ── Progress Bar ── */
.progress-bar-animated {
background: linear-gradient(90deg, #f0b429, #ffd700, #f0b429);
background-size: 200% 100%;
animation: shimmer 2s linear infinite;
box-shadow: 0 0 12px rgba(240, 180, 41, 0.4);
}
/* ── Step Number Badge ── */
.step-num {
width: 22px;
height: 22px;
min-width: 22px;
border-radius: 50%;
background: rgba(240, 180, 41, 0.15);
border: 1px solid rgba(240, 180, 41, 0.4);
color: #f0b429;
font-size: 0.7rem;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Space Grotesk', sans-serif;
}
/* ── Hex Background Pattern ── */
.hex-bg {
background-image: radial-gradient(circle at 20% 50%, rgba(240, 180, 41, 0.04) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(0, 212, 255, 0.04) 0%, transparent 50%);
}
/* ── Scan Line Effect ── */
.scan-line {
position: relative;
overflow: hidden;
}
.scan-line::after {
content: '';
position: absolute;
top: -100%;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, rgba(240, 180, 41, 0.3), transparent);
animation: scan 4s linear infinite;
}
/* ── Pulse Amber ── */
.pulse-amber {
animation: pulseAmber 2s ease-in-out infinite;
}
/* ── Fade In Up ── */
.fade-in-up {
animation: fadeInUp 0.6s ease-out;
}
}
/* ── Keyframes ── */
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
@keyframes scan {
0% { top: -2px; }
100% { top: 102%; }
}
@keyframes pulseAmber {
0%, 100% { box-shadow: 0 4px 20px rgba(240, 180, 41, 0.25); }
50% { box-shadow: 0 4px 32px rgba(240, 180, 41, 0.5); }
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}

232
client/src/lib/contracts.ts Normal file
View File

@ -0,0 +1,232 @@
// NAC XIC Token Presale — Contract Configuration
// Design: Dark Cyberpunk / Quantum Finance
// Colors: Amber Gold #f0b429, Quantum Blue #00d4ff, Deep Black #0a0a0f
// ============================================================
// CONTRACT ADDRESSES
// ============================================================
export const CONTRACTS = {
// BSC Mainnet (Chain ID: 56)
BSC: {
chainId: 56,
chainName: "BNB Smart Chain",
rpcUrl: "https://bsc-dataseed1.binance.org/",
explorerUrl: "https://bscscan.com",
nativeCurrency: { name: "BNB", symbol: "BNB", decimals: 18 },
presale: "0xc65e7a2738ed884db8d26a6eb2fecf7daca2e90c",
token: "0x59FF34dD59680a7125782b1f6df2A86ed46F5A24",
usdt: "0x55d398326f99059fF775485246999027B3197955",
},
// Ethereum Mainnet (Chain ID: 1)
ETH: {
chainId: 1,
chainName: "Ethereum",
rpcUrl: "https://eth.llamarpc.com",
explorerUrl: "https://etherscan.io",
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
presale: "0x85AB2F2d9f7ca7ecB272b5E8726c70f3fd45D1E3",
token: "", // XIC not yet on ETH
usdt: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
},
// TRON (TRC20) — Manual transfer
TRON: {
chainId: 0, // Not EVM
chainName: "TRON",
explorerUrl: "https://tronscan.org",
presale: "", // TRC20 uses manual transfer
token: "",
usdt: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
// Receiving wallet for TRC20 USDT
receivingWallet: "TYASr5UV6HEcXatwdFyffSGZszd6Gkjkvb",
},
} as const;
// ============================================================
// PRESALE PARAMETERS
// ============================================================
export const PRESALE_CONFIG = {
tokenPrice: 0.02, // $0.02 per XIC
tokenSymbol: "XIC",
tokenName: "New AssetChain Token",
tokenDecimals: 18,
minPurchaseUSDT: 10, // Minimum $10 USDT
maxPurchaseUSDT: 50000, // Maximum $50,000 USDT
totalSupply: 100_000_000_000, // 100 billion XIC
presaleAllocation: 30_000_000_000, // 30 billion for presale
// TRC20 memo format
trc20Memo: "XIC_PRESALE",
};
// ============================================================
// PRESALE CONTRACT ABI (BSC & ETH — same interface)
// ============================================================
export const PRESALE_ABI = [
// Read functions
{
"inputs": [],
"name": "tokenPrice",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalTokensSold",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalRaised",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "presaleActive",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "hardCap",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "user", "type": "address" }],
"name": "userPurchases",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
// Write functions
{
"inputs": [{ "internalType": "uint256", "name": "usdtAmount", "type": "uint256" }],
"name": "buyTokensWithUSDT",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "buyTokens",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
// Events
{
"anonymous": false,
"inputs": [
{ "indexed": true, "internalType": "address", "name": "buyer", "type": "address" },
{ "indexed": false, "internalType": "uint256", "name": "usdtAmount", "type": "uint256" },
{ "indexed": false, "internalType": "uint256", "name": "tokenAmount", "type": "uint256" }
],
"name": "TokensPurchased",
"type": "event"
}
] as const;
// ============================================================
// ERC20 USDT ABI (minimal — approve + allowance + balanceOf)
// ============================================================
export const ERC20_ABI = [
{
"inputs": [
{ "internalType": "address", "name": "spender", "type": "address" },
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
"name": "approve",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "owner", "type": "address" },
{ "internalType": "address", "name": "spender", "type": "address" }
],
"name": "allowance",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "account", "type": "address" }],
"name": "balanceOf",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
"stateMutability": "view",
"type": "function"
}
] as const;
// ============================================================
// NETWORK SWITCH HELPER
// ============================================================
export async function switchToNetwork(chainId: number): Promise<void> {
if (!window.ethereum) throw new Error("No wallet detected");
const hexChainId = "0x" + chainId.toString(16);
try {
await window.ethereum.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: hexChainId }],
});
} catch (err: unknown) {
// Chain not added yet — add it
if ((err as { code?: number }).code === 4902) {
const network = Object.values(CONTRACTS).find(n => n.chainId === chainId);
if (!network || !("rpcUrl" in network)) throw new Error("Unknown network");
await window.ethereum.request({
method: "wallet_addEthereumChain",
params: [{
chainId: hexChainId,
chainName: network.chainName,
rpcUrls: [(network as { rpcUrl: string }).rpcUrl],
nativeCurrency: (network as { nativeCurrency: { name: string; symbol: string; decimals: number } }).nativeCurrency,
blockExplorerUrls: [network.explorerUrl],
}],
});
} else {
throw err;
}
}
}
// ============================================================
// FORMAT HELPERS
// ============================================================
export function formatNumber(n: number, decimals = 2): string {
if (n >= 1_000_000_000) return (n / 1_000_000_000).toFixed(decimals) + "B";
if (n >= 1_000_000) return (n / 1_000_000).toFixed(decimals) + "M";
if (n >= 1_000) return (n / 1_000).toFixed(decimals) + "K";
return n.toFixed(decimals);
}
export function shortenAddress(addr: string): string {
if (!addr) return "";
return addr.slice(0, 6) + "..." + addr.slice(-4);
}
// Declare window.ethereum for TypeScript
declare global {
interface Window {
ethereum?: {
request: (args: { method: string; params?: unknown[] }) => Promise<unknown>;
on: (event: string, handler: (...args: unknown[]) => void) => void;
removeListener: (event: string, handler: (...args: unknown[]) => void) => void;
isMetaMask?: boolean;
};
}
}

View File

@ -1,25 +1,627 @@
import { Button } from "@/components/ui/button";
import { Loader2 } from "lucide-react";
import { Streamdown } from 'streamdown';
// NAC XIC Token Presale — Main Page
// Design: Dark Cyberpunk / Quantum Finance
// Colors: Amber Gold #f0b429 | Quantum Blue #00d4ff | Deep Black #0a0a0f
// Fonts: Space Grotesk (headings) | JetBrains Mono (numbers) | DM Sans (body)
/**
* All content in this page are only for example, replace with your own feature implementation
* When building pages, remember your instructions in Frontend Best Practices, Design Guide and Common Pitfalls
*/
export default function Home() {
// If theme is switchable in App.tsx, we can implement theme toggling like this:
// const { theme, toggleTheme } = useTheme();
import { useState, useEffect, useCallback } from "react";
import { toast } from "sonner";
import { useWallet } from "@/hooks/useWallet";
import { usePresale } from "@/hooks/usePresale";
import { CONTRACTS, PRESALE_CONFIG, formatNumber, shortenAddress } from "@/lib/contracts";
// ─── Network Tab Types ────────────────────────────────────────────────────────
type NetworkTab = "BSC" | "ETH" | "TRON";
// ─── Hero Background & Token Icon ─────────────────────────────────────────────
const HERO_BG = "https://d2xsxph8kpxj0f.cloudfront.net/310519663287655625/Ngki3MumDNGduV3xJt3mga/nac-hero-bg_7c6c173e.jpg";
const TOKEN_ICON = "https://d2xsxph8kpxj0f.cloudfront.net/310519663287655625/Ngki3MumDNGduV3xJt3mga/nac-token-icon_382e5c30.png";
// ─── Mock presale stats (replace with on-chain read in production) ─────────────
const MOCK_STATS = {
raised: 1_240_000,
hardCap: 5_000_000,
tokensSold: 62_000_000,
participants: 3847,
};
// ─── Countdown Timer ──────────────────────────────────────────────────────────
function useCountdown(targetDate: Date) {
const [timeLeft, setTimeLeft] = useState({ days: 0, hours: 0, minutes: 0, seconds: 0 });
useEffect(() => {
const tick = () => {
const diff = targetDate.getTime() - Date.now();
if (diff <= 0) { setTimeLeft({ days: 0, hours: 0, minutes: 0, seconds: 0 }); return; }
setTimeLeft({
days: Math.floor(diff / 86400000),
hours: Math.floor((diff % 86400000) / 3600000),
minutes: Math.floor((diff % 3600000) / 60000),
seconds: Math.floor((diff % 60000) / 1000),
});
};
tick();
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, [targetDate]);
return timeLeft;
}
// ─── Animated Counter ─────────────────────────────────────────────────────────
function AnimatedCounter({ value, prefix = "", suffix = "" }: { value: number; prefix?: string; suffix?: string }) {
const [display, setDisplay] = useState(0);
useEffect(() => {
let start = 0;
const step = value / 60;
const id = setInterval(() => {
start += step;
if (start >= value) { setDisplay(value); clearInterval(id); }
else setDisplay(Math.floor(start));
}, 16);
return () => clearInterval(id);
}, [value]);
return <span className="counter-digit">{prefix}{display.toLocaleString()}{suffix}</span>;
}
// ─── Network Icon ─────────────────────────────────────────────────────────────
function NetworkIcon({ network }: { network: NetworkTab }) {
if (network === "BSC") return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="12" fill="#F0B90B"/>
<path d="M9.4 12L12 9.4L14.6 12L12 14.6L9.4 12Z" fill="white"/>
<path d="M6 12L8.6 9.4L11.2 12L8.6 14.6L6 12Z" fill="white" opacity="0.7"/>
<path d="M12.8 12L15.4 9.4L18 12L15.4 14.6L12.8 12Z" fill="white" opacity="0.7"/>
<path d="M9.4 8.6L12 6L14.6 8.6L12 11.2L9.4 8.6Z" fill="white" opacity="0.5"/>
<path d="M9.4 15.4L12 12.8L14.6 15.4L12 18L9.4 15.4Z" fill="white" opacity="0.5"/>
</svg>
);
if (network === "ETH") return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="12" fill="#627EEA"/>
<path d="M12 4L7 12.5L12 15.5L17 12.5L12 4Z" fill="white" opacity="0.9"/>
<path d="M12 16.5L7 13.5L12 20L17 13.5L12 16.5Z" fill="white" opacity="0.7"/>
</svg>
);
// TRON
return (
<div className="min-h-screen flex flex-col">
<main>
{/* Example: lucide-react for icons */}
<Loader2 className="animate-spin" />
Example Page
{/* Example: Streamdown for markdown rendering */}
<Streamdown>Any **markdown** content</Streamdown>
<Button variant="default">Example Button</Button>
</main>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="12" fill="#FF0013"/>
<path d="M12 5L19 9.5V14.5L12 19L5 14.5V9.5L12 5Z" fill="white" opacity="0.9"/>
<path d="M12 8L16 10.5V13.5L12 16L8 13.5V10.5L12 8Z" fill="#FF0013"/>
</svg>
);
}
// ─── Step Badge ───────────────────────────────────────────────────────────────
function StepBadge({ num, text }: { num: number; text: string }) {
return (
<div className="flex items-start gap-3">
<div className="step-num">{num}</div>
<span className="text-sm text-white/70 leading-relaxed">{text}</span>
</div>
);
}
// ─── TRC20 Purchase Panel ─────────────────────────────────────────────────────
function TRC20Panel({ usdtAmount }: { usdtAmount: number }) {
const tokenAmount = usdtAmount / PRESALE_CONFIG.tokenPrice;
const [copied, setCopied] = useState(false);
const copyAddress = () => {
navigator.clipboard.writeText(CONTRACTS.TRON.receivingWallet);
setCopied(true);
toast.success("Address copied to clipboard!");
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="space-y-4">
<div className="nac-card-blue rounded-xl p-4 space-y-3">
<p className="text-sm font-medium text-white/80">Send TRC20 USDT to this address:</p>
<div
className="trc20-address p-3 rounded-lg cursor-pointer hover:bg-white/5 transition-colors"
style={{ background: "rgba(0,212,255,0.05)", border: "1px solid rgba(0,212,255,0.2)" }}
onClick={copyAddress}
>
{CONTRACTS.TRON.receivingWallet}
</div>
<button
onClick={copyAddress}
className="w-full py-2 rounded-lg text-sm font-semibold transition-all"
style={{ background: copied ? "rgba(0,230,118,0.2)" : "rgba(0,212,255,0.15)", border: "1px solid rgba(0,212,255,0.4)", color: copied ? "#00e676" : "#00d4ff" }}
>
{copied ? "✓ Copied!" : "Copy Address"}
</button>
</div>
<div className="space-y-2">
<StepBadge num={1} text={`Send exactly ${usdtAmount.toFixed(2)} USDT (TRC20) to the address above`} />
<StepBadge num={2} text={`Include memo: ${PRESALE_CONFIG.trc20Memo} (optional but recommended)`} />
<StepBadge num={3} text={`You will receive ${formatNumber(tokenAmount)} XIC tokens after confirmation (1-24h)`} />
<StepBadge num={4} text="Contact support with your TX hash if tokens are not received within 24 hours" />
</div>
<div className="rounded-lg p-3 text-xs text-amber-300/80" style={{ background: "rgba(240,180,41,0.08)", border: "1px solid rgba(240,180,41,0.2)" }}>
Only send USDT on the TRON network (TRC20). Sending other tokens or using a different network will result in permanent loss.
</div>
</div>
);
}
// ─── EVM Purchase Panel ───────────────────────────────────────────────────────
function EVMPurchasePanel({ network }: { network: "BSC" | "ETH" }) {
const wallet = useWallet();
const { purchaseState, buyWithUSDT, reset, calcTokens, getUsdtBalance } = usePresale(wallet, network);
const [usdtInput, setUsdtInput] = useState("100");
const [usdtBalance, setUsdtBalance] = useState<number | null>(null);
const targetChainId = CONTRACTS[network].chainId;
const isWrongNetwork = wallet.isConnected && wallet.chainId !== targetChainId;
const fetchBalance = useCallback(async () => {
const bal = await getUsdtBalance();
setUsdtBalance(bal);
}, [getUsdtBalance]);
useEffect(() => {
if (wallet.isConnected) fetchBalance();
}, [wallet.isConnected, fetchBalance]);
const usdtAmount = parseFloat(usdtInput) || 0;
const tokenAmount = calcTokens(usdtAmount);
const isValidAmount = usdtAmount >= PRESALE_CONFIG.minPurchaseUSDT && usdtAmount <= PRESALE_CONFIG.maxPurchaseUSDT;
const handleBuy = async () => {
if (!isValidAmount) {
toast.error(`Amount must be between $${PRESALE_CONFIG.minPurchaseUSDT} and $${PRESALE_CONFIG.maxPurchaseUSDT}`);
return;
}
await buyWithUSDT(usdtAmount);
};
useEffect(() => {
if (purchaseState.step === "success") {
toast.success(`Successfully purchased ${formatNumber(purchaseState.tokenAmount)} XIC tokens!`);
} else if (purchaseState.step === "error" && purchaseState.error) {
toast.error(purchaseState.error.slice(0, 120));
}
}, [purchaseState.step, purchaseState.error, purchaseState.tokenAmount]);
if (!wallet.isConnected) {
return (
<div className="space-y-4">
<div className="text-center py-6">
<div className="text-4xl mb-3">🔗</div>
<p className="text-white/60 text-sm mb-4">Connect your wallet to purchase XIC tokens with USDT</p>
<button
onClick={wallet.connect}
disabled={wallet.isConnecting}
className="btn-primary-nac w-full py-3 rounded-xl text-base font-bold pulse-amber"
style={{ fontFamily: "'Space Grotesk', sans-serif" }}
>
{wallet.isConnecting ? "Connecting..." : "Connect Wallet"}
</button>
</div>
<div className="text-xs text-white/40 text-center">
Supports MetaMask, Trust Wallet, and all EVM-compatible wallets
</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">Wrong Network</p>
<p className="text-white/60 text-sm mb-4">Please switch to {CONTRACTS[network].chainName}</p>
<button
onClick={() => wallet.switchNetwork(targetChainId)}
className="btn-primary-nac px-6 py-2 rounded-lg text-sm font-bold"
>
Switch to {network === "BSC" ? "BSC" : "Ethereum"}
</button>
</div>
</div>
);
}
if (purchaseState.step === "success") {
return (
<div className="space-y-4 text-center py-4">
<div className="text-5xl mb-3">🎉</div>
<h3 className="text-xl font-bold text-green-400" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>
Purchase Successful!
</h3>
<p className="text-white/70">
You received <span className="text-amber-400 font-bold counter-digit">{formatNumber(purchaseState.tokenAmount)}</span> XIC tokens
</p>
{purchaseState.txHash && (
<a
href={`${CONTRACTS[network].explorerUrl}/tx/${purchaseState.txHash}`}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-blue-400 hover:text-blue-300 underline block"
>
View on Explorer
</a>
)}
<button onClick={reset} className="btn-secondary-nac px-6 py-2 rounded-lg text-sm font-semibold">
Buy More
</button>
</div>
);
}
const isProcessing = ["approving", "approved", "purchasing"].includes(purchaseState.step);
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>
</div>
{usdtBalance !== null && (
<span className="text-xs text-white/50">Balance: <span className="text-white/80 counter-digit">{usdtBalance.toFixed(2)} USDT</span></span>
)}
</div>
{/* USDT Amount Input */}
<div className="space-y-2">
<label className="text-sm text-white/60 font-medium">USDT Amount</label>
<div className="relative">
<input
type="number"
value={usdtInput}
onChange={e => setUsdtInput(e.target.value)}
min={PRESALE_CONFIG.minPurchaseUSDT}
max={PRESALE_CONFIG.maxPurchaseUSDT}
placeholder="Enter USDT amount"
className="input-nac w-full px-4 py-3 rounded-xl text-lg counter-digit pr-20"
disabled={isProcessing}
/>
<span className="absolute right-4 top-1/2 -translate-y-1/2 text-white/40 text-sm font-semibold">USDT</span>
</div>
<div className="flex gap-2">
{[100, 500, 1000, 5000].map(amt => (
<button
key={amt}
onClick={() => setUsdtInput(amt.toString())}
className="flex-1 py-1 rounded-lg text-xs font-semibold transition-all"
style={{ background: "rgba(240,180,41,0.08)", border: "1px solid rgba(240,180,41,0.2)", color: "rgba(240,180,41,0.8)" }}
disabled={isProcessing}
>
${amt}
</button>
))}
</div>
</div>
{/* Token Amount Preview */}
<div className="rounded-xl p-4" style={{ background: "rgba(240,180,41,0.06)", border: "1px solid rgba(240,180,41,0.2)" }}>
<div className="flex items-center justify-between">
<span className="text-sm text-white/60">You receive</span>
<div className="text-right">
<span className="text-2xl font-bold amber-text-glow counter-digit" style={{ fontFamily: "'Space Grotesk', sans-serif", color: "#f0b429" }}>
{formatNumber(tokenAmount)}
</span>
<span className="text-amber-400 ml-2 font-semibold">XIC</span>
</div>
</div>
<div className="flex items-center justify-between mt-1">
<span className="text-xs text-white/40">Price per token</span>
<span className="text-xs text-white/60 counter-digit">$0.02 USDT</span>
</div>
</div>
{/* Purchase Steps */}
{isProcessing && (
<div className="space-y-2 py-2">
<div className={`flex items-center gap-2 text-sm ${["approving", "approved", "purchasing", "success"].includes(purchaseState.step) ? "text-amber-400" : "text-white/40"}`}>
<div className={`w-4 h-4 rounded-full border-2 flex items-center justify-center ${purchaseState.step === "approving" ? "border-amber-400 animate-spin" : "border-amber-400 bg-amber-400"}`}>
{purchaseState.step !== "approving" && <span className="text-black text-xs"></span>}
</div>
Step 1: Approve USDT
</div>
<div className={`flex items-center gap-2 text-sm ${(["purchasing", "success"] as string[]).includes(purchaseState.step) ? "text-amber-400" : "text-white/40"}`}>
<div className={`w-4 h-4 rounded-full border-2 flex items-center justify-center ${purchaseState.step === "purchasing" ? "border-amber-400 animate-spin" : (purchaseState.step as string) === "success" ? "border-amber-400 bg-amber-400" : "border-white/20"}`}>
{(purchaseState.step as string) === "success" && <span className="text-black text-xs"></span>}
</div>
Step 2: Confirm Purchase
</div>
</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" ? "Approving USDT..."
: purchaseState.step === "approved" ? "Approved! Buying..."
: "Processing..."
: `Buy ${formatNumber(tokenAmount)} XIC`}
</button>
<p className="text-xs text-center text-white/30">
Min: ${PRESALE_CONFIG.minPurchaseUSDT} · Max: ${PRESALE_CONFIG.maxPurchaseUSDT.toLocaleString()} USDT
</p>
</div>
);
}
// ─── Main Page ────────────────────────────────────────────────────────────────
export default function Home() {
const [activeNetwork, setActiveNetwork] = useState<NetworkTab>("BSC");
const [trcUsdtAmount, setTrcUsdtAmount] = useState("100");
const presaleEndDate = new Date("2025-12-31T23:59:59Z");
const countdown = useCountdown(presaleEndDate);
const progressPct = Math.min((MOCK_STATS.raised / MOCK_STATS.hardCap) * 100, 100);
const networks: NetworkTab[] = ["BSC", "ETH", "TRON"];
return (
<div className="min-h-screen" style={{ background: "#0a0a0f" }}>
{/* ── Navigation ── */}
<nav className="fixed top-0 left-0 right-0 z-50 flex items-center justify-between px-6 py-4" style={{ background: "rgba(10,10,15,0.9)", borderBottom: "1px solid rgba(240,180,41,0.1)", backdropFilter: "blur(12px)" }}>
<div className="flex items-center gap-3">
<img src={TOKEN_ICON} alt="XIC" className="w-8 h-8 rounded-full" />
<div>
<span className="font-bold text-white" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>New AssetChain</span>
<span className="ml-2 text-xs px-2 py-0.5 rounded-full font-semibold" style={{ background: "rgba(240,180,41,0.15)", color: "#f0b429", border: "1px solid rgba(240,180,41,0.3)" }}>PRESALE</span>
</div>
</div>
<div className="flex items-center gap-4">
<a href="https://newassetchain.io" target="_blank" rel="noopener noreferrer" className="text-sm text-white/60 hover:text-white/90 transition-colors hidden md:block">Website</a>
<a href="https://lens.newassetchain.io" target="_blank" rel="noopener noreferrer" className="text-sm text-white/60 hover:text-white/90 transition-colors hidden md:block">Explorer</a>
<a href="https://docs.newassetchain.io" target="_blank" rel="noopener noreferrer" className="text-sm text-white/60 hover:text-white/90 transition-colors hidden md:block">Docs</a>
</div>
</nav>
{/* ── Hero Section ── */}
<section className="relative pt-20 pb-8 overflow-hidden hex-bg" style={{ minHeight: "340px" }}>
<div
className="absolute inset-0 bg-cover bg-center opacity-30"
style={{ backgroundImage: `url(${HERO_BG})` }}
/>
<div className="absolute inset-0" style={{ background: "linear-gradient(to bottom, rgba(10,10,15,0.4) 0%, rgba(10,10,15,0.95) 100%)" }} />
<div className="relative container mx-auto px-4 pt-12 pb-4 text-center">
<div className="inline-flex items-center gap-2 mb-4 px-4 py-2 rounded-full text-sm font-semibold" style={{ background: "rgba(0,230,118,0.1)", border: "1px solid rgba(0,230,118,0.3)", color: "#00e676" }}>
<span className="w-2 h-2 rounded-full bg-green-400 animate-pulse" />
Presale is LIVE
</div>
<h1 className="text-4xl md:text-6xl font-bold mb-4 leading-tight" style={{ fontFamily: "'Space Grotesk', sans-serif", background: "linear-gradient(135deg, #f0b429 0%, #ffd700 50%, #f0b429 100%)", WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent" }}>
XIC Token Presale
</h1>
<p className="text-lg text-white/70 max-w-2xl mx-auto mb-6">
New AssetChain The next-generation RWA native blockchain with AI-native compliance, CBPP consensus, and Charter smart contracts.
</p>
<div className="flex flex-wrap justify-center gap-4 text-sm text-white/50">
<span className="flex items-center gap-1"><span className="text-amber-400"></span> $0.02 per XIC</span>
<span className="flex items-center gap-1"><span className="text-amber-400"></span> 100B Total Supply</span>
<span className="flex items-center gap-1"><span className="text-amber-400"></span> BSC · ETH · TRC20</span>
</div>
</div>
</section>
{/* ── Main Content ── */}
<section className="container mx-auto px-4 py-8">
<div className="grid grid-cols-1 lg:grid-cols-5 gap-6 max-w-6xl mx-auto">
{/* ── Left Panel: Stats & Info ── */}
<div className="lg:col-span-2 space-y-5 fade-in-up">
{/* Countdown */}
<div className="nac-card rounded-2xl p-5 scan-line">
<h3 className="text-xs font-semibold uppercase tracking-widest text-white/40 mb-4">Presale Ends In</h3>
<div className="grid grid-cols-4 gap-2 text-center">
{[
{ label: "Days", value: countdown.days },
{ label: "Hours", value: countdown.hours },
{ label: "Mins", value: countdown.minutes },
{ label: "Secs", value: countdown.seconds },
].map(({ label, value }) => (
<div key={label} className="rounded-xl py-3" style={{ background: "rgba(240,180,41,0.08)", border: "1px solid rgba(240,180,41,0.15)" }}>
<div className="text-2xl font-bold counter-digit amber-text-glow" style={{ color: "#f0b429", fontFamily: "'Space Grotesk', sans-serif" }}>
{String(value).padStart(2, "0")}
</div>
<div className="text-xs text-white/40 mt-1">{label}</div>
</div>
))}
</div>
</div>
{/* Progress */}
<div className="nac-card rounded-2xl p-5">
<div className="flex items-center justify-between mb-3">
<h3 className="text-xs font-semibold uppercase tracking-widest text-white/40">Funds Raised</h3>
<span className="text-xs font-bold counter-digit" style={{ color: "#f0b429" }}>{progressPct.toFixed(1)}%</span>
</div>
<div className="h-3 rounded-full mb-3 overflow-hidden" style={{ background: "rgba(255,255,255,0.06)" }}>
<div className="h-full rounded-full progress-bar-animated" style={{ width: `${progressPct}%` }} />
</div>
<div className="flex justify-between text-sm">
<div>
<div className="text-xl font-bold counter-digit amber-text-glow" style={{ color: "#f0b429", fontFamily: "'Space Grotesk', sans-serif" }}>
<AnimatedCounter value={MOCK_STATS.raised} prefix="$" />
</div>
<div className="text-xs text-white/40">Raised</div>
</div>
<div className="text-right">
<div className="text-xl font-bold counter-digit text-white/60" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>
${formatNumber(MOCK_STATS.hardCap)}
</div>
<div className="text-xs text-white/40">Hard Cap</div>
</div>
</div>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-2 gap-3">
{[
{ label: "Tokens Sold", value: formatNumber(MOCK_STATS.tokensSold), unit: "XIC" },
{ label: "Participants", value: MOCK_STATS.participants.toLocaleString(), unit: "Wallets" },
{ label: "Token Price", value: "$0.02", unit: "USDT" },
{ label: "Listing Price", value: "$0.10", unit: "Target" },
].map(({ label, value, unit }) => (
<div key={label} className="nac-card rounded-xl p-4">
<div className="text-lg font-bold counter-digit" style={{ fontFamily: "'Space Grotesk', sans-serif", color: "#f0b429" }}>{value}</div>
<div className="text-xs text-white/40 mt-0.5">{label}</div>
<div className="text-xs text-white/25">{unit}</div>
</div>
))}
</div>
{/* Token Info */}
<div className="nac-card rounded-2xl p-5 space-y-3">
<h3 className="text-xs font-semibold uppercase tracking-widest text-white/40">Token Details</h3>
{[
{ label: "Name", value: "New AssetChain Token" },
{ label: "Symbol", value: "XIC" },
{ label: "Network", value: "BSC (BEP-20)" },
{ label: "Decimals", value: "18" },
{ label: "Total Supply", value: "100,000,000,000" },
].map(({ label, value }) => (
<div key={label} 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">{label}</span>
<span className="text-xs font-medium text-white/80 counter-digit">{value}</span>
</div>
))}
<a
href={`https://bscscan.com/address/${CONTRACTS.BSC.token}`}
target="_blank"
rel="noopener noreferrer"
className="block text-center text-xs py-2 rounded-lg transition-all hover:bg-white/5"
style={{ color: "#00d4ff", border: "1px solid rgba(0,212,255,0.2)" }}
>
View Token Contract
</a>
</div>
</div>
{/* ── Right Panel: Purchase ── */}
<div className="lg:col-span-3">
<div className="nac-card rounded-2xl p-6 amber-glow">
{/* Token Icon + Title */}
<div className="flex items-center gap-4 mb-6">
<img src={TOKEN_ICON} alt="XIC" className="w-14 h-14 rounded-full" style={{ border: "2px solid rgba(240,180,41,0.4)" }} />
<div>
<h2 className="text-2xl font-bold" style={{ fontFamily: "'Space Grotesk', sans-serif", color: "#f0b429" }}>Buy XIC Tokens</h2>
<p className="text-sm text-white/50">1 XIC = <span className="text-white/80 font-semibold">$0.02 USDT</span></p>
</div>
</div>
{/* Network Selector */}
<div className="mb-6">
<p className="text-xs font-semibold uppercase tracking-widest text-white/40 mb-3">Select Network</p>
<div className="grid grid-cols-3 gap-2">
{networks.map(net => (
<button
key={net}
onClick={() => setActiveNetwork(net)}
className={`network-tab rounded-xl py-3 px-2 flex flex-col items-center gap-1.5 ${activeNetwork === net ? "active" : ""}`}
>
<NetworkIcon network={net} />
<span className="text-xs font-semibold">{net === "BSC" ? "BSC" : net === "ETH" ? "Ethereum" : "TRON"}</span>
<span className="text-xs opacity-60">{net === "TRON" ? "TRC20" : "ERC20"} USDT</span>
</button>
))}
</div>
</div>
{/* Purchase Area */}
<div>
{activeNetwork === "BSC" && <EVMPurchasePanel network="BSC" />}
{activeNetwork === "ETH" && <EVMPurchasePanel network="ETH" />}
{activeNetwork === "TRON" && (
<div className="space-y-4">
<div className="space-y-2">
<label className="text-sm text-white/60 font-medium">USDT Amount (TRC20)</label>
<div className="relative">
<input
type="number"
value={trcUsdtAmount}
onChange={e => setTrcUsdtAmount(e.target.value)}
placeholder="Enter USDT amount"
className="input-nac w-full px-4 py-3 rounded-xl text-lg counter-digit pr-20"
/>
<span className="absolute right-4 top-1/2 -translate-y-1/2 text-white/40 text-sm font-semibold">USDT</span>
</div>
<div className="flex gap-2">
{[100, 500, 1000, 5000].map(amt => (
<button
key={amt}
onClick={() => setTrcUsdtAmount(amt.toString())}
className="flex-1 py-1 rounded-lg text-xs font-semibold transition-all"
style={{ background: "rgba(240,180,41,0.08)", border: "1px solid rgba(240,180,41,0.2)", color: "rgba(240,180,41,0.8)" }}
>
${amt}
</button>
))}
</div>
</div>
<TRC20Panel usdtAmount={parseFloat(trcUsdtAmount) || 0} />
</div>
)}
</div>
{/* Presale Contract Links */}
<div className="mt-6 pt-4" style={{ borderTop: "1px solid rgba(255,255,255,0.06)" }}>
<p className="text-xs text-white/30 mb-2">Verified Presale Contracts</p>
<div className="flex flex-wrap gap-2">
<a href={`https://bscscan.com/address/${CONTRACTS.BSC.presale}`} target="_blank" rel="noopener noreferrer" className="text-xs px-3 py-1 rounded-full transition-all hover:bg-white/5" style={{ color: "#00d4ff", border: "1px solid rgba(0,212,255,0.2)" }}>
BSC Contract
</a>
<a href={`https://etherscan.io/address/${CONTRACTS.ETH.presale}`} target="_blank" rel="noopener noreferrer" className="text-xs px-3 py-1 rounded-full transition-all hover:bg-white/5" style={{ color: "#00d4ff", border: "1px solid rgba(0,212,255,0.2)" }}>
ETH Contract
</a>
</div>
</div>
</div>
{/* Why NAC */}
<div className="mt-5 grid grid-cols-1 md:grid-cols-3 gap-4">
{[
{ icon: "🔗", title: "Native RWA Chain", desc: "Purpose-built for Real World Asset tokenization with AI-native compliance" },
{ icon: "⚡", title: "CBPP Consensus", desc: "Constitutional Block Production Protocol — next-gen consensus beyond PoS/PoW" },
{ icon: "🛡️", title: "Charter Contracts", desc: "NAC-native smart contract language with built-in regulatory compliance" },
].map(({ icon, title, desc }) => (
<div key={title} className="nac-card rounded-xl p-4">
<div className="text-2xl mb-2">{icon}</div>
<h4 className="font-semibold text-white/90 text-sm mb-1" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>{title}</h4>
<p className="text-xs text-white/50 leading-relaxed">{desc}</p>
</div>
))}
</div>
</div>
</div>
</section>
{/* ── Footer ── */}
<footer className="mt-16 py-8 text-center" style={{ borderTop: "1px solid rgba(240,180,41,0.1)" }}>
<div className="flex items-center justify-center gap-2 mb-3">
<img src={TOKEN_ICON} alt="XIC" className="w-6 h-6 rounded-full" />
<span className="font-semibold text-white/70" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>New AssetChain</span>
</div>
<p className="text-xs text-white/30 max-w-md mx-auto">
This presale involves risk. Only invest what you can afford to lose. XIC tokens are not available to US persons or residents of restricted jurisdictions.
</p>
<div className="flex justify-center gap-6 mt-4">
{[
{ label: "Website", href: "https://newassetchain.io" },
{ label: "Explorer", href: "https://lens.newassetchain.io" },
{ label: "Telegram", href: "https://t.me/newassetchain" },
{ label: "Twitter", href: "https://twitter.com/newassetchain" },
].map(({ label, href }) => (
<a key={label} href={href} target="_blank" rel="noopener noreferrer" className="text-xs text-white/40 hover:text-white/70 transition-colors">
{label}
</a>
))}
</div>
</footer>
</div>
);
}

82
ideas.md Normal file
View File

@ -0,0 +1,82 @@
# NAC XIC Token 预售页面设计方案
## 设计方向探索
<response>
<text>
**方案 A暗黑科技 · 量子金融**
- **Design Movement**: Cyberpunk Fintech / Dark Luxury
- **Core Principles**:
1. 深黑底色配金色/琥珀色高光,营造高价值感
2. 数据可视化优先,进度条、倒计时、实时数据流
3. 极简功能区块,每个操作区域清晰独立
4. 微粒子动效背景,体现区块链技术感
- **Color Philosophy**: 背景 `#0a0a0f`(极深蓝黑),主色 `#f0b429`(琥珀金),强调 `#00d4ff`(量子蓝),成功 `#00e676`(绿)
- **Layout Paradigm**: 左侧固定信息面板(项目信息+进度),右侧购买操作区,非对称双栏布局
- **Signature Elements**:
1. 扫描线动效CSS animation
2. 数字滚动计数器(已售/目标)
3. 链式连接图标BSC/ETH/TRC20 网络选择器)
- **Interaction Philosophy**: 每次点击都有涟漪效果,状态变化有流畅过渡
- **Animation**: 背景粒子漂浮,数字实时滚动,进度条填充动画,按钮悬停发光
- **Typography System**: 标题 Space Grotesk Bold数字 JetBrains Mono正文 Inter Regular
</text>
<probability>0.08</probability>
</response>
<response>
<text>
**方案 B极简白金 · 机构级**
- **Design Movement**: Swiss Modernism / Institutional Finance
- **Core Principles**:
1. 大量留白,内容密度低,信任感强
2. 严格网格系统,精确对齐
3. 单色调为主,用尺寸和重量建立层次
4. 无装饰性元素,功能即美学
- **Color Philosophy**: 背景纯白,文字深炭灰 `#1a1a2e`,强调色 `#0066cc`(机构蓝),边框 `#e5e7eb`
- **Layout Paradigm**: 居中单栏,宽屏下分为三区(信息/购买/说明),极度克制
- **Signature Elements**:
1. 细线分隔符
2. 数据表格展示代币经济
3. 无边框卡片,仅靠阴影区分层次
- **Interaction Philosophy**: 无动效,状态变化即时,强调可靠性
- **Animation**: 仅有必要的加载状态,无装饰性动画
- **Typography System**: 标题 Playfair Display正文 Source Sans Pro数字 Roboto Mono
</text>
<probability>0.05</probability>
</response>
<response>
<text>
**方案 C深海科技 · 宇宙级**
- **Design Movement**: Deep Space / Web3 Premium Dark
- **Core Principles**:
1. 深海蓝黑渐变背景,星云质感
2. 玻璃拟态卡片glassmorphism半透明磨砂
3. 紫蓝渐变高光,体现 Web3 未来感
4. 多网络支持可视化(链图标+网络切换动画)
- **Color Philosophy**: 背景 `#050b1a`(深宇宙蓝),玻璃卡片 `rgba(255,255,255,0.05)`,主色渐变 `#6366f1→#8b5cf6`(靛紫),金色点缀 `#fbbf24`
- **Layout Paradigm**: 全屏英雄区(背景星云+核心数据),下方功能区三栏网格,购买区居中突出
- **Signature Elements**:
1. 星云粒子背景CSS + SVG filter
2. 玻璃卡片购买面板
3. 网络切换器BSC/ETH/TRC20 标签页)
- **Interaction Philosophy**: 悬停时卡片上浮,选择网络时平滑切换,购买按钮有脉冲动效
- **Animation**: 背景星云缓慢旋转,卡片入场从下淡入,进度条动态填充
- **Typography System**: 标题 Syne ExtraBold副标题 Outfit Medium正文 DM Sans数字 Space Mono
</text>
<probability>0.07</probability>
</response>
---
## 选定方案:方案 A暗黑科技 · 量子金融)
**理由**
- 预售页面的核心目标是建立信任感和紧迫感,暗黑科技风格在 Web3 预售场景中最具说服力
- 琥珀金 + 量子蓝的配色组合在深色背景上对比度极高,关键数据一目了然
- 非对称双栏布局将"项目信息"和"购买操作"清晰分离,降低用户认知负担
- JetBrains Mono 字体用于数字展示,强化技术可信度

View File

@ -39,11 +39,13 @@
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
"@walletconnect/ethereum-provider": "^2.23.7",
"axios": "^1.12.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"embla-carousel-react": "^8.6.0",
"ethers": "6",
"express": "^4.21.2",
"framer-motion": "^12.23.22",
"input-otp": "^1.4.2",

File diff suppressed because it is too large Load Diff