4: Direct launchlab integration

For developers who want to integrate directly with the Raydium LaunchLab program without using this SDK.

Core Concepts

Program Information

Program ID: 9SkAtSxgNUMvT9bGb93v6rLU5MjW1XibykqoGtqT9dbg

Key Accounts:

  • Global Config: 6s1xP3hpbAfFoNtUNF8mfHsjr2Bd97JxFJRWLbL6aHuX

  • Platform Config: 3pcjttVo7W1eTYFjTfBTrWKm1YmWU2RXY6GKND3svqYi

  • Vault Authority: WLHv2UAZm6z4KyaaELi5pjdbJh6RESMva1Rnn8pJVVh

  • SOL Mint: So11111111111111111111111111111111111111112

Program State Machine

Pools exist in three states:

Status
Value
Description

Funding

0

Initial phase - accepting trades, fundraising in progress

Migrate

1

Fundraising complete - waiting for migration to AMM

Trade

2

Migrated to AMM - trading occurs on Raydium AMM V4

Critical Account Derivations

All accounts use Program Derived Addresses (PDAs) — deterministic addresses derived from seeds.

Pool State (Bonding Curve)

Main pool account that stores all state.

const [poolState, bump] = await PublicKey.findProgramAddress(
  [
    Buffer.from('pool'),
    baseMint.toBuffer(),    // Token mint
    quoteMint.toBuffer(),   // WSOL mint: So11111111111111111111111111111111111111112
  ],
  LAUNCHLAB_PROGRAM_ID
);

Seed components:

  • Constant: "pool" (UTF-8 bytes)

  • Base Mint: Token mint public key (32 bytes)

  • Quote Mint: WSOL mint public key (32 bytes)


Vault Authority

PDA that has authority over all vault operations.

const [authority, authBump] = await PublicKey.findProgramAddress(
  [Buffer.from('vault_auth_seed')],
  LAUNCHLAB_PROGRAM_ID
);

Seed: "vault_auth_seed" (UTF-8 bytes)


Base Vault (Token Vault)

Holds the pool's base tokens (the tokens being sold).

const [baseVault, baseVaultBump] = await PublicKey.findProgramAddress(
  [
    Buffer.from('pool_vault'),
    poolState.toBuffer(),
    baseMint.toBuffer(),
  ],
  LAUNCHLAB_PROGRAM_ID
);

Seed components:

  • Constant: "pool_vault"

  • Pool State: Pool state PDA (32 bytes)

  • Base Mint: Token mint public key (32 bytes)


Quote Vault (SOL Vault)

Holds the pool's quote tokens (WSOL/SOL).

const [quoteVault, quoteVaultBump] = await PublicKey.findProgramAddress(
  [
    Buffer.from('pool_vault'),
    poolState.toBuffer(),
    wsolMint.toBuffer(),  // So11111111111111111111111111111111111111112
  ],
  LAUNCHLAB_PROGRAM_ID
);

Platform Fee Vault

Collects platform fees.

const [platformVault, platformVaultBump] = await PublicKey.findProgramAddress(
  [
    platformConfig.toBuffer(),
    wsolMint.toBuffer(),
  ],
  LAUNCHLAB_PROGRAM_ID
);

Creator Fee Vault

Collects creator fees.

const [creatorVault, creatorVaultBump] = await PublicKey.findProgramAddress(
  [
    creator.toBuffer(),      // Creator wallet public key
    wsolMint.toBuffer(),
  ],
  LAUNCHLAB_PROGRAM_ID
);

Event Authority

Used for emitting program events.

const [eventAuthority, eventBump] = await PublicKey.findProgramAddress(
  [Buffer.from('__event_authority')],
  LAUNCHLAB_PROGRAM_ID
);

Seed: "__event_authority"


Metadata Account (Metaplex)

Stores token metadata (name, symbol, URI).

const [metadata] = await PublicKey.findProgramAddress(
  [
    Buffer.from('metadata'),
    METAPLEX_TOKEN_METADATA_PROGRAM_ID.toBuffer(),
    baseMint.toBuffer(),
  ],
  METAPLEX_TOKEN_METADATA_PROGRAM_ID  // metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s
);

Manual Transaction Construction

Buy Tokens (buy_exact_in)

Instruction Discriminator: [250, 234, 13, 123, 213, 156, 19, 236]

Accounts (in order):

Index
Account
Writable
Signer
Description

0

payer

User wallet

1

authority

Vault authority PDA

2

global_config

Global config

3

platform_config

