packages/core/src/pump.ts

pump.fun program ids, instruction data and account layouts. Shown whole, as it was in the repository when this site was built. Line numbers link: add #L12 to the address.

packages/core/src/pump.ts319 lines
1// pump.fun on Solana: program ids, instruction and account discriminators, and account layouts shared by the web2// launch adapter and the keeper. Mirrors the official @pump-fun/pump-sdk 2.0.0 IDLs (pump, pump_fees, pump_amm).3//4// How Solary launches on pump.fun: ONE transaction signed by the deployer's wallet5//   create_v2(creator = deployer) → pump_fees.create_fee_sharing_config (payer = creator) →6//   pump_fees.update_fee_shares([{ Solary vault, vault bps }, { deployer, creator bps }?]) → [ATA + first buy]7// After it, the coin's creator is the per-mint sharing-config PDA, so its creator fees accrue in a per-mint vault8// (creator-vault(sharing_config)). The split is set once and locked (pump's one-time reward-split policy).9// Anyone may call pump.distribute_creator_fees (no signer) to pay the vault to the shareholders: the Solary vault (and the creator, when they kept a cut).10// After graduation, pump_amm.transfer_creator_fees_to_pump (no signer) first moves the PumpSwap creator fees11// (WSOL) into the same vault.12 13export const PUMP_IDS = {14  pump: '6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P',15  pumpAmm: 'pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA',16  pumpFees: 'pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ',17  mayhem: 'MAyhSmzXzV1pTf7LsNkrNwkWKTo4ougAJ1PPg47MD4e',18  system: '11111111111111111111111111111111',19  token: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',20  token2022: 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb',21  ata: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL',22  wsol: 'So11111111111111111111111111111111111111112',23  computeBudget: 'ComputeBudget111111111111111111111111111111',24} as const;25 26/** Anchor instruction discriminators (first 8 bytes of instruction data). */27export const PUMP_IX = {28  createV2: [214, 144, 76, 236, 95, 139, 49, 180],29  buyExactSolIn: [56, 252, 116, 8, 158, 223, 205, 95],30  distributeCreatorFees: [165, 114, 103, 0, 121, 206, 247, 81],31  createFeeSharingConfig: [195, 78, 86, 76, 111, 52, 251, 213],32  updateFeeShares: [189, 13, 136, 99, 187, 164, 237, 35],33  transferCreatorFeesToPump: [139, 52, 134, 85, 228, 229, 108, 241],34} as const;35 36/** Anchor account and event discriminators. */37export const PUMP_ACCOUNT = {38  bondingCurve: [23, 183, 248, 55, 96, 216, 172, 96],39  sharingConfig: [216, 74, 9, 0, 56, 140, 93, 75],40  distributeCreatorFeesEvent: [165, 55, 129, 112, 4, 179, 202, 40],41} as const;42 43/** Anchor's `emit_cpi!` self-CPI tag: event instruction data = tag ‖ event discriminator ‖ borsh(event). */44export const ANCHOR_EVENT_IX_TAG = [228, 69, 165, 46, 81, 203, 154, 29] as const;45 46/** PDA seeds as UTF-8 strings (program in brackets). */47export const PUMP_SEEDS = {48  global: 'global', // [pump]49  bondingCurve: 'bonding-curve', // [pump] + mint50  bondingCurveV2: 'bonding-curve-v2', // [pump] + mint51  creatorVault: 'creator-vault', // [pump] + creator52  mintAuthority: 'mint-authority', // [pump]53  eventAuthority: '__event_authority', // [each program]54  globalVolumeAccumulator: 'global_volume_accumulator', // [pump]55  userVolumeAccumulator: 'user_volume_accumulator', // [pump] + user56  feeConfig: 'fee_config', // [pump_fees] + pump program id57  sharingConfig: 'sharing-config', // [pump_fees] + mint58  ammCreatorVault: 'creator_vault', // [pump_amm] + coin creator59  mayhemGlobalParams: 'global-params', // [mayhem]60  mayhemSolVault: 'sol-vault', // [mayhem]61  mayhemState: 'mayhem-state', // [mayhem] + mint62} as const;63 64/** Protocol fee recipients the SDK picks from at random for trades (@pump-fun/pump-sdk 2.0.0 bondingCurve.ts). */65export const PUMP_FEE_RECIPIENTS = [66  '62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV',67  '7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ',68  '7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX',69  '9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz',70  'AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY',71  'CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM',72  'FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz',73  'G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP',74] as const;75 76/** Buyback fee recipients, the second remaining account of a buy (@pump-fun/pump-sdk 2.0.0). */77export const PUMP_BUYBACK_FEE_RECIPIENTS = [78  '5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD',79  '9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7',80  'GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL',81  '3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR',82  '5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6',83  'EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL',84  '5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD',85  'A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW',86] as const;87 88/** Rent-exempt minimum of a 0-byte system account (the creator vault PDA keeps this much). */89export const RENT_EXEMPT_ZERO_BYTES = 890_880;90export const LAMPORTS_PER_SOL_N = 1_000_000_000;91 92// ---------------------------------------------------------------- base5893 94const B58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';95const B58_MAP: Record<string, number> = Object.fromEntries([...B58].map((c, i) => [c, i]));96export const BASE58_RE = /^[1-9A-HJ-NP-Za-km-z]+$/;97/** A Solana address (32 bytes in base58: 32–44 characters). */98export const SOL_ADDRESS_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;99/** A transaction signature (64 bytes in base58: 64–88 characters). */100export const SOL_SIGNATURE_RE = /^[1-9A-HJ-NP-Za-km-z]{64,88}$/;101 102export function base58Encode(bytes: Uint8Array): string {103  let zeros = 0;104  while (zeros < bytes.length && bytes[zeros] === 0) zeros++;105  let n = 0n;106  for (const b of bytes) n = (n << 8n) | BigInt(b);107  let out = '';108  while (n > 0n) {109    out = B58[Number(n % 58n)] + out;110    n /= 58n;111  }112  return '1'.repeat(zeros) + out;113}114 115export function base58Decode(s: string): Uint8Array {116  if (!BASE58_RE.test(s)) throw new Error('not base58');117  let zeros = 0;118  while (zeros < s.length && s[zeros] === '1') zeros++;119  let n = 0n;120  for (const c of s) n = n * 58n + BigInt(B58_MAP[c]!);121  const body: number[] = [];122  while (n > 0n) {123    body.unshift(Number(n & 0xffn));124    n >>= 8n;125  }126  return Uint8Array.from([...new Array<number>(zeros).fill(0), ...body]);127}128 129/** A well-formed 32-byte base58 address. */130export function isSolAddress(s: string): boolean {131  if (!SOL_ADDRESS_RE.test(s)) return false;132  try {133    return base58Decode(s).length === 32;134  } catch {135    return false;136  }137}138 139// ---------------------------------------------------------------- account layouts140 141const u64 = (d: Uint8Array, o: number) => new DataView(d.buffer, d.byteOffset + o, 8).getBigUint64(0, true);142const u32 = (d: Uint8Array, o: number) => new DataView(d.buffer, d.byteOffset + o, 4).getUint32(0, true);143const u16 = (d: Uint8Array, o: number) => new DataView(d.buffer, d.byteOffset + o, 2).getUint16(0, true);144const key = (d: Uint8Array, o: number) => base58Encode(d.subarray(o, o + 32));145const hasDisc = (d: Uint8Array, disc: readonly number[]) => d.length >= 8 && disc.every((b, i) => d[i] === b);146 147export interface PumpBondingCurve {148  virtualTokenReserves: bigint;149  virtualQuoteReserves: bigint;150  realTokenReserves: bigint;151  realQuoteReserves: bigint;152  tokenTotalSupply: bigint;153  /** graduated: the curve is complete and the coin trades on PumpSwap */154  complete: boolean;155  /** receives the creator fees; the sharing-config PDA for a Solary coin */156  creator: string;157  isMayhemMode: boolean;158  /** base58; the zero key (111…1) means SOL */159  quoteMint: string | null;160}161 162/** BondingCurve account (every historical length: 81 bytes minimum up to 151 after extend_account). */163export function decodeBondingCurve(data: Uint8Array): PumpBondingCurve {164  if (!hasDisc(data, PUMP_ACCOUNT.bondingCurve) || data.length < 81) throw new Error('not a pump bonding curve');165  return {166    virtualTokenReserves: u64(data, 8),167    virtualQuoteReserves: u64(data, 16),168    realTokenReserves: u64(data, 24),169    realQuoteReserves: u64(data, 32),170    tokenTotalSupply: u64(data, 40),171    complete: data[48] === 1,172    creator: key(data, 49),173    isMayhemMode: data.length > 81 && data[81] === 1,174    quoteMint: data.length >= 115 ? key(data, 83) : null,175  };176}177 178export interface PumpSharingConfig {179  version: number;180  /** 0 paused, 1 active */181  status: number;182  mint: string;183  admin: string;184  adminRevoked: boolean;185  shareholders: Array<{ address: string; bps: number }>;186}187 188/** pump_fees SharingConfig account. */189export function decodeSharingConfig(data: Uint8Array): PumpSharingConfig {190  if (!hasDisc(data, PUMP_ACCOUNT.sharingConfig) || data.length < 80) throw new Error('not a pump sharing config');191  const n = u32(data, 76);192  if (n > 10 || data.length < 80 + n * 34) throw new Error('bad sharing config');193  const shareholders = Array.from({ length: n }, (_, i) => ({ address: key(data, 80 + i * 34), bps: u16(data, 80 + i * 34 + 32) }));194  return { version: data[9]!, status: data[10]!, mint: key(data, 11), admin: key(data, 43), adminRevoked: data[75] === 1, shareholders };195}196 197/** The reward split can no longer change (pump's one-time policy: every v1 config, or v2 with the admin revoked). */198export function sharingConfigLocked(c: PumpSharingConfig): boolean {199  return c.version === 1 || c.adminRevoked;200}201 202/** Byte offset of the first shareholder address in a SharingConfig: a getProgramAccounts memcmp finds our coins. */203export const SHARING_CONFIG_FIRST_SHAREHOLDER_OFFSET = 80;204 205export interface DistributeCreatorFeesEvent {206  timestamp: number;207  mint: string;208  bondingCurve: string;209  sharingConfig: string;210  admin: string;211  shareholders: Array<{ address: string; bps: number }>;212  /** lamports paid out by this call */213  distributed: bigint;214}215 216/** Decode the event body (after the 8-byte tag and 8-byte discriminator, or a `Program data:` payload). */217export function decodeDistributeEvent(data: Uint8Array): DistributeCreatorFeesEvent | null {218  let d = data;219  if (hasDisc(d, ANCHOR_EVENT_IX_TAG)) d = d.subarray(8);220  if (!hasDisc(d, PUMP_ACCOUNT.distributeCreatorFeesEvent)) return null;221  d = d.subarray(8);222  const n = u32(d, 136);223  const shareholders = Array.from({ length: n }, (_, i) => ({ address: key(d, 140 + i * 34), bps: u16(d, 140 + i * 34 + 32) }));224  const after = 140 + n * 34;225  return {226    timestamp: Number(new DataView(d.buffer, d.byteOffset, 8).getBigInt64(0, true)),227    mint: key(d, 8),228    bondingCurve: key(d, 40),229    sharingConfig: key(d, 72),230    admin: key(d, 104),231    shareholders,232    distributed: u64(d, after),233  };234}235 236// ---------------------------------------------------------------- instruction data237 238const utf8 = new TextEncoder();239 240function borshString(s: string): Uint8Array {241  const b = utf8.encode(s);242  const out = new Uint8Array(4 + b.length);243  new DataView(out.buffer).setUint32(0, b.length, true);244  out.set(b, 4);245  return out;246}247 248function u64Bytes(v: bigint): Uint8Array {249  const out = new Uint8Array(8);250  new DataView(out.buffer).setBigUint64(0, v, true);251  return out;252}253 254function concat(parts: ReadonlyArray<Uint8Array | readonly number[]>): Uint8Array {255  const arrs = parts.map((p) => (p instanceof Uint8Array ? p : Uint8Array.from(p)));256  const out = new Uint8Array(arrs.reduce((n, a) => n + a.length, 0));257  let o = 0;258  for (const a of arrs) {259    out.set(a, o);260    o += a.length;261  }262  return out;263}264 265/** create_v2(name, symbol, uri, creator, is_mayhem_mode=false, cashback=false, creator_fee_bps=0, holder_reward=false). */266export function createV2Data(a: { name: string; symbol: string; uri: string; creator: string }): Uint8Array {267  return concat([PUMP_IX.createV2, borshString(a.name), borshString(a.symbol), borshString(a.uri), base58Decode(a.creator), [0], [0], u64Bytes(0n), [0]]);268}269 270/** update_fee_shares(shareholders: Vec<{address, share_bps u16}>). */271export function updateFeeSharesData(shareholders: ReadonlyArray<{ address: string; bps: number }>): Uint8Array {272  const len = new Uint8Array(4);273  new DataView(len.buffer).setUint32(0, shareholders.length, true);274  const items = shareholders.map((s) => {275    const b = new Uint8Array(34);276    b.set(base58Decode(s.address), 0);277    new DataView(b.buffer).setUint16(32, s.bps, true);278    return b;279  });280  return concat([PUMP_IX.updateFeeShares, len, ...items]);281}282 283/** buy_exact_sol_in(spendable_sol_in, min_tokens_out, track_volume=true). */284export function buyExactSolInData(spendableLamports: bigint, minTokensOut: bigint): Uint8Array {285  return concat([PUMP_IX.buyExactSolIn, u64Bytes(spendableLamports), u64Bytes(minTokensOut), [1]]);286}287 288export const createFeeSharingConfigData = (): Uint8Array => Uint8Array.from(PUMP_IX.createFeeSharingConfig);289export const distributeCreatorFeesData = (): Uint8Array => Uint8Array.from(PUMP_IX.distributeCreatorFees);290export const transferCreatorFeesToPumpData = (): Uint8Array => Uint8Array.from(PUMP_IX.transferCreatorFeesToPump);291 292// ---------------------------------------------------------------- Token-2022 metadata (name / symbol / uri of a pump coin)293 294/** Read the TokenMetadata extension (type 19) of a Token-2022 mint account, or null. */295export function decodeToken2022Metadata(data: Uint8Array): { name: string; symbol: string; uri: string } | null {296  // base mint 82 bytes, padding to 165, account type byte at 165, TLV entries from 166297  if (data.length < 170 || data[165] !== 1) return null;298  const dec = new TextDecoder();299  let o = 166;300  while (o + 4 <= data.length) {301    const type = u16(data, o);302    const len = u16(data, o + 2);303    const body = data.subarray(o + 4, o + 4 + len);304    if (type === 19) {305      // update_authority 32, mint 32, then borsh strings306      let p = 64;307      const str = () => {308        const n = u32(body, p);309        const s = dec.decode(body.subarray(p + 4, p + 4 + n));310        p += 4 + n;311        return s;312      };313      return { name: str(), symbol: str(), uri: str() };314    }315    if (type === 0) break;316    o += 4 + len;317  }318  return null;319}