solana-traderclaw 1.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,265 @@
1
+ # WebSocket + Bitquery Streaming Reference
2
+
3
+ ## Purpose
4
+
5
+ Use this guide when working with OpenClaw WebSocket behavior, managed Bitquery subscriptions, policy limits, and diagnostics endpoints.
6
+
7
+ This document covers:
8
+ - Where WebSocket traffic enters the app
9
+ - How Bitquery subscriptions are multiplexed
10
+ - What external clients are allowed to do
11
+ - How limits and metering are enforced
12
+ - How to validate behavior quickly
13
+
14
+ This is a companion reference to `SKILL.md`, similar to `bitquery-schema.md` and `query-catalog.md`.
15
+
16
+ ---
17
+
18
+ ## High-Level Architecture
19
+
20
+ ### Local WebSocket Entrypoint
21
+
22
+ - Path: `/ws`
23
+ - File: `server/websocket.ts`
24
+ - Manager: `OpenClawWebSocketManager`
25
+
26
+ Clients connect to `/ws` and can subscribe to:
27
+ - Internal OpenClaw channels (`trades`, `positions`, etc.)
28
+ - Managed Bitquery channels via `bitquery_subscribe`
29
+
30
+ ### Upstream Bitquery Bridge
31
+
32
+ - File: `server/services/bitquery-ws-bridge.ts`
33
+ - Class: `BitqueryWsBridge`
34
+ - Upstream URL: `wss://streaming.bitquery.io/graphql`
35
+ - Protocol: `graphql-transport-ws`
36
+
37
+ The bridge keeps one upstream subscription per `templateKey + variables hash` and fans out events to many local clients.
38
+
39
+ ---
40
+
41
+ ## Bootstrap Wiring
42
+
43
+ The WebSocket manager is created in `server/websocket.ts` and wired into the Express server in `server/index.ts`. The `BitqueryWsBridge` is instantiated and injected into the WebSocket manager during configuration.
44
+
45
+ If WebSocket behavior is missing, verify this wiring first.
46
+
47
+ ---
48
+
49
+ ## Message Contract (Client <-> Server)
50
+
51
+ ### Server -> Client Messages
52
+
53
+ | Type | Description | Payload |
54
+ |---|---|---|
55
+ | `connected` | Sent on initial WebSocket connection | `{ type: "connected" }` |
56
+ | `authenticated` | Sent after successful `auth` message | `{ type: "authenticated" }` |
57
+ | `subscribed` | Ack for internal channel subscription | `{ type: "subscribed", channels: string[] }` |
58
+ | `bitquery_subscribed` | Ack for managed Bitquery subscription | `{ type: "bitquery_subscribed", subscriptionId: string, templateKey: string }` |
59
+ | `bitquery_event` | Upstream Bitquery data event relayed to client | `{ type: "bitquery_event", subscriptionId: string, data: object }` |
60
+ | `bitquery_error` | Error from upstream Bitquery subscription | `{ type: "bitquery_error", subscriptionId: string, error: object }` |
61
+ | `bitquery_complete` | Upstream subscription completed | `{ type: "bitquery_complete", subscriptionId: string }` |
62
+ | `bitquery_unsubscribed` | Ack for unsubscribe request | `{ type: "bitquery_unsubscribed", subscriptionId: string }` |
63
+ | `error` | General error message | `{ type: "error", message: string, code?: string }` |
64
+ | `pong` | Response to client `ping` | `{ type: "pong" }` |
65
+
66
+ ### Client -> Server Messages
67
+
68
+ | Type | Description | Payload |
69
+ |---|---|---|
70
+ | `auth` | Authenticate the WebSocket connection | `{ type: "auth", accessToken: string }` |
71
+ | `subscribe` | Subscribe to internal channels | `{ type: "subscribe", channels: string[] }` |
72
+ | `bitquery_subscribe` | Subscribe to a managed Bitquery stream | `{ type: "bitquery_subscribe", templateKey: string, walletId: string, variables: object }` |
73
+ | `bitquery_unsubscribe` | Unsubscribe from a managed Bitquery stream | `{ type: "bitquery_unsubscribe", subscriptionId: string }` |
74
+ | `ping` | Keepalive ping | `{ type: "ping" }` |
75
+
76
+ **Important:** Do not generate your own `subscriptionId` values. Always use the `subscriptionId` returned by the server in the `bitquery_subscribed` ack message.
77
+
78
+ ---
79
+
80
+ ## Auth + Policy Enforcement
81
+
82
+ ### WebSocket Authentication
83
+
84
+ 1. Client sends `{ type: "auth", accessToken: "..." }` after connecting
85
+ 2. Server validates the access token
86
+ 3. Session must be active
87
+ 4. Client must exist in storage
88
+ 5. On success, server sends `{ type: "authenticated" }`
89
+ 6. Until authenticated, only `auth` and `ping` messages are accepted
90
+
91
+ ### Bitquery Subscription Policy Checks
92
+
93
+ For every `bitquery_subscribe` request, the server enforces the following checks in order:
94
+
95
+ 1. **Template allowlist** — `templateKey` must match a known subscription template (see `query-catalog.md` Subscriptions section)
96
+ 2. **Tier + scope access** — Client must have `bitquery:catalog` scope and appropriate tier access
97
+ 3. **Funded wallet gate** — The `walletId` must reference a funded wallet with sufficient SOL balance
98
+ 4. **Usage/metering** — RPS, bandwidth, subscription counts, and advanced filter limits are checked
99
+ 5. **Per-client subscription cap** — Maximum active subscriptions per client (default: 20, configurable via `OPENCLAW_WS_MAX_SUBS_PER_CLIENT`)
100
+
101
+ If any check fails, the server sends an `error` message with a descriptive code and message.
102
+
103
+ ### Funded Wallet Source of Truth
104
+
105
+ Do not trust stale database balance alone. The current check uses:
106
+ - Live balance refresh from on-chain data
107
+ - Fallback to direct RPC wallet balance query when cached data is stale
108
+
109
+ ---
110
+
111
+ ## Subscription Lifecycle
112
+
113
+ ### Subscribe Flow
114
+
115
+ ```
116
+ Client Server Bitquery Upstream
117
+ | | |
118
+ |-- bitquery_subscribe -------->| |
119
+ | { templateKey, walletId, | |
120
+ | variables } | |
121
+ | |-- policy checks ------------->|
122
+ | | |
123
+ | |-- subscribe (if new stream) ->|
124
+ | | (reuse if same templateKey |
125
+ | | + variables hash exists) |
126
+ | | |
127
+ |<- bitquery_subscribed --------| |
128
+ | { subscriptionId, | |
129
+ | templateKey } | |
130
+ | | |
131
+ |<- bitquery_event -------------|<-- data event ----------------|
132
+ | { subscriptionId, data } | (fanned out to all |
133
+ | | subscribers on this stream)|
134
+ | | |
135
+ ```
136
+
137
+ ### Unsubscribe Flow
138
+
139
+ ```
140
+ Client Server Bitquery Upstream
141
+ | | |
142
+ |-- bitquery_unsubscribe ------>| |
143
+ | { subscriptionId } | |
144
+ | |-- remove client from stream ->|
145
+ | | (if last subscriber, |
146
+ | | close upstream sub) |
147
+ | | |
148
+ |<- bitquery_unsubscribed ------| |
149
+ | { subscriptionId } | |
150
+ ```
151
+
152
+ ### Multiplexing
153
+
154
+ The bridge maintains one upstream WebSocket subscription per unique `templateKey + variables hash`. When multiple clients subscribe to the same stream:
155
+
156
+ - Only one upstream connection is made
157
+ - Events are fanned out to all local subscribers
158
+ - When a client unsubscribes, they are removed from the fan-out list
159
+ - When the last subscriber disconnects, the upstream subscription is closed
160
+
161
+ ### Client Disconnect Cleanup
162
+
163
+ When a WebSocket client disconnects:
164
+ - All of their active Bitquery subscriptions are automatically cleaned up
165
+ - If they were the last subscriber on any stream, those upstream subscriptions are closed
166
+ - Internal channel subscriptions are also removed
167
+
168
+ ---
169
+
170
+ ## Available Subscription Templates
171
+
172
+ These are the managed subscription template keys. See `query-catalog.md` for full details.
173
+
174
+ | Template Key | Description | Variables |
175
+ |---|---|---|
176
+ | `realtimeTokenPricesSolana` | Real-time token prices on Solana | `token: String!` |
177
+ | `ohlc1s` | 1-second OHLC stream | `token: String!` |
178
+ | `dexPoolLiquidityChanges` | DEXPool liquidity changes stream | `token: String!` |
179
+ | `pumpFunTokenCreation` | Pump.fun token creation stream | (none) |
180
+ | `pumpFunTrades` | Pump.fun trades stream | `token: String` |
181
+ | `pumpSwapTrades` | PumpSwap trades stream | `token: String` |
182
+ | `raydiumNewPools` | Raydium v4/Launchpad/CLMM new pools stream | (none) |
183
+
184
+ ---
185
+
186
+ ## Raw Query Endpoint Guard
187
+
188
+ `POST /api/bitquery/query` behavior:
189
+ - Regular raw GraphQL queries are still allowed (subject to existing raw scope/tier checks)
190
+ - If operation type is `subscription` AND the query matches a managed template operation, the request is rejected with:
191
+ - Code: `BITQUERY_SUBSCRIPTION_MANAGED_ONLY`
192
+ - Message: `"Use /ws with bitquery_subscribe for managed subscriptions"`
193
+
194
+ This prevents clients from bypassing the managed subscription lifecycle (and its policy enforcement) by sending raw subscription queries through the REST endpoint.
195
+
196
+ ---
197
+
198
+ ## Diagnostics Endpoint
199
+
200
+ `GET /api/bitquery/subscriptions/active`
201
+
202
+ Returns:
203
+ - Connected WebSocket client count
204
+ - Clients with active Bitquery subscriptions
205
+ - Bridge diagnostics:
206
+ - `upstreamConnected` — whether the upstream Bitquery WebSocket is connected
207
+ - `activeStreams` — number of active multiplexed streams
208
+ - Per-stream entries: `templateKey`, `streamKey`, `subscriberCount`, `lastEventAt`
209
+
210
+ Access is policy-protected with `bitquery:catalog` scope.
211
+
212
+ ---
213
+
214
+ ## Agent Tool Integration
215
+
216
+ Three plugin tools are available for the agent to manage subscriptions programmatically (the agent operates via stateless HTTP, so the orchestrator manages WebSocket connections on its behalf):
217
+
218
+ | Tool | Description | Parameters |
219
+ |---|---|---|
220
+ | `solana_bitquery_subscribe` | Subscribe to a managed Bitquery stream | `templateKey: string`, `variables: object` |
221
+ | `solana_bitquery_unsubscribe` | Unsubscribe from a stream | `subscriptionId: string` |
222
+ | `solana_bitquery_subscriptions` | List active Bitquery subscriptions | (none) |
223
+
224
+ The orchestrator creates and manages the WebSocket subscription on behalf of the plugin. Events from the subscription feed into the orchestrator's broadcast channels and can be consumed by the agent through subsequent tool calls or polling.
225
+
226
+ ---
227
+
228
+ ## Trading Use Cases
229
+
230
+ ### New Launch Detection
231
+ - Use `pumpFunTokenCreation` subscription for real-time alerts on new Pump.fun token launches
232
+ - Replaces polling `pumpFunCreation.trackNewTokens` for lower latency
233
+ - Combine with `raydiumNewPools` for cross-launchpad coverage
234
+
235
+ ### Active Position Monitoring
236
+ - Use `pumpFunTrades` or `pumpSwapTrades` with `{ token: "MINT_ADDRESS" }` to monitor trades on tokens you hold
237
+ - Enables real-time flow detection (large sells, whale accumulation) without polling
238
+ - Useful for Step 7 (Monitor) — detect momentum collapse or flow reversal immediately
239
+
240
+ ### Real-Time Price Tracking
241
+ - Use `ohlc1s` with `{ token: "MINT_ADDRESS" }` for 1-second OHLC candles
242
+ - Use `realtimeTokenPricesSolana` for simpler price-only stream
243
+ - Enables micro-timing entries (Step 4.2) — watch for pullbacks within uptrends in real-time
244
+
245
+ ### Liquidity Monitoring
246
+ - Use `dexPoolLiquidityChanges` to detect LP drains or additions in real-time
247
+ - Critical for anti-rug detection on FRESH and EMERGING tokens
248
+
249
+ ---
250
+
251
+ ## Known Gotchas
252
+
253
+ 1. **Wallet not funded errors** — If most templates fail with "wallet not funded", verify the live on-chain balance, not only the cached database value.
254
+
255
+ 2. **Wrong process listening** — If WebSocket connections time out for all templates, check whether the API process you're connecting to is actually the one listening on the expected port.
256
+
257
+ 3. **Single template failures** — If one template fails while others pass, it is usually a schema mismatch inside that template's GraphQL query, not a WebSocket transport problem.
258
+
259
+ 4. **Environment variable typo** — `BITQUERY_API_KEYBOT` typo exists in some env naming. The bridge supports both the typo and corrected env names (`BITQUERY_API_KEY`) for compatibility.
260
+
261
+ 5. **Subscription ID ownership** — Never fabricate `subscriptionId` values. Always use the ID returned by the server in the `bitquery_subscribed` ack. Using invalid IDs for unsubscribe will result in an error.
262
+
263
+ 6. **Unauthenticated messages** — Sending `bitquery_subscribe` before completing `auth` will result in an error. Always authenticate first.
264
+
265
+ 7. **Subscription cap** — Default per-client cap is 20 active subscriptions. Exceeding this returns an error. Unsubscribe from unused streams before creating new ones.