Platform config

4

pool_state

Pool state PDA

5

user_base_token

User's token account (ATA)

6

user_quote_token

User's WSOL account (ATA)

7

base_vault

Pool's base vault

8

quote_vault

Pool's quote vault

9

base_token_mint

Token mint

10

quote_token_mint

WSOL mint

11

base_token_program

Token program

12

quote_token_program

Token program

13

event_authority

Event authority PDA

14

program

LaunchLab program ID

15

system_program

System program

16

platform_vault

Platform fee vault

17

creator_vault

Creator fee vault

Instruction data (8 bytes discriminator + 8 bytes amount_in + 8 bytes min_amount_out + 8 bytes share_fee_rate):

const discriminator = Buffer.from([250, 234, 13, 123, 213, 156, 19, 236]);

const amountInBuffer = Buffer.alloc(8);
amountInBuffer.writeBigUInt64LE(BigInt(amountIn.toString()), 0);

const minAmountOutBuffer = Buffer.alloc(8);
minAmountOutBuffer.writeBigUInt64LE(BigInt(minAmountOut.toString()), 0);

const shareFeeRateBuffer = Buffer.alloc(8);
shareFeeRateBuffer.writeBigUInt64LE(BigInt(0), 0); // Usually 0

const instructionData = Buffer.concat([
  discriminator,
  amountInBuffer,
  minAmountOutBuffer,
  shareFeeRateBuffer,
]);

Pre-instructions required:

1

