Skip to main content
Glama

raydium-launchlab-mcp

index.js11.2 kB
const dotenv = require("dotenv"); const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js"); const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js"); const { z } = require("zod"); const { Connection, Keypair, PublicKey } = require("@solana/web3.js"); const { getMint, NATIVE_MINT } = require("@solana/spl-token") const { Raydium, TxVersion, getPdaLaunchpadConfigId, LAUNCHPAD_PROGRAM } = require("@raydium-io/raydium-sdk-v2"); const BN = require("bn.js") const { PinataSDK } = require("pinata"); const fs = require("fs"); // Load environment variables from .env file dotenv.config(); // Validate environment variables const RPC_URL = process.env.RPC_URL; const PRIVATE_KEY = process.env.PRIVATE_KEY; if (!RPC_URL || !PRIVATE_KEY) { throw new Error("Missing RPC_URL or PRIVATE_KEY in environment variables"); } // Initialize keypair from private key (integer array) const keypair = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(PRIVATE_KEY))); const cluster = "mainnet" const programId = LAUNCHPAD_PROGRAM const platformId = new PublicKey("GnLd8ohnZ588sGntFFhSK9M9FyU7n3YBzsvcMwqQtoCf") // Initialize Solana connection const connection = new Connection(RPC_URL, "confirmed"); let raydium async function initSdk(){ if(raydium) return raydium raydium = await Raydium.load({ owner: keypair, connection, cluster, disableFeatureCheck: true, disableLoadToken: true, blockhashCommitment: "finalized" }) return raydium } function floatToBN(amount, decimals) { const [whole, fraction = ''] = amount.toString().split('.'); const fractionPadded = (fraction + '0'.repeat(decimals)).slice(0, decimals); return new BN(whole + fractionPadded); } async function uploadImageAndMetadata(imagePath, name, symbol, description="") { if(!process.env.PINATA_GATEWAY || !process.env.PINATA_JWT){ throw new Error("Missing PINATA_GATEWAY or PINATA_JWT in environment variables"); } const pinata = new PinataSDK({ pinataJwt: process.env.PINATA_JWT, pinataGateway: process.env.PINATA_GATEWAY }); // Step 1: Upload image const imageFile = new File( [fs.readFileSync(imagePath)], "logo.png", { type: "image/png" } ); const imageUpload = await pinata.upload.public.file(imageFile); // Construct image URL using the CID const imageUrl = `https://gateway.pinata.cloud/ipfs/${imageUpload.cid}`; // Step 2: Create and upload metadata const metadata = { name, symbol, description, image: imageUrl }; const metadataFile = new File( [JSON.stringify(metadata)], "metadata.json", { type: "application/json" } ); const metadataUpload = await pinata.upload.public.file(metadataFile); // Construct metadata URL const metadataUrl = `https://gateway.pinata.cloud/ipfs/${metadataUpload.cid}`; return { imageUrl, metadataUrl, metadata }; } // Create MCP server const server = new McpServer({ name: "Raydium LaunchLab MCP", version: "1.0.0", }); // Register buy_token tool server.tool( "buy_token", "Purchase tokens from a Raydium Launchpad pool using the token mint address", z.object({ mintAddress: z .string() .refine((val) => PublicKey.isOnCurve(val), { message: "Invalid token mint address", }) .describe("The mint address of the token to purchase"), inAmount: z .number() .positive("Amount must be positive") .describe("The amount of SOL used to buy tokens"), slippage: z .number() .positive("Slippage must be positive") .max(1.0, "Slippage must not exceed 100%") .optional() .default(0.01) .describe("The acceptable price slippage percentage (e.g., 0.01 for 1%)"), }), async ({ mintAddress, inAmount, slippage=0.01 }) => { try { const raydium = await initSdk(); const mintA = new PublicKey(mintAddress); const buyAmount = floatToBN(inAmount, 9); const slippageBN = floatToBN(slippage, 4); const { transaction, extInfo, execute } = await raydium.launchpad.buyToken({ programId, mintA, buyAmount, slippage: slippageBN, txVersion: TxVersion.V0, }); // Sign and send transaction const sentInfo = await execute({ sendAndConfirm: true }); return { content: [ { type: "text", text: `Successfully purchased tokens (mint: ${mintAddress}). Transaction signature: ${sentInfo.txId}`, }, ], isError: false, }; } catch (error) { return { content: [ { type: "text", text: `Error purchasing token: ${error.message}`, }, ], isError: true, }; } } ); // Register sell_token tool server.tool( "sell_token", "Sell tokens from a Raydium Launchpad pool using the token mint address", z.object({ mintAddress: z .string() .refine((val) => PublicKey.isOnCurve(val), { message: "Invalid token mint address", }) .describe("The mint address of the token to sell"), inAmount: z .number() .positive("Amount must be positive") .describe("The amount of tokens to sell"), slippage: z .number() .positive("Slippage must be positive") .max(1.0, "Slippage must not exceed 100%") .optional() .default(0.01) .describe("The acceptable price slippage percentage (e.g., 0.01 for 1%)"), }), async ({ mintAddress, inAmount, slippage=0.01 }) => { try { const raydium = await initSdk(); // Fetch token decimals const mintA = new PublicKey(mintAddress); const mintInfo = await getMint(connection, mintA); const decimals = mintInfo.decimals; const sellAmount = floatToBN(inAmount, decimals); const slippageBN = floatToBN(slippage, 4) const { transaction, extInfo, execute } = await raydium.launchpad.sellToken({ programId, mintA, sellAmount, slippage: slippageBN, txVersion: TxVersion.V0, }); // Sign and send transaction const sentInfo = await execute({ sendAndConfirm: true }); return { content: [ { type: "text", text: `Successfully sold tokens (mint: ${mintAddress}). Transaction signature: ${sentInfo.txId}`, }, ], isError: false, }; } catch (error) { return { content: [ { type: "text", text: `Error selling token: ${error.message}`, }, ], isError: true, }; } } ); // Register mint_token tool server.tool( "mint_token", "Create a bonding-curve based token on Raydium Launchpad", z.object({ name: z .string() .min(1, "Token name must not be empty") .max(32, "Token name must not exceed 32 characters") .describe("The name of the token (e.g., 'My Token')"), symbol: z .string() .min(1, "Token symbol must not be empty") .max(10, "Token symbol must not exceed 10 characters") .describe("The symbol of the token (e.g., 'MTK')"), imagePath: z .string() .describe("Token image file path (e.g., '/assets/logo.png')"), decimals: z .number() .int("Decimals must be an integer") .min(0, "Decimals must be non-negative") .max(18, "Decimals must not exceed 18") .default(6) .describe("The number of decimal places for the token (e.g., 6 for USDC-like precision)"), fundRaisingTarget: z .number() .positive("Fundraising target must be positive") .default(85) .describe("The target SOL amount to raise for the bonding-curve"), totalSupply: z .number() .default(1_000_000_000) .describe("The total supply of the token"), totalSellPercent: z .number() .default(0.75) .describe("The percentage of total supply for fund raising"), createOnly: z .boolean() .default(true) .describe("Whether to only create the token without buying (true) or include buying (false)"), initialBuyAmount: z .number() .nonnegative("Buy amount must be non-negative") .default(0.1) .describe("The amount of SOL to use for initial token purchase, only available if createOnly is false"), slippage: z .number() .positive("Slippage must be positive") .max(1.0, "Slippage must not exceed 100%") .optional() .default(0.01) .describe("The acceptable price slippage percentage for initial purchase (e.g., 0.01 for 1%)"), }), async ({ name, symbol, imagePath, decimals=6, totalSupply=1000000000, totalSellPercent=0.75, fundRaisingTarget=85, createOnly=true, initialBuyAmount=0.1, slippage=0.01 }) => { try { //upload image file and metadata const {imageUrl, metadataUrl, metadata} = await uploadImageAndMetadata(imagePath, name, symbol) const raydium = await initSdk(); // Create new mint keypair const mintKeypair = Keypair.generate(); const mintA = mintKeypair.publicKey; const configId = getPdaLaunchpadConfigId(programId, NATIVE_MINT, 0, 0).publicKey; // Convert float to BN const totalSupplyBN = floatToBN(totalSupply, decimals); const totalSellAmountBN = floatToBN(totalSupply * totalSellPercent, decimals); const fundRaisingTargetBN = floatToBN(fundRaisingTarget, 9); //SOL const initialBuyAmountBN = floatToBN(initialBuyAmount, 9); //SOL const slippageBN = floatToBN(slippage, 4); const { execute, transactions, extInfo } = await raydium.launchpad.createLaunchpad({ programId, mintA, decimals, name, symbol, migrateType: 'cpmm', uri: metadataUrl, configId, platformId, txVersion: TxVersion.V0, extraSigners: [mintKeypair], totalFundRaisingB: fundRaisingTargetBN, //SOL supply: totalSupplyBN, totalSellA: totalSellAmountBN, createOnly, buyAmount: initialBuyAmountBN, //SOL slippage: slippageBN, }) // Sign and send transaction const sentInfo = await execute({ sendAndConfirm: true }); return { content: [ { type: "text", text: `Successfully created token (mint: ${mintA.toBase58()}, name: ${name}, symbol: ${symbol}). Transaction signatures: ${sentInfo.txIds.join(",")}`, }, ], isError: false, }; } catch (error) { return { content: [ { type: "text", text: `Error creating token mint: ${error.message}`, }, ], isError: true, }; } } ); // Main function to start the server async function main() { const transport = new StdioServerTransport(); // Connect MCP server to transport await server.connect(transport); } // Start the server main().catch(error => console.error);

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kukapay/raydium-launchlab-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server