UNPKG

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

Version:
246 lines (222 loc) 9.78 kB
import { Config, http } from '@wagmi/core'; import { type Chain } from 'viem'; import type { UiKitConfiguration } from '@openzeppelin/contracts-ui-builder-types'; import { logger } from '@openzeppelin/contracts-ui-builder-utils'; import { type WagmiConfigChains } from '../types'; // Cache to prevent duplicate config creation and WalletConnect double initialization const cacheInstanceId = Math.random().toString(36).substring(7); const configCache = new Map<string, Config>(); logger.debug('rainbowkit/config-service', 'NEW configCache INSTANCE CREATED', cacheInstanceId); /** * Creates a cache key from the configuration to prevent duplicate config creation */ function createConfigCacheKey( userFullNativeConfig: Record<string, unknown> | undefined | null, chains: readonly Chain[] ): string { const wagmiParamsContent = userFullNativeConfig?.wagmiParams; const chainIds = chains.map((c) => c.id).sort(); const keyObject = { wagmiParams: wagmiParamsContent, chainIds: chainIds, }; const configHash = JSON.stringify(keyObject); logger.debug( 'rainbowkit/config-service:createConfigCacheKey', 'Key generation input - userFullNativeConfig.wagmiParams:', wagmiParamsContent, 'Key generation input - chainIds:', chainIds, 'Generated Key Object for stringify:', keyObject, 'Generated Hash:', configHash ); return configHash; } /** * Creates a Wagmi configuration for RainbowKit using getDefaultConfig * * @param userFullNativeConfig The full native configuration object. This object IS the result of merging * AppConfigService settings, user's native rainbowkit.config.ts, and programmatic overrides. * It is expected to contain a `wagmiParams` object for RainbowKit's getDefaultConfig * and potentially a `providerProps` object for RainbowKitProvider. * @param chains Array of viem Chain objects - will be safely cast to wagmi's expected chain type * @param chainIdToNetworkIdMap Mapping of chain IDs to network IDs for RPC override lookups * @param getRpcEndpointOverride Function to get RPC endpoint overrides * @returns Wagmi configuration for RainbowKit or null if creation fails */ export async function createRainbowKitWagmiConfig( userFullNativeConfig: Record<string, unknown> | undefined | null, // This is the fully resolved kitConfig chains: readonly Chain[], chainIdToNetworkIdMap: Record<number, string>, getRpcEndpointOverride: (networkId: string) => string | { http?: string; ws?: string } | undefined ): Promise<Config | null> { try { const cacheKey = createConfigCacheKey(userFullNativeConfig, chains); logger.info( 'rainbowkit/config-service:createRainbowKitWagmiConfig', 'Attempting to get/create config. Cache Key:', cacheKey, 'Using cache instance ID:', cacheInstanceId ); const cachedConfig = configCache.get(cacheKey); if (cachedConfig) { logger.info( 'rainbowkit/config-service:createRainbowKitWagmiConfig', 'CACHE HIT. Returning cached RainbowKit Wagmi config.', { cacheKey, cacheInstanceId, cachedConfigObjectId: typeof cachedConfig?.storage === 'object' && cachedConfig.storage !== null ? 'WagmiStorageObject' : String(cachedConfig?.storage), } ); return cachedConfig; } else { logger.warn( 'rainbowkit/config-service:createRainbowKitWagmiConfig', 'CACHE MISS. Proceeding to create new RainbowKit Wagmi config.', { cacheKey, cacheSize: configCache.size, cacheInstanceId } ); } const { getDefaultConfig } = await import('@rainbow-me/rainbowkit'); if (!getDefaultConfig) { logger.error( 'rainbowkit/config-service', 'Failed to import getDefaultConfig from RainbowKit' ); return null; } // userFullNativeConfig *is* the kitConfig here. Its wagmiParams are what we need. const wagmiParams = userFullNativeConfig?.wagmiParams as Record<string, unknown> | undefined; if (!wagmiParams) { logger.warn( 'rainbowkit/config-service', 'Resolved kitConfig does not contain a `wagmiParams` object. Cannot create RainbowKit Wagmi config.' ); return null; } // Ensure essential appName and projectId are present in user's wagmiParams if (typeof wagmiParams.appName !== 'string' || !wagmiParams.appName) { logger.warn( 'rainbowkit/config-service', 'kitConfig.wagmiParams is missing or has invalid `appName`.' ); return null; } if (typeof wagmiParams.projectId !== 'string' || !wagmiParams.projectId) { logger.warn( 'rainbowkit/config-service', 'kitConfig.wagmiParams is missing or has invalid `projectId`.' ); return null; } // Build transport configuration with RPC overrides as needed. // This iterates over the adapter-determined chains and creates a transport // for each, applying any RPC overrides specified via AppConfigService. const transportsConfig = chains.reduce( (acc, chainDefinition) => { let rpcUrlToUse: string | undefined = chainDefinition.rpcUrls.default?.http?.[0]; const appNetworkIdString = chainIdToNetworkIdMap[chainDefinition.id]; if (appNetworkIdString) { const rpcOverrideSetting = getRpcEndpointOverride(appNetworkIdString); let httpRpcOverride: string | undefined; if (typeof rpcOverrideSetting === 'string') { httpRpcOverride = rpcOverrideSetting; } else if (typeof rpcOverrideSetting === 'object') { // Handle both RpcEndpointConfig and UserRpcProviderConfig if (rpcOverrideSetting?.http) { httpRpcOverride = rpcOverrideSetting.http; } else if ('url' in rpcOverrideSetting && rpcOverrideSetting.url) { // Handle UserRpcProviderConfig httpRpcOverride = rpcOverrideSetting.url as string; } } if (httpRpcOverride) { logger.info( 'rainbowkit/config-service', `Using overridden RPC for chain ${chainDefinition.name}: ${httpRpcOverride}` ); rpcUrlToUse = httpRpcOverride; } } acc[chainDefinition.id] = http(rpcUrlToUse); return acc; }, {} as Record<number, ReturnType<typeof http>> ); // Spread all user-provided wagmiParams, then override chains and transports const finalConfigOptions = { ...wagmiParams, // User's native params (appName, projectId, wallets, ssr, etc.) chains: chains as WagmiConfigChains, // Adapter controls this transports: transportsConfig, // Adapter controls this }; // The `wallets` property from user config is of type `unknown` on our side if accessed directly from wagmiParams. // RainbowKit's `getDefaultConfig` expects a specific `WalletList` type for `wallets`. // By spreading `wagmiParams`, we pass whatever structure the user provided. // We use `eslint-disable-next-line @typescript-eslint/no-explicit-any` here to pass through // the user-provided structure directly, relying on RainbowKit to perform its own validation or type checking internally. // This aligns with our principle of not mimicking complex third-party types for pass-through configuration. // eslint-disable-next-line @typescript-eslint/no-explicit-any const config = getDefaultConfig(finalConfigOptions as any); configCache.set(cacheKey, config); logger.info( 'rainbowkit/config-service:createRainbowKitWagmiConfig', 'New config created and cached. Cache Size now:', configCache.size, { cacheKey, cacheInstanceId, } ); logger.info( 'rainbowkit/config-service', 'Successfully created RainbowKit Wagmi config object.', config ); return config; } catch (error) { logger.error('rainbowkit/config-service', 'Error creating RainbowKit Wagmi config:', error); return null; } } /** * Gets the Wagmi configuration for RainbowKit based on the global UI kit settings. * This function is intended to be called by WagmiWalletImplementation. * * @param uiKitConfiguration The UI kit configuration object from EvmUiKitManager (contains the resolved kitConfig). * @param chains Array of viem Chain objects to use with RainbowKit * @param chainIdToNetworkIdMap Mapping of chain IDs to network IDs for RPC override lookups * @param getRpcEndpointOverride Function to get RPC endpoint overrides * @returns The wagmi Config object or null if invalid or not RainbowKit */ export async function getWagmiConfigForRainbowKit( uiKitConfiguration: UiKitConfiguration | undefined, chains: readonly Chain[], chainIdToNetworkIdMap: Record<number, string>, getRpcEndpointOverride: (networkId: string) => string | { http?: string; ws?: string } | undefined ): Promise<Config | null> { if ( !uiKitConfiguration || uiKitConfiguration.kitName !== 'rainbowkit' || !uiKitConfiguration.kitConfig // kitConfig is the fully resolved config here ) { logger.debug( 'rainbowkit/config-service', 'Not configured for RainbowKit or kitConfig (resolved native + programmatic) is missing.' ); return null; } // userFullNativeConfig for createRainbowKitWagmiConfig IS uiKitConfiguration.kitConfig const resolvedKitConfig = uiKitConfiguration.kitConfig as Record<string, unknown>; return createRainbowKitWagmiConfig( resolvedKitConfig, // Pass the resolved kitConfig chains, chainIdToNetworkIdMap, getRpcEndpointOverride ); }