Розробка дашборда токенсейла
Токенсейл дашборд — це не лендинг з кнопкою "купити". Це інтерфейс, який повинен працювати безвідмовно саме тоді, коли навантаження максимальне: перші години після відкриття продаж. В цей момент десятки тисяч користувачів одночасно підключають гаманці, перевіряють whitelist, одобрюють токени, відправляють транзакції — і чекають. Будь-яка помилка в UI/UX в цей момент коштує втрачених продажів і репутаційного ущербу.
Смарт-контракт: що потрібно розуміти
Дашборд — це frontend до sale контракту. Розуміти контракт потрібно повністю, не частково. Типовий sale контракт включає:
Sale параметри:
struct SaleConfig {
uint256 startTime;
uint256 endTime;
uint256 tokenPrice; // ціна в USD/ETH за 1 токен
uint256 minPurchase; // мінімальна покупка
uint256 maxPurchase; // максимум на адресу
uint256 hardCap; // загальний hardcap
uint256 softCap; // якщо не достигнут — refund
address paymentToken; // USDC/USDT/ETH (address(0) для ETH)
bool whitelistEnabled;
bytes32 merkleRoot; // root для whitelist верифікації
}
Ключові подій:
event TokensPurchased(address indexed buyer, uint256 paymentAmount, uint256 tokenAmount);
event SaleStarted(uint256 startTime, uint256 endTime);
event HardCapReached(uint256 totalRaised);
event RefundClaimed(address indexed buyer, uint256 amount);
Дашборд повинен слухати ці подій реально-часово і оновляти UI без перезагрузки сторінки.
Архітектура дашборда
Стан продажи: state machine
Sale контракт проходить через стани. UI повинен коректно відображати кожний:
Not Started → Active → Ended (Success) → Distribution
→ Ended (Failed) → Refund Available
Для WhitelistOnly sale додається:
Whitelist Round → Public Round → Ended
type SalePhase =
| 'not_started'
| 'whitelist_only'
| 'public'
| 'ended_success'
| 'ended_failed'
| 'distribution'
| 'refund_available';
function getSalePhase(
startTime: bigint,
endTime: bigint,
totalRaised: bigint,
softCap: bigint,
hardCap: bigint,
now: bigint
): SalePhase {
// ...
}
Whitelist верифікація через Merkle Proof
Merkle tree дозволяє верифіцировать on-chain що адреса в whitelist, не храня весь список on-chain.
Real-time дані: polling vs events
Event subscription через WebSocket — оптимально для real-time progress:
const provider = new ethers.WebSocketProvider(WS_RPC_URL);
const saleContract = new ethers.Contract(SALE_ADDRESS, SALE_ABI, provider);
saleContract.on('TokensPurchased', (buyer, paymentAmount, tokenAmount, event) => {
setTotalRaised(prev => prev + paymentAmount);
setParticipantCount(prev => prev + 1);
});
Polling як fallback — WebSocket з'єднання нестабільні. Резервний polling кожні 15 секунд для критичних даних.
Оптимізація RPC вызовів через Multicall.
Покупка: UX flow
Мультивалютні платежи
Більшість sale приймають кілька токенів. Для кожного потрібна перевірка approval:
async function handlePurchase(
paymentToken: string,
paymentAmount: bigint,
proof: string[]
) {
// ... логіка покупки
}
Transaction simulation перед відправкою (staticCall) — обов'язкова. Дозволяє показати користувачу очікуваний результат і поймати помилки без витрат на gas.
Gas estimation та EIP-1559
async function estimateGasWithBuffer(tx: ContractTransaction) {
const estimated = await provider.estimateGas(tx);
return (estimated * 120n) / 100n; // +20% буфер
}
UI компоненти
Progress Bar
Hardcap progress — центральний елемент інтерфейсу. Повинен оновлюватися реально-часово без мерцання.
Countdown Timer
Синхронізація з блокчейн часом, не з системними годинами:
function useBlockchainCountdown(targetTimestamp: bigint) {
const [timeLeft, setTimeLeft] = useState<number>(0);
const { data: blockNumber } = useBlockNumber({ watch: true });
useEffect(() => {
const now = BigInt(Math.floor(Date.now() / 1000));
const diff = Number(targetTimestamp - now);
setTimeLeft(Math.max(0, diff));
}, [blockNumber, targetTimestamp]); // оновлюємо з кожним новим блоком
return timeLeft;
}
Продуктивність при пиковій нагрузці
Перші хвилини продаж — максимальна нагрузка на RPC і frontend. Підготовка:
- RPC rate limits: публічні ноди упадуть. Потрібен Alchemy/QuickNode з enterprise лімітами або власна нода
- CDN для статики: JS bundle, зображення — через Cloudflare
- Optimistic UI: показуємо передбачуваний статус до підтвердження транзакції
- Queue visualization: якщо продажи дуже гарячі — показуємо чергу транзакцій, pending count
- Кэширование: static дані (tokenomics, allocation table) — кэшуємо, не запрошуємо щоразу







