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}