Skip to content

feat(chains): add Robinhood Chain and Unichain networks; enable Uniswap v4 routing#665

Open
fengtality wants to merge 8 commits into
developmentfrom
feat/robinhood-chain
Open

feat(chains): add Robinhood Chain and Unichain networks; enable Uniswap v4 routing#665
fengtality wants to merge 8 commits into
developmentfrom
feat/robinhood-chain

Conversation

@fengtality

@fengtality fengtality commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds Robinhood Chain (docs) as an Ethereum network — robinhoodchain (chainID 4663) and robinhoodchain-testnet (chainID 46630) — and wires up Uniswap (router, AMM, CLMM) on mainnet using the official deployment addresses.

Also adds Unichain (chainID 130, Uniswap Labs' OP-stack L2), enables Uniswap v4 in /router quotes and swaps on all chains where the SDK supports it, and makes the Permit2 allowance expiration configurable (default: never expires).

New networks

  • Chain templates robinhoodchain.yml / robinhoodchain-testnet.yml with EIP-1559 gas config (Arbitrum Orbit L2, ETH as native gas token — both RPCs verified to return baseFeePerGas with 0 priority fee)
  • Chain template unichain.yml (EIP-1559, official public RPC mainnet.unichain.org)
  • Registered namespaces in root.yml, the fallback network list, the wrapped-native (WETH) map, and the EIP-1559 gates in ethereum.ts / estimate-gas.ts
  • Robinhood Chain mainnet token list: 27 tokens from the contracts page (WETH, USDG, Robinhood stock/ETF tokens) — every symbol/name/decimals verified on-chain (USDG is 6 decimals, the rest 18). Note: docs label the oil fund token "CUSO" but the contract symbol is USO, so the on-chain symbol is used. Testnet token list is empty — no token contracts are documented or deployed there.
  • Unichain token list: 11 major tokens (WETH, USDC, USDT/USDT0, DAI, WBTC, cbBTC, UNI, LINK, AAVE, COMP) from the Uniswap default token list
  • Note: the network id uses no hyphen (robinhoodchain), but geckoId keeps CoinGecko's hyphenated robinhood-chain identifier

Uniswap wiring

  • robinhoodchain and unichain added to the Uniswap networks allow-list; official V2, V3 / Universal Router, and (Unichain) V4 deployment addresses added to uniswap.contracts.ts
  • universal-router.ts now resolves the Universal Router address from the per-network contracts map instead of UNIVERSAL_ROUTER_ADDRESS() from @uniswap/universal-router-sdk, which throws for chains not in its hardcoded map (e.g. 4663) and has no override API. SwapRouter.swapCallParameters itself is chain-agnostic (calldata contains only token paths), so only the address lookup needed to change.
  • /router/quote-swap falls back to the Universal Router quote when the AlphaRouter SDK doesn't support the network (@uniswap/smart-order-router also hardcodes supported chains). The cached quote shape (trade + methodParameters) is identical, so execute-quote / execute-swap work unchanged.

Uniswap v4 routing

  • alpha-router.ts now includes Protocol.V4 in AlphaRouter routing on chains in the SDK's V4_SUPPORTED list (mainnet, arbitrum, optimism, polygon, base, bsc, avalanche, celo, unichain, …); other chains keep V2+V3 only, since the V4 quoter address lookup would throw there
  • No API or approval changes: v4 swaps execute through the existing Universal Router + Permit2 flow, and execute-quote sends the SDK's methodParameters calldata as before. The router simply considers v4 pools and picks whichever route prices best.
  • On Unichain, the SDK's default on-chain quote provider batches 80 quoter calls × 1.2M gas (~96M gas per multicall eth_call), which the public Unichain RPCs reject with intermittent ProviderGasError. alpha-router.ts overrides it there with 10-call batches (~12M gas), which succeeds consistently.

Permit2 allowance expiration

  • Permit2 grants from /approve (used by uniswap/router) were hardcoded to expire after 48 hours, forcing bots to re-approve every uniswap/router pair every 2 days — no other connector has expiring approvals
  • New optional ethereum.permit2ExpirationSeconds chain config; when unset, grants never expire (max uint48), matching plain ERC20 approval semantics elsewhere. Permit2 allowances remain uint160-scoped and revocable.
  • Unit tests cover the default, configured, and uint48-cap cases

Verified live

Robinhood Chain mainnet (4663):

  • /chains/ethereum/status, /tokens, /estimate-gas (EIP-1559 path) ✅
  • Router quote: 0.01 WETH → 17.32 USDG (~$1,732/ETH, 0.011% price impact) via Universal Router ✅
  • CLMM pool-info on the live WETH/USDG 0.05% pool (~$550k liquidity) ✅
  • AMM pool-info on the live WETH/USDG V2 pair ✅

Unichain (130) + v4:

  • Unichain WETH → USDC router quote: 3/3 runs via the official public RPC (0.1 WETH → ~174.4 USDC, V3 split route) ✅
  • Mainnet WETH → USDC with v4 enabled: quotes return V3+V4 split routes when v4 improves price, V3-only otherwise ✅
  • Mainnet v4-only route encodes valid Universal Router calldata (verified methodParameters.to = Universal Router, no tx sent) ✅

Notes for existing installs

  • Existing conf/root.yml files do not pick up new template namespaces automatically — run pnpm run setup (or add the ethereum-robinhoodchain* / ethereum-unichain namespaces manually) to enable the networks
  • Existing Permit2 approvals keep their original 48h expiration until re-approved once with the new default
  • /pools/find does not work for Robinhood Chain because GeckoTerminal does not index it; use an explicit poolAddress

estimate-gas fixes (post-review)

Follow-ups from review of this branch (072aea677, 646192f8c):

  • Unichain missing from the route's EIP-1559 gate — the network list was duplicated in three places (twice in ethereum.ts, once in estimate-gas.ts) and had drifted. Extracted a single exported EIP1559_NETWORKS constant used at all three sites.
  • Pre-existing bug (also on main): the route never returned gasType: 'eip1559' for any network — it read lastGasPriceEstimate.isEIP1559 as a flat object, but the cache is keyed by network, so the check was always undefined. Added a public getCachedGasPriceEstimate() accessor (replacing the (ethereum as any).constructor reach into a private static) and fixed the lookup. EIP-1559 networks whose estimate fell back to legacy pricing now report gasType: 'legacy' instead of omitting the field.
  • Live-verified: estimate-gas returns eip1559 with real maxFeePerGas/maxPriorityFeePerGas on mainnet, unichain, and robinhoodchain; new route tests cover eip1559/legacy/fallback cases (146 tests passing).

Universal Router fallback quote fixes (post-review)

From a full-branch code review (6ff69289d), two fixes to the fallback quote path used where the AlphaRouter SDK is unavailable (robinhoodchain):

  • Requested slippagePct now reaches the swap calldata. Previously getUniversalRouterQuote always embedded the config slippage in the Universal Router calldata while the response's minAmountOut/maxAmountIn reflected the caller's requested value — executions could revert inside the accepted tolerance or fill below the promised minimum. The parameter now threads through exactly like the AlphaRouter path.
  • V3 quotes now come from the on-chain QuoterV2 instead of local math on fabricated tick data. findV3Route previously simulated on synthetic ticks (liquidityNet: 0 across ±300 spacings), returned the first fee tier with any liquidity (a dust pool could beat a deep one), and getQuote always preferred V3 over V2. It now quotes every fee tier via QuoterV2, keeps the best, and selects the best route across V3/V2 by quoted amount.
  • Removed tick-data-provider.test.js (tested the SDK tick machinery supporting the fabricated-tick approach); added route-level tests for the fallback branch pinning slippage threading and response mapping.
  • Live-verified on robinhoodchain: decoded calldata min-out tracks the requested slippage (1% and 5% requests produce matching embedded tolerances); SELL and BUY exact-out quotes are cross-consistent (~1754 USDG/WETH); unichain AlphaRouter path unaffected.

Test plan

  • pnpm build, pnpm typecheck, pnpm lint (0 errors; only pre-existing warnings)
  • test/chains + test/connectors suites pass (146 tests across uniswap + ethereum suites, including new Permit2 expiration and estimate-gas tests)
  • Live smoke test of chain endpoints and all three Uniswap trading types on robinhoodchain (see above)
  • Live smoke test of Unichain quotes and mainnet v4 routing (see above)

🤖 Generated with Claude Code

fengtality and others added 2 commits July 8, 2026 21:35
…pport

Add Robinhood Chain (chainID 4663) and Robinhood Chain Testnet (chainID
46630) as Ethereum networks:
- Chain templates with EIP-1559 gas config (Arbitrum Orbit L2, ETH gas)
- Mainnet token list with 27 tokens (WETH, USDG, and Robinhood stock/ETF
  tokens), symbols and decimals verified on-chain
- Wrapped native (WETH) mapping and EIP-1559 gating for both networks

Wire up Uniswap on robinhood-chain (router, AMM, CLMM):
- Add official V2/V3/UniversalRouter deployment addresses
- Resolve the Universal Router address from the per-network contracts map
  instead of the SDK's hardcoded chain map, which throws for chains it
  doesn't know (e.g. 4663) and has no override mechanism
- Fall back to the Universal Router quote in /router/quote-swap when the
  AlphaRouter SDK doesn't support the network

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Uniswap instance mock was missing the new isAlphaRouterAvailable
method, so quoteSwap threw a TypeError and returned 500.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@fengtality fengtality added this to the v2.16 milestone Jul 9, 2026
@fengtality fengtality requested a review from rapcmia July 9, 2026 04:58
@rapcmia rapcmia self-assigned this Jul 9, 2026
@rapcmia rapcmia moved this to Backlog in Pull Request Board Jul 9, 2026
Add Unichain (chainID 130, OP-stack L2) as an Ethereum network:
- Chain template with EIP-1559 gas config and official public RPC
- Token list with 11 major tokens from the Uniswap default token list
- Wrapped native (WETH) mapping and EIP-1559 gating
- Official Uniswap V2/V3/V4/UniversalRouter deployment addresses

Enable Uniswap v4 in /router quotes and swaps:
- Include Protocol.V4 in AlphaRouter routing on chains in the SDK's
  V4_SUPPORTED list; v4 swaps execute through the existing Universal
  Router + Permit2 flow with no API changes
- Override the SDK's on-chain quote provider on Unichain with smaller
  multicall batches (10x1.2M gas vs the default 80x1.2M), which public
  Unichain RPCs reject with intermittent ProviderGasError

Verified live: Unichain WETH->USDC quotes 3/3 via the official RPC, and
mainnet WETH->USDC now returns V3+V4 split routes when v4 wins on price.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@fengtality fengtality changed the title feat(chains): add Robinhood Chain mainnet and testnet with Uniswap support feat(chains): add Robinhood Chain and Unichain networks; enable Uniswap v4 routing Jul 9, 2026
fengtality and others added 2 commits July 8, 2026 23:17
…t to never

Permit2 grants from /approve were hardcoded to expire after 48 hours, so
every uniswap/router pair silently required re-approval every 2 days --
no other connector has expiring approvals.

Add optional ethereum.permit2ExpirationSeconds chain config; when unset,
grants never expire (max uint48), matching plain ERC20 approval semantics
on other connectors. Permit2 allowances remain uint160-scoped and
revocable, so this keeps its safety advantages without the churn.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rename the network identifier (and testnet) everywhere: template
filenames, config namespaces, token lists, EIP-1559 gates, WETH map,
and the Uniswap networks allow-list. The geckoId values and external
docs URLs keep their original hyphenated forms since those identifiers
are owned by CoinGecko and Uniswap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ipezygj

ipezygj commented Jul 9, 2026

Copy link
Copy Markdown

Tested against both new chains — nice work. One inconsistency worth a look:

unichain is missing from the EIP-1559 network list in estimate-gas.ts. It's included in ethereum.ts (both the estimateGasPrice path and the send path), but the estimate-gas route only got robinhood-chain / robinhood-chain-testnet. So GET /chains/ethereum/estimate-gas?network=unichain falls into the else if (!isEIP1559Network) branch and returns gasType: "legacy" with no maxFeePerGas / maxPriorityFeePerGas, even though Unichain is EIP-1559 (OP-stack) and the rest of the code treats it that way. One-line fix:

// src/chains/ethereum/routes/estimate-gas.ts
      network === 'base' ||
      network === 'robinhood-chain' ||
      network === 'robinhood-chain-testnet' ||
      network === 'unichain';

Everything else I checked came back clean:

  • Deployment addresseseth_getCode on every new Uniswap address: Robinhood Chain (4663) WETH + V2 router/factory + V3 SwapRouter02/QuoterV2 + UniversalRouterV2; Unichain (130) WETH + V2 + V3 + V4 PoolManager + StateView — all present. V2/V3 router bytecode is identical across both chains (deterministic deployments).
  • ChainIDs verified against the live RPCs: robinhood-chain 4663, robinhood-chain-testnet 46630, unichain 130 — all match the templates.
  • getUniversalRouterV2Address refactor (replacing the SDK's UNIVERSAL_ROUTER_ADDRESS) looks safe — all 14 networks in contractAddresses define a non-null universalRouterV2Address, so the getter won't throw for any existing network.

(robinhood-chain-testnet is correctly absent from uniswap.config.ts since there's no Uniswap deployment there.)

fengtality and others added 3 commits July 8, 2026 23:45
…red list

The EIP-1559 network list was duplicated in three places (twice in
ethereum.ts, once in estimate-gas.ts) and the copies drifted: unichain
was added to ethereum.ts but not estimate-gas.ts, so
GET /chains/ethereum/estimate-gas?network=unichain reported
gasType 'legacy'. Extract a single exported EIP1559_NETWORKS constant
and use it at all three sites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The route read lastGasPriceEstimate.isEIP1559 directly, but the cache
became keyed by network, so the property was always undefined and the
route never returned gasType 'eip1559' for any network. Expose a
public getCachedGasPriceEstimate() accessor on Ethereum (replacing the
(ethereum as any).constructor reach into a private static) and index
the cache correctly. EIP-1559 networks whose estimate fell back to
legacy pricing now report gasType 'legacy' instead of omitting it.

Adds route tests covering eip1559 output for mainnet/unichain/
robinhoodchain, legacy output for bsc, and the fallback case. The test
automock is patched to keep the real EIP1559_NETWORKS constant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… V3 via QuoterV2

Two fixes to the Universal Router fallback path (used on networks the
AlphaRouter SDK doesn't support, e.g. robinhoodchain):

- quoteSwap now passes the requested slippagePct through
  getUniversalRouterQuote, so the min-out/max-in embedded in the swap
  calldata matches the minAmountOut/maxAmountIn advertised in the quote
  response. Previously the calldata always used the config slippage
  while the response reflected the caller's, so executions could revert
  inside the accepted tolerance or fill below the promised minimum.

- findV3Route now quotes every fee tier against the on-chain QuoterV2
  and keeps the best one, and getQuote picks the best route across
  V3/V2 by quoted amount. Previously it simulated locally on fabricated
  tick data (liquidityNet=0 across +/-300 tick spacings), returned the
  first fee tier with any liquidity (a dust pool could beat a deep
  one), and always preferred V3 over V2 regardless of price.

Removes tick-data-provider.test.js, which exercised the SDK's tick
machinery in support of the fabricated-tick approach. Adds route-level
tests for the fallback branch, pinning slippage threading and response
mapping.

Live-verified on robinhoodchain: SELL 0.1 WETH -> 175.4 USDG and BUY
exact-out cross-consistent; decoded calldata min-out tracks requested
slippage (1% -> 0.99%, 5% -> 4.76% implied, the SDK's quoted/(1+slip)
convention). Unichain AlphaRouter path unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@devatnull

Copy link
Copy Markdown

pls merge x)

@rapcmia rapcmia requested review from nikspz and removed request for rapcmia July 13, 2026 06:31
@rapcmia rapcmia assigned nikspz and unassigned rapcmia Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

5 participants