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
- Création de token - Créer un token avec réserves virtuelles initiales
- Trading - Les utilisateurs achètent/vendent sur la courbe de liaison en utilisant la formule Uniswap V2
- Graduation - Quand le seuil de capitalisation boursière est atteint, la liquidité migre vers PumpSwap
- 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
extendAccounten 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
collectCreatorFeepour 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
- Documentation publique : https://github.com/pump-fun/pump-public-docs
- Fichiers IDL : https://github.com/pump-fun/pump-public-docs/tree/main/idl
- SDK Pump :
@pump-fun/pump-sdksur npm - SDK PumpSwap :
@pump-fun/pump-swap-sdksur npm
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 (
simulateTransactionou é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
getSignatureStatusesou 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