Skip to main content

Creating a Token Launch

Creating a token launch requires three sequential transactions through the Launchpad program. The Launchpad creates a Token-2022 mint, sets up escrows, and initializes a CCA auction via CPI.

Prerequisites

npm install @runner-protocol/sdk @solana/web3.js

You need SOL on devnet for transaction fees plus 0.15 SOL for the Raydium pool creation fee.

Step 1: Derive All PDAs

import {
PublicKey,
Connection,
Transaction,
SystemProgram,
SYSVAR_RENT_PUBKEY,
} from "@solana/web3.js";
import {
findProtocolConfigPda,
findLaunchConfigPda,
findTokenMintPda,
findMintAuthorityPda,
findEscrowAuthorityPda,
findEscrowPda,
findFeeEscrowPda,
findAuctionConfigPda,
findAuctionStatePda,
findSupplySchedulePda,
findVaultAuthorityPda,
findBaseVaultPda,
findQuoteVaultPda,
findTickPda,
encodeQ96,
LAUNCHPAD_PROGRAM_ID,
CCA_V2_PROGRAM_ID,
TOKEN_2022_PROGRAM_ID,
TOKEN_PROGRAM_ID,
ESCROW_AUCTION,
ESCROW_LP,
ESCROW_TEAM,
ESCROW_PROCEEDS,
} from "@runner-protocol/sdk";

const connection = new Connection("https://api.devnet.solana.com");
const creator = new PublicKey("YOUR_WALLET");

// Use a unique launch ID
const launchId = BigInt(Date.now());

// Launchpad PDAs
const [protocolConfig] = findProtocolConfigPda();
const [launchConfig] = findLaunchConfigPda(creator, launchId);
const [tokenMint] = findTokenMintPda(launchConfig);
const [mintAuthority] = findMintAuthorityPda(launchConfig);
const [escrowAuthority] = findEscrowAuthorityPda(launchConfig);
const [auctionEscrow] = findEscrowPda(launchConfig, ESCROW_AUCTION);
const [lpEscrow] = findEscrowPda(launchConfig, ESCROW_LP);
const [teamEscrow] = findEscrowPda(launchConfig, ESCROW_TEAM);
const [proceedsEscrow] = findEscrowPda(launchConfig, ESCROW_PROCEEDS);
const [feeEscrow] = findFeeEscrowPda(launchConfig);

// CCA V2 PDAs (authority is escrowAuthority, NOT creator)
const auctionId = launchId;
const [auctionConfig] = findAuctionConfigPda(escrowAuthority, auctionId);
const [auctionState] = findAuctionStatePda(auctionConfig);
const [supplySchedule] = findSupplySchedulePda(auctionConfig);
const [vaultAuthority] = findVaultAuthorityPda(auctionConfig);
const [baseVault] = findBaseVaultPda(auctionConfig);
const [quoteVault] = findQuoteVaultPda(auctionConfig);

const floorPriceQ96 = encodeQ96(0.01);
const [floorTick] = findTickPda(auctionConfig, floorPriceQ96);
caution

The CCA authority for launchpad-created auctions is the escrowAuthority PDA, NOT the creator's wallet. Deriving auction PDAs with the wrong authority will produce incorrect addresses.

Step 2: Transaction 1 -- create_launch_step_1

Creates the LaunchConfig account and Token-2022 mint with metadata.

// The Launchpad instruction uses sha256("global:create_launch_step_1")[..8]
// as its discriminator. Build the instruction data manually or use
// frontend helper functions.

