111 lines
3.5 KiB
TypeScript
111 lines
3.5 KiB
TypeScript
// 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,
|
|
};
|
|
}
|