| name | frontend-playbook |
| description | Complete build-to-production pipeline for HyperEVM dApps — local dev, testnet deploy, mainnet deploy, Vercel config, and go-live checklist. |
HyperEVM Frontend Playbook
The Stack
Frontend: React 18 + Vite
Wallets: wagmi v2 + viem
Charts: TradingView lightweight-charts (for price charts)
Realtime: Supabase Realtime (for live feeds from indexed events)
Data: Hyperliquid /info API + WebSocket
Deploy: Vercel (frontend) + HyperEVM (contracts)
Phase 0: Local Setup
npm create vite@latest my-hl-app -- --template react
cd my-hl-app
npm install wagmi viem @tanstack/react-query
npm install @supabase/supabase-js
npm install lightweight-charts
Create .env.local:
VITE_CHAIN_ID=998
VITE_RPC_URL=https://rpc.hyperliquid-testnet.xyz/evm
VITE_CONTRACT_ADDRESS=0x...
VITE_SUPABASE_URL=https://xxx.supabase.co
VITE_SUPABASE_ANON_KEY=eyJ...
.gitignore must include:
.env
.env.local
.env.*.local
Phase 1: Contract Deployment
Testnet First (Always)
npx hardhat run scripts/deploy.js --network hyperliquid_testnet
forge script script/Deploy.s.sol \
--rpc-url https://rpc.hyperliquid-testnet.xyz/evm \
--private-key $PRIVATE_KEY \
--broadcast \
-vvvv
After deploy:
- Copy contract address →
VITE_CONTRACT_ADDRESS in .env.local
- Copy ABI →
src/abi/ContractName.json
- Verify on testnet explorer:
https://explorer.hyperliquid-testnet.xyz
- Run through all contract functions manually in the UI
- Run automated tests against testnet deployment
Mainnet Deploy
Only after testnet is solid:
npx hardhat run scripts/deploy.js --network hyperliquid_mainnet
forge script script/Deploy.s.sol \
--rpc-url https://rpc.hyperliquid.xyz/evm \
--private-key $PRIVATE_KEY \
--broadcast \
-vvvv
Update production env vars with mainnet contract address.
Phase 2: Vercel Deployment
Project Setup
npm install -g vercel
vercel login
vercel
Or connect GitHub repo directly in Vercel dashboard.
Environment Variables (Vercel Dashboard)
Set these in Vercel → Project → Settings → Environment Variables:
VITE_CHAIN_ID=999
VITE_RPC_URL=https://rpc.hyperliquid.xyz/evm
VITE_CONTRACT_ADDRESS=0x<mainnet-address>
VITE_SUPABASE_URL=https://xxx.supabase.co
VITE_SUPABASE_ANON_KEY=eyJ...
Set separate values for Preview (testnet) vs Production (mainnet) environments.
vercel.json
{
"rewrites": [{ "source": "/(.*)", "destination": "/" }],
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-XSS-Protection", "value": "1; mode=block" }
]
}
]
The rewrite rule is required for client-side routing (React Router, etc.).
Phase 3: Local Development Flow
npm run dev
VITE_CHAIN_ID=998 VITE_RPC_URL=https://rpc.hyperliquid-testnet.xyz/evm npm run dev
Fork Mode (Hardhat)
For testing contract interactions locally with mainnet/testnet state:
module.exports = {
networks: {
hardhat: {
forking: {
url: "https://rpc.hyperliquid-testnet.xyz/evm",
blockNumber: "latest",
},
},
},
};
npx hardhat node --fork https://rpc.hyperliquid-testnet.xyz/evm
Phase 4: Supabase Realtime Setup
For live trade feeds, token launches, and event feeds:
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY
);
export function subscribeToTrades(tokenAddress, onTrade) {
return supabase
.channel(`trades:${tokenAddress}`)
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'trades',
filter: `token=eq.${tokenAddress}`,
}, (payload) => onTrade(payload.new))
.subscribe();
}
import { useEffect, useState } from 'react';
import { subscribeToTrades } from '../services/supabase';
function TradeFeed({ tokenAddress }) {
const [trades, setTrades] = useState([]);
useEffect(() => {
const channel = subscribeToTrades(tokenAddress, (trade) => {
setTrades(prev => [trade, ...prev].slice(0, 50));
});
return () => channel.unsubscribe();
}, [tokenAddress]);
return (
<div>
{trades.map(trade => (
<div key={trade.tx_hash} className={trade.is_buy ? 'buy' : 'sell'}>
{trade.is_buy ? '🟢 Buy' : '🔴 Sell'} {' '}
{formatToken(BigInt(trade.token_amount))} for {' '}
{formatHYPE(BigInt(trade.hype_amount))} HYPE
</div>
))}
</div>
);
}
Phase 5: Price Charts
import { useEffect, useRef } from 'react';
import { createChart, ColorType } from 'lightweight-charts';
function PriceChart({ candles }) {
const chartRef = useRef(null);
const chartInstance = useRef(null);
useEffect(() => {
if (!chartRef.current) return;
chartInstance.current = createChart(chartRef.current, {
layout: {
background: { type: ColorType.Solid, color: '#0d0d0d' },
textColor: '#d1d4dc',
},
width: chartRef.current.clientWidth,
height: 300,
grid: {
vertLines: { color: '#1c1c1c' },
horzLines: { color: '#1c1c1c' },
},
});
const series = chartInstance.current.addCandlestickSeries({
upColor: ,
: ,
: ,
: ,
: ,
});
series.(candles);
chartInstance.?.();
}, []);
( {
}, [candles]);
;
}
Go-Live Checklist
Contracts
[ ] Deployed to HyperEVM mainnet (chain ID 999)
[ ] Verified on https://explorer.hyperliquid.xyz
[ ] Contract address correct in production env vars
[ ] ABI matches deployed contract (not a stale copy)
Frontend
[ ] All .env.local values replaced with production values in Vercel
[ ] VITE_CHAIN_ID=999 in production
[ ] No testnet RPC URLs in production code
[ ] No console.log with sensitive data
[ ] Error boundaries implemented (app doesn't white-screen on JS error)
Wallet
[ ] Tested with MetaMask
[ ] Tested with Backpack
[ ] Tested with Phantom
[ ] Wrong network prompt works and switches to chain ID 999
[ ] Mobile wallet tested (WalletConnect if supported)
Transactions
[ ] Buy flow tested end-to-end on mainnet
[ ] Sell flow tested end-to-end on mainnet
[ ] Error states shown correctly
[ ] Explorer links open correct mainnet explorer
Data
[ ] Supabase indexer running and current
[ ] Live trade feed working
[ ] Price chart loading
[ ] Token stats (price, supply, reserve) loading correctly
Performance
[ ] Lighthouse score > 80
[ ] No unnecessary re-renders (use React DevTools Profiler)
[ ] Images optimized
[ ] Contract reads batched where possible
Security
[ ] No private keys anywhere in codebase
[ ] .env files not in git history
[ ] contract addresses from config, not hardcoded
[ ] CSP headers set (vercel.json)