Create WSOL Account (if it doesn't exist)

createAssociatedTokenAccountIdempotentInstruction(
  payer,
  userWsolAccount,
  payer,
  WSOL_MINT
);
2

Transfer SOL to WSOL Account

SystemProgram.transfer({
  fromPubkey: payer,
  toPubkey: userWsolAccount,
  lamports: amountIn,
});
3

Sync Native (wrap SOL)

createSyncNativeInstruction(userWsolAccount);

Full transaction example:

buyTokensManually.ts
import {
  Connection,
  PublicKey,
  Transaction,
  TransactionInstruction,
  SystemProgram,
} from '@solana/web3.js';
import {
  createAssociatedTokenAccountIdempotentInstruction,
  createSyncNativeInstruction,
  TOKEN_PROGRAM_ID,
} from '@solana/spl-token';

// Constants
const LAUNCHLAB_PROGRAM_ID = new PublicKey('9SkAtSxgNUMvT9bGb93v6rLU5MjW1XibykqoGtqT9dbg');
const WSOL_MINT = new PublicKey('So11111111111111111111111111111111111111112');
const GLOBAL_CONFIG = new PublicKey('6s1xP3hpbAfFoNtUNF8mfHsjr2Bd97JxFJRWLbL6aHuX');
const PLATFORM_CONFIG = new PublicKey('3pcjttVo7W1eTYFjTfBTrWKm1YmWU2RXY6GKND3svqYi');
const VAULT_AUTHORITY = new PublicKey('WLHv2UAZm6z4KyaaELi5pjdbJh6RESMva1Rnn8pJVVh');

async function buyTokensManually(
  connection: Connection,
  payer: PublicKey,
  tokenMint: PublicKey,
  amountIn: bigint,  // Lamports to spend
  minAmountOut: bigint  // Minimum tokens to receive (slippage protection)
): Promise<Transaction> {
  
  const transaction = new Transaction();

  // 1. Derive all PDAs
  const [poolState] = await PublicKey.findProgramAddress(
    [Buffer.from('pool'), tokenMint.toBuffer(), WSOL_MINT.toBuffer()],
    LAUNCHLAB_PROGRAM_ID
  );

  const [baseVault] = await PublicKey.findProgramAddress(
    [Buffer.from('pool_vault'), poolState.toBuffer(), tokenMint.toBuffer()],
    LAUNCHLAB_PROGRAM_ID
  );

  const [quoteVault] = await PublicKey.findProgramAddress(
    [Buffer.from('pool_vault'), poolState.toBuffer(), WSOL_MINT.toBuffer()],
    LAUNCHLAB_PROGRAM_ID
  );

  const [eventAuthority] = await PublicKey.findProgramAddress(
    [Buffer.from('__event_authority')],
    LAUNCHLAB_PROGRAM_ID
  );

  const [platformVault] = await PublicKey.findProgramAddress(
    [PLATFORM_CONFIG.toBuffer(), WSOL_MINT.toBuffer()],
    LAUNCHLAB_PROGRAM_ID
  );

  // Get pool state to find creator
  const poolAccountInfo = await connection.getAccountInfo(poolState);
  if (!poolAccountInfo) throw new Error('Pool not found');
  
  // Parse creator from pool state (offset 280, 32 bytes)
  const creator = new PublicKey(poolAccountInfo.data.slice(280, 312));

  const [creatorVault] = await PublicKey.findProgramAddress(
    [creator.toBuffer(), WSOL_MINT.toBuffer()],
    LAUNCHLAB_PROGRAM_ID
  );

  // 2. Get user token accounts (ATAs)
  const [userTokenAccount] = await PublicKey.findProgramAddress(
    [payer.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), tokenMint.toBuffer()],
    new PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL')
  );

  const [userWsolAccount] = await PublicKey.findProgramAddress(
    [payer.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), WSOL_MINT.toBuffer()],
    new PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL')
  );

  // 3. Add pre-instructions
  transaction.add(
    createAssociatedTokenAccountIdempotentInstruction(
      payer,
      userTokenAccount,
      payer,
      tokenMint
    )
  );

  transaction.add(
    createAssociatedTokenAccountIdempotentInstruction(
      payer,
      userWsolAccount,
      payer,
      WSOL_MINT
    )
  );

  transaction.add(
    SystemProgram.transfer({
      fromPubkey: payer,
      toPubkey: userWsolAccount,
      lamports: amountIn,
    })
  );

  transaction.add(createSyncNativeInstruction(userWsolAccount));

  // 4. Build buy instruction
  const discriminator = Buffer.from([250, 234, 13, 123, 213, 156, 19, 236]);
  
  const amountInBuffer = Buffer.alloc(8);
  amountInBuffer.writeBigUInt64LE(amountIn, 0);
  
  const minAmountOutBuffer = Buffer.alloc(8);
  minAmountOutBuffer.writeBigUInt64LE(minAmountOut, 0);
  
  const shareFeeRateBuffer = Buffer.alloc(8);
  shareFeeRateBuffer.writeBigUInt64LE(0n, 0);
  
  const instructionData = Buffer.concat([
    discriminator,
    amountInBuffer,
    minAmountOutBuffer,
    shareFeeRateBuffer,
  ]);

  const buyInstruction = new TransactionInstruction({
    programId: LAUNCHLAB_PROGRAM_ID,
    data: instructionData,
    keys: [
      { pubkey: payer, isSigner: true, isWritable: true },
      { pubkey: VAULT_AUTHORITY, isSigner: false, isWritable: false },
      { pubkey: GLOBAL_CONFIG, isSigner: false, isWritable: false },
      { pubkey: PLATFORM_CONFIG, isSigner: false, isWritable: false },
      { pubkey: poolState, isSigner: false, isWritable: true },
      { pubkey: userTokenAccount, isSigner: false, isWritable: true },
      { pubkey: userWsolAccount, isSigner: false, isWritable: true },
      { pubkey: baseVault, isSigner: false, isWritable: true },
      { pubkey: quoteVault, isSigner: false, isWritable: true },
      { pubkey: tokenMint, isSigner: false, isWritable: true },
      { pubkey: WSOL_MINT, isSigner: false, isWritable: false },
      { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
      { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
      { pubkey: eventAuthority, isSigner: false, isWritable: false },
      { pubkey: LAUNCHLAB_PROGRAM_ID, isSigner: false, isWritable: false },
      { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
      { pubkey: platformVault, isSigner: false, isWritable: true },
      { pubkey: creatorVault, isSigner: false, isWritable: true },
    ],
  });

  transaction.add(buyInstruction);

  return transaction;
}

Sell Tokens (sell_exact_in)

Instruction Discriminator: [149, 39, 222, 155, 211, 124, 152, 26]

Accounts: Same as buy_exact_in (see above)

Instruction data:

const discriminator = Buffer.from([149, 39, 222, 155, 211, 124, 152, 26]);

const amountInBuffer = Buffer.alloc(8);
amountInBuffer.writeBigUInt64LE(BigInt(tokensToSell.toString()), 0);

const minAmountOutBuffer = Buffer.alloc(8);
minAmountOutBuffer.writeBigUInt64LE(BigInt(minLamportsOut.toString()), 0);

const shareFeeRateBuffer = Buffer.alloc(8);
shareFeeRateBuffer.writeBigUInt64LE(BigInt(0), 0);

const instructionData = Buffer.concat([
  discriminator,
  amountInBuffer,
  minAmountOutBuffer,
  shareFeeRateBuffer,
]);

Post-instructions required:

createCloseAccountInstruction(
  userWsolAccount,  // Account to close
  payer,            // Destination for remaining lamports
  payer             // Owner of the account
);

Create Token (initialize_v2)

Instruction Discriminator: [67, 153, 175, 39, 218, 16, 38, 32]

This is the most complex instruction. It requires:

1

Token mint keypair

Token mint keypair (must be a signer).

2

Metadata

Metadata URI uploaded to IPFS/Arweave.

3

Multiple PDAs

Several PDAs must be derived (pool state, vaults, event authority, fee vaults, metadata PDA, etc.).

4

Anchor-encoded instruction data

Anchor method call with the appropriate structured args (see example).

Simplified example using Anchor Program interface:

import { Program, AnchorProvider } from '@coral-xyz/anchor';

// Use the Anchor program interface
const program = new Program(RaydiumLaunchlabIDL, LAUNCHLAB_PROGRAM_ID, provider);

const tokenKeypair = Keypair.generate();
const [poolState] = await PublicKey.findProgramAddress(
  [Buffer.from('pool'), tokenKeypair.publicKey.toBuffer(), WSOL_MINT.toBuffer()],
  LAUNCHLAB_PROGRAM_ID
);

// ... derive other PDAs ...

const tx = await program.methods
  .initializeV2(
    {
      decimals: 6,
      name: 'My Token',
      symbol: 'MTK',
      uri: 'https://ipfs.io/ipfs/...',
    },
    {
      constant: {
        data: {
          supply: new BN('1000000000000000'),
          totalBaseSell: new BN('793100000000000'),
          totalQuoteFundRaising: new BN('30000000000'),
          migrateType: 0,
        },
      },
    },
    {
      totalLockedAmount: new BN('0'),
      cliffPeriod: new BN('0'),
      unlockPeriod: new BN('0'),
    },
    { quoteToken: {} }
  )
  .accounts({
    payer: payer,
    creator: payer,
    globalConfig: GLOBAL_CONFIG,
    platformConfig: PLATFORM_CONFIG,
    // ... other accounts
  })
  .instruction();

Reading Pool State

Manual Deserialization

Pool state is stored in the PoolState account using Borsh serialization.

Account structure (selected fields):

Field
Offset
Size
Type
Description

epoch

0

8

u64

Update epoch

auth_bump

8

1

u8

Authority bump seed

status

9

1

u8

Pool status (0/1/2)

base_decimals

10

1

u8

Token decimals

quote_decimals

11

1

u8

WSOL decimals (always 9)

migrate_type

12

1

u8

Migration type

supply

16

8

u64

Total supply

total_base_sell

24

8

u64

Total base for sale

virtual_base

32

8

u64

Virtual base reserves

virtual_quote

40

8

u64

Virtual quote reserves

real_base

48

8

u64

Real base reserves

real_quote

56

8

u64

Real quote reserves

total_quote_fund_raising

64

8

u64

Fundraising goal

...

...

...

...

...

creator

280

32

Pubkey

Token creator

Using Borsh deserialization with Anchor coder:

import { BorshAccountsCoder } from '@coral-xyz/anchor';

const accountInfo = await connection.getAccountInfo(poolState);
if (!accountInfo) throw new Error('Pool not found');

const coder = new BorshAccountsCoder(RaydiumLaunchlabIDL);
const poolData = coder.decode('PoolState', accountInfo.data);

console.log('Status:', poolData.status);
console.log('Real Quote:', poolData.real_quote.toString());

Manual parsing (without Anchor):

const accountInfo = await connection.getAccountInfo(poolState);
if (!accountInfo) throw new Error('Pool not found');

const data = accountInfo.data;

// Parse key fields
const status = data.readUInt8(9);
const baseDecimals = data.readUInt8(10);
const supply = data.readBigUInt64LE(16);
const virtualBase = data.readBigUInt64LE(32);
const virtualQuote = data.readBigUInt64LE(40);
const realBase = data.readBigUInt64LE(48);
const realQuote = data.readBigUInt64LE(56);
const totalQuoteFundRaising = data.readBigUInt64LE(64);

console.log({
  status,
  baseDecimals,
  supply: supply.toString(),
  virtualBase: virtualBase.toString(),
  virtualQuote: virtualQuote.toString(),
  realBase: realBase.toString(),
  realQuote: realQuote.toString(),
  totalQuoteFundRaising: totalQuoteFundRaising.toString(),
});

Last updated