Разработка системы аллокаций для токенсейла
Стандартная проблема при запуске токенсейла: либо whitelist переподписан в 20 раз и большинство участников получают ноль, либо аллокации распределены по принципу «first-come, first-served» и сбоку покупают боты. Система аллокаций — это механизм справедливого распределения права на покупку токенов до старта продажи.
Задача системы: определить, кто получает аллокацию, сколько, и обеспечить исполнение этого on-chain без возможности злоупотреблений.
Модели расчёта аллокаций
Lottery (случайная выборка)
Самый простой вариант: из N зарегистрированных выбираем K победителей случайно. Честно, но удача не коррелирует ни с вовлечённостью, ни с интересом к проекту.
Источник рандомности — это всегда проблема on-chain. Chainlink VRF v2 — правильное решение для production:
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
contract AllocationLottery is VRFConsumerBaseV2 {
uint256 public subscriptionId;
bytes32 public keyHash;
address[] public applicants;
address[] public winners;
uint256 public winnerCount;
mapping(uint256 => uint256) private requestToWinnerCount;
function drawWinners(uint256 count) external onlyOwner {
winnerCount = count;
uint256 requestId = COORDINATOR.requestRandomWords(
keyHash, subscriptionId, 3, 100000, 1
);
requestToWinnerCount[requestId] = count;
}
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords)
internal override
{
uint256 seed = randomWords[0];
uint256 count = requestToWinnerCount[requestId];
uint256 total = applicants.length;
// Fisher-Yates partial shuffle
address[] memory pool = applicants; // копия для мутации
for (uint256 i = 0; i < count; i++) {
uint256 j = i + (seed % (total - i));
seed = uint256(keccak256(abi.encode(seed, i)));
(pool[i], pool[j]) = (pool[j], pool[i]);
winners.push(pool[i]);
}
}
}
FCFS с ограничением по времени (Batch round)
Вместо pure FCFS делим продажу на временны́е батчи: каждый батч — 1 час, в каждом батче каждый верифицированный участник может купить не более X токенов. Боты теряют преимущество: лимит на адрес одинаковый, скорость не помогает.
Score-based аллокации
Участники накапливают баллы до продажи: холдинг токена проекта, участие в тестнете, активность в community. Аллокация пропорциональна баллам.
contract ScoreBasedAllocation {
mapping(address => uint256) public scores;
uint256 public totalScore;
uint256 public totalAllocation; // общий пул для распределения
function getAllocation(address user) public view returns (uint256) {
if (totalScore == 0) return 0;
return (scores[user] * totalAllocation) / totalScore;
}
// Snapshot на момент закрытия регистрации
function finalizeScores(address[] calldata users, uint256[] calldata userScores)
external onlyAdmin
{
for (uint256 i = 0; i < users.length; i++) {
scores[users[i]] = userScores[i];
totalScore += userScores[i];
}
}
}
Проблема: баллы считаются off-chain, что требует доверия к оператору. Решение — публиковать merkle root от score-snapshot и верифицировать on-chain при purchase.
Tiered system
Несколько уровней с разными лимитами и приоритетами:
| Tier | Условие входа | Гарантированная аллокация | FCFS сверх гарантии |
|---|---|---|---|
| Gold | Stake >= 10,000 токенов 90 дней | $5,000 | Да, до $15,000 |
| Silver | Stake >= 1,000 токенов 30 дней | $1,000 | Да, до $5,000 |
| Bronze | KYC пройден | $200 | Нет |
| Public | — | — | FCFS, остаток |
enum Tier { NONE, BRONZE, SILVER, GOLD }
struct TierConfig {
uint256 minStake;
uint256 minStakeDays;
uint256 guaranteedAllocationUSD; // в 6 decimals (USDC)
uint256 maxAllocationUSD;
}
mapping(Tier => TierConfig) public tierConfigs;
mapping(address => Tier) public userTier;
function computeTier(address user) public view returns (Tier) {
uint256 staked = stakingContract.stakedAmountFor(user);
uint256 stakeDuration = stakingContract.stakeDurationFor(user);
if (staked >= tierConfigs[Tier.GOLD].minStake &&
stakeDuration >= tierConfigs[Tier.GOLD].minStakeDays * 1 days)
return Tier.GOLD;
if (staked >= tierConfigs[Tier.SILVER].minStake &&
stakeDuration >= tierConfigs[Tier.SILVER].minStakeDays * 1 days)
return Tier.SILVER;
if (kycRegistry.isVerified(user))
return Tier.BRONZE;
return Tier.NONE;
}
Механика исполнения: whitelist + purchase
После определения аллокаций — публикация whitelist (merkle root) и период покупки:
contract TokenSale {
bytes32 public whitelistRoot;
mapping(address => uint256) public purchased; // потрачено USDC
struct AllocationProof {
uint256 maxAllocationUSD;
bytes32[] merkleProof;
}
function purchase(uint256 usdcAmount, AllocationProof calldata proof) external {
// Верификация аллокации
bytes32 leaf = keccak256(bytes.concat(
keccak256(abi.encode(msg.sender, proof.maxAllocationUSD))
));
require(MerkleProof.verify(proof.merkleProof, whitelistRoot, leaf), "Not whitelisted");
// Проверка лимита
require(
purchased[msg.sender] + usdcAmount <= proof.maxAllocationUSD,
"Exceeds allocation"
);
// Расчёт количества токенов
uint256 tokenAmount = (usdcAmount * TOKEN_PRICE_DENOMINATOR) / tokenPriceUSD;
purchased[msg.sender] += usdcAmount;
usdc.transferFrom(msg.sender, treasury, usdcAmount);
token.transfer(msg.sender, tokenAmount);
emit Purchase(msg.sender, usdcAmount, tokenAmount);
}
}
Защита от Sybil атак
Tier-based и score-based системы уязвимы к Sybil: один участник создаёт 100 адресов, распределяет stake. Защита:
Gitcoin Passport / Proof of Humanity — on-chain identity с Sybil resistance. Интегрируется как prerequisite для регистрации: require(passport.getScore(msg.sender) >= MIN_SCORE).
Quadratic scoring — аллокация пропорциональна √(stake), а не stake. Это снижает преимущество крупных держателей и повышает relative reward для малых участников.
Стейкинг с lockup — токены должны быть застейканы минимум 30-90 дней до snapshot. Это дорого для Sybil атаки: нужно купить токены заранее.
Social graph analysis — off-chain: кластеры адресов с похожими on-chain паттернами (созданы в один день, funded с одного источника) исключаются из whitelist.
Хорошо спроектированная система аллокаций — это и техника, и игровая теория. Цель: сделать честное участие дешевле, чем манипуляции. Merkle-based whitelist — это минимальный baseline; tier staking и Sybil protection — то, что отличает продуманный launchpad от примитивного FCFS.







