# 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.