# How to Parse Percolator On-Chain Solana Perpetual Futures Data in CloddsBot

> Learn how CloddsBot parses Percolator on-chain Solana perpetual futures data by decoding raw bytes from the slab account. Extract market config, risk parameters, and user positions efficiently.

- Repository: [AL/CloddsBot](https://github.com/alsk1992/CloddsBot)
- Tags: how-to-guide
- Published: 2026-09-13

---

**Percolator stores perpetual futures state in a single Solana account called the *slab*, which CloddsBot decodes by reading raw bytes and applying fixed-offset binary parsing to extract market configuration, risk parameters, and user positions.**

Percolator is a Solana-based perpetual futures protocol that compresses the entire market state—including risk engine data and all user/LP accounts—into one on-chain account. The CloddsBot repository provides a complete TypeScript parser that transforms this binary blob into structured JavaScript objects. Understanding this parsing pipeline is essential for developers building automated trading strategies that react to on-chain perp market data.

## Fetching the Raw Slab Account

The entry point for all Percolator data retrieval is the `fetchSlab` function in [`src/percolator/slab.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/percolator/slab.ts). This utility uses a Solana `Connection` to retrieve the account info and return a Node.js `Buffer` containing the raw bytes.

```typescript
// src/percolator/slab.ts
export async function fetchSlab(
  connection: Connection,
  slabPubkey: PublicKey,
): Promise<Buffer> {
  const info = await connection.getAccountInfo(slabPubkey);
  if (!info) {
    throw new Error(`Slab account not found: ${slabPubkey.toBase58()}`);
  }
  return Buffer.from(info.data);
}

```

The function validates the account exists before wrapping the data in a Buffer, ensuring downstream parsers receive consistent input.

## Decoding the Binary Layout Constants

Percolator’s slab uses a fixed-size binary layout that mirrors the Rust structs used by the on-chain program. The parser defines a series of offset constants that map these memory positions:

- **`MAGIC`**: Expected identifier `"PERCOLAT"` (hex `0x504552434f4c4154n`)
- **`HEADER_LEN`**: 72 bytes for the fixed header section
- **`ENGINE_OFF`**: 392 bytes—start of the risk-engine region
- **`ACCOUNT_SIZE`**: 240 bytes per user/LP account entry
- **`BITMAP_WORDS`**: Number of 64-bit words tracking used account slots (64 words × 64 bits = 4096 potential slots)

These constants enable zero-copy style parsing where the decoder jumps directly to specific byte offsets without scanning the entire buffer.

## Parsing 128-Bit Integers

Solana programs frequently use 128-bit signed and unsigned integers for token amounts and precise pricing. Since JavaScript `Number` types lose precision beyond 53 bits, the parser implements dedicated helpers:

```typescript
function readI128LE(buf: Buffer, offset: number): bigint { … }
function readU128LE(buf: Buffer, offset: number): bigint { … }

```

These functions read two 64-bit little-endian values and combine them into a single `bigint`, handling sign-extension for the signed variant. All financial data in Percolator—including vault balances, position sizes, and funding indices—flows through these helpers to maintain numerical accuracy.

## Extracting Market Configuration

### Validating the Header

The `parseHeader` function validates the magic string and extracts metadata including version, bump seed, flags, admin authority, and resolution status.

```typescript
export function parseHeader(data: Buffer): SlabHeader { … }

```

If the magic bytes do not match `PERCOLAT`, the parser throws immediately, preventing misinterpretation of unrelated account data.

### Reading Risk Parameters

`parseConfig` extracts the `MarketConfig` struct starting at `CONFIG_OFFSET`. This includes the collateral mint address, vault token account, index price feed, funding parameters, price thresholds, and oracle authority addresses.

```typescript
export function parseConfig(data: Buffer): MarketConfig { … }

```

### Decoding Engine Risk Settings

Located at `ENGINE_OFF + ENGINE_PARAMS_OFF`, the `RiskParams` struct contains margin requirements, trading fees, liquidation fees, and position limits. The `parseParams` function returns these as a typed object for position validation logic.

```typescript
export function parseParams(data: Buffer): RiskParams { … }

```

### Capturing Engine State

`parseEngine` builds an `EngineState` object containing real-time market data: vault balance, insurance fund reserves, current Solana slot, funding index, total open interest, and crank cursor positions used by the on-chain execution engine.

```typescript
export function parseEngine(data: Buffer): EngineState { … }

```

## Processing User and LP Accounts

### Finding Active Slots with Bitmap Parsing

Percolator supports up to 4096 concurrent user/LP accounts but uses a sparse storage model. The `parseUsedIndices` function walks a bitmap stored in the slab—64 words of 8 bytes each—to identify which account slots contain data.

```typescript
export function parseUsedIndices(data: Buffer): number[] { … }

```

This returns an array of occupied indices, allowing the parser to skip empty 240-byte chunks and reduce processing overhead.

### Parsing Individual Accounts

Given a slot index, `parseAccount` jumps to the corresponding offset (`HEADER_LEN` + bitmap size + index × `ACCOUNT_SIZE`) and decodes the 240-byte structure. It distinguishes between **User** accounts (traders with positions) and **LP** accounts (liquidity providers), mapping fields like owner public key, collateral balance, position size, entry price, and matcher program settings.

```typescript
export function parseAccount(data: Buffer, idx: number): Account { … }

```

### Aggregating All Accounts

`parseAllAccounts` combines the bitmap walker with the individual account parser to return a complete list of active positions:

```typescript
export function parseAllAccounts(data: Buffer): { idx: number; account: Account }[] { … }

```

This produces an array of occupied slots with their decoded account data, ready for risk calculations or trade execution.

## Integrating the Parsing Pipeline

In production, the `PercolatorFeed` component orchestrates the complete workflow. Located in [`src/percolator/feed.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/percolator/feed.ts), it periodically calls `fetchSlab`, then executes the parsers in sequence to build a comprehensive market snapshot.

```typescript
const slab = await fetchSlab(connection, slabPubkey);
const header = parseHeader(slab);
const config = parseConfig(slab);
const params = parseParams(slab);
const engine = parseEngine(slab);
const accounts = parseAllAccounts(slab);

```

The feed then emits typed events—`price`, `orderbook`, and `positions`—that downstream trading skills consume. This architecture separates the low-level binary parsing from high-level trading logic, allowing strategies to react to state changes without handling raw buffer offsets.

## Complete Parsing Example

The following example demonstrates pulling a full market snapshot and filtering for a specific user’s positions:

```typescript
import { Connection, PublicKey } from '@solana/web3.js';
import {
  fetchSlab,
  parseHeader,
  parseConfig,
  parseParams,
  parseEngine,
  parseAllAccounts,
} from './percolator/slab';

const connection = new Connection('https://api.mainnet-beta.solana.com');
const slabPubkey = new PublicKey('PercolatorSlab111111111111111111111111111111');

// Fetch and decode
const slabData = await fetchSlab(connection, slabPubkey);
const header   = parseHeader(slabData);
const config   = parseConfig(slabData);
const risk     = parseParams(slabData);
const engine   = parseEngine(slabData);
const accounts = parseAllAccounts(slabData);

console.log(`Percolator v${header.version} | Vault: ${engine.vault.toString()}`);
console.log(`Open Interest: ${engine.totalOpenInterest.toString()}`);
console.log(`Active Accounts: ${accounts.length}`);

// Filter for specific user
const userKey = new PublicKey('User111111111111111111111111111111111111');
const userPositions = accounts.filter(a => a.account.owner.equals(userKey));
console.log(`User has ${userPositions.length} active positions`);

```

## Summary

- **Percolator stores all perpetual futures data** in a single Solana account (the slab) using a fixed binary layout.
- **CloddsBot parses this data** through `fetchSlab` and a series of offset-based decoders defined in [`src/percolator/slab.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/percolator/slab.ts).
- **128-bit integer helpers** ensure precise handling of token amounts and prices that exceed JavaScript’s safe integer range.
- **Bitmap tracking** enables efficient iteration over up to 4096 potential user/LP accounts without scanning empty slots.
- **PercolatorFeed** orchestrates periodic polling and emits structured events for downstream trading logic.

## Frequently Asked Questions

### How does the parser handle large integer values without losing precision?

The parser implements `readI128LE` and `readU128LE` functions that combine two 64-bit reads into a native JavaScript `bigint`. This approach preserves full 128-bit precision for vault balances, position sizes, and funding indices, avoiding the rounding errors that occur with standard JavaScript `Number` types.

### What is the maximum number of accounts Percolator can store in one slab?

The slab supports **4,096 concurrent accounts** tracked via a bitmap of 64-bit words (`BITMAP_WORDS`). The `parseUsedIndices` function walks this bitmap to identify occupied slots, and each active account consumes 240 bytes of storage space within the account region.

### Why does Percolator use a single account instead of multiple PDA accounts?

Storing market configuration, risk parameters, engine state, and all user positions in one **slab account** reduces the Solana transaction complexity and account rent costs. This design requires sophisticated client-side parsing—implemented in [`src/percolator/slab.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/percolator/slab.ts)—but minimizes on-chain cross-program invocation overhead and state rental fees.

### How does CloddsBot know if the slab data structure has changed?

The `parseHeader` function validates a magic string (`PERCOLAT`) and version field before processing. If the magic bytes do not match or the version is unsupported, the parser throws an error immediately. This mechanism ensures the bot fails fast when encountering incompatible program upgrades rather than silently misinterpreting binary offsets.