Engine
The service that reads Robinhood Chain. What it watches, what it counts, and how to read it over HTTP.
The engine is the part of Hound that never renders anything. It sits on its own box, holds the only connection to the chain, and keeps the whole market in memory so a reader can have all of it in one request. Everything on screen came from here first.
It answers at https://api.houndterminal.trade.
How it finds a trade
The first version of this engine kept a list. It watched the Uniswap V3 Swap signature, decoded what came back, and looked like it was working.
Then someone measured it. 39.8% of the chain's swap transactions. The rest were happening at venues the list had never heard of, and on a chain where a new pad deploys most weeks, that gap was going to widen on its own.
So the list is gone. The engine now reads the Transfer events every token
emits anyway, nets the value moving through each address inside each
transaction, and calls any address that came out two-sided a trade. Uniswap V2,
V3, V4, Curve, a pad that launched this morning. They all look the same to it,
because money moving is money moving.
One hole had to be patched by hand. Native ETH emits no log at all, and most Uniswap V4 pools quote in native ETH rather than WETH, so netting alone cannot see them. V4's own Swap event covers that leg and nothing else.
Together the two catch 97.4% of the chain's position-moving swap transactions, scored against signature ground truth. That number is not the one to watch though. The one to watch is that the engine keeps booking trades at contracts no known signature explains. A list can never do that, no matter how long it gets.
The measurements behind all of that, including two approaches that were designed and then thrown out by their own numbers, are in Coverage.
Not everything is counted
Roughly 2.6% is missed and published rather than rounded away. A venue that nets three or more tokens in one transaction cannot say what any single leg cost, and two non quote tokens changing hands is real activity with no price attached. A guessed split is a wrong cost basis rather than a missing one.
What counts as profit
A wallet's realized PnL only ever moves against inventory the engine watched being bought. Sell a token it never saw you acquire and that is activity, not profit. It stays out of the number.
That sounds pedantic until you compare the board to any other one. Most of them rank on unrealized PnL, which means a wallet up 400% on a position it still holds sits at the top. It has won nothing. It is the next exit liquidity.
Closed positions with their cost basis are harder to compute and they are the only number that survives the exit. The full argument, including what this method costs us, is in Realized vs unrealized.
Traders are identified without paying for it. The sequencer feed hands over raw
transaction bytes, and the sender is recovered locally from those bytes rather
than asked for over RPC. Accounts behind an ERC-4337 bundle come from the
UserOperationEvent, because the bundler is not the trader.
Attribution covers the measurements and the
one percentage nobody should quote without checking it that day.
Reading it over HTTP
Every endpoint except /live needs a token. Send it as a bearer header.
curl https://api.houndterminal.trade/live
curl -H "Authorization: Bearer $HOUND_TOKEN" \
https://api.houndterminal.trade/health| Endpoint | Auth | What comes back |
|---|---|---|
GET /live | none | ok, and nothing else. Enough for a load balancer, useless to anyone else |
GET /health | token | Feed state, confirmation lag, detector coverage, attribution counters |
GET /snapshot | token | The whole market. Tokens, wallets, NFT collections, one object |
GET /activity | token | The recent trade ring. ?limit= accepts up to 50,000 |
GET /history | token | One wallet's last trades. Needs ?address= |
GET /stream | token | Server sent events. One full snapshot per second |
A request with no token, or with a wrong one of the right length, gets a 401.
The comparison is constant time, so a wrong token does not leak its own length
by answering faster.
The two numbers to read first
/health publishes a lot. Two fields tell you whether the architecture is
still doing what it claims.
detector.unknownVenueTrades should never sit at zero. Zero means the engine
has quietly gone back to finding only what it was told to look for, which is
the exact failure the netting design exists to prevent.
attribution.fromFeed should dominate attribution.fromRpc in steady state.
If it does not, the free path is broken and the metered one is absorbing the
work. Read both on the day you need the answer. A restart drags the ratio down
for a while, because a backfill's transactions are older than the feed's raw
memory and can only be resolved the expensive way.
Recent trades, and one wallet's past
Two endpoints answer questions the snapshot cannot, and they answer different ones despite looking similar.
/activity is the recent trade ring, held in memory. It runs about thirty
to forty minutes deep at this chain's rate, which is a consequence of its size
rather than a configured window. Ask it for what it has and you get the newest
trades across the whole chain, capped at fifty thousand.
Use it to answer whether something is moving right now. It is also what seeds a fresh reader, so a client opening a connection does not have to sit through an empty screen waiting for the first trade to arrive.
/history is one wallet's last trades, read from disk. Pass an address and
you get its most recent moves back, however long ago they happened.
That distinction is the useful part. A wallet that has not traded for a day is absent from the ring entirely and still has a full history here. One is about the present, the other is about a particular wallet, and a reader that needs both should ask both.
curl -H "Authorization: Bearer $HOUND_TOKEN" \
"https://api.houndterminal.trade/history?address=0x..."What else rides in the snapshot
Two fields worth naming because they are easy to miss and easy to misuse.
nativeTrend is the chain's own ETH price series, accumulated by the
engine at roughly one point a minute and persisted across restarts. Use this
when you want ETH.
The reason it exists is worth the sentence. Since the engine keyed its book by
token rather than by pool, quote currencies never enter that book at all, so
there is no entry in tokens[] that is ETH. Anything reaching for the busiest
token as a stand in will draw a memecoin under an ETH label, which is exactly
what happened before this field existed.
wallets.windowMinutes dates the board. The ledger survives restarts on disk
while the price book is rebuilt from the chain, so the two can legitimately
hold different amounts of history. Read this rather than assuming twenty four
hours.
Streaming
/stream holds the connection open and pushes a full snapshot every second.
There is no subscribe step and no message types to learn. Each event carries
the same shape /snapshot returns, so a reader can render the first paint from
one and then keep the same code path for every tick after it.
const stream = new EventSource("/api/ingest/stream");
stream.onmessage = (event) => {
const snapshot = JSON.parse(event.data);
render(snapshot.tokens, snapshot.collections, snapshot.wallets);
};EventSource cannot send headers
The browser API has no way to attach an Authorization header, so the token
has to live on a server you control and the page reads your proxy instead of
the engine. That is what /api/ingest/stream is in this app.
Getting a token
There is no self serve signup yet. If you want the engine's data on your own screens, say what you are building and we will talk.