Advanced Data Stream(高级数据流)
按池的流回答的是"这个代币发生了什么"。这条流回答的是"这笔交易里发生了什么",覆盖我们索引的每一个发射台上的每一笔交易。它以纯 WebSocket 提供(最快的接入方式),也以 Socket.IO 事件 stream 提供。随 Lite 和 Developer 提供:用你套餐的密钥连接。
用它做跟单、骗局过滤,以及任何需要整个市场而不是单个池的场景。如果你只关心少数几个代币,subscribe_trades 对你对我们都更省。
| 连接 | wss://sol.shrine.trade/api/stream/ws,纯 WebSocket(最快),或 Socket.IO 事件 stream |
| 认证 | 你套餐的密钥:WebSocket 上用 ?key= 或 x-api-key 请求头,Socket.IO 上用 auth: { api_key } |
| 套餐 | Lite(2 个 socket)和 Developer(5 个 socket) |
| 过滤 | 可选的 protocols 和 actions 列表 |
| 续传 | sinceSlot:先发该 slot 之后的一切,再接实时 |
断线了?带 sinceSlot 重连,该 slot(槽位)之后的所有内容会先被发送;见回放与续传。想在这些交易执行之前就拿到它们,见 Ultra Fast Shred Stream(超快分片流)。
不带过滤订阅一次,整个市场就是你的:我们索引的每个发射台上的每一笔交易、发行、迁移和流动性变化,每笔交易一个事件。只想要其中一部分时,用 protocols 和 actions 收窄。
对比
连接
纯 WebSocket,由读取链上数据的 API 进程直接发出,中间没有任何环节。每条消息就是一个事件,JSON 见负载。
wss://sol.shrine.trade/api/stream/ws?key=sk_…&protocols=PUMPFUN,PUMPSWAP&actions=buy,sell
| 参数 | |
|---|---|
key | 你套餐的密钥(也可以放在 x-api-key 请求头里) |
protocols | 可选,逗号分隔:只要这些发射台 |
actions | 可选,逗号分隔:只要这些动作 |
sinceSlot | 可选:续传,先发该 slot 之后的一切(回放与续传) |
protocols 和 actions 都不带就是全部。要在已打开的 socket 上修改,发送 {"protocols": [...], "actions": [...]};{"ping": 1} 会回复 {"type": "pong"}。第一条消息是 {"type": "connected", ...},带当前生效的过滤;带 type 的消息是关于 socket 本身的,其余每条都是事件。在私有专线上,地址是 ws://<私有地址>:8080/api/stream/ws。
- JavaScript
- Python
import WebSocket from "ws";
const ws = new WebSocket("wss://sol.shrine.trade/api/stream/ws?key=sk_…&actions=buy,sell,create");
ws.on("message", (raw) => {
const e = JSON.parse(raw);
if (e.type) return; // connected、resumed、pong、subscribed、lagged
console.log(e.action, e.protocol, e.mint, e.quoteAmount);
});
import asyncio, json, websockets
async def main():
url = "wss://sol.shrine.trade/api/stream/ws?key=sk_…&protocols=PUMPFUN"
async with websockets.connect(url) as ws:
async for raw in ws:
e = json.loads(raw)
if "type" in e:
continue # connected、resumed、pong、subscribed、lagged
print(e["action"], e.get("mint"), e.get("quoteAmount"))
asyncio.run(main())
Socket.IO
同样的事件,以 Socket.IO 事件 stream 提供,适合已经在用 Socket.IO、或想在同一连接上接收其他流的客户端。它在我们这边多经过一个服务,所以中位数比纯 WebSocket 晚约 1 到 2 毫秒到达。
- JavaScript
- Python
import { io } from "socket.io-client";
const socket = io("https://sol.shrine.trade", { auth: { api_key: "sk_…" } });
// 全部,或者收窄:protocols 和/或 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; // 不是干净的发行
if (e.action === "buy") console.log(e.mint, e.tradersInvolved, e.quoteAmount, e.price);
});
// 之后要停止(免费):socket.emit("unsubscribe_stream");
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()
动作
action | 何时 | 协议 |
|---|---|---|
buy / sell | 任意 swap。每笔交易每个池一个事件;一笔交易里的多次 swap 会被聚合,并带逐笔的 breakdown。 | 所有发射台 |
create | 发射台铸币。 | PUMPFUN、PUMPFUN_MAYHEM、BONK、STONKFUN、METEORA_DBC |
createPool | 在已有代币上开设 AMM 池。 | PUMPSWAP、RAYDIUM、RAYDIUM_CLMM、METEORA、METEORA_DLMM、ORCA |
migrate | 曲线毕业进入其 AMM。 | PUMPFUN→PumpSwap,BONK 和 STONKFUN→Raydium,METEORA_DBC→DAMM v2 |
curveComplete | DBC 曲线已填满,尚未迁移。 | METEORA_DBC |
add / remove | 流动性的添加或撤出。 | PumpSwap、Raydium CPMM 和 AMM v4、DAMM v2、DLMM、Orca |
claimCreatorFees | 创建者领取手续费。 | PUMPFUN、PUMPSWAP |
负载
每个事件都带 signature、block、timestamp、action、protocol 和 txSigner(手续费支付者)。block 是 slot,timestamp 是链上该 slot 自己的时间,即 getBlockTime 对它返回的那一秒,所有发射平台都一样;同一 slot 里的两笔交易带同一秒。txSigner 不一定是交易者:机器人会经由中继器路由。做跟单请用 tradersInvolved。txIndex 是这笔交易在区块中的位置:配合每个 breakdown 条目的 ixIndex 和 innerIndex,就是每次 swap 在链上的顺序。同一个池的事件按这个顺序发出,只有一个例外:同时交易多个池的交易(路由、套利)按它第一个池的顺序发出,所以在其他池里可能稍早或稍晚到达。
交易
{
"signature": "…", "block": 445627693, "timestamp": 1788976012, "txIndex": 412,
"action": "buy", "protocol": "PUMPFUN", "txSigner": "…",
"pool": "…", "mint": "…", "quoteMint": "So111…",
"tokenAmount": 31952.74, "quoteAmount": 0.0161, "price": 0.000000504,
"tokenAmountRaw": "31952740000", "quoteAmountRaw": "16100000",
"marketCapQuote": 504.1, "tokensInPool": 1041000000, "quoteInPool": 30.5,
"tokensInPoolRaw": "1041000000000000", "quoteInPoolRaw": "30500000000",
"tradersInvolved": ["…"],
"breakdown": [
{ "trader": "…", "action": "buy", "tokenAmount": 31952.74, "quoteAmount": 0.0161, "price": 0.000000504,
"tokenAmountRaw": "31952740000", "quoteAmountRaw": "16100000",
"tokensInPool": 1041000000, "quoteInPool": 30.5, "tokensInPoolRaw": "1041000000000000", "quoteInPoolRaw": "30500000000",
"ixIndex": 3, "innerIndex": 2 }
]
}
price 是最后一笔交易每个代币支付的价格:计价金额除以代币数量(与 K 线和 token_update 的价格相同)。tokensInPool 和 quoteInPool 是发射台报告的池储备量:pump.fun 曲线为交易后的状态,PumpSwap 池为交易前的金库余额。同一个池在一笔交易里有多次 swap 时,事件上的储备量是整笔交易前后的池状态(PumpSwap 池为第一次 swap 之前,曲线为最后一次交易之后),每个 breakdown 条目也带各自的储备量。marketCapQuote 是价格乘以供应量,以计价资产计。金额均为 UI 单位,各按自己代币的精度换算。每个金额和储备量还以原始值给出,即该 mint 的最小单位、整数字符串,字段名为原名加 Raw(tokenAmountRaw、quoteInPoolRaw 等):任何大小都精确,而浮点数在大池子上会丢掉最后几位。
每笔交易每个池一个事件。既有买又有卖时,tokenAmount 和 quoteAmount 是净额,action 是净代币数量的方向(代币净额为零时取计价净额的方向,两者都为零时取第一次 swap 的方向);每次 swap 本身在 breakdown 里。
PumpSwap 交易还带有为池子定价所需的数据。virtualQuoteReserves 是这次 swap 定价时池子的虚拟计价储备,有正负:池子按 quoteInPool 加上它来定价。事件上(第一次 swap 之前)和每个 breakdown 条目上都有。每个条目还带 userQuoteAmount 和 fees(费率为基点,金额以计价资产计,每个金额也有原始值:lpRaw、protocolRaw 等)。
quoteAmount 和 userQuoteAmount 是程序自己给出的两个金额。卖出,以及买入固定数量代币时,quoteAmount 是扣费前的金额,userQuoteAmount 是交易者含手续费实际收到或支付的金额。花固定金额的买入(buy_exact_quote_in,PumpSwap 的大多数买入)程序给出的方向相反:quoteAmount 是交易者含手续费支付的金额,userQuoteAmount 是扣掉手续费后这次 swap 实际定价所用的金额。买入的 userQuoteAmount 小于 quoteAmount 时就是这一种。
{ "trader": "…", "action": "sell", "tokenAmount": 120000, "quoteAmount": 0.84, "price": 0.000007,
"tokensInPool": 140000000, "quoteInPool": 92.1, "virtualQuoteReserves": 17.6, "userQuoteAmount": 0.8375,
"fees": { "lpBps": 20, "lp": 0.00168, "protocolBps": 5, "protocol": 0.00042, "creatorBps": 5, "creator": 0.00042,
"cashbackBps": 0, "cashback": 0, "lpRaw": "1680000", "protocolRaw": "420000", "creatorRaw": "420000", "cashbackRaw": "0" },
"ixIndex": 2, "innerIndex": 4 }
有些池子以 SOL 为基础代币、以代币为计价资产,其中有几个很活跃(约占 PumpSwap swap 的三分之一)。这些池子的计价资产是代币,所以同样的字段以代币计,并按代币一侧命名:virtualTokenReserves(池子按 tokensInPool 加上它来定价)、userTokenAmount 和 tokenFees。quoteAmount 和 tokenAmount 与其他地方一样,分别是 SOL 和代币。SOL 与 USDC 之间,USDC 算作代币、以 SOL 计价,无论池子把它们放在哪一侧。
发行
{
"action": "create", "protocol": "PUMPFUN",
"mint": "…", "pool": "…", "quoteMint": "So111…", "creator": "…",
"name": "…", "symbol": "…", "uri": "…", "decimals": 6, "supply": 1000000000, "supplyRaw": "1000000000000000",
"tokensInPool": 1073000000, "quoteInPool": 30, "tokensInPoolRaw": "1073000000000000", "quoteInPoolRaw": "30000000000",
"initialBuy": { "quoteAmount": 0.5, "tokenAmount": 17000000, "quoteAmountRaw": "500000000", "tokenAmountRaw": "17000000000000" },
"mintAuthority": null, "freezeAuthority": null, "tokenExtensions": []
}
creator 是发射台为这个代币记录的创建者,即创建者手续费归属的钱包。发行服务可能替别人签名创建,所以它不一定是 txSigner。诚实的发行里 mintAuthority 和 freezeAuthority 都应为 null;有值意味着可以增发供应或冻结钱包。tokenExtensions 按名称列出 Token-2022 扩展;在可信发射台之外,transferFeeConfig 和 permanentDelegate 值得留意。initialBuy 是发行者在发行交易里自己的买入(在 pump.fun 上,即发送创建的钱包和创建者的买入),没有则为 null;同一笔交易里其他钱包的买入作为交易发出。权限和扩展读自 mint 账户创建时的状态;如果 mint 早于这条流出现,它们可能缺失。
迁移与池
迁移带 fromPool、toPool 和 toProtocol。池创建带 pool、creator,以及已知时的 mint 和 quoteMint,命名与该池的交易一致:在以 SOL 为基础代币的池子里,mint 是代币。PumpSwap 的池创建还带开池时的储备 tokensInPool 和 quoteInPool。流动性事件带 pool、owner 和两侧的原始金额(amountARaw、amountBRaw);哪一侧对应哪个 mint 遵循发射台自己的排序。
试一试
说明
- 请每个客户端只开一个连接。一切都按 socket 过滤,所以开一个 socket、订阅一次即可。
- 一笔交易的事件按固定顺序发出:先交易,再发行和池创建,再迁移和流动性,最后手续费领取。
- 同一个池的事件按链上顺序发出,但同时交易多个池的交易除外:它按第一个池的顺序发出,在其他池里可能稍早或稍晚到达。需要严格顺序时,以及跨池时,按
block、txIndex,再按每次 swap 的ixIndex和innerIndex排序。 - 事件在看到区块时立即发出,早于网络确认。偶尔某个区块被丢弃,其中的交易又落在之后的区块里:同一个
signature会出现两次,block和txIndex不同。两个区块中只有一个留在链上,几乎总是后一个。更少见的是,一个区块的交易先在一个之后被网络丢弃的状态上执行:这些事件保留block和txIndex,但金额和储备可能与最终链上的不同,在最终区块里失败的交易也可能作为一笔交易发出(2026-10-29 两小时内有 3 个事件)。需要事件完全准确时,请在区块确认后核对该交易。 - 这条流上的每个事件也会按小时归档供回测:见历史回放。
LimitsLite 2 socketsDeveloper 5 socketsSee plans →