Skip to main content
Glama
README.md
# rhc-mcp

**An MCP server for Robinhood Chain — the on-chain agentic surface Robinhood's own agent can't reach.**

Robinhood's [Agentic Trading](https://robinhood.com/us/en/agentic-trading/) exposes an MCP server so
any Claude / ChatGPT / MCP agent can trade — but it's **off-chain, US-only, equities-only**. It can't
see or touch the tokenized [Robinhood Chain](https://robinhood.com/us/en/chain/) **stock tokens**.

`rhc-mcp` is the complement: point any MCP-speaking agent at Robinhood *Chain* and let it **read
on-chain stock-token positions, see the real total-return value ERC-20 reads hide, quote, and swap** —
over the same Model Context Protocol. Read tools need no key; swapping is off by default and heavily
guarded.

Swaps go through Robinhood Chain's Uniswap v4 **UniversalRouter** directly — **nothing to deploy**. One
catch worth knowing: the chain forks the v4 swap struct with an extra `minHopPriceX36` field, so stock
Uniswap SDK calldata reverts. This encoder adds it (verified byte-identical to real on-chain swaps). The
sibling repo [RHCSwap](https://github.com/jumpboxtech/rhcswap) is an alternate route (a tiny contract
that hits the v4 PoolManager directly) if you'd rather not touch Permit2.

---

## How it fits

```mermaid
flowchart LR
    A["AI agent<br/>(Claude · ChatGPT · any MCP client)"]
    subgraph RH["Robinhood's MCP"]
      O["off-chain brokerage<br/>US · equities only"]
    end
    subgraph THIS["rhc-mcp (this repo)"]
      T["get_positions · quote_swap · execute_swap"]
    end
    subgraph CHAIN["Robinhood Chain (Arbitrum Orbit, id 4663)"]
      R["stock tokens<br/>balanceOf + uiMultiplier"]
      Q["Uniswap v4 Quoter"]
      S["UniversalRouter + Permit2<br/>(v4 swap)"]
    end
    A -->|off-chain trades| O
    A -->|on-chain, this repo| T
    T --> R
    T --> Q
    T --> S
```

## Tools

| Tool | Access | What it does |
| --- | --- | --- |
| `rhc_info` | read | Chain / RPC / contract addresses; reports whether swapping is enabled or read-only. |
| `get_positions` | read | A wallet's stock-token positions: raw balance, `uiMultiplier`, the real total-return balance (`raw × multiplier ÷ 1e18`), and the accrued appreciation % that raw ERC-20 reads hide. |
| `quote_swap` | read | Exact-input single-hop quote via the Uniswap v4 Quoter. Proves the pool has liquidity and sizes `minAmountOut`. |
| `execute_swap` | **write** | Executes a swap via the Uniswap v4 UniversalRouter (+ Permit2 for ERC-20 input) — no deployed contract. Dry-run by default; guarded (see Safety). |

### The `uiMultiplier` insight

Stock tokens are [ERC-8056](https://robinhood.com/us/en/chain/) total-return tokens: raw `balanceOf`
is **static**, and `uiMultiplier()` (1e18-scaled) grows as in-kind dividends reinvest and on splits.
A naive ERC-20 balance read **understates** what a holder actually owns. `get_positions` surfaces both
and the gap between them — the on-chain yield an agent would otherwise miss.

## Install

```bash
git clone https://github.com/jumpboxtech/rhc-mcp && cd rhc-mcp
npm install && npm run build
cp .env.example .env   # edit for swapping; read tools need nothing
```

### Wire it to an agent

Claude Code:

```bash
claude mcp add rhc-mcp -- node /absolute/path/to/rhc-mcp/dist/index.js
```

Claude Desktop / any MCP client (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "rhc-mcp": {
      "command": "node",
      "args": ["/absolute/path/to/rhc-mcp/dist/index.js"],
      "env": { "RHC_RPC_URL": "https://rpc.mainnet.chain.robinhood.com" }
    }
  }
}
```

Then ask your agent: *"What are my Robinhood Chain positions for 0x…?"* or *"Quote 1 NVDA to USDG."*

## Safety model

`execute_swap` moves real funds, so it is deliberately locked down:

- **Read-only by default** — with no `RHC_PRIVATE_KEY`, the server can only read; swaps are refused.
- **Key never touches the tool surface** — the signer comes only from `RHC_PRIVATE_KEY`, never a tool
  argument, and is never returned or logged.
- **Dry-run by default** — `execute_swap` returns the plan (expected out, enforced min out, and which
  Permit2 approvals a real run would send) without sending unless the caller passes `dryRun: false`.
- **Hard size cap** — `RHC_MAX_SWAP_AMOUNT` (whole input tokens, default `1`) bounds any single swap.
- **Slippage always enforced** — a real `minAmountOut` is required, taken from the argument or derived
  from a fresh quote minus `slippageBps`.
- **Scoped Permit2 grant** — the Permit2 → router allowance is scoped to `amountIn` with a short (~1h)
  expiry, not an unbounded standing approval.

## Configuration

| Env | Required for | Meaning |
| --- | --- | --- |
| `RHC_RPC_URL` | – | RPC endpoint (defaults to the public mainnet RPC). |
| `RHC_PRIVATE_KEY` | swapping | Signer key. Unset ⇒ read-only. Keep it in a real secret store. |
| `RHC_MAX_SWAP_AMOUNT` | swapping | Per-swap input cap in whole tokens (default `1`). |

## Robinhood Chain addresses (chain id `4663`)

| Contract | Address |
| --- | --- |
| UniversalRouter (the real one; 2 decoys exist) | `0x8876789976DECBFcbBBe364623C63652dB8c0904` |
| Permit2 | `0x000000000022D473030F116dDEE9F6B43aC78BA3` |
| Uniswap v4 PoolManager | `0x8366a39CC670B4001A1121B8F6A443A643e40951` |
| StateView | `0xf3334192d15450cdd385c8b70e03f9a6bd9e673b` |
| Quoter | `0x8dc178efb8111bb0973dd9d722ebeff267c98f94` |

RPC: `https://rpc.mainnet.chain.robinhood.com` · Explorer: `robinhoodchain.blockscout.com`

## Scope & limits

Exact-input, single-hop, hookless pools — deliberately minimal. Exact-output and multi-hop aren't
covered (and haven't been checked for the same `minHopPriceX36` quirk). The known-token registry is a
small starter set (USDG, NVDA); pass any 0x address directly. Unaudited — read it before you route
funds through it.

## License

MIT © jumpbox — [jumpbox.tech](https://jumpbox.tech) · [@jumpbox_tech](https://x.com/jumpbox_tech)

TDQS

A4.3/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: rhc_info provides chain and contract info, get_positions reads wallet positions, quote_swap quotes a swap, and execute_swap executes a swap. No overlap.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern (rhc_info, get_positions, quote_swap, execute_swap) with snake_case, making them predictable.

Tool Count5/5

4 tools is well-scoped for this server's purpose: info retrieval, position reading, swap quoting, and swap execution. Not too few or too many.

Completeness4/5

The tool set covers the core swap workflow on Robinhood Chain but lacks a dedicated token list or price query tool, though rhc_info and get_positions partially address this.

Maintenance

ActivityStale
ResponsivenessNo issues