Solana terminals charge 0.5% to 1% per side. How to build your own: a free chart library, Jupiter for execution, and the Conyr API for the live tape.
A round trip on Photon costs 2% before slippage: a 1% platform fee on the buy and the same again on the sell. Median memecoin hold time is now measured around 100 seconds, so an active session runs that meter dozens of times an hour. Across the category, Solana traders paid roughly $71.6M in terminal platform fees in the 30 days ending 2026-08-30, per DefiLlama. Most traders have never added up their own share of that.
That is not an argument against terminals. The good ones earn their fee, and 2026 has been brutal about sorting which ones those are. But every terminal is assembled from parts, and most of the parts are open source or one API call away. A few hundred lines of TypeScript can put a chart, an execution path, and a live tape on your own screen for a flat monthly cost, with your keys staying on your machine.
| Terminal | Platform fee | Fees, last 30d | Where it stands |
|---|---|---|---|
| Axiom | 0.75% to 0.95%, volume-tiered | $38.7M | 54% of category fees; the desktop default |
| Fomo | ~0.5%, $0.95 minimum | $15.2M | Mobile and social; fees up 6.5x since June |
| Terminal | 0.5% to 1%, volume-tiered | $5.1M | The former Padre, acquired by pump.fun in October 2025 |
| GMGN | flat 1% | $4.7M | Solana is ~15% of its business now; the rest moved to BSC and Robinhood Chain |
| Trojan | 1%, 0.9% with referral | $1.2M | The last Telegram bot of real scale |
| Photon | flat 1%, both sides | $0.8M | Peaked at $84.6M in January 2025; down ~99% from that |
| Bloom | ~0.9% effective | $0.4M | Niche: snipes contract addresses out of X posts |
Fee percentages are from each platform's published docs or a primary review; 30-day fee totals are DefiLlama's Solana series as of 2026-08-30. pump.fun's own mobile app is not in the table because nobody calls it a terminal. It earned a separate $5.1M over the same 30 days, level with the Terminal row and more than everything below it.
Two names are missing because they no longer exist in any meaningful form. BullX suspended trading on 2026-06-01 after collecting about $203M in lifetime Solana fees from users farming an airdrop that never shipped; the site now resolves to a wallet manager so people can withdraw. Vector shut down after Coinbase announced its acquisition; the old domain redirects to a key-recovery page.
In February 2026, ZachXBT published findings, covered by Forbes, that an Axiom employee had spent months using an internal support dashboard to watch user wallets, including transaction histories and linked accounts. No keys were exposed and no funds were reported taken; the hole was access control, one employee reading user books for the better part of a year before anyone outside noticed.
Which is a reason to ask how much of this stack you could run yourself, with nobody's dashboard behind it.
Strip the branding off any of the products above and you find the same five components:
The first three are commodities now. The fourth is why a handful of terminals exist rather than hundreds: keeping it correct is expensive. The fifth is where a self-built terminal can pass the commercial ones, because they barely compete on it.
TradingView Lightweight Charts is TradingView's own open-source charting library: Apache 2.0, about 35KB, six series types. Two things to know before you copy an older tutorial. The v5 API renamed series creation, so chart.addCandlestickSeries() is gone and you now call chart.addSeries(CandlestickSeries) with the series type imported from the package. And the library is client-side only, which in a Next.js app means a 'use client' component with no server rendering.
The license asks you to keep the TradingView attribution link; the built-in attributionLogo option satisfies it. If you want built-in indicators and drawing tools instead of writing your own, KLineChart ships dozens of both at a similar size. The full TradingView Charting Library, the one with 100+ indicators that the big terminals license, is available to companies only; TradingView states plainly that it is not offered for personal use.
Jupiter's Swap V2 is the current swap surface, and the naming matters because most tutorials still point at the old quote-api.jup.ag/v6 endpoints. The current flow is GET https://api.jup.ag/swap/v2/order, sign the returned transaction, POST https://api.jup.ag/swap/v2/execute, with routing, slippage estimation, priority fees, and transaction landing handled on Jupiter's side. A free API key gets you 1 request per second, which is enough for a one-person terminal. If you want raw swap instructions to compose and send yourself, the /swap/v2/build path hands them over without a Jupiter platform fee. The only costs left are the DEX fee and the network.
For tokens still on the pump.fun bonding curve, PumpPortal's local transaction API is the third-party lane most builders use: it returns an unsigned serialized transaction for 0.5% per trade, and you sign with your own keypair and broadcast it yourself. Be precise about what that buys. Your read traffic still goes to providers, since Conyr sees which mints you subscribe to and Jupiter sees your quotes, but the keypair sits on your machine, so there is no platform between you and your funds and no internal dashboard anywhere with your trading history attached to an account.
Landing transactions under congestion means a priority fee plus, usually, a Jito tip. Do not hardcode the tip. Jito publishes a live tip floor with percentile bands; poll it and pick a percentile that matches how contested your fill is. The only hard number is the 1,000 lamport minimum for bundles. Everything above that is an auction.
Solana's public RPC endpoints allow 100 requests per 10 seconds per IP, and the docs say outright that they are not intended for production applications. Standard WebSocket subscriptions drop messages under load and offer no backfill on reconnect. The tier above that is the validator's Geyser stream, resold by RPC providers from about $49 a month (Chainstack) to $499 (Helius LaserStream), and it is what every serious terminal runs on.
But a raw stream hands you bytes, not trades. Between the firehose and a candle on your chart there is a layer nobody advertises: a decoder for every DEX program you care about, updated every time one of them ships an instruction change; router handling, because a Jupiter or aggregator swap is several legs and counting them naively produces phantom volume and price spikes that never traded; flash-swap and arbitrage filtering, so one wallet's atomic in-and-out does not print as two real trades; and backpressure, because the stream does not slow down for you. Most teams discover a broken decoder in a post-mortem, not a test run.
This is the layer Conyr runs. A self-hosted Yellowstone gRPC node parses 25 DEX programs in real time and computes candles, positions, and PnL as the chain moves; the results come out as plain REST and WebSocket (tiers and pricing). Layer 1 at $29/mo covers what a terminal's market-data pane needs: OHLCV, the trade tape, token info, holders, and the live streams. The decoder treadmill stays upstream of you.
Backfill first. The endpoint is GET /v1/token/{mint}/ohlcv, on Layer 1 ($29/mo, full tier matrix on the API page):
curl -H "Authorization: Bearer $CONYR_API_KEY" \
"https://api.conyr.ai/v1/token/{mint}/ohlcv?timeframe=1m&view=filtered&limit=500"
{
"candles": [{
"bucket": "2026-08-31 14:30:00+00",
"open": 0.00241, "high": 0.00252, "low": 0.00239, "close": 0.00247,
"volume_token": 1882344.0, "volume_usd": 4571.2,
"num_trades": 63, "buy_volume": 1074800.0, "sell_volume": 807544.0,
"vwap": 0.00244
}],
"token_mint": "...",
"timeframe": "1m",
"view": "filtered"
}
Reading it, top to bottom: candles arrive newest first, so reverse before charting; bucket is a Postgres timestamp string, not epoch and not ISO with a T; buy_volume and sell_volume are token units that sum to roughly volume_token, not USD; and vwap can be null, so guard it. The view=filtered variant strips flash swaps and arbitrage legs, view=full keeps everything, and the gap between the two on a washed token is itself a signal. Responses are cached for 5 seconds. Use the socket for the live candle; a REST poll will lag it.
That is everything the chart needs, so hand it to Lightweight Charts:
import { createChart, CandlestickSeries } from "lightweight-charts";
const chart = createChart(document.getElementById("chart"));
const series = chart.addSeries(CandlestickSeries);
const toTime = (bucket) =>
Date.parse(bucket.replace(" ", "T").replace(/([+-]\d{2})$/, "$1:00")) / 1000;
// fetched via your own server route, which adds the Bearer key and forwards
const { candles } = await fetch(`/api/ohlcv/${mint}`).then((r) => r.json());
series.setData(
candles.reverse().map((c) => ({
time: toTime(c.bucket),
open: c.open, high: c.high, low: c.low, close: c.close,
}))
);
The socket is wss://api.conyr.ai/ws, same Layer 1 key, Bearer on the upgrade header:
import WebSocket from "ws";
const ws = new WebSocket("wss://api.conyr.ai/ws", {
headers: { Authorization: `Bearer ${process.env.CONYR_API_KEY}` },
});
ws.on("open", () => {
ws.send(JSON.stringify({
op: "subscribe",
channels: [`token:${mint}:ticks`, `token:${mint}:trades`],
}));
});
let last; // most recent candle on the chart
ws.on("message", (raw) => {
const msg = JSON.parse(raw);
if (!msg.channel) return; // subscribe acks, pongs, errors
if (!msg.channel.endsWith(":ticks")) return;
const t = msg.data; // { b, p, vu, vt, s, f, ... }
if (!t.f) return; // stay consistent with the filtered view
const time = Math.floor(t.b / 60000) * 60; // floor the 1s bucket to the 1m chart
last = last && last.time === time
? { ...last, high: Math.max(last.high, t.p), low: Math.min(last.low, t.p), close: t.p }
: { time, open: t.p, high: t.p, low: t.p, close: t.p };
sendToChart(last); // relay to the browser, where the chart code calls series.update(last)
});
A browser's WebSocket constructor cannot set headers, and the socket accepts header auth only, so this half lives in your Node process or a small server-side proxy: Node owns the Bearer key and the candle merge, the browser owns the chart, and sendToChart is whatever channel connects the two (a plain local socket works). The REST calls belong behind the same proxy, which is why the chart snippet fetches /api/ohlcv/... instead of carrying an API key in client code.
Each tick carries a canonical one-second bucket start (b, unix ms), a USD unit price (p), USD and token volume (vu, vt), a side flag (s: 1 buy, -1 sell, 0 neutral), and the organic flag f, which is false for flash and arbitrage legs. Floor b to your chart's timeframe before merging, as above; the live candle then lands on exactly the bucket the historical endpoint reports, and a refresh never redraws the chart differently. If you would rather not merge ticks at all, subscribe to token:{mint}:ohlcv:filtered:1m instead and finished candle updates arrive ready to draw.
One sequencing note: a fresh socket receives live messages only, with no replay of recent history. Paint the trade panel from GET /v1/token/{mint}/trades first, then let the trades channel take over.
That is the market-data core of Photon on your own screen: chart, live tape, and a buy button wired to Jupiter, with the hard layer underneath rented for $29/mo.
Every terminal in the table above shows the same chart of the same pool. Almost none of them will tell you who is on the other side of it.
Two reads are on the free tier. GET /v1/token/{mint}/buyer-quality classifies the last hour of buyers by the historical behavior of each wallet:
curl -H "Authorization: Bearer $CONYR_API_KEY" \
"https://api.conyr.ai/v1/token/{mint}/buyer-quality"
{
"token_mint": "...",
"window_hours": 1,
"total_buyers": 87,
"quality_tiers": [
{ "tier": "elite_smart_money", "count": 5, "pct_of_buyers": 5.7,
"total_volume_usd": 8200.0, "avg_win_rate": 0.61, "notable_wallets": ["..."] },
{ "tier": "farm_funded", "count": 22, "pct_of_buyers": 25.3,
"total_volume_usd": 1200.0, "avg_win_rate": null, "notable_wallets": [] }
],
"smart_money_signal": 0.42,
"volume_decomposition": {
"organic_profitable_usd": 12500.0, "organic_other_usd": 7900.0,
"bot_usd": 5400.0, "farm_funded_usd": 1200.0, "unrated_usd": 3100.0
}
}
Reading the fields: quality_tiers is an array, avg_win_rate is null when a tier has no rated wallets, and smart_money_signal is a 0-to-1 fraction of volume from profitable organic wallets. A quarter of buyers arriving farm-funded is the kind of fact that changes an entry, and no candle shows it. Responses are cached for 30 seconds. Its free sibling, GET /v1/token/{mint}/bundles/summary, reports whether coordinated clusters hold the float; bundled_supply_pct comes back already as a percent, 0 to 100, so render it directly. What those clusters do to a token afterward is documented, wallet by wallet, in the extraction-crew teardown.
On Layer 2, GET /v1/wallet/{address}/labels turns each row of your trade tape into a profile: trader type, performance level, size class, behavioral badges, an automation label with the wallet's dominant terminal, and a farm-funding flag. The full funding chain is GET /v1/wallet/{address}/provenance on the same tier, traced hop by hop toward known origins. Two things to handle if you render a chip per tape row. The endpoint returns literal null for a wallet with no behavioral snapshot, so guard before reading fields. And there is no batch route, so dedupe addresses and cache locally against the 60-second response TTL and Layer 2's 300 requests per minute. Wire that up and your tape shows an overlay almost none of the terminals in the table ship today. The wallet:{address}:labels channel streams changes on the socket you already have open.
| Piece | Cost | What it buys |
|---|---|---|
| Chart | $0 | Lightweight Charts, Apache 2.0 |
| Execution | Network fees and tips per fill; 0.5% on the PumpPortal curve lane | Jupiter free key, 1 request per second |
| Market data + live tape | $29/mo | The decoder layer you are not maintaining |
| Wallet intelligence | $75/mo, includes Layer 1 | The chip on every tape row |
| Hosting | ~$0 | Wherever your Next.js apps already live |
Against the incumbent model: ten round trips a day at a $500 average clip is about $100 a day in platform fees at a flat 1% each way, roughly $3,000 a month. The self-built version pays $29 to $75 flat plus the network costs you were paying anyway, and 0.5% per trade only if you route curve tokens through PumpPortal. One month of those fees covers about eight years of Layer 1.
You give up the sniping toolkits, the mobile app, someone to blame when a transaction fails, and limit orders unless you wire up Jupiter's Trigger API yourself. Keys on your own machine mean key hygiene is on you: a burner wallet holding only working capital, nothing in git, nothing in logs. And GitHub is full of "Solana trading bot" repos that exist to drain the wallets of people who run them unread; if a repo touches a Keypair, read every line before it touches yours.
If you would rather have an agent do the assembling, the same intelligence ships over the hosted MCP server: 40+ tools over the same keys and tiers, so a Claude or Cursor session can pull labels, buyer quality, and bundle state without the glue code above. Market data stays on REST and WebSocket; MCP starts at the intelligence layer.
Run these reads against the live chain.
To find a Solana wallet worth copy trading, filter the leaderboard's lotto wins and bots using behavioral labels: trader type, badges, and bot share.
A temporal study of closed-FIFO-lot win rate, exit-row rate, and subsequent cumulative gross realized profitability across 194,610 eligible Solana wallets.
A forensic, fully-sourced reconstruction of one Solana pump-and-dump operation — every wallet, transaction, and SOL flow, verifiable on-chain.