Skip to content

Commit 7bc0941

Browse files
outsmartchadclaude
andcommitted
feat(streaming): add WebSocket event stream as free alternative to gRPC
Add WsEventStream class that uses Solana's native RPC WebSocket (logsSubscribe) with getTransaction fetches to produce the same typed events as the gRPC engine. No Yellowstone endpoint needed — works with any standard Solana RPC. - WsEventStream mirrors EventStream API (start/stop/on/getStats) - Auto-selects WebSocket mode in CLI when no gRPC endpoint is configured - Add --ws flag to force WebSocket mode - Fix empty mint edge case in parseSwapFromBalanceDiff - Simplify README tagline and add About section for traders/developers - Document both gRPC and WebSocket modes in streaming section Verified on mainnet: 180 events in 20s, 0 bad mints across 7+ DEXes. Co-authored-by: Claude Code <noreply@anthropic.com>
1 parent 2911bfc commit 7bc0941

5 files changed

Lines changed: 615 additions & 16 deletions

File tree

README.md

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# outsmart
22

3-
**Solana trading CLI — buy, sell, LP, stream real-time DEX events, and operate perp exchanges across 18 DEXes with 12 TX landing providers.**
3+
**Buy, sell, LP, trade perps, create AMM pools, launch coins, and stream real-time DEX events from your terminal.**
44

55
**[Documentation](https://outsmartchad.github.io/outsmart-cli/)** | **[npm](https://www.npmjs.com/package/outsmart)** | **[Discord](https://discord.gg/dc3Kh3Y3yJ)**
66

@@ -10,6 +10,14 @@ outsmart init
1010
outsmart buy --dex raydium-cpmm --pool <POOL> --amount 0.1
1111
```
1212

13+
## About
14+
15+
outsmart is a Solana trading toolkit that covers every major DEX protocol from a single CLI and Node.js library. It supports 18 on-chain DEX adapters (Raydium, Meteora, Orca, PumpFun, PumpSwap, and more), 2 swap aggregators (Jupiter Ultra, DFlow), 12 TX landing providers for competitive submission, and a real-time event streaming engine powered by Yellowstone gRPC or standard WebSocket.
16+
17+
**For traders** — execute swaps, manage LP positions, launch tokens, and create perpetual futures markets without touching a browser. Stream live swap events and new pool creations to spot opportunities in real-time.
18+
19+
**For developers** — import `outsmart` as a library in your own bots, use the typed event stream for custom analytics, or integrate with AI agents via [outsmart-agent](https://github.com/outsmartchad/outsmart-agent).
20+
1321
---
1422

1523
## Quick Start
@@ -630,14 +638,17 @@ import {
630638

631639
## Event Streaming Engine
632640

633-
Real-time gRPC transaction streaming from Solana DEX programs via Yellowstone gRPC (Geyser). Parses live transactions from 18+ DEX programs into typed events.
641+
Real-time DEX event streaming with two backends — **gRPC** (Yellowstone/Geyser, lowest latency) and **WebSocket** (free, uses standard RPC). Both produce the same typed events from 18+ DEX programs.
634642

635643
### CLI
636644

637645
```bash
638-
# Stream all DEX swaps in real-time
646+
# Stream all DEX swaps (auto-selects WebSocket if no gRPC endpoint configured)
639647
outsmart stream --preset all-dex-swaps
640648

649+
# Force WebSocket mode (free, no gRPC endpoint needed)
650+
outsmart stream --preset all-dex-swaps --ws
651+
641652
# Stream specific DEXes
642653
outsmart stream --preset pumpswap
643654
outsmart stream --preset raydium
@@ -652,32 +663,40 @@ outsmart stream --preset pumpfun-bonding
652663

653664
Available presets: `all-dex-swaps`, `new-pools`, `pumpfun-bonding`, `pumpswap`, `raydium`, `meteora`, `other-dexes`, `wallet-trades`
654665

655-
Requires `GRPC_URL` and `GRPC_XTOKEN` env vars (Yellowstone gRPC endpoint).
666+
**gRPC mode** (default if configured): requires `GRPC_URL` and `GRPC_XTOKEN` env vars. Lowest latency (~200ms).
667+
668+
**WebSocket mode** (`--ws` flag or auto-selected): uses your standard `MAINNET_ENDPOINT` RPC. Free, higher latency (~1-3s).
656669

657670
### Programmatic API
658671

659672
```typescript
673+
// gRPC mode (fastest, requires Yellowstone endpoint)
660674
import { EventStream } from "outsmart";
661675

662676
const stream = new EventStream({
663677
grpcUrl: process.env.GRPC_URL,
664678
grpcXToken: process.env.GRPC_XTOKEN,
665679
});
666680

667-
// Listen for swap events across all DEXes
681+
// WebSocket mode (free, uses standard RPC)
682+
import { WsEventStream } from "outsmart";
683+
684+
const stream = new WsEventStream({
685+
rpcUrl: process.env.MAINNET_ENDPOINT, // any Solana RPC
686+
});
687+
688+
// Both emit the same events with the same API:
668689
stream.on("Swap", (event) => {
669690
console.log(`${event.dex} ${event.direction} ${event.mint}`);
670691
console.log(` in: ${event.amountIn}, out: ${event.amountOut}`);
671692
console.log(` pool: ${event.pool}, trader: ${event.trader}`);
672693
});
673694

674-
// Listen for new pool creations
675695
stream.on("NewPool", (event) => {
676696
console.log(`New pool on ${event.dex}: ${event.pool}`);
677697
console.log(` ${event.tokenA} / ${event.tokenB}`);
678698
});
679699

680-
// Listen for PumpFun bonding curve completions
681700
stream.on("BondingComplete", (event) => {
682701
console.log(`Bonding complete: ${event.mint} → ${event.migrationPool}`);
683702
});
@@ -692,7 +711,7 @@ stream.on("*", (event) => { /* any event */ });
692711

693712
await stream.start("all-dex-swaps");
694713

695-
// Custom subscriptions
714+
// Custom subscriptions (gRPC only)
696715
import { subscribePoolActivity } from "outsmart";
697716
await stream.startCustom(subscribePoolActivity(["POOL_ADDRESS"]));
698717

src/cli.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2193,7 +2193,7 @@ for (const signal of ["SIGINT", "SIGTERM"] as const) {
21932193

21942194
program
21952195
.command("stream")
2196-
.description("Stream real-time DEX events via Yellowstone gRPC")
2196+
.description("Stream real-time DEX events via Yellowstone gRPC or WebSocket")
21972197
.option(
21982198
"-p, --preset <preset>",
21992199
"Subscription preset: all-dex-swaps | new-pools | pumpfun-bonding | pumpswap | raydium | meteora | other-dexes | wallet-trades",
@@ -2203,14 +2203,26 @@ program
22032203
.option("--threshold <sol>", "Large swap threshold in SOL", "10")
22042204
.option("--events <types...>", "Filter event types: Swap NewPool BondingComplete LargeSwap")
22052205
.option("--json", "Output events as JSON (one per line)")
2206+
.option("--ws", "Use WebSocket mode (free, no gRPC endpoint needed)")
22062207
.option("-v, --verbose", "Verbose logging (debug level)")
22072208
.action(async (opts) => {
2208-
const { EventStream } = await import("./streaming");
2209-
2210-
const stream = new EventStream({
2211-
largeSwapThresholdSol: parseFloat(opts.threshold),
2212-
logLevel: opts.verbose ? "debug" : "info",
2213-
});
2209+
const { EventStream, WsEventStream } = await import("./streaming");
2210+
2211+
const logLevel = opts.verbose ? "debug" : "info";
2212+
const threshold = parseFloat(opts.threshold);
2213+
2214+
// Use WebSocket mode if --ws flag is set or no gRPC endpoint is configured
2215+
const useWs = opts.ws || (!process.env.GRPC_URL && !process.env.GRPC_XTOKEN);
2216+
2217+
const stream = useWs
2218+
? new WsEventStream({
2219+
largeSwapThresholdSol: threshold,
2220+
logLevel: logLevel as any,
2221+
})
2222+
: new EventStream({
2223+
largeSwapThresholdSol: threshold,
2224+
logLevel: logLevel as any,
2225+
});
22142226

22152227
const eventFilter = opts.events
22162228
? new Set(opts.events as string[])

src/streaming/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
* Subscribes to on-chain transactions, parses them, and emits typed events.
66
*/
77

8-
// Main class
8+
// Main classes
99
export { EventStream } from "./event-stream";
10+
export { WsEventStream } from "./ws-event-stream";
11+
export type { WsStreamConfig } from "./ws-event-stream";
1012

1113
// Types
1214
export type {

src/streaming/tx-parser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,7 @@ function parseSwapFromBalanceDiff(
865865
}
866866

867867
if (amountBase === 0 && amountQuote === 0) return null;
868+
if (!mint) return null; // skip if we couldn't identify the base token
868869

869870
const priceAfter = reserveBase > 0 ? reserveQuote / reserveBase : 0;
870871

0 commit comments

Comments
 (0)