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_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1170
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1092
  • image_logo-advance_0.png
    B2B Advance company logo design
    563
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    830
  • image_logo-aider_0.jpg
    AIDER company logo development
    763
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    876

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.