// launch-pump.mjs — launch a pump.fun coin at YOUR vanity mint address. // // Uses PumpPortal's Local Transaction API: PumpPortal builds the create transaction, // and it is signed here on your machine by your wallet and your vanity mint keypair. // Your private keys are never sent anywhere. // // Setup (once): npm install @solana/web3.js bs58 // Preview: node launch-pump.mjs // Launch for real: node launch-pump.mjs --go // // Environment variables (set them in your terminal, don't paste keys into this file): // PRIVATE_KEY base58 private key of the wallet that pays and becomes the creator // PINATA_JWT API key (JWT) from a free pinata.cloud account, used to host the image + metadata // RPC_URL optional, defaults to the public mainnet RPC (a Helius/QuickNode URL is more reliable) // ======================= EDIT THIS ======================= const COIN = { name: 'My Coin', symbol: 'MYCOIN', description: 'Launched with a vanity CA.', image: './logo.png', // path to your image (png, jpg, gif, webp) twitter: '', // optional links, leave '' to skip telegram: '', website: '', }; const MINT_FILE = './mint.json'; // the keypair file you downloaded from the grinder const DEV_BUY_SOL = 0.1; // how much SOL you buy at launch (0 for none) const SLIPPAGE = 10; // percent const PRIORITY_FEE_SOL = 0.0005; // tip so the transaction lands fast // ========================================================= import fs from 'node:fs'; import path from 'node:path'; import { Connection, Keypair, VersionedTransaction, LAMPORTS_PER_SOL } from '@solana/web3.js'; import bs58 from 'bs58'; const GO = process.argv.includes('--go'); const RPC_URL = process.env.RPC_URL || 'https://api.mainnet-beta.solana.com'; function die(msg) { console.error('\n✖ ' + msg + '\n'); process.exit(1); } // ---- load keys ---- if (!process.env.PRIVATE_KEY) die('Set PRIVATE_KEY to your wallet\'s base58 private key first.'); let wallet; try { wallet = Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY.trim())); } catch { die('PRIVATE_KEY is not a valid base58 Solana private key.'); } if (!fs.existsSync(MINT_FILE)) die(`Can't find ${MINT_FILE}. Put the .json from the grinder next to this script.`); let mint; try { const arr = JSON.parse(fs.readFileSync(MINT_FILE, 'utf8')); if (!Array.isArray(arr) || arr.length !== 64) throw new Error(); mint = Keypair.fromSecretKey(Uint8Array.from(arr)); } catch { die(`${MINT_FILE} isn't a valid keypair file (expected a list of 64 numbers).`); } if (!fs.existsSync(COIN.image)) die(`Can't find the image at ${COIN.image}.`); if (!COIN.name || !COIN.symbol) die('Fill in COIN.name and COIN.symbol at the top of the script.'); const connection = new Connection(RPC_URL, 'confirmed'); // ---- preflight checks ---- let balance, existing; try { balance = (await connection.getBalance(wallet.publicKey)) / LAMPORTS_PER_SOL; existing = await connection.getAccountInfo(mint.publicKey); } catch (e) { die(`Couldn't reach the Solana RPC at ${RPC_URL}. Check your internet, or set RPC_URL to a Helius/QuickNode URL.`); } const needed = DEV_BUY_SOL * 1.02 + 0.03; // dev buy + fees + rent, rough console.log(` Coin ${COIN.name} ($${COIN.symbol}) Vanity CA ${mint.publicKey.toBase58()} Creator ${wallet.publicKey.toBase58()} Balance ${balance.toFixed(4)} SOL (need about ${needed.toFixed(3)}) Dev buy ${DEV_BUY_SOL} SOL RPC ${RPC_URL} `); if (existing) die('Something already exists at this mint address. This keypair has been used or leaked. Grind a new one.'); if (balance < needed) die('Not enough SOL in the creator wallet.'); if (!GO) { console.log('Looks good. Nothing has been sent. Run again with --go to launch.\n'); process.exit(0); } if (!process.env.PINATA_JWT) die('Set PINATA_JWT (free key from pinata.cloud) so the image and metadata can be hosted.'); // ---- upload image + metadata to IPFS via Pinata ---- async function pin(blob, filename) { const form = new FormData(); form.append('network', 'public'); form.append('file', blob, filename); const r = await fetch('https://uploads.pinata.cloud/v3/files', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PINATA_JWT}` }, body: form, }); if (!r.ok) die(`Pinata upload failed (${r.status}): ${await r.text()}`); const j = await r.json(); return `https://ipfs.io/ipfs/${j.data.cid}`; } const ext = path.extname(COIN.image).slice(1).toLowerCase(); const mime = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp' }[ext] || 'application/octet-stream'; console.log('Uploading image…'); const imageUri = await pin(new Blob([fs.readFileSync(COIN.image)], { type: mime }), path.basename(COIN.image)); const meta = { name: COIN.name, symbol: COIN.symbol, description: COIN.description, image: imageUri, showName: true, createdOn: 'https://pump.fun' }; for (const k of ['twitter', 'telegram', 'website']) if (COIN[k]) meta[k] = COIN[k]; console.log('Uploading metadata…'); const metadataUri = await pin(new Blob([JSON.stringify(meta)], { type: 'application/json' }), 'metadata.json'); // ---- ask PumpPortal to build the create transaction ---- console.log('Building transaction…'); const res = await fetch('https://pumpportal.fun/api/trade-local', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ publicKey: wallet.publicKey.toBase58(), action: 'create', tokenMetadata: { name: COIN.name, symbol: COIN.symbol, uri: metadataUri }, mint: mint.publicKey.toBase58(), denominatedInSol: 'true', amount: DEV_BUY_SOL, slippage: SLIPPAGE, priorityFee: PRIORITY_FEE_SOL, pool: 'pump', }), }); if (res.status !== 200) die(`PumpPortal returned ${res.status}: ${await res.text()}`); // ---- sign locally with BOTH keys and send ---- const tx = VersionedTransaction.deserialize(new Uint8Array(await res.arrayBuffer())); tx.sign([mint, wallet]); console.log('Sending…'); const sig = await connection.sendTransaction(tx, { skipPreflight: false, maxRetries: 3 }); const conf = await connection.confirmTransaction(sig, 'confirmed'); if (conf.value.err) die(`Transaction failed: ${JSON.stringify(conf.value.err)} https://solscan.io/tx/${sig}`); console.log(` ✔ Launched! CA ${mint.publicKey.toBase58()} pump.fun https://pump.fun/coin/${mint.publicKey.toBase58()} tx https://solscan.io/tx/${sig} `);