Checkpoint: NAC XIC Token Presale v1.0 — 完整实现:BSC/ETH USDT 购买(ethers.js v6 + MetaMask)、TRC20 手动转账、暗黑科技设计、倒计时、进度条、合约验证链接。TypeScript 零错误。
This commit is contained in:
parent
f871d3b777
commit
f8b007a9eb
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
@ -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 };
|
||||
}
|
||||
|
|
@ -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); }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 字体用于数字展示,强化技术可信度
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
3496
pnpm-lock.yaml
3496
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue