Imported from danteGiuliano/defi (
AGENTS.md). Install upstream withnpx skills add danteGiuliano/defi. Copyright stays with the author.
AGENTS.md - DeFi Hedge Bot
Project Overview
- Type: Full-stack DeFi trading application
- Frontend: React 19 + Vite + TypeScript + Tailwind CSS + Radix UI + wagmi/viem
- Backend: Express.js + TypeScript (in
/backendfolder) - Purpose: Automated DeFi hedging - Uniswap liquidity pools + Hyperliquid perpetual trading
- Networks: Ethereum mainnet/testnet, multiple trading pairs (ETH, BTC, SOL, ARB, OP, LINK, UNI, etc.)
Commands
Frontend (root)
# Development
npm run dev # Start Vite dev server
# Build & Preview
npm run build # TypeScript compile + Vite build to dist/
npm run preview # Preview production build
# Linting
npm run lint # ESLint on all .ts/.tsx files
Backend (/backend)
cd backend
# Development
npm run dev # Run with ts-node (src/index.ts)
# Build & Run
npm run build # Compile TypeScript to dist/
npm run start # Run compiled JS from dist/
# Linting
npm run lint # ESLint on src/**/*.ts
Running Single Test
Note: No test framework is configured in this project. Do not create tests.
Code Style - Frontend
General
- Language: English for code, error messages in Spanish
- TypeScript strict mode enabled
- React 19 with functional components and hooks
- Use
async/awaitover Promise chains - Avoid
any- use proper types
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Files (components) | PascalCase | WalletConnect.tsx |
| Files (hooks/utils) | camelCase | useStrategies.ts, utils.ts |
| Components | PascalCase | function WalletConnect() |
| Hooks | camelCase, prefix use |
useStrategies(), usePrices() |
| Interfaces/Types | PascalCase | interface Strategy |
| Functions/variables | camelCase | getAllStrategies() |
| Constants | UPPER_SNAKE | MAX_LEVERAGE = 50 |
Imports
// Order: React → external libs → @/components → @/hooks → @/services → @/types → relative
import { useState, useEffect } from 'react';
import { useAccount } from 'wagmi';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { WalletConnect } from '@/components/ui/custom/WalletConnect';
import { useStrategies } from '@/hooks/useStrategies';
import { strategyApi } from '@/services/api';
import type { Strategy, Position } from '@/types';
import { cn } from '@/lib/utils';
Components
- Use shadcn/ui components from
@/components/ui/ - Custom components in
@/components/ui/custom/ - Component file structure: imports → types → component → export
- Use
cn()from@/lib/utilsfor className merging
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
interface StrategyCardProps {
strategy: Strategy;
onStart: () => void;
onStop: () => void;
className?: string;
}
export function StrategyCard({ strategy, onStart, onStop, className }: StrategyCardProps) {
return (
<div className={cn('p-4 border rounded-lg', className)}>
{/* content */}
</div>
);
}
Types
- Define all types in
@/types/index.ts - Use interfaces for objects, type for unions/aliases
- Export const arrays for static data
// Types in @/types/index.ts
export interface Strategy {
id: string;
name: string;
isActive: boolean;
pools: LiquidityPool[];
positions: Position[];
}
export const AVAILABLE_PAIRS: TradingPair[] = [
{ symbol: 'ETH-USD', baseAsset: 'ETH', quoteAsset: 'USD', maxLeverage: 50 },
];
Error Handling
// Components: use try/catch with toast notifications
const handleCreate = async (data: StrategyInput) => {
try {
await createStrategy(data);
toast.success('¡Estrategia creada!');
} catch (error) {
toast.error(error instanceof Error ? error.message : 'Error desconocido');
}
};
// API services: throw on error
export const strategyApi = {
getAll: async (): Promise<Strategy[]> => {
const response = await apiClient.get<ApiResponse<Strategy[]>>('/strategies');
if (!response.data.success) {
throw new Error(response.data.error);
}
return response.data.data || [];
},
};
State Management
- React Query (
@tanstack/react-query) for server state - Local state with
useState/useReducer - Web3 state via
wagmihooks andWeb3Context
const { strategies, loading, createStrategy } = useStrategies();
const { address, isConnected } = useAccount();
Code Style - Backend
See /backend/AGENTS.md for backend-specific guidelines. Key points:
- Express.js routes return
{ success: boolean; data?: T; error?: string } - Services throw errors, routes catch and return JSON
- Use
Map<string, T>for in-memory storage - Use
node-cronfor scheduled tasks
Configuration
Environment Variables
Frontend (.env):
VITE_API_URL=http://localhost:3001/api
Backend (backend/.env):
PORT=3001
HYPERLIQUID_API_URL=...
UNISWAP_ROUTER_ADDRESS=...
Path Aliases
@/*maps to./src/*in frontend- Use
@/for all imports from src
File Structure
/ # Frontend (React + Vite)
├── src/
│ ├── components/ui/ # shadcn/ui components
│ ├── components/ui/custom/ # custom components
│ ├── hooks/ # React hooks
│ ├── services/ # API clients
│ ├── types/ # TypeScript interfaces
│ ├── lib/ # Utilities
│ └── contexts/ # React contexts
├── backend/ # Express.js backend
│ └── src/
│ ├── routes/ # API endpoints
│ ├── services/ # Business logic
│ └── types/ # TypeScript interfaces
Common Patterns
API Response Wrapper
// Backend returns this format
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
}
Web3 Integration
// Wrap app with Web3Provider
import { Web3Provider } from '@/contexts/Web3Context';
function App() {
return (
<Web3Provider>
<Dashboard />
</Web3Provider>
);
}
Parallel API Calls
const [prices, positions] = await Promise.all([
marketApi.getPrices(testnet),
marketApi.getPositions(privateKey, testnet),
]);
Gotchas
- ethers v6: Use
ethers.getBigInt()notBigInt() - wagmi v3: Uses
useAccount,useWriteContract,useReadContracthooks - React 19: New hooks, use compatible versions of react-dom
- Tailwind: Uses
cn()helper for conditional classes - Radix UI: All components accessed via
@/components/ui/ - Backend Port: Default 3001, frontend expects at
VITE_API_URL - Cron Jobs: Need
.stop()on shutdown to prevent hanging
Build Output
- Frontend builds to
/dist(runnpm run build) - Backend compiles to
/backend/dist(runcd backend && npm run build)