const step1Accounts = [
{ pubkey: creator, isSigner: true, isWritable: true },
{ pubkey: protocolConfig, isSigner: false, isWritable: false },
{ pubkey: launchConfig, isSigner: false, isWritable: true },
{ pubkey: tokenMint, isSigner: false, isWritable: true },
{ pubkey: mintAuthority, isSigner: false, isWritable: false },
{ pubkey: TOKEN_2022_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
];

// Args: launchId, name, symbol, uri, decimals, totalSupply,
// auctionBps, lpBps, teamBps, lpProceedsBps, quoteMint,
// auctionDurationSlots, floorPrice, maxBidPrice, tickSpacing,
// auctionBlockInterval, minBidAmount, minNewTickAmount, maxTicks,
// requiredCurrencyRaised, supplySteps[]

Token allocation must sum to 100%: auction_bps + lp_bps + team_bps == 10000.

Step 3: Transaction 2 -- create_launch_step_2

Initializes all token escrow accounts and mints tokens to escrows.

const step2Accounts = [
{ pubkey: creator, isSigner: true, isWritable: true },
{ pubkey: launchConfig, isSigner: false, isWritable: true },
{ pubkey: tokenMint, isSigner: false, isWritable: true },
{ pubkey: mintAuthority, isSigner: false, isWritable: false },
{ pubkey: escrowAuthority, isSigner: false, isWritable: false },
{ pubkey: auctionEscrow, isSigner: false, isWritable: true },
{ pubkey: lpEscrow, isSigner: false, isWritable: true },
{ pubkey: teamEscrow, isSigner: false, isWritable: true },
{ pubkey: proceedsEscrow, isSigner: false, isWritable: true },
{ pubkey: quoteMint, isSigner: false, isWritable: false },
{ pubkey: feeEscrow, isSigner: false, isWritable: true },
{ pubkey: TOKEN_2022_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
];
// No args (discriminator only)

The creator must have 0.15 SOL available for the Raydium pool fee, transferred to feeEscrow.

Step 4: Transaction 3 -- initialize_cca_auction

The Launchpad performs 5 CPI calls into CCA V2 to initialize the auction.

// Compute start/end slots
const currentSlot = await connection.getSlot();
const startSlot = BigInt(currentSlot) + 300n; // ~2 min buffer
const auctionDurationSlots = 50000n;
const endSlot = startSlot + auctionDurationSlots;
const claimSlot = endSlot + 150n; // ~1 min after end

// Supply steps define token release rate
// Simple linear schedule: 100% release over the auction duration
const supplySteps = [
{ mps: 10000, blockDelta: auctionDurationSlots },
];
// mps: 10000 = 100% rate. Total must equal MPS (10,000,000):
// 10000 * 1000 = 10,000,000 for 1000 blocks

const step3Accounts = [
{ pubkey: creator, isSigner: true, isWritable: true },
{ pubkey: launchConfig, isSigner: false, isWritable: true },
{ pubkey: escrowAuthority, isSigner: false, isWritable: true },
{ pubkey: CCA_V2_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: auctionConfig, isSigner: false, isWritable: true },
{ pubkey: auctionState, isSigner: false, isWritable: true },
{ pubkey: supplySchedule, isSigner: false, isWritable: true },
{ pubkey: baseVault, isSigner: false, isWritable: true },
{ pubkey: quoteVault, isSigner: false, isWritable: true },
{ pubkey: floorTick, isSigner: false, isWritable: true },
{ pubkey: vaultAuthority, isSigner: false, isWritable: false },
{ pubkey: auctionEscrow, isSigner: false, isWritable: true },
{ pubkey: tokenMint, isSigner: false, isWritable: false },
{ pubkey: quoteMint, isSigner: false, isWritable: false },
{ pubkey: TOKEN_2022_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
];

// Args: auctionId, startSlot, endSlot, claimSlot, supplySteps[]

Important Notes

  • Each transaction must be signed and confirmed before proceeding to the next.
  • Use a fresh blockhash for each transaction.
  • The supply steps hash from step 1 is validated in step 3 -- the same supplySteps must be provided in both transactions.
  • Token-2022 mints require TOKEN_2022_PROGRAM_ID for any token operations on the base token.
  • After all three transactions succeed, the auction will automatically start at startSlot.

After Launch

Once the auction is active:

  • Keepers create checkpoints at each auction block boundary
  • Bidders place bids using buildPlaceBidInstruction
  • After the auction ends and graduates, anyone can trigger the 4-step settlement pipeline

See the Settlement State Machine for the full lifecycle.