How to stream Bonding Curve Transactions on Pumpfun

Learn how to stream Pump.fun bonding curve transactions on Solana using
Shyft's Yellowstone gRPC. Detect buy/sell activity, pool values, and
bonding curve completion before migration to PumpSwap. Free API key included.

A step-by-step guide to streaming Pump.fun bonding curve transactions using Shyft’s Yellowstone gRPC — track buy/sell activity, pool values, and detect bonding curve completion before migration to PumpSwap

Pumpfun bonding curve cover

Pump.fun uses a bonding curve model to price tokens dynamically — as buying pressure increases, so does the price. When a token’s market cap reaches $69,000 or its pool hits 86 SOL, the bonding curve completes and the token migrates to PumpSwap AMM for open trading.

For trading bots, analytics tools, and sniper scripts, streaming bonding curve transactions in real time is essential — it tells you exactly where a token is in its lifecycle before anyone else knows.

In this guide, you’ll learn how to:

  • Fetch pool details using Shyft’s DeFi API
  • Subscribe to Pump.fun bonding curve accounts using Shyft’s Yellowstone gRPC (Program ID: 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P)
  • Decode bonding curve account data including virtualSolReserves and complete status
  • Detect bonding curve completion using memcmp filtering

What You Need Before Getting Started

To get started, you’ll need a few essentials:

Authentication: Shyft API Key, gRPC Endpoint, and gRPC Token

  • Shyft API Key: All Shyft services require an API Key for authentication. You can obtain your free Shyft API Key by signing up with your email on the Shyft Website.
  • gRPC Endpoint and Token: Shyft’s Yellowstone gRPC nodes are available globally. To access them, you need a region-specific gRPC endpoint and an access token. You can find the endpoint and your access token on your Shyft Dashboard.

Server-Side Backend (e.g., Node.js) to Receive gRPC Data

For this example, we use Node.js, but other server-side languages such as C#, Go, Java, Kotlin, Python, or PHP can also be used.

Introduction — Topics to be covered

In this article, we will look at streaming Bonding Curve trading transactions on Pumpfun using gRPC. We will be covering the following topics,

  • Cloning the git repository
  • Installing dependencies
  • Setting up a gRPC connection to the Solana network
  • Retrieve Bonding Curve Address and Pool Value
  • Determine Bonding Curve Completion
  • Conclusion

For comprehensive guidance on streaming real-time updates from Pumpfun on Solana, please consult our article: How to Stream Real-Time Pumpfun Updates on Solana.

Step 1: Clone the Repository and Install Dependencies

To get started, you will need to clone the Git repository containing the code. Follow these steps:

  1. Open your terminal.
  2. Navigate to the directory where you want to clone the repository.
  3. Execute the command git clone followed by the repository URL. For example:
$ git clone https://github.com/Shyft-to/solana-defi.git

Installing Dependencies

In your terminal, navigate to the folder containing the code by running cd grpc-moonshot, then install all dependencies by executing npm install.

cd grpc-pump-curveBonding
npm install

All the code related to this article is available on our GitHub here. You are welcome to clone it and follow along.

Step 2: Connect to Shyft’s Yellowstone gRPC

To begin streaming data from Pumpfun using gRPC, you need to establish the client service. This involves two essential pieces of information: the gRPC URL and the gRPC Access Token. The access token is a security credential, usually a long alphanumeric string, while the URL corresponds to your region-specific gRPC endpoint. With these details, you can configure the client service and start streaming data from Pumpfun.

const client = new Client(
       'Your Region specific Shyft gRPC URL',
       'Shyft gRPC Access Token',undefined
);

Now we can proceed to obtain our data from the blockchain.

Step 3: Subscribe to Pump.fun Bonding Curve Accounts

To stream data from Pumpfun, it’s not enough to establish a connection. We would need to fetch the bonding curve transaction details, which include the Contract Address of the Bonding Curve, and the Pool token details, we use the wallet token API to fetch the necessary data. To retrieve data, we would be making use of memcmp to filter our pumpfun transaction. Our code structures will look like this:

const req: SubscribeRequest = {
  "slots": {},
  "accounts": {
    "raydium": {
      "account": [],
      "filters": [],
      "owner": [pumpfun] 
    }
  },
  "transactions": {},
  "blocks": {},
  "blocksMeta": {
    "block": []
  },
  "accountsDataSlice": [],
  "commitment": CommitmentLevel.PROCESSED, // Subscribe to processed blocks for the fastest updates
  entry: {},
  transactionsStatus: {}
}

Our raw transaction should be presented in a comprehensive and detailed format to ensure clarity and accessibility

