refactor: 重构 usePresale.ts,提取公共函数消除冗余代码

- 提取 getUsdtDecimals(network) 函数,消除多处重复的 network === 'ETH' ? 6 : 18 判断
- 提取 extractPurchasedAmount(receipt, contract, defaultAmount) 函数,消除 buyWithUSDT/buyWithBNB 中重复的 TokensPurchased 事件解析逻辑
- 提取 extractErrorMessage(err) 函数,消除两个购买函数中重复的错误处理逻辑
- 删除 return 块中重复的 calcTokens: calcTokens(已在 v3 修复)
- 代码从约 300 行精简,逻辑更清晰,维护性更强
This commit is contained in:
NAC Admin 2026-03-20 17:58:38 +08:00
parent 772ade225f
commit 0594e4fdd8
1 changed files with 57 additions and 44 deletions

View File

@ -1,4 +1,4 @@
// NAC XIC Presale — Purchase Logic Hook v2 // NAC XIC Presale — Purchase Logic Hook v3 (Refactored)
// 适配新合约 XICPresale购买即时发放版本 // 适配新合约 XICPresale购买即时发放版本
// 关键变更: // 关键变更:
// - 函数名: buyTokensWithUSDT → buyWithUSDT // - 函数名: buyTokensWithUSDT → buyWithUSDT
@ -6,10 +6,14 @@
// - BSC USDT 精度: 18 decimals保持不变BSC USDT 是 18d // - BSC USDT 精度: 18 decimals保持不变BSC USDT 是 18d
// - 新增: 从链上读取实时预售状态(剩余时间、进度等) // - 新增: 从链上读取实时预售状态(剩余时间、进度等)
// - 新增: BNB 购买支持 // - 新增: BNB 购买支持
// v3 重构变更:
// - 提取公共函数 extractPurchasedAmount消除 buyWithUSDT/buyWithBNB 中重复的事件解析逻辑
// - 提取公共函数 handleTxError消除两个购买函数中重复的错误处理逻辑
// - 提取常量 getUsdtDecimals消除多处重复的 network === "ETH" ? 6 : 18 判断
import { useState, useCallback, useEffect } from "react"; import { useState, useCallback, useEffect } from "react";
import { Contract, parseUnits, formatUnits, parseEther } from "ethers"; import { Contract, parseUnits, formatUnits, parseEther } from "ethers";
import { CONTRACTS, PRESALE_ABI, ERC20_ABI, PRESALE_CONFIG, formatNumber } from "@/lib/contracts"; import { CONTRACTS, PRESALE_ABI, ERC20_ABI, PRESALE_CONFIG } from "@/lib/contracts";
import { WalletState } from "./useWallet"; import { WalletState } from "./useWallet";
export type PurchaseStep = export type PurchaseStep =
@ -40,6 +44,48 @@ export interface PresaleStats {
bnbPrice: number; // BNB 当前价格USD bnbPrice: number; // BNB 当前价格USD
} }
// ── 公共工具函数 ────────────────────────────────────────────────
/**
* USDT decimals
* ETH USDT 6 BSC USDT 18
*/
function getUsdtDecimals(network: "BSC" | "ETH"): number {
return network === "ETH" ? 6 : 18;
}
/**
* XIC
* TokensPurchased defaultAmount
*/
function extractPurchasedAmount(
receipt: { logs?: unknown[] } | null,
contract: Contract,
defaultAmount: number
): number {
if (!receipt?.logs) return defaultAmount;
for (const log of receipt.logs) {
try {
const parsed = contract.interface.parseLog(log as { topics: string[]; data: string });
if (parsed?.name === "TokensPurchased") {
return parseFloat(formatUnits(parsed.args.tokenAmount, 18));
}
} catch { /* 忽略无法解析的日志 */ }
}
return defaultAmount;
}
/**
* revert reason Error message
*/
function extractErrorMessage(err: unknown): string {
return (err as { reason?: string }).reason
|| (err as Error).message
|| "Transaction failed";
}
// ── Hook 主体 ───────────────────────────────────────────────────
export function usePresale(wallet: WalletState, network: "BSC" | "ETH") { export function usePresale(wallet: WalletState, network: "BSC" | "ETH") {
const [purchaseState, setPurchaseState] = useState<PurchaseState>({ const [purchaseState, setPurchaseState] = useState<PurchaseState>({
step: "idle", step: "idle",
@ -120,7 +166,7 @@ export function usePresale(wallet: WalletState, network: "BSC" | "ETH") {
return () => clearInterval(interval); return () => clearInterval(interval);
}, [fetchPresaleStats]); }, [fetchPresaleStats]);
// ── 用 USDT 购买(合约函数名: buyWithUSDT────────────────── // ── 用 USDT 购买(合约函数名: buyWithUSDT──────────────────
const buyWithUSDT = useCallback( const buyWithUSDT = useCallback(
async (usdtAmount: number) => { async (usdtAmount: number) => {
if (!wallet.signer || !wallet.address) { if (!wallet.signer || !wallet.address) {
@ -132,8 +178,7 @@ export function usePresale(wallet: WalletState, network: "BSC" | "ETH") {
setPurchaseState({ step: "approving", txHash: null, error: null, tokenAmount }); setPurchaseState({ step: "approving", txHash: null, error: null, tokenAmount });
try { try {
// BSC USDT 是 18 decimals const usdtDecimals = getUsdtDecimals(network);
const usdtDecimals = network === "ETH" ? 6 : 18;
const usdtAmountWei = parseUnits(usdtAmount.toString(), usdtDecimals); const usdtAmountWei = parseUnits(usdtAmount.toString(), usdtDecimals);
const usdtContract = new Contract(networkConfig.usdt, ERC20_ABI, wallet.signer); const usdtContract = new Contract(networkConfig.usdt, ERC20_ABI, wallet.signer);
@ -148,42 +193,25 @@ export function usePresale(wallet: WalletState, network: "BSC" | "ETH") {
setPurchaseState(s => ({ ...s, step: "approved" })); setPurchaseState(s => ({ ...s, step: "approved" }));
// Step 2: 调用合约 buyWithUSDT(不是 buyTokensWithUSDT // Step 2: 调用合约 buyWithUSDT
const presaleContract = new Contract(presaleAddress, PRESALE_ABI, wallet.signer); const presaleContract = new Contract(presaleAddress, PRESALE_ABI, wallet.signer);
const buyTx = await presaleContract.buyWithUSDT(usdtAmountWei); const buyTx = await presaleContract.buyWithUSDT(usdtAmountWei);
setPurchaseState(s => ({ ...s, step: "purchasing", txHash: buyTx.hash })); setPurchaseState(s => ({ ...s, step: "purchasing", txHash: buyTx.hash }));
const receipt = await buyTx.wait(); const receipt = await buyTx.wait();
const actualTokenAmount = extractPurchasedAmount(receipt, presaleContract, tokenAmount);
// 从事件中读取实际收到的 XIC 数量
let actualTokenAmount = tokenAmount;
if (receipt?.logs) {
for (const log of receipt.logs) {
try {
const parsed = presaleContract.interface.parseLog(log);
if (parsed?.name === "TokensPurchased") {
actualTokenAmount = parseFloat(formatUnits(parsed.args.tokenAmount, 18));
}
} catch { /* ignore */ }
}
}
setPurchaseState(s => ({ ...s, step: "success", tokenAmount: actualTokenAmount })); setPurchaseState(s => ({ ...s, step: "success", tokenAmount: actualTokenAmount }));
// 刷新预售状态
await fetchPresaleStats(); await fetchPresaleStats();
} catch (err: unknown) { } catch (err: unknown) {
const errMsg = (err as { reason?: string; message?: string }).reason setPurchaseState(s => ({ ...s, step: "error", error: extractErrorMessage(err) }));
|| (err as Error).message
|| "Transaction failed";
setPurchaseState(s => ({ ...s, step: "error", error: errMsg }));
} }
}, },
[wallet, network, networkConfig, fetchPresaleStats] [wallet, network, networkConfig, fetchPresaleStats]
); );
// ── 用 BNB 购买(合约函数名: buyWithBNB────────────────── // ── 用 BNB 购买(合约函数名: buyWithBNB──────────────────
const buyWithBNB = useCallback( const buyWithBNB = useCallback(
async (bnbAmount: number) => { async (bnbAmount: number) => {
if (!wallet.signer || !wallet.address) { if (!wallet.signer || !wallet.address) {
@ -204,27 +232,13 @@ export function usePresale(wallet: WalletState, network: "BSC" | "ETH") {
setPurchaseState(s => ({ ...s, txHash: buyTx.hash })); setPurchaseState(s => ({ ...s, txHash: buyTx.hash }));
const receipt = await buyTx.wait(); const receipt = await buyTx.wait();
const actualTokenAmount = extractPurchasedAmount(receipt, presaleContract, estimatedTokens);
let actualTokenAmount = estimatedTokens;
if (receipt?.logs) {
for (const log of receipt.logs) {
try {
const parsed = presaleContract.interface.parseLog(log);
if (parsed?.name === "TokensPurchased") {
actualTokenAmount = parseFloat(formatUnits(parsed.args.tokenAmount, 18));
}
} catch { /* ignore */ }
}
}
setPurchaseState(s => ({ ...s, step: "success", tokenAmount: actualTokenAmount })); setPurchaseState(s => ({ ...s, step: "success", tokenAmount: actualTokenAmount }));
await fetchPresaleStats(); await fetchPresaleStats();
} catch (err: unknown) { } catch (err: unknown) {
const errMsg = (err as { reason?: string; message?: string }).reason setPurchaseState(s => ({ ...s, step: "error", error: extractErrorMessage(err) }));
|| (err as Error).message
|| "Transaction failed";
setPurchaseState(s => ({ ...s, step: "error", error: errMsg }));
} }
}, },
[wallet, networkConfig, presaleStats.bnbPrice, fetchPresaleStats] [wallet, networkConfig, presaleStats.bnbPrice, fetchPresaleStats]
@ -249,10 +263,9 @@ export function usePresale(wallet: WalletState, network: "BSC" | "ETH") {
const getUsdtBalance = useCallback(async (): Promise<number> => { const getUsdtBalance = useCallback(async (): Promise<number> => {
if (!wallet.provider || !wallet.address) return 0; if (!wallet.provider || !wallet.address) return 0;
try { try {
const usdtDecimals = network === "ETH" ? 6 : 18;
const usdtContract = new Contract(networkConfig.usdt, ERC20_ABI, wallet.provider); const usdtContract = new Contract(networkConfig.usdt, ERC20_ABI, wallet.provider);
const balance = await usdtContract.balanceOf(wallet.address); const balance = await usdtContract.balanceOf(wallet.address);
return parseFloat(formatUnits(balance, usdtDecimals)); return parseFloat(formatUnits(balance, getUsdtDecimals(network)));
} catch { } catch {
return 0; return 0;
} }