The Solana market activity API from Solana Tracker is one request, GET /lighthouse, which returns rolling volume, trades, unique wallets, token launches and migrations for every indexed DEX and launchpad across 5-minute, 1-hour, 6-hour and 24-hour windows. Each window also carries a percentage change against the previous window of the same length. Use it to see where activity is concentrated before you drill into individual tokens; it is not a token price feed or a trade stream.
This guide covers the response shape, a TypeScript leaderboard with a timeout and retry, how to read each metric without fooling yourself, and where Lighthouse fits next to search, trending and streaming endpoints.
What the Solana market activity API returns
GET /lighthouse takes no query parameters. Authenticate with the x-api-key header, or call getLighthouse() in the Solana Data API SDK (@solana-tracker/data-api 0.5.0). The response is an array of market entries. It typically starts with all, followed by DEXs, launchpads and child launchpad markets.
curl "https://data.solanatracker.io/lighthouse" \
-H "x-api-key: $ST_API_KEY"
One entry, trimmed to a single window (values are illustrative):
{
"market": "pumpfun",
"label": "Pump.fun",
"icon": "https://...",
"url": "https://pump.fun",
"stats": {
"5m": {
"transactions": { "total": 120, "buys": 72, "sells": 48, "changePct": 18.4 },
"wallets": { "total": 96, "changePct": 11.2 },
"volume": { "total": 185000, "buys": 112000, "sells": 73000, "changePct": 24.7 },
"tokensCreated": { "total": 34, "changePct": 8.1 },
"migrations": { "total": 5, "changePct": 25 }
}
}
}
Every entry has all four windows: 5m, 1h, 6h and 24h. A child market adds a parent field.
| Field | Type | Meaning |
|---|---|---|
market | string | Stable key: all, pumpfun, raydium-all, meteora-curve:bags and others |
label | string | Display name |
icon, url | string | Logo and venue URL, or an empty string when unavailable |
parent | string, optional | Parent market for a child launchpad, such as meteora-curve or raydium-launchpad. Omitted for top-level markets |
stats[window].transactions | total, buys, sells, changePct | Trade count, split by side |
stats[window].wallets | total, changePct | Approximate unique trader wallets (a probabilistic count) |
stats[window].volume | total, buys, sells, changePct | Traded volume in USD, split by side |
stats[window].tokensCreated | total, changePct | Tokens launched in the window |
stats[window].migrations | total, changePct | Migrations in the window |
The documented error responses are 401 for a missing or invalid key and 500 for a server error. Both return { "error": "..." }.
Market keys: venues, families and children
The market key identifies three kinds of rows:
| Market key | What it represents |
|---|---|
all | All indexed markets |
pumpfun | A single venue |
raydium-all | A family rollup across related Raydium markets |
meteora-curve:bags | A discovered launchpad under a parent market (parent: "meteora-curve") |
Use market as the identifier in your code and label for display. Keep the parent relationship so a UI can show a family and its members without counting them twice.
When to use Lighthouse and when to use something else
Lighthouse answers a market-level question: which venues are busy, and is activity rising or falling? Token-level questions need a different call.
| Need | Call | Why |
|---|---|---|
| Compare DEX and launchpad activity | getLighthouse() | One request, every market, four windows, change vs the previous window |
| Overall market pulse for a header or status card | getLighthouse(), all row | Pre-aggregated, so no client-side summing |
| Find tokens that match liquidity, volume or risk filters | searchTokens() | Token rows with filters, sorting and pagination |
| Show the tokens with the most recent momentum | getTrendingTokens('1h') | Full token objects, including risk |
| React to individual trades, launches or price moves as they happen | Datastream WebSocket | Per-event push, included on Premium plans and higher |
| Store activity history for charts over days | Your own snapshots of getLighthouse() | The endpoint returns current rolling windows only |
A common flow is to start with Lighthouse to pick a venue, run the token screener on that venue, then check each candidate with the rug check API before acting.
Build a market activity leaderboard in TypeScript
The script below fetches Lighthouse once, prints the all row for the chosen window, and ranks top-level markets by volume. The window and row count come from the environment. The SDK has no request timeout and no retry, so the script adds both.
Install and run (Node.js 20.18 or newer):
npm init -y && npm pkg set type=module
npm install @solana-tracker/data-api@^0.5.0
npm install --save-dev tsx typescript @types/node
node --env-file=.env --import tsx index.ts
.env.example:
# Data API key from https://www.solanatracker.io/account/data-api
ST_API_KEY=
# Window to rank by: 5m, 1h, 6h or 24h
LIGHTHOUSE_WINDOW=1h
# Number of markets to show (1-100)
TOP_N=10
index.ts:
import { Client, DataApiError, RateLimitError, type LighthouseResponse } from '@solana-tracker/data-api';
const WINDOWS = ['5m', '1h', '6h', '24h'] as const;
type Window = (typeof WINDOWS)[number];
function exit(message: string): never {
console.error(message);
process.exit(1);
}
const apiKey = process.env.ST_API_KEY?.trim();
if (!apiKey) exit('Missing ST_API_KEY (create one at https://www.solanatracker.io/account/data-api)');
const windowKey = (process.env.LIGHTHOUSE_WINDOW?.trim() || '1h') as Window;
if (!WINDOWS.includes(windowKey)) exit(`LIGHTHOUSE_WINDOW must be one of ${WINDOWS.join(', ')}`);
const topN = Number(process.env.TOP_N?.trim() || 10);
if (!Number.isInteger(topN) || topN < 1 || topN > 100) exit('TOP_N must be an integer from 1 to 100');
const client = new Client({ apiKey });
// The SDK has no timeout and no retry: add both.
async function call<T>(label: string, fn: () => Promise<T>, attempts = 3): Promise<T> {
for (let attempt = 1; ; attempt++) {
let timer: NodeJS.Timeout | undefined;
try {
const timeout = new Promise<never>((_, reject) => {
timer = setTimeout(() => reject(new Error(`${label} timed out`)), 15_000);
});
return await Promise.race([fn(), timeout]);
} catch (error) {
const retryable =
error instanceof RateLimitError ||
!(error instanceof DataApiError) ||
error.status === undefined ||
error.status >= 500;
if (!retryable || attempt >= attempts) throw error;
const retryAfterMs = error instanceof RateLimitError && error.retryAfter ? error.retryAfter * 1000 : 0;
await new Promise((resolve) => setTimeout(resolve, Math.max(retryAfterMs, 500 * 2 ** attempt)));
} finally {
clearTimeout(timer);
}
}
}
const usd = (n: number) => `$${Math.round(n).toLocaleString('en-US')}`;
// changePct is 0 when the previous window had no activity, so a zero total shows "-".
const change = (total: number, pct: number) => (total === 0 ? '-' : `${pct >= 0 ? '+' : ''}${pct.toFixed(1)}%`);
try {
const markets: LighthouseResponse = await call('getLighthouse', () => client.getLighthouse());
if (!Array.isArray(markets)) throw new Error('unexpected response: expected an array of markets');
// The all row is the overall view. Never sum the other rows: families and children overlap.
const all = markets.find((m) => m.market === 'all');
if (all) {
const s = all.stats[windowKey];
console.log(
`All markets, ${windowKey}: ${usd(s.volume.total)} volume (${change(s.volume.total, s.volume.changePct)}), ` +
`${s.transactions.total.toLocaleString('en-US')} trades, ~${s.wallets.total.toLocaleString('en-US')} wallets, ` +
`${s.tokensCreated.total} launches, ${s.migrations.total} migrations`,
);
}
const ranked = markets
.filter((m) => m.market !== 'all' && !m.parent && m.stats?.[windowKey])
.sort((a, b) => b.stats[windowKey].volume.total - a.stats[windowKey].volume.total)
.slice(0, topN);
console.table(
ranked.map((m) => {
const s = m.stats[windowKey];
return {
market: m.market,
volume: usd(s.volume.total),
change: change(s.volume.total, s.volume.changePct),
trades: s.transactions.total,
wallets: s.wallets.total,
launches: s.tokensCreated.total,
migrations: s.migrations.total,
buyShare: s.volume.total > 0 ? `${Math.round((s.volume.buys / s.volume.total) * 100)}%` : '-',
};
}),
);
} catch (error) {
const reason =
error instanceof DataApiError && error.status
? `HTTP ${error.status}: ${error.message}`
: error instanceof Error
? error.message
: String(error);
console.error(`Lighthouse request failed: ${reason}`);
process.exitCode = 1;
}
A few choices in this script are deliberate:
- Child markets are excluded from the ranking (
!m.parent). A child launchpad's activity can also be counted in its parent market, so mixing both levels in one leaderboard risks double-counting. Rank children in a separate view if you need them. - A zero total prints
-, not+0.0%. The API setschangePctto0when the previous window had no activity, which is not the same as flat. - Missing data fails loudly. A non-array response throws instead of rendering an empty table that looks like a quiet market.
The companion project adds an overview table across all four windows, a SORT_METRIC option, an INCLUDE_CHILD_MARKETS switch and a watch mode that keeps the last good snapshot when a refresh fails.
Reading the metrics correctly
Change is window over window, not since midnight
changePct compares the current window with the window of the same length immediately before it. The 1h value compares the latest hour with the hour before that. It is not a daily change and not a price return. When a user switches the view from 5m to 24h, switch the value and its change together; leaving the old percentage in place produces a misleading card.
Trades, wallets and volume measure different things
More trades can come from more wallets, from the same wallets trading more often, or from automated activity. Read the three together. A jump in trades with flat wallets and flat volume usually means smaller, more frequent trades, not new demand. wallets.total is an approximate unique count (a probabilistic estimate), so treat small differences as noise.
The buy and sell split on volume and transactions is useful for direction. A buy share of volume well above 50% over the 1h window says more than either side's raw total.
Launches and migrations are not a graduation rate
tokensCreated counts tokens launched inside the window. migrations counts migrations inside the same window. The tokens that migrated in the last hour were mostly launched earlier, so migrations / tokensCreated is not a cohort graduation rate. To measure how many launches graduate, follow a set of tokens over time, for example with the Pump.fun graduation API or the Pump.fun API.
Small baselines produce big percentages
A launchpad that went from 2 trades to 8 shows +300%. Show the absolute value next to every percentage, and set a minimum volume or trade count before a change can trigger an alert or a "trending" badge.
Production pitfalls
- Summing rows. Family rollups such as
raydium-allcover several related markets, child launchpads sit under a parent, and one wallet can trade on several markets. Adding rows inflates volume and wallet counts. Use theallrow for totals. - Hard-coding the market list. Lighthouse includes discovered launchpad markets, so new keys can appear. Key your UI on
market, render unknown keys with theirlabel, and do not crash on a key you have not seen. - Treating empty
iconorurlas bad data. Both are empty strings when branding is unavailable. Show a fallback and keep the row. - Calling Lighthouse from the browser. That exposes your API key and multiplies requests by the number of viewers. Fetch on your backend, cache the response, and serve every client from the cache.
- Dropping the last good snapshot on a failed refresh. If a refresh fails, keep showing the previous response with its timestamp instead of blanking the dashboard. The companion project does this in watch mode.
- Polling faster than you need. The documentation does not state a refresh cadence. Poll at an interval that suits your view (the companion project enforces 15 seconds or more), and honor
retryAfteron a429. - Using Lighthouse as a trade feed. Rolling aggregates smooth over individual events. For per-trade or per-launch reactions, use a stream such as the Pump.fun WebSocket.
Choosing a window
| Window | Best for | Watch out for |
|---|---|---|
5m | "What is moving right now" cards, alert triggers | Noisy; small baselines swing hard |
1h | Default leaderboard, venue comparison | Change compares to the previous hour only |
6h | Session-level trends | Slower to reflect a sudden shift |
24h | Daily summaries, venue share | Not a calendar day; it is a rolling 24 hours |
FAQ
Is there an official Solana market activity API for DEXs and launchpads?
Solana Tracker's Lighthouse endpoint, GET /lighthouse, returns volume, trades, approximate unique wallets, launches and migrations for each indexed DEX and launchpad across 5m, 1h, 6h and 24h windows. It is part of the Solana Data API and available in the SDK as getLighthouse().
Can I add up every market to get total Solana volume?
No. Family rows and child launchpads overlap, and wallets trade on several markets. The all row is the overall figure for indexed markets.
Does Lighthouse accept filters such as a single market or window?
No. It takes no query parameters and returns every market with all four windows. Filter the array on your side by market, parent or window.
Is volume.changePct the same as price change?
No. It compares traded USD volume in the current window with the previous window of the same length. For token prices, use the price endpoints or a price stream.
Can I get historical Lighthouse data?
The endpoint returns the current rolling windows. To chart activity over days, store a timestamped snapshot on each poll and build the history yourself.
How do I go from a busy market to specific tokens?
Pick a venue in Lighthouse, then query the search endpoint with that venue as a market filter, sorted by volume. Search market ids such as pumpfun-amm are individual venues; family keys like raydium-all exist only in Lighthouse.
References
- Compare Solana DEX and launchpad activity (Lighthouse guide)
- Solana memecoin market stats API reference
- Find tokens: from market signals to token research
- @solana-tracker/data-api on npm
Companion project
The full example lives at solanatracker/examples/19-solana-lighthouse-market-activity-api. It prints the all row across every window, ranks markets by any metric, and can refresh on an interval. Get a key from your Data API dashboard.
cp .env.example .env && npm install && npm start