From dd24e6ba138878b2cc037b3d4b895cde94cc5f6d Mon Sep 17 00:00:00 2001 From: Manus Date: Mon, 9 Mar 2026 23:14:34 -0400 Subject: [PATCH] =?UTF-8?q?Checkpoint:=20=E4=BF=AE=E5=A4=8D=E9=A2=84?= =?UTF-8?q?=E5=94=AE=E7=BD=91=E7=AB=99=E4=B8=89=E4=B8=AA=E5=85=B3=E9=94=AE?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=9A1)=20=E8=B4=AD=E4=B9=B0=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E6=B0=B8=E8=BF=9C=E7=A6=81=E7=94=A8=EF=BC=88maxPurcha?= =?UTF-8?q?seUSDT=3D0=E5=AF=BC=E8=87=B4=EF=BC=89=EF=BC=8C2)=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9EAdd=20XIC=20to=20Wallet=E6=8C=89=E9=92=AE=EF=BC=8C3)?= =?UTF-8?q?=20=E5=AE=8C=E6=95=B4=E9=87=8D=E5=86=99useWallet.ts=E6=94=AF?= =?UTF-8?q?=E6=8C=81TokenPocket/OKX/Bitget=E7=AD=89=E4=B8=AD=E5=9B=BD?= =?UTF-8?q?=E9=92=B1=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/hooks/useWallet.ts | 113 ++++++++++++++++++---------------- client/src/lib/contracts.ts | 6 +- client/src/pages/Home.tsx | 56 ++++++++++++++++- server/onchain.ts | 2 +- test-onchain.mjs | 113 ++++++++++++++++++++++++++++++++++ 5 files changed, 230 insertions(+), 60 deletions(-) create mode 100644 test-onchain.mjs diff --git a/client/src/hooks/useWallet.ts b/client/src/hooks/useWallet.ts index 4a33cac..6c84b39 100644 --- a/client/src/hooks/useWallet.ts +++ b/client/src/hooks/useWallet.ts @@ -1,6 +1,6 @@ // NAC XIC Presale — Wallet Connection Hook -// Supports MetaMask, Trust Wallet, OKX Wallet, Coinbase Wallet, and all EVM-compatible wallets -// v3: improved error handling, MetaMask initialization detection, toast notifications +// Supports MetaMask, TokenPocket, OKX, Bitget, Trust Wallet, imToken, SafePal, and all EVM wallets +// v4: improved Chinese wallet support (TokenPocket, OKX, Bitget first priority) import { useState, useEffect, useCallback, useRef } from "react"; import { BrowserProvider, JsonRpcSigner, Eip1193Provider } from "ethers"; @@ -30,53 +30,60 @@ const INITIAL_STATE: WalletState = { error: null, }; +type EthProvider = Eip1193Provider & { + isMetaMask?: boolean; + isTrust?: boolean; + isTrustWallet?: boolean; + isOKExWallet?: boolean; + isOkxWallet?: boolean; + isCoinbaseWallet?: boolean; + isTokenPocket?: boolean; + isBitkeep?: boolean; + isBitgetWallet?: boolean; + providers?: EthProvider[]; +}; + // Detect the best available EVM provider across all major wallets +// Priority: TokenPocket > OKX > Bitget > Trust Wallet > MetaMask > others export function detectProvider(): Eip1193Provider | null { if (typeof window === "undefined") return null; const w = window as unknown as Record; - const eth = w.ethereum as (Eip1193Provider & { - providers?: Eip1193Provider[]; - isMetaMask?: boolean; - isTrust?: boolean; - isOKExWallet?: boolean; - isCoinbaseWallet?: boolean; - }) | undefined; - if (!eth) { - // Fallback: check wallet-specific globals - if (w.okxwallet) return w.okxwallet as Eip1193Provider; - if (w.coinbaseWalletExtension) return w.coinbaseWalletExtension as Eip1193Provider; - return null; - } - - // If multiple providers are injected (common when multiple extensions installed) - if (eth.providers && Array.isArray(eth.providers) && eth.providers.length > 0) { - const metamask = eth.providers.find((p: Eip1193Provider & { isMetaMask?: boolean }) => p.isMetaMask); - return metamask ?? eth.providers[0]; - } - - return eth; -} - -// Check if MetaMask is installed but not yet initialized (no wallet created/imported) -export async function checkWalletReady(rawProvider: Eip1193Provider): Promise<{ ready: boolean; reason?: string }> { - try { - // eth_accounts is silent — if it returns empty array, wallet is installed but locked or not initialized - const accounts = await (rawProvider as { request: (args: { method: string }) => Promise }).request({ - method: "eth_accounts", - }); - // If we get here, the wallet is at least initialized (even if locked / no accounts) - return { ready: true }; - } catch (err: unknown) { - const error = err as { code?: number; message?: string }; - // -32002: Request already pending (MetaMask not initialized or another request pending) - if (error?.code === -32002) { - return { ready: false, reason: "pending" }; + // 1. TokenPocket — injects window.ethereum with isTokenPocket flag + const eth = w.ethereum as EthProvider | undefined; + if (eth) { + // Check providers array first (multiple extensions installed) + if (eth.providers && Array.isArray(eth.providers) && eth.providers.length > 0) { + // Priority order for Chinese users + const tp = eth.providers.find((p: EthProvider) => p.isTokenPocket); + if (tp) return tp; + const okx = eth.providers.find((p: EthProvider) => p.isOKExWallet || p.isOkxWallet); + if (okx) return okx; + const bitget = eth.providers.find((p: EthProvider) => p.isBitkeep || p.isBitgetWallet); + if (bitget) return bitget; + const trust = eth.providers.find((p: EthProvider) => p.isTrust || p.isTrustWallet); + if (trust) return trust; + const metamask = eth.providers.find((p: EthProvider) => p.isMetaMask); + if (metamask) return metamask; + return eth.providers[0]; } - // Any other error — treat as not ready - return { ready: false, reason: error?.message || "unknown" }; + + // Single provider — return it directly + return eth; } + + // 2. OKX Wallet — sometimes injects window.okxwallet separately + if (w.okxwallet) return w.okxwallet as Eip1193Provider; + + // 3. Bitget Wallet — sometimes injects window.bitkeep.ethereum + const bitkeep = w.bitkeep as { ethereum?: Eip1193Provider } | undefined; + if (bitkeep?.ethereum) return bitkeep.ethereum; + + // 4. Coinbase Wallet + if (w.coinbaseWalletExtension) return w.coinbaseWalletExtension as Eip1193Provider; + + return null; } // Build wallet state from a provider and accounts @@ -136,7 +143,7 @@ export function useWallet() { const rawProvider = detectProvider(); if (!rawProvider) { - const msg = "未检测到钱包插件。请安装 MetaMask 或其他 EVM 兼容钱包后刷新页面。"; + const msg = "未检测到钱包插件。请安装 TokenPocket、MetaMask 或其他 EVM 兼容钱包后刷新页面。"; if (mountedRef.current) setState(s => ({ ...s, error: msg })); return { success: false, error: msg }; } @@ -168,16 +175,10 @@ export function useWallet() { // User rejected msg = "已取消连接 / Connection cancelled"; } else if (error?.code === -32002) { - // MetaMask has a pending request — usually means it's not initialized or popup is already open - msg = "钱包请求处理中,请检查 MetaMask 弹窗。如未弹出,请先完成 MetaMask 初始化设置(创建或导入钱包),然后刷新页面重试。"; + // Wallet has a pending request + msg = "钱包请求处理中,请检查钱包弹窗。如未弹出,请先完成钱包初始化设置,然后刷新页面重试。"; } else if (error?.message === "no_accounts") { msg = "未获取到账户,请确认钱包已解锁并授权此网站。"; - } else if ( - error?.message?.toLowerCase().includes("not initialized") || - error?.message?.toLowerCase().includes("setup") || - error?.message?.toLowerCase().includes("onboarding") - ) { - msg = "MetaMask 尚未完成初始化。请先打开 MetaMask 扩展,创建或导入钱包,然后刷新页面重试。"; } else { msg = `连接失败: ${error?.message || "未知错误"}。请刷新页面重试。`; } @@ -226,8 +227,9 @@ export function useWallet() { const rawProvider = detectProvider(); if (!rawProvider) { - if (attempt < 3) { - retryRef.current = setTimeout(() => tryAutoDetect(attempt + 1), 800 * attempt); + if (attempt < 5) { + // Retry more times — some wallets inject later (especially mobile in-app browsers) + retryRef.current = setTimeout(() => tryAutoDetect(attempt + 1), 600 * attempt); } return; } @@ -242,11 +244,14 @@ export function useWallet() { if (!cancelled && mountedRef.current) { setState({ ...INITIAL_STATE, ...partial }); } - } else if (attempt < 3) { - retryRef.current = setTimeout(() => tryAutoDetect(attempt + 1), 1000 * attempt); + } else if (attempt < 5) { + retryRef.current = setTimeout(() => tryAutoDetect(attempt + 1), 800 * attempt); } } catch { // Silently ignore — user hasn't connected yet + if (attempt < 3) { + retryRef.current = setTimeout(() => tryAutoDetect(attempt + 1), 1000); + } } }; diff --git a/client/src/lib/contracts.ts b/client/src/lib/contracts.ts index 227e742..bffda2a 100644 --- a/client/src/lib/contracts.ts +++ b/client/src/lib/contracts.ts @@ -13,7 +13,7 @@ export const CONTRACTS = { rpcUrl: "https://bsc-dataseed1.binance.org/", explorerUrl: "https://bscscan.com", nativeCurrency: { name: "BNB", symbol: "BNB", decimals: 18 }, - presale: "0xc65e7a2738ed884db8d26a6eb2fecf7daca2e90c", + presale: "0x5953c025dA734e710886916F2d739A3A78f8bbc4", // XICPresale v2 — 购买即时发放 token: "0x59FF34dD59680a7125782b1f6df2A86ed46F5A24", usdt: "0x55d398326f99059fF775485246999027B3197955", }, @@ -50,9 +50,9 @@ export const PRESALE_CONFIG = { tokenName: "New AssetChain Token", tokenDecimals: 18, minPurchaseUSDT: 0, // No minimum purchase limit - maxPurchaseUSDT: 50000, // Maximum $50,000 USDT + maxPurchaseUSDT: 50000, // Max $50,000 USDT per purchase totalSupply: 100_000_000_000, // 100 billion XIC - presaleAllocation: 30_000_000_000, // 30 billion for presale + presaleAllocation: 2_500_000_000, // 2.5 billion for presale (25亿) // TRC20 memo format trc20Memo: "XIC_PRESALE", }; diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index c2a86f7..819001c 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -334,7 +334,8 @@ function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; l const usdtAmount = parseFloat(usdtInput) || 0; const tokenAmount = calcTokens(usdtAmount); - const isValidAmount = usdtAmount > 0 && usdtAmount <= PRESALE_CONFIG.maxPurchaseUSDT; + // maxPurchaseUSDT=0 means no limit; otherwise check against the limit + const isValidAmount = usdtAmount > 0 && (PRESALE_CONFIG.maxPurchaseUSDT === 0 || usdtAmount <= PRESALE_CONFIG.maxPurchaseUSDT); const handleBuy = async () => { if (!isValidAmount) { @@ -516,8 +517,59 @@ function EVMPurchasePanel({ network, lang, wallet }: { network: "BSC" | "ETH"; l

