Pump now has three v2 trade instructions, two quote assets, and a separate migration step into PumpSwap. A decoder that only recognizes legacy buy and sell can keep receiving healthy transactions while silently missing the newer path.

The right monitor tracks a lifecycle, not a keyword:

pump-lifecycle.txt
v2 trade observed
    -> transaction executed successfully
    -> bonding curve reaches complete state
    -> migrate executes successfully
    -> PumpSwap pool and migration event are recorded

Each arrow has a different proof requirement. Collapsing them into one "graduated" flag creates duplicate alerts and false pool records.

Start from Pump's public contracts

The Pump program runs at:

pump-program-id.txt
6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P

Pump's public repository documents the unified v2 trade interface:

  • buy_v2
  • sell_v2
  • buy_exact_quote_in_v2

All accounts are mandatory and use one order across the supported quote paths. The interface adds quote_mint, quote-side token accounts, sharing configuration, and volume accumulators. For an existing SOL-paired coin, the caller passes wrapped SOL as quote_mint, while the trade still uses native SOL.

Pump's fee documentation says token creators could select USDC as the paired token from May 21, 2026. That makes quote-mint decoding part of the business contract, not an optional display field.

The instruction name identifies the operation. The quote mint identifies the market.

Filter by program, then decode every instruction layer

A Yellowstone transaction filter can remove unrelated traffic at the server:

pump-subscription.ts
import { CommitmentLevel, type SubscribeRequest } from "@triton-one/yellowstone-grpc";
 
const PUMP_PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
 
export const request: SubscribeRequest = {
  accounts: {},
  slots: {},
  transactions: {
    pump: {
      vote: false,
      failed: false,
      signature: undefined,
      accountInclude: [PUMP_PROGRAM_ID],
      accountExclude: [],
      accountRequired: [],
    },
  },
  transactionsStatus: {},
  blocks: {},
  blocksMeta: {},
  entry: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
  ping: undefined,
};

accountInclude means the transaction mentions the program account. It does not tell you which Pump instruction ran. It also does not replace transaction-status validation.

The decoder must inspect:

  1. Static message account keys.
  2. Addresses loaded through lookup tables.
  3. Outer compiled instructions.
  4. Inner instructions emitted by CPI.
  5. Transaction metadata, including the execution error.

Missing inner instructions is a common source of partial histories. A router can invoke another program, which then invokes Pump. If the consumer reads only the top-level message, the trade never reaches its decoder.

Match the eight-byte discriminator before parsing arguments

Anchor instructions begin with an eight-byte discriminator. The May 18 public IDL defines these values:

InstructionDiscriminator bytes
buy_v2184, 23, 238, 97, 103, 197, 211, 61
sell_v293, 246, 130, 60, 231, 233, 64, 178
buy_exact_quote_in_v2194, 171, 28, 70, 104, 77, 91, 47
migrate155, 234, 231, 146, 236, 158, 162, 30

Keep classification small and testable:

classify-pump-instruction.ts
const PUMP_PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
 
const PUMP_IX = new Map<string, string>([
  [hex([184, 23, 238, 97, 103, 197, 211, 61]), "buy_v2"],
  [hex([93, 246, 130, 60, 231, 233, 64, 178]), "sell_v2"],
  [hex([194, 171, 28, 70, 104, 77, 91, 47]), "buy_exact_quote_in_v2"],
  [hex([155, 234, 231, 146, 236, 158, 162, 30]), "migrate"],
]);
 
function hex(bytes: number[] | Uint8Array): string {
  return Buffer.from(bytes).toString("hex");
}
 
export function classifyPumpInstruction(
  programId: string,
  data: Uint8Array,
): string | null {
  if (programId !== PUMP_PROGRAM_ID || data.byteLength < 8) return null;
  return PUMP_IX.get(hex(data.subarray(0, 8))) ?? null;
}

Classification should fail closed. An unknown discriminator is a version signal, not permission to guess an account layout from token balance changes.

After classification, decode arguments and accounts with the pinned IDL or Pump SDK. The current IDL still contains argument names such as max_sol_cost and min_sol_output, while the v2 account model supports another quote mint. Treat the decoded quote_mint and quote-side amounts as authoritative for asset identity.

Completion and migration are separate states

Pump's program documentation defines a completed curve as complete == true with real_token_reserves == 0. At that point, the permissionless migrate instruction can move liquidity into PumpSwap. The documentation describes migrate as idempotent, so another caller may complete it before your worker acts.

Model at least these states:

