# shrine.trade
> API-first Solana DEX
---
# Solana Trading API: Keyless Trades on pump.fun, PumpSwap, Bonk, Meteora, Raydium and Orca
> 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. 0.25% per trade, no sign-up, every launchpad.
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import TryIt from '@site/src/components/TryIt';
# 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.
## What you need
- **A Solana wallet** - its private key in base58. (Phantom: Settings → Show Secret Recovery Phrase → Show Private Key.)
- **Some SOL** in that wallet - enough for the trade plus a small amount of network fees.
- **A 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(process.env.WALLET_SECRET));
// 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" },
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. |
## 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 pump.fun coins
pump.fun coins can be priced in USDC or another token instead of SOL. The API detects the coin's quote from its curve: a buy's `amount` is then in that quote, a sell pays out in it, and the 0.25% fee is taken in it too. The response says which with `quoteMint` and `feeMint`; `feeLamports` is in the fee asset's base units. No field to set, and [`token-info`](./wallet-utilities#get-apitoken-infomint) shows a coin's `quote` up front.
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, included in the transaction. See [Fees](/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.
## 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 non-custodially through the API: you hold the mint keypair, we build the create transaction with an optional dev buy in the same transaction, custom quote pairs and holder rewards.
import TryIt from '@site/src/components/TryIt';
# Create Token
Launch a pump.fun coin without handing anyone a key. You generate the **mint keypair** on your machine and send only its public key; the returned transaction needs two signatures, your wallet's and the mint's, and you apply both locally. An optional `initialBuy` puts your dev buy in the same transaction so nobody can front-run the launch.
`POST https://sol.shrine.trade/api/create-token`
## Request
| Field | Type | Notes |
|---|---|---|
| `publicKey` | string | Creator wallet. Fee payer, first signer, receives creator fees later. |
| `mint` | string | Public key of a keypair you generated. Second signer. |
| `name` | string | Up to 32 bytes. |
| `symbol` | string | Up to 13 bytes. |
| `uri` | string | Metadata JSON URL. Get one from [`upload-metadata`](#upload-metadata) below, or host it yourself. |
| `initialBuy` | number | Optional dev buy in SOL, in the same transaction. |
| `slippage` | number | Percent, for the dev buy. Default 10. |
| `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 quote-control list instead of SOL. `initialBuy` is then in that quote, and so is the fee. |
| `mayhemMode` | boolean | pump.fun mayhem mode for the coin. |
| `holderReward` | boolean | A holder rewards coin: the creator fee of every trade is set aside for the coin's holders and paid out by pump.fun. Permanent; there is nothing for the creator to claim afterwards, and `creatorFeeAddress` is not used as the recipient. |
| `creatorFeeBps` | number | Custom pairs only (a quote other than SOL or USDC): the coin's own creator fee, up to pump.fun's current cap (300 today). SOL- and USDC-paired coins always use the standard schedule. |
| `priorityFee` | number | SOL. |
| `jitoTip` | number | SOL. |
Every launch uses pump.fun's `create_v2`: the coin is a Token-2022 mint. With `quoteMint` set, the dev buy and every later trade settle in that quote, and the response carries `quoteMint` and `feeMint`. Allowed quotes are USDC and the mints on pump.fun's [custom pairs list](https://pump.fun/docs/custom-pairs); anything else is refused before a transaction is built, as are cashback coins, which pump.fun no longer creates.
To launch and have other wallets buy in the same block, put the create in the first transaction of a [bundle](./jito-bundles) and the buys in the next ones, referring to the new coin by `mintRef`. A dev buy plus one more buy also fits in one [multi-action transaction](./local-actions#launch-and-buy-in-one-go), on SOL and on a custom quote alike.
## Upload metadata
`POST https://sol.shrine.trade/api/upload-metadata` pins the image and the metadata JSON through pump.fun's own IPFS endpoint and hands back the `uri`. Free.
```json
{
"name": "My Coin",
"symbol": "COIN",
"description": "…",
"imageUrl": "https://…/logo.png",
"twitter": "https://x.com/…",
"telegram": "https://t.me/…",
"website": "https://…"
}
```
`imageUrl` is fetched by us (up to 8 MB); pass `imageBase64` instead to send the bytes directly. The answer is `{ "uri": "https://ipfs.io/ipfs/…", "image": "https://ipfs.io/ipfs/…", "metadata": { … } }`; put `uri` in the create request.
## Example
```js
import { Connection, Keypair, VersionedTransaction } from "@solana/web3.js";
import bs58 from "bs58";
const wallet = Keypair.fromSecretKey(bs58.decode(process.env.WALLET_SECRET));
const mint = Keypair.generate(); // keep this: it is the token's identity
const res = await fetch("https://sol.shrine.trade/api/create-token", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
publicKey: wallet.publicKey.toBase58(),
mint: mint.publicKey.toBase58(),
name: "My Coin",
symbol: "MINE",
uri: "https://ipfs.io/ipfs/",
initialBuy: 0.5,
slippage: 10,
priorityFee: 0.0005,
}),
});
const { tx } = await res.json();
const transaction = VersionedTransaction.deserialize(Buffer.from(tx, "base64"));
transaction.sign([wallet, mint]); // both signers, wallet first
const connection = new Connection(process.env.RPC_URL);
const sig = await connection.sendRawTransaction(transaction.serialize());
console.log(`https://solscan.io/tx/${sig}`);
```
## Response
```json
{
"tx": "",
"blockhash": "…",
"mint": "…",
"bondingCurve": "…",
"feeLamports": 1250000,
"signers": ["", ""]
}
```
## Fees
Creation itself is free apart from network rent. The dev buy pays the normal **0.25%** trade fee, included in the transaction.
## Try it
Builds the unsigned transaction only. Generate a throwaway mint keypair and paste its public key; nothing is created until you sign and submit.
---
# 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). Both the live candle stream and the `ohlcv_history` seed need a free data key, which you get in the Telegram group; everything else on the API is keyless. 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 and the [`ohlcv_history`](../ohlcv-history) seed use the same free data key. 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
```
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.
- **Gaps.** A restart of the collector can lose a minute or two inside an hour. There is no marker; compare `block` numbers if you need to know.
---
# 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`
Pool + token metadata - name, symbol, decimals, supply, program, quote mint, the live pool address. Pass exactly one of `mint` or `pool` (passing both is fine; `pool` wins). Auth via the `x-api-key` header or an `?api_key=` query string.
| | |
|---|---|
| **Method** | `GET` |
| **Auth** | None - no API key needed |
## Example
```js
// Pass exactly one of `mint` or `pool`:
const params = new URLSearchParams({
mint: "Gc6rNxGnoQt6vCfhNzn5iJnPBu1V38GZfdYERfnXpump",
// or: pool: "4mBLRPUyfE7CvxmXiGJx5WA51isanbxpdbdPjFuuBzmY"
});
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": "Gc6rNxGnoQt6vCfhNzn5iJnPBu1V38GZfdYERfnXpump"},
)
print(res.json())
```
## Response
```json
{
"pool": "4mBLRPUyfE7CvxmXiGJx5WA51isanbxpdbdPjFuuBzmY",
"mint": "Gc6rNxGnoQt6vCfhNzn5iJnPBu1V38GZfdYERfnXpump",
"quote": "So11111111111111111111111111111111111111112",
"decimals": 6,
"program": "PUMPSWAP",
"total_supply": 1000000000,
"active": true,
"name": "Murio Newful",
"symbol": "MURIO",
"uri": "https://meta.lqsgqxmvlk.uk/metadata/fQNy9JEL"
}
```
## Try it
## Notes
- `program` is one of `PUMPFUN`, `PUMPSWAP`, `METEORA`, `RAYDIUM`, `ORCA` - useful for routing UI logic per DEX.
- `active: false` means the pool has been superseded (e.g. PumpFun bonding curve migrated to PumpSwap). Use `mint` lookup to find the current active one.
- `/lookup` is an alias for the same handler - same params, same cost.
---
# 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, free data key from Telegram.
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).
This event needs a free data key. Join the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk) and ask for one; it puts a name to who is pulling candles, meters nothing and costs nothing. Everything else on the data API is keyless.
| | |
|---|---|
| **Channel** | Socket.IO event with ack |
| **Auth** | free key in the handshake, `auth: { api_key }` - ask for one in the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk) |
| **Cost** | Free |
## 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
- Needs a free key - the same one as [`subscribe_ohlcv`](./subscribe-ohlcv); everything else on the API is keyless. Calling it on a keyless connection returns an `api_key_required` error in the ack; connect with `auth: { api_key: … }` to use it.
- Returns whatever's currently in the rolling Redis window (typically the last ~2 minutes of 1s candles between flushes to ClickHouse). For deeper history, a REST `/ohlcv` endpoint with `from` / `to` is on the roadmap.
---
# Free Solana Memecoin Data API: Real-Time 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.
# 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**. Everything is **free**. Every live stream and REST read needs no key at all - no account, no sign-up. The two candle events, `subscribe_ohlcv` and `ohlcv_history`, need a free key, which you get by asking in the Telegram group.
## 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
Nothing on this API costs money. Only two events ask for a key, and the key is free:
- [`subscribe_ohlcv`](./subscribe-ohlcv) - live 1-second candles
- [`ohlcv_history`](./ohlcv-history) - the one-shot candle fetch
Join the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk) and ask for one. Every other stream, [`GET /metadata`](./metadata) and [`GET /sol-price`](./sol-price) work without a key.
Pass the key 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 streams everything else fine; calling either candle event on it returns an `api_key_required` ack. A connection that *does* present a key must present a valid one - unknown or disabled keys are rejected at the handshake. One key can hold as many sockets as you like.
## 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 handshake carried a key that is not recognized, or one that has been switched off. |
| 404 | `not_found` | Pool/mint isn't registered (yet). |
| 429 | `rate_limited` | More than 3 requests a second from your IP to one endpoint. Wait a second and 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: "…" }`. The candle events on a keyless socket answer `api_key_required`; a stream type another socket from your address already holds answers `too_many_connections`.
## Limits
| | Value |
|---|---|
| Max concurrent Socket.IO subscriptions per connection | 50 |
| Open streams per IP | one per stream type - a second socket from the same address can hold other types, not one already held |
| REST | 3 requests per second per IP per endpoint |
| 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; a second socket asking for a type your address already holds gets `too_many_connections`. The trade endpoints share the REST cap and answer `rate_limited` with `Retry-After: 1`.
---
# 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 - no API key needed |
| **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';
# 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`**. Free, keyless, one socket.
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** | none - no API key needed |
| **Emits** | `stream` |
| **Cost** | Free |
| **Filter** | optional `protocols` and `actions` lists |
## Example
```js
import { io } from "socket.io-client";
const socket = io("https://sol.shrine.trade");
// 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")
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`, `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`→Raydium, `METEORA_DBC`→DAMM v2 |
| `curveComplete` | A DBC curve filled, before migration. | `METEORA_DBC` |
| `add` / `remove` | Liquidity provided or withdrawn. | PumpSwap, Raydium CPMM and AMM v4, DAMM v2, DLMM, Orca |
| `claimCreatorFees` | A creator collecting fees. | `PUMPFUN`, `PUMPSWAP` |
## Payload
Every event carries `signature`, `block`, `timestamp`, `action`, `protocol` and `txSigner`, the fee payer. `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.
### 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 and liquidity, 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 - no API key needed |
| **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 - no API key needed |
| **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, with a free data key from Telegram.
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 key: this and [`ohlcv_history`](./ohlcv-history) are the two events that ask for one - join the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk) to get yours.
This event needs a free data key. Join the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk) and ask for one; it puts a name to who is pulling candles, meters nothing and costs nothing. Everything else on the data API is keyless.
| | |
|---|---|
| **Channel** | Socket.IO event |
| **Auth** | free key in the handshake, `auth: { api_key }` |
| **Emits** | `ohlcv` |
| **Cost** | Free |
## Example
```js
import { io } from "socket.io-client";
const socket = io("https://sol.shrine.trade", { auth: { api_key: "sk_…" } }); // free key
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_…"}) # free key
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
- Free. The key only puts a name to who is pulling candles; nothing is metered.
- Granularity is 1s; aggregate client-side if you want 1m/5m/1h.
- Seed the initial window with [`ohlcv_history`](./ohlcv-history) (same key), then keep it live here.
- For per-trade detail use [`subscribe_trades`](./subscribe-trades).
- For a full working chart see [Examples → OHLCV chart](./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 - no API key needed |
| **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 - no API key needed |
| **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 - no API key needed |
| **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).
## 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).
---
# Fees: 0.25% per Solana Trade, Everything Else Free
> What shrine.trade charges on Solana: 0.25% of each trade taken in the trade's quote asset, nothing for the data streams, the historical archive, launches, transfers, burns or claims.
# Fees
## Trading
| Action | Fee |
|---|---|
| Buy (local transaction) | **0.25%** |
| Sell (local transaction) | **0.25%** |
| Create token | Free (network rent only); the dev buy pays 0.25% |
| Claim creator fees | Free |
| Jito bundle | 0.25% per trade inside it; the tip goes to Jito |
The fee is taken in SOL, or in the quote token of a pump.fun coin priced in USDC or another mint, and included automatically in the transaction we build for you. There's nothing extra to configure.
### Volume discount
Trading a lot? Frequent users get a lower rate. Message us in the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk) with your wallet and we set a reduced fee on it. The discount applies per wallet to every trade built for it, with nothing to change on your side.
## Data API
Free, all of it. Every live stream and REST read needs no key and is never metered. The two candle events, `subscribe_ohlcv` and `ohlcv_history`, ask for a key - also free, handed out in the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk). See the [Data API overview](/data-api/overview#keys).
No monthly fee, no credit packs, no data charges.
---
# 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, both keyless:
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
The normal **0.25%** trade fee per trade. The tip goes to Jito, not to us.
---
# 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. Keyless, unsigned, 0.25% only on the trades.
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`
```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 your own RPC, exactly like a [single trade](/).
## 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. 0.25% fee. |
| `sell` | `mint`, `amount` (tokens or `"100%"`), `slippage`, `pool`, `poolAddress` | 0.25% fee. |
| `create` | `mint`, `mintRef`, `name`, `symbol`, `uri`, `initialBuy`, `slippage`, `creatorFeeAddress`, `quoteMint` and the V2 flags | A pump.fun launch, see [create-token](./create-token). The mint keypair signs. |
| `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.
```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 { Connection, Keypair, VersionedTransaction } from "@solana/web3.js";
import bs58 from "bs58";
const wallet = Keypair.fromSecretKey(bs58.decode(process.env.SECRET));
const conn = new Connection(process.env.RPC_URL, "confirmed");
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`
console.log(await conn.sendRawTransaction(txObj.serialize()));
```
```python
import base64, os, requests
from solders.keypair import Keypair
from solders.transaction import VersionedTransaction
from solana.rpc.api import Client
wallet = Keypair.from_base58_string(os.environ["SECRET"])
conn = Client(os.environ["RPC_URL"])
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"]
print(conn.send_raw_transaction(bytes(signed)).value)
```
## 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.** 0.25% on each buy and sell, taken in SOL inside the same transaction and merged into one transfer per wallet. Everything else is free.
- **Keyless and unsigned**, like every trade endpoint. No key, no account; nothing is signed or sent by us.
---
# StonkFun: Launch and Trade Tokenised-Stock Coins on Raydium LaunchLab
> Trade StonkFun coins through the same buy and sell endpoint, launch one priced in an xStock, a pre-IPO stock, a currency or SOL, and claim its creator fees. Keyless on both sides; you sign locally.
# StonkFun
[StonkFun](https://www.stonkfun.xyz) launches coins on Raydium LaunchLab priced in tokenised stocks, pre-IPO stocks, currencies or SOL. Trading them goes through the [trade endpoint](/) like any other coin: leave `pool` out and the mint is routed by itself, or force it with `pool: "stonkfun"`. The live streams tag them `STONKFUN`.
## Trading
A StonkFun coin is priced in its quote asset, not in SOL, and the trade endpoint follows that: on a buy, `amount` is how much of the quote to spend (0.5 of an xStock, 100 CARDS), the wallet must already hold that much plus the 0.25% fee, and the fee is taken in the quote. A sell pays out in the quote and the fee comes out of the proceeds. The response says which asset with `quoteMint` and `feeMint`. Quotes that are Token-2022 mints with a transfer fee (the xStocks) are handled; the buyer needs SOL only for the network fee.
Launching one, and claiming a launched coin's creator fees, goes through StonkFun's own hosted flow: their service quotes the launch, you sign one payment transaction, and StonkFun mints and lists the coin. These endpoints wrap that flow so a bot talks to one API. Neither side asks for a key or a private key.
## Quote assets
`GET https://sol.shrine.trade/api/stonkfun/pairs`
Everything a launch may be priced in, with `launchable` and `launchLabReady` flags. Pass `category=xstock` (or `prestock`, `currency`, …) to narrow it. `quoteMint` in a launch must come from this list.
## Launch
`POST https://sol.shrine.trade/api/stonkfun/launch`
```json
{
"publicKey": "",
"quoteMint": "XsoCS1TfEyfFhfvj8EtZ528L3CaKBDBRqRapnBbDF2W",
"name": "My Coin",
"symbol": "COIN",
"mode": "standard",
"devBuySol": 0.5,
"logo": "data:image/png;base64,…"
}
```
| Field | Notes |
|---|---|
| `mode` | `standard`: a 1% pool fee of which the creator earns half, or `feeTier: "2%"` for a 2% pool where the creator earns 1.5%. `reward`: a 1% pool plus a transfer tax paid to holders, no creator fee. |
| `devBuyPercent` / `devBuySol` | The dev buy as a share of supply or in SOL. One of the two. |
| `logo` | A data URL. Send the same bytes again on submit. |
| `airdropPercent`, `airdropTier` | Optional airdrop to the quote asset's top holders (`top100` … `top5000`). |
The answer carries `payment` (the launch fee plus your dev buy, in SOL, and the address it goes to), `paymentTransaction` (base64, unsigned), `signedQuote` and `expiresAt`. Sign `paymentTransaction` with the creator wallet within ten minutes and submit:
`POST https://sol.shrine.trade/api/stonkfun/launch/submit`
```json
{ "signedQuote": "…", "signedTransaction": "", "logo": "data:image/png;base64,…" }
```
The answer is `{ "status": "processing" | "completed", "mint": "…", "paymentSignature": "…" }`. Poll `GET /api/stonkfun/launch/{paymentSignature}` until `completed`. Never pay twice: a second payment is a second coin.
StonkFun's launch fee is set by them (about 0.29 SOL at the time of writing, shown in `payment.feeSol`); shrine takes nothing on a launch.
## Claim creator fees
`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, so anyone else gets a refusal. Sign it and submit:
`POST https://sol.shrine.trade/api/stonkfun/claim-fees/submit` with `{ "publicKey", "mint", "intentId", "signedTransaction" }`. Resubmitting a claim that already went through returns the original signature with `alreadySubmitted: true`.
## Limits
StonkFun rate-limits its flow per IP: 25 launch quotes and 20 claim quotes a minute, 300 other calls. Those apply on top of ours. Their answers pass through unchanged, `data` wrapper included, so an error from their side names the field or limit that was hit.
---
# 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 send it through your own RPC. Each one is also available as an action inside a [multi-action transaction](./local-actions).
## `POST /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 }
```
## `POST /api/burn`
Destroys tokens for good. Burning everything also closes the token account, which refunds its rent.
```json
{ "publicKey": "", "mint": "", "amount": "100%" }
```
## `POST /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" }
```
## `GET /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" }
]
}
```
This one read lists accounts by owner, an indexed query some public RPCs refuse; the API sends it to a dedicated RPC (`INDEXED_RPC_URL`).
## `GET /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.
## Copy this
```js
const base = "https://sol.shrine.trade";
// Read
const bal = await (await fetch(`${base}/api/balances?publicKey=${wallet.publicKey}`)).json();
const info = await (await fetch(`${base}/api/token-info?mint=${mint}`)).json();
// Write: build, sign, send
const { tx } = await (await fetch(`${base}/api/transfer`, {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ publicKey: wallet.publicKey.toBase58(), to: friend, mint, amount: "100%" }),
})).json();
const t = VersionedTransaction.deserialize(Buffer.from(tx, "base64"));
t.sign([wallet]);
await conn.sendRawTransaction(t.serialize());
```