Swap on Robinhood Chain without the SDK reverts.
Robinhood Chain ships a modified Uniswap UniversalRouter — its v4 swap struct carries an extra
minHopPriceX36 field — so calldata built by the standard Uniswap SDK reverts. (There are also
look-alike router addresses floating around the chain; only one is real.)
RHCSwap sidesteps the router entirely and talks to the Uniswap v4 PoolManager directly:
unlock → swap → settle → take. That path is plain, unmodified v4 — the quirk only lives in the router
layer. ~140 lines, zero dependencies, no owner, no state.
Proven on live Robinhood Chain: 1 NVDA → $201.98 USDG, ~167k gas, nothing stranded.
A v4 swap has to happen inside the PoolManager's unlock callback, and every currency balance-delta
must net to zero before unlock returns — otherwise it reverts. RHCSwap does exactly that:
sequenceDiagram
autonumber
participant U as Your contract
participant R as RHCSwap
participant PM as v4 PoolManager
U->>R: swap(key, zeroForOne, amountIn, minOut, to)
R->>R: pull amountIn of tokenIn (transferFrom)
R->>PM: unlock(data)
PM->>R: unlockCallback(data)
R->>PM: swap(key, {exactInput, priceLimit})
PM-->>R: BalanceDelta (−in, +out)
R->>PM: sync(tokenIn) · transfer(tokenIn) · settle()
R->>PM: take(tokenOut, to, amountOut)
Note over R,PM: deltas net to 0 → unlock returns
R-->>U: amountOut
forge install jumpboxtech/rhcswapDeploy RHCSwap (or reuse a deployment), approve it for your input token, and call swap:
import {RHCSwap, PoolKey, Currency} from "rhcswap/RHCSwap.sol";
// NVDA / USDG 0.3% pool. currency0 < currency1 by address:
// USDG 0x5fc5… < NVDA 0xd060… → USDG is currency0.
PoolKey memory key = PoolKey({
currency0: Currency.wrap(USDG),
currency1: Currency.wrap(NVDA),
fee: 3000,
tickSpacing: 60,
hooks: address(0)
});
IERC20(NVDA).approve(address(rhcSwap), amountIn);
// selling NVDA (currency1) for USDG (currency0) → zeroForOne = false
uint256 usdgOut = rhcSwap.swap(key, /*zeroForOne*/ false, amountIn, minUsdgOut, msg.sender);v4 doesn't have per-pair pool addresses — a pool is identified by its PoolKey. Get fee /
tickSpacing / hooks for a pair from the pool's Initialize event on the PoolManager
(topics carry currency0 / currency1; the data carries the rest). Then confirm liquidity /
quote the output with the Quoter before you route real size — many RHC pools are thin or
high-fee placeholders.
| Contract | Address |
|---|---|
| PoolManager | 0x8366a39CC670B4001A1121B8F6A443A643e40951 |
| StateView | 0xf3334192d15450cdd385c8b70e03f9a6bd9e673b |
| Quoter | 0x8dc178efb8111bb0973dd9d722ebeff267c98f94 |
RPC: https://rpc.mainnet.chain.robinhood.com · Explorer: robinhoodchain.blockscout.com
amountSpecifiedis negative for exact-input.sqrtPriceLimitX96→zeroForOne ? MIN_SQRT + 1 : MAX_SQRT - 1. This gives no price protection — always pass a realminAmountOut; it's your only slippage guard.- Settle pattern is
sync(currency) → transfer(currency, poolManager) → settle(). There is nosettle(currency)in v4 — only the no-argsettle(), which settles the last-synced currency. BalanceDeltapacksamount0in the high 128 bits andamount1in the low 128 bits of anint256. Output leg isamount1whenzeroForOne, elseamount0.- This assumes the pool's
hooks == address(0). A hooked pool can mint/take extra deltas and break the settle/take netting — check the PoolKey first.
Exact-input, single-hop, hookless pools — deliberately minimal. No multi-hop, no exact-output, no native ETH. It's a starting point; fork it. Unaudited — read it before you route funds through it.
forge test --fork-url rhc -vvForks live Robinhood Chain and swaps a real NVDA holder's token → USDG through the real pool.
MIT © jumpbox — jumpbox.tech · @jumpbox_tech