pump-asset-state.ts
type PumpAssetState =
  | { kind: "bonding_curve"; mint: string; quoteMint: string }
  | { kind: "migration_eligible"; mint: string; quoteMint: string; completedAtSlot: bigint }
  | { kind: "pumpswap"; mint: string; quoteMint: string; pool: string; migratedAtSlot: bigint };

Do not mark an asset as migrated because a transaction contains the migrate discriminator. Require all of the following:

  • The transaction finished without an execution error.
  • The decoded mint and bonding curve match the asset record.
  • The CompletePumpAmmMigrationEvent or verified PumpSwap pool identifies the resulting pool.
  • A replay of the same transaction updates the record without creating a second pool row.

The current public IDL gives CompletePumpAmmMigrationEvent fields for the user, mint, token and quote amounts, migration fee, bonding curve, timestamp, pool, and quote mint. Decode the official event decoder rather than hand-counting log offsets.

Persist a replay-safe event key

Yellowstone reconnect and replay can deliver an observation more than once. Processed data may also return before the transaction reaches a stronger commitment.

Persist one key per decoded instruction or event:

pump-event-key.ts
type EventLocation = {
  signature: string;
  outerInstructionIndex: number;
  innerInstructionIndex: number | null;
  eventIndex: number | null;
};
 
export function pumpEventKey(location: EventLocation): string {
  return [
    location.signature,
    location.outerInstructionIndex,
    location.innerInstructionIndex ?? "outer",
    location.eventIndex ?? "instruction",
  ].join(":");
}

Upsert by that key. Store the first observed slot and receive time, then update commitment and decoded fields as stronger evidence arrives. Notifications, orders, and analytics counters must use the same idempotency record before producing a side effect.

The recovery state machine in our Yellowstone reconnect and replay guide covers checkpoint overlap and reconciliation. Pump-specific decoding sits after that transport boundary.

Verify the monitor with captured transactions

Make the Pump Lifecycle Fixture Set the release gate. Capture and retain successful examples for:

  • One SOL-paired buy_v2 and one sell_v2.
  • One USDC-paired v2 trade.
  • One buy_exact_quote_in_v2.
  • One curve-completion transaction.
  • One successful PumpSwap migration.
  • One failed v2 trade and one unknown discriminator.
  • One replayed update for an already stored signature.

For each fixture, assert the program ID, discriminator, resolved account keys, quote mint, amounts, execution result, event key, and final lifecycle state. A decoder upgrade is safe only when old fixtures remain stable and new upstream fields are handled deliberately.

Do not build fixtures from explorer screenshots. Retain raw transaction responses or serialized Yellowstone updates with their software and IDL versions.

Keep the decoder narrow

This monitor can power activity feeds, wallet alerts, migration notifications, analytics, and bot inputs. It does not prove that a trade should be copied or that a newly migrated pool is safe.

Keep market policy outside the decoder. The decoder answers what executed, for which mint and quote asset, and where the asset sits in the Pump lifecycle. Risk controls decide whether the application does anything with that fact.

Start with the Pump Lifecycle Fixture Set and one filtered stream. To compare the workload against a second Yellowstone endpoint, review the RPCEdge plans.

Frequently asked questions

What changed in the Pump.fun v2 trade instructions?
Pump introduced buy_v2, sell_v2, and buy_exact_quote_in_v2 with one mandatory account order for supported quote assets. The current interface adds quote-mint accounts, sharing configuration, and volume-accumulator accounts while retaining support for older SOL-paired coins.
How do I filter Pump.fun transactions with Yellowstone gRPC?
Subscribe to successful non-vote transactions with the Pump program ID in accountInclude. Then resolve every account key and inspect both outer and inner instructions. Account inclusion narrows the stream but does not identify the instruction or prove the transaction succeeded.
How can I tell whether a Pump coin uses SOL or USDC?
Read the bonding curve quote_mint or decode the quote_mint account in the v2 instruction. Existing SOL-paired coins use the wrapped SOL mint address in the unified interface even though trading still transfers native SOL. Never infer the quote asset from old field names alone.
Does a completed bonding curve mean the PumpSwap migration finished?
No. A complete bonding curve is eligible for migration, but completion and migration are separate lifecycle events. Record the completed state first, then require a successful migrate transaction and the Pump AMM migration event or verified pool state.
Can a replayed Yellowstone update trigger the same bot action twice?
It can if the consumer lacks idempotency. Use the transaction signature plus instruction or log position as a durable event key. Replayed updates may refresh status, but they must not repeat notifications, orders, or database inserts.