- {t("buy_no_min_max")} ${PRESALE_CONFIG.maxPurchaseUSDT.toLocaleString()} USDT + {PRESALE_CONFIG.maxPurchaseUSDT > 0 + ? `${t("buy_no_min_max")} $${PRESALE_CONFIG.maxPurchaseUSDT.toLocaleString()} USDT` + : (lang === "zh" ? "无最低/最高购买限制" : "No minimum or maximum purchase limit")}

+ + {/* Add XIC to Wallet button — only show on BSC where token address is known */} + {network === "BSC" && CONTRACTS.BSC.token && ( + + )} ); } diff --git a/server/onchain.ts b/server/onchain.ts index b6c7387..d3730d3 100644 --- a/server/onchain.ts +++ b/server/onchain.ts @@ -38,7 +38,7 @@ const RPC_POOLS = { // ─── Contract Addresses ──────────────────────────────────────────────────────── export const CONTRACTS = { BSC: { - presale: "0xc65e7a2738ed884db8d26a6eb2fecf7daca2e90c", + presale: "0x5953c025dA734e710886916F2d739A3A78f8bbc4", // XICPresale v2 token: "0x59ff34dd59680a7125782b1f6df2a86ed46f5a24", rpc: RPC_POOLS.BSC[0], chainId: 56, diff --git a/test-onchain.mjs b/test-onchain.mjs new file mode 100644 index 0000000..7b3c4aa --- /dev/null +++ b/test-onchain.mjs @@ -0,0 +1,113 @@ +/** + * 测试链上数据读取 + * 直接调用BSC和ETH合约,查看能读到哪些数据 + */ +import { ethers } from "ethers"; + +const BSC_PRESALE = "0xc65e7a2738ed884db8d26a6eb2fecf7daca2e90c"; +const ETH_PRESALE = "0x85AB2F2d9f7ca7ecB272b5E8726c70f3fd45D1E3"; + +// 尝试多种可能的函数名 +const TEST_ABI = [ + "function totalUSDTRaised() view returns (uint256)", + "function totalTokensSold() view returns (uint256)", + "function weiRaised() view returns (uint256)", + "function tokensSold() view returns (uint256)", + "function usdtRaised() view returns (uint256)", + "function totalRaised() view returns (uint256)", + "function amountRaised() view returns (uint256)", + "function hardCap() view returns (uint256)", + "function cap() view returns (uint256)", + "function owner() view returns (address)", + "function paused() view returns (bool)", +]; + +async function testContract(name, address, rpcUrl) { + console.log(`\n=== 测试 ${name} 合约 ===`); + console.log(`地址: ${address}`); + console.log(`RPC: ${rpcUrl}`); + + try { + const provider = new ethers.JsonRpcProvider(rpcUrl, undefined, { + staticNetwork: true, + polling: false, + }); + + // 先检查合约是否存在 + const code = await provider.getCode(address); + if (code === "0x") { + console.log("❌ 该地址没有合约代码!合约地址可能错误。"); + return; + } + console.log(`✅ 合约存在,字节码长度: ${code.length} 字符`); + + const contract = new ethers.Contract(address, TEST_ABI, provider); + + // 逐个测试函数 + const functions = [ + "totalUSDTRaised", + "totalTokensSold", + "weiRaised", + "tokensSold", + "usdtRaised", + "totalRaised", + "amountRaised", + "hardCap", + "cap", + "owner", + "paused", + ]; + + for (const fn of functions) { + try { + const result = await contract[fn](); + console.log(` ✅ ${fn}() = ${result}`); + } catch (e) { + console.log(` ❌ ${fn}() 不存在或调用失败`); + } + } + + // 获取合约事件日志(最近100个块) + const latestBlock = await provider.getBlockNumber(); + console.log(`\n当前区块高度: ${latestBlock}`); + + // 查找Transfer事件(USDT转入) + const usdtAddress = name === "BSC" + ? "0x55d398326f99059fF775485246999027B3197955" + : "0xdAC17F958D2ee523a2206206994597C13D831ec7"; + + const usdtAbi = ["event Transfer(address indexed from, address indexed to, uint256 value)"]; + const usdtContract = new ethers.Contract(usdtAddress, usdtAbi, provider); + + console.log(`\n查询最近1000个块内转入预售合约的USDT...`); + const fromBlock = latestBlock - 1000; + const filter = usdtContract.filters.Transfer(null, address); + + try { + const events = await usdtContract.queryFilter(filter, fromBlock, latestBlock); + console.log(`找到 ${events.length} 笔USDT转入记录`); + + let totalUsdt = 0n; + for (const event of events.slice(-5)) { + const args = event.args; + const amount = args[2]; + totalUsdt += amount; + const decimals = name === "BSC" ? 18 : 6; + console.log(` ${args[0]} → ${ethers.formatUnits(amount, decimals)} USDT`); + } + } catch (e) { + console.log(`查询事件失败: ${e}`); + } + + } catch (e) { + console.error(`测试失败: ${e}`); + } +} + +// 测试BSC +await testContract("BSC", BSC_PRESALE, "https://bsc-dataseed1.binance.org/"); + +// 测试ETH +await testContract("ETH", ETH_PRESALE, "https://eth.llamarpc.com"); + +console.log("\n=== 测试完成 ===");