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(购买即时发放版本)
|
// 适配新合约 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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue