pumpfun

Par elophanto · elophanto

Guide complet du protocole PumpFun pour créer des lancements de tokens, des bonding curves et des intégrations AMM sur Solana. Couvre le Pump Program (création de tokens, achat/vente sur bonding curves), le PumpSwap AMM (pools de liquidité, swaps), les structures de frais, les frais créateur et l'intégration SDK.

npx skills add https://github.com/elophanto/elophanto --skill pumpfun

Guide d'intégration du protocole PumpFun

Un guide complet pour construire des applications avec PumpFun - le protocole de lancement de tokens et d'AMM leader de Solana permettant le trading instantané sans liquidité initiale.

Vue d'ensemble

PumpFun est un protocole de lancement et de trading de tokens sur Solana offrant :

  • Pump Program - Créer des tokens SPL avec trading instantané sur courbes de liaison
  • PumpSwap (AMM) - Teneur de marché automatisé à produit constant pour les tokens gradués
  • Creator Fees - Distribution automatique des frais aux créateurs de tokens
  • Token2022 Support - Standard de token moderne via l'instruction create_v2
  • Mayhem Mode - Mode spécial pour les lancements de tokens améliorés

IDs de programme

Programme Adresse
Pump Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P
PumpSwap AMM pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA
Pump Fees pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ
Mayhem Program MAyhSmzXzV1pTf7LsNkrNwkWKTo4ougAJ1PPg47MD4e

Démarrage rapide

Installation

# Installer les SDKs PumpFun
npm install @pump-fun/pump-sdk @pump-fun/pump-swap-sdk

# Ou avec pnpm
pnpm add @pump-fun/pump-sdk @pump-fun/pump-swap-sdk

Configuration basique

import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import bs58 from 'bs58';

// Configuration de la connexion
const connection = new Connection('https://api.mainnet-beta.solana.com');
const wallet = Keypair.fromSecretKey(bs58.decode('YOUR_SECRET_KEY'));

// Adresses des programmes
const PUMP_PROGRAM_ID = new PublicKey('6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P');
const PUMP_AMM_PROGRAM_ID = new PublicKey('pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA');
const PUMP_FEES_PROGRAM_ID = new PublicKey('pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ');

Pump Program (courbes de liaison)

Le programme Pump permet la création de tokens SPL avec trading instantané sur une courbe de liaison sans nécessiter de liquidité initiale.

Fonctionnement des courbes de liaison

  1. Création de token - Créer un token avec réserves virtuelles initiales
  2. Trading - Les utilisateurs achètent/vendent sur la courbe de liaison en utilisant la formule Uniswap V2
  3. Graduation - Quand le seuil de capitalisation boursière est atteint, la liquidité migre vers PumpSwap
  4. Burn de LP - Les tokens LP sont brûlés, rendant la liquidité permanente

Configuration globale

interface Global {
  initialized: boolean;
  authority: PublicKey;
  feeRecipient: PublicKey;
  initialVirtualTokenReserves: bigint;  // Défaut : 1 073 000 000 000 000
  initialVirtualSolReserves: bigint;    // Défaut : 30 000 000 000 (30 SOL)
  initialRealTokenReserves: bigint;     // Défaut : 793 100 000 000 000
  tokenTotalSupply: bigint;             // Défaut : 1 000 000 000 000 000
  feeBasisPoints: bigint;               // Défaut : 100 (1%)
  creatorFeeBasisPoints: bigint;        // Pourcentage de frais du créateur
}

Compte de courbe de liaison

interface BondingCurve {
  virtualTokenReserves: bigint;
  virtualSolReserves: bigint;
  realTokenReserves: bigint;
  realSolReserves: bigint;
  tokenTotalSupply: bigint;
  complete: boolean;
  creator: PublicKey;        // Adresse du créateur du token
  isMayhemMode: boolean;     // Flag du mode Mayhem
}

Dérivation des PDAs

import { PublicKey } from '@solana/web3.js';

const PUMP_PROGRAM_ID = new PublicKey('6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P');

// Dériver le PDA de courbe de liaison
function getBondingCurvePDA(mint: PublicKey): [PublicKey, number] {
  return PublicKey.findProgramAddressSync(
    [Buffer.from('bonding-curve'), mint.toBuffer()],
    PUMP_PROGRAM_ID
  );
}

// Dériver la courbe de liaison associée (compte de token)
function getAssociatedBondingCurve(mint: PublicKey): [PublicKey, number] {
  return PublicKey.findProgramAddressSync(
    [
      Buffer.from('associated-bonding-curve'),
      mint.toBuffer()
    ],
    PUMP_PROGRAM_ID
  );
}

// Dériver le PDA du coffre du créateur
function getCreatorVaultPDA(creator: PublicKey): [PublicKey, number] {
  return PublicKey.findProgramAddressSync(
    [Buffer.from('creator-vault'), creator.toBuffer()],
    PUMP_PROGRAM_ID
  );
}

// Dériver le PDA du compte global
function getGlobalPDA(): [PublicKey, number] {
  return PublicKey.findProgramAddressSync(
    [Buffer.from('global')],
    PUMP_PROGRAM_ID
  );
}

Créer un token (SPL legacy)

import {
  Connection,
  Keypair,
  PublicKey,
  Transaction,
  TransactionInstruction,
  SystemProgram,
  SYSVAR_RENT_PUBKEY,
} from '@solana/web3.js';
import {
  TOKEN_PROGRAM_ID,
  ASSOCIATED_TOKEN_PROGRAM_ID,
  getAssociatedTokenAddressSync,
} from '@solana/spl-token';

