# shrine.trade
> API-first Solana DEX
---
# Local Trade API
> The simplest way to trade Solana memecoins through an API. Your wallet stays on your computer - we never see your private key.
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# Local Trade API
The non-custodial way to trade Solana memecoins through an API. Your wallet stays on your computer - **we never see your private key.** You submit your trade through your own RPC.
## What you need
- **A Solana wallet** - its private key in base58. (Phantom: Settings → Show Secret Recovery Phrase → Show Private Key.)
- **Some SOL** in that wallet - enough for the trade plus a small amount of network fees.
- **A Solana RPC endpoint** to actually submit the trade. The public one (`https://api.mainnet-beta.solana.com`) works for testing but gets rate-limited; for real use grab a free or paid RPC from Helius, Triton, QuickNode, or similar.
- **Node.js or Python** installed if you want to run the snippet below as-is.
## Example
```js
import { Connection, Keypair, VersionedTransaction } from "@solana/web3.js";
import bs58 from "bs58";
const conn = new Connection("https://api.mainnet-beta.solana.com"); // your own RPC
const wallet = Keypair.fromSecretKey(bs58.decode(process.env.WALLET_SECRET));
// 1. Ask pumpdata to build an unsigned transaction.
const res = await fetch("https://api.pumpdata.fun/api/local-trade", {
method: "POST",
headers: { "content-type": "application/json", "accept": "application/json" },
body: JSON.stringify({
action: "buy", // "buy" or "sell"
publicKey: wallet.publicKey.toBase58(),
mint: "", // base58 token mint
amount: 0.01, // SOL when amountInSol=true, tokens otherwise
amountInSol: true, // true → amount is SOL; false → tokens
slippage: 10, // percent
priorityFee: 0.0001, // SOL
pool: "pump", // dex selector
}),
});
const { tx } = await res.json();
// 2. Sign locally - your private key never leaves this script.
const txObj = VersionedTransaction.deserialize(Buffer.from(tx, "base64"));
txObj.sign([wallet]);
// 3. Submit via your own RPC.
const sig = await conn.sendRawTransaction(txObj.serialize());
console.log("https://solscan.io/tx/" + sig);
```
```python
import base64, requests, base58
from solana.rpc.api import Client
from solders.keypair import Keypair
from solders.transaction import VersionedTransaction
client = Client("https://api.mainnet-beta.solana.com") # your own RPC
wallet = Keypair.from_bytes(base58.b58decode(WALLET_SECRET))
# 1. Ask pumpdata to build an unsigned transaction.
res = requests.post(
"https://api.pumpdata.fun/api/local-trade",
headers={"accept": "application/json"},
json={
"action": "buy", # "buy" or "sell"
"publicKey": str(wallet.pubkey()),
"mint": "", # base58 token mint
"amount": 0.01, # SOL when amountInSol=True, tokens otherwise
"amountInSol": True, # True → amount is SOL; False → tokens
"slippage": 10, # percent
"priorityFee": 0.0001, # SOL
"pool": "pump", # dex selector
},
)
tx_b64 = res.json()["tx"]
# 2. Sign locally - your private key never leaves this script.
raw = VersionedTransaction.from_bytes(base64.b64decode(tx_b64))
signed = VersionedTransaction(raw.message, [wallet])
# 3. Submit via your own RPC.
sig = client.send_raw_transaction(bytes(signed)).value
print(f"https://solscan.io/tx/{sig}")
```
## How it works
1. **You ask** pumpdata for a trade. We build an unsigned transaction.
2. **You sign** the transaction on your machine, with your own wallet.
3. **You submit** the signed transaction through your own RPC.
## If something goes wrong
You'll get a response like `{ "error": "..." }` with a short message. Usual causes: a wrong wallet address, a wrong token mint, or not enough SOL.
If you want pumpdata to sign and submit on your behalf, see the **Lightning Transactions** page - that's the managed-wallet flow with one-click execution.
---
# Account
import AccountPage from '@site/src/components/AccountPage';
---
# GET /metadata
> Pool + token metadata - name, symbol, decimals, supply, program, the active pool address.
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import TryIt from '@site/src/components/TryIt';
# `GET /metadata`
Pool + token metadata - name, symbol, decimals, supply, program, quote mint, the live pool address. Pass exactly one of `mint` or `pool` (passing both is fine; `pool` wins). Auth via the `x-api-key` header or an `?api_key=` query string.
| | |
|---|---|
| **Method** | `GET` |
| **Auth** | `x-api-key` header, or `?api_key=` query string |
## Example
```js
const res = await fetch(
"https://api.pumpdata.fun/metadata?mint=Gc6rNxGnoQt6vCfhNzn5iJnPBu1V38GZfdYERfnXpump",
{ headers: { "x-api-key": "pd_xxxxxxxx…" } },
);
const meta = await res.json();
console.log(meta);
```
```python
import requests
res = requests.get(
"https://api.pumpdata.fun/metadata",
params={"mint": "Gc6rNxGnoQt6vCfhNzn5iJnPBu1V38GZfdYERfnXpump"},
headers={"x-api-key": "pd_xxxxxxxx…"},
)
print(res.json())
```
## Response
```json
{
"pool": "4mBLRPUyfE7CvxmXiGJx5WA51isanbxpdbdPjFuuBzmY",
"mint": "Gc6rNxGnoQt6vCfhNzn5iJnPBu1V38GZfdYERfnXpump",
"quote": "So11111111111111111111111111111111111111112",
"decimals": 6,
"program": "PUMPSWAP",
"total_supply": 1000000000,
"active": true,
"name": "Murio Newful",
"symbol": "MURIO",
"uri": "https://meta.lqsgqxmvlk.uk/metadata/fQNy9JEL"
}
```
## Try it
## Notes
- `program` is one of `PUMPFUN`, `PUMPSWAP`, `METEORA`, `RAYDIUM`, `ORCA` - useful for routing UI logic per DEX.
- `active: false` means the pool has been superseded (e.g. PumpFun bonding curve migrated to PumpSwap). Use `mint` lookup to find the current active one.
- `/lookup` is an alias for the same handler - same params, same cost.
---
# WS ohlcv_history
> One-shot fetch of the most recent N candles for a pool.
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import TryIt from '@site/src/components/TryIt';
# `ohlcv_history` - recent candles (one-shot)
One-shot request: returns the most recent **N 1-second candles** for a pool. Use it to seed a chart, then upgrade to [`subscribe_ohlcv`](./subscribe-ohlcv) for live updates. Pass one of `mint` or `pool`, plus an optional `limit` (1 – 500, default 200).
| | |
|---|---|
| **Channel** | Socket.IO event with ack |
| **Auth** | `auth.api_key` in the handshake |
## Example
```js
socket.emit("ohlcv_history", { pool: "4mBL…", limit: 200 }, (ack) => {
if (!ack.ok) return console.error(ack);
for (const [t, o, h, l, c, v] of ack.data) {
console.log(new Date(t), `o=${o} h=${h} l=${l} c=${c} v=${v}`);
}
});
```
```python
from datetime import datetime
# sio.call blocks until the server acks the one-shot request.
ack = sio.call("ohlcv_history", {"pool": "4mBL…", "limit": 200})
if ack["ok"]:
for t, o, h, l, c, v in ack["data"]:
print(datetime.fromtimestamp(t / 1000), f"o={o} h={h} l={l} c={c} v={v}")
```
## Response (ack)
```json
{
"ok": true,
"pool": "4mBL…",
"data": [
[1779812627000, 0.000000033, 0.000000034, 0.000000033, 0.000000034, 5.12],
[1779812628000, 0.000000034, 0.000000035, 0.000000034, 0.000000035, 7.40]
]
}
```
Tuples are `[ timestamp_ms, open, high, low, close, volume ]`, sorted oldest → newest. Format matches the live [`ohlcv`](./subscribe-ohlcv) stream.
## Try it
## Notes
- This is the **only paid one-shot WS call** - the rest are subscribes.
- Returns whatever's currently in the rolling Redis window (typically the last ~2 minutes of 1s candles between flushes to ClickHouse). For deeper history, a REST `/ohlcv` endpoint with `from` / `to` is on the roadmap.
---
# Data API · Overview
> Real-time and historical Solana memecoin data - REST + WebSocket. Pay per event from your wallet's SOL balance.
# Data API
Live + historical data across **PumpFun, PumpSwap, Meteora DAMM v2, Raydium (CPMM/CLMM/AMM v4) and Orca Whirlpool**. One API key, billed in SOL directly from your wallet - no monthly fee, no credit packs.
## 30-second start
1. Get a key on the [Account](/account) page (connect Phantom → sign → done).
2. Fund your wallet with SOL.
3. Call any endpoint:
```bash
curl "https://api.pumpdata.fun/metadata?mint=Gc6r…pump" \
-H "x-api-key: pd_xxxxxxxx…"
```
## Auth
Every paid endpoint accepts the key as either:
- **Header** - `x-api-key: pd_xxxxxxxx…` (preferred)
- **Query string** - `?api_key=pd_xxxxxxxx…` (when you can't set headers)
WebSocket clients pass it in the Socket.IO handshake:
```js
import { io } from "socket.io-client";
const socket = io("https://api.pumpdata.fun", {
auth: { api_key: "pd_xxxxxxxx…" },
});
```
## How billing works
1. We snapshot your wallet's on-chain SOL balance into a cached value (refreshed at least once a minute).
2. Subscribing is free. You pay per **event delivered** to you (each price tick, trade, candle, new-token and migration message), plus each REST call and one-shot fetch.
3. Stream usage is metered in small batches every few seconds and charged atomically against that cached balance.
4. Your **effective balance = wallet balance − charged since last settlement**. You see it on the [Account](/account) page.
5. When you top up the wallet, the next balance refresh picks it up. If you run out mid-stream, the server emits a `billing_error` event and disconnects you.
There's no monthly fee, no rate-limit tier.
## Pricing
Subscribing is free. Streams are billed per event delivered; REST calls and the one-shot `ohlcv_history` fetch are billed per call.
| Event | Per event | Per 1,000 |
|---|---|---|
| [`subscribe`](./subscribe-price) - each `token_update` | 0.000000005 SOL | 0.000005 SOL |
| [`subscribe_ohlcv`](./subscribe-ohlcv) - each `ohlcv` candle | 0.000000005 SOL | 0.000005 SOL |
| [`subscribe_trades`](./subscribe-trades) - each `trade` | 0.00000001 SOL | 0.00001 SOL |
| [`subscribe_new_tokens`](./subscribe-new-tokens) - each `new_token` | 0.0000001 SOL | 0.0001 SOL |
| [`subscribe_migrations`](./subscribe-migrations) - each `migration` | 0.0000002 SOL | 0.0002 SOL |
| [`GET /metadata`](./metadata) (and `/lookup`) - per call | 0.000001 SOL | 0.001 SOL |
| [`ohlcv_history`](./ohlcv-history) - per call | 0.000003 SOL | 0.003 SOL |
Subscribes, unsubscribes and disconnects are free. Rough mental model: **1 SOL ≈ 1,000,000 metadata calls**, **100,000,000 trade messages**, or **200,000,000 price ticks**.
## Errors
All errors are JSON with the same shape:
```json
{ "error": "insufficient_balance", "effective_sol": 0.00000024 }
```
| Status | `error` | Meaning |
|---|---|---|
| 400 | `mint_or_pool_required` | Missing required param. |
| 401 | `missing_api_key` | No `x-api-key` header / `api_key` query param. |
| 401 | `unknown_key` | API key not recognized. |
| 402 | `insufficient_balance` | Top up your wallet. |
| 404 | `not_found` | Pool/mint isn't registered (yet). |
| 502 | `authorize_upstream_…` / `authorize_unavailable` | Backend hiccup; safe to retry. |
WebSocket subscribe errors come as a callback ack: `{ error: "…", message: "…" }`. If your balance runs out while streaming, the server emits a `billing_error` event and disconnects.
## Limits
| | Value |
|---|---|
| Max concurrent Socket.IO subscriptions per connection | 50 |
| OHLCV history `limit` | 1 – 500 (default 200) |
| Wallet balance refresh | ≤ 60s |
---
# WS subscribe_migrations
> Live feed of bonding-curve → AMM migrations on the DEXes you pick. Emits `migration`.
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import DexPicker from '@site/src/components/DexPicker';
import TryIt from '@site/src/components/TryIt';
# `subscribe_migrations` - migrations
Live feed of pool migration events on the DEXes you pick (today: PumpFun bonding-curve → PumpSwap AMM). Server emits **`migration`**. Pass `protocols` (today only `PUMPFUN` emits migration events).
| | |
|---|---|
| **Channel** | Socket.IO event |
| **Auth** | `auth.api_key` in the handshake |
| **Emits** | `migration` |
## Example
Click an icon to toggle it. The example regenerates live, so you can paste it straight into your client.
```python
import socketio
sio = socketio.Client()
@sio.on("migration")
def on_migration(m):
print(f"{m['mint']} migrated {m['old_pool']} → {m['new_pool']}")
sio.connect("https://api.pumpdata.fun", auth={"api_key": "pd_xxxxxxxx…"})
sio.emit("subscribe_migrations", {"protocols": ["PUMPFUN"]})
sio.wait()
# later, to stop (free):
# sio.emit("unsubscribe_migrations", {"protocols": ["PUMPFUN"]})
```
## Response - `migration`
```json
{
"protocol": "PUMPFUN",
"mint": "Gc6r…pump",
"old_pool": "",
"new_pool": "",
"tx": "",
"time": 1779812627
}
```
## Try it
## Notes
- After a migration the old pool is marked `active: false` in [`/metadata`](./metadata); subsequent lookups by `mint` resolve to the new pool automatically.
- Combine with [`subscribe_new_tokens`](./subscribe-new-tokens) for the launch side, and [`subscribe_trades`](./subscribe-trades) on the new pool for the first trades.
---
# WS subscribe_new_tokens
> Live feed of new pool/token creations on the DEXes you pick. Emits `new_token`.
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import DexPicker from '@site/src/components/DexPicker';
import TryIt from '@site/src/components/TryIt';
# `subscribe_new_tokens` - live launches
Live feed of new pool/token creations on the DEXes you pick. Server emits **`new_token`**. Pass `protocols`, any subset of `PUMPFUN`, `PUMPSWAP`, `METEORA`, `RAYDIUM`, `ORCA`, `BONK` (one room per protocol; re-subscribe to change the set).
PUMPFUN: FREE
PumpFun `new_token` events are delivered at no charge. You can connect with a 0 SOL balance and stay subscribed indefinitely as long as you only request `PUMPFUN`. Every other protocol on this stream is billed per delivered event ([see fees](/fees#data-api)).
| | |
|---|---|
| **Channel** | Socket.IO event |
| **Auth** | `auth.api_key` in the handshake |
| **Emits** | `new_token` |
| **Cost** | `PUMPFUN`: free. Others: per event ([fees](/fees#data-api)) |
## Example
Click an icon to toggle it. The example regenerates live, so you can paste it straight into your client.
```python
import socketio
sio = socketio.Client()
@sio.on("new_token")
def on_new(n):
# AMM dexes omit name/symbol/uri/creator, so use .get() with a default
print(f"[{n['protocol']}] {n.get('symbol','')} ({n.get('name','')}) by {n.get('creator','')[:4]}…")
# your filter / alert / auto-buy here
sio.connect("https://api.pumpdata.fun", auth={"api_key": "pd_xxxxxxxx…"})
sio.emit("subscribe_new_tokens", {"protocols": ["PUMPFUN", "RAYDIUM"]})
sio.wait()
# later, to stop (free):
# sio.emit("unsubscribe_new_tokens", {"protocols": ["PUMPFUN", "RAYDIUM"]})
```
## Response - `new_token`
Every DEX you subscribe to emits `new_token` on each pool/token creation. The fields `protocol`, `mint`, `pool`, `decimals`, `quote`, `supply` and `timestamp` are always present. The on-chain metadata fields (`name`, `symbol`, `creator`, `uri`) are only included when the creation instruction actually carries them, so **plan for them to be absent** (not just empty).
### Common `quote` mints
`quote` is the mint address of the token the new pool is quoted in (what the launched token trades against). The vast majority of launches are SOL-quoted; compare against these addresses to filter or to label prices correctly:
| Quote token | Mint address | Where you'll see it |
|---|---|---|
| WSOL (wrapped SOL) | `So11111111111111111111111111111111111111112` | Default quote on every DEX; nearly all launches |
| USDC | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | AMM pools (Raydium, Orca, Meteora, PumpSwap) opened against USDC |
| USD1 | `USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB` | Bonk launches on USD1-quoted pools |
PumpFun (and Bonk) emit name/symbol/uri inline, so their events are complete:
```json
{
"protocol": "PUMPFUN",
"mint": "3StpvEPanQtd25SvBamVjvyRaawNapjUTmygnVsPpump",
"pool": "Hm9HUNoF3Ua2tSZM1Uzk6HM6tS5LxmR8p7wtxUeYcD6E",
"name": "Mayhem",
"symbol": "Venum",
"decimals": 6,
"quote": "So11111111111111111111111111111111111111112",
"creator": "7ZbfkTHfyeMdXt98sdvJHT2kY9hrYntJm2A5aepK8ypn",
"uri": "https://ipfs.io/ipfs/Qmdq5na6gt6xtEjCS4Lb83yPVCwYTmZ8CKufX1iFPQFE3b",
"supply": 1000000000,
"timestamp": 1779825514
}
```
Plain AMM pool creations (PumpSwap, Raydium, Orca, and Meteora's AMM pools) are usually opened on a token that already exists, so their create transaction carries no token metadata and those fields are omitted. A Raydium event looks like:
```json
{
"protocol": "RAYDIUM",
"mint": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
"pool": "58oQChx4yWmvKdwLLZzBi4ChoCc2fqCUWBkwMihLYQo2",
"decimals": 6,
"quote": "So11111111111111111111111111111111111111112",
"creator": "GThUX1Atko4tqhN2NaiTazWSeFWMuiUvfFnyJyUghFMJ",
"supply": 1000000000,
"timestamp": 1779825514
}
```
Launchpad creations are the exception: when the create transaction mints the token itself (PumpFun, Bonk, and Meteora's Dynamic Bonding Curve), name/symbol/uri/creator are read straight from that same transaction and included. Meteora DBC tokens arrive on the `METEORA` stream and keep `protocol: "METEORA_DBC"`, so they look complete like a PumpFun event:
```json
{
"protocol": "METEORA_DBC",
"mint": "CRkgM1oyuYpCLjg39xDAyEwiicEE4xr8hXXRhjDEaQRc",
"pool": "B31Kz7nxfPrWSd9avAto6CZfiANN22LKbAW3g3AktbQ",
"name": "market dead send this",
"symbol": "bread",
"decimals": 6,
"quote": "So11111111111111111111111111111111111111112",
"creator": "9RvZ8kgeHVzpc1f3pWn21i51QMXFsLyvXLXp3uqLxPJM",
"uri": "https://meta.uxento.io/data/b9fe1da7-6a1c-4f93-a3fe-a2cd1798ac9f",
"supply": 1000000000,
"timestamp": 1779825514
}
```
> When a token created on PumpFun later migrates to an AMM, the AMM event still omits name/symbol/uri, but the stored token record keeps the original PumpFun metadata. The live `new_token` event always reflects only what is in the create instruction.
## Try it
## Notes
- All subscribed DEXes emit `new_token`. PumpFun is simply the highest-volume launch source, so it dominates the feed.
- Launchpad creates carry name/symbol/uri: PumpFun and Bonk inline them, and Meteora DBC tokens (delivered on the `METEORA` stream with `protocol: "METEORA_DBC"`) have them read from the same create transaction. Plain AMM pool creations (PumpSwap, Raydium, Orca, and Meteora AMM pools) usually open on a pre-existing token, so those keys are omitted. Always null-check them.
- The `new_token` event is real-time and reflects only on-chain create data. Off-chain metadata (image, socials) is fetched separately from the token's `uri` and is not part of this event.
- For the corresponding migration events (e.g. PumpFun to PumpSwap) see [`subscribe_migrations`](./subscribe-migrations).
---
# WS subscribe_ohlcv
> Live 1-second OHLCV candles for a pool. Emits `ohlcv`.
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import TryIt from '@site/src/components/TryIt';
# `subscribe_ohlcv` - live candles
Live **1-second** OHLCV candles for a pool. The server emits **`ohlcv`** every time the current bucket changes (≤1 update/sec). Pass one of `mint` or `pool`.
| | |
|---|---|
| **Channel** | Socket.IO event |
| **Auth** | `auth.api_key` in the handshake |
| **Emits** | `ohlcv` |
## Example
```js
import { io } from "socket.io-client";
const socket = io("https://api.pumpdata.fun", {
auth: { api_key: "pd_xxxxxxxx…" },
});
socket.on("ohlcv", (rows) => {
for (const [t, o, h, l, c, v] of rows) {
console.log(new Date(t), `o=${o} h=${h} l=${l} c=${c} v=${v}`);
}
});
socket.emit("subscribe_ohlcv", { pool: "4mBL…" });
// later, to stop (free): socket.emit("unsubscribe_ohlcv", { pool: "4mBL…" });
```
```python
import socketio
from datetime import datetime
sio = socketio.Client()
@sio.on("ohlcv")
def on_ohlcv(rows):
for t, o, h, l, c, v in rows:
print(datetime.fromtimestamp(t / 1000), f"o={o} h={h} l={l} c={c} v={v}")
sio.connect("https://api.pumpdata.fun", auth={"api_key": "pd_xxxxxxxx…"})
sio.emit("subscribe_ohlcv", {"pool": "4mBL…"})
sio.wait()
# later, to stop (free): sio.emit("unsubscribe_ohlcv", {"pool": "4mBL…"})
```
## Response - `ohlcv`
An array of candle tuples (usually one per emission). Each tuple is `[ timestamp_ms, open, high, low, close, volume ]`:
```json
[[1779812627000, 0.000000033, 0.000000034, 0.000000033, 0.000000034, 5.12]]
```
The tuple format matches what [lightweight-charts](https://github.com/tradingview/lightweight-charts), TradingView, Chart.js financial, etc. expect - so it drops straight into a chart.
## Errors
If the subscription cannot be created, the ack callback receives an error object instead of `{ ok: true }`:
```json
{ "error": "not_found", "message": "pool not found" }
```
| `error` | When |
|---|---|
| `invalid_request` | Neither `mint` nor `pool` was provided. |
| `not_found` | The `mint` has no active pool, or the given `pool` has neither live data nor candle history. |
| `unavailable` | A transient backend error (the server already retried). **Safe to retry** - it is not a definitive "not found". |
| `limit_exceeded` | This connection already has the maximum number of subscriptions. |
If you do not pass an ack callback, the same payload is emitted as a `subscription_error` event instead.
## Try it
## Notes
- Granularity is 1s; aggregate client-side if you want 1m/5m/1h.
- Seed the initial window with [`ohlcv_history`](./ohlcv-history), then keep it live here.
- For per-trade detail use [`subscribe_trades`](./subscribe-trades).
---
# WS subscribe (price)
> Live price + marketcap updates for a pool. Emits `token_update`.
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import TryIt from '@site/src/components/TryIt';
import SupportedDexes from '@site/src/components/SupportedDexes';
# `subscribe` - live price
Live price / market-cap updates for one pool. The server emits **`token_update`** every time the pool's last-trade price changes. Pass one of `mint` or `pool` (mint → pool lookup happens server-side).
| | |
|---|---|
| **Channel** | Socket.IO event |
| **Auth** | `auth.api_key` in the handshake |
| **Emits** | `token_update` |
## Supported DEXes
Prices stream from trades on every major Solana DEX and launchpad:
Meteora covers DAMM v2 and DBC (dynamic bonding curve); Raydium covers CPMM and AMM v4. The `quote` field on each update tells you which mint (WSOL or USDC) the price is denominated in.
## Example
```js
import { io } from "socket.io-client";
const socket = io("https://api.pumpdata.fun", {
auth: { api_key: "pd_xxxxxxxx…" },
});
socket.on("token_update", (u) => {
console.log(`${u.pool} → ${u.price} (mcap ${u.mcap})`);
});
socket.emit("subscribe", { mint: "Gc6rNxGnoQt…pump" }, (ack) => {
console.log("subscribed:", ack); // { ok: true, pool: "...", room: "..." }
});
// later, to stop (free): socket.emit("unsubscribe", { pool: "4mBL…" });
```
```python
import socketio
sio = socketio.Client()
@sio.on("token_update")
def on_update(u):
print(u["pool"], "→", u["price"], "(mcap", u["mcap"], ")")
sio.connect("https://api.pumpdata.fun", auth={"api_key": "pd_xxxxxxxx…"})
sio.emit("subscribe", {"mint": "Gc6rNxGnoQt…pump"},
callback=lambda ack: print("subscribed:", ack))
sio.wait()
# later, to stop (free): sio.emit("unsubscribe", {"pool": "4mBL…"})
```
## Response - `token_update`
```json
{
"pool": "4mBLRPUyfE7CvxmXiGJx5WA51isanbxpdbdPjFuuBzmY",
"price": 0.000000033,
"mcap": 32.62,
"quote": "So11111111111111111111111111111111111111112",
"priceUSD": 0.0000049,
"mcapUSD": 4893.0,
"time": 1779812627
}
```
| field | meaning |
|---|---|
| `price` | price in the quote token per base token |
| `quote` | the quote-side mint the price is denominated in — WSOL, USDC, or USD1 |
| `mcap` | market cap in the quote token (price × total supply) |
| `priceUSD` | `price` converted to US dollars |
| `mcapUSD` | `mcap` converted to US dollars |
For USDC- and USD1-quoted pools the USD fields equal `price` / `mcap` (those quotes are already dollars). For WSOL-quoted pools they're converted using the live SOL/USD rate (the same rate streamed by [`subscribe_sol_price`](./subscribe-sol-price)). `priceUSD` / `mcapUSD` are `0` in the brief window before the first SOL/USD tick is seen.
## Errors
If the subscription cannot be created, the ack callback receives an error object instead of `{ ok: true }`:
```json
{ "error": "not_found", "message": "pool not found" }
```
| `error` | When |
|---|---|
| `invalid_request` | Neither `mint` nor `pool` was provided. |
| `not_found` | The `mint` has no active pool, or the given `pool` is not known to the data plane. |
| `unavailable` | A transient backend error (the server already retried). **Safe to retry** - it is not a definitive "not found". |
| `limit_exceeded` | This connection already has the maximum number of subscriptions. |
If you do not pass an ack callback, the same payload is emitted as a `subscription_error` event instead.
## Try it
## Notes
- This is the cheapest live stream - use it for "current price" cards or simple tickers.
- For full trade details (wallet, signature, amounts) use [`subscribe_trades`](./subscribe-trades).
- For candlesticks use [`subscribe_ohlcv`](./subscribe-ohlcv).
---
# WS subscribe_sol_price
> Live SOL/USD price feed. Emits `sol_price`.
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import TryIt from '@site/src/components/TryIt';
# `subscribe_sol_price` - live SOL/USD
A single global feed of the current **SOL/USD** price, derived from the deepest on-chain SOL/USDC market. The server emits **`sol_price`** whenever the rate moves. This is the same rate used to fill `priceUSD` / `mcapUSD` on [`subscribe`](./subscribe-price), exposed on its own so you can convert WSOL-quoted values yourself.
Takes no arguments. **Free** - never metered.
| | |
|---|---|
| **Channel** | Socket.IO event |
| **Auth** | `auth.api_key` in the handshake |
| **Emits** | `sol_price` |
## Example
```js
import { io } from "socket.io-client";
const socket = io("https://api.pumpdata.fun", {
auth: { api_key: "pd_xxxxxxxx…" },
});
socket.on("sol_price", ({ price }) => {
console.log(`SOL is $${price}`);
});
socket.emit("subscribe_sol_price", (ack) => {
// ack.snapshot is the current value (or null if not known yet)
console.log("subscribed:", ack);
});
// later, to stop: socket.emit("unsubscribe_sol_price");
```
```python
import socketio
sio = socketio.Client()
@sio.on("sol_price")
def on_price(p):
print("SOL is $", p["price"])
sio.connect("https://api.pumpdata.fun", auth={"api_key": "pd_xxxxxxxx…"})
sio.emit("subscribe_sol_price", callback=lambda ack: print("subscribed:", ack))
sio.wait()
```
## Response - `sol_price`
```json
{
"price": 182.45,
"time": 1779812600
}
```
`price` is USD per SOL; `time` is the unix second of the update. On subscribe, the current snapshot is sent immediately (and also returned as `ack.snapshot`) so you have a value without waiting for the next move.
## Try it
## Notes
- One global value - there is no `mint` / `pool` argument.
- Free: subscribing and every `sol_price` event are never billed.
- Use it to convert any WSOL-quoted `price` to dollars yourself, or just read `priceUSD` / `mcapUSD` straight off [`subscribe`](./subscribe-price).
---
# WS subscribe_trades
> Every trade on a pool, in real time. Emits `trade`.
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import TryIt from '@site/src/components/TryIt';
# `subscribe_trades` - live trades
Every trade executed on the pool, with full details (wallet, signature, amounts, price). Server emits **`trade`**. Pass one of `mint` or `pool`.
| | |
|---|---|
| **Channel** | Socket.IO event |
| **Auth** | `auth.api_key` in the handshake |
| **Emits** | `trade` |
## Example
```js
import { io } from "socket.io-client";
const socket = io("https://api.pumpdata.fun", {
auth: { api_key: "pd_xxxxxxxx…" },
});
socket.on("trade", (t) => {
const side = t.is_buy ? "BUY " : "SELL";
console.log(`${side} ${t.token_amount.toFixed(2)} @ ${t.price} by ${t.wallet.slice(0, 4)}…`);
});
socket.emit("subscribe_trades", { pool: "4mBL…" });
// later, to stop (free): socket.emit("unsubscribe_trades", { pool: "4mBL…" });
```
```python
import socketio
sio = socketio.Client()
@sio.on("trade")
def on_trade(t):
side = "BUY " if t["is_buy"] else "SELL"
print(f"{side} {t['token_amount']:.2f} @ {t['price']} by {t['wallet'][:4]}…")
sio.connect("https://api.pumpdata.fun", auth={"api_key": "pd_xxxxxxxx…"})
sio.emit("subscribe_trades", {"pool": "4mBL…"})
sio.wait()
# later, to stop (free): sio.emit("unsubscribe_trades", {"pool": "4mBL…"})
```
## Response - `trade`
```json
{
"protocol": "PUMPSWAP",
"pool": "4mBLRPUyfE7CvxmXiGJx5WA51isanbxpdbdPjFuuBzmY",
"mint": "Gc6rNxGnoQt6vCfhNzn5iJnPBu1V38GZfdYERfnXpump",
"signature": "c9asMSPXxiEYC4RQpLBFUAASCL63TXZpPgSPgivDcJxsNoNc7ptrh6DfJy67NS2p7nEQBbVR45ZuPhyBqectR76",
"is_buy": true,
"token_amount": 153285714.285714,
"quote_amount": 5.0,
"price": 0.000000033,
"marketcap": 32.618826,
"wallet": "9AiXyDfy6cKieVPgwyfsNS37HypoS8k7ju2CV3WnyvZG",
"timestamp": 1779812627
}
```
- `is_buy` - `true` if the trader received the base token (price went up); `false` if they sold it.
- `token_amount` / `quote_amount` - in human units (already adjusted for decimals).
## Errors
If the subscription cannot be created, the ack callback receives an error object instead of `{ ok: true }`:
```json
{ "error": "not_found", "message": "pool not found" }
```
| `error` | When |
|---|---|
| `invalid_request` | Neither `mint` nor `pool` was provided. |
| `not_found` | The `mint` has no active pool, or the given `pool` is not known to the data plane. |
| `unavailable` | A transient backend error (the server already retried). **Safe to retry** - it is not a definitive "not found". |
If you do not pass an ack callback, the same payload is emitted as a `subscription_error` event instead.
## Try it
## Notes
- We only publish trades for pools with at least one live subscriber, so the firehose stays cheap. You're billed per `trade` message delivered (see [Pricing](./overview#pricing)).
- Trade order within a pool is preserved.
- Want just price/mcap? Use the cheaper [`subscribe`](./subscribe-price).
---
# Fees
> pumpdata.fun fees by action.
# Fees
## Trading
| Action | Fee |
|---|---|
| Buy (local or lightning transaction) | **0.30%** |
| Sell (local or lightning transaction) | **0.30%** |
The fee is taken in SOL and included automatically in the transaction we build for you. There's nothing extra to configure.
## Data API
Billed in SOL directly from your wallet. Subscribing is free; streams are billed per event delivered, and REST calls plus the one-shot `ohlcv_history` fetch are billed per call. See the [Data API overview](/data-api/overview#pricing) for how billing works.
| Event | Per event | Per 1,000 |
|---|---|---|
| `subscribe` (each `token_update`) | 0.000000005 SOL | 0.000005 SOL |
| `subscribe_ohlcv` (each `ohlcv` candle) | 0.000000005 SOL | 0.000005 SOL |
| `subscribe_trades` (each `trade`) | 0.00000001 SOL | 0.00001 SOL |
| `subscribe_new_tokens` (each `new_token`) - **PUMPFUN** | **Free** | **Free** |
| `subscribe_new_tokens` (each `new_token`) - all other DEXes | 0.0000001 SOL | 0.0001 SOL |
| `subscribe_migrations` (each `migration`) | 0.0000002 SOL | 0.0002 SOL |
| `GET /metadata` (and `/lookup`), per call | 0.000001 SOL | 0.001 SOL |
| `ohlcv_history`, per call | 0.000003 SOL | 0.003 SOL |
No monthly fee, no credit packs. Subscribes, unsubscribes and disconnects are free.
> **PumpFun `new_token` is free.** When you call `subscribe_new_tokens` with `PUMPFUN` in the protocol list, every `new_token` event delivered for that protocol is billed at 0. Other protocols on the same subscription are charged at the rate above. You can fund a wallet with 0 SOL and stay connected as long as you only subscribe to PumpFun.
---
# Lightning Transactions
> One-call trades. pumpdata generates a wallet for you, signs trades on your behalf, and submits with optional SWQOS for faster landing.
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import LightningGenerator from '@site/src/components/LightningGenerator';
import TryIt from '@site/src/components/TryIt';
# Lightning Transactions
The one-call flow. You click **Generate**, we create a wallet just for you and store an encrypted copy. After that, send us an API key with each trade request and we sign + submit on your behalf - optionally through Jito and other validators for fastest landing.
Use this if you don't want to manage signing or run your own RPC.
## Generate a wallet
## How it works
1. **Generate** - pumpdata creates a Solana wallet just for you. We give you the public key, the private key (shown once), and an API key. The private key is encrypted with a server-side key before it touches the database.
2. **Fund** - send some SOL to the publicKey. That's the wallet pumpdata will trade with.
3. **Trade** - call `/api/lightning-trade` with your API key + the trade you want. We sign and submit immediately.
## Example
The `apiKey` goes in the request body (not a header). `amount` is in **SOL when buying** and **tokens when selling**.
```js
const res = await fetch("https://api.pumpdata.fun/api/lightning-trade", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
apiKey: "pd_xxxxxxxx…", // from Generate, above
action: "buy", // "buy" or "sell"
mint: "", // base58 token mint
amount: 0.01, // SOL when buying, tokens when selling
slippage: 10, // percent (default 5)
priorityFee: 0.0001, // SOL (optional)
pool: "pumpfun", // pumpfun | pumpswap | bonk | raydium_cpmm | raydium_amm_v4 | meteora_damm_v2
// poolAddress: "...", // base58, optional - auto-resolves to largest WSOL/USDC pool on raydium_cpmm | raydium_amm_v4 | meteora_damm_v2
swqos: true, // route via Jito etc. for faster landing
}),
});
const { success, signature, error } = await res.json();
console.log(success ? "https://solscan.io/tx/" + signature : error);
```
```python
import requests
res = requests.post(
"https://api.pumpdata.fun/api/lightning-trade",
json={
"apiKey": "pd_xxxxxxxx…", # from Generate, above
"action": "buy", # "buy" or "sell"
"mint": "", # base58 token mint
"amount": 0.01, # SOL when buying, tokens when selling
"slippage": 10, # percent (default 5)
"priorityFee": 0.0001, # SOL (optional)
"pool": "pumpfun", # pumpfun | pumpswap | bonk | raydium_cpmm | raydium_amm_v4 | meteora_damm_v2
# "poolAddress": "...", # base58, optional - auto-resolves to largest WSOL/USDC pool on raydium_cpmm | raydium_amm_v4 | meteora_damm_v2
"swqos": True, # route via Jito etc. for faster landing
},
)
body = res.json()
print(f"https://solscan.io/tx/{body['signature']}" if body["success"] else body["error"])
```
## Try it
Live mode: this widget submits the request to `/api/lightning-trade` and prints the response. Your custodial wallet WILL be used. Paste your API key (we never log it). `poolAddress` is optional - if omitted on `raydium_cpmm` / `raydium_amm_v4` / `meteora_damm_v2`, we auto-resolve to the cached pool with the largest WSOL or USDC vault balance for the mint.
## Response
```json
{
"signature": "5xZ…",
"success": true,
"error": null,
"fee_lamports": 30000
}
```
## Why this is convenient (and what you give up)
- One round-trip per trade - no client-side signing, no RPC of your own.
- Optional SWQOS routing through providers like Jito for faster landing.
- Tradeoff: we hold the encrypted private key. If you want zero custodial risk, use the [Local Trade API](/) instead, where your private key never leaves your machine.
## If something goes wrong
You'll get `{ "success": false, "error": "...", "signature": null }`. Usual causes: invalid mint, wallet not funded, slippage exceeded.