UNPKG

@openzeppelin/contracts-ui-builder-adapter-evm

Version:
202 lines (180 loc) 6.3 kB
import type { EvmNetworkConfig, UserRpcProviderConfig, } from '@openzeppelin/contracts-ui-builder-types'; import { appConfigService, isValidUrl, logger, userRpcConfigService, } from '@openzeppelin/contracts-ui-builder-utils'; /** * Builds a complete RPC URL from a user RPC provider configuration. * For simplified RPC configuration, this just returns the URL as-is since * users are now providing complete RPC URLs including any API keys. * * @param config The user RPC provider configuration * @returns The RPC URL */ export function buildRpcUrl(config: UserRpcProviderConfig): string { return config.url; } /** * Resolves the RPC URL for a given EVM network configuration. * Priority order: * 1. User-provided RPC configuration (from UserRpcConfigService) * 2. RPC URL override from AppConfigService * 3. Default rpcUrl from the network configuration * * @param networkConfig - The EVM network configuration. * @returns The resolved RPC URL string. * @throws If no RPC URL can be resolved (neither user config, override, nor default is present and valid). */ export function resolveRpcUrl(networkConfig: EvmNetworkConfig): string { const logSystem = 'RpcResolver'; const networkId = networkConfig.id; // First priority: Check user-provided RPC configuration const userRpcConfig = userRpcConfigService.getUserRpcConfig(networkId); if (userRpcConfig) { const userRpcUrl = buildRpcUrl(userRpcConfig); if (isValidUrl(userRpcUrl)) { logger.info( logSystem, `Using user-configured RPC URL for network ${networkId}: ${userRpcConfig.name || 'Custom'}` ); return userRpcUrl; } else { logger.warn( logSystem, `User-configured RPC URL for ${networkId} is invalid: ${userRpcUrl}. Falling back.` ); } } // Second priority: Check AppConfigService for an override const rpcOverrideSetting = appConfigService.getRpcEndpointOverride(networkId); let rpcUrlFromOverride: string | undefined; if (typeof rpcOverrideSetting === 'string') { rpcUrlFromOverride = rpcOverrideSetting; } else if (typeof rpcOverrideSetting === 'object' && rpcOverrideSetting) { // Check if it's a UserRpcProviderConfig if ('url' in rpcOverrideSetting && 'isCustom' in rpcOverrideSetting) { const userConfig = rpcOverrideSetting as UserRpcProviderConfig; rpcUrlFromOverride = buildRpcUrl(userConfig); } else if ('http' in rpcOverrideSetting) { // It's an RpcEndpointConfig rpcUrlFromOverride = rpcOverrideSetting.http; } } if (rpcUrlFromOverride) { logger.info( logSystem, `Using overridden RPC URL for network ${networkId}: ${rpcUrlFromOverride}` ); if (isValidUrl(rpcUrlFromOverride)) { return rpcUrlFromOverride; } else { logger.warn( logSystem, `Overridden RPC URL for ${networkId} is invalid: ${rpcUrlFromOverride}. Falling back.` ); } } // Third priority: Fallback to the rpcUrl in the networkConfig if (networkConfig.rpcUrl && isValidUrl(networkConfig.rpcUrl)) { logger.debug( logSystem, `Using default RPC URL for network ${networkId}: ${networkConfig.rpcUrl}` ); return networkConfig.rpcUrl; } logger.error( logSystem, `No valid RPC URL could be resolved for network ${networkId}. Checked user config, override, and networkConfig.rpcUrl.` ); throw new Error( `No valid RPC URL configured for network ${networkConfig.name} (ID: ${networkId}).` ); } /** * Validates an RPC endpoint configuration for EVM networks. * @param rpcConfig - The RPC provider configuration to validate * @returns True if the configuration is valid, false otherwise */ export function validateEvmRpcEndpoint(rpcConfig: UserRpcProviderConfig): boolean { try { // Check if it's a valid URL (our validator already ensures HTTP/HTTPS) if (!isValidUrl(rpcConfig.url)) { logger.error('validateEvmRpcEndpoint', `Invalid RPC URL format: ${rpcConfig.url}`); return false; } // Additional EVM-specific validation could be added here // For example, checking if the URL follows known provider patterns return true; } catch (error) { logger.error('validateEvmRpcEndpoint', 'Error validating RPC endpoint:', error); return false; } } /** * Tests the connection to an EVM RPC endpoint with a timeout. * @param rpcConfig - The RPC provider configuration to test * @param timeoutMs - Timeout in milliseconds (default: 5000ms) * @returns Connection test results including success status, latency, and any errors */ export async function testEvmRpcConnection( rpcConfig: UserRpcProviderConfig, timeoutMs: number = 5000 ): Promise<{ success: boolean; latency?: number; error?: string; }> { if (!rpcConfig.url) { return { success: false, error: 'RPC URL is required' }; } // Create an AbortController for timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeoutMs); try { const startTime = Date.now(); // Use fetch to make a JSON-RPC call to test the connection const response = await fetch(rpcConfig.url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ jsonrpc: '2.0', method: 'eth_blockNumber', params: [], id: 1, }), signal: controller.signal, }); if (!response.ok) { return { success: false, error: `HTTP error: ${response.status}` }; } const data = await response.json(); const latency = Date.now() - startTime; if (data.error) { return { success: false, error: data.error.message || 'RPC error' }; } return { success: true, latency }; } catch (error) { logger.error('testEvmRpcConnection', 'Connection test failed:', error); // Check if the error was due to timeout if (error instanceof Error && error.name === 'AbortError') { return { success: false, error: `Connection timeout after ${timeoutMs}ms`, }; } return { success: false, error: error instanceof Error ? error.message : 'Connection failed', }; } finally { // Clear the timeout clearTimeout(timeoutId); } }