async function createToken(
  connection: Connection,
  payer: Keypair,
  name: string,
  symbol: string,
  uri: string
): Promise<string> {
  const mint = Keypair.generate();

  const [bondingCurve] = getBondingCurvePDA(mint.publicKey);
  const [associatedBondingCurve] = getAssociatedBondingCurve(mint.publicKey);
  const [global] = getGlobalPDA();

  // PDA de métadonnées Metaplex
  const METADATA_PROGRAM_ID = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s');
  const [metadata] = PublicKey.findProgramAddressSync(
    [
      Buffer.from('metadata'),
      METADATA_PROGRAM_ID.toBuffer(),
      mint.publicKey.toBuffer(),
    ],
    METADATA_PROGRAM_ID
  );

  // Construire les données de l'instruction de création
  const nameBuffer = Buffer.from(name);
  const symbolBuffer = Buffer.from(symbol);
  const uriBuffer = Buffer.from(uri);

  const data = Buffer.alloc(
    8 + 4 + nameBuffer.length + 4 + symbolBuffer.length + 4 + uriBuffer.length
  );

  // Écrire le discriminateur pour l'instruction 'create'
  const discriminator = Buffer.from([0x18, 0x1e, 0xc8, 0x28, 0x05, 0x1c, 0x07, 0x77]);
  discriminator.copy(data, 0);

  let offset = 8;
  data.writeUInt32LE(nameBuffer.length, offset);
  offset += 4;
  nameBuffer.copy(data, offset);
  offset += nameBuffer.length;

  data.writeUInt32LE(symbolBuffer.length, offset);
  offset += 4;
  symbolBuffer.copy(data, offset);
  offset += symbolBuffer.length;

  data.writeUInt32LE(uriBuffer.length, offset);
  offset += 4;
  uriBuffer.copy(data, offset);

  const instruction = new TransactionInstruction({
    programId: PUMP_PROGRAM_ID,
    keys: [
      { pubkey: mint.publicKey, isSigner: true, isWritable: true },
      { pubkey: payer.publicKey, isSigner: false, isWritable: true },
      { pubkey: bondingCurve, isSigner: false, isWritable: true },
      { pubkey: associatedBondingCurve, isSigner: false, isWritable: true },
      { pubkey: global, isSigner: false, isWritable: false },
      { pubkey: METADATA_PROGRAM_ID, isSigner: false, isWritable: false },
      { pubkey: metadata, isSigner: false, isWritable: true },
      { pubkey: payer.publicKey, isSigner: true, isWritable: true },
      { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
      { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
      { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
      { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
    ],
    data,
  });

  const tx = new Transaction().add(instruction);
  tx.feePayer = payer.publicKey;
  tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
  tx.sign(payer, mint);

  const signature = await connection.sendRawTransaction(tx.serialize());
  await connection.confirmTransaction(signature);

  return mint.publicKey.toBase58();
}

Créer un token V2 (Token2022)

// create_v2 utilise le programme Token2022 au lieu de Metaplex legacy
// Structure des comptes (14 comptes) :
const createV2Accounts = [
  'mint',                    // 0 : Nouvelle mint du token (signataire, inscriptible)
  'mintAuthority',           // 1 : PDA de l'autorité de mint
  'bondingCurve',            // 2 : PDA de courbe de liaison (inscriptible)
  'associatedBondingCurve',  // 3 : Compte de token pour la courbe de liaison (inscriptible)
  'global',                  // 4 : Config globale
  'user',                    // 5 : Créateur/payeur (signataire, inscriptible)
  'systemProgram',           // 6 : Programme système
  'token2022Program',        // 7 : Programme Token2022
  'associatedTokenProgram',  // 8 : Programme de token associé
  'rent',                    // 9 : Sysvar Rent
  'mayhemProgram',           // 10 : ID du programme Mayhem (pour le mode mayhem)
  'mayhemFeeRecipient',      // 11 : Destinataire de frais Mayhem
  'eventAuthority',          // 12 : Autorité d'événement
  'program',                 // 13 : Programme Pump
];

Acheter des tokens sur la courbe de liaison

interface BuyArgs {
  amount: bigint;        // Montant de token à acheter
  maxSolCost: bigint;    // Maximum SOL à dépenser (protection contre le slippage)
}

async function buyTokens(
  connection: Connection,
  payer: Keypair,
  mint: PublicKey,
  amount: bigint,
  maxSolCost: bigint
): Promise<string> {
  const [bondingCurve] = getBondingCurvePDA(mint);
  const [associatedBondingCurve] = getAssociatedBondingCurve(mint);
  const [global] = getGlobalPDA();

  // Obtenir les données de la courbe de liaison pour trouver le créateur
  const bondingCurveData = await connection.getAccountInfo(bondingCurve);
  // Analyser pour obtenir l'adresse du créateur...
  const creator = parseCreatorFromBondingCurve(bondingCurveData);
  const [creatorVault] = getCreatorVaultPDA(creator);

  // Compte de token associé de l'utilisateur
  const userAta = getAssociatedTokenAddressSync(mint, payer.publicKey);

  // Destinataire des frais (depuis la config globale)
  const feeRecipient = new PublicKey('FEE_RECIPIENT_ADDRESS');

  // Construire l'instruction d'achat
  const data = Buffer.alloc(8 + 8 + 8);
  const discriminator = Buffer.from([0x66, 0x06, 0x3d, 0x12, 0x01, 0xda, 0xeb, 0xea]);
  discriminator.copy(data, 0);
  data.writeBigUInt64LE(amount, 8);
  data.writeBigUInt64LE(maxSolCost, 16);

  const instruction = new TransactionInstruction({
    programId: PUMP_PROGRAM_ID,
    keys: [
      { pubkey: global, isSigner: false, isWritable: false },
      { pubkey: feeRecipient, isSigner: false, isWritable: true },
      { pubkey: mint, isSigner: false, isWritable: false },
      { pubkey: bondingCurve, isSigner: false, isWritable: true },
      { pubkey: associatedBondingCurve, isSigner: false, isWritable: true },
      { pubkey: userAta, isSigner: false, isWritable: true },
      { pubkey: payer.publicKey, isSigner: true, isWritable: true },
      { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
      { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
      { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
      { pubkey: creatorVault, isSigner: false, isWritable: true },
      // Comptes de configuration des frais (requis depuis septembre 2025)
      { pubkey: PUMP_FEES_PROGRAM_ID, isSigner: false, isWritable: false },
      { pubkey: feeConfigPDA, isSigner: false, isWritable: false },
    ],
    data,
  });

  // Vérifier si l'extension de compte est nécessaire (taille du compte < 150 bytes)
  const accountInfo = await connection.getAccountInfo(bondingCurve);
  if (accountInfo && accountInfo.data.length < 150) {
    // Ajouter l'instruction extendAccount en avant
    const extendIx = createExtendAccountInstruction(bondingCurve);
    const tx = new Transaction().add(extendIx, instruction);
  } else {
    const tx = new Transaction().add(instruction);
  }

  tx.feePayer = payer.publicKey;
  tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
  tx.sign(payer);

  const signature = await connection.sendRawTransaction(tx.serialize());
  await connection.confirmTransaction(signature);

  return signature;
}

Vendre des tokens sur la courbe de liaison

interface SellArgs {
  amount: bigint;        // Montant de token à vendre
  minSolOutput: bigint;  // Minimum SOL à recevoir (protection contre le slippage)
}

async function sellTokens(
  connection: Connection,
  payer: Keypair,
  mint: PublicKey,
  amount: bigint,
  minSolOutput: bigint
): Promise<string> {
  const [bondingCurve] = getBondingCurvePDA(mint);
  const [associatedBondingCurve] = getAssociatedBondingCurve(mint);
  const [global] = getGlobalPDA();

  const bondingCurveData = await connection.getAccountInfo(bondingCurve);
  const creator = parseCreatorFromBondingCurve(bondingCurveData);
  const [creatorVault] = getCreatorVaultPDA(creator);

  const userAta = getAssociatedTokenAddressSync(mint, payer.publicKey);
  const feeRecipient = new PublicKey('FEE_RECIPIENT_ADDRESS');

  const data = Buffer.alloc(8 + 8 + 8);
  const discriminator = Buffer.from([0x33, 0xe6, 0x85, 0xa4, 0x01, 0x7f, 0x83, 0xad]);
  discriminator.copy(data, 0);
  data.writeBigUInt64LE(amount, 8);
  data.writeBigUInt64LE(minSolOutput, 16);

  const instruction = new TransactionInstruction({
    programId: PUMP_PROGRAM_ID,
    keys: [
      { pubkey: global, isSigner: false, isWritable: false },
      { pubkey: feeRecipient, isSigner: false, isWritable: true },
      { pubkey: mint, isSigner: false, isWritable: false },
      { pubkey: bondingCurve, isSigner: false, isWritable: true },
      { pubkey: associatedBondingCurve, isSigner: false, isWritable: true },
      { pubkey: userAta, isSigner: false, isWritable: true },
      { pubkey: payer.publicKey, isSigner: true, isWritable: true },
      { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
      { pubkey: creatorVault, isSigner: false, isWritable: true },
      { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
      // Comptes de configuration des frais
      { pubkey: PUMP_FEES_PROGRAM_ID, isSigner: false, isWritable: false },
      { pubkey: feeConfigPDA, isSigner: false, isWritable: false },
    ],
    data,
  });

  const tx = new Transaction().add(instruction);
  tx.feePayer = payer.publicKey;
  tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
  tx.sign(payer);

  const signature = await connection.sendRawTransaction(tx.serialize());
  return signature;
}

Calculer le prix (mathématiques de la courbe de liaison)

// Formule de produit constant Uniswap V2 : x * y = k
// Devis : SOL -> Tokens

interface BondingCurveState {
  virtualTokenReserves: bigint;
  virtualSolReserves: bigint;
  realTokenReserves: bigint;
  realSolReserves: bigint;
}

function calculateBuyQuote(
  state: BondingCurveState,
  solAmountIn: bigint,
  feeBasisPoints: bigint = 100n
): bigint {
  // Calculer le SOL net après frais
  const netSol = (solAmountIn * 10000n) / (10000n + feeBasisPoints);

  // Calculer les tokens en utilisant la formule de produit constant
  // tokensOut = (netSol * virtualTokenReserves) / (virtualSolReserves + netSol)
  const tokensOut = (netSol * state.virtualTokenReserves) /
                   (state.virtualSolReserves + netSol);

  // Limiter aux réserves réelles de tokens
  return tokensOut > state.realTokenReserves ? state.realTokenReserves : tokensOut;
}

function calculateSellQuote(
  state: BondingCurveState,
  tokenAmountIn: bigint,
  feeBasisPoints: bigint = 100n
): bigint {
  // Calculer le SOL en utilisant la formule de produit constant
  // solOut = (tokenAmountIn * virtualSolReserves) / (virtualTokenReserves + tokenAmountIn)
  const grossSol = (tokenAmountIn * state.virtualSolReserves) /
                  (state.virtualTokenReserves + tokenAmountIn);

  // Déduire les frais
  const netSol = (grossSol * (10000n - feeBasisPoints)) / 10000n;

  // Limiter aux réserves réelles de SOL
  return netSol > state.realSolReserves ? state.realSolReserves : netSol;
}

function calculateMarketCap(state: BondingCurveState, tokenSupply: bigint): bigint {
  // marketCap = virtualSolReserves * mintSupply / virtualTokenReserves
  return (state.virtualSolReserves * tokenSupply) / state.virtualTokenReserves;
}

Migration vers PumpSwap

Quand une courbe de liaison se termine (tokens réels épuisés), la liquidité peut être migrée vers PumpSwap :

async function migrateToAMM(
  connection: Connection,
  payer: Keypair,
  mint: PublicKey
): Promise<string> {
  const [bondingCurve] = getBondingCurvePDA(mint);

  // L'instruction de migration est sans permission - n'importe qui peut l'appeler
  // après que la courbe de liaison soit complète
  const discriminator = Buffer.from([0x9d, 0xaf, 0x1e, 0x65, 0xe8, 0x69, 0x9b, 0x26]);

  const instruction = new TransactionInstruction({
    programId: PUMP_PROGRAM_ID,
    keys: [
      // ... comptes de migration incluant la création du pool PumpSwap
    ],
    data: discriminator,
  });

  const tx = new Transaction().add(instruction);
  tx.feePayer = payer.publicKey;
  tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
  tx.sign(payer);

  return await connection.sendRawTransaction(tx.serialize());
}

PumpSwap (AMM)

PumpSwap est un AMM à produit constant pour les tokens qui ont quitté la courbe de liaison.

Configuration globale

// Adresse GlobalConfig
const GLOBAL_CONFIG = new PublicKey('ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw');

interface GlobalConfig {
  admin: PublicKey;
  lpFeeBasisPoints: number;          // 20 bps (0,2%)
  protocolFeeBasisPoints: number;    // 5 bps (0,05%)
  coinCreatorFeeBasisPoints: number; // Frais du créateur
  disableFlags: number;              // Flags de désactivation des opérations
  protocolFeeRecipients: PublicKey[]; // 8 destinataires de frais pour l'équilibrage de charge
  tokenIncentivesEnabled: boolean;
}

Compte du pool

interface Pool {
  bump: number;
  poolCreator: PublicKey;
  baseMint: PublicKey;          // Mint du token
  quoteMint: PublicKey;         // Généralement WSOL
  lpMint: PublicKey;            // Mint du token LP
  poolBaseTokenAccount: PublicKey;
  poolQuoteTokenAccount: PublicKey;
  lpSupply: bigint;             // Trace l'offre LP originale (indépendante des burns)
  coinCreator: PublicKey;       // Créateur pour la distribution des frais
  isMayhemMode: boolean;        // Flag du mode Mayhem
}

Dérivation des PDAs du pool

const PUMP_AMM_PROGRAM_ID = new PublicKey('pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA');

// Dériver le PDA du pool
function getPoolPDA(
  index: number,
  creator: PublicKey,
  baseMint: PublicKey,
  quoteMint: PublicKey
): [PublicKey, number] {
  const indexBuffer = Buffer.alloc(2);
  indexBuffer.writeUInt16LE(index);

  return PublicKey.findProgramAddressSync(
    [
      Buffer.from('pool'),
      indexBuffer,
      creator.toBuffer(),
      baseMint.toBuffer(),
      quoteMint.toBuffer(),
    ],
    PUMP_AMM_PROGRAM_ID
  );
}

// Dériver le PDA de mint LP
function getLpMintPDA(pool: PublicKey): [PublicKey, number] {
  return PublicKey.findProgramAddressSync(
    [Buffer.from('pool_lp_mint'), pool.toBuffer()],
    PUMP_AMM_PROGRAM_ID
  );
}

// Dériver le PDA du compte de token du pool
function getPoolTokenAccountPDA(pool: PublicKey, mint: PublicKey): [PublicKey, number] {
  return PublicKey.findProgramAddressSync(
    [Buffer.from('pool_token_account'), pool.toBuffer(), mint.toBuffer()],
    PUMP_AMM_PROGRAM_ID
  );
}

// Dériver le PDA de l'autorité du coffre du créateur
function getCreatorVaultAuthorityPDA(coinCreator: PublicKey): [PublicKey, number] {
  return PublicKey.findProgramAddressSync(
    [Buffer.from('creator_vault'), coinCreator.toBuffer()],
    PUMP_AMM_PROGRAM_ID
  );
}

Échanger sur PumpSwap

interface SwapArgs {
  baseAmountIn: bigint;    // Montant du token base (pour vendre)
  quoteAmountIn: bigint;   // Montant du token quote (pour acheter)
  minAmountOut: bigint;    // Sortie minimale (protection contre le slippage)
}

async function swapOnPumpSwap(
  connection: Connection,
  payer: Keypair,
  pool: PublicKey,
  amountIn: bigint,
  minAmountOut: bigint,
  isBuy: boolean
): Promise<string> {
  const poolData = await fetchPoolData(connection, pool);

  const userBaseAta = getAssociatedTokenAddressSync(
    poolData.baseMint,
    payer.publicKey
  );
  const userQuoteAta = getAssociatedTokenAddressSync(
    poolData.quoteMint,
    payer.publicKey
  );

  const [creatorVaultAuthority] = getCreatorVaultAuthorityPDA(poolData.coinCreator);
  const creatorVaultAta = getAssociatedTokenAddressSync(
    poolData.quoteMint,
    creatorVaultAuthority,
    true // allowOwnerOffCurve
  );

  // Construire l'instruction d'échange
  const data = Buffer.alloc(8 + 8 + 8 + 8);
  const discriminator = isBuy
    ? Buffer.from([0x66, 0x06, 0x3d, 0x12, 0x01, 0xda, 0xeb, 0xea]) // achat
    : Buffer.from([0x33, 0xe6, 0x85, 0xa4, 0x01, 0x7f, 0x83, 0xad]); // vente

  discriminator.copy(data, 0);
  data.writeBigUInt64LE(isBuy ? 0n : amountIn, 8);      // baseAmountIn
  data.writeBigUInt64LE(isBuy ? amountIn : 0n, 16);     // quoteAmountIn
  data.writeBigUInt64LE(minAmountOut, 24);              // minAmountOut

  const instruction = new TransactionInstruction({
    programId: PUMP_AMM_PROGRAM_ID,
    keys: [
      { pubkey: pool, isSigner: false, isWritable: true },
      { pubkey: payer.publicKey, isSigner: true, isWritable: true },
      { pubkey: GLOBAL_CONFIG, isSigner: false, isWritable: false },
      { pubkey: poolData.baseMint, isSigner: false, isWritable: false },
      { pubkey: poolData.quoteMint, isSigner: false, isWritable: false },
      { pubkey: userBaseAta, isSigner: false, isWritable: true },
      { pubkey: userQuoteAta, isSigner: false, isWritable: true },
      { pubkey: poolData.poolBaseTokenAccount, isSigner: false, isWritable: true },
      { pubkey: poolData.poolQuoteTokenAccount, isSigner: false, isWritable: true },
      { pubkey: protocolFeeRecipient, isSigner: false, isWritable: true },
      { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
      // Comptes des frais du créateur (indices 17-18, requis si pool.dataLen < 300)
      { pubkey: creatorVaultAta, isSigner: false, isWritable: true },
      { pubkey: creatorVaultAuthority, isSigner: false, isWritable: false },
      // Configuration des frais
      { pubkey: PUMP_FEES_PROGRAM_ID, isSigner: false, isWritable: false },
      { pubkey: feeConfigPDA, isSigner: false, isWritable: false },
    ],
    data,
  });

  // Vérifier si l'extension de compte est nécessaire
  const poolInfo = await connection.getAccountInfo(pool);
  if (poolInfo && poolInfo.data.length < 300) {
    const extendIx = createExtendAccountInstruction(pool);
    const tx = new Transaction().add(extendIx, instruction);
  } else {
    const tx = new Transaction().add(instruction);
  }

  tx.feePayer = payer.publicKey;
  tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
  tx.sign(payer);

  return await connection.sendRawTransaction(tx.serialize());
}

Ajouter de la liquidité

async function addLiquidity(
  connection: Connection,
  payer: Keypair,
  pool: PublicKey,
  baseAmount: bigint,
  quoteAmount: bigint,
  minLpTokens: bigint
): Promise<string> {
  const poolData = await fetchPoolData(connection, pool);

  const userBaseAta = getAssociatedTokenAddressSync(poolData.baseMint, payer.publicKey);
  const userQuoteAta = getAssociatedTokenAddressSync(poolData.quoteMint, payer.publicKey);
  const userLpAta = getAssociatedTokenAddressSync(poolData.lpMint, payer.publicKey);

  const data = Buffer.alloc(8 + 8 + 8 + 8);
  const discriminator = Buffer.from([0xf2, 0x23, 0xc6, 0x89, 0x52, 0xe1, 0xf2, 0xb6]);
  discriminator.copy(data, 0);
  data.writeBigUInt64LE(baseAmount, 8);
  data.writeBigUInt64LE(quoteAmount, 16);
  data.writeBigUInt64LE(minLpTokens, 24);

  const instruction = new TransactionInstruction({
    programId: PUMP_AMM_PROGRAM_ID,
    keys: [
      { pubkey: pool, isSigner: false, isWritable: true },
      { pubkey: GLOBAL_CONFIG, isSigner: false, isWritable: false },
      { pubkey: payer.publicKey, isSigner: true, isWritable: true },
      { pubkey: poolData.baseMint, isSigner: false, isWritable: false },
      { pubkey: poolData.quoteMint, isSigner: false, isWritable: false },
      { pubkey: poolData.lpMint, isSigner: false, isWritable: true },
      { pubkey: userBaseAta, isSigner: false, isWritable: true },
      { pubkey: userQuoteAta, isSigner: false, isWritable: true },
      { pubkey: userLpAta, isSigner: false, isWritable: true },
      { pubkey: poolData.poolBaseTokenAccount, isSigner: false, isWritable: true },
      { pubkey: poolData.poolQuoteTokenAccount, isSigner: false, isWritable: true },
      { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
      { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
      { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
    ],
    data,
  });

  const tx = new Transaction().add(instruction);
  tx.feePayer = payer.publicKey;
  tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
  tx.sign(payer);

  return await connection.sendRawTransaction(tx.serialize());
}

Retirer de la liquidité

async function removeLiquidity(
  connection: Connection,
  payer: Keypair,
  pool: PublicKey,
  lpTokenAmount: bigint,
  minBaseOut: bigint,
  minQuoteOut: bigint
): Promise<string> {
  const poolData = await fetchPoolData(connection, pool);

  const userBaseAta = getAssociatedTokenAddressSync(poolData.baseMint, payer.publicKey);
  const userQuoteAta = getAssociatedTokenAddressSync(poolData.quoteMint, payer.publicKey);
  const userLpAta = getAssociatedTokenAddressSync(poolData.lpMint, payer.publicKey);

  const data = Buffer.alloc(8 + 8 + 8 + 8);
  const discriminator = Buffer.from([0xb7, 0x12, 0x46, 0x9c, 0x94, 0x6d, 0xa1, 0x22]);
  discriminator.copy(data, 0);
  data.writeBigUInt64LE(lpTokenAmount, 8);
  data.writeBigUInt64LE(minBaseOut, 16);
  data.writeBigUInt64LE(minQuoteOut, 24);

  const instruction = new TransactionInstruction({
    programId: PUMP_AMM_PROGRAM_ID,
    keys: [
      { pubkey: pool, isSigner: false, isWritable: true },
      { pubkey: GLOBAL_CONFIG, isSigner: false, isWritable: false },
      { pubkey: payer.publicKey, isSigner: true, isWritable: true },
      { pubkey: poolData.baseMint, isSigner: false, isWritable: false },
      { pubkey: poolData.quoteMint, isSigner: false, isWritable: false },
      { pubkey: poolData.lpMint, isSigner: false, isWritable: true },
      { pubkey: userBaseAta, isSigner: false, isWritable: true },
      { pubkey: userQuoteAta, isSigner: false, isWritable: true },
      { pubkey: userLpAta, isSigner: false, isWritable: true },
      { pubkey: poolData.poolBaseTokenAccount, isSigner: false, isWritable: true },
      { pubkey: poolData.poolQuoteTokenAccount, isSigner: false, isWritable: true },
      { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
    ],
    data,
  });

  const tx = new Transaction().add(instruction);
  tx.feePayer = payer.publicKey;
  tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
  tx.sign(payer);

  return await connection.sendRawTransaction(tx.serialize());
}

Structure des frais

Niveaux de frais dynamiques

Les frais sont calculés en fonction de la capitalisation boursière en lamports :

interface FeeTier {
  marketCapLamportsThreshold: bigint;
  fees: Fees;
}

interface Fees {
  lpFeeBps: number;        // Frais du fournisseur de liquidité
  protocolFeeBps: number;  // Frais du protocole
  creatorFeeBps: number;   // Frais du créateur
}

// Calcul des frais pour les courbes de liaison
function calculateBondingCurveMarketCap(
  virtualSolReserves: bigint,
  mintSupply: bigint,
  virtualTokenReserves: bigint
): bigint {
  return (virtualSolReserves * mintSupply) / virtualTokenReserves;
}

// Calcul des frais pour les pools AMM
function calculatePoolMarketCap(
  quoteReserve: bigint,
  baseMintSupply: bigint,
  baseReserve: bigint
): bigint {
  return (quoteReserve * baseMintSupply) / baseReserve;
}

// Obtenir l'étage de frais applicable
function getFeeTier(marketCap: bigint, feeTiers: FeeTier[]): Fees {
  // Trier les étages par seuil décroissant
  const sortedTiers = [...feeTiers].sort(
    (a, b) => Number(b.marketCapLamportsThreshold - a.marketCapLamportsThreshold)
  );

  for (const tier of sortedTiers) {
    if (marketCap >= tier.marketCapLamportsThreshold) {
      return tier.fees;
    }
  }

  // Retourner l'étage défaut/minimum
  return sortedTiers[sortedTiers.length - 1].fees;
}

Configuration de partage des frais

interface SharingConfig {
  status: 'Active' | 'Paused';
  mint: PublicKey;
  admin: PublicKey;
  adminRevoked: boolean;
  shareholders: Shareholder[];
}

interface Shareholder {
  address: PublicKey;
  shareBps: number;  // Part en points de base (le total doit égaler 10000)
}

// Créer une configuration de partage des frais
async function createFeeSharingConfig(
  connection: Connection,
  payer: Keypair,
  mint: PublicKey,
  shareholders: Shareholder[]
): Promise<string> {
  // Valider que les parts somment à 10000 bps (100%)
  const totalShares = shareholders.reduce((sum, s) => sum + s.shareBps, 0);
  if (totalShares !== 10000) {
    throw new Error('Les parts des actionnaires doivent sommer à 10000 bps');
  }

  // Construire l'instruction de création de configuration de partage des frais
  // ...
}

Collecter les frais du créateur

// Collecter les frais de la courbe de liaison
async function collectBondingCurveCreatorFee(
  connection: Connection,
  creator: Keypair
): Promise<string> {
  const [creatorVault] = PublicKey.findProgramAddressSync(
    [Buffer.from('creator-vault'), creator.publicKey.toBuffer()],
    PUMP_PROGRAM_ID
  );

  const discriminator = Buffer.from([0x85, 0xb1, 0x29, 0x6d, 0x3a, 0x47, 0x2c, 0x5e]);

  const instruction = new TransactionInstruction({
    programId: PUMP_PROGRAM_ID,
    keys: [
      { pubkey: creatorVault, isSigner: false, isWritable: true },
      { pubkey: creator.publicKey, isSigner: true, isWritable: true },
      { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
    ],
    data: discriminator,
  });

  const tx = new Transaction().add(instruction);
  tx.feePayer = creator.publicKey;
  tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
  tx.sign(creator);

  return await connection.sendRawTransaction(tx.serialize());
}

// Collecter les frais du pool PumpSwap
async function collectPoolCreatorFee(
  connection: Connection,
  coinCreator: Keypair,
  pool: PublicKey
): Promise<string> {
  const poolData = await fetchPoolData(connection, pool);

  const [creatorVaultAuthority] = getCreatorVaultAuthorityPDA(coinCreator.publicKey);
  const creatorVaultAta = getAssociatedTokenAddressSync(
    poolData.quoteMint,
    creatorVaultAuthority,
    true
  );
  const creatorWalletAta = getAssociatedTokenAddressSync(
    poolData.quoteMint,
    coinCreator.publicKey
  );

  // Construire l'instruction de collecte des frais du créateur de coin
  // ...
}

Utilisation du SDK

Utiliser PumpAmmSdk

import { PumpAmmSdk } from '@pump-fun/pump-swap-sdk';

const sdk = new PumpAmmSdk(connection);

// Création du pool avec autocomplément
const initialPrice = sdk.createAutocompleteInitialPoolPrice(
  baseAmount,
  quoteAmount
);

const createIxs = await sdk.createPoolInstructions({
  index: 0,
  creator: payer.publicKey,
  baseMint: tokenMint,
  quoteMint: WSOL_MINT,
  baseAmountIn: baseAmount,
  quoteAmountIn: quoteAmount,
});

// Autocomplément de dépôt (changement d'entrée base)
const depositCalc = sdk.depositAutocompleteBaseInput({
  pool: poolAddress,
  baseAmountIn: userBaseInput,
  slippageBps: 50, // 0,5%
});
console.log('Quote nécessaire :', depositCalc.quoteAmountIn);
console.log('Tokens LP :', depositCalc.lpTokensOut);

// Autocomplément de dépôt (changement d'entrée quote)
const depositCalc2 = sdk.depositAutocompleteQuoteInput({
  pool: poolAddress,
  quoteAmountIn: userQuoteInput,
  slippageBps: 50,
});

// Exécuter le dépôt
const depositIxs = await sdk.depositInstructions({
  pool: poolAddress,
  user: payer.publicKey,
  lpTokenAmountOut: depositCalc.lpTokensOut,
  maxBaseAmountIn: depositCalc.baseAmountIn,
  maxQuoteAmountIn: depositCalc.quoteAmountIn,
});

// Autocomplément d'échange
const swapCalc = sdk.swapAutocompleteBaseInput({
  pool: poolAddress,
  baseAmountIn: sellAmount,
  slippageBps: 100, // 1%
});
console.log('Quote reçue :', swapCalc.quoteAmountOut);

// Exécuter l'échange
const swapIxs = await sdk.swapInstructions({
  pool: poolAddress,
  user: payer.publicKey,
  baseAmountIn: sellAmount,
  quoteAmountIn: 0n,
  minAmountOut: swapCalc.minQuoteAmountOut,
});

// Retrait
const withdrawCalc = sdk.withdrawAutocomplete({
  pool: poolAddress,
  lpTokenAmountIn: lpTokens,
  slippageBps: 50,
});
console.log('Base reçue :', withdrawCalc.baseAmountOut);
console.log('Quote reçue :', withdrawCalc.quoteAmountOut);

const withdrawIxs = await sdk.withdrawInstructions({
  pool: poolAddress,
  user: payer.publicKey,
  lpTokenAmountIn: lpTokens,
  minBaseAmountOut: withdrawCalc.minBaseAmountOut,
  minQuoteAmountOut: withdrawCalc.minQuoteAmountOut,
});

Extension de compte

Les deux programmes Pump et PumpSwap nécessitent une extension de compte quand les tailles de compte ont augmenté :

// Vérifier et étendre le compte de courbe de liaison (taille < 150 bytes)
async function ensureBondingCurveExtended(
  connection: Connection,
  bondingCurve: PublicKey
): Promise<TransactionInstruction | null> {
  const accountInfo = await connection.getAccountInfo(bondingCurve);

  if (accountInfo && accountInfo.data.length < 150) {
    return createExtendAccountInstruction(PUMP_PROGRAM_ID, bondingCurve);
  }

  return null;
}

// Vérifier et étendre le compte du pool (taille < 300 bytes)
async function ensurePoolExtended(
  connection: Connection,
  pool: PublicKey
): Promise<TransactionInstruction | null> {
  const accountInfo = await connection.getAccountInfo(pool);

  if (accountInfo && accountInfo.data.length < 300) {
    return createExtendAccountInstruction(PUMP_AMM_PROGRAM_ID, pool);
  }

  return null;
}

function createExtendAccountInstruction(
  programId: PublicKey,
  account: PublicKey
): TransactionInstruction {
  const discriminator = Buffer.from([0x9a, 0x3f, 0x2c, 0x8b, 0x45, 0xe7, 0x12, 0xd6]);

  return new TransactionInstruction({
    programId,
    keys: [
      { pubkey: account, isSigner: false, isWritable: true },
    ],
    data: discriminator,
  });
}

Mode Mayhem

Le mode Mayhem est un mode spécial de lancement de tokens avec différents destinataires de frais :

const MAYHEM_PROGRAM_ID = new PublicKey('MAyhSmzXzV1pTf7LsNkrNwkWKTo4ougAJ1PPg47MD4e');

// Destinataires de frais Mayhem (rotatés aléatoirement pour l'équilibrage de charge)
const MAYHEM_FEE_RECIPIENTS = [
  'DzPPWKfYYMuHxR98xhPkYSp7KHsLpcLZU8tHCqXjC3HG',
  '5AbGBKS6NHKcTFyJaZCk3dbMRNFG3y6kkN4y7Rp3iHCk',
  'DWM9EuZ3e9cRYdHYRKwsCqXFKgn4jUNkUPEAyNE2Dxnc',
  '9BUPJ65gFVaGQKyXiR6xSE1DdLRqkUMv9HJvL8wGfJaL',
];

// Vérifier si le token est en mode mayhem
function isMayhemMode(bondingCurve: BondingCurve): boolean {
  return bondingCurve.isMayhemMode;
}

// Obtenir le destinataire de frais correct selon le mode mayhem
function getFeeRecipient(
  bondingCurve: BondingCurve,
  defaultFeeRecipient: PublicKey
): PublicKey {
  if (bondingCurve.isMayhemMode) {
    // Sélectionner aléatoirement parmi les destinataires mayhem
    const index = Math.floor(Math.random() * MAYHEM_FEE_RECIPIENTS.length);
    return new PublicKey(MAYHEM_FEE_RECIPIENTS[index]);
  }
  return defaultFeeRecipient;
}

Incitations aux tokens

PumpFun offre des incitations basées sur le volume :

// Initialiser l'accumulateur de volume utilisateur
async function initUserVolumeAccumulator(
  connection: Connection,
  payer: Keypair
): Promise<string> {
  const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
    [Buffer.from('user_volume'), payer.publicKey.toBuffer()],
    PUMP_PROGRAM_ID
  );

  // Construire l'instruction d'initialisation de l'accumulateur de volume utilisateur
  // ...
}

// Réclamer les incitations aux tokens
async function claimTokenIncentives(
  connection: Connection,
  user: Keypair
): Promise<string> {
  const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
    [Buffer.from('user_volume'), user.publicKey.toBuffer()],
    PUMP_PROGRAM_ID
  );

  // Construire l'instruction de réclamation des incitations aux tokens
  // ...
}

Optimisation des unités de calcul

// Limite de CU statique recommandée pour les opérations d'achat/vente
const RECOMMENDED_CU_LIMIT = 100_000;

// Ajouter des instructions de budget de calcul
import { ComputeBudgetProgram } from '@solana/web3.js';

function addComputeBudget(
  tx: Transaction,
  units: number = 100_000,
  microLamports: number = 10_000
): Transaction {
  tx.add(
    ComputeBudgetProgram.setComputeUnitLimit({ units }),
    ComputeBudgetProgram.setComputeUnitPrice({ microLamports })
  );
  return tx;
}

Gestion des erreurs

// Erreurs PumpFun courantes
enum PumpError {
  SlippageExceeded = 6001,
  InsufficientFunds = 6002,
  BondingCurveComplete = 6003,
  BondingCurveNotComplete = 6004,
  InvalidAmount = 6005,
  MathOverflow = 6006,
  Unauthorized = 6007,
}

function handlePumpError(error: any): string {
  const code = error?.code || error?.message?.match(/custom program error: 0x(\w+)/)?.[1];

  switch (parseInt(code, 16)) {
    case PumpError.SlippageExceeded:
      return 'Transaction échouée : Tolerance de slippage dépassée. Essayez d\'augmenter le slippage.';
    case PumpError.InsufficientFunds:
      return 'Fonds insuffisants pour cette transaction.';
    case PumpError.BondingCurveComplete:
      return 'Courbe de liaison complétée. Tradez sur PumpSwap à la place.';
    case PumpError.BondingCurveNotComplete:
      return 'Courbe de liaison non encore complétée. Impossible de migrer.';
    default:
      return `Transaction échouée : ${error.message}`;
  }
}

Bonnes pratiques

Construction de transactions

  • Toujours vérifier les tailles de compte avant achat/vente et ajouter extendAccount en avant si nécessaire
  • Utiliser une limite CU statique de 100 000 pour les opérations d'achat/vente
  • Inclure une protection de slippage appropriée (1-5% recommandé)
  • Utiliser les destinataires de frais appropriés selon l'état du mode mayhem

Gestion des frais

  • Toujours inclure les comptes de configuration des frais (requis depuis septembre 2025)
  • Pour les tokens en mode mayhem, passer le destinataire de frais mayhem à l'index de compte correct
  • Vérifier l'étage de capitalisation boursière pour une calcul des frais exact

Frais du créateur

  • Les frais du créateur s'accumulent dans les PDAs des coffres du créateur
  • Utiliser collectCreatorFee pour retirer les frais accumulés
  • Les frais s'appliquent aux courbes de liaison non complétées et aux pools PumpSwap canoniques

Sécurité

  • Ne jamais exposer les clés privées
  • Valider toutes les entrées utilisateur
  • Utiliser devnet pour tester avant mainnet
  • Implémenter une gestion d'erreur appropriée

Ressources

Structure de skill

pumpfun/
├── SKILL.md                              # Ce fichier
├── resources/
│   ├── pump-program-reference.md         # API du programme de courbe de liaison
│   ├── pump-swap-reference.md            # API du programme AMM
│   ├── fee-structure.md                  # Calcul et étages des frais
│   └── program-addresses.md              # Tous les IDs de programme
├── examples/
│   ├── bonding-curve/
│   │   ├── create-token.ts               # Exemple de création de token
│   │   └── buy-sell.ts                   # Achat/vente sur courbes de liaison
│   ├── swap/
│   │   └── swap.ts                       # Trading PumpSwap
│   ├── liquidity/
│   │   └── add-remove.ts                 # Ajouter/retirer liquidité
│   └── creator-fees/
│       └── collect-fees.ts               # Collecte des frais
├── templates/
│   └── pumpfun-setup.ts                  # Modèle de démarrage
└── docs/
    └── troubleshooting.md                # Problèmes courants

Vérification

  • Un appel RPC/SDK réel a été effectué (mainnet, devnet, ou validateur local) et la réponse du payload est capturée dans la transcription, pas juste paraphrasée
  • Chaque transaction a été simulée (simulateTransaction ou équivalent) avant toute étape de signature/envoi ; les logs de simulation sont joints
  • Pour toute transaction signée/envoyée, la signature résultante est enregistrée et confirmée on-chain (statut retourné par getSignatureStatuses ou une URL d'explorateur)
  • Slippage, frais prioritaires, et limites d'unités de calcul ont été définis explicitement avec des valeurs numériques concrètes, pas laissés aux valeurs par défaut de la librairie
  • Les adresses de compte, les mints et les IDs de programme utilisés dans l'exécution correspondent aux adresses documentées pumpfun-launches pour le cluster ciblé (pas de mélange mainnet/devnet)
  • Le chemin d'échec a été exercé au moins une fois (solde insuffisant, oracle obsolète, blockhash expiré, etc.) et la gestion d'erreur de l'agent a produit un message lisible pour l'utilisateur

Skills similaires