Swap event decoding, no ABI per DEX.
This page is the working reference: how to subscribe, what each field means, what the error codes are, the three things that will trip you up, and what it costs. Endpoint reference is at GET /sse/transactions and POST /sse/transactions.
Before anything else: pick your method carefully
Two methods, and the difference between them is three orders of magnitude in cost.
Both figures were measured on 2026-08-21 in the same hour.
token on a major asset is 208 times the traffic of a single pool, because a major asset is in thousands of pools.
Since each delivered event costs one credit, that rate is your bill:
Subscribe
Both forms needAuthorization carrying the key as the entire header value. No Bearer, no other scheme word.
request_id is echoed on every event so you can tell subscriptions apart on a multiplexed connection. It is optional and defaults to the array index, which is fine until you reorder the array. Set it explicitly.
Add ?limit=N to close the connection after N events. It is the cheapest way to sample a feed before you commit to it.
What an event looks like
A real capture from the Orca SOL/USDC pool:pool or token.
Direction is not a field. Derive it from the sign of
amount_0: negative means token_0 left the pool, so the trader bought it. In the event above amount_0 is positive, so SOL went in and USDC came out: a sell.
There are no symbols and no decimals. REST rows carry token_0_symbol; stream events do not. If you need “1.13 WETH” rather than a raw integer, fetch decimals once from the pool endpoint and cache them. The _usd fields are already computed, so most consumers never need the raw amounts at all.
Three pitfalls
These are the ones that cost real time. Each was measured, not inferred.1. token_0 is not the same token in REST and in the stream
Same pool, same minute, on Ethereum:
0x4200… sorts below 0x8335…) and on Solana, and do not on Ethereum (0xa0b8… sorts below 0xc02a…).
2. Raw amounts arrive as JSON numbers, and JavaScript will round them
amount_0 and amount_1 are JSON numbers, not strings. For an 18-decimal token they exceed Number.MAX_SAFE_INTEGER routinely:
reserve, delta and block as strings precisely to avoid this. The transactions feed does not.
This also hides on Solana. Nine-decimal SOL and six-decimal USDC keep raw amounts small: across 93 captured Solana events the largest absolute amount was 90,504,206,102, four orders of magnitude below the limit. Test on an 18-decimal EVM token or you will not see it.
If you need exact raw amounts, parse with a big-integer-aware reader rather than JSON.parse, or work from the _usd fields, which are ordinary floats and safe.
block_number is already a string for the same reason. Use Number() on it only for comparisons, never for arithmetic you care about.
3. volume_usd is about half the notional
Measured across both chains, volume_usd sits at 0.4999 to 0.5057 of each leg’s USD value:
volume_usd and compare it against a REST 24h volume figure without checking the convention, or you will be out by a factor of two.
When you want the size of a trade, amount_0_usd and amount_1_usd are unambiguous.
Error codes
Every one of these was produced against the live endpoint. They arrive as an HTTP status with a JSON body, before the stream opens.403 and 401 mean different things and the difference is useful. 403 is “you sent no key”. 401 is “you sent a key and it was rejected”. Branch on them: the first needs registration, the second needs a look at the header.In-stream events
Once the stream is open, HTTP status no longer applies. Watch the event name:
Filter on the event name. A handler that parses every
data: line will try to read {"time":1787305761} as a swap.
Good practices
Trust the heartbeat, not the socket. A TCP connection that has silently died looks identical to a quiet pool. Pings arrive every 15 seconds; if you have seen nothing for 45, reconnect. Do not use “no swaps” as a liveness signal, because on a slow pool that is the normal state. Reconnect with backoff, and never in a tight loop. A retry storm turns one rate limit into a persistent one. Start at a second, double up to a minute, and add jitter so a fleet does not resynchronise. Multiplex deliberately. 25 subscriptions per connection, 10 concurrent connections per IP, so 250 subscriptions is the ceiling on a free key. Group by how you will process the data, not by chain:request_id is what routes an event, and it is per subscription.
Deduplicate on tx_hash plus pool_id. A reconnect can redeliver the block you were in the middle of. One swap that touches two subscribed pools also arrives twice, once per request_id, which is correct but is not what a naive counter expects.
Size the cost before you leave it running. Use ?limit=100 and time it. A hundred events tells you the rate, the rate tells you the monthly bill, and both take a minute to find out. Plan your credit usage works through the arithmetic.
Persist what you need at write time. The feed is a live tail, not a queryable history. For backfill and for anything older than your connection, use the REST transactions endpoint.
A working consumer
Handles the event names, the heartbeat, reconnection, and the two pitfalls that bite in JavaScript.Where to go next
Endpoint reference
Parameters, schemas and every event type.
Plan your credit usage
What a continuous stream costs, with worked models.
Production streaming
Reconnection, missed blocks and error classification. Written for reserves, applies here.
Pool reserves
The other key-only feed: liquidity movement per block.