White-Label Launchpad Development
White-label launchpad—not "make Polkastarter but with your logo". Clients coming with this brief usually miss main point: Polkastarter and TrustPad work because behind them stands liquidity and community. Technically reproducing platform—not hard. But white-label launchpad valuable only when there's incoming stream of projects wanting to conduct IDO and community ready to participate. Therefore technical solution must be flexible—so owner can differentiate not via features but via exclusive projects and deal flow.
White-label Solution Architecture
Right architecture built around parametrization, not forks. Each client gets:
- Own set of smart contracts (doesn't share contracts with other clients)
- Customizable frontend with branding
- Admin panel for managing pools and tier system
- Own platform token (optionally)
Alternative—SaaS model on shared infrastructure with data isolation, but that's not full white-label.
Core Contract System
// Pool factory—central platform contract
contract LaunchpadFactory is AccessControl, Pausable {
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
// Registry of all pools created via this factory
address[] public allPools;
mapping(address => bool) public isValidPool;
mapping(address => address[]) public projectPools; // project → their pools
// Platform parameters
address public feeRecipient;
uint256 public platformFee; // basis points (200 = 2% of 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 System with Platform Token
Staking platform token—main mechanism retaining users and creating value for launchpad's own token:
contract LaunchpadStaking is ReentrancyGuard, Ownable {
IERC20 public immutable platformToken;
struct TierConfig {
string name; // "Bronze", "Silver", "Gold", "Diamond"
uint256 minStake; // minimum stake in platform token
uint256 weight; // weight in allocation distribution (basis points)
bool guaranteed; // guaranteed allocation or lottery
uint256 multiplier; // allocation multiplier (10000 = 1x)
}
TierConfig[] public tiers;
struct StakeInfo {
uint256 amount;
uint256 stakedAt;
uint256 lockUntil; // lock period before IDO snapshots
}
mapping(address => StakeInfo) public stakes;
uint256 public snapshotBlock; // block for snapshot before IDO
mapping(uint256 => mapping(address => uint256)) public snapshotStakes;
// snapshot tier for specific IDO
function takeSnapshot(uint256 poolId) external onlyOwner {
// Fix balances at snapshot moment
// Further changes don't affect allocation in this 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 for Lower Tiers
For Tier 1/2 (low stake) usually can't give everyone guaranteed allocation. Use lottery:
contract AllocationLottery {
// Chainlink VRF for verifiable randomness
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 for 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 fair winner selection
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)));
}
// First `winners` addresses—winners
emit LotteryCompleted(poolId, winners);
}
}
Multi-chain Support
Modern white-label launchpad should support multiple networks—EVM-compatible (Ethereum, BNB Chain, Polygon, Arbitrum, Avalanche) at minimum. Architectural approach:
- Same contract codebase deployed to each network
- Frontend switches networks via wagmi chain config
- Subgraph (The Graph) deployed separately per network
- Backend API aggregates data across networks via multicall
// wagmi config for 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 Integration
Most jurisdictions require KYC for token sale participation. Integration with Sumsub or Synaps:
// API endpoint for KYC status
app.get("/api/kyc/status/:address", async (req, res) => {
const { address } = req.params;
// Check status in database
const kycRecord = await db.kyc.findOne({ walletAddress: address.toLowerCase() });
if (!kycRecord || kycRecord.status !== "approved") {
return res.json({ approved: false, reason: kycRecord?.rejectionReason });
}
// Optionally: record on-chain via verified backend
// for platforms with on-chain KYC verification
res.json({ approved: true, tier: kycRecord.accreditationLevel });
});
Admin Panel
Platform operator functionality:
| Section | Features |
|---|---|
| Pool management | Create/edit/close pools |
| Project KYC | Verify projects requesting IDO |
| Whitelist | Upload and manage whitelists |
| Allocation | Manual allocation adjustment |
| Tier config | Configure levels and minimum stakes |
| Analytics | Raised by pools, active users, conversions |
| Fee management | Configure platform commissions |
Monetization Models
White-label launchpad can monetize several ways:
- Platform fee: 1.5–3% of raised amount on successful IDO
- Token allocation: 3–5% of sale tokens for platform services
- Staking APY: staker yields partially financed from platform fees
- Premium listing: increased visibility for paying projects
- White-label licensing: if selling platform to other operators







