There are two ways to work with a Coin Communities API on Solana Tracker, and most projects only need the first. To find Pump.fun tokens with active community chat, the Data API /search endpoint already returns hasCoinCommunity and communityMessages for every token and lets you filter and sort on them. To embed the chat itself, posting and reading messages as a signed-in user with a linked wallet, you integrate the Coin Communities provider directly, and the rules that matter are architectural: keep the business key on your server, scope every request to one user and one mint, and treat wallet linking as a signed challenge.
This guide documents the verified Data API fields with a runnable example, then lays out the integration boundaries we use for the chat itself. It does not document the provider's endpoints or SDK calls. The provider's public quickstart is no longer available at its original address, and the 2.0.0 release of @coin-communities/sdk on npm ships without a README, so confirm current onboarding and SDK contracts with the provider before you build the chat layer.
Choose the layer you need
| Need | Use | Auth | What you get |
|---|---|---|---|
| Rank or filter tokens by chat activity | Data API GET /search | Data API key (x-api-key) | hasCoinCommunity, communityMessages, sort by communityMessages |
| Show chat activity next to token data | Data API GET /search with query or filters | Data API key | Counts alongside price, liquidity, launchpad |
| Read and post messages, link wallets | Coin Communities provider | Provider business key on your server, plus a per-user session | Messages, user identity, wallet links |
If your feature is a screener column, a watchlist sort or an alert on rising chat activity, stop at the first two rows. A full chat integration adds a second credential, an OAuth flow, wallet signing and moderation handling, which is a lot of surface for a number you can already read from the Pump.fun API.
Coin Communities API fields in Data API search
GET /search on the Solana Data API includes two community fields on every result:
| Field | Type | Meaning |
|---|---|---|
hasCoinCommunity | boolean | true when communityMessages > 0 |
communityMessages | integer | Live message count from Coin Communities |
And three filters plus a sort key:
hasCoinCommunity=true(orfalsefor tokens with no monitored messages)minCommunityMessagesandmaxCommunityMessages, both inclusivesortBy=communityMessageswithsortOrder=asc|desc
{
"mint": "7sGdNQSvUGpahh6qyXB3g5gsdK9FAzZM299KyCXspump",
"hasCoinCommunity": true,
"communityMessages": 12
}
Counts reflect messages observed since monitoring started for that token. A count is activity, not unique users, sentiment or quality, and a token monitored for a week is not comparable with one monitored for an hour.
For Pump.fun, pick the right scope filter. launchpad=pumpfun matches tokens that launched on Pump.fun wherever they trade now; market=pumpfun matches tokens still on the bonding curve, and market=pumpfun-amm those trading on PumpSwap after graduation.
Find Pump.fun tokens with active chat
The script below lists Pump.fun-launched tokens with at least MIN_COMMUNITY_MESSAGES messages and a liquidity floor, sorted by message count.
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 /account/data-api
ST_API_KEY=
# Minimum monitored community messages (inclusive)
MIN_COMMUNITY_MESSAGES=5
# Minimum pool liquidity in USD
MIN_LIQUIDITY_USD=10000
index.ts:
import { Client, DataApiError, RateLimitError } from "@solana-tracker/data-api";
const apiKey = process.env.ST_API_KEY?.trim();
const MIN_MESSAGES = Number(process.env.MIN_COMMUNITY_MESSAGES ?? "5");
const MIN_LIQUIDITY_USD = Number(process.env.MIN_LIQUIDITY_USD ?? "10000");
if (!apiKey) exit("Missing ST_API_KEY (Data API key from /account/data-api)");
if (!Number.isInteger(MIN_MESSAGES) || MIN_MESSAGES < 1) exit("MIN_COMMUNITY_MESSAGES must be a positive integer");
if (!Number.isFinite(MIN_LIQUIDITY_USD) || MIN_LIQUIDITY_USD < 0) exit("MIN_LIQUIDITY_USD must be >= 0");
function exit(message: string): never {
console.error(message);
process.exit(1);
}
const client = new Client({ apiKey });
// The REST client has no timeout or retry of its own.
async function withRetry<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((r) => setTimeout(r, Math.max(500 * 2 ** (attempt - 1), retryAfterMs)));
} finally {
clearTimeout(timer);
}
}
}
async function main(): Promise<void> {
const res = await withRetry("GET /search", () =>
client.searchTokens({
launchpad: "pumpfun", // where the token launched; add market: "pumpfun" for curve-only
hasCoinCommunity: true,
minCommunityMessages: MIN_MESSAGES, // inclusive
minLiquidity: MIN_LIQUIDITY_USD,
sortBy: "communityMessages",
sortOrder: "desc",
limit: 25,
}),
);
for (const t of res.data) {
console.log(
[
String(t.communityMessages ?? 0).padStart(5),
(t.symbol || "?").slice(0, 12).padEnd(12),
t.mint,
t.market.padEnd(12),
`liq $${Math.round(t.liquidityUsd)}`,
`mcap $${Math.round(t.marketCapUsd)}`,
].join(" "),
);
}
console.log(`${res.data.length} Pump.fun tokens with >= ${MIN_MESSAGES} community messages`);
}
main().catch((error: unknown) => {
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
process.exitCode = 1;
});
Search results can include a few promoted rows beyond limit; if that matters for your ranking, re-check the filters client-side. Keys and plan limits are in the Data API dashboard.
Architecture for an embedded chat
When you do need the chat, keep three identities separate: your application (the business credential), the signed-in user (a session), and the wallet that user has linked. Mixing them is how messages end up posted under the wrong session or a business key ends up in a browser bundle.
Browser composer -> your authenticated server route -> Coin Communities provider
Wallet signature -> your session validation route -> linked wallet identity
Provider response -> your validation and moderation -> active mint's message list
Keep the business key on the server
Store the provider key and base URL in server configuration. The browser calls your routes; your routes attach the key. Never ship it in client code, query strings or analytics events. If the provider's real-time feed needs a ticket or token, fetch it server-side for the current user and hand the browser only that short-lived value.
Scope every request to one user
Use request-scoped provider configuration or explicit per-request authorization. Mutating a shared SDK client's bearer token on a server that handles concurrent users is how one user's session leaks into another's request.
Treat the OAuth callback as a session boundary
Start the provider's sign-in flow from your server, use an approved callback URL, and bind the callback to the browser session that started it. Reject missing, expired or mismatched state instead of falling back. Keep provider access and refresh tokens server-side or in scoped HttpOnly cookies, never in local storage or redirect URLs.
Link the wallet the user actually controls
Request the provider's challenge for the connected public key, have the wallet sign the exact challenge bytes, and submit the signature in the encoding the provider expects. A message signature is not a transaction and moves no funds. If the connected wallet changes, re-check the link instead of trusting a wallet address the browser sends.
Key every conversation by mint
Symbols and names are display metadata and are not unique. Use the mint as the community key, attach it to every read and write, and drop responses that arrive for a mint the user has already navigated away from.
Production pitfalls
- Ambiguous writes. A timeout after posting does not mean the message failed. Do not retry blindly unless the API offers idempotency, or check for the message first.
- Optimistic UI drift. Keep pending messages separate from confirmed ones and reconcile on the provider's message ID.
- Moderation state. Messages can be flagged or removed. Filter flagged content server-side and keep deletion states in your model.
- Untrusted media. Render text safely and only load media from an allowlist of hosts you control or have verified.
- Length and rate limits. Validate message length and session on your server before forwarding.
- Counts as signals. Use
communityMessagesto rank and alert, not as proof of organic interest; combine it with liquidity, holders and risk.
For broader discovery filters see the token search and screener API guide. To catch tokens before they have any chat, start from the Pump.fun WebSocket launch stream, and check concentration with the rug risk API guide.
FAQ
Is the Coin Communities credential the same as my Data API key?
No. The Data API key authorizes /search and the rest of the Data API. A chat integration uses a separate provider business credential. Store and rotate them independently.
Can I read message counts without integrating chat?
Yes. GET /search returns hasCoinCommunity and communityMessages and supports filtering and sorting on them with a normal Data API key.
Does communityMessages count unique users?
No. It is a count of messages observed since monitoring started for that token.
Which @coin-communities/sdk version should I use?
The latest on npm is 2.0.0, described as an SDK for React, React Native and Node, with React, React Native and TanStack Query as optional peers. It is a major version bump and ships without a README, so check the provider's current documentation before relying on any API surface.
Does a linked wallet make a message trustworthy?
It shows the poster controlled that wallet when linking. It says nothing about the truth of the message or the safety of the token.