Every one of these endpoints returns something that looks completely normal: data present, well-formed, no error. The problem is that it is not the thing you assumed — truncated, differently based, or a placeholder. All eight caught me; one of them made me publish a conclusion I later withdrew.
I ran a census of winners to answer a natural question: with $1,000 of capital, following the top players' approach, how far can you get?
The method looked fine: query each winner's profit from the leaderboard, query their holdings from another endpoint, divide the two for "capital efficiency", and scale to $1,000. The answer came out at $1,000 → $3,723.
It is wrong, and badly. One sentence explains it:
The sample contained a wallet with $4.55M of lifetime profit and $5,700 of current holdings — a long-retired player who had withdrawn. Dividing made it a god that turned a few thousand into millions, and it dragged the entire estimate with it.
I withdrew the figure. The endpoint did not lie; I failed to ask which time scale it spoke in. The eight below are all versions of that same failure.
| # | Endpoint / field | You assume | Actually |
|---|---|---|---|
| ① | activity feed | this wallet's full history | at most 5,500 rows; older data unreachable |
| ② | the same, on a busy wallet | enough to compute a daily rate | may cover only hours |
| ③ | leaderboard /profit | what this person earned | excludes all subsidy income |
| ④ | /profit ÷ holdings | capital efficiency | lifetime ÷ instant — meaningless |
| ⑤ | one large recipient address | a whale | contaminates every statistic; must be excluded |
| ⑥ | clobRewards: [] | this board has no rewards | does not follow; use the authoritative endpoint |
| ⑦ | public RPC eth_getLogs | any provider will serve chain queries | most refuse outright; the workable one is limited |
| ⑧ | /rebates/current | documented as public, returns rebates | returns None |
Paging looks like it continues indefinitely. It stops at 5,500 — no error, no notice, simply nothing more. So what you hold is always a recent slice, never a lifetime, and how long that slice covers depends entirely on how active the wallet is. Two wallets I took apart differed by more than an order of magnitude:
| Wallet type | 5,500 rows covered | If you assume lifetime |
|---|---|---|
| mid-frequency arbitrage | 18.5 days | you call two weeks a career |
| high-frequency market making | about 30 hours | you call one day a career |
On receiving the data, first check the gap between the first and last timestamps and use that as the denominator.
And the subtler part: the truncated portion may be its most profitable period. A wallet that was unusually active during a high-return phase generated the most rows then — exactly the rows the 5,500 cap discards. You are seeing its present, not its peak.
This one matters most, because the leaderboard is where everyone starts copying. That field is trading P&L, and the venue's rewards and rebates are transfers, not trades — they do not enter it.
It cuts both ways: you underrate players who live on subsidies (their leaderboard number can be poor or negative while they earn steadily), and if you count your own subsidies as strategy income you overrate your strategy. Detail in subsidies are not P&L.
The §1 failure. The rule: before dividing, confirm both numbers share a time scale.
Some "volume traded" fields do not state their measurement window. A number with an unknown window must never be divided by one with a known window. No denominator, no division — that is a hard rule.
Enumerating reward recipients you will meet an address receiving $600k to $920k per day inside the batches, with no corresponding trading record on the leaderboard.
It is plainly not an ordinary participant. Any per-capita, median or distribution statistic must exclude it first, or one address skews the whole shape.
BasisI state only that it contaminates the measurement. No identity attribution is made — I do not know whose it is and have not looked.
The obvious way to find boards with rewards is the clobRewards field on the events endpoint.
An empty array does not license the conclusion "no pool".
The authoritative source is the matching layer's sampling-markets endpoint.
Pulling it in full returned 8,631 boards carrying a genuinely live rate —
an entirely different set from filtering on clobRewards.
Board metadata carries fields like minimum size for rewards and maximum spread. Their presence does not mean money is paid — they are part of a config template.
There is exactly one dependable way to establish "does this pay": query the authoritative endpoint, or look on-chain for actual payments.
| Provider | Measured |
|---|---|
| publicnode / polygon-rpc / ankr | all refused (403 / 401 / key required) |
| 1rpc | rate limited |
| drpc | works, but requires an address filter and ≤ 40 blocks per call |
Chain-wide queries without an address filter are refused at every block range. So "pull all of a contract's events" becomes thousands of small calls on free tiers.
If you hand-assemble JSON-RPC requests rather than using a library, send a User-Agent header — otherwise you are silently refused. Library users never hit this, because libraries set one.
Silently is the operative word: it does not say "missing header", it just returns nothing. This is the hardest class of failure to diagnose, because it looks exactly like "there is no such record on-chain".
/rebates/current is documented as requiring no authentication.
In practice it returns None, and I did not find the parameter shape.
Do not spend time on it: the same information is available from the activity feed — split income by record type, sum transfers and trades separately. Equally reliable, and verifiable.
There is a separate endpoint that does reconcile precisely (per-day, per-board detail), but it requires authentication and only serves your own account — useful for your own books, useless for censusing others.
| Trap | Note |
|---|---|
| Leaderboard capped at 50 | paging parameters are entirely ignored; ranks 100–200 are not exposed |
| Holder index lags | by minutes. A single snapshot is unreliable — union across snapshots |
| Rewards paid ~400 per batch | not a trap, a shape you must know to parse: hundreds of recipients in one transaction |
| Config "base fee" fields | the two base-fee values are raw config, not the actual rate. Do not compute with them |
Abstract all eight and they are one mistake: taking what the endpoint gave you as what you wanted.
The check is cheap and needs doing once:
① Count the rows. If the count is exactly a round number — 5,500, 50, 1,000 — you almost certainly hit a cap rather than the end.
② Read the first and last timestamps. What span does it really cover, and is that what you assumed?
③ Validate against a case whose answer you know. Take one of your own records, or something independently verifiable on-chain, and check the endpoint agrees.
④ Distinguish the kinds of empty. "There genuinely is none" and "this endpoint will not tell you" are completely different facts — and they return identical-looking data.
The fourth is the most expensive lesson here. I have paid for "absent read as non-existent" elsewhere too: once treating "no record in the activity feed" as "no loss" (off by eight times), and once treating "the position list is empty" as "the position is closed" when it was ingestion lag (the redeemable trap).
These endpoints do not lie. They answer a slightly different question from the one you asked. So before dividing anything, ask three things: how long does it cover, what types does it include, and which kind of empty is this?