apps/web/lib/solana/pump.ts

pump.fun launch instructions and the lookup table contents. Shown whole, as it was in the repository when this site was built. Line numbers link: add #L12 to the address.

apps/web/lib/solana/pump.ts213 lines
1// pump.fun instruction builders for the launch, with @solana/web3.js only. Account lists and data mirror the official2// @pump-fun/pump-sdk 2.0.0 (`createV2Instruction`, `createFeeSharingConfig`, `updateFeeShares`, and `buy`'s account3// list for `buy_exact_sol_in`); checked byte for byte against the SDK and simulated read-only on mainnet (sigVerify off).4// Isomorphic: no React, no server-only.5import '../launch/polyfill';6import { PublicKey, TransactionInstruction, type AccountMeta } from '@solana/web3.js';7import { createAssociatedTokenAccountIdempotentInstruction, getAssociatedTokenAddressSync } from '@solana/spl-token';8import {9  PUMP_BUYBACK_FEE_RECIPIENTS,10  PUMP_FEE_RECIPIENTS,11  PUMP_IDS,12  PUMP_SEEDS,13  buyExactSolInData,14  createFeeSharingConfigData,15  createV2Data,16  updateFeeSharesData,17} from '@solary/core/pump';18 19export const PUMP_PROGRAM = new PublicKey(PUMP_IDS.pump);20export const PUMP_AMM_PROGRAM = new PublicKey(PUMP_IDS.pumpAmm);21export const PUMP_FEES_PROGRAM = new PublicKey(PUMP_IDS.pumpFees);22export const MAYHEM_PROGRAM = new PublicKey(PUMP_IDS.mayhem);23export const TOKEN_PROGRAM = new PublicKey(PUMP_IDS.token);24export const TOKEN_2022_PROGRAM = new PublicKey(PUMP_IDS.token2022);25export const ATA_PROGRAM = new PublicKey(PUMP_IDS.ata);26export const SYSTEM_PROGRAM = new PublicKey(PUMP_IDS.system);27export const WSOL_MINT = new PublicKey(PUMP_IDS.wsol);28 29const enc = (s: string) => new TextEncoder().encode(s);30const pda = (seeds: Array<string | PublicKey>, program: PublicKey) =>31  PublicKey.findProgramAddressSync(32    seeds.map((s) => (typeof s === 'string' ? enc(s) : s.toBytes())),33    program,34  )[0];35 36export const pumpPda = {37  global: () => pda([PUMP_SEEDS.global], PUMP_PROGRAM),38  mintAuthority: () => pda([PUMP_SEEDS.mintAuthority], PUMP_PROGRAM),39  bondingCurve: (mint: PublicKey) => pda([PUMP_SEEDS.bondingCurve, mint], PUMP_PROGRAM),40  bondingCurveV2: (mint: PublicKey) => pda([PUMP_SEEDS.bondingCurveV2, mint], PUMP_PROGRAM),41  creatorVault: (creator: PublicKey) => pda([PUMP_SEEDS.creatorVault, creator], PUMP_PROGRAM),42  eventAuthority: (program: PublicKey) => pda([PUMP_SEEDS.eventAuthority], program),43  globalVolumeAccumulator: () => pda([PUMP_SEEDS.globalVolumeAccumulator], PUMP_PROGRAM),44  userVolumeAccumulator: (user: PublicKey) => pda([PUMP_SEEDS.userVolumeAccumulator, user], PUMP_PROGRAM),45  feeConfig: () => pda([PUMP_SEEDS.feeConfig, PUMP_PROGRAM], PUMP_FEES_PROGRAM),46  sharingConfig: (mint: PublicKey) => pda([PUMP_SEEDS.sharingConfig, mint], PUMP_FEES_PROGRAM),47  ammCreatorVaultAuthority: (creator: PublicKey) => pda([PUMP_SEEDS.ammCreatorVault, creator], PUMP_AMM_PROGRAM),48  mayhemGlobalParams: () => pda([PUMP_SEEDS.mayhemGlobalParams], MAYHEM_PROGRAM),49  mayhemSolVault: () => pda([PUMP_SEEDS.mayhemSolVault], MAYHEM_PROGRAM),50  mayhemState: (mint: PublicKey) => pda([PUMP_SEEDS.mayhemState, mint], MAYHEM_PROGRAM),51};52 53const ro = (pubkey: PublicKey): AccountMeta => ({ pubkey, isSigner: false, isWritable: false });54const w = (pubkey: PublicKey): AccountMeta => ({ pubkey, isSigner: false, isWritable: true });55const signerW = (pubkey: PublicKey): AccountMeta => ({ pubkey, isSigner: true, isWritable: true });56 57/** create_v2: a Token-2022 pump.fun coin quoted in SOL; `creator` receives the creator fees until fee sharing is set. */58export function createV2Ix(a: { mint: PublicKey; user: PublicKey; creator: PublicKey; name: string; symbol: string; uri: string }): TransactionInstruction {59  const bondingCurve = pumpPda.bondingCurve(a.mint);60  const solVault = pumpPda.mayhemSolVault();61  return new TransactionInstruction({62    programId: PUMP_PROGRAM,63    keys: [64      signerW(a.mint),65      ro(pumpPda.mintAuthority()),66      w(bondingCurve),67      w(getAssociatedTokenAddressSync(a.mint, bondingCurve, true, TOKEN_2022_PROGRAM)),68      ro(pumpPda.global()),69      signerW(a.user),70      ro(SYSTEM_PROGRAM),71      ro(TOKEN_2022_PROGRAM),72      ro(ATA_PROGRAM),73      w(MAYHEM_PROGRAM),74      ro(pumpPda.mayhemGlobalParams()),75      w(solVault),76      w(pumpPda.mayhemState(a.mint)),77      w(getAssociatedTokenAddressSync(a.mint, solVault, true, TOKEN_2022_PROGRAM)),78      ro(pumpPda.eventAuthority(PUMP_PROGRAM)),79      ro(PUMP_PROGRAM),80    ],81    data: Buffer.from(createV2Data({ name: a.name, symbol: a.symbol, uri: a.uri, creator: a.creator.toBase58() })),82  });83}84 85/** pump_fees.create_fee_sharing_config for an ungraduated coin; the payer must be the coin's creator. */86export function createFeeSharingConfigIx(a: { mint: PublicKey; creator: PublicKey }): TransactionInstruction {87  return new TransactionInstruction({88    programId: PUMP_FEES_PROGRAM,89    keys: [90      ro(pumpPda.eventAuthority(PUMP_FEES_PROGRAM)),91      ro(PUMP_FEES_PROGRAM),92      signerW(a.creator),93      ro(pumpPda.global()),94      ro(a.mint),95      w(pumpPda.sharingConfig(a.mint)),96      ro(SYSTEM_PROGRAM),97      w(pumpPda.bondingCurve(a.mint)),98      ro(PUMP_PROGRAM),99      ro(pumpPda.eventAuthority(PUMP_PROGRAM)),100      // `pool` is optional: Anchor's "none" is the instruction's own program id (read-only)101      ro(PUMP_FEES_PROGRAM),102      ro(PUMP_AMM_PROGRAM),103      ro(pumpPda.eventAuthority(PUMP_AMM_PROGRAM)),104    ],105    data: Buffer.from(createFeeSharingConfigData()),106  });107}108 109/** pump_fees.update_fee_shares: set the (one-time, then locked) reward split. */110export function updateFeeSharesIx(a: {111  mint: PublicKey;112  authority: PublicKey;113  currentShareholders: PublicKey[];114  shareholders: Array<{ address: PublicKey; bps: number }>;115}): TransactionInstruction {116  const cfg = pumpPda.sharingConfig(a.mint);117  const vaultAuthority = pumpPda.ammCreatorVaultAuthority(cfg);118  return new TransactionInstruction({119    programId: PUMP_FEES_PROGRAM,120    keys: [121      ro(pumpPda.eventAuthority(PUMP_FEES_PROGRAM)),122      ro(PUMP_FEES_PROGRAM),123      { pubkey: a.authority, isSigner: true, isWritable: false },124      ro(pumpPda.global()),125      ro(a.mint),126      w(cfg),127      ro(pumpPda.bondingCurve(a.mint)),128      w(pumpPda.creatorVault(cfg)),129      ro(SYSTEM_PROGRAM),130      ro(PUMP_PROGRAM),131      ro(pumpPda.eventAuthority(PUMP_PROGRAM)),132      ro(PUMP_AMM_PROGRAM),133      ro(pumpPda.eventAuthority(PUMP_AMM_PROGRAM)),134      ro(WSOL_MINT),135      ro(TOKEN_PROGRAM),136      ro(ATA_PROGRAM),137      w(vaultAuthority),138      w(getAssociatedTokenAddressSync(WSOL_MINT, vaultAuthority, true, TOKEN_PROGRAM)),139      ...a.currentShareholders.map(w),140    ],141    data: Buffer.from(updateFeeSharesData(a.shareholders.map((s) => ({ address: s.address.toBase58(), bps: s.bps })))),142  });143}144 145const pick = <T,>(list: readonly T[]) => list[Math.floor(Math.random() * list.length)]!;146 147/**148 * The deployer's first buy, in the launch transaction: spend exactly `lamports` (fees included). Atomic with the149 * create, so nothing trades in between and `minTokensOut` can stay at 1. `creator` is the curve's creator at the150 * time of the buy: the sharing config once fee sharing is set.151 */152export function buyExactSolInIxs(a: { mint: PublicKey; user: PublicKey; creator: PublicKey; lamports: bigint; minTokensOut?: bigint }): TransactionInstruction[] {153  const bondingCurve = pumpPda.bondingCurve(a.mint);154  const userAta = getAssociatedTokenAddressSync(a.mint, a.user, true, TOKEN_2022_PROGRAM);155  return [156    createAssociatedTokenAccountIdempotentInstruction(a.user, userAta, a.user, a.mint, TOKEN_2022_PROGRAM),157    new TransactionInstruction({158      programId: PUMP_PROGRAM,159      keys: [160        ro(pumpPda.global()),161        w(new PublicKey(pick(PUMP_FEE_RECIPIENTS))),162        ro(a.mint),163        w(bondingCurve),164        w(getAssociatedTokenAddressSync(a.mint, bondingCurve, true, TOKEN_2022_PROGRAM)),165        w(userAta),166        signerW(a.user),167        ro(SYSTEM_PROGRAM),168        ro(TOKEN_2022_PROGRAM),169        w(pumpPda.creatorVault(a.creator)),170        ro(pumpPda.eventAuthority(PUMP_PROGRAM)),171        ro(PUMP_PROGRAM),172        ro(pumpPda.globalVolumeAccumulator()),173        w(pumpPda.userVolumeAccumulator(a.user)),174        ro(pumpPda.feeConfig()),175        ro(PUMP_FEES_PROGRAM),176        // remaining accounts, as the SDK's buy passes them177        ro(pumpPda.bondingCurveV2(a.mint)),178        w(new PublicKey(pick(PUMP_BUYBACK_FEE_RECIPIENTS))),179      ],180      data: Buffer.from(buyExactSolInData(a.lamports, a.minTokensOut ?? 1n)),181    }),182  ];183}184 185/**186 * Every account a Solary launch uses that is the same for every launch: what a lookup table should hold so the187 * whole launch (create + fee sharing + first buy + priority fee) fits one transaction.188 */189export function launchLookupAddresses(): PublicKey[] {190  return [191    PUMP_PROGRAM,192    PUMP_FEES_PROGRAM,193    PUMP_AMM_PROGRAM,194    MAYHEM_PROGRAM,195    SYSTEM_PROGRAM,196    TOKEN_PROGRAM,197    TOKEN_2022_PROGRAM,198    ATA_PROGRAM,199    WSOL_MINT,200    new PublicKey(PUMP_IDS.computeBudget),201    pumpPda.global(),202    pumpPda.mintAuthority(),203    pumpPda.eventAuthority(PUMP_PROGRAM),204    pumpPda.eventAuthority(PUMP_FEES_PROGRAM),205    pumpPda.eventAuthority(PUMP_AMM_PROGRAM),206    pumpPda.mayhemGlobalParams(),207    pumpPda.mayhemSolVault(),208    pumpPda.globalVolumeAccumulator(),209    pumpPda.feeConfig(),210    ...PUMP_FEE_RECIPIENTS.map((k) => new PublicKey(k)),211    ...PUMP_BUYBACK_FEE_RECIPIENTS.map((k) => new PublicKey(k)),212  ];213}