Инвестор, участвовавший в private round, получает vesting-контракт с графиком разблокировки. Но как ему отслеживать, когда можно забрать токены? Etherscan показывает raw-данные, но неудобен: нет сводки по всем контрактам, нет уведомлений, сложно делать claim. Мы решаем эту проблему — разрабатываем vesting-панели, которые агрегируют данные из всех цепочек в единый интерфейс. За много лет работы создали более 30 таких панелей для токенсейлов и венчурных фондов.
Типичная ситуация: у инвестора 20 контрактов на Ethereum, Arbitrum и Polygon. Каждый имеет свой график (cliff, duration). Вручную следить за разлоками невозможно. Панель показывает общую сумму заблокированных токенов ($5 млн), ближайший разлок (через 7 дней), и позволяет одним кликом забрать всё доступное. В результате нагрузка на поддержку снижается на 80%, а инвесторы довольны.
Почему инвестору нужен личный кабинет, а не Etherscan?
Etherscan показывает raw-данные контракта, но не удобен:
- Нет сводной информации по всем контрактам инвестора.
- Не показывает releasable в человекочитаемом виде.
- Отсутствуют уведомления о новых разлоках.
- Невозможно сделать claim без переключения между контрактами.
Готовая панель решает эти проблемы и снижает количество обращений в саппорт в 5-10 раз.
Архитектура панели
Минимальный набор данных для каждого инвестора
- Total allocation — сколько токенов выделено; Vested — сколько разблокировалось к текущему моменту; Released — сколько уже забрано; Releasable — сколько можно забрать прямо сейчас; Locked — ещё под vesting; Vesting schedule — график разблокировки (визуально); Next unlock — когда следующий разлок и сколько.
Как эффективно читать данные из контрактов?
Если используется OpenZeppelin VestingWallet:
import { createPublicClient, http, parseAbi } from "viem";
const VESTING_ABI = parseAbi([
"function beneficiary() view returns (address)",
"function start() view returns (uint64)",
"function duration() view returns (uint64)",
"function released(address token) view returns (uint256)",
"function releasable(address token) view returns (uint256)",
"function vestedAmount(address token, uint64 timestamp) view returns (uint256)",
]);
async function getVestingData(
vestingAddress: `0x${string}`,
tokenAddress: `0x${string}`,
client: PublicClient
) {
const [start, duration, released, releasable] = await client.multicall({
contracts: [
{ address: vestingAddress, abi: VESTING_ABI, functionName: "start" },
{ address: vestingAddress, abi: VESTING_ABI, functionName: "duration" },
{
address: vestingAddress,
abi: VESTING_ABI,
functionName: "released",
args: [tokenAddress],
},
{
address: vestingAddress,
abi: VESTING_ABI,
functionName: "releasable",
args: [tokenAddress],
},
],
});
// Общий allocation = баланс контракта + уже released
const balance = await client.readContract({
address: tokenAddress,
abi: parseAbi(["function balanceOf(address) view returns (uint256)"]),
functionName: "balanceOf",
args: [vestingAddress],
});
const totalAllocation = balance.result! + released.result!;
return {
start: Number(start.result),
duration: Number(duration.result),
released: released.result!,
releasable: releasable.result!,
totalAllocation,
locked: totalAllocation - released.result! - releasable.result!,
};
}
multicall — обязательно использовать для batch запросов. Один вызов к ноде вместо четырёх-пяти последовательных — это ускоряет получение данных в 4-5 раз, критично для производительности при нескольких контрактах.
Frontend: компонент графика vesting
Визуализация schedule помогает инвестору понять когда и сколько он получит:
import { LineChart, Line, XAxis, YAxis, Tooltip, ReferenceLine } from "recharts";
import { formatUnits } from "viem";
function VestingChart({ start, cliffDuration, vestingDuration, totalAllocation, decimals }) {
const cliffEnd = start + cliffDuration;
const vestingEnd = cliffEnd + vestingDuration;
const now = Date.now() / 1000;
// Генерируем точки для графика
const dataPoints = [];
const step = vestingDuration / 30; // 30 точек на период vesting
for (let t = start; t <= vestingEnd; t += step) {
let vested = 0;
if (t >= cliffEnd) {
const elapsed = Math.min(t - cliffEnd, vestingDuration);
vested = Number(formatUnits(
BigInt(Math.floor(Number(totalAllocation) * elapsed / vestingDuration)),
decimals
));
}
dataPoints.push({
date: new Date(t * 1000).toLocaleDateString("ru-RU", { month: "short", year: "2-digit" }),
vested,
});
}
return (
<LineChart width={600} height={300} data={dataPoints}>
<XAxis dataKey="date" tick={{ fontSize: 11 }} />
<YAxis tickFormatter={(v) => `${(v / 1000).toFixed(0)}k`} />
<Tooltip
formatter={(value) => [`${Number(value).toLocaleString()} tokens`, "Vested"]}
/>
<ReferenceLine
x={new Date(now * 1000).toLocaleDateString("ru-RU", { month: "short", year: "2-digit" })}
stroke="#f59e0b"
label={{ value: "Сейчас", position: "top" }}
/>
{cliffDuration > 0 && (
<ReferenceLine
x={new Date(cliffEnd * 1000).toLocaleDateString("ru-RU", { month: "short", year: "2-digit" })}
stroke="#6366f1"
strokeDasharray="4 4"
label={{ value: "Cliff", position: "top" }}
/>
)}
<Line type="monotone" dataKey="vested" stroke="#10b981" strokeWidth={2} dot={false} />
</LineChart>
);
}
Как реализовать claim транзакцию?
Кнопка "Claim" должна корректно обрабатывать все состояния:
import { useWriteContract, useWaitForTransactionReceipt } from "wagmi";
function ClaimButton({ vestingAddress, tokenAddress, releasable, decimals }) {
const { writeContract, data: txHash, isPending, error } = useWriteContract();
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
hash: txHash,
});
const handleClaim = () => {
writeContract({
address: vestingAddress,
abi: VESTING_ABI,
functionName: "release",
args: [tokenAddress],
});
};
const formattedReleasable = Number(formatUnits(releasable, decimals)).toLocaleString();
if (releasable === 0n) {
return <Button disabled>Нечего забирать</Button>;
}
return (
<div>
<Button
onClick={handleClaim}
disabled={isPending || isConfirming}
>
{isPending ? "Подтвердите в кошельке..." :
isConfirming ? "Ожидание подтверждения..." :
`Забрать ${formattedReleasable} токенов`}
</Button>
{isSuccess && (
<p className="text-green-600">
Успешно! {" "}
<a href={`https://etherscan.io/tx/${txHash}`} target="_blank" rel="noreferrer">
Транзакция
</a>
</p>
)}
{error && <p className="text-red-600">{error.shortMessage}</p>}
</div>
);
}
Multi-wallet и multi-chain поддержка
Инвесторы используют разные кошельки (MetaMask, WalletConnect, Coinbase Wallet, Ledger). wagmi v2 с ConnectKit или RainbowKit обрабатывает это. Если проект задеплоен на нескольких сетях, инвестор должен видеть все свои vesting контракты в одном месте:
const NETWORKS = [
{ chainId: 1, name: "Ethereum", client: mainnetClient },
{ chainId: 42161, name: "Arbitrum", client: arbitrumClient },
];
async function getAllVestings(investorAddress: string) {
const results = await Promise.all(
NETWORKS.map(async (network) => {
const vestingAddress = VESTING_CONTRACTS[network.chainId]?.[investorAddress];
if (!vestingAddress) return null;
const data = await getVestingData(vestingAddress, TOKEN_ADDRESS, network.client);
return { ...data, network: network.name, chainId: network.chainId, vestingAddress };
})
);
return results.filter(Boolean);
}
Инвестор-специфические фичи
- Email / Telegram уведомления об разлоках: за 7 дней до cliff, за 24 часа до каждого значимого unlock. Требует off-chain сервис, который мониторит блокчейн и отправляет уведомления.
-
CSV экспорт для налоговой отчётности: история всех claim транзакций с датами и суммами. Берётся из событий
ERC20TransferилиTokensReleasedчерез getLogs или indexer (The Graph). - Whitelist check: если токен нельзя продавать до определённой даты (lock-up дополнительный к vesting), это не всегда отражено в vesting контракте — может быть в самом токене. Панель должна это показывать.
Как работает аутентификация?
Для вестинг панели аутентификация через кошелёк (Sign-In with Ethereum, EIP-4361) достаточна и предпочтительна — нет паролей, нет баз пользователей.
import { SiweMessage } from "siwe";
// На клиенте
async function signIn(address: string, chainId: number) {
const nonce = await fetch("/api/nonce").then((r) => r.text());
const message = new SiweMessage({
domain: window.location.host,
address,
statement: "Sign in to view your vesting schedule",
uri: window.location.origin,
version: "1",
chainId,
nonce,
});
const signature = await walletClient.signMessage({
message: message.prepareMessage(),
});
await fetch("/api/verify", {
method: "POST",
body: JSON.stringify({ message, signature }),
});
}
Что включает разработка под ключ?
Мы предоставляем готовое решение, которое включает:
- Backend сервер для агрегации данных и нотификаций (Node.js, NestJS, Prisma).
- Frontend на Next.js 14 с TypeScript, wagmi v2, RainbowKit.
- Интеграцию с любым vesting-контрактом (OpenZeppelin, простые ABI).
- Multi-chain обёртку для 3+ сетей.
- SIWE-аутентификацию.
- CSV-экспорт транзакций.
- Развёртывание в облаке (AWS, DigitalOcean) и настройку CI/CD.
- Обучение команды и документацию API.
Стек и сравнение подходов
Таблица стека
| Компонент | Технология |
|---|---|
| Frontend | Next.js 14 + TypeScript |
| Web3 | wagmi v2 + viem + RainbowKit |
| Чтение данных | viem multicall + The Graph (опц.) |
| Графики | Recharts или Victory |
| Auth | SIWE (EIP-4361) |
| Уведомления | cron-сервис + SendGrid / Telegram Bot API |
Сравнение: on-chain vs off-chain расчёт vestedAmount
| Критерий | On-chain (viem multicall) | Off-chain (cron + DB) |
|---|---|---|
| Задержка данных | Реал-тайм (после каждого блока) | До 15 минут |
| Нагрузка на RPC | Высокая при многих запросах | Низкая (кешированные данные) |
| Сложность разработки | Низкая (только клиент) | Средняя (требует инфраструктуры) |
| Поддержка истории | Нет (только текущее состояние) | Да (храним историю изменений) |
| Идеальный сценарий | MVP, небольшая аудитория | Продакшен с тысячами инвесторов |
On-chain подход проще, но при большой нагрузке off-chain надёжнее. Для крупных проектов рекомендуем off-chain — свяжитесь с нами для детального обсуждения.
Сроки и условия
Срок разработки MVP (read-only dashboard + claim): 2–3 недели. Полная панель с уведомлениями, multi-chain, CSV экспортом — 4–6 недель. Стоимость рассчитывается индивидуально — свяжитесь с нами, чтобы мы оценили ваш проект.
Опыт команды
Мы занимаемся блокчейн-разработкой много лет. За это время реализовали 30+ проектов с vesting-механиками, включая токенсейлы панели для DeFi-протоколов и венчурных фондов. Все решения проходят аудит безопасности с помощью Slither и Echidna.
Закажите разработку панели, адаптированной под вашу структуру контрактов. Свяжитесь с нами — поможем спроектировать панель под ваши контракты и бизнес-логику.







