Launchpad Contract Development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Launchpad Contract Development
Complex
~1-2 weeks
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Development of Launchpad Contract

Launchpad is not just "a contract that sells tokens". If task reduced to this, 50 lines of Solidity enough. Real task — system simultaneously managing whitelist participants, multiple rounds with different prices and limits, vesting schedule for allocated tokens, refund mechanic if softcap not met, and anti-whale logic. Plus need survive at least one audit without critical findings. Let's see what this means practically.

Multi-Round Launchpad Architecture

Round Structure

Typical launchpad has several sequential rounds:

Seed Round    → Private Round → Public Round (IDO)
[whitelist]     [whitelist]      [open / guaranteed + FCFS]
[limit $500]    [limit $2000]    [limit $300 / no limit FCFS]
[$0.05/token]   [$0.08/token]    [$0.12/token]

In smart contract represented via configurable rounds:

struct Round {
    uint256 startTime;
    uint256 endTime;
    uint256 price;           // in stablecoin (6 decimals for USDC)
    uint256 minAllocation;   // minimum purchase
    uint256 maxAllocation;   // maximum purchase per address
    uint256 totalCap;        // maximum for entire round
    uint256 raised;          // already raised
    bytes32 merkleRoot;      // whitelist via Merkle tree
    bool    requiresKYC;     // KYC verification flag
    bool    isActive;
}

Merkle tree for whitelist — standard pattern. Alternative (mapping of approved addresses) doesn't scale: 10k addresses in mapping — 10k transactions to fill. Merkle proof verifies participation without storing entire list on-chain.

function participate(
    uint256 roundId,
    uint256 amount,
    bytes32[] calldata merkleProof
) external nonReentrant whenNotPaused {
    Round storage round = rounds[roundId];
    require(block.timestamp >= round.startTime, "Not started");
    require(block.timestamp < round.endTime, "Ended");
    require(round.raised + amount <= round.totalCap, "Cap exceeded");
    
    // Whitelist verification via Merkle proof
    if (round.merkleRoot != bytes32(0)) {
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
        require(
            MerkleProof.verify(merkleProof, round.merkleRoot, leaf),
            "Not whitelisted"
        );
    }
    
    // KYC check via on-chain registry
    if (round.requiresKYC) {
        require(kycRegistry.isVerified(msg.sender), "KYC required");
    }
    
    uint256 newTotal = contributions[roundId][msg.sender] + amount;
    require(newTotal >= round.minAllocation, "Below minimum");
    require(newTotal <= round.maxAllocation, "Exceeds maximum");
    
    contributions[roundId][msg.sender] = newTotal;
    round.raised += amount;
    
    // Accept payment (USDC)
    paymentToken.transferFrom(msg.sender, address(this), amount);
    
    emit Participated(msg.sender, roundId, amount);
}

Vesting: Main Technical Complexity

Most audit findings in launchpad contracts — in vesting logic. Not because it's fundamentally complex, but because edge cases poorly tested.

Linear Vesting with Cliff

struct VestingSchedule {
    uint256 totalAmount;     // total tokens to receive
    uint256 cliffEnd;        // until this time — nothing
    uint256 vestingStart;    // linear vesting start (usually = cliffEnd)
    uint256 vestingEnd;      // vesting period end
    uint256 claimed;         // already received
    bool    revocable;       // can be revoked (for team, not investors)
}

function claimable(address beneficiary) public view returns (uint256) {
    VestingSchedule memory schedule = vestingSchedules[beneficiary];
    
    if (block.timestamp < schedule.cliffEnd) {
        return 0;
    }
    
    uint256 elapsed = block.timestamp - schedule.vestingStart;
    uint256 total = schedule.vestingEnd - schedule.vestingStart;
    
    uint256 vested = elapsed >= total
        ? schedule.totalAmount
        : (schedule.totalAmount * elapsed) / total;
    
    return vested - schedule.claimed;
}

function claim() external nonReentrant {
    uint256 amount = claimable(msg.sender);
    require(amount > 0, "Nothing to claim");
    
    vestingSchedules[msg.sender].claimed += amount;
    projectToken.transfer(msg.sender, amount);
    
    emit TokensClaimed(msg.sender, amount);
}

Typical Vesting Mistakes

Problem 1: TGE (Token Generation Event) percent — part unlocked immediately at TGE, rest vests. Inexperienced devs implement as separate claim, users often forget. Correct — TGE part included in claimable() calculation automatically from TGE moment.

