The Robinhood Chain x402 rail has been live since July, but both SDKs were key-mode only — an agent that wanted to pay per call had to hand-roll the EIP-3009 signing from the 402 challenge. robinhood-chain-x402 0.7.0 (npm) and robinhood-chain 0.7.0 (PyPI) close that: pass a privateKey / private_key of an EVM wallet holding USDG on Robinhood Chain instead of an API key, and the client does the 402 → sign transferWithAuthorization (EIP-712 domain {Global Dollar, 1, 4663}) → retry flow itself, one payment per call, from $0.04. The wallet needs USDG but no ETH — our facilitator relays gas. lastPayment / last_payment carries the on-chain settlement. Both were verified with real USDG settlements before publish.
- Keyless rail is now 10 endpoints, not 6 — token risk (with live sell simulation), holder census, wallet PnL and tracked-deployer alerts joined the KOL feed / hot tokens / leaderboard / token snapshot / buyer quality / KOL consensus. The new four delegate to the keyed v1 handlers in-process, so the keyless bodies cannot drift from the keyed ones.
- Honest boundary: any method that is not on the rail throws
KeylessNotAvailableError on a keyless client and names the ten that are. Nothing silently downgrades.
- TypeScript:
createKeylessClient(process.env.RHC_PAYER_KEY), optional peer dependency viem; key mode still has zero runtime dependencies. New deployerAlerts() binding (52 methods).
- Python:
RobinhoodClient(private_key=…), extra pip install "robinhood-chain[x402]" (eth-account); sync and async.
- Also on the API today:
GET /rhc/equities (beacon-verified tokenized stocks/ETFs — 20 fake "GameStop • Robinhood Token" contracts existed on ship day and none are listed), /rhc/tokens?sort=newest&since= (+ /rhc/tokens/new), /rhc/lp-events, ?include= on the always-paired Solana calls, since= + stream pointers on the polled KOL feeds, sequence numbers + client-side replay on the WebSocket stream, and price/pool/reserve enrichment on the dex:trades firehose.
2026-08-16 — A trade row said it moved N tokens for M SOL, then quoted a price that was not M divided by N
An alert fired claiming our per-trade price was 71% wrong. It was not 71% wrong — that number came from a sampling flaw in our own monitor. But chasing it down surfaced something worse than the alert: for months, price_sol on a trade row was not the price of that trade.
What it actually was. Our market-cap tracker samples a canonical pool price per token, once per pool update. The ingest path stamped that value onto whatever trades landed near it. So a trade row told you it moved N tokens for M SOL, and then reported a price that was not M ÷ N. The gap ran to a 7.9% median and roughly 74% at the 90th percentile.
The mechanism is granularity, not a bug in the tracker. Every trade in the same slot gets one canonical price. On 2026-08-16, slot 439589939 held a dump: seven trades executing from 9.80e-6 down to 6.12e-6, all seven stamped 6.071174e-6. Their individual errors ran from −38% to −0.9%. On top of that sits a lag with a direction: a stale market price is low during a pump and high during a dump, so the error does not cancel when you average it — candles and VWAP built from that tape inherit the bias. Nothing regressed to cause this. The tape simply got faster, and a price sampled per-token stopped being a reasonable stand-in for a price paid per-trade.
What changed. On /tokens/{mint}/trades and /wallet/{address}/trades, price_sol and price_usd are now this trade's executed price, derived from the row's own amounts. They reconcile exactly with sol_amount / token_amount and with what the PnL endpoints sum. Because sol_amount is the wallet's net SOL movement, that price is the trader's all-in effective rate — it includes the swap fee and any account rent paid in the same transaction, and it is not the pool mid. We would rather tell you that plainly than quietly hand you a number you cannot reproduce from the chain.
The old number did not disappear. It moved to market_price_sol and market_price_usd on the same rows. A single per-token price series is genuinely useful, because executed prices bounce around with trade size and direction — it just should not have been wearing the label "the price of this trade". Use price_sol for cost basis, fills and PnL; use market_price_sol when you want a series independent of who traded and how much.
This is computed when you read, not when we write, so it is correct for every row we have ever captured — no backfill, no gap between old and new history. /wallet/{address}/trades gained all four fields; it previously returned amounts and no price at all, leaving every caller to divide by hand.
And we fixed the monitor that cried 71%. The check sampled the newest 1,000 trade rows, which at today's throughput is about twelve seconds of tape — usually one hot token. The mint that produced the 71% reading had 1,574 trades in three minutes and a 71% median all by itself, while the median across all mints in the same window was 12.4%. It now samples six slices across three hours and takes the median of per-mint medians, so no single token can page us. Two neighbouring monitor bugs went with it: a check that read "our node is one block ahead of the reference" as "DNS is dead on that box" (21 false alarms a day and climbing), and a self-healer that restarted services which were merely mid-restart.
Every SDK ships both prices. madeonsol 2.23.0 (TypeScript), madeonsol-x402 1.25.0 (npm and PyPI), mcp-server-madeonsol 1.22.0, @madeonsol/plugin-madeonsol 1.21.0, solana-agent-kit-plugin-madeonsol 1.21.0, and madeonsol 0.24.0 on crates.io. TokenTrade and WalletTrade carry all four fields, and the MCP tool descriptions spell out which price to use — an agent reading the schema should not have to guess which one belongs in a PnL calculation. The Rust fields are Option<f64>, so a client pinned to an older server still deserializes. Nothing breaks if you don't upgrade: price_sol still exists with the same type, it just finally means what its name says. The Robinhood Chain packages are untouched at 0.6.0 — that chain's trades have their own price columns.
Unrelated, but shipping the same day: the crypto checkout asks a harder question. A card payment comes with a counterparty — the issuer checks the billing address. A crypto payment has none, so an address is whatever someone types. One order arrived with a street, a city and a country belonging to three different jurisdictions, and it passed validation because the country it named has no postal codes, which waived the only checkable field. Now countries without a postal system must give a state or region instead, postal codes are checked against their country's format, and when the country you select disagrees with where you're connecting from, the checkout shows you the mismatch and asks you to confirm your country of residence. It isn't a block — VPNs are normal, and we'd rather ask than guess.
2026-08-15 — We audited our own SDKs and found fifteen endpoints you were paying for but could not call
This week's audit turned on our own packages rather than the API. Fourteen of them across npm, PyPI and crates.io. Registry hygiene was clean — every published version matched its manifest, the OpenAPI spec covers 134 of 135 routes, and every built package emits correct ESM. The problem was the gap between what the API sells and what the SDKs expose, plus a set of dependency ranges that described a version of the x402 ecosystem that stopped existing twenty majors ago.
The entire Robinhood Chain wallet surface had no SDK binding. Eight routes — the 90-day wallet profile, FIFO cost-basis PnL, open positions marked to market, the per-wallet trade tape, and all four watchlist routes — were live, documented in the spec, and reachable by curl, but a grep across all seven RHC packages returned nothing. If you wanted a wallet's PnL on Robinhood Chain you had to hand-roll the HTTP call while holding an SDK that covered forty other endpoints. All eight are now bound in every RHC package: the TypeScript, Python and Rust SDKs, the x402 client, the MCP server, and both agent plugins. The profile, PnL and positions calls share one 90-day snapshot cache server-side, so asking for all three on an address costs roughly one computation rather than three — cache_hit tells you which call paid for it.
Deployer Hunter was better covered on the newer chain than on the flagship one. Seven Solana routes — the reputation leaderboard, chain-wide stats, a single deployer's profile and full launch history, alert-volume stats, best recent tokens, and fresh graduations — were missing from all five Solana packages, while the Robinhood Chain packages covered the exact same seven on their side. The newer, smaller product line was more complete than the one we lead with. Fixed; all seven now ship in the Solana TypeScript, Python, MCP and both plugin packages. The bare madeonsol TypeScript and Rust SDKs already had them.
Our dependency ranges were fiction in both directions. Every x402-capable package declared its peers as unbounded >=0.0.1 or >=0.1.0. The x402 packages are at 2.22, and our code is written against 2.8–2.11. So a user pinned to @x402/[email protected] satisfied our range and got a package that could not possibly work, while a user installing fresh resolved a major we had never built against. The same held for @solana/kit (>=2.0.0 declared, 5.5.1 tested, 7.1.0 latest) and for the Python x402 extra (>=0.1 declared, against a 2.x module layout that does not exist on 0.x). Every range is now a caret bound on the version we actually test. Those peers are also lazily imported and only needed in keyless mode, so they are now marked optional — installing with an API key no longer drags in the entire Solana stack.
A Python bug that would have shipped with the new methods. The synchronous REST client passed query parameters straight to httpx, which encodes None as an empty value — ?tier=. Our routes use strict schemas that reject an empty enum with a 400 rather than reading it as "not supplied". Every method we were adding has optional parameters, so this would have bitten on the first call. Unset parameters are now dropped at the transport layer, matching what the Robinhood Chain Python client already did.
And we fixed the guard that should have caught all of this. Each SDK repo runs a route-parity check that fails CI when a package references an API path that does not exist. It was red on two of the four standalone repos — and every hit was a false positive, because the extractor was reading JSDoc and rustdoc prose as code and flagging backtick code spans like `/risk` as routes. A gate that cries wolf on every push is worse than no gate, because it trains you to ignore the day it is right. Both extractors now skip comments. The two Robinhood Chain checkers were failing differently: their route manifests were hand-maintained and forty entries stale, so the reverse direction — "is every documented route actually bound?" — had been silently passing on an incomplete list. Refreshed from the live spec, that check immediately flagged four unbound routes, all of which turned out to be server-side aliases of endpoints already covered; they are now allow-listed by name with the canonical target written next to each.
Versions. Robinhood Chain: TypeScript, Python, Rust, x402, MCP and both plugins all to 0.6.0. Solana: madeonsol-x402 1.24.0 (npm) and 1.24.0 (PyPI), mcp-server-madeonsol 1.21.0, the ElizaOS plugin 1.20.0, the Solana Agent Kit plugin 1.20.0. No API changes — the endpoints were already there. This release is entirely about being able to reach them.