Skip to content

Commit 32bbf32

Browse files
committed
docs(flashblocks): add WebSocket streaming guide to app-integration
The app-integration page covered RPC usage (viem, wagmi, ethers) but had no documentation for direct WebSocket streaming — a common pattern for real-time use cases like address monitoring and mempool tracking. Added a new WebSocket Streaming section covering: - Endpoint URLs for mainnet and sepolia - Flashblock message format (Brotli-compressed JSON, index 0 vs diff) - Basic connection example with Brotli decompression - Auto-reconnect with exponential backoff for production use - Address watcher pattern for real-time tx monitoring All examples are TypeScript and use only the built-in zlib module plus the ws package — no additional dependencies required.
1 parent 83ce90f commit 32bbf32

File tree

1 file changed

+187
-0
lines changed

1 file changed

+187
-0
lines changed

docs/base-chain/flashblocks/app-integration.mdx

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,193 @@ console.log(`Time difference: ${confirmTime - submissionTime}ms`);
198198

199199
You should see the confirmation time significantly lower than the standard RPC endpoint.
200200

201+
202+
## WebSocket Streaming
203+
204+
For real-time monitoring — such as watching a specific address or tracking mempool activity — connect directly to the Flashblocks WebSocket stream.
205+
206+
<Warning>
207+
Avoid hard dependencies on WebSocket in transaction-sending flows. Use WebSocket for monitoring only; use the RPC API for sending transactions and querying state.
208+
</Warning>
209+
210+
### Endpoints
211+
212+
| Network | WebSocket URL |
213+
|---------|--------------|
214+
| Mainnet | `wss://mainnet.flashblocks.base.org/ws` |
215+
| Sepolia | `wss://sepolia.flashblocks.base.org/ws` |
216+
217+
Public endpoints are rate-limited. For production use, connect through a Flashblocks-enabled node provider.
218+
219+
### Message Format
220+
221+
Flashblock messages are **Brotli-compressed JSON**. Each message represents one sub-block:
222+
223+
- **`index: 0`** — contains full block header (`block_number`, `gas_limit`, `base_fee_per_gas`, `timestamp`)
224+
- **`index: 1–9`** — contains only the diff: new transactions and receipts added in this sub-block
225+
226+
```json
227+
{
228+
"payload_id": "0x03997352d799c31a",
229+
"index": 0,
230+
"base": {
231+
"block_number": "0x158a0e9",
232+
"gas_limit": "0x3938700",
233+
"base_fee_per_gas": "0xfa",
234+
"timestamp": "0x67bf8332"
235+
},
236+
"diff": {
237+
"transactions": [...],
238+
"receipts": [...]
239+
}
240+
}
241+
```
242+
243+
### Basic Connection
244+
245+
Install the `ws` package:
246+
247+
```bash
248+
npm install ws
249+
```
250+
251+
```typescript
252+
import WebSocket from "ws";
253+
import { brotliDecompressSync } from "zlib";
254+
255+
const WS_URL = "wss://mainnet.flashblocks.base.org/ws";
256+
257+
function parseFlashblock(data: Buffer) {
258+
try {
259+
// Messages are Brotli-compressed
260+
return JSON.parse(brotliDecompressSync(data).toString());
261+
} catch {
262+
// Some messages may arrive as plain JSON
263+
return JSON.parse(data.toString());
264+
}
265+
}
266+
267+
const ws = new WebSocket(WS_URL);
268+
269+
ws.on("open", () => console.log("Connected to Flashblocks"));
270+
271+
ws.on("message", (data: Buffer) => {
272+
const flashblock = parseFlashblock(data);
273+
const txCount = flashblock.diff?.transactions?.length ?? 0;
274+
console.log(`Flashblock #${flashblock.index}: ${txCount} transaction(s)`);
275+
});
276+
277+
ws.on("error", (err) => console.error("WebSocket error:", err.message));
278+
ws.on("close", () => console.log("Disconnected"));
279+
```
280+
281+
### Auto-Reconnect
282+
283+
Production apps should reconnect automatically when the connection drops:
284+
285+
```typescript
286+
import WebSocket from "ws";
287+
import { brotliDecompressSync } from "zlib";
288+
289+
const WS_URL = "wss://mainnet.flashblocks.base.org/ws";
290+
291+
function createFlashblocksClient(onFlashblock: (data: unknown) => void) {
292+
let ws: WebSocket | null = null;
293+
let reconnectDelay = 1000;
294+
295+
function connect() {
296+
ws = new WebSocket(WS_URL);
297+
298+
ws.on("open", () => {
299+
console.log("Connected to Flashblocks");
300+
reconnectDelay = 1000; // reset on successful connection
301+
});
302+
303+
ws.on("message", (data: Buffer) => {
304+
try {
305+
const decompressed = brotliDecompressSync(data);
306+
onFlashblock(JSON.parse(decompressed.toString()));
307+
} catch {
308+
try { onFlashblock(JSON.parse(data.toString())); } catch { /* ignore */ }
309+
}
310+
});
311+
312+
ws.on("close", () => {
313+
console.log(`Reconnecting in ${reconnectDelay}ms...`);
314+
setTimeout(() => {
315+
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
316+
connect();
317+
}, reconnectDelay);
318+
});
319+
320+
ws.on("error", (err) => {
321+
console.error("WebSocket error:", err.message);
322+
ws?.close();
323+
});
324+
}
325+
326+
connect();
327+
return { disconnect: () => ws?.close() };
328+
}
329+
330+
// Usage
331+
const client = createFlashblocksClient((flashblock) => {
332+
console.log("New flashblock:", flashblock);
333+
});
334+
335+
process.on("SIGINT", () => { client.disconnect(); process.exit(0); });
336+
```
337+
338+
### Watch a Specific Address
339+
340+
Monitor an address for incoming or outgoing transactions in real-time:
341+
342+
```typescript
343+
import WebSocket from "ws";
344+
import { brotliDecompressSync } from "zlib";
345+
346+
interface FlashblockTx {
347+
hash: string;
348+
from: string;
349+
to: string | null;
350+
value: string;
351+
}
352+
353+
interface Flashblock {
354+
index: number;
355+
diff?: { transactions?: FlashblockTx[] };
356+
}
357+
358+
function watchAddress(address: string) {
359+
const target = address.toLowerCase();
360+
const ws = new WebSocket("wss://mainnet.flashblocks.base.org/ws");
361+
362+
ws.on("open", () => console.log(`Watching ${address}`));
363+
364+
ws.on("message", (data: Buffer) => {
365+
let flashblock: Flashblock;
366+
try {
367+
flashblock = JSON.parse(brotliDecompressSync(data).toString());
368+
} catch {
369+
try { flashblock = JSON.parse(data.toString()); } catch { return; }
370+
}
371+
372+
for (const tx of flashblock.diff?.transactions ?? []) {
373+
if (tx.from?.toLowerCase() === target || tx.to?.toLowerCase() === target) {
374+
const valueEth = Number(BigInt(tx.value)) / 1e18;
375+
console.log(`\nTransaction detected in flashblock #${flashblock.index}`);
376+
console.log(` Hash: ${tx.hash}`);
377+
console.log(` From: ${tx.from}`);
378+
console.log(` To: ${tx.to ?? "contract creation"}`);
379+
console.log(` Value: ${valueEth.toFixed(6)} ETH`);
380+
}
381+
}
382+
});
383+
}
384+
385+
watchAddress("0xYourAddressHere");
386+
```
387+
201388
## Support
202389

203390
For feedback, support or questions about Flashblocks, please don't hesitate to contact us in the `#developer-chat` channel in the [Base Discord](https://base.org/discord).

0 commit comments

Comments
 (0)