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:
parent
772ade225f
commit
0594e4fdd8
|
|
@ -1,4 +1,4 @@
|
|||
// NAC XIC Presale — Purchase Logic Hook v2
|
||||
// NAC XIC Presale — Purchase Logic Hook v3 (Refactored)
|
||||
// 适配新合约 XICPresale(购买即时发放版本)
|
||||
// 关键变更:
|
||||
// - 函数名: buyTokensWithUSDT → buyWithUSDT
|
||||
|
|
@ -6,10 +6,14 @@
|
|||
// - BSC USDT 精度: 18 decimals(保持不变,BSC USDT 是 18d)
|
||||
// - 新增: 从链上读取实时预售状态(剩余时间、进度等)
|
||||
// - 新增: BNB 购买支持
|
||||
// v3 重构变更:
|
||||
// - 提取公共函数 extractPurchasedAmount:消除 buyWithUSDT/buyWithBNB 中重复的事件解析逻辑
|
||||
// - 提取公共函数 handleTxError:消除两个购买函数中重复的错误处理逻辑
|
||||
// - 提取常量 getUsdtDecimals:消除多处重复的 network === "ETH" ? 6 : 18 判断
|
||||
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
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";
|
||||
|
||||
export type PurchaseStep =
|
||||
|
|
@ -40,6 +44,48 @@ export interface PresaleStats {
|
|||
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") {
|
||||
const [purchaseState, setPurchaseState] = useState<PurchaseState>({
|
||||
step: "idle",
|
||||
|
|
@ -120,7 +166,7 @@ export function usePresale(wallet: WalletState, network: "BSC" | "ETH") {
|
|||
return () => clearInterval(interval);
|
||||
}, [fetchPresaleStats]);
|
||||
|
||||
// ── 用 USDT 购买(新合约函数名: buyWithUSDT)──────────────────
|
||||
// ── 用 USDT 购买(合约函数名: buyWithUSDT)──────────────────
|
||||
const buyWithUSDT = useCallback(
|
||||
async (usdtAmount: number) => {
|
||||
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 });
|
||||
|
||||
try {
|
||||
// BSC USDT 是 18 decimals
|
||||
const usdtDecimals = network === "ETH" ? 6 : 18;
|
||||
const usdtDecimals = getUsdtDecimals(network);
|
||||
const usdtAmountWei = parseUnits(usdtAmount.toString(), usdtDecimals);
|
||||
|
||||
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" }));
|
||||
|
||||
// Step 2: 调用新合约的 buyWithUSDT(不是 buyTokensWithUSDT)
|
||||
// Step 2: 调用合约 buyWithUSDT
|
||||
const presaleContract = new Contract(presaleAddress, PRESALE_ABI, wallet.signer);
|
||||
const buyTx = await presaleContract.buyWithUSDT(usdtAmountWei);
|
||||
setPurchaseState(s => ({ ...s, step: "purchasing", txHash: buyTx.hash }));
|
||||
|
||||
const receipt = await buyTx.wait();
|
||||
|
||||
// 从事件中读取实际收到的 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 */ }
|
||||
}
|
||||
}
|
||||
const actualTokenAmount = extractPurchasedAmount(receipt, presaleContract, tokenAmount);
|
||||
|
||||
setPurchaseState(s => ({ ...s, step: "success", tokenAmount: actualTokenAmount }));
|
||||
|
||||
// 刷新预售状态
|
||||
await fetchPresaleStats();
|
||||
|
||||
} 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 }));
|
||||
setPurchaseState(s => ({ ...s, step: "error", error: extractErrorMessage(err) }));
|
||||
}
|
||||
},
|
||||
[wallet, network, networkConfig, fetchPresaleStats]
|
||||
);
|
||||
|
||||
// ── 用 BNB 购买(新合约函数名: buyWithBNB)──────────────────
|
||||
// ── 用 BNB 购买(合约函数名: buyWithBNB)──────────────────
|
||||
const buyWithBNB = useCallback(
|
||||
async (bnbAmount: number) => {
|
||||
if (!wallet.signer || !wallet.address) {
|
||||
|
|
@ -204,27 +232,13 @@ export function usePresale(wallet: WalletState, network: "BSC" | "ETH") {
|
|||
setPurchaseState(s => ({ ...s, txHash: buyTx.hash }));
|
||||
|
||||
const receipt = await buyTx.wait();
|
||||
|
||||
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 */ }
|
||||
}
|
||||
}
|
||||
const actualTokenAmount = extractPurchasedAmount(receipt, presaleContract, estimatedTokens);
|
||||
|
||||
setPurchaseState(s => ({ ...s, step: "success", tokenAmount: actualTokenAmount }));
|
||||
await fetchPresaleStats();
|
||||
|
||||
} 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 }));
|
||||
setPurchaseState(s => ({ ...s, step: "error", error: extractErrorMessage(err) }));
|
||||
}
|
||||
},
|
||||
[wallet, networkConfig, presaleStats.bnbPrice, fetchPresaleStats]
|
||||
|
|
@ -249,10 +263,9 @@ export function usePresale(wallet: WalletState, network: "BSC" | "ETH") {
|
|||
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));
|
||||
return parseFloat(formatUnits(balance, getUsdtDecimals(network)));
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue