Skip to content

Instantly share code, notes, and snippets.

@rubpy
Created June 22, 2024 19:35
Show Gist options
  • Save rubpy/171fdd565569191b4fc7ba9555e4564f to your computer and use it in GitHub Desktop.
Save rubpy/171fdd565569191b4fc7ba9555e4564f to your computer and use it in GitHub Desktop.
Tracking changes in Pump.fun global state (initialVirtualTokenReserves, initialVirtualSolReserves, initialRealTokenReserves, tokenTotalSupply, feeBasisPoints).
import * as web3 from "@solana/web3.js";
import bs58 from "bs58";
//////////////////////////////////////////////////
function readBytes(buf: Buffer, offset: number, length: number): Buffer {
const end = offset + length;
if (buf.byteLength < end) throw new RangeError("range out of bounds");
return buf.subarray(offset, end);
}
function readBigUintLE(buf: Buffer, offset: number, length: number): bigint {
switch (length) {
case 1: return BigInt(buf.readUint8(offset));
case 2: return BigInt(buf.readUint16LE(offset));
case 4: return BigInt(buf.readUint32LE(offset));
case 8: return buf.readBigUint64LE(offset);
}
throw new Error(`unsupported data size (${length} bytes)`);
}
function readBoolean(buf: Buffer, offset: number, length: number): boolean {
const data = readBytes(buf, offset, length);
for (const b of data) {
if (b) return true;
}
return false;
}
function readPublicKey(buf: Buffer, offset: number): web3.PublicKey {
return new web3.PublicKey(readBytes(buf, offset, web3.PUBLIC_KEY_LENGTH));
}
//////////////////////////////////////////////////
const PUMP_PROGRAM_ID = new web3.PublicKey("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
interface PumpGlobalState {
initialized: boolean
authority: web3.PublicKey
feeRecipient: web3.PublicKey
initialVirtualTokenReserves: bigint
initialVirtualSolReserves: bigint
initialRealTokenReserves: bigint
tokenTotalSupply: bigint
feeBasisPoints: bigint
}
// Calculated as the first 8 bytes of: `sha256("account:Global")`.
const PUMP_GLOBAL_STATE_SIGNATURE = Uint8Array.from([0xa7, 0xe8, 0xe8, 0xb1, 0xc8, 0x6c, 0x72, 0x7f]);
const PUMP_GLOBAL_STATE_SIZE = 0x69;
const PUMP_GLOBAL_STATE_OFFSETS = {
INITIALIZED: 0x08,
AUTHORITY: 0x09,
FEE_RECIPIENT: 0x29,
INITIAL_VIRTUAL_TOKEN_RESERVES: 0x49,
INITIAL_VIRTUAL_SOL_RESERVES: 0x51,
INITIAL_REAL_TOKEN_RESERVES: 0x59,
TOKEN_TOTAL_SUPPLY: 0x61,
FEE_BASIS_POINTS: 0x69,
};
function decodePumpGlobalState(data: Buffer): PumpGlobalState {
const idlSignature = readBytes(data, 0, PUMP_GLOBAL_STATE_SIGNATURE.byteLength);
if (idlSignature.compare(PUMP_GLOBAL_STATE_SIGNATURE) !== 0) {
throw new Error("unexpected global state IDL signature");
}
return {
initialized: readBoolean(data, PUMP_GLOBAL_STATE_OFFSETS.INITIALIZED, 1),
authority: readPublicKey(data, PUMP_GLOBAL_STATE_OFFSETS.AUTHORITY),
feeRecipient: readPublicKey(data, PUMP_GLOBAL_STATE_OFFSETS.FEE_RECIPIENT),
initialVirtualTokenReserves: readBigUintLE(data, PUMP_GLOBAL_STATE_OFFSETS.INITIAL_VIRTUAL_TOKEN_RESERVES, 8),
initialVirtualSolReserves: readBigUintLE(data, PUMP_GLOBAL_STATE_OFFSETS.INITIAL_VIRTUAL_SOL_RESERVES, 8),
initialRealTokenReserves: readBigUintLE(data, PUMP_GLOBAL_STATE_OFFSETS.INITIAL_REAL_TOKEN_RESERVES, 8),
tokenTotalSupply: readBigUintLE(data, PUMP_GLOBAL_STATE_OFFSETS.TOKEN_TOTAL_SUPPLY, 8),
feeBasisPoints: readBigUintLE(data, PUMP_GLOBAL_STATE_OFFSETS.FEE_BASIS_POINTS, 8),
};
}
//////////////////////////////////////////////////
(async (rpcUrl: string) => {
const conn = new web3.Connection(rpcUrl, "confirmed");
conn.onProgramAccountChange(PUMP_PROGRAM_ID, (info: web3.KeyedAccountInfo, ctx: web3.Context) => {
let state: PumpGlobalState | null = null;
try {
state = decodePumpGlobalState(info.accountInfo.data);
} catch (e) {}
if (!state) return null;
console.log(ctx.slot, "|", info.accountId.toBase58());
console.dir(state);
console.log();
}, undefined, [
{ dataSize: PUMP_GLOBAL_STATE_SIGNATURE.byteLength + PUMP_GLOBAL_STATE_SIZE },
{ memcmp: { offset: 0, bytes: bs58.encode(PUMP_GLOBAL_STATE_SIGNATURE) } },
]);
//
// --- OUTPUT ---
//
// 273403135 | 4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf
// {
// initialized: true,
// authority: PublicKey [PublicKey(DCpJReAfonSrgohiQbTmKKbjbqVofspFRHz9yQikzooP)] {
// _bn: BN { negative: 0, words: [Array], length: 10, red: null }
// },
// feeRecipient: PublicKey [PublicKey(CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM)] {
// _bn: BN { negative: 0, words: [Array], length: 10, red: null }
// },
// initialVirtualTokenReserves: 1073000000000000n,
// initialVirtualSolReserves: 30000000000n,
// initialRealTokenReserves: 793100000000000n,
// tokenTotalSupply: 1000000000000000n,
// feeBasisPoints: 100n
// }
//
// --------------
//
})(process.env.SOL_RPC_URL || "https://mainnet.helius-rpc.com/?api-key=00000000-0000-0000-0000-000000000000");
@nshen
Copy link

nshen commented Mar 17, 2025

dataSize should be 512, not 113

    const accounts = await conn.getProgramAccounts(PUMP_PROGRAM_ID,
      {
        filters: [
          { dataSize: 512 },
          { memcmp: { offset: 0, bytes: bs58.encode(PUMP_GLOBAL_STATE_SIGNATURE) } },
        ],
        dataSlice: { offset: PUMP_GLOBAL_STATE_SIGNATURE.byteLength, length: PUMP_GLOBAL_STATE_SIZE }
      }
    );


    if (accounts.length === 0) {
      console.log("can't find global state account");
      return;
    }

@rubpy
Copy link
Author

rubpy commented Mar 17, 2025

@nshen 113 used to be the correct dataSize, but as it stands, you're right (dataSize is now 512).

Recently, they've been making some changes to the Pump.fun program.
For the sake of completeness, here's the updated structure:

[{
  "name": "Global",
  "discriminator": [167, 232, 232, 177, 200, 108, 114, 127],
  // └──► Buffer.from("a7e8e8b1c86c727f", "hex")

  "type": {
    "kind": "struct",
    "fields": [
      { "name": "initialized",                    "type": "bool"                   },
      //         └──► true
      { "name": "authority",                      "type": "pubkey"                 },
      //         └──► "FFWtrEQ4B4PKQoVuHYzZq8FabGkVatYzDpEVHsK5rrhF"
      { "name": "fee_recipient",                  "type": "pubkey"                 },
      //         └──► "62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV"
      { "name": "initial_virtual_token_reserves", "type": "u64"                    },
      //         └──► 1073000000000000
      { "name": "initial_virtual_sol_reserves",   "type": "u64"                    },
      //         └──► 30000000000
      { "name": "initial_real_token_reserves",    "type": "u64"                    },
      //         └──► 793100000000000
      { "name": "token_total_supply",             "type": "u64"                    },
      //         └──► 1000000000000000
      { "name": "fee_basis_points",               "type": "u64"                    },
      //         └──► 100

      /* ┌────────────── ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ──────────────┐ */
      { "name": "withdraw_authority",             "type": "pubkey"                 },
      //         └──► "39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg"
      { "name": "enable_migrate",                 "type": "bool"                   },
      //         └──► false
      { "name": "pool_migration_fee",             "type": "u64"                    },
      //         └──► 15000001
      { "name": "creator_fee",                    "type": "u64"                    },
      //         └──► 0
      { "name": "fee_recipients",                 "type": {"array": ["pubkey", 7]} },
      //         └──► [
      //           "7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ",
      //           "7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX",
      //           "9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz",
      //           "AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY",
      //           "CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM",
      //           "FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz",
      //           "G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP",
      //         ]

      // { "name": "__padding1", "type": {"array": ["u8", 126]} }
      /* └────────────── ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ➕ ──────────────┘ */
    ]
  }
}]

@nshen
Copy link

nshen commented Mar 18, 2025

so cool @rubpy would you mind telling me how you know that?
I randomly tried lots of numbers yesterday.

{
  initialized: true,
  authority: PublicKey [PublicKey(FFWtrEQ4B4PKQoVuHYzZq8FabGkVatYzDpEVHsK5rrhF)] {
    _bn: BN { negative: 0, words: [Array], length: 10, red: null }
  },
  feeRecipient: PublicKey [PublicKey(62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV)] {
    _bn: BN { negative: 0, words: [Array], length: 10, red: null }
  },
  initialVirtualTokenReserves: 1073000000000000n,
  initialVirtualSolReserves: 30000000000n,
  initialRealTokenReserves: 793100000000000n,
  tokenTotalSupply: 1000000000000000n,
  feeBasisPoints: 100n,
  withdrawAuthority: PublicKey [PublicKey(39azUYFWPz3VHgKCf3VChUwbpURdCHRxjWVowf5jUJjg)] {
    _bn: BN { negative: 0, words: [Array], length: 10, red: null }
  },
  enableMigrate: false,
  poolMigrationFee: 15000001n,
  creatorFee: 0n,
  feeRecipients: [
    PublicKey [PublicKey(7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ)] {
      _bn: [BN]
    },
    PublicKey [PublicKey(7hTckgnGnLQR6sdH7YkqFTAA7VwTfYFaZ6EhEsU3saCX)] {
      _bn: [BN]
    },
    PublicKey [PublicKey(9rPYyANsfQZw3DnDmKE3YCQF5E8oD89UXoHn9JFEhJUz)] {
      _bn: [BN]
    },
    PublicKey [PublicKey(AVmoTthdrX6tKt4nDjco2D775W2YK3sDhxPcMmzUAmTY)] {
      _bn: [BN]
    },
    PublicKey [PublicKey(CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM)] {
      _bn: [BN]
    },
    PublicKey [PublicKey(FWsW1xNtWscwNmKv6wVsU1iTzRN6wmmk3MjxRP5tT7hz)] {
      _bn: [BN]
    },
    PublicKey [PublicKey(G5UZAVbAf46s7cKWoyKu8kYTip9DGTpbLZ2qa9Aq69dP)] {
      _bn: [BN]
    }
  ]
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment