# Pump Trading API by shrine.trade > API-first Solana DEX --- # pump.fun API: Keyless Solana Trading on Every Launchpad > Trade Solana memecoins through a keyless REST API. Send a mint and an amount, get an unsigned transaction back, sign it locally and relay it. No key and no sign-up on the Free plan at 0.25% per trade, 0% with Lite or Developer, every launchpad. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import TryIt from '@site/src/components/TryIt'; import SupportedDexes from '@site/src/components/SupportedDexes'; # 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 the signed trade through our relay, or through your own RPC. One API, four networks: the same requests, streams and keys work on [Solana](/), [BNB Chain](https://shrine.trade/flap/), [Robinhood Chain](https://shrine.trade/pons/) and [Arc](https://shrine.trade/arc/); a bot moves between them by changing the base URL. The Solana side runs in Frankfurt, Amsterdam and New York; pick yours with the region picker at the bottom of the sidebar, see [Regions](/regions). The fastest routes are [Lightspeed](/lightspeed/overview) for trades and [Ultra Fast Shred Stream](/ultrafast/overview) for data. Around the trade endpoint: the [Ultra Fast Shred Stream](/ultrafast/overview) delivers every pump.fun trade before the block runs, the [24-hour replay](/data-api/resume) means a disconnect loses nothing, and the same API runs in three [regions](/regions). One endpoint for a coin from any launchpad, wherever it trades right now: import Admonition from '@theme/Admonition'; No API key needed, no account. The API builds the transaction, your machine signs it, and the relay sends it. The only thing we ever see is your public key. On the Free plan a trade pays 0.25%; send your [plan's key](/fees#the-key) as `x-api-key` and it pays 0%, with higher limits. ## What you need - **A Solana wallet.** Its private key in base58. (Phantom: Settings → Show Secret Recovery Phrase → Show Private Key.) - **Some SOL.** Enough in that wallet for the trade plus a small amount of network fees. - **A way to submit.** By default you post the signed transaction back to `/api/send` and we broadcast it through our own endpoint, so no RPC is needed. If you prefer your own Solana RPC (Helius, Triton, QuickNode or the public one), send with that instead. - **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 wallet = Keypair.fromSecretKey(bs58.decode("")); // 1. Ask shrine.trade to build an unsigned transaction. const res = await fetch("https://sol.shrine.trade/api/local-trade", { method: "POST", headers: { "content-type": "application/json", "accept": "application/json" }, // on a plan, add "x-api-key": "sk_…" to the headers: 0% fee, higher limits body: JSON.stringify({ action: "buy", // "buy" or "sell" publicKey: wallet.publicKey.toBase58(), mint: "", // base58 token mint amount: 0.01, // SOL on a buy; tokens or "100%" on a sell slippage: 10, // percent priorityFee: 0.0001, // SOL }), }); 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. Send it through the shrine.trade relay - no RPC needed. Waits for confirmation. const sent = await (await fetch("https://sol.shrine.trade/api/send", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ signedTx: Buffer.from(txObj.serialize()).toString("base64") }), })).json(); console.log(sent.status, sent.explorer); // "landed" https://solscan.io/tx/… // Or, with your own RPC: // const conn = new Connection("https://your-rpc"); // const sig = await conn.sendRawTransaction(txObj.serialize()); ``` ```python import base64, requests, base58 from solana.rpc.api import Client from solders.keypair import Keypair from solders.transaction import VersionedTransaction wallet = Keypair.from_bytes(base58.b58decode(WALLET_SECRET)) # 1. Ask shrine.trade to build an unsigned transaction. res = requests.post( "https://sol.shrine.trade/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 on a buy; tokens or "100%" on a sell "slippage": 10, # percent "priorityFee": 0.0001, # SOL }, ) 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. Send it through the shrine.trade relay - no RPC needed. Waits for confirmation. sent = requests.post("https://sol.shrine.trade/api/send", json={"signedTx": base64.b64encode(bytes(signed)).decode()}).json() print(sent["status"], sent["explorer"]) # landed https://solscan.io/tx/… # Or, with your own RPC: # sig = Client("https://your-rpc").send_raw_transaction(bytes(signed)).value print(f"https://solscan.io/tx/{sig}") ``` ## How it works 1. **You ask** shrine.trade 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: post it to `/api/send` and we broadcast it, or send it through your own RPC. ## Sending `POST https://sol.shrine.trade/api/send` takes one signed transaction and broadcasts it through our Solana endpoint, so the scripts need no RPC at all. It only forwards transactions this API built: every build is remembered for 15 minutes, and the signed transaction must match one of them unchanged. Anything assembled elsewhere, or edited after building, is refused with `not_ours`. Using it is optional; a signed transaction can always go through your own RPC instead. | Field | | | |---|---|---| | `signedTx` | base64 | The signed transaction: `Buffer.from(tx.serialize()).toString("base64")` in web3.js, `base64.b64encode(bytes(signed))` with solders. | | `wait` | boolean, optional | Default `true`: wait for confirmation. `false` returns the signature as soon as the node accepts it. | ```json { "signature": "5Kd…", "status": "landed", "slot": 372114820, "explorer": "https://solscan.io/tx/5Kd…" } ``` `status` is `landed` (confirmed), `failed` (executed but errored on-chain; `error` carries the program error) or `pending` (not waited for, or not confirmed within 60 seconds). A transaction the node refuses in preflight - not enough SOL, slippage exceeded, expired blockhash - comes back as a `400` with the node's reason. ## Request fields | Field | Type | Notes | |---|---|---| | `action` | `"buy"` / `"sell"` | | | `publicKey` | string | Your wallet. It signs and pays fees. | | `mint` | string | Token mint. | | `amount` | number or string | What goes in on a buy: SOL, or the coin's quote (USDC, …) for a pump.fun coin quoted in something else. Tokens on a sell, or a share of what the wallet holds: `"100%"` sells everything, `"50%"` half. | | `slippage` | number | Percent. Default 5. | | `priorityFee` | number | SOL. Default about 0.0002. | | `pool` | string | Which launchpad to route through, see below. Omit to auto-route from the mint. | | `poolAddress` | string | Required for `raydium_cpmm`, `raydium_amm_v4` and `meteora_damm_v2` when no pool is cached for the mint; optional for `meteora_dbc`. | | `jitoTip` | number | SOL. Appends a transfer to a Jito tip account so the signed transaction can go straight to a block engine. | | `settle` | string | For a coin paired with a pump coin: `"sol"` (default) trades with SOL through one multi-hop swap, `"quote"` pays in the quote coin from the wallet and is paid in it. See [Coins paired with a pump coin](#coins-paired-with-a-pump-coin). | ## Launchpads Every launchpad and AMM the data stream covers is tradable through the same call. Leave `pool` out and the mint is looked up and routed to its active pool automatically, following migrations. Set it only to force a launchpad. | `pool` | Launchpad | |---|---| | `pumpfun` (or `pump`) | pump.fun bonding curve | | `pumpswap` | PumpSwap, where pump.fun coins trade after graduation | | `bonk`, `stonkfun` | Raydium LaunchLab: letsbonk.fun, StonkFun and every other LaunchLab platform | | `meteora_dbc` (or `bags`, `moonshot`) | Meteora Dynamic Bonding Curve: Bags, Moonshot and the rest of the Meteora launchpad | | `raydium_cpmm` | Raydium CPMM, where LaunchLab coins trade after graduation | | `raydium_amm_v4` | Raydium AMM v4 | | `meteora_damm_v2` | Meteora DAMM v2, where DBC coins trade after graduation | ### Quoted coins A coin need not be priced in SOL. pump.fun coins can be priced in USDC or another token, StonkFun coins on LaunchLab in a tokenised stock, a pre-IPO stock or a currency. The trade endpoint reads the quote from the coin's curve or pool and follows it, whatever the launchpad: a buy's `amount` is then how much of the quote to spend, the wallet must already hold that much plus the fee, a sell pays out in the quote, and the trade fee is taken in the quote too. The response says which asset with `quoteMint` and `feeMint`; `feeLamports` is in the fee asset's base units. Quotes that are Token-2022 mints with a transfer fee (the xStocks) are handled; SOL is needed only for the network fee. Nothing to set, and [`token-info`](/wallet/token-info) shows a coin's `quote` up front. ### Coins paired with a pump coin A pump.fun coin can be paired with another pump coin, which is itself paired with SOL. Such a coin is bought and sold with SOL: `amount` on a buy is SOL, a sell pays out SOL, and the trade fee is taken in SOL, as on any SOL-paired coin. The trade is one multi-hop instruction, SOL → quote coin → coin on a buy and the reverse on a sell, through the quote coin's bonding curve or, once it has migrated, its pool. The wallet never holds the quote coin, and slippage is checked once, on the final amount. The response carries `route` (the coins passed through, `["SOL", "QUOTE", "COIN"]` on a buy), `hops` (each venue with its amounts and the fee it charged) and `expectedOut` / `minAmountOut` (the quote the transaction was built on, in the coin's base units on a buy and lamports on a sell). A hop through a bonding curve that would empty it continues into the pool the migration opens with, at that pool's price; the hop then says `syntheticMigration: true`. Pass `settle: "quote"` to pay in the quote coin from the wallet instead, as for any quoted coin. Mayhem-mode coins and cashback coins cannot be passed through: they take the quoted path. Several operations in one atomic transaction, including a launch followed by buys from other wallets, go through [`local-actions`](./local-actions). Transfers, burns, wrapping SOL and balance reads are on [Wallet utilities](/wallet-utilities). `meteora_dbc` supports SOL-quoted pools. Slippage on DBC is measured against the spot price, so leave headroom on a large buy into a steep curve. The fee is **0.25%** of the SOL side of the trade on the Free plan and **0%** on Lite and Developer, included in the transaction. See [Plans and limits](/fees). ## Try it No API key needed - this endpoint is keyless. Paste your wallet **private key** and a token mint and press **Sign & send**. The API only ever receives your public key and returns an unsigned transaction; your browser signs it and posts the signed bytes to `/api/send`, or to your own RPC if you enter one. The key never leaves this tab. **This is a real trade on mainnet**, so start with a small amount. ## 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. From `/api/send`, a `400` quotes the node's preflight reason (slippage, balance, expired blockhash) and `not_ours` means the transaction was not built by this API, or was changed after building. --- # Claim pump.fun and PumpSwap Creator Fees by API > Collect a creator's accrued pump.fun bonding-curve and PumpSwap fees in one unsigned transaction, signed locally by the creator wallet. Keyless, with the claimable amount reported before you sign. import TryIt from '@site/src/components/TryIt'; # Claim Creator Fees pump.fun pays coin creators a share of every trade, before graduation into the bonding curve's creator vault and after graduation into a PumpSwap vault. This call reads both vaults for a creator and returns one unsigned transaction that collects whatever is there. Legs with nothing to claim are left out; if both are empty the call answers 400. `POST https://sol.shrine.trade/api/claim-creator-fees` ## Request | Field | Type | Notes | |---|---|---| | `publicKey` | string | The creator wallet. Signs and receives the SOL. | | `priorityFee` | number | SOL. Optional. | | `jitoTip` | number | SOL. Optional. | ## Response ```json { "tx": "", "blockhash": "…", "pumpfunLamports": 123456789, "pumpswapLamports": 0 } ``` `pumpfunLamports` is what the bonding-curve vault holds above rent; `pumpswapLamports` is the WSOL in the PumpSwap vault, unwrapped to SOL by the transaction. No platform fee. ## Example Build, sign, send through the relay. The relay broadcasts through our own Solana endpoint, so the script needs no RPC. ```js import { Keypair, VersionedTransaction } from "@solana/web3.js"; import bs58 from "bs58"; const wallet = Keypair.fromSecretKey(bs58.decode("")); // Everything the creator wallet can claim, on the curve and on PumpSwap. const { tx } = await (await fetch("https://sol.shrine.trade/api/claim-creator-fees", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ publicKey: wallet.publicKey.toBase58() }), })).json(); const transaction = VersionedTransaction.deserialize(Buffer.from(tx, "base64")); transaction.sign([wallet]); const sent = await (await fetch("https://sol.shrine.trade/api/send", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ signedTx: Buffer.from(transaction.serialize()).toString("base64") }), })).json(); console.log(sent.status, `https://solscan.io/tx/${sent.signature}`); ``` ## StonkFun coins A coin launched on [StonkFun](./create-token#launch-on-stonkfun) earns its creator fees in the coin's quote asset, and the claim goes through StonkFun's flow instead. `POST https://sol.shrine.trade/api/stonkfun/claim-fees` with `{ "publicKey": "", "mint": "" }` returns an unsigned transaction, valid for 90 seconds, an `intentId`, and what is claimable in the quote and the coin. Only the wallet holding the coin's fee key can claim; anyone else gets a refusal. Sign it and hand it back to `POST /api/stonkfun/claim-fees/submit` as `{ "publicKey", "mint", "intentId", "signedTransaction" }`. Resubmitting a claim that already went through returns the original signature with `alreadySubmitted: true`. StonkFun allows 20 claim quotes a minute per IP. ## Try it Read-only until you sign. Paste a creator wallet to see what is claimable and get the transaction. --- # Create a pump.fun Token by API: Launch with a Dev Buy, Custom Quote Pairs and Holder Rewards > Launch a pump.fun coin through the API: send the name, symbol and image, get back the create transaction with an optional dev buy in it, sign with your own wallet. import CreateTokenForm from '@site/src/components/CreateToken'; import SupportedDexes from '@site/src/components/SupportedDexes'; import Admonition from '@theme/Admonition'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Create Token Send the coin's name, symbol and image. You get back the create transaction, with your dev buy inside it if you want one, and the coin gets an address ending in `pump`. Sign with your wallet, send it, done. The image and details are uploaded for you; your keys never leave your machine. `POST https://sol.shrine.trade/api/create-token` ## Example ```js import { Keypair, VersionedTransaction } from "@solana/web3.js"; import bs58 from "bs58"; const wallet = Keypair.fromSecretKey(bs58.decode("")); const { tx, mint } = await (await fetch("https://sol.shrine.trade/api/create-token", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ publicKey: wallet.publicKey.toBase58(), name: "My Coin", symbol: "MINE", description: "What it is, in a sentence.", imageUrl: "https://…/logo.png", twitter: "https://x.com/…", initialBuy: 0.5, // the dev buy, in the same transaction }), })).json(); const transaction = VersionedTransaction.deserialize(Buffer.from(tx, "base64")); transaction.sign([wallet]); // the mint's signature is already in place const sent = await (await fetch("https://sol.shrine.trade/api/send", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ signedTx: Buffer.from(transaction.serialize()).toString("base64") }), })).json(); console.log(mint, sent.status, `https://solscan.io/tx/${sent.signature}`); ``` ```python import base64, requests from solders.keypair import Keypair from solders.transaction import VersionedTransaction wallet = Keypair.from_base58_string("") r = requests.post("https://sol.shrine.trade/api/create-token", json={ "publicKey": str(wallet.pubkey()), "name": "My Coin", "symbol": "MINE", "description": "What it is, in a sentence.", "imageUrl": "https://…/logo.png", "twitter": "https://x.com/…", "initialBuy": 0.5, # the dev buy, in the same transaction }).json() raw = VersionedTransaction.from_bytes(base64.b64decode(r["tx"])) mint = Keypair.from_base58_string(r["mintSecretKey"]) # solders rebuilds every signature signed = VersionedTransaction(raw.message, [wallet, mint]) sent = requests.post("https://sol.shrine.trade/api/send", json={"signedTx": base64.b64encode(bytes(signed)).decode()}).json() print(r["mint"], sent["status"], sent["explorer"]) ``` A create that lands is a coin on mainnet, with your wallet as its creator, for good. Try it with a throwaway wallet first. ## Request | Field | Type | Notes | |---|---|---| | `publicKey` | string | Creator wallet. Signs, pays, and collects the creator fees later. | | `name` | string | Up to 32 bytes. | | `symbol` | string | Up to 13 bytes. | | `imageUrl` | string | The coin's image, fetched by the API (up to 8 MB). Or `imageBase64` with the bytes. | | `description` | string | Optional. | | `twitter`, `telegram`, `website` | string | Optional links. | | `initialBuy` | number | Optional dev buy in SOL, in the same transaction, so nobody can front-run the launch. | | `slippage` | number | Percent, for the dev buy. Default 10. | | `uri` | string | Optional. Already have a metadata JSON hosted? Pass its address instead of the image and links. | | `mint` | string | Optional. The public key of a mint keypair you generated yourself; it then signs too. Leave it out for an address ending in `pump`, already signed for in `tx`. | | `creatorFeeAddress` | string | Optional. The wallet that collects the creator's share of trading fees. Defaults to `publicKey`. | | `quoteMint` | string | Optional. Price the coin in USDC or any mint on pump.fun's [custom pairs list](https://pump.fun/docs/custom-pairs) instead of SOL. `initialBuy` and the fee are then in that quote. | | `mayhemMode` | boolean | pump.fun mayhem mode. | | `holderReward` | boolean | The creator fee of every trade goes to the coin's holders, paid out by pump.fun. Permanent. | | `creatorFeeBps` | number | Custom pairs only: the coin's own creator fee, up to pump.fun's cap (300 today). | | `priorityFee` | number | SOL. | | `jitoTip` | number | SOL. | ## Response ```json { "tx": "", "mint": "…pump", "mintSecretKey": "…", "bondingCurve": "…", "feeLamports": 1250000, "signers": ["", ""] } ``` When the launch and its dev buy do not fit one transaction, the answer also carries `buyTx`, the dev buy on the same blockhash: send `tx` then `buyTx`, or both as one [bundle](./jito-bundles). To have other wallets buy in the launch block, put the create first in a [bundle](./jito-bundles) and refer to the coin by `mintRef` in the buys; a dev buy plus one more buy also fits one [multi-action transaction](./local-actions#launch-and-buy-in-one-go). ## Launch on StonkFun [StonkFun](https://www.stonkfun.xyz) launches coins priced in tokenised stocks, pre-IPO stocks, currencies or SOL. Their hosted flow is wrapped here: you get a quote, sign one payment transaction, StonkFun mints and lists the coin. Trading it afterwards is the [trade endpoint](/#quoted-coins) like any other coin. 1. `GET /api/stonkfun/pairs` lists what a launch may be priced in; pick a `quoteMint` from it. 2. `POST /api/stonkfun/launch` with `publicKey`, `quoteMint`, `name`, `symbol`, `logo` (a data URL), `mode` (`standard` or `reward`) and `devBuySol` or `devBuyPercent`. The answer carries `paymentTransaction`, `signedQuote` and `expiresAt`. 3. Sign `paymentTransaction` within ten minutes and `POST /api/stonkfun/launch/submit` with `{ "signedQuote", "signedTransaction", "logo" }`; poll `GET /api/stonkfun/launch/{paymentSignature}` until `completed`. StonkFun sets the launch fee (about 0.29 SOL, in `payment.feeSol`); shrine takes nothing on a launch. Never pay twice: a second payment is a second coin. ## Try it Drop the coin's image, fill in the name and symbol, paste your wallet **private key**, and press **Launch**. The key stays in your browser: it signs there, and only the signed transaction goes to the relay. The image and details are uploaded for you. **This launches a real coin on mainnet.** --- # Live Solana Memecoin OHLCV Chart Example, Open Source > A dependency-free candlestick chart for any Solana memecoin, built on subscribe_ohlcv and ohlcv_history with KLineChart, with an in-browser demo and the source on GitHub. import useBaseUrl from '@docusaurus/useBaseUrl'; # OHLCV chart A small, framework free demo that streams live 1 second candles over [`subscribe_ohlcv`](../subscribe-ohlcv) (seeded with [`ohlcv_history`](../ohlcv-history)) and renders them with [KLineChart](https://github.com/klinecharts/KLineChart). History works without a key once a minute; live candles need a key, a free one from the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk) (one token) or [your plan's](/fees#the-key) (every token). It also includes a "pick a live token" button that grabs a freshly launched pool from the free [`subscribe_new_tokens`](../subscribe-new-tokens) stream. The chart below is the same app running in **demo mode**: the candles are generated in your browser, with no API call and no key. Switch timeframes and use the drawing toolbar to get a feel for it. To chart a real token, run the app yourself: - Repo: [shrine-ohlcv-chart](https://github.com/shrinetrade/shrine-ohlcv-chart) Live candles need a key (a free one from the Telegram group, or your plan's); the [`ohlcv_history`](../ohlcv-history) seed runs without one once a minute. The demo mode above needs no key and is entirely client side. --- # Historical Replay: Every Solana Memecoin Trade, Archived by the Hour > Free hourly archives of the full-depth data stream - every buy, sell, launch and migration across pump.fun, PumpSwap, Bonk, Meteora, Raydium and Orca - as compressed JSON lines, for backtesting and research. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Historical replay Every event the [full-depth stream](./stream) pushes is also written to an hourly archive. Pull an hour, replay it through the same code that consumes the live socket, and you have a backtest. Free, no key. ``` https://replay.shrine.trade/pump/YYYY/MM/DD/HH.jsonl.zst ``` No script needed. Paste an hour's address into a browser or a download tool and you get the file: ``` https://replay.shrine.trade/pump/2026/09/14/12.jsonl.zst ``` That is 12:00 to 13:00 UTC on 14 September 2026. Decompress it with any zstd tool (`zstd -d`, 7-Zip, or the `zstandard` package) and read it line by line. To see which hours exist, open [`/pump/index.json`](https://replay.shrine.trade/pump/index.json). The hour is UTC. A file appears a few minutes after its hour ends, and the archive runs from the day it was switched on; earlier hours do not exist. ## Browse the bucket The archive lives in a public Cloudflare R2 bucket. Object storage has no directory listing, so the bucket keeps its own: [`https://replay.shrine.trade/pump/index.json`](https://replay.shrine.trade/pump/index.json) lists every hour it holds, oldest first, and is rewritten after each upload. ```json { "base": "https://replay.shrine.trade/pump", "pattern": "/.jsonl.zst", "count": 312, "hours": ["2026/09/11/07", "2026/09/11/08", "…"] } ``` Join `base`, an entry of `hours` and `.jsonl.zst` to get a file. To fetch everything, walk `hours`; to fill a gap, look for the missing entry. ## Format One JSON object per line, [zstd](https://facebook.github.io/zstd/) compressed. Each line is exactly the live `stream` event, with one extra field: `localTimestamp`, the millisecond our server saw the transaction, which the live stream does not carry. Everything else, `signature`, `block`, `timestamp`, `action`, `protocol`, `mint`, `breakdown`, `tradersInvolved`, reserves and the launch authorities, is as documented on the [stream page](./stream). An hour is a few hundred megabytes compressed and a couple of gigabytes unpacked. Stream it rather than loading it whole. ## Copy this ```js // Node 22.15+ has zstd in zlib. Older Node: npm install fzstd. import { createZstdDecompress } from "node:zlib"; import { createInterface } from "node:readline"; import { Readable } from "node:stream"; const url = "https://replay.shrine.trade/pump/2026/09/11/07.jsonl.zst"; const res = await fetch(url); const lines = createInterface({ input: Readable.fromWeb(res.body).pipe(createZstdDecompress()) }); let buys = 0; for await (const line of lines) { const e = JSON.parse(line); if (e.action === "buy" && e.protocol === "PUMPFUN") buys++; } console.log("pump.fun buys that hour:", buys); ``` ```python import io, json, requests, zstandard # pip install zstandard requests url = "https://replay.shrine.trade/pump/2026/09/11/07.jsonl.zst" with requests.get(url, stream=True) as r: reader = zstandard.ZstdDecompressor().stream_reader(r.raw) buys = 0 for line in io.TextIOWrapper(reader, encoding="utf-8"): e = json.loads(line) if e["action"] == "buy" and e["protocol"] == "PUMPFUN": buys += 1 print("pump.fun buys that hour:", buys) ``` ```bash curl -s https://replay.shrine.trade/pump/2026/09/11/07.jsonl.zst | zstd -d | head -3 ``` ## Notes - **Same shape as live.** Code written against `subscribe_stream` runs unchanged on the archive; feed it lines instead of socket events. - **Filter early.** Every transaction on every launchpad is in there. Keep the protocols and actions you care about and drop the rest while streaming. - **Ordering.** Lines are in the order events were seen, which is block order with the occasional neighbour swapped. Sort by `block` then `signature` when it matters. - **Completeness.** Compare `block` numbers when an hour has to be provably whole. --- # GET /metadata: Solana Token and Pool Metadata by Mint > Look up any Solana memecoin by mint or pool: name, symbol, decimals, supply, launchpad, quote asset and the active pool address after migrations. Free and keyless. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import TryIt from '@site/src/components/TryIt'; # `GET /metadata` Everything known about a coin in one read: name, symbol, decimals, supply, launchpad, quote mint and the live pool address; the image, description and socials from its metadata JSON; the creator and launch time; whether it graduated and where to; what the mint account says (token program, authorities, Token-2022 extensions and any transfer fee); and for a pump.fun coin its curve with the creator fee. Pass exactly one of `mint` or `pool` (passing both is fine; `pool` wins). Keyless. | | | |---|---| | **Method** | `GET` | | **Auth** | none needed; [your plan's key](/fees#the-key) raises the limits | ## Example ```js // Pass exactly one of `mint` or `pool`: const params = new URLSearchParams({ mint: "26ttsGMZeMGv6rLAaYwvdtj4671wJJjc55UGNpTVLbRL", // or: pool: "9EHowJR1dph68EGpHNA8SZbpbVYqD3kx5NLcYVWrCFUy" }); const res = await fetch(`https://sol.shrine.trade/metadata?${params}`); const meta = await res.json(); console.log(meta); ``` ```python import requests res = requests.get( "https://sol.shrine.trade/metadata", params={"mint": "26ttsGMZeMGv6rLAaYwvdtj4671wJJjc55UGNpTVLbRL"}, ) print(res.json()) ``` ## Response ```json { "pool": "9EHowJR1dph68EGpHNA8SZbpbVYqD3kx5NLcYVWrCFUy", "mint": "26ttsGMZeMGv6rLAaYwvdtj4671wJJjc55UGNpTVLbRL", "quote": "BMKdM4yUxX12moFqVk195k7coMbaybd4RUKCUdm7D1Sk", "decimals": 6, "program": "PUMPSWAP", "total_supply": 1000000000, "active": true, "name": "Proof Of Quantum", "symbol": "Quantum", "uri": "https://metadata.j7tracker.io/m/Oq2myOORpA", "image": "https://metadata.j7tracker.io/m/Oq2myOORpA-img", "description": "", "twitter": "https://x.com/POQuantum/status/2108240662265012379", "telegram": "", "website": "https://starkrdp.net/", "metadata_fetched": true, "creator": "9xLUBtnozQ3eosq61oSuKjKw9uLGLyybwEHZsMr2b8G", "created_at": 1791478908, "graduated": true, "graduated_pool": "9EHowJR1dph68EGpHNA8SZbpbVYqD3kx5NLcYVWrCFUy", "graduated_at": 1791479381, "token_program": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", "mint_authority": null, "freeze_authority": null, "token_extensions": ["metadataPointer", "tokenMetadata"], "transfer_fee_bps": null, "transfer_fee_max": null, "pumpfun": { "bonding_curve": "4FjdFepnY4DdqnUxLNkKrKEYEHhB6jSehKcXv9gBasNm", "creator": "9xLUBtnozQ3eosq61oSuKjKw9uLGLyybwEHZsMr2b8G", "quote_mint": "BMKdM4yUxX12moFqVk195k7coMbaybd4RUKCUdm7D1Sk", "creator_fee_bps": 0, "mayhem_mode": false, "holder_reward": false, "cashback": false, "complete": true, "virtual_token_reserves": 0, "virtual_sol_reserves": 0, "real_token_reserves": 0, "real_sol_reserves": 0 } } ``` | Field | Notes | |---|---| | `image`, `description`, `twitter`, `telegram`, `website` | From the coin's metadata JSON behind `uri`, fetched once in the background right after the launch. `metadata_fetched` is `false` while that is still on its way (ask again a moment later); empty strings mean the JSON has no value. | | `creator`, `created_at` | The launching wallet and the launch time (unix seconds). | | `graduated`, `graduated_pool`, `graduated_at` | Whether the coin left its curve, where it trades now and when. | | `token_program`, `mint_authority`, `freeze_authority`, `token_extensions` | Read from the mint account. Both authorities should be `null` on an honest coin; `token_extensions` lists the Token-2022 extensions the mint carries. | | `transfer_fee_bps`, `transfer_fee_max` | The tax: a Token-2022 transfer fee in basis points and its cap in raw units, `null` when the mint has none. | | `pumpfun` | Only for pump.fun coins: the bonding curve, the creator fee in basis points, mayhem mode, holder rewards, cashback, whether the curve completed, and its reserves while it is live. `null` for other launchpads. | ## 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. --- # ohlcv_history: Recent 1-Second Candles for a Solana Token > Fetch the most recent candles for a pool in one call to seed a chart, then keep it live with subscribe_ohlcv. Up to 500 one-second bars, keyless once a minute, more often with a plan key. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import Admonition from '@theme/Admonition'; # `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). Without a key it answers once a minute per IP address; sooner, the ack is `rate_limited` with `retryAfterMs`. With [your plan's key](/fees#the-key) in the handshake it answers every second (Lite) or five times a second (Developer). | | | |---|---| | **Channel** | Socket.IO event with ack | | **Auth** | none for once a minute; your plan's key, `auth: { api_key }`, for more | | **Cost** | Free: 1 a minute · Lite: 1 a second · Developer: 5 a second | ## 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. ## Notes - Works without a key once a minute per IP address. With your plan's key in the handshake, `auth: { api_key: … }`, every second (Lite) or five times a second (Developer). Sooner than that, the ack is `rate_limited` with `retryAfterMs`. - Returns up to `limit` of the most recent candles. For anything older, the [hourly replay files](./historical-replay) hold every trade. --- # pump.fun Data API: Free Real-Time Solana Trades, Prices, Launches and 1-Second Candles > Free, keyless REST and WebSocket data for every Solana launchpad and DEX: live trades, prices, new tokens, migrations, SOL/USD and 1-second OHLCV candles, plus an hourly archive of every trade. The Advanced Data Stream and replay come with Lite, the shred stream with Developer. # Data API Live + historical data across **PumpFun, PumpSwap, Bonk and StonkFun (Raydium LaunchLab), Meteora DBC and DAMM v2, Raydium (CPMM/CLMM/AMM v4) and Orca Whirlpool**. Most of it is **free and keyless**: the per-token streams, the launch and migration feeds, candle history, [`GET /metadata`](./metadata) and [`GET /sol-price`](./sol-price) need no key, no account and no sign-up. Live candles ([`subscribe_ohlcv`](./subscribe-ohlcv)) take a free key, which you ask for in the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk). The Lite plan adds the [Advanced Data Stream](./stream) and the [24-hour replay](./resume), Developer the [Ultra Fast Shred Stream](/ultrafast/overview). See [Plans and limits](/fees). ## 30-second start No key needed - connect and subscribe: ```js import { io } from "socket.io-client"; const socket = io("https://sol.shrine.trade"); socket.on("token_update", (u) => console.log(u.pool, u.priceUSD)); socket.emit("subscribe", { mint: "Gc6r…pump" }); ``` Need the past as well as the present? [Historical replay](./historical-replay) archives every stream event by the hour, free. ## Keys The free streams and reads need no key, with one exception: live candles ([`subscribe_ohlcv`](./subscribe-ohlcv)) need a key on every plan; on Free that is a free key you ask for in the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk). Elsewhere a key is optional and raises the limits to your plan's: candles for every token instead of one, history every second instead of once a minute, more sockets. The [Advanced Data Stream](./stream) and the [24-hour replay](./resume) need a key from Lite or Developer, [Ultra Fast Shred Stream](/ultrafast/overview) one from Developer. The limits per plan are on [Plans and limits](/fees#data). Get your key on the [dashboard](https://shrine.trade/api/dashboard/) once you have a plan, and pass it in the Socket.IO handshake: ```js import { io } from "socket.io-client"; const socket = io("https://sol.shrine.trade", { auth: { api_key: "sk_…" }, }); ``` A connection with no key gets everything on the Free plan. A connection that *does* present a key must present a valid one: unknown or disabled keys are rejected at the handshake. Over a limit, the call's ack says so: `plan_required` (not in your plan), `limit_exceeded` (every socket your plan allows for that stream is in use, or a second token of candles on Free) or `rate_limited` with `retryAfterMs`. Each carries `plan` and, below Developer, `upgrade`: see [when you reach a limit](/fees#errors). ## Errors All errors are JSON with the same shape: ```json { "error": "not_found" } ``` | Status | `error` | Meaning | |---|---|---| | 400 | `mint_or_pool_required` | Missing required param. | | 401 | `unknown_key` / `key_disabled` | The key is not recognized, or a new one replaced it. Send no key for Free. | | 403 | `plan_required` | Not in your plan; `plan` names the plan that has it. | | 404 | `not_found` | Pool/mint isn't registered (yet). | | 429 | `rate_limited` | Past your plan's rate ([Plans and limits](/fees#data)). `Retry-After` and `retryAfterMs` say when to retry. | | 503 | `unavailable` | [`GET /sol-price`](./sol-price) before the first SOL/USD tick (cold start); retry shortly. | WebSocket subscribe errors come as a callback ack: `{ error: "…", message: "…" }`. Live candles without a key answer `api_key_required`; past your plan's limit, the candle events answer `limit_exceeded` or `rate_limited`; a stream whose sockets are all in use answers `limit_exceeded`; a stream your plan doesn't have answers `plan_required`. ## Limits | | Value | |---|---| | Max concurrent Socket.IO subscriptions per connection | 50 | | Open sockets per stream type | your plan's: 2 on Free, 5 on Lite, 50 on Developer. Counted per key, or per IP without one | | REST | [Metadata](./metadata) and [SOL price](./sol-price) at your plan's rate; other reads 3 requests a second per IP | | OHLCV history `limit` | 1 – 500 (default 200) | The stream types are `subscribe` (price), `subscribe_trades`, `subscribe_ohlcv`, `subscribe_new_tokens`, `subscribe_migrations`, `subscribe_sol_price` and `subscribe_stream`. Following several pools on one type from one socket is fine and takes one socket of that type; a socket past your plan's number for a type gets `limit_exceeded`. The trade endpoints have their own rates per plan ([Plans and limits](/fees#trading)) and answer `rate_limited` with `Retry-After: 1`. --- # Replay: 24 Hours of Stream, Resumable After a Disconnect > Nothing on the stream is lost to a disconnect: our feed replays up to 24 hours from the chain, your socket resumes from a slot, and every hour is archived. import Admonition from '@theme/Admonition'; # 24-hour replay stream You can replay 24 hours of the stream. Whatever drops between the chain and your bot, the events arrive late instead of never. - **Our feed** reconnects from the last slot it saw and replays everything it missed from the chain, for gaps of up to 24 hours. Every stream on this API is built from that feed, so a stream never has a hole in it. - **Your socket** resumes from a slot. Pass `sinceSlot` on reconnect and the last two minutes are sent to you first, marked `replayed`, then live events follow. - **Everything older** is in the [hourly replay files](/data-api/historical-replay): every event of every hour, the same format as the stream, for backtests and for any gap longer than two minutes. ## Resume from a slot Every `stream` event carries `block`, the slot it was in. Keep the last one you handled. On reconnect, pass it as `sinceSlot` and everything published since that slot is sent to you first, through your usual filter, then the live events follow: ```js import { io } from "socket.io-client"; const socket = io("https://sol.shrine.trade", { auth: { api_key: "sk_…" } }); let lastSlot = 0; socket.on("connect", () => { socket.emit("subscribe_stream", { protocols: ["PUMPFUN", "PUMPSWAP"], sinceSlot: lastSlot || undefined }, (ack) => { // ack.replayed events were sent before this ack; ack.oldestSlot is how far back we could reach if (lastSlot && ack.oldestSlot > lastSlot) console.warn("gap before slot", ack.oldestSlot); }); }); socket.on("stream", (e) => { lastSlot = Math.max(lastSlot, e.block); if (e.replayed) { /* delivered late, not live */ } // ... }); ``` A replay runs up to your plan's number at once per key: one on Lite, three on Developer. Another `sinceSlot` while those run answers `limit_exceeded`; subscribe without it, or retry when one ends. Replayed events are the original events with `"replayed": true` added, in their original order. The acknowledgement carries `replayed` (how many were sent) and `oldestSlot` (the oldest slot still on the socket). If `oldestSlot` is newer than your `sinceSlot`, the stretch before it is in the hourly files. The resume includes `sinceSlot` itself, so the last slot you handled is sent again. Deduplicate on `signature` plus `pool` if a double counts for you. --- # GET /sol-price: Live SOL/USD Price Endpoint > One REST call for the current SOL/USD price, the same rate the sol_price stream pushes, read from the canonical SOL/USDC pool on chain. Free, keyless, no rate card. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import TryIt from '@site/src/components/TryIt'; # `GET /sol-price` The current **SOL/USD** price as a one-shot REST read - the same value the [`subscribe_sol_price`](./subscribe-sol-price) stream pushes, derived from the deepest on-chain SOL/USDC market. Use this when you just need the rate once (to convert a WSOL-quoted price to dollars) and don't want to hold a WebSocket open. Takes no parameters. **Free** - no API key needed. | | | |---|---| | **Method** | `GET` | | **Auth** | none needed; [your plan's key](/fees#the-key) raises the limits | | **Cost** | Free | ## Example ```js const res = await fetch("https://sol.shrine.trade/sol-price"); // no key needed const { priceUSD, time } = await res.json(); console.log(`SOL is $${priceUSD}`); ``` ```python import requests res = requests.get("https://sol.shrine.trade/sol-price") # no key needed print(res.json()) ``` ## Response ```json { "priceUSD": 182.45, "time": 1779812600 } ``` `priceUSD` is USD per SOL; `time` is the unix second of the underlying tick (`0` if the gateway hasn't attached one yet). ## Try it ## Notes - One global value - there is no `mint` / `pool` argument. - Returns `503 unavailable` in the brief window after a cold start, before the first SOL/USD tick has been observed - retry shortly. - Need it continuously? Use the [`subscribe_sol_price`](./subscribe-sol-price) WebSocket stream instead: it's free and pushes a new value on every move, versus polling this endpoint. --- # Advanced Data Stream: Every Solana Memecoin Trade, Launch and Migration in One Feed > The full-depth firehose. One enriched event per transaction across every protocol - trades with per-swap breakdown and reserves, launches with mint authorities, migrations, liquidity, fee claims. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import TryIt from '@site/src/components/TryIt'; import useBaseUrl from '@docusaurus/useBaseUrl'; import Admonition from '@theme/Admonition'; # Advanced Data Stream The per-pool streams answer "what happened to this token". This one answers "what happened in this transaction", for every transaction on every launchpad we index. Server emits **`stream`**. Comes with Lite and Developer: connect with your plan's key. Use it for copy-trading, scam filtering and anything that needs the whole market rather than one pool. If you only care about a few tokens, [`subscribe_trades`](./subscribe-trades) is cheaper for you and for us. | | | |---|---| | **Channel** | Socket.IO event | | **Auth** | your plan's key in the handshake, `auth: { api_key }` | | **Emits** | `stream` | | **Plan** | Lite (2 sockets) and Developer (5 sockets) | | **Filter** | optional `protocols` and `actions` lists | Disconnected? Reconnect with `sinceSlot` and everything since that slot is sent first; see [Replay and resume](/data-api/resume). For the same transactions before they execute, see [Ultra Fast Shred Stream](/ultrafast/overview). Subscribe once with no filter and you have the whole market: every trade, launch, migration and liquidity change on every launchpad we index, one event per transaction. Narrow it with `protocols` and `actions` when you only want part of it. ## How it compares The same pump.fun transaction on every feed: Ultra Fast Shred Stream on the New York region first, the New York Advanced Data Stream 6 ms later, pumpapi.io 16 ms later, PumpPortal 115 ms later ## Example ```js import { io } from "socket.io-client"; const socket = io("https://sol.shrine.trade", { auth: { api_key: "sk_…" } }); // Everything, or narrow it: protocols and/or actions. socket.emit("subscribe_stream", { protocols: ["PUMPFUN", "PUMPSWAP", "STONKFUN"], actions: ["buy", "sell", "create"], }); socket.on("stream", (e) => { if (e.action === "create" && (e.mintAuthority || e.freezeAuthority)) return; // not a clean launch if (e.action === "buy") console.log(e.mint, e.tradersInvolved, e.quoteAmount, e.price); }); // later, to stop (free): socket.emit("unsubscribe_stream"); ``` ```python import socketio sio = socketio.Client() sio.connect("https://sol.shrine.trade", auth={"api_key": "sk_…"}) sio.emit("subscribe_stream", {"actions": ["create", "migrate"]}) @sio.on("stream") def on_stream(e): print(e["action"], e["protocol"], e.get("mint"), e.get("tradersInvolved")) sio.wait() ``` ## Actions | `action` | When | Protocols | |---|---|---| | `buy` / `sell` | Any swap. One event per pool per transaction; several swaps in one transaction are aggregated with a per-swap `breakdown`. | every launchpad | | `create` | A launchpad mint. | `PUMPFUN`, `PUMPFUN_MAYHEM`, `BONK`, `STONKFUN`, `METEORA_DBC` | | `createPool` | An AMM pool opened on an existing token. | `PUMPSWAP`, `RAYDIUM`, `RAYDIUM_CLMM`, `METEORA`, `METEORA_DLMM`, `ORCA` | | `migrate` | A curve graduating into its AMM. | `PUMPFUN`→PumpSwap, `BONK` and `STONKFUN`→Raydium, `METEORA_DBC`→DAMM v2 | | `curveComplete` | A DBC curve filled, before migration. | `METEORA_DBC` | | `curveComplete` | A pump.fun curve filled by the trade in this transaction, before migration. | `PUMPFUN` | | `add` / `remove` | Liquidity provided or withdrawn. | PumpSwap, Raydium CPMM and AMM v4, DAMM v2, DLMM, Orca | | `claimCreatorFees` | A creator collecting fees. | `PUMPFUN`, `PUMPSWAP` | | `sweep` | Fees a trade kept on its curve or pool paid out to the protocol or the creator's vault. Not a trade: no price, no volume. | `PUMPFUN`, `PUMPSWAP` | ## Payload Every event carries `signature`, `block`, `timestamp`, `action`, `protocol` and `txSigner`, the fee payer. `block` is the slot and `timestamp` is the chain's own time for that slot, the second `getBlockTime` answers for it, on every launchpad; two trades in one slot carry the same second. `txSigner` is not always the trader: bots route through relayers. For copy-trading use `tradersInvolved`. ### Trades ```json { "signature": "…", "block": 445627693, "timestamp": 1788976012, "action": "buy", "protocol": "PUMPFUN", "txSigner": "…", "pool": "…", "mint": "…", "quoteMint": "So111…", "tokenAmount": 31952.74, "quoteAmount": 0.0161, "price": 0.000000504, "marketCapQuote": 504.1, "tokensInPool": 1041000000, "quoteInPool": 30.5, "tradersInvolved": ["…"], "breakdown": [ { "trader": "…", "action": "buy", "tokenAmount": 31952.74, "quoteAmount": 0.0161, "price": 0.000000504 } ] } ``` `price` and the pool reserves are the post-trade state where the launchpad reports it. `marketCapQuote` is price times supply, in the quote asset. Amounts are in UI units. Two pump.fun cases carry extra fields: - **A buy that completes the curve.** The event carries `"completesCurve": true` and the `breakdown` entry that emptied the curve is tagged `"stage": "curve"`. A v3 buy may take more than the curve has left: it empties the curve at the curve's price, then buys the rest from the tokens the migration deposits, at the price that pool opens with. That second part is a further `breakdown` entry for the same trader tagged `"stage": "postComplete"`; the event's `tokenAmount` and `quoteAmount` are then the buyer's total, and `price` and the reserves are the pool's opening state. A `curveComplete` event follows in the same transaction. - **A multi-hop route.** A PumpSwap `multi_hop_swap` trades through several pools and curves in one instruction, for coins paired with another pump coin. Each hop is a normal `buy` or `sell` on its own pool, tagged `"via": "multi_hop"` on the event and on its `breakdown` entries. The middle coins net to zero for the trader; the volume on every pool is real. ### Fee sweeps ```json { "action": "sweep", "protocol": "PUMPFUN", "mint": "…", "pool": "…", "quoteMint": "So111…", "recipient": "…", "fee": "creator", "bucket": 1, "amount": 0.000888889, "amountRaw": 888889 } ``` v3 curve trades and v2 pool trades keep the protocol fee and the creator fee where the trade happened; a sweep pays them out later. `fee` is `protocol` (`bucket` 0) or `creator` (`bucket` 1), `amount` is in the quote asset. Fee income is in the trades; a sweep is the payout, not new income. ### Creates ```json { "action": "create", "protocol": "PUMPFUN", "mint": "…", "pool": "…", "quoteMint": "So111…", "creator": "…", "name": "…", "symbol": "…", "uri": "…", "decimals": 6, "supply": 1000000000, "tokensInPool": 1073000000, "quoteInPool": 30, "initialBuy": { "quoteAmount": 0.5, "tokenAmount": 17000000 }, "mintAuthority": null, "freezeAuthority": null, "tokenExtensions": [] } ``` `mintAuthority` and `freezeAuthority` should both be `null` on an honest launch; a value means supply can be minted or wallets frozen. `tokenExtensions` lists Token-2022 extensions by name; `transferFeeConfig` and `permanentDelegate` deserve a look outside trusted launchpads. `initialBuy` is the creator's own buy in the launch transaction, `null` if there was none. Authorities and extensions are read from the mint account as it was created; if the mint predates the stream they may be absent. ### Migrations and pools Migrations carry `fromPool`, `toPool` and `toProtocol`. Pool creates carry `pool`, `creator` and, when known, `mint` and `quoteMint`. Liquidity events carry `pool`, `owner` and the raw amounts of both sides (`amountARaw`, `amountBRaw`); the side-to-mint mapping follows the launchpad's own ordering. ## Try it ## Notes - One connection per client, please. Everything is filtered per socket, so open one socket and subscribe once. - Events for a transaction are emitted in a fixed order: trades, then creates and pool creates, then migrations, liquidity and sweeps, then fee claims. - Every event on this stream is also archived by the hour for backtesting: see [Historical replay](./historical-replay). --- # subscribe_migrations: pump.fun to PumpSwap Migration Feed > Real-time feed of bonding-curve graduations: the moment a pump.fun coin migrates to PumpSwap, with the old and new pool addresses. Free and keyless. 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** | none needed; [your plan's key](/fees#the-key) raises the limits | | **Emits** | `migration` | | **Cost** | Free | ## 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://sol.shrine.trade") # no key needed 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. --- # subscribe_new_tokens: Real-Time pump.fun and Solana Launchpad Token Launches > Get every new token the second it launches on pump.fun, PumpSwap, Bonk, Meteora, Raydium, Orca or StonkFun, with name, symbol, creator and quote asset. Free, keyless, one socket. 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`, `STONKFUN` (one room per protocol; re-subscribe to change the set). FREE `new_token` events are delivered at no charge on **every protocol**, and no API key is needed - you can connect keyless and stay subscribed indefinitely. | | | |---|---| | **Channel** | Socket.IO event | | **Auth** | none needed; [your plan's key](/fees#the-key) raises the limits | | **Emits** | `new_token` | | **Cost** | Free, every protocol | ## 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://sol.shrine.trade") # no key needed 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 - `STONKFUN` is StonkFun on Raydium LaunchLab: coins paired with tokenized stocks (xStocks), pre-IPO tokens, crypto assets or SOL. `quote` tells you the pair; it is often not WSOL. Reward launches are Token-2022 mints with a transfer fee. - 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, Bonk and StonkFun 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). --- # subscribe_ohlcv: Live 1-Second OHLCV Candles for Solana Memecoins > Stream 1-second open, high, low, close and volume candles for any Solana memecoin pool over Socket.IO, the finest resolution available: one token with a free key from Telegram, every token with a plan key. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import TryIt from '@site/src/components/TryIt'; import Admonition from '@theme/Admonition'; # `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`. **Free** with a free key from the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk), one token at a time; with your plan's key, every token. Live candles need a key. On the Free plan, ask for a free one in the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk): it follows one token's candles at a time, so unsubscribe before you switch, or a second subscribe answers `limit_exceeded`. With [your plan's key](/fees#the-key) from the dashboard, subscribe to every token you like. Pass either in the handshake, `auth: { api_key }`; without one the call answers `api_key_required`. | | | |---|---| | **Channel** | Socket.IO event | | **Auth** | a key in the handshake, `auth: { api_key }`: a free one from the Telegram group (one token) or your plan's (every token) | | **Emits** | `ohlcv` | | **Cost** | Free: 1 token, 1 socket · Lite: every token, 3 sockets · Developer: every token, 10 sockets | ## Example ```js import { io } from "socket.io-client"; const socket = io("https://sol.shrine.trade", { auth: { api_key: "sk_…" } }); // optional: your plan's key, for every token 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://sol.shrine.trade", auth={"api_key": "sk_…"}) # optional: your plan's key, for every token 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 - On Free, a free key from the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk); nothing is metered. - Granularity is 1s; aggregate client-side if you want 1m/5m/1h. - Seed the initial window with [`ohlcv_history`](./ohlcv-history) (no key needed once a minute), then keep it live here. - For per-trade detail use [`subscribe_trades`](./subscribe-trades). - For a full working chart see the [chart example](./examples/ohlcv-chart). --- # subscribe: Live Solana Token Price and Market Cap Feed over WebSocket > Subscribe to a mint or pool and receive price, market cap, USD value and reserves on every trade over Socket.IO. Follows the token through migration. Free and keyless. 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** | none needed; [your plan's key](/fees#the-key) raises the limits | | **Emits** | `token_update` | | **Cost** | Free | ## 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://sol.shrine.trade"); // no key needed 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://sol.shrine.trade") # no key needed 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 - Free and keyless, like every 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). --- # subscribe_sol_price: Live SOL/USD Price Stream > A push of the SOL/USD rate every time the canonical SOL/USDC pool trades, over Socket.IO, for pricing memecoins in dollars without another provider. Free and keyless. 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, no API key needed. | | | |---|---| | **Channel** | Socket.IO event | | **Auth** | none needed; [your plan's key](/fees#the-key) raises the limits | | **Emits** | `sol_price` | ## Example ```js import { io } from "socket.io-client"; const socket = io("https://sol.shrine.trade"); // no key needed socket.on("sol_price", ({ priceUSD }) => { console.log(`SOL is $${priceUSD}`); }); 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["priceUSD"]) sio.connect("https://sol.shrine.trade") # no key needed sio.emit("subscribe_sol_price", callback=lambda ack: print("subscribed:", ack)) sio.wait() ``` ## Response - `sol_price` ```json { "priceUSD": 182.45, "time": 1779812600 } ``` `priceUSD` 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). - Just need the rate once? Use the [`GET /sol-price`](./sol-price) one-shot REST read instead of holding a socket open. --- # subscribe_trades: Real-Time Solana Memecoin Trade Feed over WebSocket > Every buy and sell on a pool the moment it lands: trader, amounts, price and signature, streamed over Socket.IO for pump.fun, PumpSwap, Bonk, Meteora, Raydium and Orca. Free and keyless. 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** | none needed; [your plan's key](/fees#the-key) raises the limits | | **Emits** | `trade` | | **Cost** | Free | ## Example ```js import { io } from "socket.io-client"; const socket = io("https://sol.shrine.trade"); // no key needed 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://sol.shrine.trade") # no key needed 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). - `timestamp` - the chain's time for the trade's slot, in seconds: the value `getBlockTime` returns for that slot, on every launchpad. ## 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 - Free and keyless - `trade` messages are never billed. - We only publish trades for pools with at least one live subscriber, so the firehose stays cheap. - Trade order within a pool is preserved. - Want just price/mcap? Use the lighter [`subscribe`](./subscribe-price). --- # FAQ: pump.fun API Keys, Fees, Regions and Speed > Short answers on keys, fees, regions, latency, slippage, sells, errors and what to use when: the Local Trade API, Lightspeed or Ultra Fast Shred Stream. # FAQ
Do I need an API key? Not on the Free plan. The [Local Trade API](/), launches, fee claims, wallet tools, the per-token streams, candle history and the REST reads work with no key and no account; live candles need a free key, which you ask for in the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk). A key comes with Lite and Developer, on the [dashboard](https://shrine.trade/api/dashboard/): add it to any request for your plan's limits and 0% fee, and the paid features need it, the [Advanced Data Stream](/data-api/stream), the [24-hour replay](/data-api/resume), [Lightspeed](/lightspeed/overview) and [Ultra Fast Shred Stream](/ultrafast/overview). See [Plans and limits](/fees).
What does it cost? On the Free plan, 0.25% of each trade, taken inside the transaction in the asset the trade settles in; everything else on Free costs nothing. Lite and Developer trade at 0% and add the faster routes; their prices are on the [pricing page](https://shrine.trade/api/pricing/). Tips to landing providers on Lightspeed are yours to set and go to the provider. See [Plans and limits](/fees).
Local Trade, Lightspeed or Ultra Fast Shred Stream: which one? - **Local Trade API** when you hold the key: we build, you sign on your machine and send. Three hops, your custody of the key. - **Lightspeed** when speed decides: one request builds, signs with your Lightspeed wallet and races every landing provider. The wallet is encrypted under your API key; nobody opens it without the key. - **Ultra Fast Shred Stream** is data, not trading: pump.fun and PumpSwap transactions read from the shreds before they execute, so a bot reacts a block earlier. Pair it with Lightspeed.
Where should my bot run? Next to the API. Frankfurt is the main region, with Amsterdam and New York beside it; the region picker at the bottom of this sidebar rewrites every address in the docs to the one you choose. From the same city a request takes about a millisecond, from across the Atlantic 80 to 100 ms. See [Regions](/regions).
How fast is the stream compared with others? Measured from the US on 2026-10-08: Ultra Fast Shred Stream on the New York region had each pump.fun transaction first; our New York Advanced Data Stream came 6 ms later, pumpapi.io's event, served from Frankfurt, 16 ms later, PumpPortal's new-token feed 115 ms later. Read from Frankfurt the two executed feeds tie. The table and the image are on the [Advanced Data Stream](/data-api/stream) page.
My buy reverted. Why? The usual reasons, in order: slippage too tight for a coin that moves between your request and the block; the bonding curve completed and the coin now trades on PumpSwap (the response tells you, retry or pass `pool: "pumpswap"`); a quoted coin (priced in USDC or a stock) bought without holding the quote. On Lightspeed, copies of a trade that lose the race revert on purpose: only one copy fills, the rest fail before moving funds. See the [Lightspeed trade page](/lightspeed/trade).
How do I sell everything? `amount: "100%"` on a sell, on both the Local Trade API and Lightspeed. `"50%"` sells half. A token amount sells that many tokens.
What slippage should I use? 5% is the default and fine for a coin that is already trading. A fresh launch wants more, 15 to 25%, because the dev buy and the first snipes land in the same blocks as yours. Lightspeed's response shows the minimum the transaction enforces.
I was disconnected. Did I miss trades? Reconnect to the Advanced Data Stream with `sinceSlot` set to the last slot you handled and everything since is sent first, marked `replayed`. About two minutes are kept; older gaps are in the hourly [replay files](/data-api/historical-replay). See [Replay and resume](/data-api/resume).
What are the rate limits? They depend on the plan: on Free, 1 to 3 requests a second per endpoint and 2 sockets for the streams; Lite and Developer raise them, up to 50 sockets and 20 requests a second. Every limit is on [Plans and limits](/fees). Per connection: 50 tokens and 50 wallets on a stream, 500 mints and wallets on Ultra Fast Shred Stream. A `429` carries no wait time; a short pause is enough.
Can I run several wallets? Yes. Each Lightspeed account is its own wallet with its own key; create as many as you need (20 an hour per IP). On the Local Trade API the wallet is whatever key signs.
Which launchpads are covered? pump.fun and PumpSwap, Bonk and StonkFun on LaunchLab, Meteora DBC and DAMM v2, Raydium CPMM and AMM v4, Orca. Pass the mint and the API finds the venue; `pool` forces one.
Something is wrong with the API. Where do I ask? The [Telegram group](https://t.me/+nEqAowTK8BZhZjFk). Include the request, the response and the time; a transaction signature if there is one.
--- # Plans and Limits: Free, Lite and Developer for the pump.fun API > Three plans for the Solana API. Free: trading, launches, wallet tools and live data with no key, 0.25% per trade. Lite: 0% fee, Lightspeed, the Advanced Data Stream, candles for every token. Developer: all 8 Lightspeed lanes, the block 0 lane, Ultra Fast Shred Stream, every region and a private link. # Plans and limits Three plans. **Free** needs no key and no account: trade, launch, move tokens and read live data straight away, at 0.25% per trade. **Lite** and **Developer** are monthly, paid in SOL, and take the trade fee to 0%. Compare them side by side on the [pricing page](https://shrine.trade/api/pricing/), and get a plan from the [dashboard](https://shrine.trade/api/dashboard/). | | Free | Lite | Developer | |---|---|---|---| | Price | free | [see pricing](https://shrine.trade/api/pricing/) | [see pricing](https://shrine.trade/api/pricing/) | | Trade fee | 0.25% | **0%** | **0%** | | API key | not needed (live candles: a free key) | yes | yes | These plans are for Solana. The APIs for [BNB Chain](https://shrine.trade/flap/), [Robinhood Chain](https://shrine.trade/pons/) and [Arc](https://shrine.trade/arc/) stay free to use, with a fee per trade and no subscription. ## The key Everything on Free works without a key, except live candles: for those, ask for a free key in the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk). Elsewhere a key is optional: add your plan's key and the same request runs at your plan's limits, at 0%. Paid features need it. You get one key per account, on the [dashboard](https://shrine.trade/api/dashboard/), once you have a plan. Send it with every request: - **REST:** the `x-api-key` header. - **WebSocket (Socket.IO):** `auth: { api_key }` in the handshake. - **Ultra Fast Shred Stream:** `?key=` on the URL, or the `x-api-key` header. ```js // REST await fetch("https://sol.shrine.trade/api/local-trade", { method: "POST", headers: { "content-type": "application/json", "x-api-key": "sk_…" }, body: JSON.stringify({ /* … */ }), }); // WebSocket const socket = io("https://sol.shrine.trade", { auth: { api_key: "sk_…" } }); ``` Keep it secret: anyone with the key uses your plan. Make a new one on the dashboard and the old one stops working. ## Trading | | Free | Lite | Developer | |---|---|---|---| | [Local Trade API](/) | 3 req/s, 0.25% | 3 req/s, **0%** | 10 req/s, **0%** | | [Create token](/create-token) | 1 req/s | 3 req/s | 5 req/s | | [Claim creator fees](/claim-creator-fees) | 1 req/s | 5 req/s | 10 req/s | | [Wallet utilities](/wallet-utilities) | 1 req/s | 3 req/s | 10 req/s | | [Jito bundles](/jito-bundles) | – | 1 req/s | 10 req/s | | [Multi-action transactions](/local-actions) | – | 3 req/s | 10 req/s | A launch is free; its dev buy is a trade and pays the plan's trade fee. ## Lightspeed {/* #lightspeed */} | | Free | Lite | Developer | |---|---|---|---| | [Shrine staked lane](/lightspeed/overview), ~100 ms | – | 3 req/s, **0%** | 10 req/s, **0%** | | [Provider racing](/lightspeed/how-it-works): all 8 SWQoS lanes, ~50 ms | – | – | 10 req/s, **0%** | | [Block 0 lane](/lightspeed/block-zero) | – | – | ✓ | Tips you set go to the landing provider, not to us. A trade goes out from a wallet made on the [dashboard](https://shrine.trade/api/dashboard/) (its `ls_` key in `x-api-key`), or from your own wallet: your plan's key in `x-api-key`, its `privateKey` in the body. See [Trade](/lightspeed/trade). ## Data | | Free | Lite | Developer | |---|---|---|---| | [Metadata](/data-api/metadata) | 1 req/s | 5 req/s | 20 req/s | | [SOL price](/data-api/sol-price) | 1 req/s | 10 req/s | 20 req/s | | Price, trades, SOL price, new tokens and migrations streams | 2 sockets | 5 sockets | 50 sockets | | [Live candles](/data-api/subscribe-ohlcv) | 1 token, 1 socket, free key from Telegram | every token, 3 sockets | every token, 10 sockets | | [Candle history](/data-api/ohlcv-history) | 1 a minute | 1 a second | 5 a second | | [Advanced Data Stream](/data-api/stream) | – | 2 sockets | 5 sockets | | [24-hour replay](/data-api/resume) | – | 1 socket | 3 sockets | | [Ultra Fast Shred Stream](/ultrafast/overview) | – | – | 3 sockets | | [Hourly archive](/data-api/historical-replay) | ✓ | ✓ | ✓ | ## Developer only - **[Every region](/regions):** Frankfurt, Amsterdam and New York. Free and Lite run on Frankfurt. - **[Private link](/private-link):** your server in our datacenter, wired straight to the API. - **A private Telegram group** with the team. ## When you reach a limit {/* #errors */} Every refusal is JSON: what was refused, the plan, and where the plans are. ```json // 403: not in your plan { "error": "plan_required", "plan": "lite", "message": "Jito bundles come with Lite and Developer", "upgrade": "https://shrine.trade/api/pricing/" } // 429: past your plan's rate (also the Retry-After header) { "error": "rate_limited", "plan": "free", "message": "3 requests a second on this endpoint on Free - slow down and retry", "retryAfterMs": 1000, "upgrade": "https://shrine.trade/api/pricing/" } // WebSocket ack: every socket your plan allows for this stream is in use { "error": "limit_exceeded", "plan": "lite", "message": "…", "upgrade": "https://shrine.trade/api/pricing/" } ``` - **`plan`:** on `plan_required`, the cheapest plan that has it; otherwise the plan you are on. - **`upgrade`:** the pricing page; left out on Developer. - **`401 unknown_key` / `key_disabled`:** the key is not one of ours, or a new one replaced it. Send no key to use Free. ## Paying On the [dashboard](https://shrine.trade/api/dashboard/): log in with Phantom or Telegram, pick a plan and pay in SOL. The plan starts as soon as the payment arrives and runs for a calendar month; pay again to add another. A discount code, if you have one, goes in the field under the plans. --- # Guide: Copy Trade Solana Wallets > A complete Node.js copy trader: follow wallets on the Advanced Data Stream, mirror their pump.fun and PumpSwap buys and sells through Lightspeed, size the copies, and cap the risk. # Copy trade Follow one or more wallets, buy what they buy, sell when they sell. The [Advanced Data Stream](/data-api/stream) shows every trade of a wallet on every launchpad we index, and [Lightspeed](/lightspeed/overview) mirrors it in one request. ## What you need - Node.js 22 or newer, `npm install socket.io-client`. - A [Lite or Developer plan](/fees) and its key, for the [Advanced Data Stream](/data-api/stream) and Lightspeed. - A funded [Lightspeed account](/lightspeed/account). - Wallets to follow. The stream itself is the place to find them: watch a few launches, note who buys early and sells in profit. ## The script ```js import { io } from "socket.io-client"; // ─── change these ───────────────────────────────────── const LS_KEY = "ls_…"; const API_KEY = "sk_…"; // your plan's key, from the dashboard const FOLLOW = ["WALLET_1", "WALLET_2"]; // wallets to copy const COPY_RATIO = 0.1; // copy 10% of their size ... const MIN_SOL = 0.01; // ... but at least this const MAX_SOL = 0.5; // ... and at most this const SLIPPAGE = 10; const TIP = 0.0007; const MAX_OPEN = 8; const DRY_RUN = true; // ────────────────────────────────────────────────────── const API = "https://sol.shrine.trade"; const follow = new Set(FOLLOW); const open = new Map(); // mint -> { from } async function lightspeed(body) { if (DRY_RUN) { console.log(" dry-run", body); return { success: true }; } const r = await (await fetch(`${API}/api/lightspeed`, { method: "POST", headers: { "content-type": "application/json", "x-api-key": LS_KEY }, body: JSON.stringify(body), })).json(); if (!r.success) throw new Error(`${r.error}: ${r.message}`); return r; } const socket = io(API, { transports: ["websocket"], auth: { api_key: API_KEY } }); socket.on("connect", () => { // Trades only, every launchpad; the filter below picks the wallets. socket.emit("subscribe_stream", { actions: ["buy", "sell"] }); console.log("following", FOLLOW.join(", ")); }); socket.on("stream", async (e) => { // One stream event per transaction; `breakdown` lists each trader's leg. // A followed wallet's leg is what we copy, whatever router it went through. const leg = (e.breakdown || []).find((b) => follow.has(b.trader)); if (!leg) return; if (leg.action === "buy") { if (open.has(e.mint) || open.size >= MAX_OPEN) return; const sol = Math.min(MAX_SOL, Math.max(MIN_SOL, leg.quoteAmount * COPY_RATIO)); console.log(`${leg.trader.slice(0, 6)} bought ${e.mint.slice(0, 6)} for ${leg.quoteAmount} SOL -> copying ${sol}`); try { await lightspeed({ action: "buy", mint: e.mint, amount: sol, slippage: SLIPPAGE, tip: TIP }); open.set(e.mint, { from: leg.trader }); } catch (err) { console.error(` buy failed: ${err.message}`); } } else if (leg.action === "sell" && open.has(e.mint)) { console.log(`${leg.trader.slice(0, 6)} sold ${e.mint.slice(0, 6)}`); open.delete(e.mint); try { await lightspeed({ action: "sell", mint: e.mint, amount: "100%", slippage: SLIPPAGE, tip: TIP }); } catch (err) { console.error(` sell failed: ${err.message}`); } } }); ``` ## What each part does - **Matching on the breakdown**, not on `txSigner`: most trades go through a router, so the signer is the router's payer and the wallet you follow is the trader inside the breakdown. Matching there copies trades sent through any bot. - **Sizing** by ratio with a floor and a cap: a whale's 20 SOL becomes 0.5, a 0.05 SOL probe becomes 0.01. Change the rule to a fixed size if you prefer. - **Partial sells.** The script closes the whole position on the first sell it sees. To mirror partial exits, compare `leg.tokenAmount` with the wallet's balance before the trade and sell the same fraction. - **Quoted coins.** A coin priced in USDC or a stock is bought with SOL on Lightspeed; `amount` stays SOL. The response's `quoteMint` says what it settled in. ## Being first behind them Your copy lands a slot or two after the trade it copies, behind everyone else copying the same wallet. Two things shorten that: run in the region closest to you, and raise `tip` on the copies that matter. For launches specifically, the [sniper guide](/guides/sniper) shows the Ultra Fast Shred Stream feed, which also carries wallet filters (`wallets=`): the same copy, a block earlier, before the trade has executed. ## Finding wallets worth following The stream has what you need. Keep a map of trader → buys on launches under a minute old, and their sells; after a day, rank by realised profit. Wallets that are early and that sell are the ones to copy; wallets that only buy are bags. --- # Guide: Build a pump.fun Sniper on Solana > A complete Node.js sniper: watch launches, filter them, buy through Lightspeed in one request, exit on a target, a stop or a timer. The fast version reads launches from Ultra Fast Shred Stream before the block executes. # Build a sniper A sniper does four things: see a launch, decide, buy, exit. This guide builds one in about a hundred lines with the launch feed, [Lightspeed](/lightspeed/overview) for the buy and the trade stream for the exits, then shows the one change that moves the launch signal a block earlier with [Ultra Fast Shred Stream](/ultrafast/overview). ## What you need - Node.js 22 or newer (WebSocket is built in), `npm install socket.io-client`. - A [Lite or Developer plan](/fees): Lightspeed comes with both. The Ultra Fast version at the end needs Developer. - A [Lightspeed account](/lightspeed/account), funded with SOL. Its key signs the buys. - Run it next to the API, in Frankfurt ([Regions](/regions)). ## The script ```js import { io } from "socket.io-client"; // ─── change these ───────────────────────────────────── const LS_KEY = "ls_…"; // your Lightspeed account key const BUY_SOL = 0.05; // per launch const SLIPPAGE = 20; // percent; launches move fast const TIP = 0.001; // SOL per provider copy, paid once by the one that lands const TAKE_PROFIT = 2.0; // sell at 2x const STOP_LOSS = 0.6; // ... or at -40% const MAX_HOLD_S = 120; // ... or after two minutes const MAX_OPEN = 3; const MIN_DEV_BUY = 0.5; // skip launches whose creator bought less than this (SOL) const DRY_RUN = true; // ────────────────────────────────────────────────────── const API = "https://sol.shrine.trade"; const open = new Map(); // mint -> { entry, since, pool } async function lightspeed(body) { if (DRY_RUN) { console.log(" dry-run", body); return { success: true }; } const r = await (await fetch(`${API}/api/lightspeed`, { method: "POST", headers: { "content-type": "application/json", "x-api-key": LS_KEY }, body: JSON.stringify(body), })).json(); if (!r.success) throw new Error(`${r.error}: ${r.message}`); return r; } const socket = io(API, { transports: ["websocket"] }); socket.on("connect", () => { socket.emit("subscribe_new_tokens", { protocols: ["PUMPFUN"] }); console.log("watching launches"); }); // 1. A launch. Filter, then buy. socket.on("new_token", async (t) => { if (open.size >= MAX_OPEN || open.has(t.mint)) return; const devBuy = t.initialBuy?.quoteAmount ?? 0; if (devBuy < MIN_DEV_BUY) return; // the creator did not back it if (t.mintAuthority || t.freezeAuthority) return; // can still mint or freeze: skip console.log(`launch ${t.symbol} ${t.mint} dev buy ${devBuy} SOL`); try { const r = await lightspeed({ action: "buy", mint: t.mint, amount: BUY_SOL, slippage: SLIPPAGE, tip: TIP }); open.set(t.mint, { entry: null, since: Date.now(), pool: t.pool }); socket.emit("subscribe_trades", { pool: t.pool }); // 2. follow its price for the exits console.log(` bought ${t.symbol}: ${r.signature ?? "dry"}`); } catch (e) { console.error(` buy failed: ${e.message}`); } }); // 3. Exits from the live trades: first price seen after the buy is the entry. socket.on("trade", async (tr) => { const pos = open.get(tr.mint); if (!pos) return; if (pos.entry === null) { pos.entry = tr.price; return; } const x = tr.price / pos.entry; const age = (Date.now() - pos.since) / 1000; const why = x >= TAKE_PROFIT ? "take profit" : x <= STOP_LOSS ? "stop loss" : age >= MAX_HOLD_S ? "timer" : null; if (!why) return; open.delete(tr.mint); socket.emit("unsubscribe_trades", { pool: pos.pool }); try { await lightspeed({ action: "sell", mint: tr.mint, amount: "100%", slippage: SLIPPAGE, tip: TIP }); console.log(` sold ${tr.mint} (${why}, ${x.toFixed(2)}x)`); } catch (e) { console.error(` sell failed: ${e.message}`); open.set(tr.mint, pos); // keep it; try again on the next trade } }); ``` Run it with `DRY_RUN = true` first; it prints every launch it would buy and every exit it would take. ## What each part does - **The filter** is where the edge is. The launch event carries the creator's own buy (`initialBuy`), the authorities and token extensions, the name and URI; a creator who bought nothing, or kept a mint authority, is a launch to skip. Add your own: a minimum name length, a URI that resolves, a creator you have seen before. - **The buy** is one request. Lightspeed builds and signs from its own cache of every curve, so nothing is read from the chain on the way, and the copies race every landing provider at once; only one fills. `tip` is what buys priority in a crowded slot. - **The exits** come from the pool's trades: the first price after your buy is your entry, every trade after it a check against the target, the stop and the clock. One sell request closes the position, `"100%"` whatever the fill was. ## The fast version: Ultra Fast Shred Stream The launch feed above is an executed feed: the create has run and a node has served it. [Ultra Fast Shred Stream](/ultrafast/overview) delivers the same create the moment its shreds arrive, before the block executes, and it carries the mint already. Replace the launch source and keep everything else. `UF_KEY` is your Developer plan's key: ```js const uf = new WebSocket(`wss://sol.shrine.trade/api/ultrafast/stream?key=${UF_KEY}&creates=1`); uf.onmessage = async (m) => { const e = JSON.parse(m.data); if (e.type !== "create") return; // e.mint, e.symbol, e.name, e.creator, e.bondingCurve are known before the block exists. // The dev buy follows as a trade with createBuy: true on this same socket. // ... same filter and the same lightspeed({ action: "buy", mint: e.mint, ... }) as above }; ``` The buy then leaves more than 100 ms before a bot on PumpPortal has seen the launch at all, and 6 ms before one on our own executed stream in the same region. The exits stay on the executed stream, where prices are real. ## Where the time goes | Step | Time | |---|---| | launch on Ultra Fast Shred Stream to your bot in Frankfurt | about 1 ms | | your filter | yours | | Lightspeed build and sign | about 1 ms | | first provider acknowledgement | 10 to 20 ms | | landed | the next slot, usually | A bot elsewhere in Europe adds 10 to 30 ms each way; one in the US should use the New York region instead of crossing the Atlantic. That is the difference between the first slot and the third. --- # Guide: Trading Strategies on the Solana API > Exits, position sizing, DCA, dip buying on 1-second candles, migration plays and what to watch on the data streams: patterns that work with the Local Trade API and Lightspeed, with code. # Strategies Patterns that come up again and again, each as a small piece you can bolt onto the [sniper](/guides/sniper) or the [copy trader](/guides/copy-trade). All of them trade through [Lightspeed](/lightspeed/overview) in the examples; the [Local Trade API](/) builds the same trades for your own key. ## Exits Every position needs three exits before it is opened: a target, a stop and a clock. Price comes from the pool's live trades: ```js socket.emit("subscribe_trades", { pool }); socket.on("trade", (t) => { const x = t.price / entry; if (x >= 2.0) sell("take profit"); if (x <= 0.6) sell("stop loss"); if (Date.now() - since > 120_000) sell("timer"); }); ``` Two refinements that pay: **a trailing stop** that sells when price falls 25% from the high since entry, instead of a fixed stop; and **scaling out**, `amount: "50%"` at 2x and the rest at the trailing stop, so a runner is not sold whole at the first target. ## Position sizing Size by what you can lose, not by what you hope to win. A simple rule: risk 1% of the wallet per position, so with a 40% stop the size is 2.5% of the wallet. Cap the number of open positions, and halve the size after three stops in a row. Read the wallet with `GET /api/lightspeed/account` at startup and after every landed trade rather than keeping your own ledger. ## Launch filters The launch event tells you more than a name. Skip when `mintAuthority` or `freezeAuthority` is set, when `tokenExtensions` is not empty on a pump.fun coin, when `initialBuy` is missing or tiny, when the creator launched something in the last hour (keep a map of creators). Require that the `uri` answers and has an image. None of this is foolproof; together they remove most of the launches that go nowhere. ## DCA into a position Instead of one buy, several small ones a few seconds apart, so one bad fill does not set the entry: ```js for (let i = 0; i < 4; i++) { await lightspeed({ action: "buy", mint, amount: 0.025, slippage: 10 }); await new Promise((r) => setTimeout(r, 3000)); } ``` The same loop with `sell` and a percentage scales out of a position over time. ## Dip buying on candles [`subscribe_ohlcv`](/data-api/subscribe-ohlcv) gives 1-second candles. A coin that is up a lot and prints a red candle of more than 15% while volume stays high is a dip in an uptrend; one whose volume collapses is a top. Keep the last 60 candles, compute the high, buy when the close is more than 20% off the high and the last three candles' volume is above the average, sell at the previous high. ## Migration plays A pump.fun coin that completes its curve moves to PumpSwap; the stream's `migrate` and `createPool` actions and [`subscribe_migrations`](/data-api/subscribe-migrations) tell you when. The first minutes on the new pool are volatile: one approach buys on the migration event and sells into the first spike; another waits for the first 20% pullback on the pool. Pass `pool: "pumpswap"` on the buy if the cache is a moment behind. ## Following size, not names The stream's `breakdown` shows every trader in a transaction. A buy above 5 SOL on a coin under a minute old is a signal on its own, whoever sent it. Count them per mint over a 10-second window; three large buys from different wallets is a stronger signal than one. ## What not to do - Do not chase with slippage. A buy that needs 40% slippage to land is a buy you should not make. - Do not keep positions without exits because "it will come back". - Do not run from a region 100 ms away and expect to be first; pick the nearest one, see [Regions](/regions). - Do not poll REST for prices; the streams exist so you never have to. --- # Guide: Build a Telegram Sniper Bot for pump.fun (Buy the Second a Token Is Called) > A complete Node.js Telegram sniper: your own Telegram account watches the chats you pick, finds the Solana mint in each new call and buys it through Lightspeed in one request, plus the ready-made bot that comes with Developer. # Build a Telegram sniper bot A call on Telegram moves a coin: a caller posts a mint, and everyone reading it buys at once. The bot that buys first gets the price before the crowd. This guide builds that bot in about a hundred lines of Node.js: your own Telegram account reads the chats you choose, and [Lightspeed](/lightspeed/overview) buys the mint the moment it is posted, in one request. If you would rather not run your own, the ready-made bot at the end of this page comes with Developer. ## How it works - **A user account, not a bot.** A bot made with BotFather reads only the chats it is added to, and a channel only as its admin. An ordinary account, logged in through Telegram's API, sees every chat it has joined, the way the app does. Use a spare account, not your own. - **The mint in the post.** A Solana address is 32 to 44 base58 characters, and a pump.fun mint ends in `pump`. The bot reads each new post's text, its hidden links and its buttons, and takes the first mint it has not seen before. - **One request to buy.** Lightspeed builds, signs and sends the buy from your Lightspeed wallet; nothing is read from the chain on the way. - **A report to you.** Each buy is sent, with its signature, to a chat you pick: the account's Saved Messages or your own username. ## What you need - Node.js 22 or newer, `npm install telegram input`: [GramJS](https://gram.js.org), the Telegram client, and a prompt for the first login. - A Telegram `api_id` and `api_hash`: log in at [my.telegram.org](https://my.telegram.org), open API development tools and create an app. - A spare Telegram account to listen with, joined to every chat you want to watch. Telegram only sends an account the posts of chats it is a member of. - A [Lite or Developer plan](/fees) for Lightspeed, and a funded [Lightspeed account](/lightspeed/account): its `ls_…` key signs the buys. - A server next to the API: Frankfurt on Lite; Frankfurt, Amsterdam or New York on Developer ([Regions](/regions)). ## The script Save it as `tg-sniper.mjs`. ```js import { existsSync, readFileSync, writeFileSync } from "node:fs"; import input from "input"; import { TelegramClient } from "telegram"; import { StringSession } from "telegram/sessions/index.js"; import { NewMessage } from "telegram/events/index.js"; // ─── change these ───────────────────────────────────── const API_ID = 1234567; // my.telegram.org → API development tools const API_HASH = "0123456789abcdef0123456789abcdef"; const WATCH = []; // chats: "username" or id; empty: list this account's chats and exit const SENDERS = []; // in groups, only these user ids trigger a buy; empty: anyone const REPORT_TO = "me"; // "me" is Saved Messages; or your own "username" const LS_KEY = "ls_…"; // your Lightspeed account key const BUY_SOL = 0.05; // per call const SLIPPAGE = 25; // percent; a called coin moves fast const PRIORITY_FEE = 0.0002; // SOL; on Lite the lever that counts (the Shrine lane takes no tip) const TIP = 0.001; // SOL per provider copy, paid once by the copy that lands const PROVIDERS = ["all"]; // Developer: race every lane and the block 0 lane; Lite ignores it const PUMP_ONLY = true; // only mints ending in "pump" const SKIP_FORWARDS = true; // a forward is someone else's call, posted again later const DRY_RUN = true; // ────────────────────────────────────────────────────── const API = "https://sol.shrine.trade"; const SESSION_FILE = "telegram.session"; // the account's login: guard it like a password const MINT_RE = /\b[1-9A-HJ-NP-Za-km-z]{32,44}\b/g; // base58, the length of a Solana address const senders = new Set(SENDERS.map(String)); const seen = new Set(); // every mint already bought or tried async function lightspeed(body) { if (DRY_RUN) { console.log(" dry-run", body); return { success: true }; } const r = await (await fetch(`${API}/api/lightspeed`, { method: "POST", headers: { "content-type": "application/json", "x-api-key": LS_KEY }, body: JSON.stringify(body), })).json(); if (!r.success) throw new Error(`${r.error}: ${r.message}`); return r; } // The post's text plus the links behind words and buttons ("BUY" -> pump.fun/coin/). function textOf(msg) { const links = [ ...(msg.entities ?? []).map((e) => e.url), ...(msg.replyMarkup?.rows ?? []).flatMap((row) => row.buttons.map((b) => b.url)), ]; return [msg.message ?? "", ...links.filter(Boolean)].join(" "); } // 1. Log in as the listening account. The first run asks for the phone, the code and the 2FA password. const session = new StringSession(existsSync(SESSION_FILE) ? readFileSync(SESSION_FILE, "utf8") : ""); const client = new TelegramClient(session, API_ID, API_HASH, { connectionRetries: 10 }); client.setLogLevel("error"); await client.start({ phoneNumber: () => input.text("phone number (+44…): "), phoneCode: () => input.text("code Telegram sent you: "), password: () => input.password("2FA password: "), onError: (e) => console.error(e.message), }); writeFileSync(SESSION_FILE, client.session.save()); if (!WATCH.length) { // no chats yet: print the ones this account is in for (const d of await client.getDialogs({ limit: 200 })) console.log(String(d.id).padEnd(16), d.title); process.exit(0); } const chats = await Promise.all(WATCH.map((c) => client.getPeerId(c))); // usernames to ids now, not on the first post // 2. Every new post in a watched chat: find a mint, buy it once. client.addEventHandler(async (event) => { const msg = event.message; if (SKIP_FORWARDS && msg.fwdFrom) return; if (senders.size && !senders.has(String(msg.senderId))) return; const mint = [...textOf(msg).matchAll(MINT_RE)].map((m) => m[0]) .find((m) => !seen.has(m) && (!PUMP_ONLY || m.endsWith("pump"))); if (!mint) return; seen.add(mint); // before the request: a repost cannot buy it again const lag = Date.now() / 1000 - msg.date; // msg.date is whole seconds: an upper bound console.log(`call ${mint} in ${event.chatId} from ${msg.senderId}, ≤${lag.toFixed(1)} s after the post`); const t0 = performance.now(); try { const r = await lightspeed({ action: "buy", mint, amount: BUY_SOL, slippage: SLIPPAGE, priorityFee: PRIORITY_FEE, tip: TIP, providers: PROVIDERS, }); console.log(` sent in ${(performance.now() - t0).toFixed(0)} ms: ${r.signature ?? "dry"}`); // 3. Report it. Not awaited: the buy is already out. client.sendMessage(REPORT_TO, { message: `bought ${mint}\n${r.signature ?? "dry run"}` }).catch(() => {}); } catch (e) { console.error(` buy failed: ${e.message}`); } }, new NewMessage({ chats })); // new posts only: edits are another event console.log(`watching ${chats.length} chats`); ``` ## Run it 1. **Log in and list your chats.** Leave `WATCH` empty and run `node tg-sniper.mjs`. It asks for the phone number, the code Telegram sends and the 2FA password if the account has one, saves the login to `telegram.session` and prints every chat the account is in, with its id. 2. **Pick the chats.** Put their usernames or ids into `WATCH`. A private chat has no username: use the id the first run printed. 3. **Dry run.** With `DRY_RUN = true` it prints every call it would buy, who posted it and how long after the post it saw it. Let it run through a few calls and check what it would have bought. 4. **Go live.** `DRY_RUN = false`, a small `BUY_SOL`, and watch the first fills. ## What each part does - **Reading the post.** Callers often hide the mint behind a word or a button linking to pump.fun, so `textOf` adds those links to the text. The pattern matches anything shaped like a Solana address; `PUMP_ONLY` keeps the ones ending in `pump`, which also drops the wallet and pool addresses a post quotes. - **The filters.** Only posts in `WATCH` reach the handler. In a group, `SENDERS` limits it to the callers you trust, so a member pasting a mint buys nothing; the dry run prints each poster's id. Forwards are skipped: a forward repeats a call made earlier, somewhere else. Edits never arrive, since `NewMessage` is new posts only; for chats that post first and edit the mint in, GramJS has `EditedMessage` in `telegram/events/EditedMessage.js`. - **One buy per mint.** Telegram sometimes delivers a post twice, and a call is reposted across channels within seconds. The mint goes into `seen` before the request leaves, so a second sighting, even one that arrives while the first buy is in flight, buys nothing. - **The buy.** One Lightspeed request, `amount` in SOL. Lightspeed routes from its own cache: a coin on its curve is bought on pump.fun, a graduated one on PumpSwap. An address that is not a tradable token answers with an error, and nothing is sent. - **Fees.** On Lite the buy goes out on Shrine's staked lane alone, which takes no tip: raise `PRIORITY_FEE` when calls get crowded. On Developer the copies race; each copy that reverts pays its priority fee and only the one that lands pays the tip, so the budget goes into `TIP`. - **The delay line.** `msg.date` is when Telegram took the post, in whole seconds, so `≤1.4 s` means somewhere between 0.4 and 1.4 s. Keep the server's clock synced: a clock half a second off moves the number by as much. ## Exits The script buys and stops there. For a target, a stop and a timer, follow the coin's trades and sell through Lightspeed, as the [sniper guide](/guides/sniper) does. `subscribe_trades` takes the mint itself, so a call needs nothing more: ```js import { io } from "socket.io-client"; const socket = io("https://sol.shrine.trade", { transports: ["websocket"] }); // after a buy: socket.emit("subscribe_trades", { mint }); // then the sniper guide's "trade" handler: entry, target, stop, timer, one "100%" sell ``` ## Where the time goes | Step | Time | |---|---| | caller presses send, post reaches your account | about 0.5 to 2 s, on Telegram's side | | mint found, filters | well under a millisecond | | request reaches Lightspeed from the same city | about 1 ms | | sent to landed | about 100 ms on Lite, about 50 ms raced on Developer | - **Telegram is the slow part.** Delivery takes about 0.5 to 2 s and varies by chat and by moment. No bot can beat that; yours can only add nothing to it. Everything after the post arrives should take milliseconds. - **Close to Telegram and to the API.** Telegram's European data centres are in Amsterdam, and accounts with European numbers are usually served from there. A listener in Frankfurt or Amsterdam is a few milliseconds from both Telegram and the API; one on another continent adds a crossing each way. - **Nothing on the way.** Keep the Lightspeed wallet funded and look nothing up between the post and the request: no balance check, no metadata, no chain read. The filters work on the text you already have. - **One listener.** One connection per account. The same session used from two places at once is revoked by Telegram, and two listeners would each buy the same call. - **De-dup reposts.** The same mint arrives from several channels within seconds; without `seen` every one of them is a buy. - **Racing on Developer.** On Lite the buy goes out on Shrine's staked lane, about 100 ms to landing. Developer races all 8 SWQoS lanes and the [block 0 lane](/lightspeed/block-zero) at once, about 50 ms ([provider racing](/lightspeed/how-it-works)), which decides the slot when every bot in the chat fires together. ## Safety - **Dry run first.** Watch a few calls with `DRY_RUN = true` before any SOL moves. A wrong filter buys every address in every post. - **Small amounts.** Fund the Lightspeed wallet with what you snipe with, not more, and withdraw profits. - **A call is not a reason.** Many calls are paid for, many coins are sold into the buyers a call brings, and copies with the same name and ticker under a different mint are posted to catch fast bots. Watch callers you trust, and keep `SENDERS` and `PUMP_ONLY` on. - **Your keys stay yours.** Never paste your Lightspeed key, a private key or `telegram.session` into a chat or a bot: the session file is a full login to the account. Anyone asking for them, "support" included, is a scam. ## The ready-made bot We run our own Telegram sniper, and it comes with the Developer plan: - **Watches the chats you set.** Channels and groups, the ones you choose. - **Buys through the race.** The same one-request flow as above, raced over Lightspeed's lanes. - **Buys at the migration.** When a called coin's curve is already full, it buys the coin as it migrates, on PumpSwap. - **Lives in Telegram.** Every buy is reported to your Telegram, and you run the bot from a Telegram bot. To get it: 1. **Get Developer.** Compare the plans on the [pricing page](https://shrine.trade/api/pricing/), then get Developer on the [dashboard](https://shrine.trade/api/dashboard/). 2. **Join the private group.** It is invite only; the invite is on your dashboard once Developer is active. 3. **Ask for the bot.** Post in the group and we give you access. Developer also brings [provider racing](/lightspeed/how-it-works) over every lane and the block 0 lane, all three [regions](/regions) and the [Ultra Fast Shred Stream](/ultrafast/overview) for bots of your own. See [Plans and limits](/fees). --- # Jito Bundles API: Atomic Multi-Trade Bundles for Solana Memecoins with Guaranteed Delivery > Build up to five Solana trades as one atomic Jito bundle, sign them locally, and send the bundle through our block-engine relay with guaranteed delivery or straight to Jito yourself. # Jito Bundles A bundle lands up to five transactions in one block, atomically and in order: all land or none do, and nothing gets between them. Typical uses are a create plus buys from several wallets, or a coordinated sell. Two calls: 1. `POST /api/local-bundle` builds the unsigned transactions with a shared blockhash and the Jito tip on the last one. 2. Sign each transaction with its wallet, then `POST /api/send-bundle` forwards the signed set to a Jito block engine and returns the bundle id. You can also submit to Jito yourself. ## Build `POST https://sol.shrine.trade/api/local-bundle` ```json { "trades": [ { "action": "buy", "publicKey": "", "mint": "", "amount": 0.5, "slippage": 15, "pool": "pumpfun" }, { "action": "buy", "publicKey": "", "mint": "", "amount": 0.3, "slippage": 15, "pool": "pumpfun" } ], "jitoTip": 0.001 } ``` Each entry takes the same fields as [local trade](/). Wallets may differ per trade; each wallet signs its own transaction. The tip is paid by the last trade's wallet. Response: ```json { "transactions": ["", ""], "blockhash": "…", "feeLamports": 2000000, "tipLamports": 1000000 } ``` ### Multi-action transactions in a bundle Instead of `trades`, pass `transactions`: each entry is a [multi-action](./local-actions) body with its own `publicKey` and `actions`. This is how a launch gets its snipers: the create in transaction one, buys from other wallets after it. A `mintRef` set in one transaction resolves in the later ones. ```json { "transactions": [ { "publicKey": "", "actions": [ { "action": "create", "mintRef": "coin", "mint": "", "name": "My Coin", "symbol": "COIN", "uri": "https://…/metadata.json", "initialBuy": 0.5 } ]}, { "publicKey": "", "actions": [{ "action": "buy", "mint": "$coin", "amount": 0.3, "slippage": 30 }] }, { "publicKey": "", "actions": [ { "action": "buy", "mint": "$coin", "amount": 0.3, "slippage": 30 }, { "action": "buy", "mint": "$coin", "amount": 0.2, "slippage": 30, "publicKey": "" } ]} ], "jitoTip": 0.001 } ``` The response then carries `signers`, one list per transaction, naming every key that must sign it (the dev wallet and the mint keypair for the first one here). Buys on the not-yet-existing coin are quoted from its launch constants, so give them room with `slippage`. ## Send `POST https://sol.shrine.trade/api/send-bundle` ```json { "transactions": ["", ""], "blockEngine": "https://frankfurt.mainnet.block-engine.jito.wtf", "guaranteedDelivery": true } ``` `blockEngine` is optional and must be a Jito mainnet host; the default is the global endpoint. Submissions carry our block-engine authentication, which Jito requires for bundles to enter its system. The response is `{ "bundleId": "…", "guaranteedDelivery": true }`. Poll Jito's `getBundleStatuses` with it, or watch your wallets. With `guaranteedDelivery` we keep resending the same signed bundle every two seconds until Jito reports it landed, or its blockhash expires about a minute later. One call, no retry loop on your side; the transactions cannot land twice, since a landed signature is rejected on resend. ## Single-transaction tip Any [local trade](/), [create](/create-token) or [claim](/claim-creator-fees) accepts `jitoTip`, which appends the tip transfer to that one transaction so you can send it alone as a one-transaction bundle. ## Fees No trade fee: bundles come with Lite and Developer, which trade at 0%. Send your [plan's key](/fees#the-key) as `x-api-key`. The tip goes to Jito, not to us. --- # Lightspeed Account: Hosted Wallet, Balance and Withdraw > Create your Lightspeed wallet, fund it, read its balance and withdraw SOL. The private key is encrypted under your API key: without it nobody, including us, can open the wallet. import SnipeAccount from '@site/src/components/SnipeAccount'; # Account A Lightspeed wallet is a wallet we hold for you, encrypted under its `ls_` key. It trades with that key alone, on the plan of the account it belongs to. ## On the dashboard The [dashboard](https://shrine.trade/api/dashboard/) is the simplest place to keep them: - **Make a wallet.** Up to 10 per account. Its address, `ls_` key and private key are shown once: save them. - **See them all.** Each wallet with its name, its address on Solscan and its SOL. - **Link one you already have.** Paste its `ls_` key and it joins your account, so it trades on your plan. - **Remove one.** It leaves your account and keeps its key and its SOL; it trades again once it is linked to an account with a plan, or with a plan's key in `x-api-key`. ## Here The form below makes a wallet without an account. Link it on the dashboard afterwards so it trades on your plan. ## From code Two requests: create an account (no body), read the balance with the key in the `x-api-key` header. ```bash curl -X POST https://sol.shrine.trade/api/lightspeed/account curl https://sol.shrine.trade/api/lightspeed/account -H "x-api-key: ls_…" ``` To move SOL out without importing the private key, `POST /api/lightspeed/withdraw` with `to` and `amount` (`"100%"` for everything above the network fee), same header. The create response is `{ "pubkey", "api_key", "private_key" }`, shown once. Fund `pubkey` with SOL (plus a little for tips and fees); for a coin quoted in USDC or another token, fund the quote as well. :::caution Your wallet, your key The API key is the key to the wallet. Whoever holds it, or the private key, controls the funds; we cannot recover a lost API key, so keep the private key as your backup. Keep only what you snipe with, withdraw profits, and treat both like a password. If either leaks, create a new account and move the balance. ::: ## Balance ```bash curl https://sol.shrine.trade/api/lightspeed/account -H "x-api-key: ls_…" ``` ```json { "pubkey": "5Vk…nq2", "sol": 1.204, "lamports": 1204000000, "tokens": [ { "mint": "…", "amount": 1250000, "amount_raw": "1250000000000", "decimals": 6 } ] } ``` ## Withdraw Move SOL out, no fee. `amount` is SOL, or `"100%"` for everything above the network fee. ```bash curl -X POST https://sol.shrine.trade/api/lightspeed/withdraw \ -H "content-type: application/json" \ -d '{ "apiKey": "ls_…", "to": "", "amount": "100%" }' ``` --- # Block 0 Lane: Land a Solana Trade in the Slot It Was Sent > The leader-aware lane in a Lightspeed race: a copy goes out only when the current leader is close to our region, right as its slot starts, so it lands in that same slot. Developer plan. import Admonition from '@theme/Admonition'; # Block 0 lane Most lanes send a transaction the moment you ask, wherever the leader is. The block 0 lane waits for the right leader. Solana hands the block to a new leader every 4 slots, and those leaders sit all over the world. When the current leader is close to our region and its slot has just started, a copy sent now reaches it before it seals the block, and lands in the slot it was sent for: block 0. When the leader is far away, the copy would arrive late, so the lane does not send it at all. The leader schedule a new leader every 4 slots, anywhere in the world Leader A near our region Leader B another continent Leader C near our region Leader D far away block 0 lane: send lands in the same slot block 0 lane: hold not sent, costs nothing; the other lanes land the trade block 0 lane: send lands in the same slot block 0 lane: hold the other lanes land the trade slot → ## What it does in a race The block 0 lane is one more copy in a [Lightspeed](/lightspeed/overview) race, next to the [SWQoS lanes](/lightspeed/swqos). It does not replace them. - **Leader near:** the copy goes out the instant the slot starts and is usually first. About three in four copies that go out land in the slot they were sent for. - **Leader far:** the copy is held back. It never reaches the network and costs nothing; its signature in the response simply never confirms. The other lanes carry the trade as usual. About a quarter of copies go out. When one does, it is the fastest way onto the chain we have; when one doesn't, you lose nothing. The [one-fill rule](/lightspeed/swqos#provider-racing) holds as everywhere: only the first copy to land trades, every other copy fails before it moves funds. ## Use it Add `block0` to `providers`: ```js const res = await fetch("https://sol.shrine.trade/api/lightspeed", { method: "POST", headers: { "content-type": "application/json", "x-api-key": "" }, body: JSON.stringify({ action: "buy", mint: "", amount: 0.5, slippage: 25, priorityFee: 0.002, tip: 0.001, providers: ["shrine", "temporal", "block0"], // or ["all"]: block0 is in it on Developer }), }); ``` In the response, a held copy shows as accepted with a signature like every other; check the landed signature, not the list of acceptances. Snipes and arbitrage, where landing a slot earlier is the whole game and a copy that never goes out costs nothing. Run your bot in the same region as the API (or on a [private link](/private-link)) so the request reaches the race while the slot is still young. The shares above are measured on live Frankfurt traffic in October 2026. They move with the leader schedule: a run of leaders near the region means more copies go out. --- # Provider Racing: How a Lightspeed Trade Fans Out to Every SWQoS Lane, and the Milliseconds Involved > Developer plan. Step by step through a raced snipe: build, sign one copy per lane, fan out to every SWQoS path and the block 0 lane at once, first to land wins. # Provider racing On Developer, every Lightspeed trade is raced: one request in, one transaction per lane out, one fill. On Lite the trade goes out on the Shrine lane alone. The diagram follows a buy from your bot to the block, with the time each step takes measured on our server in Amsterdam. your bot POST /api/lightspeed sol.shrine.trade ≈0.5 ms build + sign one copy per provider t≈1 ms fan out one fill, never two Jito ack 12 ms Astralane ack 10 ms Temporal ack 9 ms fastest this slot: lands bloXroute ack 14 ms Node1 ack 11 ms FlashBlock ack 13 ms BlockRazor ack 15 ms RPC ack 24 ms one copy per provider, sent in the same instant ## Why fan-out instead of the best provider Each provider's path to the leader changes slot to slot: their stake, their peering with that particular validator, their load. The one that was fastest a second ago may be third now. Sending to all of them means the snipe always takes the fastest path that exists at that moment, and a slow provider costs nothing since its copy simply loses. ## Why only one fill Every copy is a distinct transaction with its own signature. The first to execute trades. Every later copy fails on chain before it moves funds, so the trade happens once and the tip is paid once, to the provider whose copy landed. A losing copy costs its priority fee only. Nothing on your side has to coordinate this. ## What you see in the response `submissions` lists each provider with its signature, whether it accepted, and its `ms`. `signature` is the first accepted copy, but any accepted one may be the copy that landed, so confirm by watching all of them or simply the wallet's balance. See the [Snipe page](/lightspeed/trade) for the full shape. --- # Lightspeed: Fastest pump.fun Sniper API, Signed and Sent in One Request > One request builds, signs and sends a trade from your Lightspeed wallet over Shrine's staked lane, about 100 ms. On Developer, provider racing sends the same request over all 8 SWQoS lanes and the block 0 lane at once, about 50 ms. Your wallet is encrypted under your own API key: nobody, including us, can open it without it. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import useBaseUrl from '@docusaurus/useBaseUrl'; import LightspeedRace from '@site/src/components/LightspeedRace'; # Lightspeed One request. We build the trade, sign it with your Lightspeed wallet, which lives on our server, and send it over Shrine's own staked lane. No unsigned transaction comes back to you and nothing to send on your side. On Developer the same request goes further: [provider racing](/lightspeed/how-it-works) fires a copy at every landing lane at the same instant. `POST https://sol.shrine.trade/api/lightspeed` ## Two speeds - **Lightspeed, on Lite.** Built, signed and sent in one request over Shrine's staked lane, the stake-weighted path in your region. About 100 ms from request to landing. - **Provider racing, on Developer.** The same request goes out on all 8 SWQoS lanes and the block 0 lane at the same instant, and the fastest path that slot lands it; only one copy ever fills. About 50 ms. See [Provider racing](/lightspeed/how-it-works). ## Lanes per plan | | Lite | Developer | |---|---|---| | Shrine staked lane, ~100 ms | ✓ | ✓ | | All 8 [SWQoS lanes](/lightspeed/swqos), raced, ~50 ms | – | ✓ | | [Block 0 lane](/lightspeed/block-zero) | – | ✓ | | Trade fee | 0% | 0% | On Lite every trade goes out on our own staked lane. Developer races all eight lanes and the block 0 lane at once, so the fastest path that slot wins. Next: [create an account](/lightspeed/account), then [snipe](/lightspeed/trade). --- # SWQoS Providers Raced by Lightspeed: Jito, Astralane, Temporal, bloXroute, Node1, FlashBlock, BlockRazor > The stake-weighted QoS and block-engine providers a Lightspeed is raced across today, with minimum tips and the names to use in the providers field. # SWQoS Stake-weighted quality of service is how a transaction jumps the queue on Solana. A validator gives connection capacity in proportion to stake, so a sender that has stake behind it, or leases it, gets its transactions in front of the leader before the public RPC path does. Jito, Astralane, Temporal, bloXroute, Node1, FlashBlock and BlockRazor each run such a path, each with its own stake, its own peering to the current leader and its own latency that changes from slot to slot. ## Provider racing No single provider is fastest every slot. A Lightspeed on Developer does not pick one; it sends to all of them at the same instant. (On Lite it goes out on the Shrine lane alone.) 1. **One copy per provider.** The trade is built once, then signed once per provider with that provider's own tip account inside. Each copy is a distinct transaction with its own signature. 2. **Fired together.** All copies leave our server in the same moment, in parallel, not one after another. The response lists every provider with its signature and the milliseconds until it acknowledged. 3. **Fastest path wins.** Whichever copy reaches the leader first is the one that executes. The others fail on chain before they move any funds, so the trade happens once and only one tip is paid, to the provider that landed it. 4. **A slow provider costs nothing.** Its copy simply loses the race. That is why racing beats choosing: the worst slot for one provider is covered by the others. Your priority fee is in every copy, so a failed copy costs only that. The tip is a transfer inside the same transaction and fails with it. ## Steering the race - `tip` is per copy. Only the landed copy pays it, so a higher tip is not multiplied by the number of providers. It must clear the minimum of every provider you want in the race; a provider under its minimum drops its copy. - `providers` chooses the race: two defaults when omitted (`shrine` and `temporal`), `["all"]` for everything, or exactly the names you list, nothing added. - `priorityFee` still matters inside the leader's block once a copy is delivered; keep it competitive on hot launches. ## Providers Each provider gets its own copy of the transaction, tipped to that provider's own account, and whichever path is fastest that slot wins. Two race by default; name the rest when you want them. | Provider | Minimum tip (SOL) | Name | Default | |---|---|---|---| | Shrine, the staked RPC in each region | none | `shrine` | yes | | Temporal (Nozomi) | 0.001 | `temporal` | yes | | Jito | 0.000001 | `jito` | | | Astralane | 0.00001 | `astralane` | | | bloXroute | 0.001 | `bloxroute` | | | Node1 | 0.002 | `node1` | | | FlashBlock | 0.0001 | `flashblock` | | | BlockRazor | 0.0001 | `blockrazor` | | `GET /api/lightspeed/providers` is the live list for the region you are on. A provider whose minimum is above your tip is left out of the race; the default tip of 0.001 reaches every provider but Node1, `tip: 0.002` reaches all of them. The minimums are the providers' own, from their documentation; a tip under a provider's minimum is dropped on their side without a word, which is why the race leaves such a copy out. --- # POST /api/lightspeed: Build, Sign and Race One Trade Across Every Provider > The Lightspeed request and response: fields, the one-fill rule, fees, and a speed checklist. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; import Admonition from '@theme/Admonition'; # Trade `POST https://sol.shrine.trade/api/lightspeed`. Lightspeed comes with Lite and Developer, and a trade goes out from one of two wallets: - **A dashboard wallet.** Make one on the [dashboard](https://shrine.trade/api/dashboard/), or link one you made [here](/lightspeed/account), and send its `ls_` key in `x-api-key`. It trades on your account's plan. - **Your own wallet.** Send your plan's key (`sk_…`) in `x-api-key` and the wallet's `privateKey` in the body. ```js const res = await fetch("https://sol.shrine.trade/api/lightspeed", { method: "POST", headers: { "content-type": "application/json", "x-api-key": "ls_…" }, // a dashboard wallet's key body: JSON.stringify({ action: "buy", // "buy" or "sell" mint: "", amount: 0.5, // SOL on a buy; tokens or "100%" on a sell slippage: 25, // percent; snipes want room priorityFee: 0.002, // SOL, total tip: 0.001, // SOL, per provider copy; paid once, by the copy that lands providers: ["shrine", "temporal"], // Developer: the lanes that race (omit for these two, ["all"] for every one); Lite: shrine only }), }); const r = await res.json(); console.log(r.success, r.signature, r.submissions); ``` ```python import os, requests r = requests.post( "https://sol.shrine.trade/api/lightspeed", headers={"x-api-key": "ls_…"}, # a dashboard wallet's key json={ "action": "buy", "mint": "", "amount": 0.5, "slippage": 25, "priorityFee": 0.002, "tip": 0.001, "providers": ["shrine", "temporal"], # Developer: the lanes that race (omit for these two, ["all"] for every one); Lite: shrine only }, ).json() print(r["success"], r["signature"], r["submissions"]) ``` ```bash curl -X POST https://sol.shrine.trade/api/lightspeed \ -H "content-type: application/json" -H "x-api-key: ls_…" \ -d '{ "action": "buy", "mint": "", "amount": 0.5, "slippage": 25, "priorityFee": 0.002, "tip": 0.001, "providers": ["shrine", "temporal"] }' ``` ### Request | Field | | | |---|---|---| | `apiKey` | string | A Lightspeed wallet's `ls_` key, here or in the `x-api-key` header. | | `privateKey` | string | Instead of a wallet's key: your own wallet's private key, base58, with your plan's key in `x-api-key`. It signs this one trade and is dropped; nothing is stored. | | `action` | `"buy"` \| `"sell"` | | | `mint` | string | Token mint, base58. | | `amount` | number \| string | SOL on a buy (the quote token on a quoted coin). Tokens, or `"100%"` / `"50%"` of the balance, on a sell. | | `slippage` | number | Percent. Default 5. | | `priorityFee` | number | Total priority fee in SOL. Default 0.001. | | `tip` | number | Tip per provider copy in SOL. Default 0.001, which reaches every provider but Node1 (0.002). Only the copy that lands pays it. A provider whose minimum is above it is left out of the race. | | `pool` | string | Force a launchpad: `pumpfun`, `pumpswap`, `bonk`, `raydium_cpmm`, `raydium_amm_v4`, `meteora_damm_v2`, `meteora_dbc`. Omit to auto-route from the live cache. | | `poolAddress` | string | For the AMMs when the cache has no pool for the mint. | | `guard` | boolean | One-fill protection. Default `true`. With `false` every copy can land and the wallet trades once per provider. | | `providers` | string[] | Developer: which landing providers race this trade, by lowercase name. On Lite the trade goes out on `shrine` alone and this field is ignored. Omitted: the two defaults, `shrine` and `temporal`. `["all"]`: every provider the region has. Any other list: exactly those, nothing added. A name that is not on `GET /api/lightspeed/providers` answers 400, and a list none of whose providers accepts your `tip` answers 400 too. | Send your plan's key in `x-api-key` and `privateKey` in the body, and the trade is signed with your wallet. The private key is used for that one request and dropped: nothing is stored, nothing is logged. A dashboard wallet keeps the private key off the wire; this is for a bot that already holds its wallet and wants the same race. ```js headers: { "content-type": "application/json", "x-api-key": "sk_…" }, // your plan's key body: JSON.stringify({ action: "buy", mint, amount: 0.5, slippage: 25, privateKey: "" }), ``` A Lightspeed wallet that is not on your account works the same way: your plan's key in `x-api-key`, the wallet's `ls_` key as `apiKey` in the body. A wallet whose account has no running plan, or a request without a plan's key, answers 403: ```json { "error": "plan_required", "plan": "lite", "message": "Lightspeed comes with Lite and Developer: …", "upgrade": "https://shrine.trade/api/pricing/" } ``` ## Providers: which paths race On Developer, with [provider racing](/lightspeed/how-it-works), a Lightspeed trade is not sent down one path. It is signed once per provider and all copies leave at the same instant; the first to reach the leader fills, the rest fail before moving funds, and only the winning copy pays its tip. Details on the [SWQoS page](/lightspeed/swqos). Without `providers` a trade races **Shrine** (`shrine`, the staked RPC in the region, never tipped) and **Temporal** (`temporal`). That pair lands most trades in the next slot at one tip. Add more when you are fighting for a crowded slot, not before: every extra copy that reverts costs its priority fee. Enable more, or different, providers per trade: ```js // Everything the region has body: JSON.stringify({ action: "buy", mint, amount: 0.5, slippage: 25, tip: 0.001, providers: ["all"] }) // Exactly these, nothing added body: JSON.stringify({ action: "buy", mint, amount: 0.5, slippage: 25, tip: 0.001, providers: ["temporal", "jito", "astralane"] }) // Only our own path, no tip at all body: JSON.stringify({ action: "buy", mint, amount: 0.5, slippage: 25, tip: 0, providers: ["shrine"] }) ``` `GET https://sol.shrine.trade/api/lightspeed/providers` lists what the region has right now, each with its minimum tip and whether it is a default: ```json [ { "name": "shrine", "minTipSol": 0, "default": true }, { "name": "temporal", "minTipSol": 0.001, "default": true }, { "name": "jito", "minTipSol": 0.000001, "default": false }, { "name": "astralane","minTipSol": 0.00001, "default": false } ] ``` A provider whose minimum is above your `tip` is left out of the race. The default 0.001 reaches all of them but Node1, `tip: 0.002` reaches all; `tip: 0` reaches only `shrine`. A list that leaves no provider in the race answers 400 with each one's minimum. ### Response ```json { "success": true, "signature": "3nQ…", "submissions": [ { "provider": "temporal", "signature": "3nQ…", "accepted": true, "ms": 9.1 }, { "provider": "shrine", "signature": "4Rc…", "accepted": true, "ms": 11.7 } ], "feeLamports": 1250000, "quoteMint": "So11111111111111111111111111111111111111112", "tipLamports": 1000000, "slot": 447483508, "guard": true, "buildMs": 1.8 } ``` On Lite a request that names other providers still goes out on `shrine` alone, and the answer carries a `warning` saying so. `success` means at least one provider accepted the transaction. Any accepted copy may be the one that lands, so to confirm, watch every signature in `submissions` (or just the wallet's balance). `ms` is measured from the request's arrival on our server to that provider's acknowledgement. `slot` is the newest slot our feed had seen when the copies left; the landed transaction's slot minus it is your slots-to-land. The answer leaves on the first acceptance: the trade is on its way the moment one provider has it, and a slower acknowledgement from another does not hold it. A copy still in flight at that moment is listed in `pending` with its `provider` and `signature` (absent when every provider had answered). Its signature is fixed before it leaves, so watch it like the ones in `submissions`: any copy may be the one that lands. ### One fill, never two Every copy carries the same trade and a different tip, so each has its own signature and any of them may be the one that lands. The first to execute trades; the rest fail on chain before moving funds. A failed copy costs its priority fee and nothing else: the tip is inside the same transaction and fails with it. So the trade happens once and the tip is paid once, to the provider whose copy landed. Set `guard: false` only if you mean to buy once per provider. What a reverted copy costs is its base fee (0.000005 SOL) plus its priority fee, nothing more. Copies the leader never includes cost nothing. So put the budget into `tip`, which only the winning copy pays, and keep `priorityFee` modest: at 0.00002 SOL three reverted copies cost 0.000075 SOL, at 0.001 they cost 0.003. ## Fees None: Lightspeed comes with Lite and Developer, which trade at 0%. The tip is yours to set and goes to the provider whose copy landed. On Lite the race is the Shrine lane alone; `providers` with more names, or `["all"]`, races every lane on Developer. See [Plans and limits](/fees#lightspeed). ## Speed checklist - Keep the wallet funded so no request waits on a deposit. - Send `mint` the moment your stream shows the launch; the `create` event on `subscribe_stream` carries it. A pump.fun coin can be bought from its create event alone, the API needs no chain read for it. - Run your bot in the [region](/regions) next to it; our side adds about a millisecond. - Add providers with `providers: ["all"]` and raise `tip` when a launch is crowded; keep the defaults otherwise. --- # Multi-Action Transactions: Buy, Sell, Create, Transfer and Burn in One Solana Transaction > Combine several operations - trades on any launchpad, a token launch, transfers, burns, fee claims, SOL wrapping - into one atomic Solana transaction, signed by one or several wallets. Unsigned, signed on your machine, 0% fee on Lite and Developer. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Multi-action transactions One transaction, several things in it: buy on one launchpad and sell on another, launch a coin and buy it, buy and move the tokens somewhere else, claim creator fees and burn. Everything in the list lands together or not at all. `POST https://sol.shrine.trade/api/local-actions`, with your [plan's key](/fees#the-key) in the `x-api-key` header. ```json { "publicKey": "", "actions": [ { "action": "buy", "mint": "", "amount": 0.05, "slippage": 15 }, { "action": "transfer", "to": "", "mint": "", "amount": "50%" }, { "action": "burn", "mint": "", "amount": "100%" } ], "priorityFee": 0.0005, "jitoTip": 0 } ``` The answer is one unsigned transaction plus the list of keys that must sign it: ```json { "tx": "", "blockhash": "…", "signers": ["", ""], "feeLamports": 125000, "sizeBytes": 1088, "actions": [ { "action": "buy", "wallet": "…", "mint": "…", "amount": 0.05, "feeLamports": 125000 }, { "action": "transfer", "wallet": "…", "mint": "…", "amount": 12345.6, "feeLamports": 0 }, { "action": "burn", "wallet": "…", "mint": "…", "amount": 12345.6, "feeLamports": 0 } ] } ``` Sign with every key in `signers` and send it through the [relay](/#sending) at `POST /api/send`, exactly like a single trade: the relay broadcasts through our own Solana endpoint and waits for confirmation, so the bot needs no RPC. The full script is under [Copy this](#copy-this). ## Actions Every action takes an optional `publicKey`. Without one it runs as the top-level wallet; with one, that wallet signs too and appears in `signers`. | `action` | Fields | Notes | |---|---|---| | `buy` | `mint`, `amount` (SOL, or the coin's quote), `slippage`, `pool`, `poolAddress` | Any launchpad the [trade endpoint](/#launchpads) routes. | | `sell` | `mint`, `amount` (tokens or `"100%"`), `slippage`, `pool`, `poolAddress` | | | `create` | `name`, `symbol`, then either `uri` or `imageUrl` (+ `description`, `twitter`, `telegram`, `website`), optional `mint`, `mintRef`, `initialBuy`, `slippage`, `creatorFeeAddress`, `quoteMint` and the V2 flags | A pump.fun launch, see [create-token](./create-token). Without `uri` the metadata is pinned for you; without `mint` you get an address ending in `pump` that we sign for. | | `transfer` | `to`, `amount`, `mint` (omit for SOL) | Creates the recipient's token account if needed. Free. | | `burn` | `mint`, `amount` | Burning all of it closes the account and refunds its rent. Free. | | `claimCreatorFees` | | Same as [claim-creator-fees](./claim-creator-fees). Free. | | `wrap` | `amount` (SOL) | SOL into the wallet's WSOL account. Free. | | `unwrap` | | Closes the WSOL account, every lamport comes back. Free. | `amount` on a sell, transfer or burn may be a number of tokens or a percentage of what the wallet holds. `"100%"`, `"50%"` and `"all"` are all understood. ## Launch and buy in one go A coin created in the transaction has no address yet when you write the request, so give the create a `mintRef` and refer to it as `"$name"` afterwards. Generate the mint keypair yourself and pass its public key as `mint`; it signs the transaction along with the wallet. Or leave `mint` out: the coin gets an address ending in `pump`, the transaction comes back already signed by it, and `mintSecretKeys` in the answer lists each provided mint with its secret. The same holds inside a [bundle](./jito-bundles). ```json { "publicKey": "", "actions": [ { "action": "create", "mintRef": "coin", "mint": "", "name": "My Coin", "symbol": "COIN", "uri": "https://…/metadata.json", "initialBuy": 0.5 }, { "action": "buy", "mint": "$coin", "amount": 0.2, "slippage": 30 } ] } ``` Buys on a coin born in the same transaction are quoted from the curve's launch constants, moved along by the SOL already put in before them, so give them a generous `slippage`. ## How much fits Solana caps a transaction at 1232 bytes. With the shared lookup table that is about four swaps on one launchpad, fewer across launchpads, and a launch with a dev buy and one more buy. When a set does not fit, the answer is a `400` naming the size; split it across a [bundle](./jito-bundles), where `mintRef` still resolves from one transaction to the next. ## Copy this ```js import { Keypair, VersionedTransaction } from "@solana/web3.js"; import bs58 from "bs58"; const wallet = Keypair.fromSecretKey(bs58.decode("")); const res = await fetch("https://sol.shrine.trade/api/local-actions", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ publicKey: wallet.publicKey.toBase58(), actions: [ { action: "buy", mint: "", amount: 0.01, slippage: 15 }, { action: "transfer", to: "", mint: "", amount: "50%" }, ], }), }); const { tx, signers } = await res.json(); const txObj = VersionedTransaction.deserialize(Buffer.from(tx, "base64")); txObj.sign([wallet]); // every key listed in `signers` // Send through the relay; it waits for confirmation. const sent = await (await fetch("https://sol.shrine.trade/api/send", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ signedTx: Buffer.from(txObj.serialize()).toString("base64") }), })).json(); console.log(sent.status, `https://solscan.io/tx/${sent.signature}`); ``` ```python import base64, os, requests from solders.keypair import Keypair from solders.transaction import VersionedTransaction wallet = Keypair.from_base58_string("") r = requests.post("https://sol.shrine.trade/api/local-actions", json={ "publicKey": str(wallet.pubkey()), "actions": [ {"action": "buy", "mint": "", "amount": 0.01, "slippage": 15}, {"action": "transfer", "to": "", "mint": "", "amount": "50%"}, ], }).json() raw = VersionedTransaction.from_bytes(base64.b64decode(r["tx"])) signed = VersionedTransaction(raw.message, [wallet]) # every key in r["signers"] sent = requests.post("https://sol.shrine.trade/api/send", json={"signedTx": base64.b64encode(bytes(signed)).decode()}).json() print(sent["status"], sent["signature"]) ``` ## Notes - **Atomic.** One instruction failing reverts the whole transaction: nothing partial ever lands. - **Priority fee** is one number for the whole transaction. The compute budget is sized from the actions inside. - **Fees.** None: multi-action transactions come with Lite and Developer, which trade at 0%. - **Unsigned**, like every trade endpoint: the API builds, your wallets sign; nothing is signed or sent by us. --- # Private Link: Your Server in Our Datacenter, Wired Straight to the Solana API > A Developer plan connection: your server runs in the same datacenter as the API in Frankfurt, Amsterdam or New York, and a direct link inside the building connects the two. No public internet in between, well under a millisecond each way. import Admonition from '@theme/Admonition'; # Private link Your bot's server sits in the same datacenter as ours, and a direct link inside the building connects the two. Requests and streams go from your machine to the API over that link only. They never cross the public internet. Public path every plan your server anywhere the public internet: networks and routers in between, shared with everyone public front door Shrine API sol.shrine.trade Private link Developer one datacenter: Frankfurt, Amsterdam or New York your server your rack direct link inside the building, nothing in between Shrine API our rack ## Why it matters Every request a bot makes, and every event it waits for, pays the trip between its server and the API twice: out and back. On the public internet that trip runs through other people's networks, and its length changes from one moment to the next. On a private link it is a few metres of cable. Typical one-way time from your bot to the API Across an ocean ~80 ms Another country 10–30 ms Same city ~1 ms Private link well under 1 ms, and the same every time - **Streams reach you first.** A trade on the [Advanced Data Stream](/data-api/stream) or the [Ultra Fast Shred Stream](/ultrafast/overview) is on your machine the moment we publish it, not after a trip across the internet. - **Trades start sooner.** A [Lightspeed](/lightspeed/overview) request reaches the race while the slot is still young, which is what the [block 0 lane](/lightspeed/block-zero) needs. - **The same timing every time.** No route changes, no busy hours, no shared queue at the front door. A bot tuned on Monday behaves the same on Friday. ## What goes over it Everything. The [Local Trade API](/), Lightspeed, every WebSocket stream and Ultra Fast Shred Stream all answer on the private address. Your plan's key works the same on it as on the public one, and the public address keeps working as a fallback. ## Getting one 1. Get the [Developer plan](https://shrine.trade/api/pricing/). 2. Ask in the private Telegram group. Tell us the region: Frankfurt, Amsterdam or New York. 3. We tell you which datacenter our servers are in. You rent a server or a rack there. 4. The datacenter runs the link between your rack and ours. We hand you the private address. Put your bot in the same city as the [region](/regions) you use. That alone takes the trip to about a millisecond; the private link is the step after. --- # Regions: Where the Solana API Runs > The Solana API runs in Frankfurt, Amsterdam and New York. The same API, the same keys and the same accounts in every region; pick the one next to your bot with the region picker. import Admonition from '@theme/Admonition'; # Regions
| Region | Address | |---|---| | Frankfurt | `sol.shrine.trade` | | Amsterdam | `sol-ams.shrine.trade` | | New York | `sol-nyc.shrine.trade` |
Free and Lite run on Frankfurt, `sol.shrine.trade`. Developer adds Amsterdam and New York. Every region runs the whole API: trades, [Lightspeed](/lightspeed/overview), every stream and [Ultra Fast Shred Stream](/ultrafast/overview), each on its own chain feed in its own city. Your Lightspeed account and key work in every region, so a bot moves by changing the address and nothing else. Next to the region you use. A request from the same city reaches the API in about a millisecond; from across an ocean it takes the ocean. A bot in the US belongs on New York, a bot in Europe on Frankfurt or Amsterdam. The region picker at the bottom of the sidebar rewrites every address in these docs to the region you choose, so the snippets you copy already point where your bot runs. Candle history (`ohlcv_history`) is kept in Frankfurt and answered from there in every region. Everything else, trades, streams and accounts, is served by the region you connect to. **One API, four networks.** The same shape of request, the same stream events and the same keys work on every network shrine.trade serves: [Solana](/), [BNB Chain](https://shrine.trade/flap/), [Robinhood Chain](https://shrine.trade/pons/) and [Arc](https://shrine.trade/arc/). A bot written for one moves to the next by changing the base URL. --- # Shrinemaps: Embed a pump.fun Token's Holder Map on Your Site > Add a live holder map of any Solana token to your page with one iframe: the largest holders as bubbles, linked where wallets belong together (funding, shared funders, launch bundles, bundlers), grouped into clusters. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Shrinemaps A token's largest holders as a map of bubbles, linked where wallets belong together and grouped into clusters: wallets that are likely one hand. Put it on your own site with one iframe. 8.4% cluster: 14.1% same funder bundler KOL 5.2% Holder map one bubble per holder, sized by its share of the supply ## Add it to your page Paste the frame and put the token's mint in the address: ```html ``` Or ask the API for the frame of a token, ready to paste: ```js const res = await fetch("https://sol.shrine.trade/api/maps//embed"); const { url, page, iframe } = await res.json(); document.querySelector("#holders").innerHTML = iframe; ``` ```bash curl https://sol.shrine.trade/api/maps//embed ``` ```json { "mint": "", "url": "https://maps.shrine.trade/embed/sol/", "page": "https://maps.shrine.trade/sol/", "iframe": "" } ``` `page` is the same map as its own page on maps.shrine.trade, for a "full map" link. No key is needed for either. | Option | Values | | |---|---|---| | `?n=` | `50`, `100`, `200` | How many of the largest holders the map shows at first. Default 100; the viewer can change it in the map. | **Size.** The map fills its frame: give it the full width of your column and at least 480 px of height; 600 suits a token page. It works on phones too. ## Reading the map - **Bubbles.** One per holder, sized by its share of the supply. Hover or tap one for the wallet, its share and how it is linked. - **Links.** A line joins two wallets that belong together: one sent the other its first SOL, both got their first SOL from the same address (a small funder dot between them), both bought in the launch slot with the dev, or both traded through a bundler (a red bundler dot). - **Clusters.** Wallets linked together share a colour and count as one cluster: likely one hand. The side panel lists the clusters with their combined share. - **Known traders.** KOLs show as their picture with their name and share. - **Exchanges.** An exchange funds everyone, so it is shown as a label, never as a link. - **Live.** The map refreshes every few seconds while it is open. Holdings count the token's trades Shrine has seen: a token that launched before Shrine started is marked partial. Links fill in as more of the token's wallets are looked at, so a fresh token's map grows over its first minutes. The advanced bundle detection embed comes with the [Developer plan](https://shrine.trade/api/pricing/). ## Talk to the map (optional) The map and your page can exchange a few messages with `postMessage`. It tells you only public facts: which token it shows, and which wallet someone clicked. | From | Message | Meaning | |---|---|---| | map | `{ type: "shrinemaps:ready" }` | The map has loaded. | | map | `{ type: "shrinemaps:coin", mint, symbol }` | The token it shows, for your page's title. | | your page | `{ type: "shrinemaps:host" }` | Your page opens wallets itself: the map's wallet button then reads "Open the wallet" and tells you which one. | | map | `{ type: "shrinemaps:wallet", wallet }` | Someone asked to open this wallet. | ```js const MAPS = "https://maps.shrine.trade"; const frame = document.querySelector(`iframe[src^="${MAPS}/embed/"]`); window.addEventListener("message", (e) => { if (e.origin !== MAPS || e.source !== frame.contentWindow) return; const m = e.data; if (m?.type === "shrinemaps:ready") frame.contentWindow.postMessage({ type: "shrinemaps:host" }, MAPS); if (m?.type === "shrinemaps:coin") document.title = `${m.symbol} holders`; if (m?.type === "shrinemaps:wallet") window.open(`https://solscan.io/account/${m.wallet}`, "_blank", "noopener"); }); ``` ## Safe to embed - **Its own scripts only.** The frame runs Shrinemaps' code and reads Shrine's data; it cannot change or navigate your page. - **Your address stays yours.** With `referrerpolicy="no-referrer"` the map never learns which page it is on. - **Nothing to sign.** No wallet connection, no account and no buttons that act for your visitors. --- # Ultra Fast Shred Stream: pump.fun Trades and Launches Before They Execute > The pre-execution route: every pump.fun and PumpSwap transaction read from the shreds the moment they arrive, before the block runs, on one WebSocket: what shreds are, what the feed carries and what it cannot, the subscriptions and the events. Developer plan. import useBaseUrl from '@docusaurus/useBaseUrl'; import Admonition from '@theme/Admonition'; # Ultra Fast Shred Stream Every other feed on this chain, ours included, waits for a block to execute: a node runs the transactions, writes the result, and a feed hands it on. Ultra Fast Shred Stream does not wait. It reads the transactions straight from the shreds, the moment they arrive, and sends you each pump.fun and PumpSwap transaction before the block has run. ## What shreds are A Solana block is not sent around as one piece. The leader cuts it into small packets called shreds and sends those out as it builds the block, so the rest of the network receives the block while it is still being made. Put enough shreds together and you have the transactions in the block, as the leader ordered them, before any node has executed them. That is what this feed listens to. It is the earliest point at which a transaction can be seen by anyone who is not the leader. ## Why it carries less than the Advanced Data Stream The [Advanced Data Stream](/data-api/stream) describes what a transaction did: the price it got, the tokens it received, the fees it paid, the reserves it left behind. None of that exists yet when a shred arrives, because nothing has executed. Ultra Fast Shred Stream can only tell you what a transaction asks for: who sends it, which coin, buy or sell, the amounts as submitted. It is sent earlier, so it knows less. That is the trade, and it is the whole point: the launch and the intent are what a bot acts on, the result is what it accounts with. `wss://sol.shrine.trade/api/ultrafast/stream?key=…` ## What you get earlier - **A launch, with its mint.** A pump.fun `create` names the mint, the bonding curve, the creator and the metadata URI in the instruction itself. You hold the mint before the block exists, and a [Lightspeed](/lightspeed/overview) buy can leave the same instant. - **Every buy and sell as sent.** The trader, the mint, the tokens asked for and the SOL offered. On PumpSwap, the pool and the quote mint too. - **Reverts included.** A transaction that will fail is still a transaction the leader is about to run; it is on this feed, and never on an executed feed. ## What it cannot carry Nothing that only exists after execution: the SOL actually paid, the tokens actually received, reserves after the trade, fees, logs, balances or success. The [Advanced Data Stream](/data-api/stream) has all of that a few milliseconds later. Run both: Ultra Fast Shred Stream to act, the executed stream to account. ## How far ahead The same pump.fun transaction on every feed, read from the US: Ultra Fast Shred Stream on the New York region first, the New York Advanced Data Stream 6 ms later, pumpapi.io 16 ms later, PumpPortal 115 ms later Measured on one machine in the US against the same transactions on 2026-10-08, over the public sockets, Ultra Fast Shred Stream on the New York region had each pump.fun transaction first on 98% of them: 6 ms before our own Advanced Data Stream in the same region, 16 ms before pumpapi.io, which serves every client from Frankfurt, and about 115 ms before PumpPortal's new-token feed. The gain is small against the best executed feed and large against everything else; what it buys is the whole order of events, not a fixed head start. ## Key Ultra Fast Shred Stream comes with the Developer plan, up to 3 sockets. Pass [your plan's key](/fees#the-key) as `?key=` or in the `x-api-key` header. A key that is not Developer's is refused before the upgrade: 401 for a missing or unknown key, 403 `plan_required` for one on another plan, 429 `limit_exceeded` with 3 sockets already open on the key. ## The stream `wss://sol.shrine.trade/api/ultrafast/stream?key=…[&mints=…][&wallets=…][&creates=1][&all=1]` A plain WebSocket, JSON text frames. Say what you want in the query string, or change it with messages afterwards; `subscribed` confirms each change. ```js const ws = new WebSocket("wss://sol.shrine.trade/api/ultrafast/stream?key=…&creates=1"); ws.onmessage = (m) => { const e = JSON.parse(m.data); if (e.type === "create") { console.log("launch", e.symbol, e.mint); // the mint, before the block exists ws.send(JSON.stringify({ subscribe: [e.mint] })); // and follow its trades } if (e.type === "trade") console.log(e.side, e.mint, e.solAmount, "SOL", e.trader); }; ``` ## Subscriptions | Query | Message | | |---|---|---| | `mints=…,…` | `{"subscribe": [...]}`, `{"unsubscribe": [...]}` | Trades of these mints (up to 500). | | `wallets=…,…` | `{"subscribeWallets": [...]}`, `{"unsubscribeWallets": [...]}` | Everything these wallets do on pump.fun and PumpSwap. | | `creates=1` | `{"creates": true}` | Every launch. Off by default. | | `all=1` | `{"all": true}` | The whole tape. | | | `{"ping": true}` | Answered with `{"type": "pong"}`. | `connected` comes once, with the newest `slot` the feed has seen and the subscriptions in force. ## Events Every event carries `signature`, `slot`, `recv` (wall clock in ms when it came off the feed), `mint`, `trader` and `"preExecution": true`. ### `create` ```json { "type": "create", "protocol": "PUMPFUN", "preExecution": true, "signature": "3nQ…", "slot": 447483508, "recv": 1791325200123, "mint": "…pump", "trader": "…", "name": "…", "symbol": "…", "uri": "https://…", "bondingCurve": "…", "creator": "…" } ``` `createV2: true` marks a launch through the newer create instruction. The dev buy that usually follows in the same transaction arrives as a `trade` with `"createBuy": true`. ### `trade` ```json { "type": "trade", "protocol": "PUMPFUN", "preExecution": true, "signature": "5aP…", "slot": 447483509, "recv": 1791325200231, "mint": "…pump", "trader": "…", "side": "buy", "tokenAmount": 1250000000000, "solAmount": 0.5, "solLamports": 500000000, "createBuy": false } ``` The amounts are what the instruction asked for, since nothing has executed: on a buy, `tokenAmount` is the tokens requested and `solAmount` the most SOL the trader will pay; on a sell, `tokenAmount` is what is sold and `solAmount` the least SOL accepted. A PumpSwap trade carries `pool`, `quoteMint`, `tokenAmount` and `quoteLamports` (the maximum in on a buy, the minimum out on a sell) instead. On the few pools opened with SOL or USDC as the base and the coin as the quote, the trade is still given from the coin's side (`mint` is the coin, `quoteMint` is SOL or USDC); there the limit sits on `tokenAmount` (the least received on a buy, the most sold on a sell) and `quoteLamports` is exact. The v3 curve trades (`buy_v3`, `buy_exact_quote_in_v3`, `sell_v3`) arrive like any other pump.fun trade; a v3 buy that empties the curve keeps buying from the tokens the migration deposits, so its executed total can be more than the curve had left. ### Multi-hop routes A PumpSwap `multi_hop_swap` trades through several pools and curves in one instruction (a coin paired with another pump coin, bought with SOL). It arrives as one `trade` per hop, in route order: ```json { "type": "trade", "protocol": "PUMPFUN", "preExecution": true, "signature": "3b5…", "slot": 454824976, "recv": 1791538132004, "mint": "3Tdp…Mpump", "trader": "…", "side": "buy", "via": "multi_hop", "hop": 1, "hops": 2, "bondingCurve": "…", "quoteMint": "56Zt…fpump", "tokenAmount": null, "quoteLamports": null, "minAmountOut": 1 } ``` `hop` counts from 0; `protocol` is `PUMPSWAP` for a pool hop (with `pool`) and `PUMPFUN` for a bonding curve hop (with `bondingCurve`); `quoteMint` is what the hop is paid in. Every hop of a route has the same `side`. Only the route's input is known before execution: on a buy route the first hop carries `quoteLamports` (and `solLamports` when that quote is SOL), on a sell route the first hop carries `tokenAmount`; every other amount is what the previous hop produces and is `null`. `minAmountOut` is on the last hop. Filter by `mint` to receive the hop for your coin. ### Routed trades Seven in ten pump.fun transactions do not call the program directly: a router, a bot's own contract or an aggregator calls it from inside. Before execution there is no inner instruction to read, so such a trade arrives with what the transaction itself gives away, marked `"routed": true` with the `router` program: ```json { "type": "trade", "protocol": "PUMPFUN", "preExecution": true, "signature": "2kq…", "slot": 455047120, "recv": 1791623412044, "mint": "…pump", "trader": "…", "routed": true, "router": "FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9", "bondingCurve": "…", "side": null, "tokenAmount": null, "solAmount": null, "solLamports": null } ``` The coin and the trader are always there: on a curve trade the mint is the key whose bonding curve is also in the transaction (with `bondingCurve`), on a PumpSwap trade it is the pump mint among the keys (no `pool`). `side` and the amounts are filled in when the router carries the pump.fun instruction verbatim in its own data, and `null` when it does not. A routed trade is an intent with less detail, not a different kind of event: filter by `mint` or `trader` as for any other. ### Connection messages `{"type": "lagged", "dropped": n}` means your client read too slowly and `n` events were skipped. `{"type": "error", "message": …}` answers a bad message. The feed reconnects on its own. ## Pairing with Lightspeed A `create` from this stream and a [Lightspeed](/lightspeed/trade) request with its `mint` is the shortest path from a launch to a landed buy that this API offers. The [copy trade](/lightspeed/trade) pattern is the same: follow a wallet here, mirror its buys there. --- # Wallet Utilities: Transfer, Burn, Wrap SOL, Balances and Token Info > Free, keyless helpers around the trade API - send SOL or any token, burn tokens, wrap and unwrap SOL, read every balance of a wallet, and get a token's decimals, supply, launchpad and last price. # Wallet utilities The small things a bot needs around its trades. All free, all keyless. The write endpoints return an unsigned transaction like a trade does: sign it with the wallet and post it to the relay, `/api/send`, or send it through your own RPC. Each one is also available as an action inside a [multi-action transaction](/local-actions). | | | |---|---| | [Transfer](/wallet/transfer) | SOL or any token to another wallet | | [Burn](/wallet/burn) | destroy tokens, close the account | | [Wrap SOL](/wallet/wrap-sol) | into and out of wrapped SOL | | [Balances](/wallet/balances) | SOL and every token of a wallet | | [Token info](/wallet/token-info) | decimals, supply, launchpad, last price | --- # Wallet Balances by API > SOL plus every token account of a wallet in one read, both token programs, frozen accounts marked. # Balances `GET https://sol.shrine.trade/api/balances?publicKey=…` SOL plus every token account of the wallet, both token programs, largest first. Frozen accounts carry `"frozen": true`, which is what a scam token looks like from the holder's side. ```json { "publicKey": "…", "sol": 1.2345, "lamports": 1234500000, "tokens": [ { "mint": "…pump", "account": "…", "amount": 12345.6, "amountRaw": "12345600000", "decimals": 6, "tokenProgram": "spl-token" } ] } ``` ## Example ```js const bal = await (await fetch(`https://sol.shrine.trade/api/balances?publicKey=${wallet.publicKey.toBase58()}`)).json(); console.log(bal.sol, "SOL,", bal.tokens.length, "tokens"); ``` --- # Burn Tokens by API > Destroy tokens for good in one unsigned transaction; burning everything closes the token account and refunds its rent. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Burn `POST https://sol.shrine.trade/api/burn` Destroys tokens for good. Burning everything also closes the token account, which refunds its rent. ```json { "publicKey": "", "mint": "", "amount": "100%" } ``` | Field | Notes | |---|---| | `amount` | Tokens in normal units, or a percentage of the balance. | | `priorityFee`, `jitoTip` | Optional, SOL. | ## Example Build, sign, send through the relay. The relay broadcasts through our own Solana endpoint, so the script needs no RPC. ```js import { Keypair, VersionedTransaction } from "@solana/web3.js"; import bs58 from "bs58"; const wallet = Keypair.fromSecretKey(bs58.decode("")); // Burn everything and close the account. const { tx } = await (await fetch("https://sol.shrine.trade/api/burn", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ publicKey: wallet.publicKey.toBase58(), mint: "", amount: "100%" }), })).json(); const transaction = VersionedTransaction.deserialize(Buffer.from(tx, "base64")); transaction.sign([wallet]); const sent = await (await fetch("https://sol.shrine.trade/api/send", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ signedTx: Buffer.from(transaction.serialize()).toString("base64") }), })).json(); console.log(sent.status, `https://solscan.io/tx/${sent.signature}`); ``` ```python import base64, requests from solders.keypair import Keypair from solders.transaction import VersionedTransaction wallet = Keypair.from_base58_string("") # Burn everything and close the account. r = requests.post("https://sol.shrine.trade/api/burn", json={"publicKey": str(wallet.pubkey()), "mint": "", "amount": "100%"}).json() raw = VersionedTransaction.from_bytes(base64.b64decode(r["tx"])) signed = VersionedTransaction(raw.message, [wallet]) sent = requests.post("https://sol.shrine.trade/api/send", json={"signedTx": base64.b64encode(bytes(signed)).decode()}).json() print(sent["status"], sent["explorer"]) ``` --- # Token Info by API: Decimals, Supply, Launchpad and Price > Decimals, supply and token program from the chain; name, symbol, launchpad, active pool and the last price from the data API's registry. # Token info `GET https://sol.shrine.trade/api/token-info?mint=…` Decimals, supply and token program from the chain; name, symbol, launchpad, active pool and the last price from the data API's own registry. ```json { "mint": "…pump", "decimals": 6, "tokenProgram": "spl-token", "totalSupply": 1000000000, "totalSupplyRaw": "1000000000000000", "name": "…", "symbol": "…", "uri": "…", "protocol": "PUMPFUN", "pool": "", "graduated": false, "quote": "So11111111111111111111111111111111111111112", "price": 3.2e-8, "marketCap": 32.1, "priceUSD": 0.0000031, "marketCapUSD": 3100, "priceTime": 1789126483, "solPriceUSD": 98.9, "holderReward": false, "mayhemMode": false, "cashbackCoin": false } ``` On a pump.fun coin the curve's flags are read live: `holderReward`, `mayhemMode`, `cashbackCoin`, and `creatorFeeBps` when a custom-pair coin sets its own. `quote` is the curve's quote mint. `price` is in the quote asset (SOL for most coins). The price fields appear once the token has traded since the API started watching it; a coin nobody has touched yet reports only the chain facts. ## Example ```js const info = await (await fetch(`https://sol.shrine.trade/api/token-info?mint=${mint}`)).json(); console.log(info.symbol, info.protocol, info.graduated ? "graduated" : "on the curve", info.priceUSD); ``` --- # Transfer SOL or Any Token by API > Send SOL or any SPL token to another wallet in one unsigned transaction; the recipient's token account is created when missing. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Transfer `POST https://sol.shrine.trade/api/transfer` SOL, or any SPL token, to another wallet. A token transfer creates the recipient's token account when it does not exist yet, paid by the sender. ```json { "publicKey": "", "to": "", "amount": 0.1 } { "publicKey": "", "to": "", "mint": "", "amount": "100%" } ``` | Field | Notes | |---|---| | `mint` | Omit for SOL. | | `amount` | SOL or tokens in normal units, or a percentage of the balance: `"100%"`, `"25%"`. | | `priorityFee`, `jitoTip` | Optional, SOL. | ```json { "tx": "", "blockhash": "…", "mint": null, "amount": 0.1, "amountRaw": 100000000, "feeLamports": 0 } ``` ## Example Build, sign, send through the relay. The relay broadcasts through our own Solana endpoint, so the script needs no RPC. ```js import { Keypair, VersionedTransaction } from "@solana/web3.js"; import bs58 from "bs58"; const wallet = Keypair.fromSecretKey(bs58.decode("")); // Every token of this mint the wallet holds, to another wallet. const { tx } = await (await fetch("https://sol.shrine.trade/api/transfer", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ publicKey: wallet.publicKey.toBase58(), to: "", mint: "", amount: "100%" }), })).json(); const transaction = VersionedTransaction.deserialize(Buffer.from(tx, "base64")); transaction.sign([wallet]); const sent = await (await fetch("https://sol.shrine.trade/api/send", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ signedTx: Buffer.from(transaction.serialize()).toString("base64") }), })).json(); console.log(sent.status, `https://solscan.io/tx/${sent.signature}`); ``` ```python import base64, requests from solders.keypair import Keypair from solders.transaction import VersionedTransaction wallet = Keypair.from_base58_string("") # Every token of this mint the wallet holds, to another wallet. r = requests.post("https://sol.shrine.trade/api/transfer", json={"publicKey": str(wallet.pubkey()), "to": "", "mint": "", "amount": "100%"}).json() raw = VersionedTransaction.from_bytes(base64.b64decode(r["tx"])) signed = VersionedTransaction(raw.message, [wallet]) sent = requests.post("https://sol.shrine.trade/api/send", json={"signedTx": base64.b64encode(bytes(signed)).decode()}).json() print(sent["status"], sent["explorer"]) ``` --- # Wrap and Unwrap SOL by API > Move SOL into the wallet's wrapped-SOL account, or unwrap by closing it, in one unsigned transaction. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Wrap SOL `POST https://sol.shrine.trade/api/wrap-sol` Moves SOL into the wallet's wrapped-SOL account, or unwraps by closing that account; unwrapping always returns every lamport in it. ```json { "publicKey": "", "action": "wrap", "amount": 0.5 } { "publicKey": "", "action": "unwrap" } ``` ## Example Build, sign, send through the relay. The relay broadcasts through our own Solana endpoint, so the script needs no RPC. ```js import { Keypair, VersionedTransaction } from "@solana/web3.js"; import bs58 from "bs58"; const wallet = Keypair.fromSecretKey(bs58.decode("")); // Half a SOL into wrapped SOL; { action: "unwrap" } brings everything back. const { tx } = await (await fetch("https://sol.shrine.trade/api/wrap-sol", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ publicKey: wallet.publicKey.toBase58(), action: "wrap", amount: 0.5 }), })).json(); const transaction = VersionedTransaction.deserialize(Buffer.from(tx, "base64")); transaction.sign([wallet]); const sent = await (await fetch("https://sol.shrine.trade/api/send", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ signedTx: Buffer.from(transaction.serialize()).toString("base64") }), })).json(); console.log(sent.status, `https://solscan.io/tx/${sent.signature}`); ``` ```python import base64, requests from solders.keypair import Keypair from solders.transaction import VersionedTransaction wallet = Keypair.from_base58_string("") # Half a SOL into wrapped SOL; {"action": "unwrap"} brings everything back. r = requests.post("https://sol.shrine.trade/api/wrap-sol", json={"publicKey": str(wallet.pubkey()), "action": "wrap", "amount": 0.5}).json() raw = VersionedTransaction.from_bytes(base64.b64decode(r["tx"])) signed = VersionedTransaction(raw.message, [wallet]) sent = requests.post("https://sol.shrine.trade/api/send", json={"signedTx": base64.b64encode(bytes(signed)).decode()}).json() print(sent["status"], sent["explorer"]) ```