{
  filters: [ 'pumpfun' ],
  account: {
    account: {
      pubkey: <Buffer 94 3c 0a 59 44 07 80 77 53 51 c6 e0 fd 26 20 83 ef ad cb d5 d2 fc 5c 22 84 9d 19 8b 0b 1e e7 a6>,
      lamports: '1232028',
      owner: <Buffer 01 56 e0 f6 93 66 5a cf 44 db 15 68 bf 17 5b aa 51 89 cb 97 f5 d2 ff 3b 65 5d 2b b6 fd 6d 18 b0>,
      executable: false,
      rentEpoch: '18446744073709551615',
      data: <Buffer 17 b7 f8 37 60 d8 ac 60 a4 86 a2 47 e3 cf 03 00 6c ac 23 fc 06 00 00 00 a4 ee 8f fb 51 d1 02 00 6c 00 00 00 00 00 00 00 00 80 c6 a4 7e 8d 03 00 00>,
      writeVersion: '1455967774790',
      txnSignature: undefined
    },
    slot: '292282145',
    isStartup: false
  },
  slot: undefined,
  transaction: undefined,
  block: undefined,
  ping: undefined,
  pong: undefined,
  blockMeta: undefined,
  entry: undefined
}

To retrieve our pool details, we will utilize Shyft’s DeFi API. This process involves making a request to the API endpoint designed for fetching pool information.

var myHeaders = new Headers();
export const api = "api"
myHeaders.append("x-api-key", api);

var requestOptions:any = {
  method: 'GET',
  headers: myHeaders,
  redirect: 'follow'
};
export async function getTokenBalance(address){

 const info = await fetch(`https://api.shyft.to/sol/v1/wallet/all_tokens?network=mainnet-beta&wallet=${address}`, requestOptions)
 const infoJson = await info.json();
 const result = infoJson?.result[0];
 const ca = result?.address
 const name = result?.info?.name
 const symbol = result?.info.symbol
const balance = result?.balance;
 return {
  name,
  symbol,
  ca,
  balance
 };
}

This is how our result should be presented:

{
  name: 'Key Opinion Loser',
  symbol: 'KOL',
  ca: 'KoL17ZJJ8ccwM2zxvpvDuVD9YpMW4KumAsaRFB9JCKe',
  balance: 0
}

Step 4: Decode Bonding Curve Account Data

Our output is currently in a Buffer format, which is not very user-friendly. To make this data more accessible and easier to understand, we need to decode and trim it into a more readable text format.

import base58 from "bs58";
import { struct, bool, u64, Layout } from "@coral-xyz/borsh";

export const structure = struct([
    u64("discriminator"),
    u64("virtualTokenReserves"),
    u64("virtualSolReserves"),
    u64("realTokenReserves"),
    u64("realSolReserves"),
    u64("tokenTotalSupply"),
    bool("complete"),
  ]);


export function decodeTransact(data){
    const output = base58.encode(Buffer.from(data,'base64'))
    return output;
}
export function  bondingCurveData(buffer: Buffer) {

    let value = structure.decode(buffer);
    const discriminator = BigInt(value.discriminator);
    const virtualTokenReserves = BigInt(value.virtualTokenReserves);
    const virtualSolReserves = BigInt(value.virtualSolReserves);
    const realTokenReserves = BigInt(value.realTokenReserves);
    const realSolReserves = BigInt(value.realSolReserves);
    const tokenTotalSupply = BigInt(value.tokenTotalSupply);
    const complete = value.complete;
    return {
        discriminator,
        virtualTokenReserves,
        virtualSolReserves,
        realTokenReserves,
        realSolReserves,
        tokenTotalSupply,
        complete
    };
}

We decode the pumpfun account data by defining its struct

import { LIQUIDITY_STATE_LAYOUT_V4 } from "@raydium-io/raydium-sdk";
import { bondingCurveData, decodeTransact } from "./decodeTransaction";

/**
 * Deserializes and trims blockchain data to extract relevant information.
 * @param {Object} data - The data object containing blockchain account information.
 * @returns {Object} - An object containing the deserialized signature, public key, owner, and pool state.
 */
export function tOutPut(data) {
    // Ensure data is defined and contains the necessary properties
    if (!data || !data.account || !data.account.account) {
        throw new Error("Invalid data format");
    }

    const dataTx = data.account.account;

    // Safely decode each piece of transaction data
    const signature = dataTx.txnSignature ? decodeTransact(dataTx.txnSignature) : null;
    const pubKey = dataTx.pubkey ? decodeTransact(dataTx.pubkey) : null;
    const owner = dataTx.owner ? decodeTransact(dataTx.owner) : null;
    
    let poolstate = null;
    try {
        poolstate = bondingCurveData(dataTx.data);
    
    } catch (error) {
        console.error("Failed to decode pool state:", error);
    }

    return {
        signature,
        pubKey,
        owner,
        poolstate
    };
}

