Разработка White-label launchpad
White-label launchpad — это не просто "сделайте как Polkastarter, но со своим логотипом". Заказчики, которые приходят с такой формулировкой, обычно не учитывают главное: Polkastarter и TrustPad работают потому, что за ними стоит ликвидность и комьюнити. Технически воспроизвести платформу — несложно. Но white-label launchpad ценен только когда есть входящий поток проектов, желающих провести IDO, и сообщество, готовое в них участвовать. Поэтому техническое решение должно быть гибким, чтобы хозяин платформы мог дифференцироваться не функциями, а эксклюзивными проектами и deal flow.
Архитектура white-label решения
Правильная архитектура строится вокруг параметризации, а не форков. Каждый клиент получает:
- Собственный набор смарт-контрактов (не разделяет контракты с другими клиентами)
- Настраиваемый frontend с брендингом
- Admin panel для управления пулами и tier-системой
- Собственный платформенный токен (опционально)
Альтернатива — SaaS-модель на общей инфраструктуре с изоляцией по данным, но это не white-label в полном смысле.
Core контрактная система
// Фабрика пулов — центральный контракт платформы
contract LaunchpadFactory is AccessControl, Pausable {
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
// реестр всех пулов, созданных через эту фабрику
address[] public allPools;
mapping(address => bool) public isValidPool;
mapping(address => address[]) public projectPools; // project → их пулы
// параметры платформы
address public feeRecipient;
uint256 public platformFee; // basis points (200 = 2% от raise)
// whitelist approved sale token
mapping(address => bool) public approvedTokens;
event PoolCreated(
address indexed pool,
address indexed saleToken,
address indexed creator,
PoolType poolType
);
enum PoolType { FIXED_PRICE, DUTCH_AUCTION, OVERFLOW }
function createPool(
PoolType poolType,
bytes calldata poolParams
) external onlyRole(OPERATOR_ROLE) whenNotPaused returns (address pool) {
if (poolType == PoolType.FIXED_PRICE) {
FixedPricePool.Config memory config = abi.decode(poolParams, (FixedPricePool.Config));
require(approvedTokens[address(config.saleToken)], "Token not approved");
pool = address(new FixedPricePool(config, feeRecipient, platformFee));
} else if (poolType == PoolType.DUTCH_AUCTION) {
pool = address(new DutchAuctionPool(abi.decode(poolParams, (DutchAuctionPool.Config)), feeRecipient, platformFee));
} else {
pool = address(new OverflowPool(abi.decode(poolParams, (OverflowPool.Config)), feeRecipient, platformFee));
}
allPools.push(pool);
isValidPool[pool] = true;
emit PoolCreated(pool, address(0), msg.sender, poolType);
return pool;
}
}
Tier система с платформенным токеном
Стейкинг платформенного токена — основной механизм удержания пользователей и создания value для собственного токена launchpad:
contract LaunchpadStaking is ReentrancyGuard, Ownable {
IERC20 public immutable platformToken;
struct TierConfig {
string name; // "Bronze", "Silver", "Gold", "Diamond"
uint256 minStake; // минимальный stake в platform token
uint256 weight; // вес при распределении аллокаций (basis points)
bool guaranteed; // гарантированная аллокация или lottery
uint256 multiplier; // множитель аллокации (10000 = 1x)
}
TierConfig[] public tiers;
struct StakeInfo {
uint256 amount;
uint256 stakedAt;
uint256 lockUntil; // lock период перед IDO snapshots
}
mapping(address => StakeInfo) public stakes;
uint256 public snapshotBlock; // блок для snapshot перед IDO
mapping(uint256 => mapping(address => uint256)) public snapshotStakes;
// snapshot tier для конкретного IDO
function takeSnapshot(uint256 poolId) external onlyOwner {
// фиксируем балансы на момент snapshot
// дальнейшие изменения не влияют на аллокацию в этом IDO
snapshotBlock = block.number;
emit SnapshotTaken(poolId, block.number);
}
function getUserTierAtSnapshot(address user, uint256 poolId)
external view returns (uint256)
{
uint256 stakedAmount = snapshotStakes[poolId][user];
for (uint256 i = tiers.length; i > 0; i--) {
if (stakedAmount >= tiers[i-1].minStake) return i - 1;
}
return type(uint256).max;
}
}
Lottery для нижних тиров
Для Tier 1/2 (низкий stake) обычно невозможно дать всем гарантированную аллокацию. Используется lottery:
contract AllocationLottery {
// Chainlink VRF для верифицируемой случайности
VRFCoordinatorV2Interface public coordinator;
bytes32 public keyHash;
uint64 public subscriptionId;
mapping(uint256 => address[]) public lotteryParticipants; // poolId → participants
mapping(uint256 => uint256) public requestToPool;
function requestLotteryResult(uint256 poolId) external onlyOwner returns (uint256 requestId) {
requestId = coordinator.requestRandomWords(
keyHash,
subscriptionId,
3, // confirmations
100000, // gas limit для callback
1 // numWords
);
requestToPool[requestId] = poolId;
}
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
uint256 poolId = requestToPool[requestId];
address[] storage participants = lotteryParticipants[poolId];
uint256 winners = winnersCount[poolId];
uint256 rand = randomWords[0];
// Fisher-Yates shuffle для честного выбора победителей
for (uint256 i = 0; i < winners && i < participants.length; i++) {
uint256 j = i + (rand % (participants.length - i));
(participants[i], participants[j]) = (participants[j], participants[i]);
rand = uint256(keccak256(abi.encode(rand, i)));
}
// первые `winners` адресов в массиве — победители
emit LotteryCompleted(poolId, winners);
}
}
Multi-chain поддержка
Современный white-label launchpad должен поддерживать несколько сетей — EVM-совместимые (Ethereum, BNB Chain, Polygon, Arbitrum, Avalanche) минимум. Архитектурный подход:
- Одинаковая кодовая база контрактов деплоится на каждую сеть
- Фронтенд переключается между сетями через wagmi chain config
- Субграф (The Graph) деплоится на каждую сеть отдельно
- Backend API агрегирует данные с нескольких сетей через multicall
// wagmi config для multi-chain
import { createConfig, http } from "wagmi";
import { mainnet, polygon, bsc, arbitrum, avalanche } from "wagmi/chains";
export const config = createConfig({
chains: [mainnet, polygon, bsc, arbitrum, avalanche],
transports: {
[mainnet.id]: http(process.env.ETH_RPC),
[polygon.id]: http(process.env.POLYGON_RPC),
[bsc.id]: http(process.env.BSC_RPC),
[arbitrum.id]: http(process.env.ARB_RPC),
[avalanche.id]: http(process.env.AVAX_RPC),
},
});
KYC/AML интеграция
Большинство юрисдикций требует KYC для участия в token sale. Интеграция с Sumsub или Synaps:
// API endpoint для получения KYC status
app.get("/api/kyc/status/:address", async (req, res) => {
const { address } = req.params;
// проверяем статус в базе данных
const kycRecord = await db.kyc.findOne({ walletAddress: address.toLowerCase() });
if (!kycRecord || kycRecord.status !== "approved") {
return res.json({ approved: false, reason: kycRecord?.rejectionReason });
}
// опционально: записываем on-chain через верифицированный backend
// для платформ с on-chain KYC verification
res.json({ approved: true, tier: kycRecord.accreditationLevel });
});
Admin Panel
Функциональность admin panel для оператора платформы:
| Раздел | Функции |
|---|---|
| Pool management | Создание/редактирование/закрытие пулов |
| Project KYC | Верификация проектов, запрашивающих IDO |
| Whitelist | Загрузка и управление whitelist'ами |
| Allocation | Ручная корректировка аллокаций |
| Tier config | Настройка уровней и минимального стейка |
| Analytics | Raised по пулам, активные пользователи, конверсии |
| Fee management | Настройка платформенных комиссий |
Monetization модели
White-label launchpad может монетизироваться несколькими способами:
- Platform fee: 1.5–3% от raised amount при успешном IDO
- Token allocation: 3–5% сейл-токенов проекта за услуги платформы
- Staking APY: доходность для стейкеров частично финансируется из platform fees
- Premium listing: повышенная видимость для платящих проектов
- White-label licensing: если продаёте саму платформу другим операторам







