Overview
Lunar is an interface onto liquidity that already exists on Robinhood Chain. It does two things: it turns a liquidity position into a claim you can move, and it routes trades in crypto and tokenized equities through one execution path with a price floor enforced at every hop.
Nothing on the site is a stored number. Prices, pool state, balances, routes and yields are read from the chain when you ask for them, through the same API the terminal uses. If a figure looks wrong, the explorer link next to it will tell you why.
The protocol is non custodial. Lunar never holds keys, never takes deposits and cannot move funds. The server builds calldata; your wallet decides whether it is ever signed.
Network and contracts
Everything settles on Robinhood Chain, an Arbitrum Orbit L2 that pays gas in ETH.
| Item | Value |
|---|---|
| Chain | Robinhood Chain |
| Chain id | 4663 (0x1237) |
| Gas token | ETH, 18 decimals |
| Block time | roughly 101 ms |
| RPC | https://rpc.mainnet.chain.robinhood.com |
| Explorer | robinhoodchain.blockscout.com |
Contracts the interface reads and writes
| Contract | Address | Used for |
|---|---|---|
| Uniswap v3 factory | 0x1f7d7550B1b028f7571E69A784071F0205FD2EfA | Pool discovery per fee tier |
| Universal Router | 0x8876789976dEcBfCbBbe364623C63652db8C0904 | Swap execution and hop price floors |
| Permit2 | 0x000000000022D473030F116dDEE9F6B43aC78BA3 | Scoped, expiring spending rights |
| Multicall3 | 0xcA11bde05977b3631167028862bE2a173976CA11 | Batching every chain read |
| WETH | 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73 | Wrapping inside the router call |
| Chainlink ETH/USD | 0x78F3556b67E17Df817D51Ef5a990cDaF09E8d3A9 | Reference price |
| Chainlink USDG/USD | 0x61B7e5650328764B076A108EFF5fa7282a1B9aD2 | Dollar anchor for every quote |
The live list, including the block the server last saw, is always at /api/network.
Listed assets
The registry ships with 32 assets: native ETH and wrapped ETH, the USDG quote asset, stablecoins and majors, the protocol's own VAI token, plus 17 Robinhood tokenized equities and ETFs including AAPL, NVDA, MSFT, TSLA, GOOGL, AMZN, META, PLTR, COIN, SPY and QQQ.
The list is a convenience, not a boundary. Paste any ERC-20 address into the token picker and the
terminal reads symbol, name, decimals and totalSupply
straight from the chain, then discovers its pools the same way it does for a listed asset.
Prices for tokenized equities carry two numbers: the pool price and, where a Chainlink feed exists, the reference price, with the premium between them shown next to both.
How the APR is measured
No projection and no incentive assumptions. Every Uniswap v3 pool exposes two monotonically increasing
counters, feeGrowthGlobal0X128 and feeGrowthGlobal1X128, holding fees earned
per unit of liquidity in Q128 fixed point. Two samples separated by a known interval give the revenue
for that interval:
fees = (feeGrowthNow - feeGrowthThen) × liquidity ÷ 2¹²⁸
Both token amounts are priced with the same pool derived map used everywhere else, summed, divided by the pool's total value and annualised by the length of the window. Because liquidity itself moves, the server accumulates each interval against the liquidity that actually stood behind it rather than assuming one figure held throughout.
The node prunes historical state after a few thousand blocks, so a full day of fee growth cannot be
read in a single call. The server samples every listed pool on a timer and keeps up to 24 hours in
data/fee-series.json. The measured span is printed next to every APR, because a five
minute sample and a twelve hour sample deserve different amounts of trust.
Quoting and routing
A quote is a simulation, not an estimate. The engine loads slot0, liquidity
and tickSpacing for a pool, then runs the pool's own swap loop off chain: compute a swap
step toward the next initialised tick, cross the boundary, apply liquidityNet, repeat.
Tick bitmaps and tick data are fetched lazily as the walk needs them, so a small trade costs one round
trip and a large one costs a few.
This matters for size. A spot price estimate is accurate for dust and wrong for anything that moves the pool. Walking ticks returns the amount the contract will actually deliver, including the liquidity changes on the way.
Route selection
- The direct pair at every fee tier: 0.01%, 0.05%, 0.30% and 1.00%.
- Two hop routes through USDG.
- Two hop routes through wrapped ETH.
Every candidate is simulated in full and the one with the highest output wins. The chosen path, its fee tiers, its pool addresses and the number of tick steps per hop are all shown on the quote.
Pricing
USDG anchors the dollar scale through its Chainlink feed. Every other asset is priced from its deepest pool, relaxing outwards from USDG so that assets quoted only against ETH still resolve. Where an asset also carries a reference feed, both numbers are displayed along with the premium between them, which is the honest way to show a tokenized equity.
Reads go through Multicall3, so a full market refresh across dozens of pools is one eth_call
rather than a hundred round trips.
Approvals
Spending runs through Permit2 rather than an open ended ERC-20 allowance to a router. There are two scoped steps:
- The token approves Permit2 once.
- Permit2 grants the Universal Router a specific amount with a 30 day expiry.
The second can be revoked without touching the first, and it lapses on its own. The terminal checks both before offering a swap and asks only for the step that is missing.
Execution
A swap is one call on the Universal Router:
execute(bytes commands, bytes[] inputs, uint256 deadline)
The commands depend on what you are paying with and receiving:
| Trade | Commands | Notes |
|---|---|---|
| ERC-20 to ERC-20 | V3_SWAP_EXACT_IN | Paid from your wallet through Permit2 |
| ETH to ERC-20 | WRAP_ETH + V3_SWAP_EXACT_IN | Wrapping costs no extra transaction |
| ERC-20 to ETH | V3_SWAP_EXACT_IN + UNWRAP_WETH | Unwraps to you in the same call |
Before the action unlocks, the assembled transaction is estimated against the live node with your address as sender. A revert is decoded and surfaced with its reason rather than swallowed, and the gas figure that comes back is the one attached to the transaction you sign.
After you sign, the terminal records the hash locally and polls for the receipt, moving the order from pending to filled or reverted on the Orders page.
The MEV guard
The Universal Router deployed on Robinhood Chain is a fork. Its V3_SWAP_EXACT_IN takes a
sixth parameter the upstream contract does not have:
V3_SWAP_EXACT_IN(
address recipient,
uint256 amountIn,
uint256 amountOutMin,
bytes path,
bool payerIsUser,
uint256[] minHopPriceX36 // minimum price per hop, scaled by 1e36
)
An empty array disables the check. Lunar populates it from the simulated hop amounts and your slippage budget, so the floors travel inside the calldata you sign.
This is the meaningful protection on a multi hop route. Without it, a path can degrade badly at an intermediate pool and still clear a final minimum that was set for the whole route. With it, the trade reverts at the pool that moved. You can see the floors on every quote, and turn the guard off in Settings if you would rather only enforce the final amount.
The guard bounds price, not privacy. Transactions are public in the mempool like any other. It limits what a reorder can cost you; it does not hide the trade.
The terminal
Eight views, all reading the same live data.
| View | What it does |
|---|---|
| Swap | Route crypto and tokenized equities. Live balances, simulated quotes, approvals, gas estimate and the hop floors. |
| YieldShares | Every listed pool with reserves, TVL and the fee yield measured from its own accumulators. |
| Portfolio | Native ETH, every listed ERC-20 and anything else the explorer reports, priced and weighted. |
| Crypto Market | Listed crypto assets priced from live pool state. |
| Stock Market | Tokenized equities and ETFs, pool price beside the Chainlink reference. |
| Orders | Everything submitted from this browser, tracked to its receipt. |
| History | Transactions and token transfers for the connected address. |
| Settings | Slippage, deadline, MEV guard, refresh rate, wallet and network status. |
Watching an address
Portfolio and History accept an address parameter, so any wallet can be inspected read only
without connecting one:
/app/portfolio?address=0x…
/app/history?address=0x…
Deep links
The market tables link straight into a prepared swap:
/app/swap?in=USDG&out=NVDA
What is stored
Settings, order history and your wallet preference live in your browser's local storage. Nothing is uploaded, no account is created and no analytics are collected. The server logs API paths for operations and holds no personal data.
Public API
Every number on the site is available as JSON, with no key and no rate limit beyond the upstream RPC.
| Endpoint | Returns |
|---|---|
| GET /api/network | Chain head, gas price, contract addresses, sampler depth |
| GET /api/overview | Everything the landing page needs in one call |
| GET /api/tokens | The listed asset registry |
| GET /api/markets?kind=crypto|stock | Priced market rows with oracle premium |
| GET /api/pools | Pool state, reserves, TVL and measured APR |
| GET /api/protocol | Protocol totals and the top pools |
| GET /api/prices | Address to USD price map |
GET /api/quote | Route, amounts, calldata, approvals and gas |
GET /api/portfolio/:address | Holdings and valuations |
GET /api/activity/:address | Transactions and token transfers |
GET /api/token/:address | Resolve any ERC-20 from the chain |
GET /api/tx/:hash | Receipt status |
| GET /api/health | Uptime and cache counters |
Quote parameters
| Parameter | Default | Meaning |
|---|---|---|
in, out | required | Symbol or address |
amount | required | Decimal amount of the input token |
slippageBps | 50 | Sets the minimum output and the hop floors |
account | none | Enables approval checks and gas estimation |
deadlineMinutes | 20 | How long the router will accept the swap |
mevGuard | true | false ships an empty hop floor array |
Example
curl "http://localhost:4173/api/quote?in=ETH&out=AAPL&amount=0.5"
The response carries the full route, the amounts, the readable hop floors, the Universal Router calldata and, when an account is given, the approvals still required and the gas estimate.
Self hosting
The server has no runtime dependencies and no build step. Node 18 or newer is enough.
node server.js # serves on http://localhost:4173
node scripts/selftest.mjs # end to end check against a running server
PORT=8080 node server.js # different port
Configuration
| Variable | Default | Meaning |
|---|---|---|
LUNAR_RPC_URLS | Robinhood mainnet RPC | Comma separated, used in failover order |
LUNAR_EXPLORER | Blockscout | Explorer base URL |
LUNAR_CACHE_TTL | 12000 | Upstream cache lifetime in milliseconds |
LUNAR_SAMPLE_MS | 60000 | Fee sampler interval |
LUNAR_RPC_CONCURRENCY | 3 | Parallel RPC requests |
PORT, HOST | 4173, 0.0.0.0 | Listener |
Risks and limits
Stated plainly, because a trading interface earns trust by being specific.
- Smart contract risk remains. Trades settle against Uniswap v3 pools and the Universal Router deployed on Robinhood Chain. Those are third party contracts.
- Quotes are simulations, not guarantees. They match execution for the state read at quote time; the chain can move between the quote and the block your transaction lands in. That is what the minimum output and the hop floors are for.
- The MEV guard is a price floor, not privacy. Transactions are public like any other.
- Tokenized equities carry their own risk. The token tracks a reference price, but pool price and oracle price can diverge. That premium is displayed, never smoothed over.
- Vault contracts are not live. YieldShare vaults are in progress; the analytics describe pools that already exist.
- No audit to point at. There is no completed third party review of vault contracts, because those contracts are not deployed. When they are, this page will say who reviewed them.
- Explorer dependency. Blockscout sits behind a bot check, so history and unlisted balances can fail independently of the RPC. Views that depend on it degrade rather than break.