Orbital
A spherical AMM engine that enables multi-token pools like Curve while providing higher-dimensional concentrated liquidity inspired by Uniswap v3. Implemented as a Uniswap v4 hook, with the AMM mthematics fully derived and implemented from Paradigm’s Orbital paper.
Video
Công nghệ sử dụng
Sự miêu tả
Orbital Hook
One pool for every stablecoin. A Uniswap v4 hook that replaces constant-product math with the Orbital sphere/torus curve, so a basket of stablecoins trades out of a single shared reserve book instead of a web of shallow pairs.
Live app: https://orbital-hook.vercel.app/ · Robinhood Chain testnet · Arbitrum Sepolia · Unichain Sepolia
The problem
Every stablecoin targets the same dollar, and no AMM treats them that way.
Liquidity fragments. A pool is built two tokens at a time, so four stablecoins need six of them: USDC/USDT, USDC/DAI, USDC/FRAX, USDT/DAI, USDT/FRAX, DAI/FRAX. The same deposits get divided across every combination, each pool ends up shallower than it should be, and a USDC/FRAX trade cannot reach USDC/USDT depth no matter how deep that pool is. Listing a fifth coin means standing up four more markets.
Capital sits idle. Curve holds the whole basket together but lays liquidity flatly along the entire curve, most of it parked at prices a dollar never reaches. Uniswap v3 concentrates properly and then caps you at two tokens. Today you pick breadth or depth, never both.
Size moves the market. Thin pools and flat curves mean large stablecoin trades pay more slippage than an asset pegged to $1 ought to cost.
A depeg lands on whoever is in that pair. A flat pool keeps quoting a failing asset near a dollar long after the market has stopped. Arbitrage sells it in and takes the healthy coins out until LPs hold little else. This is the tail impermanent loss that wrecks flat stable pools; USDC in March 2023 is the reference case.
What Orbital does
Orbital implements the curve from Paradigm's Orbital paper (June 2025) and runs it inside a Uniswap v4 hook. The whole idea is a change in the shape of the curve:
x · y = k → ‖r − x‖² = Σ (r − xᵢ)² = r²
Uniswap prices two tokens on a hyperbola. Orbital prices N tokens on an N-sphere. Three pieces make it work:
Sphere. The reserve vector x = (x₁ … xₙ) is a point on an N-sphere of radius r centred at (r … r). The equal-price point, where every coin trades exactly 1:1, is xᵢ = r(1 − 1/√n). The curve only bends as the basket drifts off peg, which is precisely the behaviour you want from assets that are supposed to be worth the same thing.
Ticks. Each LP picks a plane Σ xᵢ = k that cuts the sphere at a chosen depeg bound — "provide liquidity only while this holds above $0.95", say. That is the concentration: capital packs into the narrow band around $1 where stablecoins actually change hands. It also caps the LP's loss by construction. Cross the bound and that tick exits to the boundary and stops quoting, so a broken coin cannot drain the LPs who supplied the healthy ones. There is no oracle and no pause switch involved; it is geometry.
Torus. Stacked interior ticks fold into a single torus the pool tracks with two running sums, Σ xᵢ and Σ xᵢ². A swap costs the same to compute no matter how many coins or ticks exist.
The result is one book where any pair draws on the whole of it, depth is concentrated where dollars trade, and a depeg is fenced off rather than socialised.
How it is built as a v4 hook
A v4 pool is always exactly two tokens. To fit four stablecoins into one book, the six pairs are registered as six v4 pools and every PoolKey.hooks field points at the same contract. Those pairs are only views. Behind all of them the hook keeps one shared reserve vector, so a trade on USDC/USDT and a trade on DAI/FRAX move the same reserves and the same price.
On every swap beforeSwap evaluates the Orbital engine and returns a BeforeSwapDelta that fully specifies the trade, so the PoolManager's default x·y math never executes. v4 keeps everything else: custody, settlement, accounting, the lock, and the router interface. Only the pricing step is ours.
The hook serves both halves of the v4 swap interface. amountSpecified < 0 fixes what the trader pays; amountSpecified > 0 fixes what they receive. Exact output runs the same segmenting solver in reverse across tick crossings, grossing the fee up per segment. This matters in practice: routers and quoters that quote by output are the normal case, and a hook that reverts on positive amountSpecified is invisible to them.
Two claim ledgers, kept separate
This is the part that is easy to get wrong.
PoolManager claim tokens, held by the hook. Real ERC-20s always sit in the PoolManager. When value flows in, the hook converts its positive balance-delta into claim tokens (
poolManager.mint), a redeemable IOU against the singleton's custody. To pay out it burns them and the PoolManager releases the underlying.Orbital LP shares, held by the LP. The hook keeps its own soulbound, ERC-6909-shaped ledger with
tokenId = band, one of a fixed menu of 31 depeg bands. Adding liquidity mints a share of that band's tick; repeat deposits into the same band grow the same position. No real token ever sits here.
All of it happens inside one unlock frame. Balances move only as deltas in transient storage, and unlock refuses to return until every currency delta nets to zero.
Engineering details worth knowing
Real stablecoins, real decimals. USDC and USDT are 6-decimal, DAI and FRAX are 18. The engine runs entirely in WAD and converts only at the token boundary, rounding in the pool's favour every time. A 128k-call stateful fuzz runs on a mixed-decimal (6/6/18) profile specifically because an all-18 run leaves every conversion a no-op and proves nothing about the scaling layer.
Issuer controls do not lock the pool. Circle can blacklist, Tether can block, Paxos can freeze. If one asset in the basket becomes untransferable, withdrawals and fee collection still pay out every other asset, and the stuck amount is held for its owner and claimable once the token moves again. One frozen token no longer freezes every LP.
Solver. Swaps are solved with a safeguarded Newton method (bracket invariant maintained throughout) over the segments between tick crossings, with a hard cap on bracket doublings. Past the point where the output would peak and fall, the swap reverts rather than quietly paying out less.
Size. The deployed hook is 24,346 bytes against the EIP-170 limit of 24,576 — 230 bytes of headroom. Getting there meant dropping Permit2, writing a minimal soulbound share ledger instead of inheriting v4-core's ERC-6909, and deploying TickLib as an external library. Solidity 0.8.30 with via_ir enabled, which the Orbital math libraries require.
Liquidity shape is not invented. Each pool is seeded on an eight-tier ladder — seven concentrated bands plus a small full-range backstop — whose shape mirrors where LPs in the live Uniswap v3 USDC/USDT0 0.01% pool on Arbitrum actually place their capital, read tick by tick on-chain. About two thirds of it sits within 5 bps of $1.
What is live
Three pools, each a separate book with its own reserves, each seeded with $5M across USDC · USDT · DAI · FRAX on a realistic decimal mix.
Chain | Chain ID | Hook |
|---|---|---|
Robinhood Chain testnet (primary) |
|
|
Arbitrum Sepolia |
|
|
Unichain Sepolia |
|
|
Random retail swap flow runs on top, interleaved across the three chains rather than chain by chain, with each trade capped at $5,000.
Asset index order differs per chain. The hook sorts assets ascending by address, and addresses are unrelated across chains, so USDC is index 3 on Arbitrum and 2 on Unichain and Robinhood. Always resolve by symbol.
Measured results
All-in cost including the 1 bp fee, quoted on the live $5M pool:
Trade size | $1k | $10k | $100k | $250k |
|---|---|---|---|---|
All-in cost | 1.00 bps | 1.02 bps | 1.22 bps | 1.54 bps |
The same figure holds on every one of the six pairs, because they share one book. Against a flat pool holding the same reserves over the same peg range, the paper's figure is roughly 154× the effective depth at N = 5.
Testing
205 tests across 18 suites. The ones that carry the weight:
Solvency.invariant.t.sol— stateful fuzz, 256 runs / 128k calls, two decimal profiles, swaps fixed by input and by output, assets frozen and unfrozen mid-sequence. Real claim-token custody always covers reserves plus accrued fees plus any payout held back, and rounding only ever favours the pool.MixedDecimalsLifecycle.t.sol— deterministic add → swap → collect → partial burn → full burn on a 6dp book, so every operation is asserted to succeed, not merely to not break. Also asserts a fee-on-transfer token is refused at the deposit boundary.OrbitalHook.t.sol— 67 tests over the hook surface: constructor guards, hook permissions, bands and deadlines, LP entry and exit during a depeg, swaps, tick crossings, boundary behaviour, pause and ownership.IssuerFreeze.t.sol— a paused stablecoin, a frozen LP and a frozen PoolManager.VirtualLiquidity.t.sol,SolverRobustness.t.sol— depth per unit of capital, and swaps up to the edge of the book without breaking the solver.Math libraries —
SphereMath,TorusMath,TickLib,QuadraticSolver, including fuzzed solver residual and stability bounds.
cd orbitalHook && forge test
Extension: cross-chain settlement
Built on top of the hook, not required to use it. Arbitrum Sepolia and Unichain Sepolia each run an OrbitalIntentSettler implementing ERC-7683, peered over Hyperlane. A user signs an intent and escrows on the origin chain; a filler pays out on the destination, routed through the local Orbital pool; a Hyperlane message verified by handle() releases the escrow.
Two things make this fit an N-asset book well:
A filler needs inventory in only one asset per chain. Because every stablecoin shares one book, any asset is a single hop from any other, so a filler holding just USDC on Arbitrum can satisfy an order for DAI there. That collapses N × M inventory positions to 1 × M.
Settlement is cryptographic, not social. The origin releases escrow only when its Hyperlane Mailbox delivers a message whose
(domain, sender)matches a registered peer. No arbiter, no bond, no dispute game. The trust assumption is exactly Hyperlane's ISM for that route.
Funds are never stuck: an unsettled order is reclaimable after fillDeadline + refundBuffer. Robinhood Chain testnet has no Hyperlane, so its pool is same-chain only. The settlers move orders between chains rather than merging liquidity — each chain's book stays its own.
Stack
Contracts | Solidity 0.8.30, Foundry, |
Venue | Uniswap v4 ( |
Hook address | CREATE2 via HookMiner, mined to carry the permission flags |
Cross-chain | ERC-7683 intents over Hyperlane |
Indexing | The Graph — one manifest, per-chain addresses |
App | Next.js, wagmi/viem |
UHI/
├── orbitalHook/ Solidity
│ ├── src/OrbitalHook.sol the v4 hook and Orbital engine
│ ├── src/libraries/ SphereMath, TorusMath, TickLib, QuadraticSolver, BandLib
│ ├── src/crosschain/ ERC-7683 settler + Hyperlane interfaces
│ ├── script/ deploy and simulation scripts
│ └── deployments.json machine-readable address registry
├── subgraph/ Pool, Asset, Tick, Swap, TickCross, PoolSnapshot
└── frontend/ swap, pools, positions, transactions
The subgraph is more than a dashboard. TickCrossed only fires after a depeg bound is hit, so as a warning it arrives too late. Every handler therefore also reads live slot0 and ticks at its own block and reproduces the engine's own crossing condition, storing how much slack each tick has left. That turns the feed into a leading indicator: which tick will cross first, and how much of the book leaves with it, before it happens.
References
Paradigm, Orbital — https://www.paradigm.xyz/2025/06/orbital
Uniswap v4 — https://docs.uniswap.org/contracts/v4/overview
ERC-7683 cross-chain intents — https://eips.ethereum.org/EIPS/eip-7683
Hyperlane — https://docs.hyperlane.xyz