公共 API 提供当前监控池目录(Raydium,可选 Orca/Meteora 24h 榜单)、Raydium/Orca 分钟级成交量与实际手续费、单池历史成交量、CLMM 建池 WebSocket 及按创建者地址订阅开仓、加仓、减仓和关仓的 WebSocket。所有接口只读、无需 ADMIN_PASSWORD,并开放跨域 GET 请求。
以下示例使用:
https://your-dashboard.example.com
替换为实际部署域名即可。HTTP 成交量金额单位为 USD,时间使用 UTC ISO 8601;建池事件的 Unix 秒/毫秒字段见第 4 节,仓位原始数量与时间字段见第 5 节。
1. 获取当前监控的池子
GET /api/public/pools?dex={dex}&excludeMeme={boolean}
| 参数 | 必填 | 默认值 | 说明 |
|---|---|---|---|
dex | 否 | raydium | 池子来源:raydium、orca、meteora |
excludeMeme | 否 | true | 是否排除 Meme 池;对 dex=raydium 和 dex=orca 生效。支持 true/false(也兼容 1/0) |
raydium(默认,与不传dex行为一致):返回 Raydium RWA 池目录与手工添加池合并后的当前监控目录。默认excludeMeme=true,只保留包含 USDC 的池,以及 xStocks/Backpack 股票代币与 USDC、USDG、SOL 或 WSOL 的池;传excludeMeme=false可返回全部监控池。manuallyAdded表示该池是否在/ops/pool-registry中手工登记;手工池同样遵循 Meme 过滤参数。orca:返回 Orca RWA 榜单,与交易量监控使用同一个categories=securityTOP 50 接口,按 24h 手续费降序;默认只保留 xStocks / Backpack Securities 与 USDC、USDG、SOL 或 WSOL 组成的池,传excludeMeme=false可返回该接口的完整结果。meteora:返回 Meteora RWA 榜单,与/meteora菜单页同源,24h 统计窗口、按手续费降序 TOP 50。
excludeMeme 不过滤 Meteora,该 DEX 的榜单始终保持原有返回口径。
curl 'https://your-dashboard.example.com/api/public/pools'
curl 'https://your-dashboard.example.com/api/public/pools?dex=raydium&excludeMeme=false'
curl 'https://your-dashboard.example.com/api/public/pools?dex=orca'
响应示例(dex=raydium):
{
"data": [
{
"id": "POOL_ADDRESS",
"dex": "raydium",
"pair": "TOKEN/USDC",
"baseToken": {
"symbol": "TOKEN",
"mint": "BASE_MINT_ADDRESS",
"decimals": 6,
"logoURI": "https://example.com/token-logo.png"
},
"quoteToken": {
"symbol": "USDC",
"mint": "QUOTE_MINT_ADDRESS",
"decimals": 6,
"logoURI": "https://example.com/usdc-logo.png"
},
"feeRate": 0.0025,
"feeBps": 25,
"tvlUsd": 1250000.5,
"volume24hUsd": null,
"fees24hUsd": null,
"manuallyAdded": true,
"addedAt": "2026-07-15T02:30:00.000Z"
}
],
"meta": {
"count": 1,
"generatedAt": "2026-07-15T03:00:00.000Z"
}
}
baseToken.logoURI 和 quoteToken.logoURI 来自对应 DEX 的 token metadata;元数据未提供图片地址时返回 null。
dex=orca / dex=meteora 时返回榜单条目,差异如下:
manuallyAdded固定为false,addedAt固定为null。decimals来自上游 token metadata;上游未提供时为null。volume24hUsd和fees24hUsd为 24h 窗口的交易量与手续费(USD);dex=raydium时这两个字段固定为null。- 排序保持上游榜单顺序(24h 手续费降序)。
- 上游不可用时返回
502。
{
"data": [
{
"id": "POOL_ADDRESS",
"dex": "orca",
"pair": "SOL/USDC",
"baseToken": { "symbol": "SOL", "mint": "BASE_MINT_ADDRESS", "decimals": 9, "logoURI": "https://example.com/sol-logo.png" },
"quoteToken": { "symbol": "USDC", "mint": "QUOTE_MINT_ADDRESS", "decimals": 6, "logoURI": "https://example.com/usdc-logo.png" },
"feeRate": 0.0004,
"feeBps": 4,
"tvlUsd": 52300000.12,
"volume24hUsd": 18200000.45,
"fees24hUsd": 72800.18,
"manuallyAdded": false,
"addedAt": null
}
],
"meta": {
"count": 1,
"generatedAt": "2026-07-15T03:00:00.000Z"
}
}
2. 一次查询全部池成交量
GET /api/public/pools/volume?dex={dex}&interval={interval}&limit={limit}&excludeMeme={boolean}
一次返回当前全部 Raydium 或 Orca 监控池的成交量和手续费时间序列。参数与单池接口一致:
| 参数 | 必填 | 默认值 | 说明 |
|---|---|---|---|
dex | 否 | raydium | 成交量来源:raydium 或 orca;meteora 暂无链上成交量监控 |
interval | 否 | 1h | 聚合时间段:1m、5m、1h、24h |
limit | 否 | 24 | 每个池返回最近多少个 interval,正整数 |
excludeMeme | 否 | true | 是否排除 Meme 池;dex=orca 时只保留 xStocks / Backpack Securities 与 USDC、USDG、SOL 或 WSOL 组成的池 |
curl 'https://your-dashboard.example.com/api/public/pools/volume?interval=1h&limit=24'
curl 'https://your-dashboard.example.com/api/public/pools/volume?dex=orca&interval=1m&limit=60'
curl 'https://your-dashboard.example.com/api/public/pools/volume?interval=5m&limit=12&excludeMeme=false'
响应中的顶层 totals 是全部返回池在查询窗口内的合计;每个池也有自己的 totals 和固定 limit 个时间桶。data 按窗口交易量从高到低排列,交易量相同时按交易对名称排列。
{
"query": {
"dex": "raydium",
"interval": "1h",
"limit": 24,
"excludeMeme": true,
"windowStart": "2026-07-14T04:00:00.000Z",
"windowEnd": "2026-07-15T03:03:20.000Z"
},
"totals": {
"volumeUsd": 128300.5,
"feesUsd": 320.75,
"buyVolumeUsd": 71400.25,
"sellVolumeUsd": 56900.25,
"trades": 241,
"pools": 18
},
"data": [
{
"pool": {
"id": "POOL_ADDRESS",
"dex": "raydium",
"pair": "TOKEN/USDC",
"baseToken": { "symbol": "TOKEN", "mint": "BASE_MINT_ADDRESS", "decimals": 6, "logoURI": null },
"quoteToken": { "symbol": "USDC", "mint": "QUOTE_MINT_ADDRESS", "decimals": 6, "logoURI": null },
"feeRate": 0.0025,
"feeBps": 25,
"tvlUsd": 1250000.5,
"volume24hUsd": null,
"fees24hUsd": null,
"manuallyAdded": false,
"addedAt": null
},
"totals": {
"volumeUsd": 42150.75,
"feesUsd": 105.38,
"buyVolumeUsd": 23700.25,
"sellVolumeUsd": 18450.5,
"trades": 86
},
"data": [
{
"startTime": "2026-07-14T04:00:00.000Z",
"endTime": "2026-07-14T05:00:00.000Z",
"complete": true,
"volumeUsd": 3120.5,
"feesUsd": 7.8,
"buyVolumeUsd": 1870.25,
"sellVolumeUsd": 1250.25,
"trades": 7
}
]
}
]
}
该接口只返回本服务捕获并持久化的链上成交,不代理上游榜单统计。dex=orca 使用当前 Orca RWA TOP 50 监控目录;dex=meteora 返回 400。即使某个当前监控池在窗口内没有成交,也会返回该池,并以零填充所有时间桶。数据库聚合在一次查询中完成,不会按池逐个查询。
3. 按池 ID 查询成交量
GET /api/public/pools/{poolId}/volume?dex={dex}&interval={interval}&limit={limit}&excludeMeme={boolean}
查询当前监控池的成交量时间序列。
| 参数 | 必填 | 默认值 | 说明 |
|---|---|---|---|
poolId | 是 | - | Solana 池地址,且必须位于当前监控目录 |
dex | 否 | raydium | 池来源:raydium 或 orca |
interval | 否 | 1h | 聚合时间段:1m、5m、1h、24h |
limit | 否 | 24 | 返回最近多少个 interval,正整数 |
excludeMeme | 否 | true | 是否排除 Meme 池;dex=orca 时只保留发行方可识别的证券池 |
例如,以 5 分钟为一段,查询最近 12 段(约 1 小时):
curl 'https://your-dashboard.example.com/api/public/pools/POOL_ADDRESS/volume?interval=5m&limit=12'
curl 'https://your-dashboard.example.com/api/public/pools/ORCA_POOL_ADDRESS/volume?dex=orca&interval=1m&limit=60'
curl 'https://your-dashboard.example.com/api/public/pools/MEME_POOL_ADDRESS/volume?interval=5m&limit=12&excludeMeme=false'
响应示例:
{
"pool": {
"id": "POOL_ADDRESS",
"dex": "raydium",
"pair": "TOKEN/USDC",
"baseToken": { "symbol": "TOKEN", "mint": "BASE_MINT_ADDRESS", "decimals": 6, "logoURI": "https://example.com/token-logo.png" },
"quoteToken": { "symbol": "USDC", "mint": "QUOTE_MINT_ADDRESS", "decimals": 6, "logoURI": "https://example.com/usdc-logo.png" },
"feeRate": 0.0025,
"feeBps": 25,
"tvlUsd": 1250000.5,
"volume24hUsd": null,
"fees24hUsd": null,
"manuallyAdded": true,
"addedAt": "2026-07-15T02:30:00.000Z"
},
"query": {
"dex": "raydium",
"interval": "5m",
"limit": 12,
"excludeMeme": true,
"windowStart": "2026-07-15T02:05:00.000Z",
"windowEnd": "2026-07-15T03:03:20.000Z"
},
"totals": {
"volumeUsd": 42150.75,
"feesUsd": 105.38,
"buyVolumeUsd": 23700.25,
"sellVolumeUsd": 18450.5,
"trades": 86
},
"data": [
{
"startTime": "2026-07-15T02:05:00.000Z",
"endTime": "2026-07-15T02:10:00.000Z",
"complete": true,
"volumeUsd": 3120.5,
"feesUsd": 7.8,
"buyVolumeUsd": 1870.25,
"sellVolumeUsd": 1250.25,
"trades": 7
}
]
}
时间和数据口径
- 时间段按 UTC 自然边界对齐。例如
5m使用10:00–10:05、10:05–10:10;24h使用 UTC 自然日。 - 返回数组固定包含
limit个时间段,无成交的时间段补零。 - 最后一段通常仍在进行,此时
complete为false。query.windowEnd是服务器实际查询时间。 volumeUsd来自监控服务捕获的 Swap,以 1 分钟蜡烛聚合;加池和撤池不计入成交量。feesUsd是新捕获成交的链上实际手续费。Orca 使用Traded事件的lp_fee + protocol_fee,动态费率池也可按实际值统计;历史 Raydium 蜡烛未回填实际手续费时可能为0。dex=raydium默认不返回被 Meme 规则排除的池,显式传excludeMeme=false后可查询;dex=orca默认只返回 xStocks / Backpack Securities 证券池,传excludeMeme=false后可查询其他当前监控池。dex=orca从服务启动后开始累积本地成交,不扫描上线前历史;退榜池返回404。- 服务默认保留 90 天蜡烛数据,可通过
CANDLE_RETENTION_DAYS修改。单次最多返回 1000 段,并且不能超过当前保留期;超出时返回400。
错误响应
{
"error": {
"message": "interval must be one of: 1m, 5m, 1h, 24h",
"parameter": "interval"
}
}
| HTTP 状态码 | 含义 |
|---|---|
400 | 池地址或查询参数无效 |
404 | 池不在当前监控目录,或被默认 Meme 过滤规则排除(可传 excludeMeme=false 查询) |
500 | 服务内部错误 |
502 | dex=orca / dex=meteora 上游数据服务不可用 |
OpenAPI 3.1 机器可读定义见 docs/openapi.yaml。
4. WebSocket:订阅 Raydium CLMM 新建池
wss://your-dashboard.example.com/api/public/pool-creations/ws
wss://your-dashboard.example.com/api/public/pool-creations/ws?after=0
这是标准 WebSocket(RFC 6455),不是 SSE 或 Solana logsSubscribe。与其他公共 API 一样,只读、无需鉴权,可从浏览器跨域连接。不要传 ADMIN_PASSWORD,此接口不需要签名密钥。
- 只发布本项目捕获的成功 Raydium CLMM 建池事件,不包含 CPMM、Orca、Meteora 或失败交易。
- 新池发现独立于 RWA / 手工池目录,包含普通代币新池;本接口不提供
excludeMeme或dex参数。 - 发送时过滤已拉黑的池及已知黑名单创建者。已发送的事件不会因之后拉黑而撤回;创建者未知时为
null。 - 复用现有链上订阅及 SQLite 事件,不为每个客户端增加 RPC 或链上 WebSocket。元数据在服务端统一补充;失败最多尝试 3 次后发布可用字段,名称、创建者等可能为
null。 - 事件正式发布后分配持久化游标,后续不再更新同一事件的 payload。元数据补充较慢的池可能晚于其他池发布,因此游标顺序不等同于 slot 或建池时间顺序。
- 建池事件仅通过 WebSocket 分享。byreal-copy 使用已有
RAYDIUM_DASHBOARD_API_BASE自动订阅此接口,首次after=0,后续按持久化游标续传。
连接参数与补发
| 参数 | 必填 | 默认 | 说明 |
|---|---|---|---|
after | 否 | 连接时的最新游标 | 十进制非负整数,以字符串保存,最大 9007199254740991。从该游标之后开始补发,再持续推送新事件 |
不传 after 只接收之后发布的事件;after=0 从数据库保存的第一条可见事件开始补发。每个连接每秒最多扫描 100 条记录,积压会逐批追上。没有订阅指令,握手成功即开始推送;不要发送 JSON subscribe 消息。
游标与本实例的 SQLite 数据库绑定,重启后仍有效。持久化卷丢失、还原旧备份或改连另一份数据库时,应重新确定起点。游标大于数据库当前最大值会在握手阶段返回 409,不会静默忽略。
服务端目前不按时间清理建池 feed。黑名单会使游标出现跳号。客户端应在成功处理事件后保存游标,并按 data.eventId 幂等入库;重连补发可能重复已接收但未提交游标的事件。此机制只能重放已经捕获并持久化的事件,不能补齐链上 WS 断线期间从未收到的新池。
服务端消息
1. ready: 握手后的第一条消息。
{"type":"ready","version":1,"cursor":"120","latestCursor":"150","heartbeatSeconds":25}
cursor 是本次读取起点,latestCursor 是握手时的最新位置。请求 after=120 时仍需处理 121~150,不能直接保存 latestCursor 跳过补发。不传 after 时两者相同,可保存 cursor 作为初始重连起点。
2. pool.created: WebSocket 外层消息包裹 v1 建池事件。
{
"type": "pool.created",
"cursor": "121",
"data": {
"version": 1,
"type": "raydium.pool.created",
"eventId": "SIGNATURE:pool:POOL_ADDRESS",
"signature": "SIGNATURE",
"slot": 350000000,
"blockTime": null,
"detectedAt": 1789084800000,
"programId": "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK",
"poolType": "clmm",
"poolAddress": "POOL_ADDRESS",
"creator": null,
"ammConfig": null,
"sqrtPriceX64": "18446744073709551616",
"tick": 0,
"tickSpacing": 60,
"vaultA": "VAULT_A",
"vaultB": "VAULT_B",
"tokenA": {"mint":"MINT_A","symbol":"AAPLx","name":"Apple xStock","icon":null,"decimals":6},
"tokenB": {"mint":"MINT_B","symbol":"USDC","name":"USD Coin","icon":null,"decimals":6}
}
}
| 字段 | 类型 / 含义 |
|---|---|
外层 cursor | 持久化发布序号,十进制字符串,不是 slot |
version / type | 固定为 1 / raydium.pool.created |
eventId | ${signature}:pool:${poolAddress};一笔交易多个池有不同 ID |
signature / slot | 交易签名与确认时所在 slot;commitment 为 confirmed |
blockTime | Unix 秒或 null;实时日志没有精确区块时间,通常为 null |
detectedAt | 本项目发现事件的 Unix 毫秒,不是池开盘时间 |
programId / poolType | Raydium CLMM 程序地址 / clmm |
poolAddress | 新池地址 |
creator / ammConfig | 从池账户补充的创建者 / 配置地址;不可用时为 null |
sqrtPriceX64 | 创建时链上 Q64.64 平方根价格,u128 十进制字符串;不得转换为 JS Number 后再持久化 |
tick / tickSpacing | 初始 tick / 池 tick 间距 |
vaultA / vaultB | 两侧 token vault 地址 |
tokenA / tokenB | 保持链上 token0 / token1 顺序;含 mint、symbol、name、icon、decimals,除 mint 外均可为 null |
该事件不包含初始加池金额、TVL 或实际开仓:CLMM 创建池与添加流动性是不同操作。价格参数保持链上原始口径,未应用 Token-2022 ScaledUiAmount 倍率。
3. checkpoint: 一批扫描完成,可在前面的事件都处理成功后保存此游标。它也会跨过被黑名单隐藏的记录。
{"type":"checkpoint","cursor":"150"}
JavaScript 重连示例
let cursor = localStorage.getItem('raydium-pool-cursor');
let retryMs = 1000;
function connect() {
const url = new URL('wss://your-dashboard.example.com/api/public/pool-creations/ws');
if (cursor !== null) url.searchParams.set('after', cursor);
const ws = new WebSocket(url);
ws.onopen = () => { retryMs = 1000; };
ws.onmessage = ({ data }) => {
const message = JSON.parse(data);
if (message.type === 'pool.created') {
// 示例同步处理;生产消费者按 eventId 幂等提交后才推进游标。
console.log(message.data);
}
if (['ready', 'pool.created', 'checkpoint'].includes(message.type)) {
cursor = message.cursor; // ready 用 cursor,不能用 latestCursor
localStorage.setItem('raydium-pool-cursor', cursor);
}
};
ws.onclose = () => {
const wait = retryMs + Math.random() * 500;
retryMs = Math.min(retryMs * 2, 30000);
setTimeout(connect, wait);
};
}
connect();
如果处理是异步的,须按收到的顺序串行完成事件和 checkpoint,不能让后续 checkpoint 先提交。数据库被重置导致 409 时,人工选择清除本地游标(从实时开始)或改用 after=0(全部重放),不要无限重试旧游标。Node 客户端可使用 ws 包,浏览器使用内置 WebSocket。
心跳、错误与部署
- 每 25 秒发送 WebSocket 协议 ping;浏览器和
ws默认自动 pong,无须发送 JSON 心跳。下一次检查仍未 pong 时终止连接。 - 默认每个进程最多 200 个连接,可通过
POOL_CREATION_WS_MAX_CONNECTIONS调整;超过上限返回503。 - 握手
400:非法、重复或不支持的参数;409:游标超前;503:存储尚未准备好、mock 模式或连接已满。普通 HTTP GET 返回426 Upgrade Required。 - 连接后关闭码
1008:发送了应用消息或数据库回退导致游标失效;1011:存储不可用。慢客户端发送缓冲超过 1 MiB 时终止连接,客户端通常看到1006,应从最后成功处理的游标重连。 - 启动必须使用
pnpm start(node server.mjs),开发使用pnpm dev。直接运行next start不提供此 WebSocket。Dockerfile 已同步复制入口及 transport,监听与网页相同的 3000 端口。 - EasyPanel / Nginx 必须转发
Upgrade和Connection;公网用 HTTPS / WSS。代理空闲超时建议大于 60 秒。多个实例必须各自使用一致的事件存储和游标范围;不要把本地 SQLite 游标用于另一份独立数据库。
Nginx 示例(按实际 upstream 调整):
location /api/public/pool-creations/ws {
proxy_pass http://dashboard:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 75s;
}
部署方式参考 Next.js Custom Server,协议处理使用 ws。OpenAPI 文件描述握手及 x-websocket 消息协议;普通 OpenAPI HTTP 客户端生成器不会自动生成 WebSocket 订阅代码。
5. WebSocket:按创建者地址订阅新建仓位与关仓
wss://your-dashboard.example.com/api/public/position-openings/ws?creator=CREATOR_ADDRESS
wss://your-dashboard.example.com/api/public/position-openings/ws?creator=CREATOR_ADDRESS&dex=orca&after=120
wss://your-dashboard.example.com/api/public/position-openings/ws?creator=ADDRESS_A&creator=ADDRESS_B&dex=all
标准只读 WebSocket,无需密码;连接后自动订阅,不发送 subscribe 消息。支持 Raydium CLMM / Orca Whirlpool,范围为本服务已有监控池(Raydium RWA/手工池目录、Orca RWA TOP 50),不是该钱包的全链交易监听。增加客户端不会增加链上 RPC 或链上订阅。
| 参数 | 必填 | 默认 | 说明 |
|---|---|---|---|
creator | 是 | — | 开仓时仓位持有人的 Solana 地址,不是出资人或手续费支付人。可重复传入 1–100 个参数,如 creator=A&creator=B;每个值必须是一个有效完整地址,不接受逗号分隔。重复地址去重,只推送一次;数量限制按去重前参数个数计算 |
dex | 否 | all | all、raydium 或 orca |
after | 否 | 连接时最新发布游标 | 非负十进制整数字符串,最大 9007199254740991;从此游标之后续传。0 补发仍保留的历史事件 |
一个连接合并所有订阅地址的开仓、加仓、减仓与关仓事件,按全局发布游标升序推送,data.creator 标明事件所属地址。dex 统一作用于整组地址。只有一条 after / checkpoint 游标,无需为组内各地址分别续传;单地址 URL 保持兼容。
发布条件: 已捕获创建事件、已首次注入非零代币数量、已知创建者,并且保存了原始 tick/金额。Raydium 直接使用开仓日志;Orca 等开仓交易中的持有人补齐后再发布,空仓创建和首次入金可以是两笔交易。后续加仓发布为 position.increased,不重复发布新开仓。创建者补齐失败、旧版记录缺少原始参数时不发布,不能从展示价格猜测 tick。
事件先与仓位一起持久化,再分配独立发布游标;同一开仓事件只发布一次。延迟补齐的 Orca 创建者获得新的发布游标,因此不会被已经推进的订阅游标漏掉。按当前池/创建者黑名单过滤。
消息与跟单参数
第一条消息:
{"type":"ready","version":1,"creator":"CREATOR_ADDRESS","creators":["CREATOR_ADDRESS"],"dex":"all","scope":"monitored-pools","retentionDays":10,"cursor":"120","latestCursor":"125","heartbeatSeconds":25}
ready.creators 总是返回按字典序排序、去重后的完整地址数组;ready.creator 为兼容旧客户端保留,只有一个地址时为该地址,多地址时为 null。事件中的 data.creator 始终是该事件的单个原始持有人地址。
多地址 ready 示例:
{"type":"ready","version":1,"creator":null,"creators":["ADDRESS_A","ADDRESS_B"],"dex":"all","scope":"monitored-pools","retentionDays":10,"cursor":"120","latestCursor":"125","heartbeatSeconds":25}
cursor 是本次续传起点;不要把 latestCursor 保存为已处理游标。after=0 时 cursor 可能是已经清理的历史边界。
新开仓消息(以下地址为占位示例):
{
"type": "position.opened",
"cursor": "121",
"data": {
"version": 1,
"type": "position.opened",
"eventId": "orca:OPEN_SIGNATURE:open:0",
"dex": "orca",
"programId": "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc",
"poolAddress": "POOL_ADDRESS",
"creator": "CREATOR_ADDRESS",
"signature": "OPEN_SIGNATURE",
"eventIndex": 0,
"slot": 454600000,
"openedAt": 1791504000000,
"fundingSignature": "FUNDING_SIGNATURE",
"fundingSlot": 454600001,
"fundedAt": 1791504000400,
"publishedAt": 1791504002000,
"commitment": "confirmed",
"positionAddress": "POSITION_PDA",
"nftMint": "POSITION_NFT_MINT",
"tickLower": -120,
"tickUpper": 240,
"tickSpacing": 60,
"tokenA": {"mint":"MINT_A","symbol":"TOKEN","decimals":6,"amountRaw":"1000000"},
"tokenB": {"mint":"MINT_B","symbol":"USDC","decimals":6,"amountRaw":"100000000"},
"amountUsd": 200,
"display": {"baseMint":"MINT_A","quoteMint":"MINT_B","priceLower":0.9880723,"priceUpper":1.0242891}
}
}
| 字段 | 用途与口径 |
|---|---|
eventId | ${dex}:${signature}:open:${eventIndex},幂等键;同一交易多个仓位分别发布 |
dex / programId / poolAddress | 选择 DEX SDK、程序和目标池 |
creator | 原始开仓持有人;Orca 组合仓位从该交易的组合 NFT token balance 识别,不把代理权限地址当成持有人 |
tickLower / tickUpper | 链上原始整数 tick,保持 token A/B 方向;展示交易对反转也不改变这两个值,不从价格反推 |
tickSpacing | Orca 池目录中的 tick 间距;Raydium 当前未保存该元数据,返回 null,消费者可随池状态读取 |
tokenA / tokenB | 链上 token0/token1 或 Whirlpool A/B 顺序,含 mint、symbol、decimals、首次注入的原始数量 amountRaw |
amountRaw | u64 最小单位十进制字符串,应使用 BigInt/BN;没有 ScaledUiAmount 展示倍率,也不是已含滑点的 maximum amount。转账费按对应 DEX/Token-2022 规则处理 |
amountUsd | 初次入金数量乘当时缓存价格的 USD 估值;缺少价格可能为 0,不是下单预算或保证成交金额 |
display | 仅供显示的 base/quote 价格区间,可能反向且已应用 ScaledUiAmount;不要用于还原链上 tick |
signature / slot | 创建仓位交易;eventIndex 为该 DEX 解码事件中的序号,不是 instruction index |
fundingSignature / fundingSlot | 首次入金交易;Raydium 与开仓一致,Orca 可能不同 |
openedAt / fundedAt | Unix 毫秒;实时路径使用本服务收到日志的时间,补漏路径使用区块时间,并非精确成交时间 |
publishedAt | 本服务正式发布的 Unix 毫秒,可能晚于开仓/首次入金 |
positionAddress / nftMint | 原始仓位身份,Raydium 日志缺失时为 null;跟单者创建自己的仓位/NFT,不复用原仓位身份 |
commitment | confirmed;事件不是 finalized 通知,也不发送链回滚撤销事件 |
消费者可以按原始数量或自定比例/预算跟单,使用返回的目标池与原始 tick 区间。执行前仍需通过对应 DEX SDK 读取当前池状态,计算当前所需数量、滑点上限、账户与 Token-2022 转账费;接口提供开仓信号和原始条件,不返回已签名交易,也不代替消费者下单。
加仓与减仓消息
同一订阅连接新增两种消息:position.increased(后续增加流动性)和 position.decreased(减少流动性)。订阅地址、DEX 过滤、单一游标、幂等与十天回放规则不变。旧客户端需要处理这两种类型后再提交后续 checkpoint。
- 第一次入金记为
position.opened;Orca 的空仓创建后第一次入金不重复算加仓,同笔交易之后再次增加流动性才算加仓。 - 只有流动性变化量大于零才发布;纯领取手续费/奖励不是减仓。减到零但账户尚未关闭仍只是减仓,后续账户关闭单独发布
position.closed。同笔交易内同一仓位先减仓再关仓,只发布一次position.closed,不再发布该仓位的position.decreased;同笔交易中其他未关闭仓位的减仓仍正常推送。识别到关仓日志时,减仓会等待现有关仓交易查询确认对应 PDA,复用查询结果,不为此新增 RPC。 creator始终是原始开仓持有人,不是本次操作签名者;NFT 转手后仍按原持有人订阅。openingEventId用于关联自己的跟单仓位,不能仅按地址/池匹配。- 仅覆盖已有监控池内、能关联到仍保留开仓快照且已知创建者/原始参数的仓位;不会扫描全链或补造上线前缺失的开仓。未知创建者的 Orca 变动先持久化,待身份补齐后发布。
- 事件来自成功程序调用;失败交易和被上层回滚的 CPI 不发布。同笔交易多次变动各有不同 ID,WS 与补漏重放去重。金额保持事件原始整数,不改变开仓快照为“当前余额”,LP 变动也不计入 Swap 成交量。
{
"type": "position.decreased",
"cursor": "123",
"data": {
"version": 1,
"type": "position.decreased",
"eventId": "orca:CHANGE_SIGNATURE:liquidity:0",
"dex": "orca",
"programId": "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc",
"poolAddress": "POOL_ADDRESS",
"creator": "CREATOR_ADDRESS",
"positionAddress": "POSITION_PDA",
"nftMint": "POSITION_NFT_MINT",
"openingEventId": "orca:OPEN_SIGNATURE:open:0",
"openingSignature": "OPEN_SIGNATURE",
"signature": "CHANGE_SIGNATURE",
"eventIndex": 0,
"slot": 454600100,
"occurredAt": 1791504100000,
"publishedAt": 1791504101000,
"commitment": "confirmed",
"liquidityDelta": "12345678901234567890",
"tickLower": -120,
"tickUpper": 240,
"tickSpacing": 60,
"tokenA": {"mint":"MINT_A","symbol":"TOKEN","decimals":6,"amountRaw":"500000","feeAmountRaw":"0","transferFeeRaw":"0"},
"tokenB": {"mint":"MINT_B","symbol":"USDC","decimals":6,"amountRaw":"50000000","feeAmountRaw":"0","transferFeeRaw":"0"}
}
}
加仓消息字段相同,内外层 type 均为 position.increased。
| 字段 | 含义 |
|---|---|
eventId | ${dex}:${signature}:liquidity:${eventIndex},按事件幂等;不是开仓事件 ID |
eventIndex | 当前 DEX 交易日志中加/减流动性事件的序号,从 0 开始;Orca 的首次入金也占序号,但不作为加仓发布。不同于开仓序号与 instruction index |
liquidityDelta | u128 十进制字符串,非零、无符号的流动性变动绝对值;方向由消息类型决定。不是剩余流动性或增减仓比例,不能据此推断“已全部减仓” |
tokenA/B.amountRaw | 链上事件的本次流动性对应原始代币数量,u64 字符串。Raydium 减仓的独立手续费字段不并入该数量;不是滑点上限或保证净到账量 |
tokenA/B.feeAmountRaw | Raydium 减仓事件单独报告的已领取手续费;其他事件为 "0"。不包含奖励;Orca 独立领取手续费/奖励指令不在此事件内 |
tokenA/B.transferFeeRaw | 事件报告的 Token-2022 转账费,旧版 Raydium 日志没有该字段时为 null。Raydium 减仓转账费可能对应本金与手续费合并转出,不能直接当作本金净额扣除 |
tickLower/Upper | 原始链上 token A/B 方向;Orca 使用本次事件的 tick,Raydium 使用关联开仓快照的 tick |
occurredAt | Unix 毫秒;实时接收时间或补漏交易区块时间 |
slot | 变动事件所在 slot(实时日志 context 或完整交易 slot) |
openingEventId / openingSignature | 原开仓事件 ID 与签名;用于精确关联 |
跟单程序应读取当前目标仓位/池状态,自行计算加减仓比例、数量、滑点和转账费。liquidityDelta、原始数量不能当成当前仓位余额;不要重复使用 openedAt/开仓金额表示本次变动。本消息不提供保证成交报价或盈亏。
Orca 复用现有日志及已保存仓位;Raydium 变动日志只有 NFT 标识,未建立关联时会异步查询已识别的变动交易,先过滤监控池,再沿该 PDA 历史精确关联仍保留的开仓。关联和非监控池结果会缓存;重试最多 8 次,新增订阅客户端不增加 RPC。监控范围仍为现有池和十天仓位保留期。
事件字段依据 Raydium 仓位事件定义 与 Orca 事件定义。
关仓消息
同一连接推送 position.opened、position.increased、position.decreased 和 position.closed,使用同一条发布游标;接口路径与 creator / dex / after 参数不变。订阅程序通过 openingEventId 查找自己记录的跟单仓位映射。
{
"type": "position.closed",
"cursor": "122",
"data": {
"version": 1,
"type": "position.closed",
"eventId": "orca:CLOSE_SIGNATURE:close:POSITION_PDA",
"dex": "orca",
"programId": "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc",
"poolAddress": "POOL_ADDRESS",
"creator": "CREATOR_ADDRESS",
"signature": "CLOSE_SIGNATURE",
"slot": 454601000,
"closedAt": 1791504400000,
"publishedAt": 1791504401500,
"commitment": "confirmed",
"positionAddress": "POSITION_PDA",
"nftMint": "POSITION_NFT_MINT",
"openingEventId": "orca:OPEN_SIGNATURE:open:0",
"openingSignature": "OPEN_SIGNATURE"
}
}
eventId为${dex}:${关闭交易签名}:close:${仓位PDA},重试和断线重放按此幂等;同一交易关闭多个仓位分别发布。openingEventId与开仓消息的eventId一致;openingSignature是原开仓签名。旧版仅有 PDA 的快照无法还原开仓 ID 时,这两个字段为null,可使用positionAddress匹配。不要只按创建者/池地址关掉所有跟单仓位。creator保持原始开仓持有人,因此 NFT 后来转手、由其他地址关闭时,原订阅仍按同一地址接收;该字段不是关仓签名者。signature/slot属于关仓交易;closedAt为该交易区块时间的 Unix 毫秒,区块时间不可用时取本地处理时间。publishedAt为发布时刻。- 只对成功的 Raydium
ClosePosition、OrcaClosePosition/ClosePositionWithTokenExtensions/CloseBundledPosition指令发布。部分撤流动性、把流动性降为零但未关闭账户、本地保留期清理都不属于关仓消息。 - 复用现有关仓日志/补漏和持久化重试,不新增客户端专属 RPC。Raydium 通过原有 PDA 创建历史解析精确开仓 ID;Orca 按 PDA 和 slot 防止旧关仓误删后来复用的组合仓位。
- 关仓发布与删除原仓位在同一个数据库事务中完成,发布失败会保留仓位待重试。必须能关联本服务仍保留的仓位快照(开仓快照保留十天)且已知原创建者;不扫描全链历史、不补发升级前已删除的仓位,也不凭空猜测未知创建者。
- 关仓消息不返回撤回金额或盈亏;关闭账户指令本身不提供这些数量,不能把原开仓金额当成实际赎回金额。
续传、保留与示例程序
{"type":"checkpoint","cursor":"125"}
只在前序事件处理成功后持久化 checkpoint。服务按创建者索引读取,每连接每秒最多处理整组地址合计 100 条候选记录(不是每个地址 100 条);checkpoint 可直接跨过其他地址、DEX 或黑名单记录,不能用游标差判断漏单数量。
- 发布记录保留十天(启动/每小时清理)。记录是历史开仓/关仓信号,关闭后保留原开仓消息并追加关仓消息;旧开仓消息不表示此刻仍持仓;消费者应检查事件新鲜度、需要时确认原仓位状态。
- 不传
after从实时开始;after=0会重放历史,消费者必须按eventId幂等,避免重连重复跟单。API 上线前缺少原始参数的仓位不回填。 - 游标与数据库及订阅条件绑定。游标必须绑定同一地址集合、
dex和部署实例;调整地址顺序或重复同一地址不改变订阅集合。增加、删除地址或更换dex时需重连,并使用新的游标文件/订阅状态:不传after从实时开始,或显式after=0重放仍保留的历史并按eventId去重。不能直接取多个旧单地址游标的最大值合并,否则会跳过其他地址尚未处理的事件。不支持连接内动态发送增删地址指令。 400:地址无效、缺少地址、creator参数超过 100 个、DEX/游标格式错误、重复dex/after或未知参数;409:游标超过当前数据库最大值;410:游标已超出保留期,需要调用者选择新起点;426:普通 HTTP GET;503:数据库未就绪、mock 模式或连接已满。- 连接后游标回退/过期或收到应用消息关闭码
1008;存储异常1011;慢客户端缓冲超过 1 MiB 会被终止。每 25 秒协议 ping,未回复 pong 的连接被清理。 - 默认最多 200 个开仓订阅连接,可通过
POSITION_OPENING_WS_MAX_CONNECTIONS修改。使用既有server.mjs/pnpm start,端口与网页相同;反向代理对该路径启用 WebSocket Upgrade,超时大于 60 秒。
仓库提供可运行示例 examples/subscribe-position-openings.mjs,串行处理事件、原子保存游标、断线退避重连,并区分订阅条件。示例只打印信号;将 handleOpening / handleLiquidityChange / handleClose 替换为按 eventId 幂等的持久化处理函数;先保存开仓事件到自有仓位的映射,关仓时按 openingEventId 查询该映射。
CURSOR_FILE=./my-wallet-orca-cursor.json node examples/subscribe-position-openings.mjs \
https://your-dashboard.example.com CREATOR_ADDRESS orca
# 示例程序的命令行支持逗号分隔,内部会转换为重复 creator 参数
CURSOR_FILE=./my-wallets-cursor.json node examples/subscribe-position-openings.mjs \
https://your-dashboard.example.com ADDRESS_A,ADDRESS_B all