Skip to main content
Alpha feature: Token Filters access is gated. Ask the Mobula team to enable it for your account. Until then, use Pulse; it works the same way for the same view/filter workflow.
Filter Reference: For complete filter documentation including all available fields and operators, see Token Filters Reference. All available factory names and pool types can be found at: https://api.mobula.io/api/1/system-metadata
Migrating from Pulse V2? Check out the Migration Guide for step-by-step instructions.
This endpoint is only available to Growth and Enterprise plans.

What’s New in Token Filters

Token Filters Stream introduces significant improvements over Pulse V2:
  • Clean Data Models: Output follows exactly TokenDetailsOutput and MarketDetailsOutput schemas with consistent camelCase naming
  • Partial Updates: Only changed fields are sent, reducing bandwidth and JSON parsing overhead
  • Real-time View Updates: Views are updated in real-time based on incoming swaps - no more periodic sync messages
  • REST API Initialization: Initialize views via REST API before connecting to WebSocket for better control
  • WebSocket-only Subscriptions: Send the complete mode + views payload directly over WebSocket when you do not need a REST init response first
  • OHLCV Attachments: Add historical candles to initial view results and receive live candle updates per swap
  • Improved Delete Messages: Delete messages now use structured objects instead of pipe-separated selectors
  • Consistent camelCase: All input parameters use camelCase matching the output format

Architecture Overview

Connection Flow

You can connect to Token Filters in two ways:
  1. WebSocket Only (recommended): Send the full payload directly via WebSocket
  2. REST + WebSocket: Initialize via REST API first, then connect to WebSocket