Problem 2: Revoke while preserving earned — if team vesting is revocable, on revoke must preserve already earned tokens. Only unearned part returned to project.

Problem 3: Precision loss on division(totalAmount * elapsed) / total with small amounts and large total can give 0. Operation order matters: always multiply before dividing.

Softcap / Hardcap and Refund Mechanism

uint256 public softcap;  // minimum for successful IDO
uint256 public hardcap;  // maximum

enum SaleStatus { Active, SoftcapMet, HardcapMet, Failed, Finalized }
SaleStatus public status;

function finalizeSale() external onlyOwner {
    require(block.timestamp > saleEndTime, "Sale not ended");
    
    uint256 totalRaised = getTotalRaised();
    
    if (totalRaised < softcap) {
        status = SaleStatus.Failed;
        // Activate refund mode
        emit SaleFailed(totalRaised, softcap);
    } else {
        status = SaleStatus.Finalized;
        // Transfer funds to treasury
        paymentToken.transfer(treasury, totalRaised);
        // Enable vesting claimability
        vestingStartTime = block.timestamp + TGE_DELAY;
        emit SaleFinalized(totalRaised);
    }
}

function refund(uint256 roundId) external nonReentrant {
    require(status == SaleStatus.Failed, "Sale not failed");
    
    uint256 contribution = contributions[roundId][msg.sender];
    require(contribution > 0, "Nothing to refund");
    
    contributions[roundId][msg.sender] = 0;
    paymentToken.transfer(msg.sender, contribution);
    
    emit Refunded(msg.sender, roundId, contribution);
}

Anti-Whale and Fairness Mechanics

Max allocation per wallet — basic protection, implemented via maxAllocation in round.

Anti-bot protection in FCFS round — First-Come-First-Served rounds attacked by bots. Common protections:

  • Whitelist even for public round: all who registered before certain time get guaranteed allocation. FCFS only for unregistered.
  • Commit-reveal: participant first commits (hash of purchase intent), reveal happens in separate transaction N blocks later. Bots lose speed advantage.
  • Dutch auction instead fixed price: price starts high and decreases. Market mechanism finds equilibrium price, anti-bot built-in.

Anti-sniper on round start:

modifier antiSnipe(uint256 roundId) {
    Round storage round = rounds[roundId];
    // First 30 seconds — only whitelist tier 1 (OG participants)
    if (block.timestamp < round.startTime + 30) {
        require(tier1Whitelist[msg.sender], "OG round");
    }
    _;
}

Token Contract Integration

Launchpad doesn't issue tokens immediately — records allocations, tokens issued via vesting. Requires either pre-mint tokens to launchpad or mint-on-claim mechanism:

// Option 1: pre-funded
// Before IDO start project team transfers needed token quantity
// to launchpad contract
projectToken.transferFrom(projectOwner, address(this), totalTokensForSale);

// Option 2: mint-on-claim (if token grants MINTER_ROLE to launchpad)
function claim() external nonReentrant {
    uint256 amount = claimable(msg.sender);
    require(amount > 0, "Nothing to claim");
    vestingSchedules[msg.sender].claimed += amount;
    IProjectToken(projectToken).mint(msg.sender, amount);  // mint instead of transfer
    emit TokensClaimed(msg.sender, amount);
}

Option 2 more popular for projects where total supply not finalized before IDO closing.

Testing and Audit

For launchpad contract fork testing mandatory — tests on mainnet fork with real USDC addresses:

forge test --fork-url $ETH_RPC -vvv --match-contract LaunchpadTest

Fuzz testing for vesting:

function testFuzz_vestingClaimable(
    uint256 totalAmount,
    uint256 elapsed,
    uint256 duration
) public {
    totalAmount = bound(totalAmount, 1e6, 1e30);  // reasonable bounds
    duration = bound(duration, 1 days, 4 * 365 days);
    elapsed = bound(elapsed, 0, duration);
    
    // Invariant: claimable never exceeds totalAmount
    uint256 vested = (totalAmount * elapsed) / duration;
    assertLe(vested, totalAmount);
}

Critical scenarios for manual testing: refund after failed sale with multiple rounds; claim with partially revoked vesting; participation via contract wallet (not EOA) — FOMO.finance hack 2021 was exactly such exploit.

Timeline: full launchpad contract with audit — 8–14 weeks. Without audit won't go to production — launchpad TVL too high.