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 a paid-plan key as the entire header value, with no scheme word in front of it, and they live on streaming-pro.dexpaprika.com only; see upgrading to Pro.
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 “this plan or host cannot open the feed”: no key, a free key, or a paid key on the wrong host. 401 is “you sent a key and it was rejected”. Branch on them: the first needs a plan or a base URL change, 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.
Resume after a disconnect
Every swap event carries an SSEid: line. Hand it back and the stream replays what you missed before it continues live, on the same connection. There are two ways in:
Last-Event-IDheader, set to the lastid:you received.EventSourcesends it by itself on reconnect; a hand-written client has to record the id and send it.sincequery parameter, in Unix seconds, for “everything since this time”. It must be within the last 15 minutes. Older values answer400with{"error":"invalid_param","message":"invalid query parameters: since (must be within the last 15 minutes)","param":"since"}.
tx_hash), then the ones after it. Deduplicate, which you need anyway.
The replay is capped, and the cap drops the oldest swaps. A GET replays up to 100 swaps, a POST up to 500 across all its subscriptions. When more were missed, you get the most recent ones: a since 14 minutes back on WETH returned exactly 100 swaps covering only the last two minutes. Fill the hole from REST transactions if you need every swap.
limit counts replayed swaps. With limit=2 and a since, the connection closes after two replayed swaps, before anything live arrives.
An id the server does not recognise is not an error. The connection opens with 200, so a stale or mangled id fails quietly. Keep since as the fallback when you know how long you were away.
On a method=token subscription the replay reads only the token’s largest pools, so swaps in its small pools during the outage are not replayed. The live tail covers every pool.
One since or Last-Event-ID covers every subscription on a POST. With subscriptions on several chains, swaps from chains other than the one in your Last-Event-ID may be sent again, which deduplication also covers.
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, and the plan caps concurrent connections: 30 on Dev, 100 on Pro, so 750 or 2,500 subscriptions is the ceiling. 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 replays at most the last 15 minutes. For anything older, and for a gap the replay cap leaves, use the REST transactions endpoint.
A working consumer
Handles the event names, the heartbeat, reconnection with resume, 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
Liquidity movement per block. Opens with a free key.