Lunar Shadow Labs
Documentation

Lunar Shadow Labs feed

One WebSocket with every trade, launch, curve completion, migration, pool creation and liquidity change on 12 Solana venues, decoded into one JSON shape with Token-2022 risk flags.

Market data only. No signals, no advice, no profit claims (see Terms).

  1. Connect
  2. Filters
  3. Messages
  4. swap
  5. launch · curve_complete · migration
  6. pool_create · liquidity
  7. Mint flags
  8. mint_update
  9. Control messages, gaps, resume
  10. Venues
  11. Limits and performance

Connect

wss://feed.lunarshadowlabs.com/ws
Authorization: Bearer <your license key>

Your API key is your Whop license key: open whop.com/lunarshadow-b705 → Software → Your license keys (it is not on the order page). Browsers can't set WebSocket headers: use the subprotocols solfeed.v1, key.<your key> instead. Never put the key in the URL — that is refused. Your key allows 2 simultaneous connections.

Python

import asyncio, json, os, websockets   # pip install websockets

async def main():
    url = "wss://feed.lunarshadowlabs.com/ws?kinds=swap,launch&venues=pump,pumpswap"
    headers = {"Authorization": f"Bearer {os.environ['LSL_KEY']}"}
    async with websockets.connect(url, additional_headers=headers, compression=None, max_size=None) as ws:
        async for raw in ws:
            m = json.loads(raw)
            if m["kind"] == "gap":
                print("GAP", m["reason"], m.get("missed"))   # the only signal that data was missed
            elif m["kind"] == "swap":
                print(m["venue"], "buy" if m["is_buy"] else "sell", m["base_mint"], m["price"])

asyncio.run(main())

TypeScript (Node 22+)

const ws = new WebSocket("wss://feed.lunarshadowlabs.com/ws?kinds=swap",
  ["solfeed.v1", `key.${process.env.LSL_KEY}`]);
ws.onmessage = (e) => {
  const m = JSON.parse(e.data);
  if (m.kind === "swap") console.log(m.venue, m.is_buy ? "buy" : "sell", m.base_mint, m.price);
};

Check your key's status any time on your account page.

Filters

The full stream is about 480 messages/second (~0.65 MB/s). Filters run on our side, so you only receive what you ask for:

Or send {"op":"subscribe","kinds":[…],"venues":[…],"mints":[…],"pools":[…]} at any time; it replaces the current filter. With a filter, skipped seq numbers are normal.

Messages

Every message is one JSON object whose first fields are always v, seq, kind, venue, keys.

swap

{"v":1,"seq":184467,"kind":"swap","venue":"pumpswap","keys":["<base>","<quote>","<pool>"],
 "stage":"executed","source":"geyser","commitment":"processed","slot":371234567,"sig":"5x…",
 "ix":3,"inner":1,"t_ingest_us":1791262754000000,"t_emit_us":1791262754000025,"via":null,
 "pool":"…","user":"…","base_mint":"…","quote_mint":"So11111111111111111111111111111111111111112",
 "is_buy":true,"base_amount":"3500000000000","quote_amount":"125000000",
 "base_decimals":6,"quote_decimals":9,"price":0.0000357,
 "fees":{"lp":"…","protocol":"…","creator":"…"},"partial":false,"derived":false,
 "base_flags":{…}|null,"quote_flags":{…}|null,"x":{…}}

launch · curve_complete · migration

launch: mint, curve, creator, user, name, symbol, uri, quote_mint, token_program, flags. curve_complete: mint, curve, user, quote_mint. migration: mint, from_curve, to_venue, to_pool, base_amount, quote_amount (what the new pool received), quote_mint.

pool_create · liquidity

pool_create: pool, creator, base/quote mints and decimals, initial amounts (null when the venue creates pools empty), lp_mint, flags. liquidity: pool, user, add, mints, amounts, lp_amount.

Mint flags (base_flags / quote_flags)

null = not known yet — never a guess. Otherwise always program (spl | token2022), decimals, supply, mint_authority, freeze_authority (pubkey, or null = revoked), plus — only when the mint carries it — transfer_fee (older/newer with bps, max, epoch), transfer_hook, permanent_delegate, paused, default_frozen, non_transferable, interest_rate_bps, scaled_ui, close_authority, confidential, metadata_pointer, permissioned_burn, and extensions (every Token-2022 extension type number present).

A permanent delegate can move anyone's tokens; a freeze authority can freeze them; a transfer fee or hook changes what a transfer delivers; a paused mint cannot move. Quote mints can carry these too.

mint_update

{"kind":"mint_update","venue":"token2022","mint":"…",
 "changes":[{"field":"paused","old":false,"new":true}]}

A Token-2022 mint changed one of the fields above (not supply). sig may be null.

Control messages, gaps, resume

Venues

venue idprogram
pumppump.fun bonding curve
pumpswapPumpSwap AMM
launchlabRaydium LaunchLab (StonkFun, bonk.fun, …) — x.platform names the launchpad
meteora_dbcMeteora Dynamic Bonding Curve (Believe, Bags, Jupiter Studio, …) — x.config identifies the launchpad
raydium_v4, raydium_cpmm, raydium_clmmRaydium AMM v4, CPMM, CLMM
pancake_v3PancakeSwap v3 (Solana)
orcaOrca Whirlpool
meteora_dlmm, meteora_damm_v1, meteora_damm_v2Meteora DLMM, Dynamic AMM, DAMM v2
token2022not a venue: the source of mint_update events

Limits and performance