Capital has gravity.
GravFi pulls fragmented capital into Robinhood Chain. An asset is locked in a single-purpose vault on Ethereum, a message is delivered by a configured messenger, and a GravFi representation (gToken) is minted 1:1 on Robinhood Chain. The reverse path burns the gToken and unlocks the collateral. Proof of Gravity compares the two sides on-chain, continuously.
What the interface promises
- Every number is read from a contract or an RPC. There is no seeded TVL, no sample transaction, no placeholder address.
- A feature whose configuration is missing is disabled and labelled NOT CONFIGURED / NOT DEPLOYED.
- A reserve that cannot be read is UNVERIFIED, never “100% backed”.
- Bridge stages derive from receipts, events and contract views — not timers.
Architecture
src/config env · chains · assets · contracts · brand (single source of configuration) src/lib/chain viem public clients (browser → /api/rpc/<chain> relay) src/lib/reserves Proof of Gravity engine (pinned-block reads on both chains) src/lib/bridge BridgeAdapter interface + adapters/native · adapters/layerzero src/lib/swap SwapAdapter interface + adapters/uniswapV2 src/lib/liquidity factory/pair enumeration src/lib/history HistoryAdapter: rpc-logs (default) · indexer (when configured) src/lib/router Gravity Router: bridge leg + swap leg composition src/hooks TanStack Query + wagmi hooks; no transaction logic in components contracts/ Foundry workspace: GravVault · GravToken · GravBridge · IGravMessenger
Bridge
GravBridge is deployed once per chain. On the source chain each route points at a GravVault; on Robinhood Chain each route points at a GravToken. The bridge does not implement a messaging protocol: it calls IGravMessenger.send and accepts receiveMessage only from the messenger it was configured with. A LayerZero OApp, a native rollup messenger or a test double can sit behind that interface.
wrap user → approve(vault) → bridge.bridgeOut{value: fee}(USDC, amount, recipient)
vault.depositFrom(user, amount) → totalLocked += received
messenger.send(dstEid, abi.encode(nonce, gUSDC, recipient, received))
… remote messenger → bridge.receiveMessage(srcEid, payload)
gUSDC.mintFromBridge(recipient, received)
unwrap user → bridge.bridgeOut{value: fee}(gUSDC, amount, recipient) // burns caller's gUSDC
… → vault.withdrawTo(recipient, amount) // subject to the rolling withdraw limitFrontend adapters live in src/lib/bridge/adapters. The native adapter quotes the messaging fee from the bridge contract, prepares the write for the wallet (it never signs), and tracks completion via processed(nonce) and the BridgeCompleted event on the far chain. The LayerZero adapter reports NOT CONFIGURED: no LayerZero endpoint is deployed on Robinhood Chain (checked on-chain), so the live transport is GravRelayMessenger — an operator-signed relayer. The Bridge page says so on every pending message, and an account with RELAYER_ROLE can deliver from the page or with npm run relayer. Deploy everything from /app/deploy with your own wallet.
Proof of Gravity
For each asset, at a pinned block per chain:
source token.balanceOf(vault) vault.totalLocked() token.decimals() destination gToken.totalSupply() gToken.decimals() backingBps = balanceOf(vault) · 10_000 / totalSupply (decimals aligned) ≥ 100.00% FULLY BACKED · 99–100% WATCH · < 99% UNDERCOLLATERALIZED unreadable → UNVERIFIED · unconfigured → AWAITING DEPLOYMENT
The inspector shows every raw read with its contract, method, value and block, with copy and explorer actions. The classification lives in src/lib/reserves/engine.ts and is never stored.
Swap & liquidity
Swaps run through a router exposing the Uniswap V2 interface on Robinhood Chain. Route discovery tries the direct pair, then a two-hop route through WETH. Quotes come from getAmountsOut; price impact is derived from pair reserves along the route. Quotes older than 30 seconds are re-fetched before signing. The liquidity explorer enumerates the factory and omits USD TVL and volume because no price source is configured.
Gravity Router
The router composes the bridge leg and the swap leg into one plan with per-stage availability, fees and outputs. It is explicit that execution requires two signatures on two chains and is not atomic.
Configuration
NEXT_PUBLIC_APP_ENV selects the network pair: mainnet → Ethereum (1) → Robinhood Chain (4663); anything else → Sepolia (11155111) → Robinhood Chain Testnet (46630). All addresses come from .env; see .env.example. The full activation sequence is in the README.
Security
- Roles: DEFAULT_ADMIN (routes, messenger, limits), PAUSER (pause/unpause), BRIDGE_ROLE (vault deposit/withdraw, token mint/burn) — granted only to the bridge contract.
- No public mint; no tx.origin; SafeERC20; ReentrancyGuard on every fund-moving path; Pausable everywhere.
- Vault accounting uses the amount actually received, so fee-on-transfer tokens cannot inflate supply.
- Replay protection by nonce on the receiving bridge; wrong-source and unauthorized-messenger reverts.
- Invariant tests: wrapped supply ≤ locked collateral; exact accounting including in-flight messages; vault holds what it reports.
- Contracts are experimental until independently audited.