14 min read
Overview
Pump.fun is a permissionless token coin creation and trading platform on Solana that utilizes fair-launch concepts (e.g., no presale or team allocations). In this guide, you'll learn how to interact with the Metis Pump.fun API using Solana Web3.js 2.0 to fetch quotes and execute swaps programmatically. We'll create a TypeScript implementation that handles API requests, transaction signing, and sending transactions to the Solana network.
This guide walks you through using the Metis Pump.fun API with Solana Web3.js 2.0.
If you prefer building with Solana Web3.js Legacy (v1.x), check out our sample code on GitHub.
What You Will Do
- Create a custom RPC transport for Pump.fun API requests
- Implement quote fetching and swap execution
- Handle transaction signing and sending using Web3.js 2.0
- Test the implementation with a sample script
What You Will Need
- A QuickNode Account with the Metis Jupiter Swap API add-on enabled.
- Node.js (version 21.0 or higher recommended)
- Basic familiarity with TypeScript and Solana development concepts
- Experience with Solana Web3.js 2.0 is recommended
Metis Overview
Metis is a powerful tool for developers to access liquidity on Solana. By integrating Jupiter's V6 Swap API, Jupiter's Limit Order API, Pump.fun trading, Trading websockets, and more, you have access to the tools you need to build robust trading tools that access many DeFi protocols across Solana.
Getting Started
1. Set Up Your Project
First, create a new project directory:
mkdir pumpfun-integration && cd pumpfun-integration
Initialize your Node.js project:
npm init -y
Install the required dependencies:
npm install @solana/web3.js@2 dotenv
And if you do not have TypeScript installed globally, you can add it to the project as a dev dependency:
npm install --save-dev typescript ts-node @types/node
Initialize TypeScript configuration (tsconfig.json):
tsc --init
Create the files you will use for this project:
echo > index.ts && echo > types.ts && echo > .env
Update package.json
scripts to run our TypeScript file:
"scripts": {
"start": "ts-node index.ts",
}
2. Create Type Definitions
If you have used Solana Web3.js 2.0 before, you know that a little bit of type definitions at the frontend of your project can go a long way in saving you time and effort. We have gone ahead and created the type definitions for the Pump.fun API for you. You can find the complete type definitions here.
Copy the entire content of the file and paste it into your types.ts
file. These types define the structure of requests and responses for the Pump.fun API, which can be found in our Metis Docs.
3. Set Up Environment Variables
For this guide, you will need a QuickNode account with a Solana endpoint and the Metis API enabled. You can sign up for a QuickNode account here.
Open your .env
and add your wallet secret key (can be generated by running solana-keygen new
if you do not have one), your Metis Endpoint, and your Solana Mainnet HTTP & WSS URLS:
WALLET_SECRET_KEY=[1, 1, 1, 1, 1, 1 ...etc.]
METIS_URL='https://jupiter-swap-api.quiknode.pro/YOUR_ENDPOINT'
HTTP_ENDPOINT='https://example.solana-mainnet.quiknode.pro/123456/'
WSS_ENDPOINT='wss://example.solana-mainnet.quiknode.pro/123456/'
Make sure to replace your HTTP and WSS endpoints with the Solana endpoints from your QuickNode dashboard, and replace https://jupiter-swap-api.quiknode.pro/YOUR_ENDPOINT
with our own Metis endpoint. You can find your Metis address from your QuickNode Dashboard's add-on page (https://dashboard.quicknode.com/endpoints/YOUR_ENDPOINT/add-ons
):
Implementation
Now let's create our implementation and test script to interact with the Pump.fun API. Open index.ts
, and let's implement each part step by step.
1. Import Dependencies and Define Types
First, import the required dependencies and define the types for the Metis PumpFun API:
import {
Rpc,
createRpc,
RpcTransport,
createJsonRpcApi,
address,
getBase64Encoder,
FullySignedTransaction,
TransactionMessageBytes,
getTransactionDecoder,
signTransaction,
createKeyPairFromBytes,
TransactionWithBlockhashLifetime,
getSignatureFromTransaction,
createSolanaRpcSubscriptions,
sendAndConfirmTransactionFactory,
createSolanaRpc,
SolanaRpcApi,
RpcSubscriptions,
SolanaRpcSubscriptionsApi,
getAddressDecoder,
getAddressFromPublicKey
} from "@solana/web3.js";
import {
HttpRequestMethod,
PumpFunEndpoint,
PumpFunQuoteParams,
PumpFunQuoteResponse,
PumpFunRequest,
PumpFunSwapInstructionsResponse,
PumpFunSwapParams,
PumpFunSwapResponse,
SignAndSendTransactionParams
} from "./types";
import dotenv from 'dotenv';
dotenv.config();
type MetisPumpFunApi = {
pumpfun_quote(params: PumpFunQuoteParams): Promise<PumpFunQuoteResponse>;
pumpfun_swap(params: PumpFunSwapParams): Promise<PumpFunSwapResponse>;
pumpfun_swap_instructions(params: PumpFunSwapParams): Promise<PumpFunSwapInstructionsResponse>;
}
const METHOD_TO_ENDPOINT: Record<string, PumpFunEndpoint> = {
pumpfun_quote: {
path: 'pump-fun/quote',
method: 'GET'
},
pumpfun_swap: {
path: 'pump-fun/swap',
method: 'POST'
},
pumpfun_swap_instructions: {
path: 'pump-fun/swap-instructions',
method: 'POST'
}
};
In addition to the types we defined in the previous step, we've imported the necessary Solana Web3.js 2.0 dependencies and defined the MetisPumpFunApi
type, which represents the Pump.fun API methods we'll implement. This is a subset of the Metis API that we'll use for this guide. If this looks new or unfamiliar to you, we recommend checking out our Guides on Building custom APIs with Solana Web3.js 2.0. We are also creating a mapping of method names to API endpoints that we can use in our transport layer to make sure we handle each method correctly.
2. Implement API Configuration
Next, let's add a few helper functions to configure the PumpFun API and handle requests. Add the following code to your index.ts
:
function createPumpFunUrl(metisEndpoint: string, method: string): URL {
const baseUrl = metisEndpoint.replace(/\/$/, ''); // Remove trailing slash if present
const endpointPath = METHOD_TO_ENDPOINT[method].path;
return new URL(`${baseUrl}/${endpointPath}`);
}
function createPumpFunTransport(metisEndpoint: string): RpcTransport {
return async <TResponse>(...args: Parameters<RpcTransport>): Promise<TResponse> => {
const { method, params } = args[0].payload as { method: string; params: PumpFunRequest };
const url = createPumpFunUrl(metisEndpoint, method);
const normalizedParams = Array.isArray(params) ? params[0] : params;
switch (METHOD_TO_ENDPOINT[method].method) {
case 'GET':
return handlePumpFunGET<PumpFunRequest, TResponse>(url, normalizedParams);
case 'POST':
return handlePumpFunPOST<PumpFunRequest, TResponse>(url, normalizedParams);
default:
throw new Error(`Unknown HTTP method for PumpFun method: ${method}`);
}
};
}
function createPumpFunApi(metisEndpoint: string): Rpc<MetisPumpFunApi> {
const api = createJsonRpcApi<MetisPumpFunApi>();
const transport = createPumpFunTransport(metisEndpoint);
return createRpc({ api, transport });
}
Here's a breakdown of what each function does:
createPumpFunUrl
: Creates a URL object for the Pump.fun API endpoint based on the Metis endpoint and method name (and removes any trailing slashes).createPumpFunTransport
: Creates a custom RPC transport function that validates our endpoint/method, normalizes our params, and routes requests to the appropriate handler based on the HTTP method (GET/POST) - we will define these handlers next.createPumpFunApi
: Creates an RPC instance of the PumpFun API with the custom transport function.
3. Implement Request Handlers
Now, let's implement the core request handling functionality. The GET methods will utilize url search parameters to pass the request parameters, while the POST methods will send the parameters in the request body. Add the following code to your index.ts
:
async function handlePumpFunGET<TParams, TResponse>(
url: URL,
params: TParams
): Promise<TResponse> {
if (typeof params === 'object' && params !== null) {
Object.entries(params as Record<string, unknown>).forEach(([key, value]) => {
url.searchParams.append(key, String(value));
});
}
const response = await fetch(url.toString(), {
method: 'GET',
redirect: 'follow',
headers: {
'Content-Type': 'application/json',
}
});
if (!response.ok) {
throw new Error(`Error making GET request to ${url}: ${response.statusText}`);
}
return await response.json() as TResponse;
}
async function handlePumpFunPOST<TParams, TResponse>(
url: URL,
params: TParams
): Promise<TResponse> {
try {
const response = await fetch(url.toString(), {
method: 'POST',
redirect: 'follow',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
if (!response.ok) {
throw new Error(`Error making POST request to ${url}: ${response.statusText}`);
}
return await response.json() as TResponse;
} catch (error) {
console.error('Error making POST request:', error);
throw error;
}
}
Here's what each function does:
handlePumpFunGET
: Appends the request parameters to the URL search params and sends a GET request to the Pump.fun API endpoint. It returns the JSON response.handlePumpFunPOST
: Sends a POST request to the Pump.fun API endpoint with the request parameters in the body. It returns the JSON response.
4. Implement Transaction Handling
Let's add the transaction signing and sending functionality:
async function signAndSendTransaction({
transactionBase64,
signerSecretKey,
solanaRpc,
solanaRpcSubscriptions,
commitment = 'confirmed'
}: SignAndSendTransactionParams): Promise<string> {
// Create signer keypair from secret
const signerKeypair = await createKeyPairFromBytes(
new Uint8Array(signerSecretKey)
);
// Decode the base64 transaction
const transactionBytes = getBase64Encoder().encode(transactionBase64) as TransactionMessageBytes;
const transactionDecoder = getTransactionDecoder();
const decodedTransaction = transactionDecoder.decode(transactionBytes);
// Sign the transaction
const signedTransaction = await signTransaction(
[signerKeypair],
decodedTransaction
);
// Get latest blockhash and prepare transaction with lifetime
const { value: { lastValidBlockHeight, blockhash } } = await solanaRpc.getLatestBlockhash().send();
const signedTransactionWithLifetime: FullySignedTransaction & TransactionWithBlockhashLifetime = {
...signedTransaction,
lifetimeConstraint: {
blockhash,
lastValidBlockHeight,
},
};
// Get transaction signature
const transactionSignature = getSignatureFromTransaction(signedTransactionWithLifetime);
// Create sendAndConfirm function
const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({
rpc: solanaRpc,
rpcSubscriptions: solanaRpcSubscriptions,
});
// Send and confirm transaction
await sendAndConfirmTransaction(signedTransactionWithLifetime, {
commitment,
});
return transactionSignature;
}
This function will make executing our base64-encoded transactions a breeze. The signAndSendTransaction
function handles the complete transaction lifecycle:
- Creates a keypair from the provided secret key
- Decodes the base64-encoded transaction
- Signs the transaction
- Fetches the latest blockhash
- Adds lifetime constraints to the transaction to ensure it is "fresh"
- Uses Web3.js 2.0's
sendAndConfirmTransaction
factory to send and confirm the transaction - Returns the transaction signature for tracking
5. ENV validation
Finally, let's write a simple environment variable validation function to ensure all required variables are set:
function validateEnv() {
const envVars = ['WALLET_SECRET_KEY','METIS_URL','HTTP_ENDPOINT','WSS_ENDPOINT'];
envVars.forEach((envVar) => {
if (!process.env[envVar]) {
throw new Error(`${envVar} environment variable is required`);
}
});
}
We are simply checking each expected environment variable and throwing an error if it is not set. Reminder: never expose your QuickNode API key in client-side code. Always keep it secure on your backend. Check our Guide: How to Protect Your Endpoint - Front End Best Practices for more information on Endpoint Security.
Test Script
Fetching a Quote
Let's create a new function, main
, that will test our API. Add the following code to your index.ts
:
async function main() {
validateEnv();
const metisUrl = process.env.METIS_URL as string;
const rpcUrl = process.env.HTTP_ENDPOINT as string;
const rpcSubscriptionsUrl = process.env.WSS_ENDPOINT as string;
const signerSecretKey = JSON.parse(process.env.WALLET_SECRET_KEY as string) as number[];
const signerKeypair = await createKeyPairFromBytes(new Uint8Array(signerSecretKey));
const wallet = await getAddressFromPublicKey(signerKeypair.publicKey);
const targetMint = address("8gXN67Nmw9FZQjunJZzRoi2Qf1ykZtN9Q3BqxhCypump");
const pumpFunApi = createPumpFunApi(metisUrl);
const solanaRpc: Rpc<SolanaRpcApi> = createSolanaRpc(rpcUrl);
const solanaRpcSubscriptions: RpcSubscriptions<SolanaRpcSubscriptionsApi>
= createSolanaRpcSubscriptions(rpcSubscriptionsUrl);
try {
const pumpFunQuote = await pumpFunApi.pumpfun_quote({
type: 'BUY',
mint: targetMint,
amount: 1_000_000,
}).send();
console.log(`PumpFun Quote:\n ${JSON.stringify(pumpFunQuote.quote, null, 2)}`);
} catch (error) {
console.error('Error getting PumpFun quote:', error);
}
}
main().catch(console.error);
Here, we are declaring our environment variables, creating the PumpFun API instance, and fetching a quote from the PumpFun API. We are using the pumpfun_quote
method to get a quote for buying 0.001 SOL of the specified token. You can adjust the parameters to meet your needs.
In your terminal, run the following command to start the script:
npm start
If everything is set up correctly, you should see the quote response logged to the console like this:
PumpFun Quote:
{
"mint": "8gXN67Nmw9FZQjunJZzRoi2Qf1ykZtN9Q3BqxhCypump",
"bondingCurve": "6TWadAgufwkQqZfKm1fqLgSQBUGKoMup7x5uC8fHUtnZ",
"type": "BUY",
"inAmount": "1000000",
"inAmountUi": 0.001,
"inTokenAddress": "So11111111111111111111111111111111111111112",
"outAmount": "33339932484",
"outAmountUi": 33339.932484,
"outTokenAddress": "8gXN67Nmw9FZQjunJZzRoi2Qf1ykZtN9Q3BqxhCypump",
"meta": {
"isCompleted": false,
"outDecimals": 6,
"inDecimals": 9,
"totalSupply": "1000000000000000",
"currentMarketCapInSol": 29.993096666
}
}
Great job!
Executing a Swap
And if you are ready to start trading, you can build a transaction and send it to the network by adding the following code to your main
function`:
try {
const pumpFunQuote = await pumpFunApi.pumpfun_swap({
wallet,
type: 'BUY',
mint: targetMint,
inAmount: 1_000_000,
priorityFeeLevel: 'high', // optionally set priority fee level
}).send();
const sig = await signAndSendTransaction({
transactionBase64: pumpFunQuote.tx,
signerSecretKey: JSON.parse(process.env.WALLET_SECRET_KEY as string) as number[],
solanaRpc,
solanaRpcSubscriptions,
});
console.log(`Transaction Signature: ${sig}`);
} catch (error) {
console.error('Error getting PumpFun quote:', error);
}
Warning: This code will execute a real transaction on the Solana network and result in an irreversible transfer of funds. Make sure you understand the implications before running it.
Conclusion
You now have a robust implementation for interacting with the Metis Pump.fun API using Solana Web3.js 2.0. This implementation provides a type-safe, error-resistant way to fetch quotes and execute swaps on the Pump.fun platform. If you want to keep building, check out our Guide: How to Build a Jupiter Trading Bot on Solana for more ideas on how to take this project to the next level.
Remember to always test thoroughly in a development environment before deploying to production, and ensure proper security measures are in place for handling private keys and QuickNode credentials.
To learn more about Solana development, check out our other guides on QuickNode Guides. For support or questions, join our Discord community or reach out on Twitter.
We ❤️ Feedback!
Let us know if you have any feedback or requests for new topics. We'd love to hear from you.