┌─────────────────────────────────────────────────────────────────┐
│                    Option 1: WebSocket Only                      │
├─────────────────────────────────────────────────────────────────┤
│   WebSocket Connect  ──▶  Send Full Payload  ──▶  Real-time     │
│   (wss://api.mobula.io)   (views + filters)      Updates        │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    Option 2: REST + WebSocket                    │
├─────────────────────────────────────────────────────────────────┤
│   REST API  ──▶  Get Initial Data  ──▶  WebSocket  ──▶  Updates │
│   (POST)         + subscriptionId       (subscribe)             │
└─────────────────────────────────────────────────────────────────┘
Important: Even when using WebSocket, you must send the complete payload with mode and views - not just a subscriptionId. The REST API is optional and only useful if you need the initial data before connecting.
Token Filters WebSocket accepts the same GraphQL/Codex-style aliases as the REST token filters, including marketCap, sellCount4, sniperCount, and bundlerCount. See the Token Filters Reference for the full alias table.

Modes

Token Filters supports two modes:
ModeDescriptionOutput Schema
tokenToken-based analytics and statisticsTokenDetailsOutput
marketPool/market-based data with pair informationMarketDetailsOutput

Step 1: Initialize Views via REST API

Endpoint

POST /api/2/token/filters

Request Body

{
  "mode": "token",
  "views": {
    "new-tokens": {
      "sortBy": "createdAt",
      "sortOrder": "desc",
      "limit": 50,
      "filters": {
        "chainId": { "in": ["solana:solana"] },
        "source": { "in": ["pumpfun"] },
        "bonded": { "equals": false }
      }
    },
    "trending": {
      "sortBy": "trendingScore5min",
      "sortOrder": "desc",
      "limit": 30,
      "filters": {
        "chainId": { "in": ["solana:solana", "evm:8453"] },
        "source": { "in": ["pumpfun", "moonshot-evm"] }
      }
    }
  }
}

Response

{
  "subscriptionId": "sub_abc123xyz",
  "views": {
    "new-tokens": {
      "data": [
        {
          "address": "DPQxrrxJfTrLEjyknrUBcMDsAMYbAQJMnKTuJh4kbonk",
          "chainId": "solana:solana",
          "symbol": "EXAMPLE",
          "name": "Example Token",
          "priceUSD": 0.000123,
          "marketCapUSD": 123000,
          "volumeUSD24h": 50000,
          ...
        }
      ]
    },
    "trending": {
      "data": [...]
    }
  }
}

Step 2: Connect to WebSocket

Connection

wss://api.mobula.io

Subscribe Message

Send the complete payload with your views configuration:
{
  "type": "token-filters",
  "authorization": "YOUR_API_KEY",
  "payload": {
    "mode": "token",
    "delta": true,
    "maxUpdatesPerMinute": 120,
    "views": {
      "new-tokens": {
        "sortBy": "createdAt",
        "sortOrder": "desc",
        "limit": 50,
        "filters": {
          "chainId": { "in": ["solana:solana"] },
          "source": { "in": ["pumpfun"] },
          "bonded": { "equals": false }
        }
      },
      "trending": {
        "sortBy": "trendingScore5min",
        "sortOrder": "desc",
        "limit": 30,
        "filters": {
          "chainId": { "in": ["solana:solana", "evm:8453"] }
        }
      }
    }
  }
}
Payload fieldTypeDefaultDescription
mode"token" | "market"RequiredSelects token output or market/pool output
viewsobjectRequiredObject keyed by view name. Each value is a view definition
deltabooleantrueWhen true, update-token / update-market messages contain only changed fields plus identifiers
maxUpdatesPerMinutenumberNo throttlePer-entity update throttle for the subscription
Do NOT send just a subscriptionId. You must always send the full mode and views configuration, even if you previously called the REST API. The WebSocket subscription is stateless.

Preset Models

Use preset models for common use cases without defining full custom views:
{
  "mode": "token",
  "views": {
    "new-tokens": {
      "model": "new",
      "filters": {
        "chainId": { "in": ["solana:solana"] },
        "source": { "in": ["pumpfun"] }
      }
    }
  }
}

Available Presets

ModelDescriptionSort ByDefault Filters
newNewly created launchpad tokenscreatedAt descbonded: false, source: { in: launchpad pools }
bondingTokens in bonding phasebondingPercentage descbondingPercentage: { gt: 0, lt: 100 }, volume1hUSD: { gte: 10 }, bonded: false, bondedAt: null, launchpad pools
bondedRecently bonded tokensbondedAt descbonded: true, bondedAt: { not: null }, launchpad pools
trendingFee-weighted trending tokensfeesPaid15minUSD descLiquidity, fee, volume, holder-concentration, transfer-tax and chain-specific thresholds
explorerExplorer view by trendingtrendingScore1h desc-
topGainersTop price gainerspriceChange6hPercentage descvolume1hUSD: { gte: 500 }
surgeSmall-cap surge tokenssurgeScore descliquidityUSD: { gte: 3000, lte: 50000 }, surgeScore: { gt: 0 }

OHLCV Mode

Enable OHLCV candlestick data for each token in a view by setting ohlcv: true in the view definition. Parameters:
ParameterTypeDefaultDescription
ohlcvbooleanfalseEnable OHLCV candle data
ohlcvTimeframestring"5m"Candle timeframe: 1s, 5s, 15s, 1m, 5m, 15m, 30m, 1h, 4h, 1d
Constraints:
  • When ohlcv: true, the max limit is 15 tokens (automatically clamped)
  • Up to 200 candles per token are returned on init when history is available
  • Candle format: { o, h, l, c, v, t } (open, high, low, close, volume, timestamp ms)
Example (surge + OHLCV):
{
  "mode": "token",
  "views": {
    "surge-ohlcv": {
      "model": "surge",
      "ohlcv": true,
      "ohlcvTimeframe": "5m",
      "limit": 10
    }
  }
}
Init response includes ohlcv array per token:
{
  "subscriptionId": "sub_abc123",
  "views": {
    "surge-ohlcv": {
      "data": [
        {
          "address": "TokenAddress...",
          "chainId": "solana:solana",
          "priceUSD": 0.001,
          "ohlcv": [
            { "o": 0.0009, "h": 0.0012, "l": 0.0008, "c": 0.001, "v": 5000, "t": 1709300000000 },
            { "o": 0.001, "h": 0.0013, "l": 0.0009, "c": 0.0012, "v": 3200, "t": 1709300300000 }
          ]
        }
      ]
    }
  }
}
Real-time candle updates are sent via update-token-ohlcv messages when new swaps occur:
{
  "type": "update-token-ohlcv",
  "payload": {
    "viewName": "surge-ohlcv",
    "token": { "address": "TokenAddress...", "chainId": "solana:solana" },
    "candle": { "o": 0.001, "h": 0.0015, "l": 0.001, "c": 0.0014, "v": 8500, "t": 1709300600000 }
  }
}
When a new candle period starts, the previous candle’s close is used as the new candle’s open. Volume resets to 0 for each new period.

Message Types

Update Message (Partial)

When token/market data changes, only the modified fields are sent:
{
  "type": "update-token",
  "payload": {
    "viewName": "trending",
    "token": {
      "address": "DPQxrrxJfTrLEjyknrUBcMDsAMYbAQJMnKTuJh4kbonk",
      "chainId": "solana:solana",
      "priceUSD": 0.000135,
      "priceChange1hPercentage": 15.2,
      "volume1hUSD": 55000
    }
  }
}
Partial Updates: Only changed fields are included. Apply these as patches to your local state. The address and chainId are always included for identification.

New Token/Market Message

When a new token enters a view:
{
  "type": "new-token",
  "payload": {
    "viewName": "new-tokens",
    "token": {
      "address": "NewTokenAddress123",
      "chainId": "solana:solana",
      "symbol": "NEW",
      "name": "New Token",
      "decimals": 6,
      "priceUSD": 0.0001,
      "priceToken": 0.00000005,
      "priceTokenString": "0.00000005",
      "marketCapUSD": 100000,
      "volume1hUSD": 5000,
      "volume24hUSD": 25000,
      "trades1h": 150,
      "trades24h": 800,
      "holdersCount": 250,
      "liquidityUSD": 15000,
      "bonded": false,
      "bondingPercentage": 45.5,
      "createdAt": "2025-01-23T10:30:00.000Z",
      "socials": {
        "twitter": "https://twitter.com/newtoken",
        "website": "https://newtoken.com",
        "telegram": null,
        "others": null
      },
      ...
    }
  }
}

Remove Token/Market Message

When a token exits a view:
{
  "type": "remove-token",
  "payload": {
    "viewName": "trending",
    "token": {
      "chainId": "solana:solana",
      "address": "DPQxrrxJfTrLEjyknrUBcMDsAMYbAQJMnKTuJh4kbonk"
    }
  }
}
Improved Format: Token Filters uses a structured object { chainId, address } instead of the V2 pipe-separated format chainId|address.

OHLCV Update Message

When OHLCV mode is enabled, candle updates are sent on each swap:
{
  "type": "update-token-ohlcv",
  "payload": {
    "viewName": "surge-view",
    "token": {
      "chainId": "solana:solana",
      "address": "DPQxrrxJfTrLEjyknrUBcMDsAMYbAQJMnKTuJh4kbonk"
    },
    "candle": {
      "o": 0.001,
      "h": 0.0013,
      "l": 0.0009,
      "c": 0.0012,
      "v": 6500,
      "t": 1709300000000
    }
  }
}

OHLCV Mode

Enable OHLCV mode to receive historical candlestick data on init and real-time candle updates on each swap.

View Definition with OHLCV

Add ohlcv: true and optionally ohlcvTimeframe to a view definition:
{
  "mode": "token",
  "views": {
    "surge-candles": {
      "model": "surge",
      "ohlcv": true,
      "ohlcvTimeframe": "5m"
    }
  }
}

Parameters

ParameterTypeDefaultDescription
ohlcvbooleanfalseEnable OHLCV candlestick data
ohlcvTimeframestring"5m"Candle timeframe. Allowed: 1s, 5s, 15s, 1m, 5m, 15m, 30m, 1h, 4h, 1d

Constraints

  • When ohlcv: true, the view limit is clamped to 15 (max 15 tokens per view)
  • Each token receives up to 200 historical candles on init when history is available
  • Real-time candle updates are sent via update-token-ohlcv messages

Init Response with OHLCV

When OHLCV is enabled, each token in the response includes an ohlcv array:
{
  "subscriptionId": "sub_abc123",
  "views": {
    "surge-candles": {
      "data": [
        {
          "address": "DPQxrrxJfTrLEjyknrUBcMDsAMYbAQJMnKTuJh4kbonk",
          "chainId": "solana:solana",
          "priceUSD": 0.001,
          "ohlcv": [
            { "o": 0.001, "h": 0.0012, "l": 0.0009, "c": 0.0011, "v": 5000, "t": 1709300000000 },
            { "o": 0.0011, "h": 0.0015, "l": 0.001, "c": 0.0013, "v": 3200, "t": 1709300300000 }
          ]
        }
      ]
    }
  }
}

Candle Fields

FieldDescription
oOpen price
hHigh price
lLow price
cClose price
vVolume (USD)
tCandle timestamp (ms, aligned to timeframe)

Token Data Model (mode: “token”)

The token mode returns data following the TokenDetailsOutput schema:
interface TokenDetailsOutput {
  // Identification
  address: string;
  chainId: string;
  symbol: string | null;
  name: string | null;
  decimals: number;
  id: number | null;

  // Pricing
  priceUSD: number;
  priceToken: number;
  priceTokenString: string;

  // Market Data
  marketCapUSD: number;
  marketCapDilutedUSD: number;
  totalSupply: number;
  circulatingSupply: number;
  approximateReserveUSD: number;
  approximateReserveToken: number;
  approximateReserveTokenRaw: string;

  // Liquidity
  liquidityUSD: number;
  liquidityMaxUSD: number;

  // Bonding
  bonded: boolean;
  bondingPercentage: number;
  bondingCurveAddress: string | null;
  preBondingFactory: string;
  bondedAt: Date | null;

  // Price Changes
  priceChange1minPercentage: number;
  priceChange5minPercentage: number;
  priceChange1hPercentage: number;
  priceChange4hPercentage: number;
  priceChange6hPercentage: number;
  priceChange12hPercentage: number;
  priceChange24hPercentage: number;

  // Volume (USD)
  volume1minUSD: number;
  volume5minUSD: number;
  volume15minUSD: number;
  volume1hUSD: number;
  volume4hUSD: number;
  volume6hUSD: number;
  volume12hUSD: number;
  volume24hUSD: number;

  // Volume Buy/Sell
  volumeBuy1minUSD: number;
  volumeBuy1hUSD: number;
  volumeBuy24hUSD: number;
  volumeSell1minUSD: number;
  volumeSell1hUSD: number;
  volumeSell24hUSD: number;
  // ... all timeframes

  // Trades
  trades1min: number;
  trades5min: number;
  trades1h: number;
  trades24h: number;
  // ... all timeframes

  // Buys/Sells
  buys1min: number;
  buys1h: number;
  buys24h: number;
  sells1min: number;
  sells1h: number;
  sells24h: number;
  // ... all timeframes

  // Unique Participants
  buyers1h: number;
  buyers24h: number;
  sellers1h: number;
  sellers24h: number;
  traders1h: number;
  traders24h: number;
  // ... all timeframes

  // Organic Metrics (excluding bot activity)
  organicTrades1h: number;
  organicVolume1hUSD: number;
  organicBuyers1h: number;
  // ... all organic fields

  // Trending Scores
  trendingScore1min: number;
  trendingScore5min: number;
  trendingScore15min: number;
  trendingScore1h: number;
  trendingScore4h: number;
  trendingScore6h: number;
  trendingScore12h: number;
  trendingScore24h: number;

  // Holdings Analysis
  holdersCount: number;
  top10HoldingsPercentage: number;
  top50HoldingsPercentage: number;
  top100HoldingsPercentage: number;
  top200HoldingsPercentage: number;
  devHoldingsPercentage: number;
  insidersHoldingsPercentage: number;
  bundlersHoldingsPercentage: number;
  snipersHoldingsPercentage: number;
  proTradersHoldingsPercentage: number;
  freshTradersHoldingsPercentage: number;
  smartTradersHoldingsPercentage: number;

  // Trader Categories
  insidersCount: number;
  bundlersCount: number;
  snipersCount: number;
  freshTradersCount: number;
  proTradersCount: number;
  smartTradersCount: number;
  smartTradersBuys: number;
  freshTradersBuys: number;
  proTradersBuys: number;

  // Fees
  feesPaid1hUSD: number;
  feesPaid24hUSD: number;
  totalFeesPaidUSD: number;
  totalFeesPaidNativeRaw: string;
  // ... all timeframes

  // ATH/ATL
  athUSD: number;
  atlUSD: number;
  athDate: Date;
  atlDate: Date;

  // Metadata
  logo: string | null;
  originLogoUrl: string | null;
  description: string | null;
  deployer: string | null;
  deployerMigrationsCount: number;
  deployerTokensCount: number;
  rank: number | null;
  cexs: string[];

  // Social
  socials: {
    twitter: string | null;
    website: string | null;
    telegram: string | null;
    others: Record<string, unknown> | undefined;
    uri: string | undefined;
  };

  // Security
  security: SecurityFlags | null;

  // Twitter Analysis
  twitterReusesCount: number;
  twitterRenameCount: number;
  twitterRenameHistory: Array<{
    username: string;
    lastChecked: string;
  }>;

  // DexScreener
  dexscreenerListed: boolean;
  dexscreenerHeader: string | null;
  dexscreenerAdPaid: boolean;
  dexscreenerAdPaidDate: Date | null;
  dexscreenerSocialPaid: boolean;
  dexscreenerSocialPaidDate: Date | null;
  dexscreenerBoosted: boolean;
  dexscreenerBoostedDate: Date | null;
  dexscreenerBoostedAmount: number;

  // Livestream
  liveStatus: string | null;
  liveThumbnail: string | null;
  livestreamTitle: string | null;
  liveReplyCount: number | null;

  // Exchange & Factory
  exchange: { name: string; logo: string } | undefined;
  factory: string | null;
  source: string | null;
  sourceFactory: string | null;
  poolAddress: string;
  blockchain: string;
  tokenType: string;

  // Timestamps
  createdAt: Date | null;
  latestTradeDate: Date | null;

  // Internationalization
  i18n: Record<string, unknown> | null;
}

Market Data Model (mode: “market”)

The market mode returns data following the MarketDetailsOutput schema with pair information:
interface MarketDetailsOutput {
  // Pool Information
  address: string;
  blockchain: string;
  type: string;
  factory: string | null;
  createdAt: Date | null;
  latestTradeDate: Date | null;

  // Pair Tokens
  base: TokenData;
  quote: TokenData;
  baseToken: "token0" | "token1";
  quoteToken: "token0" | "token1";

  // Pricing
  priceUSD: number;
  priceToken: number;
  priceTokenString: string;
  liquidityUSD: number;

  // Bonding
  bonded: boolean;
  bondingPercentage: number;
  preBondingPoolAddress: string | null;
  sourceFactory: string | null;

  // Price Changes
  priceChange1minPercentage: number;
  priceChange5minPercentage: number;
  priceChange1hPercentage: number;
  // ... all timeframes

  // Volume, Trades, Participants
  // Same structure as TokenDetailsOutput

  // Holdings, Security, Socials
  // Inherited from base token

  // Exchange
  exchange: { name: string; logo: string };
}

View Configuration

View Parameters

ParameterTypeRequiredDescription
view keystringYesUnique identifier for the view, e.g. "new-tokens" in views
modelstringNoPreset model: new, bonding, bonded, trending, explorer, topGainers, surge
sortBystringNoField to sort by (camelCase)
sortOrder”asc” | “desc”NoSort direction
limitnumberNoMax results (default: 50, max: 100)
offsetnumberNoPagination offset
filtersobjectNoFilter configuration
selectorsarrayNoExplicit token or market selectors: { "chainId": "...", "address": "..." }
ohlcvbooleanNoInclude OHLCV history and live candle updates for token views
ohlcvTimeframestringNoOHLCV timeframe when ohlcv is true
Use filters.chainId for chains and filters.source for token launchpads/factories. In market mode, use filters.pool.type or filters.type for pool type filtering.

Filter Operators

OperatorDescriptionExample
equalsExact match{ "bonded": { "equals": true } }
gteGreater than or equal{ "marketCapUSD": { "gte": 10000 } }
lteLess than or equal{ "volume1hUSD": { "lte": 100000 } }
gtGreater than{ "holdersCount": { "gt": 100 } }
ltLess than{ "bondingPercentage": { "lt": 100 } }
notNot equal{ "source": { "not": null } }
inIn array{ "chainId": { "in": ["solana:solana"] } }
containsString contains{ "symbol": { "contains": "DOGE" } }
startsWithString prefix{ "chainId": { "startsWith": "ton" } }
endsWithString suffix{ "symbol": { "endsWith": "INU" } }

Custom Views Example

{
  "mode": "token",
  "views": {
    "high-volume-pumpfun": {
      "sortBy": "volume1hUSD",
      "sortOrder": "desc",
      "limit": 50,
      "filters": {
        "chainId": { "in": ["solana:solana"] },
        "source": { "in": ["pumpfun"] },
        "volume1hUSD": { "gte": 5000 },
        "marketCapUSD": { "gte": 10000 },
        "bonded": { "equals": false }
      }
    },
    "whale-accumulation": {
      "sortBy": "volumeBuy1hUSD",
      "sortOrder": "desc",
      "limit": 30,
      "filters": {
        "chainId": { "in": ["solana:solana", "evm:8453"] },
        "volumeBuy1hUSD": { "gte": 10000 },
        "top10HoldingsPercentage": { "lte": 30 },
        "holdersCount": { "gte": 100 }
      }
    },
    "safe-tokens": {
      "sortBy": "trendingScore1h",
      "sortOrder": "desc",
      "limit": 25,
      "filters": {
        "chainId": { "in": ["solana:solana"] },
        "security": {
          "noMintAuthority": { "equals": true },
          "isBlacklisted": { "equals": false }
        },
        "devHoldingsPercentage": { "lte": 5 }
      }
    }
  }
}

View Management

Pause Views

Temporarily stop receiving updates for specific views:
{
  "type": "token-filters-pause",
  "payload": {
    "action": "pause",
    "views": ["high-volume-pumpfun", "whale-accumulation"]
  }
}

Unpause Views

Resume updates:
{
  "type": "token-filters-pause",
  "payload": {
    "action": "unpause",
    "views": ["high-volume-pumpfun"]
  }
}

Unsubscribe

Unsubscribe from All

{
  "type": "unsubscribe",
  "authorization": "YOUR_API_KEY",
  "payload": {
    "type": "token-filters"
  }
}

Unsubscribe from Specific View

{
  "type": "unsubscribe",
  "authorization": "YOUR_API_KEY",
  "payload": {
    "type": "token-filters",
    "viewName": "high-volume-pumpfun"
  }
}

Unsubscribe by Subscription ID

{
  "type": "unsubscribe",
  "authorization": "YOUR_API_KEY",
  "payload": {
    "type": "token-filters",
    "subscriptionId": "sub_abc123xyz"
  }
}

Connection Keepalive

Send periodic ping messages to maintain the connection:
{"event": "ping"}
The server responds with a pong message.
Send ping messages every 30-60 seconds for long-lived connections.

Complete TypeScript Example

import WebSocket from 'ws';

interface TokenFiltersConfig {
  apiKey: string;
  mode: 'token' | 'market';
  views: Record<string, ViewConfig>;
  delta?: boolean;
  maxUpdatesPerMinute?: number;
}

interface ViewConfig {
  model?: 'new' | 'bonding' | 'bonded' | 'trending' | 'explorer' | 'topGainers' | 'surge';
  sortBy?: string;
  sortOrder?: 'asc' | 'desc';
  limit?: number;
  offset?: number;
  filters?: Record<string, unknown>;
  ohlcv?: boolean;
  ohlcvTimeframe?: string;
}

class TokenFiltersClient {
  private ws: WebSocket | null = null;
  private state: Map<string, Map<string, unknown>> = new Map();

  constructor(private config: TokenFiltersConfig) {}

  async initialize(): Promise<void> {
    // Initialize view state maps
    for (const viewName of Object.keys(this.config.views)) {
      this.state.set(viewName, new Map());
    }

    // Connect to WebSocket directly
    await this.connect();
  }

  private async connect(): Promise<void> {
    this.ws = new WebSocket('wss://api.mobula.io');

    this.ws.on('open', () => {
      // Send FULL payload with mode and views (not just subscriptionId)
      this.ws!.send(JSON.stringify({
        type: 'token-filters',
        authorization: this.config.apiKey,
        payload: {
          mode: this.config.mode,
          delta: this.config.delta ?? true,
          maxUpdatesPerMinute: this.config.maxUpdatesPerMinute,
          views: this.config.views
        }
      }));
    });

    this.ws.on('message', (data) => {
      const message = JSON.parse(data.toString());
      this.handleMessage(message);
    });

    // Keepalive
    setInterval(() => {
      if (this.ws?.readyState === WebSocket.OPEN) {
        this.ws.send(JSON.stringify({ event: 'ping' }));
      }
    }, 30000);
  }

  private handleMessage(message: {
    type: string;
    payload: {
      viewName: string;
      token: { address: string; chainId: string; [key: string]: unknown };
    };
  }): void {
    const { type, payload } = message;

    switch (type) {
      case 'new-token':
      case 'new-market': {
        const viewState = this.state.get(payload.viewName);
        if (viewState) {
          const key = `${payload.token.chainId}|${payload.token.address}`;
          viewState.set(key, payload.token);
          console.log(`[NEW] ${payload.viewName}: ${payload.token.symbol}`);
        }
        break;
      }

      case 'update-token':
      case 'update-market': {
        const viewState = this.state.get(payload.viewName);
        if (viewState) {
          const key = `${payload.token.chainId}|${payload.token.address}`;
          const existing = viewState.get(key) as Record<string, unknown> | undefined;
          if (existing) {
            // Apply partial update
            const updated = { ...existing, ...payload.token };
            viewState.set(key, updated);
            console.log(`[UPDATE] ${payload.viewName}: ${(existing as { symbol?: string }).symbol}`);
          }
        }
        break;
      }

      case 'remove-token':
      case 'remove-market': {
        const viewState = this.state.get(payload.viewName);
        if (viewState) {
          const key = `${payload.token.chainId}|${payload.token.address}`;
          viewState.delete(key);
          console.log(`[REMOVE] ${payload.viewName}: ${payload.token.address}`);
        }
        break;
      }
    }
  }

  getViewData(viewName: string): unknown[] {
    const viewState = this.state.get(viewName);
    return viewState ? Array.from(viewState.values()) : [];
  }
}

// Usage
const client = new TokenFiltersClient({
  apiKey: 'YOUR_API_KEY',
  mode: 'token',
  views: {
    trending: {
      sortBy: 'feesPaid15minUSD',
      sortOrder: 'desc',
      limit: 50,
      filters: {
        chainId: { in: ['solana:solana'] },
        source: { in: ['pumpfun'] }
      }
    }
  }
});

await client.initialize();

Performance Considerations

View Limits

  • Maximum 10 views per connection
  • Maximum 100 items per view
  • Maximum 1000 total items across all views

Bandwidth Optimization

  • Partial updates significantly reduce bandwidth
  • Use pause for inactive views
  • Filter aggressively to reduce data volume
  • Use appropriate limit values

Best Practices

  1. Initialize via REST API to get initial state
  2. Apply partial updates to local state
  3. Use ping/pong for connection health
  4. Implement reconnection logic
  5. Handle all message types gracefully

Support

Need help? Our response times are < 1h.

Support

Telegram

Support

Slack

Need help?

Email