rpc-proxy.ts
1 import { ChainId } from '@aave/contract-helpers'; 2 import { NextApiRequest, NextApiResponse } from 'next'; 3 4 // Documentation: ./server-side-rpc-proxy.md 5 const NETWORK_CONFIG: Record<number, { network: string; apiKey: string }> = { 6 // Mainnets 7 [ChainId.mainnet]: { network: 'eth-mainnet', apiKey: process.env.MAINNET_RPC_API_KEY || '' }, 8 [ChainId.polygon]: { network: 'polygon-mainnet', apiKey: process.env.POLYGON_RPC_API_KEY || '' }, 9 [ChainId.avalanche]: { network: 'avax-mainnet', apiKey: process.env.AVALANCHE_RPC_API_KEY || '' }, 10 [ChainId.arbitrum_one]: { 11 network: 'arb-mainnet', 12 apiKey: process.env.ARBITRUM_RPC_API_KEY || '', 13 }, 14 [ChainId.base]: { network: 'base-mainnet', apiKey: process.env.BASE_RPC_API_KEY || '' }, 15 [ChainId.optimism]: { network: 'opt-mainnet', apiKey: process.env.OPTIMISM_RPC_API_KEY || '' }, 16 [ChainId.metis_andromeda]: { 17 network: 'metis-mainnet', 18 apiKey: process.env.METIS_RPC_API_KEY || '', 19 }, 20 [ChainId.xdai]: { network: 'gnosis-mainnet', apiKey: process.env.GNOSIS_RPC_API_KEY || '' }, 21 [ChainId.bnb]: { network: 'bnb-mainnet', apiKey: process.env.BNB_RPC_API_KEY || '' }, 22 [ChainId.scroll]: { network: 'scroll-mainnet', apiKey: process.env.SCROLL_RPC_API_KEY || '' }, 23 [ChainId.zksync]: { network: 'zksync-mainnet', apiKey: process.env.ZKSYNC_RPC_API_KEY || '' }, 24 [ChainId.linea]: { network: 'linea-mainnet', apiKey: process.env.LINEA_RPC_API_KEY || '' }, 25 [ChainId.sonic]: { network: 'sonic-mainnet', apiKey: process.env.SONIC_RPC_API_KEY || '' }, 26 [ChainId.celo]: { network: 'celo-mainnet', apiKey: process.env.CELO_RPC_API_KEY || '' }, 27 28 // Testnets 29 [ChainId.sepolia]: { network: 'eth-sepolia', apiKey: process.env.MAINNET_RPC_API_KEY || '' }, 30 [ChainId.fuji]: { network: 'avax-fuji', apiKey: process.env.AVALANCHE_RPC_API_KEY || '' }, 31 [ChainId.arbitrum_sepolia]: { 32 network: 'arb-sepolia', 33 apiKey: process.env.ARBITRUM_RPC_API_KEY || '', 34 }, 35 [ChainId.base_sepolia]: { network: 'base-sepolia', apiKey: process.env.BASE_RPC_API_KEY || '' }, 36 [ChainId.optimism_sepolia]: { 37 network: 'opt-sepolia', 38 apiKey: process.env.OPTIMISM_RPC_API_KEY || '', 39 }, 40 [ChainId.scroll_sepolia]: { 41 network: 'scroll-sepolia', 42 apiKey: process.env.SCROLL_RPC_API_KEY || '', 43 }, 44 }; 45 46 function getRpcUrl(chainId: number): string | null { 47 const config = NETWORK_CONFIG[chainId]; 48 if (!config) return null; 49 return `https://${config.network}.g.alchemy.com/v2/${config.apiKey}`; 50 } 51 52 export default async function handler(req: NextApiRequest, res: NextApiResponse) { 53 const allowedOrigins = ['https://app.aave.com', 'https://aave.com']; 54 const origin = req.headers.origin; 55 56 const isOriginAllowed = (origin: string | undefined): boolean => { 57 if (!origin) return false; 58 59 if (allowedOrigins.includes(origin)) return true; 60 61 // Match any subdomain ending with avaraxyz.vercel.app for deployment urls 62 const allowedPatterns = [/^https:\/\/.*avaraxyz\.vercel\.app$/]; 63 64 return allowedPatterns.some((pattern) => pattern.test(origin)); 65 }; 66 67 if (process.env.CORS_DOMAINS_ALLOWED === 'true') { 68 res.setHeader('Access-Control-Allow-Origin', '*'); 69 } else if (origin && isOriginAllowed(origin)) { 70 res.setHeader('Access-Control-Allow-Origin', origin); 71 } 72 73 res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); 74 res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); 75 76 if (req.method === 'OPTIONS') { 77 return res.status(200).end(); 78 } 79 80 if (req.method !== 'POST') { 81 return res.status(405).json({ error: 'Method not allowed' }); 82 } 83 84 try { 85 const { chainId, method, params } = req.body; 86 const chainIdNumber = typeof chainId === 'string' ? parseInt(chainId) : chainId; 87 const rpcUrl = getRpcUrl(chainIdNumber); 88 89 if (!rpcUrl) { 90 return res.status(400).json({ error: `Unsupported chain ID: ${chainIdNumber}` }); 91 } 92 93 const rpcRequest = { 94 jsonrpc: '2.0', 95 id: Date.now(), 96 method, 97 params, 98 }; 99 100 const response = await fetch(rpcUrl, { 101 method: 'POST', 102 headers: { 103 'Content-Type': 'application/json', 104 Origin: origin || 'https://app.aave.com', 105 Referer: 'https://app.aave.com/', 106 }, 107 body: JSON.stringify(rpcRequest), 108 }); 109 110 const data = await response.json(); 111 112 return res.status(200).json(data); 113 } catch (error) { 114 console.error('RPC proxy error:', error); 115 return res.status(500).json({ error: 'Internal server error', details: String(error) }); 116 } 117 }