One line.
No key.
nyctal tells you about every new pump.fun coin and every trade before its block is confirmed. Over plain HTTP.
curl "https://nyctal.xyz/v1/mints/latest?limit=1"
const res = await fetch("https://nyctal.xyz/v1/mints/latest?limit=1");
const { data } = await res.json();
console.log(data[0].symbol, data[0].mint);
import requests
data = requests.get("https://nyctal.xyz/v1/mints/latest?limit=1").json()["data"]
print(data[0]["symbol"], data[0]["mint"])
claude mcp add --transport http nyctal https://nyctal.xyz/mcp
How a coin is heard
A block does not arrive whole. It crosses the network as shreds, then it is replayed, then validators vote on it. That takes a few hundred milliseconds. During that time the block is in the air, and most tools can't see it yet.
nyctal reads the pump program at processed commitment. A coin is heard the moment the transaction is replayed. When its slot gets optimistic confirmation, the same record turns confirmed, and lead_ms tells you how long you had it. It is measured on every event, on one clock. It is not a marketing number.
Below is the real life of the last slots. Each row is one block.
first shred ──► block complete ──► bank frozen ──► confirmed ──► rooted
│ │
│ shreds_ms replay_ms vote_ms │
└─────────────── a block in the air ─────────┘
▲ ▲
│ │
nyctal hears the tx a confirmed RPC
state: heard state: confirmed
└───────── lead_ms ───────────────┘
Live from /v1/slots/latest. Light: shreds arriving. Mid: replay. Ice: votes.
States
Every coin and trade has a state. It only moves forward.
| State | Means | What to do |
|---|---|---|
heard | The transaction was replayed by a node. No votes yet. | Act, if you accept the risk of a fork. |
confirmed | Its slot has optimistic confirmation. lead_ms is set. | Treat as real. |
dropped | The slot died or fell off the fork. | Take it back. It may land again in a later slot. |
{
"symbol": "HALH",
"state": "confirmed",
"lead_ms": 338,
"heard_at": "2026-11-05T16:46:33.443Z",
"confirmed_at": "2026-11-05T16:46:33.781Z",
"slot": 465777971
}
Recipes
Copy, paste, run. Each one works as it is, against the live API.
Alert on every coin with a big dev buy
The stream sends each coin at processed. Filter in your own code. You see the dev buy before the block is confirmed.
const es = new EventSource("https://nyctal.xyz/v1/stream?types=mint");
es.addEventListener("mint", (e) => {
const coin = JSON.parse(e.data);
if (coin.dev_buy_sol >= 1) {
console.log(coin.symbol, coin.dev_buy_sol, "SOL", coin.mint);
}
});
import json, requests
url = "https://nyctal.xyz/v1/stream?types=mint"
with requests.get(url, stream=True) as r:
for line in r.iter_lines():
if line.startswith(b"data: "):
coin = json.loads(line[6:])
if coin["dev_buy_sol"] >= 1:
print(coin["symbol"], coin["dev_buy_sol"], coin["mint"])
Check a coin for a bundle before you enter
One request. flags.bundled is true when three or more non-dev wallets bought in the create slot. top_early_buyer_pct shows how much of the early money is one wallet.
curl -s "https://nyctal.xyz/v1/mints/{mint}" | jq '.data.flags'
const { data } = await (await fetch("https://nyctal.xyz/v1/mints/{mint}")).json();
const { bundled, dev_sold, top_early_buyer_pct } = data.flags;
if (bundled || dev_sold || top_early_buyer_pct > 60) console.log("skip", data.symbol);
Follow one wallet in real time
Pass wallet to the stream. You get its trades one by one, each with the coin, the size and the curve after the trade.
curl -N "https://nyctal.xyz/v1/stream?types=trade&wallet={wallet}"
const es = new EventSource("https://nyctal.xyz/v1/stream?types=trade&wallet={wallet}");
es.addEventListener("trade", (e) => {
const t = JSON.parse(e.data);
console.log(t.side, t.sol, "SOL", t.symbol, "curve", t.curve_pct);
});
Telegram alert when a curve passes 90%
Poll the near-full list every few seconds and send what is new. Fifteen lines of shell.
seen=""
while true; do
curl -s "https://nyctal.xyz/v1/curves/near-full?min_pct=90" \
| jq -r '.data[] | "\(.mint) \(.symbol) \(.curve_pct)"' \
| while read -r mint sym pct; do
case "$seen" in *"$mint"*) continue;; esac
seen="$seen $mint"
curl -s "https://api.telegram.org/bot$TOKEN/sendMessage" \
-d chat_id="$CHAT" -d text="$sym at $pct%"
done
sleep 5
done
A whale tape in the terminal
Only trades over 5 SOL, as they are heard. This is a good first line to leave open on a second screen.
curl -sN "https://nyctal.xyz/v1/stream?types=trade&min_sol=5" \
| grep --line-buffered '^data' \
| cut -c7- | jq -c '{symbol, side, sol, curve_pct}'
Time a send by the next leader
The schedule tells you who builds the next blocks. in_ms is slots away times 400 ms. Send to the leader that comes up next.
curl -s "https://nyctal.xyz/v1/leaders/next?limit=4" | jq '.data[] | {leader, in_ms}'
Let an AI agent watch it for you
Add the MCP server, then talk. The agent picks the tools by itself.
Look at the last 20 coins on pump.fun with nyctal.
Find the ones with a dev buy over 0.5 SOL and no bundle.
For each one, show the first buyers and tell me if the dev sold.
Mints
Every new pump.fun coin, the moment the transaction is replayed.
GET /v1/mints/latest
New coins, newest first
Each row starts as heard. When its slot gets optimistic confirmation it turns confirmed and lead_ms is filled in: how long you had the coin before a confirmed RPC did.
| Name | In | Type | What it does |
|---|---|---|---|
limit | query | integer | Rows to return, 1 to 100. |
min_dev_sol | query | number | Only coins where the creator bought at least this much in the create slot. |
state | query | heard | confirmed | dropped | Only rows in this state. |
q | query | string | Search in symbol and name. |
curl "https://nyctal.xyz/v1/mints/latest?limit=3"
const res = await fetch("https://nyctal.xyz/v1/mints/latest?limit=3");
const { data } = await res.json();
console.log(data);
import requests
data = requests.get("https://nyctal.xyz/v1/mints/latest?limit=3").json()["data"]
print(data)
Tool: latest_mints
Ask your agent:
"new coins, newest first, use nyctal"
GET /v1/mints/{mint}
One coin: curve, first buyers, flags
Everything nyctal heard about a coin since its create: curve position, the first 24 buyers with their slot offset, the dev buy, the dev sell, and bundle flags. Coins are kept while they are in the last 6,000 mints.
| Name | In | Type | What it does |
|---|---|---|---|
mint required | path | string | Mint address. |
curl "https://nyctal.xyz/v1/mints/{mint}"
const res = await fetch("https://nyctal.xyz/v1/mints/{mint}");
const { data } = await res.json();
console.log(data);
import requests
data = requests.get("https://nyctal.xyz/v1/mints/{mint}").json()["data"]
print(data)
Tool: mint_snapshot
Ask your agent:
"one coin: curve, first buyers, flags, use nyctal"
GET /v1/mints/{mint}/trades
Trades of one coin
Newest first. Works for any mint that traded recently, not only for coins created while the ear was on.
| Name | In | Type | What it does |
|---|---|---|---|
mint required | path | string | Mint address. |
limit | query | integer | Rows to return, 1 to 200. |
curl "https://nyctal.xyz/v1/mints/{mint}/trades?limit=3"
const res = await fetch("https://nyctal.xyz/v1/mints/{mint}/trades?limit=3");
const { data } = await res.json();
console.log(data);
import requests
data = requests.get("https://nyctal.xyz/v1/mints/{mint}/trades?limit=3").json()["data"]
print(data)
Tool: mint_trades
Ask your agent:
"trades of one coin, use nyctal"
Trades
Every bonding curve buy and sell, with the wallet and the curve after it.
GET /v1/trades/latest
The tape, newest first
All pump.fun curve trades. Filter by side, size, coin or wallet. slot_offset is the number of slots since the coin was created, so 0 means same slot as the create.
| Name | In | Type | What it does |
|---|---|---|---|
limit | query | integer | Rows to return, 1 to 200. |
side | query | buy | sell | buy or sell. |
min_sol | query | number | Smallest trade to include, in SOL. |
mint | query | string | Only this coin. |
wallet | query | string | Only this wallet. |
state | query | heard | confirmed | dropped | Only rows in this state. |
curl "https://nyctal.xyz/v1/trades/latest?limit=3&min_sol=1"
const res = await fetch("https://nyctal.xyz/v1/trades/latest?limit=3&min_sol=1");
const { data } = await res.json();
console.log(data);
import requests
data = requests.get("https://nyctal.xyz/v1/trades/latest?limit=3&min_sol=1").json()["data"]
print(data)
Tool: latest_trades
Ask your agent:
"the tape, newest first, use nyctal"
Curves
Curves on the way to full. This is where migration starts.
GET /v1/curves/near-full
Curves about to fill
Curves with a trade in the last 30 minutes, closest to 100% first. A pump.fun curve migrates when it is full. climbed_pct is how far it moved since nyctal first heard it.
| Name | In | Type | What it does |
|---|---|---|---|
limit | query | integer | Rows to return, 1 to 100. |
min_pct | query | number | Lowest curve position, in percent. |
curl "https://nyctal.xyz/v1/curves/near-full?limit=3"
const res = await fetch("https://nyctal.xyz/v1/curves/near-full?limit=3");
const { data } = await res.json();
console.log(data);
import requests
data = requests.get("https://nyctal.xyz/v1/curves/near-full?limit=3").json()["data"]
print(data)
Tool: near_full_curves
Ask your agent:
"curves about to fill, use nyctal"
Wallets
What a wallet did on pump.fun while the ear was listening.
GET /v1/wallets/{address}
One wallet on pump.fun
Buys, sells, SOL in and out, coins touched, coins created and the last 20 trades. Counts start when the ear woke up, so this is a recent window and not a lifetime.
| Name | In | Type | What it does |
|---|---|---|---|
address required | path | string | Wallet address. |
curl "https://nyctal.xyz/v1/wallets/{address}"
const res = await fetch("https://nyctal.xyz/v1/wallets/{address}");
const { data } = await res.json();
console.log(data);
import requests
data = requests.get("https://nyctal.xyz/v1/wallets/{address}").json()["data"]
print(data)
Tool: wallet_activity
Ask your agent:
"one wallet on pump.fun, use nyctal"
Wire
Slots, shreds, leaders and pressure. The layer under the coins.
GET /v1/status
Is it listening, and how far ahead
The measured lead over confirmation (p50, p90, p99), the chain tip, the pressure on the pump program and the totals since start.
curl "https://nyctal.xyz/v1/status"
const res = await fetch("https://nyctal.xyz/v1/status");
const { data } = await res.json();
console.log(data);
import requests
data = requests.get("https://nyctal.xyz/v1/status").json()["data"]
print(data)
Tool: status
Ask your agent:
"is it listening, and how far ahead, use nyctal"
GET /v1/pressure
How crowded pump.fun is right now
Transactions per second that touch the pump program over the last minute, and how many of them failed. Most of them fail: that is the fight for the first slots.
curl "https://nyctal.xyz/v1/pressure"
const res = await fetch("https://nyctal.xyz/v1/pressure");
const { data } = await res.json();
console.log(data);
import requests
data = requests.get("https://nyctal.xyz/v1/pressure").json()["data"]
print(data)
Tool: pressure
Ask your agent:
"how crowded pump.fun is right now, use nyctal"
GET /v1/slots/latest
The life of the last slots
Measured on a Solana node: first shred received, block complete, bank frozen, optimistic confirmation, root. This is the time a block spends in the air.
| Name | In | Type | What it does |
|---|---|---|---|
limit | query | integer | Rows to return, 1 to 100. |
curl "https://nyctal.xyz/v1/slots/latest?limit=3"
const res = await fetch("https://nyctal.xyz/v1/slots/latest?limit=3");
const { data } = await res.json();
console.log(data);
import requests
data = requests.get("https://nyctal.xyz/v1/slots/latest?limit=3").json()["data"]
print(data)
Tool: latest_slots
Ask your agent:
"the life of the last slots, use nyctal"
GET /v1/slots/{slot}
One slot
The same record for one slot. The last 4,000 slots are kept, about 27 minutes.
| Name | In | Type | What it does |
|---|---|---|---|
slot required | path | integer | Slot number. |
curl "https://nyctal.xyz/v1/slots/{slot}"
const res = await fetch("https://nyctal.xyz/v1/slots/{slot}");
const { data } = await res.json();
console.log(data);
import requests
data = requests.get("https://nyctal.xyz/v1/slots/{slot}").json()["data"]
print(data)
Tool: slot
Ask your agent:
"one slot, use nyctal"
GET /v1/slots/{slot}/shreds
Shred map of a slot (modeled)
A modeled layout of the slot: how its data would be cut into FEC sets of 32 data and 32 coding shreds and spread through Turbine, timed against the real first shred and complete marks. modeled is always true: nyctal does not read raw shreds on this tier.
| Name | In | Type | What it does |
|---|---|---|---|
slot required | path | integer | Slot number. |
curl "https://nyctal.xyz/v1/slots/{slot}/shreds"
const res = await fetch("https://nyctal.xyz/v1/slots/{slot}/shreds");
const { data } = await res.json();
console.log(data);
import requests
data = requests.get("https://nyctal.xyz/v1/slots/{slot}/shreds").json()["data"]
print(data)
GET /v1/leaders/next
Who builds the next blocks
The leader schedule from the tip forward. Know who gets your transaction before you send it.
| Name | In | Type | What it does |
|---|---|---|---|
limit | query | integer | Rows to return, 1 to 32. |
curl "https://nyctal.xyz/v1/leaders/next?limit=3"
const res = await fetch("https://nyctal.xyz/v1/leaders/next?limit=3");
const { data } = await res.json();
console.log(data);
import requests
data = requests.get("https://nyctal.xyz/v1/leaders/next?limit=3").json()["data"]
print(data)
Tool: next_leaders
Ask your agent:
"who builds the next blocks, use nyctal"
Stream
One open HTTP connection. Events as they are heard.
GET /v1/stream
Live stream (Server-Sent Events)
A plain HTTP connection that never ends. Open it with curl -N or EventSource. Event names: mint, trade, confirm, drop, slot, hello. A comment line is sent every 15 seconds. Swagger waits for a response to finish, so it cannot show this one: press Run in the docs, or use curl.
| Name | In | Type | What it does |
|---|---|---|---|
types | query | string | Comma list of event names. |
mint | query | string | Only events of this coin. |
wallet | query | string | Only trades of this wallet. |
min_sol | query | number | Only trades at least this big. |
curl -N "https://nyctal.xyz/v1/stream?types=mint,confirm"
const es = new EventSource("https://nyctal.xyz/v1/stream?types=mint,confirm");
es.addEventListener("mint", (e) => console.log(JSON.parse(e.data)));
es.addEventListener("confirm", (e) => console.log(JSON.parse(e.data)));
import json, requests
with requests.get("https://nyctal.xyz/v1/stream?types=mint,confirm", stream=True) as r:
for line in r.iter_lines():
if line.startswith(b"data: "):
print(json.loads(line[6:]))
Streaming, live
This box is a real open connection to /v1/stream. Coins appear as heard, and a confirm line follows with the measured lead.
| Event | When |
|---|---|
hello | Right after you connect. |
mint | A new coin, state heard. |
trade | A curve trade. Pick it with types=trade. |
confirm | A slot confirmed: event count, mean lead, coins inside. |
drop | A slot died. Its events are taken back. |
slot | Slot stages: first shred, completed, confirmed. |
If the connection breaks, reconnect. EventSource does this by itself. One address can hold 6 streams.
MCP for agents
nyctal is also an MCP server. Add one line and Claude, Cursor or any MCP client can ask about pump.fun in plain words. Same data, same limits, no key.
| Tool | What it does |
|---|---|
latest_mints | New coins, newest first |
mint_snapshot | One coin: curve, first buyers, flags |
mint_trades | Trades of one coin |
latest_trades | The tape, newest first |
near_full_curves | Curves about to fill |
wallet_activity | One wallet on pump.fun |
status | Is it listening, and how far ahead |
pressure | How crowded pump.fun is right now |
latest_slots | The life of the last slots |
slot | One slot |
next_leaders | Who builds the next blocks |
claude mcp add --transport http nyctal https://nyctal.xyz/mcp
// .cursor/mcp.json
{
"mcpServers": {
"nyctal": { "url": "https://nyctal.xyz/mcp" }
}
}
curl -s https://nyctal.xyz/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"status","arguments":{}}}'
Limits and errors
| Limit | Value |
|---|---|
| Requests | 300 a minute per address |
| Open streams | 6 per address |
| Coins kept | Last 6,000 |
| Trades kept | Last 30,000 |
| Slots kept | Last 4,000, about 27 minutes |
| Wallets kept | Last 120,000 seen |
| Code | Error | Fix |
|---|---|---|
| 400 | bad_address | Check the base58 address. |
| 404 | not_heard | It is older than the window. Take a fresh one from a latest list. |
| 429 | rate_limited | Wait for Retry-After seconds, or use the stream. |
| 429 | too_many_streams | Close a stream. One stream with filters beats six. |
{
"error": {
"code": "not_heard",
"message": "This coin is not in memory. Take a fresh one from /v1/mints/latest."
}
}
What is real
Everything on this site comes from Solana mainnet, right now, unless it says otherwise.
| Data | Source |
|---|---|
| Coins, trades, curves, wallets, flags | Real. pump program logs at processed. |
lead_ms, slot timings | Real. Measured on a live node. |
| Leaders | Real. The on-chain schedule. |
/v1/slots/{slot}/shreds | Modeled. Marked modeled: true. |
The open API reads at processed commitment, through an RPC node. It does not read raw shreds. That is the private lane.
The private lane
This part is not open yet. It is what comes next: nyctal's own place on the wire, under processed. Connect a wallet on the main page to be heard first.
The wire
Turbine cuts every slot into shreds and fans them down a tree of validators, layer by layer, so no node carries a whole block alone. Where you sit in that tree decides when a piece reaches you. The private lane sits in many places at once, keeps the first copy of each shred and drops the rest by signature. It never asks for a block. It waits for pieces and puts them back together itself.
Shred anatomy
A shred is a header, a payload and the leader's signature. Data shreds carry slices of entries. Coding shreds carry parity, so lost data can be rebuilt. Each one names its slot, its index, its FEC set and a Merkle path back to a root the leader signed once. A bad path or a bad signature, and the shred is dropped where it lands.
Erasure, recovered
Shreds ride UDP and some never arrive. Each FEC set carries enough coding shreds to rebuild missing data shreds with Reed-Solomon. The ear does not wait for a full set. As soon as it holds enough pieces, the gaps are filled and the set moves on.
Deshred
Rebuilt data shreds are stitched back together in index order into entries: a hash, a count of hashes since the last entry, and a list of transactions. Inside a slot, order is the order of entries, then of transactions in each entry. That order is fixed the moment it is heard, before any vote.
Silence
When the wire goes quiet, the ear says so. A gap in a slot, a leader that sends nothing, a set that will not rebuild: each one is an event too. Silence is reported. It is never filled in.
slot 465,778,159 ├─ fec 0 ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ data 32 □□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□ coding 32 ├─ fec 1 ■■■■■■■■■■■■■■■■■■■■■■·■■■■■■■■■ recovered □□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□ ├─ fec 2 ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ □□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□ └─ merkle root, signed once by the leader
Layout from /v1/slots/{slot}/shreds, modeled.
Words
- Shred
- A small signed piece of a block.
- Turbine
- How shreds spread from the leader through validators.
- Processed
- A node replayed the transaction. No votes yet.
- Confirmed
- Most stake voted for the slot. Almost final.
- Rooted
- Final.
- Lead
- Time between heard and confirmed.
- Curve
- The pump.fun bonding curve of a coin. At 100% it migrates.
- Bundle
- Several wallets buying in the create slot, usually one person.
FAQ
Do I need a key?
No. No signup either.
Is it free?
The open API is free for now.
Can a heard event be wrong?
Yes. That is the cost of being early. If a slot falls off, the event turns dropped and the stream says so.
Does nyctal trade for me?
No. It hears and repeats. What you do with it is yours.
Why do most transactions fail?
Bots fight for the first slots and lose on slippage. /v1/pressure shows the share live.
Can I use it in production?
Use it to build. Keep your own fallback. The private lane is made for production.