Here is the result we aim to achieve in our console output.

{
  signature: '3PfY1M74yoKerd38LV7ZYQejVBxiYGCW2BmhfEr2UGGt5eAWQGRkdFJUniqvr6yyZMVCsYxMTJQmymXT9ECEFR3J',
  pubKey: 'LjCVC9PKfX5WzWTbD6T1tHbHW4EDCuk3cdE53BTwXxa',
  owner: '6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P',
  poolstate: {
    discriminator: 6966180631402821399n,
    virtualTokenReserves: 925163590557167n,
    virtualSolReserves: 34793847131n,
    realTokenReserves: 645263590557167n,
    realSolReserves: 4793847131n,
    tokenTotalSupply: 1000000000000000n,
    complete: false
  }
}

Step 5: Determine the Bonding Curve Completion with memcmp

Our code becomes much simpler as we leverage memcmp to retrieve the Bonding Curve completion on Pumpfun. This approach allows us to streamline our process significantly. By focusing solely on filtering our transactions, we can efficiently isolate the relevant data needed for our analysis. This optimization not only reduces complexity but also improves performance, making it easier to work with the specific transaction details we require.

const req: SubscribeRequest = {
  "slots": {},
  "accounts": {
    "raydium": {
      "account": [],
      "filters": [
        {
          "memcmp": {
            "offset": structure.offsetOf('complete').toString(),
            "bytes" : Uint8Array.from([1])
          }
        }
      ],
      "owner": [pumpfun] 
    }
  },
  "transactions": {},
  "blocks": {},
  "blocksMeta": {
    "block": []
  },
  "accountsDataSlice": [],
  "commitment": CommitmentLevel.PROCESSED, // Subscribe to processed blocks for the fastest updates
  entry: {},
  transactionsStatus: {}
}

We should anticipate receiving the following output from the API request.

{ 
  signature: null,
  pubKey: '3ZEsfmh28PAfxyqJjHppcqzkEQbhKgtxCPg6WQG21ZKg',
  owner: '6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P', 
  poolstate: { 
    discriminator: 6966180631402821399n,
    virtualTokenReserves: 0n,
    virtualSolReserves: 0n,
    realTokenReserves: 0n,
    realSolReserves: 0n,
    tokenTotalSupply: 1000000000000000n,
    complete: true
 }
}

To proceed, we should enhance our stream.on function to include the functionality for retrieving the pool details.

stream.on("data", async (data) => {
    try{
    const result = await tOutPut(data);
    const tokenInfo = await getTokenBalance(result.pubKey);
    console.log(
      `
      CA : ${tokenInfo.ca}
      Name : ${tokenInfo.name} (${tokenInfo.symbol})
      POOL DETAILS : 0 ${tokenInfo.symbol}
                     0 SOL
      BONDING CURVE STATUS : COMPLETE                    
      `
    )
}catch(error){
  if(error){
    console.log(error)
  }
}
});

We want to ensure that our console output is formatted in a clear and informative manner.

      CA : 7FhLiAXYaJX1HbpJPqASucasxdRYRUbvR25QVXfxua3G
      Name : VibeAI (VAI)
      POOL DETAILS : 0 VAI
                     0 SOL
      BONDING CURVE STATUS : COMPLETED


      CA : 4iBqSNn9i3PqNv1shXVHjEWkD7MvoaZmcCFpTJSwNqep
      Name : Its Over Cat (OVER)
      POOL DETAILS : 0 OVER
                     0 SOL
      BONDING CURVE STATUS : COMPLETED

Pro Tip: Add reconnect logic to your Yellowstone gRPC project and never miss a completion event.

Conclusion

We’re excited to unveil new solution for streaming data on the Solana blockchain: gRPC. This technology streamlines the process of accessing on-chain data, enabling developers to concentrate on creating powerful applications that leverage the Solana blockchain. gRPC simplifies the code base and reduces latency, making data transmission faster and more efficient. Traditionally, streaming data on Solana with dedicated nodes has been complex and resource-intensive, often requiring a more elaborate code base. However, gRPC simplifies this process, allowing developers to build even the most sophisticated projects with ease.

For the absolute earliest detection of bonding curve activity — even before transaction confirmation — RabbitStream streams directly from Solana shreds with 15–40ms advantage over standard gRPC.

Key benefits of using gRPC for Solana blockchain development include:

  • A streamlined code base for easier data streaming
  • Reduced latency for quicker data transmission
  • Simplified development of complex projects

For additional support, join our Discord Server or follow us on Twitter for the latest updates. Our team is here to assist you in leveraging this innovative technology.

You can find all the code related to this article on our GitHub repository. Feel free to clone it and follow along.

Resources