@openzeppelin/contracts-ui-builder-adapter-evm
Version:
EVM Adapter for Contracts UI Builder
1 lines • 430 kB
Source Map (JSON)
{"version":3,"sources":["../src/wallet/rainbowkit/rainbowkitAssetManager.ts","../src/index.ts","../src/adapter.ts","../src/abi/comparison.ts","../src/abi/types.ts","../src/types/providers.ts","../src/wallet/components/EvmWalletUiRoot.tsx","../src/wallet/context/wagmi-context.tsx","../src/wallet/evmUiKitManager.ts","../src/wallet/utils/walletImplementationManager.ts","../src/wallet/implementation/wagmi-implementation.ts","../src/networks/mainnet.ts","../src/networks/testnet.ts","../src/networks/index.ts","../src/wallet/rainbowkit/components.tsx","../src/wallet/components/connect/ConnectButton.tsx","../src/wallet/utils/SafeWagmiComponent.tsx","../src/wallet/hooks/useIsWagmiProviderInitialized.ts","../src/wallet/components/connect/ConnectorDialog.tsx","../src/wallet/hooks/useUiKitConfig.ts","../src/wallet/components/account/AccountDisplay.tsx","../src/wallet/components/network/NetworkSwitcher.tsx","../src/wallet/rainbowkit/types.ts","../src/wallet/rainbowkit/utils.ts","../src/wallet/rainbowkit/componentFactory.ts","../src/wallet/rainbowkit/config-service.ts","../src/wallet/hooks/facade-hooks.ts","../src/wallet/rainbowkit/config-generator.ts","../src/wallet/rainbowkit/export-service.ts","../src/wallet/services/configResolutionService.ts","../src/wallet/utils.ts","../src/wallet/utils/uiKitService.ts","../src/wallet/utils/filterWalletComponents.ts","../src/abi/transformer.ts","../src/types/artifacts.ts","../src/utils/artifacts.ts","../src/utils/json.ts","../src/utils/formatting.ts","../src/utils/validation.ts","../src/utils/gas.ts","../src/abi/etherscan.ts","../src/configuration/explorer.ts","../src/abi/etherscan-v2.ts","../src/abi/loader.ts","../src/proxy/detection.ts","../src/configuration/execution.ts","../src/validation/eoa.ts","../src/validation/relayer.ts","../src/configuration/rpc.ts","../src/abi/sourcify.ts","../src/mapping/constants.ts","../src/mapping/type-mapper.ts","../src/mapping/field-generator.ts","../src/query/view-checker.ts","../src/query/handler.ts","../src/transform/input-parser.ts","../src/transform/output-formatter.ts","../src/transaction/relayer.ts","../src/transaction/sender.ts","../src/transaction/formatter.ts","../src/transaction/eoa.ts","../src/transaction/components/EvmRelayerOptions.tsx","../src/transaction/components/AdvancedInfo.tsx","../src/transaction/components/CustomGasParameters.tsx","../src/transaction/components/SpeedSelection.tsx","../src/transaction/components/useEvmRelayerOptions.ts","../src/wallet/utils/connection.ts","../src/wallet/utils/wallet-status.ts","../src/config.ts"],"sourcesContent":["import type React from 'react';\n\nimport { logger } from '@openzeppelin/contracts-ui-builder-utils';\n\nexport interface RainbowKitAssets {\n ProviderComponent: React.ComponentType<React.PropsWithChildren<unknown>> | null;\n cssLoaded: boolean;\n}\n\nlet loadedAssets: RainbowKitAssets | null = null;\n\n// Promises to ensure assets are loaded only once\nlet providerPromise: Promise<React.ComponentType<React.PropsWithChildren<unknown>> | null> | null =\n null;\nlet cssPromise: Promise<boolean> | null = null;\n\n/**\n * Ensures RainbowKit provider component and CSS are loaded.\n * Loads them dynamically only once and caches the result.\n * @returns A promise resolving to an object containing the ProviderComponent and cssLoaded status.\n */\nexport async function ensureRainbowKitAssetsLoaded(): Promise<RainbowKitAssets> {\n if (loadedAssets) {\n logger.debug('RainbowKitAssetManager', 'Assets already loaded, returning cached.');\n return loadedAssets;\n }\n\n if (!providerPromise) {\n providerPromise = import('@rainbow-me/rainbowkit')\n .then((module) => {\n const component = module.RainbowKitProvider as React.ComponentType<\n React.PropsWithChildren<unknown>\n >;\n logger.info('RainbowKitAssetManager', 'RainbowKitProvider module loaded.');\n return component;\n })\n .catch((err) => {\n logger.error('RainbowKitAssetManager', 'Failed to load RainbowKitProvider module:', err);\n return null; // Resolve with null on error to allow Promise.all to complete\n });\n }\n\n if (!cssPromise) {\n cssPromise = import('@rainbow-me/rainbowkit/styles.css')\n .then(() => {\n logger.info('RainbowKitAssetManager', 'RainbowKit CSS loaded successfully.');\n return true;\n })\n .catch((err) => {\n logger.error('RainbowKitAssetManager', 'Failed to load RainbowKit CSS:', err);\n return false; // Resolve with false on error\n });\n }\n\n try {\n const [ProviderComponent, cssLoadedSuccess] = await Promise.all([providerPromise, cssPromise]);\n\n loadedAssets = { ProviderComponent, cssLoaded: cssLoadedSuccess };\n if (!ProviderComponent || !cssLoadedSuccess) {\n logger.warn(\n 'RainbowKitAssetManager',\n 'One or more RainbowKit assets failed to load.',\n loadedAssets\n );\n // Potentially throw here if assets are critical, or let caller decide based on null/false\n }\n return loadedAssets;\n } catch (error) {\n // This catch is for Promise.all failing, though individual catches should handle module errors.\n logger.error('RainbowKitAssetManager', 'Error in Promise.all for asset loading:', error);\n loadedAssets = { ProviderComponent: null, cssLoaded: false }; // Ensure loadedAssets is set\n return loadedAssets;\n }\n}\n","// Re-export the main adapter class\nexport { EvmAdapter } from './adapter';\n\n// Export RainbowKit customization types\nexport * from './wallet/rainbowkit/types';\n\n// Optionally re-export types if they need to be accessible directly\n// export * from './types';\n\nexport {\n evmNetworks,\n evmMainnetNetworks,\n evmTestnetNetworks,\n // Individual networks\n ethereumMainnet,\n arbitrumMainnet,\n polygonMainnet,\n polygonZkEvmMainnet,\n baseMainnet,\n bscMainnet,\n optimismMainnet,\n avalancheMainnet,\n lineaMainnet,\n scrollMainnet,\n zkSyncEraMainnet,\n ethereumSepolia,\n arbitrumSepolia,\n polygonAmoy,\n polygonZkEvmCardona,\n baseSepolia,\n bscTestnet,\n optimismSepolia,\n avalancheFuji,\n lineaSepolia,\n scrollSepolia,\n zksyncSepoliaTestnet,\n // ... other individual network exports\n} from './networks';\n\n// Export adapter configuration\nexport { evmAdapterConfig } from './config';\n\nexport type { TypedEvmNetworkConfig, WriteContractParameters } from './types';\nexport type { EvmRelayerTransactionOptions } from './transaction/relayer';\n\n// Re-export adapter-specific types\nexport type { EvmContractArtifacts } from './types/artifacts';\nexport { isEvmContractArtifacts } from './types/artifacts';\n\n// Export abi module for comparison functionality\nexport { abiComparisonService } from './abi';\n","import { type TransactionReceipt } from 'viem';\nimport React from 'react';\n\nimport type {\n AvailableUiKit,\n Connector,\n ContractAdapter,\n ContractFunction,\n ContractSchema,\n EcosystemReactUiProviderProps,\n EcosystemSpecificReactHooks,\n EcosystemWalletComponents,\n ExecutionConfig,\n ExecutionMethodDetail,\n FieldType,\n FormFieldType,\n FunctionParameter,\n NativeConfigLoader,\n NetworkConfig,\n ProxyInfo,\n RelayerDetails,\n RelayerDetailsRich,\n TransactionStatusUpdate,\n TxStatus,\n UiKitConfiguration,\n UserExplorerConfig,\n UserRpcProviderConfig,\n WalletConnectionStatus,\n} from '@openzeppelin/contracts-ui-builder-types';\nimport { logger } from '@openzeppelin/contracts-ui-builder-utils';\n\nimport { abiComparisonService } from './abi/comparison';\nimport { EvmProviderKeys, type EvmContractDefinitionProviderKey } from './types/providers';\nimport { EvmWalletUiRoot } from './wallet/components/EvmWalletUiRoot';\nimport { evmUiKitManager } from './wallet/evmUiKitManager';\nimport { evmFacadeHooks } from './wallet/hooks/facade-hooks';\nimport { loadInitialConfigFromAppService } from './wallet/hooks/useUiKitConfig';\nimport { generateRainbowKitConfigFile } from './wallet/rainbowkit/config-generator';\nimport { generateRainbowKitExportables } from './wallet/rainbowkit/export-service';\nimport { resolveFullUiKitConfiguration } from './wallet/services/configResolutionService';\n\nimport { loadEvmContract } from './abi';\nimport {\n getEvmExplorerAddressUrl,\n getEvmExplorerTxUrl,\n getEvmSupportedExecutionMethods,\n testEvmExplorerConnection,\n testEvmRpcConnection,\n validateEvmExecutionConfig,\n validateEvmExplorerConfig,\n validateEvmRpcEndpoint,\n} from './configuration';\nimport {\n generateEvmDefaultField,\n getEvmCompatibleFieldTypes,\n mapEvmParamTypeToFieldType,\n} from './mapping';\nimport { isEvmViewFunction, queryEvmViewFunction } from './query';\nimport {\n EoaExecutionStrategy,\n EvmRelayerOptions,\n ExecutionStrategy,\n formatEvmTransactionData,\n RelayerExecutionStrategy,\n waitForEvmTransactionConfirmation,\n} from './transaction';\nimport { formatEvmFunctionResult } from './transform';\nimport { TypedEvmNetworkConfig } from './types';\nimport type { WriteContractParameters } from './types';\nimport { isValidEvmAddress, validateAndConvertEvmArtifacts } from './utils';\nimport {\n connectAndEnsureCorrectNetwork,\n convertWagmiToEvmStatus,\n disconnectEvmWallet,\n evmSupportsWalletConnection,\n EvmWalletConnectionStatus,\n getEvmAvailableConnectors,\n getEvmWalletConnectionStatus,\n getEvmWalletImplementation,\n getInitializedEvmWalletImplementation,\n getResolvedWalletComponents,\n} from './wallet';\n\n/**\n * Type guard to check if a network config is a TypedEvmNetworkConfig\n * @param config The network configuration to check\n * @returns True if the config is for EVM\n */\nconst isTypedEvmNetworkConfig = (config: NetworkConfig): config is TypedEvmNetworkConfig =>\n config.ecosystem === 'evm';\n\n/**\n * EVM-specific adapter implementation\n */\nexport class EvmAdapter implements ContractAdapter {\n readonly networkConfig: TypedEvmNetworkConfig;\n readonly initialAppServiceKitName: UiKitConfiguration['kitName'];\n\n constructor(networkConfig: TypedEvmNetworkConfig) {\n if (!isTypedEvmNetworkConfig(networkConfig)) {\n throw new Error('EvmAdapter requires a valid EVM network configuration.');\n }\n this.networkConfig = networkConfig;\n logger.info(\n 'EvmAdapter',\n `Adapter initialized for network: ${networkConfig.name} (ID: ${networkConfig.id})`\n );\n\n // Determine the initial kitName from AppConfigService at the time of adapter construction.\n // This provides a baseline kitName preference from the application's static/global configuration.\n // It defaults to 'custom' if no specific kitName is found in AppConfigService.\n // This value is stored on the instance to inform the first call to configureUiKit if no programmatic overrides are given.\n const initialGlobalConfig = loadInitialConfigFromAppService();\n this.initialAppServiceKitName =\n (initialGlobalConfig.kitName as UiKitConfiguration['kitName']) || 'custom';\n\n logger.info(\n 'EvmAdapter:constructor',\n 'Initial kitName from AppConfigService noted:',\n this.initialAppServiceKitName\n );\n // The actual EvmUiKitManager.configure call (which drives UI setup) is deferred.\n // It's typically triggered by WalletStateProvider after this adapter instance is fully initialized and provided to it,\n // ensuring that loadConfigModule (for user native configs) is available.\n }\n\n /**\n * @inheritdoc\n */\n public async loadContract(source: string | Record<string, unknown>): Promise<ContractSchema> {\n // Convert generic input to EVM-specific artifacts\n const artifacts = validateAndConvertEvmArtifacts(source);\n const result = await loadEvmContract(artifacts, this.networkConfig);\n return result.schema;\n }\n\n /**\n * @inheritdoc\n */\n public async loadContractWithMetadata(source: string | Record<string, unknown>): Promise<{\n schema: ContractSchema;\n source: 'fetched' | 'manual';\n contractDefinitionOriginal?: string;\n metadata?: {\n fetchedFrom?: string;\n contractName?: string;\n verificationStatus?: 'verified' | 'unverified' | 'unknown';\n fetchTimestamp?: Date;\n definitionHash?: string;\n };\n proxyInfo?: ProxyInfo;\n }> {\n try {\n // Convert generic input to EVM-specific artifacts\n const artifacts = validateAndConvertEvmArtifacts(source);\n const result = await loadEvmContract(artifacts, this.networkConfig);\n\n return {\n schema: result.schema,\n source: result.source,\n contractDefinitionOriginal: result.contractDefinitionOriginal,\n metadata: result.metadata,\n proxyInfo: result.proxyInfo,\n };\n } catch (error) {\n // Check if this is an unverified contract error\n const errorMessage = (error as Error).message || '';\n if (errorMessage.includes('not verified on the block explorer')) {\n // For unverified contracts, we still need to throw but include metadata\n // The UI will handle this error and can extract verification status from the message\n throw error;\n }\n\n // Re-throw other errors\n throw error;\n }\n }\n\n /**\n * @inheritdoc\n */\n mapParameterTypeToFieldType(parameterType: string): FieldType {\n return mapEvmParamTypeToFieldType(parameterType);\n }\n\n /**\n * @inheritdoc\n */\n getCompatibleFieldTypes(parameterType: string): FieldType[] {\n return getEvmCompatibleFieldTypes(parameterType);\n }\n\n /**\n * @inheritdoc\n */\n generateDefaultField<T extends FieldType = FieldType>(\n parameter: FunctionParameter\n ): FormFieldType<T> {\n return generateEvmDefaultField(parameter);\n }\n\n /**\n * @inheritdoc\n */\n public formatTransactionData(\n contractSchema: ContractSchema,\n functionId: string,\n submittedInputs: Record<string, unknown>,\n fields: FormFieldType[]\n ): WriteContractParameters {\n return formatEvmTransactionData(contractSchema, functionId, submittedInputs, fields);\n }\n\n /**\n * @inheritdoc\n */\n public async signAndBroadcast(\n transactionData: unknown,\n executionConfig: ExecutionConfig,\n onStatusChange: (status: TxStatus, details: TransactionStatusUpdate) => void,\n runtimeApiKey?: string\n ): Promise<{ txHash: string }> {\n const walletImplementation = await getEvmWalletImplementation();\n let strategy: ExecutionStrategy;\n\n switch (executionConfig.method) {\n case 'relayer':\n strategy = new RelayerExecutionStrategy();\n break;\n case 'eoa':\n default:\n strategy = new EoaExecutionStrategy();\n break;\n }\n\n return strategy.execute(\n transactionData as WriteContractParameters,\n executionConfig,\n walletImplementation,\n onStatusChange,\n runtimeApiKey\n );\n }\n\n /**\n * @inheritdoc\n */\n public async getRelayers(serviceUrl: string, accessToken: string): Promise<RelayerDetails[]> {\n const relayerStrategy = new RelayerExecutionStrategy();\n return relayerStrategy.getEvmRelayers(serviceUrl, accessToken, this.networkConfig);\n }\n\n /**\n * @inheritdoc\n */\n public async getRelayer(\n serviceUrl: string,\n accessToken: string,\n relayerId: string\n ): Promise<RelayerDetailsRich> {\n const relayerStrategy = new RelayerExecutionStrategy();\n return relayerStrategy.getEvmRelayer(serviceUrl, accessToken, relayerId, this.networkConfig);\n }\n\n /**\n * Returns a React component for configuring EVM-specific relayer transaction options.\n * @returns The EVM relayer options component\n */\n public getRelayerOptionsComponent():\n | React.ComponentType<{\n options: Record<string, unknown>;\n onChange: (options: Record<string, unknown>) => void;\n }>\n | undefined {\n return EvmRelayerOptions;\n }\n\n /**\n * @inheritdoc\n */\n public getWritableFunctions(contractSchema: ContractSchema): ContractSchema['functions'] {\n return contractSchema.functions.filter((fn) => fn.modifiesState);\n }\n\n /**\n * @inheritdoc\n */\n isValidAddress(address: string, _addressType?: string): boolean {\n // TODO: Could support ENS names as a different addressType (e.g., 'ens')\n // Viem provides normalize() and isAddress() functions that could validate ENS names\n // Example: addressType === 'ens' ? isValidEnsName(address) : isValidEvmAddress(address)\n\n // Currently, EVM treats all addresses uniformly (hex format)\n // The addressType parameter is ignored for backward compatibility with other chains\n return isValidEvmAddress(address);\n }\n\n /**\n * @inheritdoc\n */\n public async getSupportedExecutionMethods(): Promise<ExecutionMethodDetail[]> {\n return getEvmSupportedExecutionMethods();\n }\n\n /**\n * @inheritdoc\n */\n public async validateExecutionConfig(config: ExecutionConfig): Promise<true | string> {\n const walletStatus = this.getWalletConnectionStatus();\n return validateEvmExecutionConfig(config, walletStatus);\n }\n\n /**\n * @inheritdoc\n */\n isViewFunction(functionDetails: ContractFunction): boolean {\n return isEvmViewFunction(functionDetails);\n }\n\n /**\n * @inheritdoc\n */\n public filterAutoQueryableFunctions(functions: ContractFunction[]): ContractFunction[] {\n // Exclude admin/upgrade management getters that often revert or require permissions\n const skipNames = new Set([\n 'admin',\n 'implementation',\n 'getImplementation',\n '_implementation',\n 'proxyAdmin',\n 'changeAdmin',\n 'upgradeTo',\n 'upgradeToAndCall',\n ]);\n return functions.filter((f) => !skipNames.has(f.name));\n }\n\n /**\n * @inheritdoc\n */\n async queryViewFunction(\n contractAddress: string,\n functionId: string,\n params: unknown[] = [],\n contractSchema?: ContractSchema\n ): Promise<unknown> {\n const walletImplementation = await getEvmWalletImplementation();\n return queryEvmViewFunction(\n contractAddress,\n functionId,\n this.networkConfig,\n params,\n contractSchema,\n walletImplementation,\n (src) => this.loadContract({ contractAddress: src })\n );\n }\n\n /**\n * @inheritdoc\n */\n formatFunctionResult(decodedValue: unknown, functionDetails: ContractFunction): string {\n return formatEvmFunctionResult(decodedValue, functionDetails);\n }\n\n /**\n * @inheritdoc\n */\n public supportsWalletConnection(): boolean {\n return evmSupportsWalletConnection();\n }\n\n /**\n * @inheritdoc\n */\n public async getAvailableConnectors(): Promise<Connector[]> {\n return getEvmAvailableConnectors();\n }\n\n /**\n * @inheritdoc\n */\n public async connectWallet(\n connectorId: string\n ): Promise<{ connected: boolean; address?: string; error?: string }> {\n const result = await connectAndEnsureCorrectNetwork(connectorId, this.networkConfig.chainId);\n\n if (result.connected && result.address) {\n return { connected: true, address: result.address };\n } else {\n return {\n connected: false,\n error: result.error || 'Connection failed for an unknown reason.',\n };\n }\n }\n\n /**\n * @inheritdoc\n */\n public async disconnectWallet(): Promise<{ disconnected: boolean; error?: string }> {\n return disconnectEvmWallet();\n }\n\n /**\n * @inheritdoc\n */\n public getWalletConnectionStatus(): EvmWalletConnectionStatus {\n const status = getEvmWalletConnectionStatus();\n return convertWagmiToEvmStatus(status);\n }\n\n /**\n * @inheritdoc\n */\n public onWalletConnectionChange(\n callback: (\n currentStatus: WalletConnectionStatus,\n previousStatus: WalletConnectionStatus\n ) => void\n ): () => void {\n const walletImplementation = getInitializedEvmWalletImplementation();\n if (!walletImplementation) {\n logger.warn(\n 'EvmAdapter:onWalletConnectionChange',\n 'Wallet implementation not ready. Subscription may not work.'\n );\n return () => {};\n }\n return walletImplementation.onWalletConnectionChange(\n (currentWagmiStatus, previousWagmiStatus) => {\n // Convert wagmi's GetAccountReturnType to the rich adapter interface\n // This preserves all the enhanced UX capabilities from wagmi\n const current = convertWagmiToEvmStatus(currentWagmiStatus);\n const previous = convertWagmiToEvmStatus(previousWagmiStatus);\n\n callback(current, previous);\n }\n );\n }\n\n /**\n * @inheritdoc\n */\n getExplorerUrl(address: string): string | null {\n return getEvmExplorerAddressUrl(address, this.networkConfig);\n }\n\n /**\n * @inheritdoc\n */\n getExplorerTxUrl?(txHash: string): string | null {\n if (getEvmExplorerTxUrl) {\n return getEvmExplorerTxUrl(txHash, this.networkConfig);\n }\n return null;\n }\n\n /**\n * @inheritdoc\n */\n async waitForTransactionConfirmation(txHash: string): Promise<{\n status: 'success' | 'error';\n receipt?: TransactionReceipt;\n error?: Error;\n }> {\n const walletImplementation = await getEvmWalletImplementation();\n return waitForEvmTransactionConfirmation(txHash, walletImplementation);\n }\n\n /**\n * @inheritdoc\n */\n public async configureUiKit(\n programmaticOverrides: Partial<UiKitConfiguration> = {},\n options?: {\n loadUiKitNativeConfig?: NativeConfigLoader;\n }\n ): Promise<void> {\n const currentAppServiceConfig = loadInitialConfigFromAppService();\n\n // Delegate the entire configuration resolution to the service function.\n const finalFullConfig = await resolveFullUiKitConfiguration(\n programmaticOverrides,\n this.initialAppServiceKitName,\n currentAppServiceConfig,\n options\n );\n\n // Delegate the application of this configuration to the central EvmUiKitManager.\n await evmUiKitManager.configure(finalFullConfig);\n logger.info(\n 'EvmAdapter:configureUiKit',\n 'EvmUiKitManager configuration requested with final config:',\n finalFullConfig\n );\n }\n\n /**\n * @inheritdoc\n */\n public getEcosystemReactUiContextProvider():\n | React.ComponentType<EcosystemReactUiProviderProps>\n | undefined {\n // EvmWalletUiRoot is now the stable provider that subscribes to evmUiKitManager\n logger.info('EvmAdapter:getEcosystemReactUiContextProvider', 'Returning EvmWalletUiRoot.');\n return EvmWalletUiRoot;\n }\n\n /**\n * @inheritdoc\n */\n public getEcosystemReactHooks(): EcosystemSpecificReactHooks | undefined {\n // Always provide hooks for EVM adapter regardless of UI kit\n return evmFacadeHooks;\n }\n\n /**\n * @inheritdoc\n */\n public getSupportedContractDefinitionProviders(): Array<{\n key: EvmContractDefinitionProviderKey;\n label?: string;\n }> {\n return [\n { key: EvmProviderKeys.Etherscan, label: 'Etherscan' },\n { key: EvmProviderKeys.Sourcify, label: 'Sourcify' },\n ];\n }\n\n /**\n * @inheritdoc\n */\n public getEcosystemWalletComponents(): EcosystemWalletComponents | undefined {\n const currentManagerState = evmUiKitManager.getState();\n // Only attempt to resolve components if the manager has a configuration set.\n // During initial app load, currentFullUiKitConfig might be null until the first configure call completes.\n if (!currentManagerState.currentFullUiKitConfig) {\n logger.debug(\n // Changed from warn to debug, as this can be normal during init sequence\n 'EvmAdapter:getEcosystemWalletComponents',\n 'No UI kit configuration available in manager yet. Returning undefined components.'\n );\n return undefined; // Explicitly return undefined if manager isn't configured yet\n }\n // If manager has a config, use that for resolving components.\n return getResolvedWalletComponents(currentManagerState.currentFullUiKitConfig);\n }\n\n public async getAvailableUiKits(): Promise<AvailableUiKit[]> {\n const rainbowkitDefaultCode = generateRainbowKitConfigFile({});\n\n return [\n {\n id: 'custom',\n name: 'Wagmi Custom',\n configFields: [],\n },\n {\n id: 'rainbowkit',\n name: 'RainbowKit',\n linkToDocs: 'https://www.rainbowkit.com/docs/installation#configure',\n description: `Configure RainbowKit for your exported application. This code will be saved as <code class=\"bg-muted px-1 py-0.5 rounded text-xs\">rainbowkit.config.ts</code>.<br/><br/>\n<strong>Export Only:</strong> This configuration is <em>only used in exported apps</em>. The preview always uses the default RainbowKit configuration.<br/><br/>\n<strong>Available options:</strong><br/>\n• <code>wagmiParams</code>: Configure app name, projectId, wallets, etc.<br/>\n• <code>providerProps</code>: Set theme, modal size, and other UI options<br/><br/>\nGet your WalletConnect projectId from <a href=\"https://cloud.walletconnect.com\" target=\"_blank\" rel=\"noopener\" class=\"text-primary underline\">cloud.walletconnect.com</a>`,\n hasCodeEditor: true,\n defaultCode: rainbowkitDefaultCode,\n configFields: [],\n },\n ];\n }\n\n public async getExportableWalletConfigFiles(\n uiKitConfig?: UiKitConfiguration\n ): Promise<Record<string, string>> {\n if (uiKitConfig?.kitName === 'rainbowkit') {\n return generateRainbowKitExportables(uiKitConfig);\n }\n return {};\n }\n\n /**\n * @inheritdoc\n */\n public getUiLabels(): Record<string, string> | undefined {\n return {\n relayerConfigTitle: 'Gas Configuration',\n relayerConfigActiveDesc: 'Customize gas pricing strategy for transaction submission',\n relayerConfigInactiveDesc: 'Using recommended gas configuration for reliable transactions',\n relayerConfigPresetTitle: 'Fast Speed Preset Active',\n relayerConfigPresetDesc:\n 'Transactions will use high priority gas pricing for quick inclusion',\n relayerConfigCustomizeBtn: 'Customize Gas Settings',\n detailsTitle: 'Relayer Details',\n network: 'Network',\n relayerId: 'Relayer ID',\n active: 'Active',\n paused: 'Paused',\n systemDisabled: 'System Disabled',\n balance: 'Balance',\n nonce: 'Nonce',\n pending: 'Pending Transactions',\n lastTransaction: 'Last Transaction',\n };\n }\n\n /**\n * @inheritdoc\n */\n public getContractDefinitionInputs(): FormFieldType[] {\n return [\n {\n id: 'contractAddress',\n name: 'contractAddress',\n label: 'Contract Address',\n type: 'blockchain-address',\n validation: { required: true },\n placeholder: '0x1234...abcd',\n helperText:\n 'Enter the deployed contract address. For verified contracts, the ABI will be fetched automatically from the block explorer.',\n },\n {\n id: 'contractDefinition',\n name: 'contractDefinition',\n label: 'Contract ABI (Optional)',\n type: 'code-editor',\n validation: { required: false },\n placeholder:\n '[{\"inputs\":[],\"name\":\"myFunction\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]',\n helperText:\n \"If the contract is not verified on the block explorer, paste the contract's ABI JSON here. You can find this in your contract's compilation artifacts or deployment files.\",\n codeEditorProps: {\n language: 'json',\n placeholder: 'Paste your contract ABI JSON here...',\n maxHeight: '500px',\n performanceThreshold: 3000, // Disable syntax highlighting for large ABIs\n },\n },\n ];\n }\n\n /**\n * @inheritdoc\n */\n public async validateRpcEndpoint(rpcConfig: UserRpcProviderConfig): Promise<boolean> {\n return validateEvmRpcEndpoint(rpcConfig);\n }\n\n /**\n * @inheritdoc\n */\n public async testRpcConnection(rpcConfig: UserRpcProviderConfig): Promise<{\n success: boolean;\n latency?: number;\n error?: string;\n }> {\n return testEvmRpcConnection(rpcConfig);\n }\n\n /**\n * @inheritdoc\n */\n public async validateExplorerConfig(explorerConfig: UserExplorerConfig): Promise<boolean> {\n return validateEvmExplorerConfig(explorerConfig);\n }\n\n /**\n * @inheritdoc\n */\n public async testExplorerConnection(explorerConfig: UserExplorerConfig): Promise<{\n success: boolean;\n latency?: number;\n error?: string;\n }> {\n return testEvmExplorerConnection(explorerConfig, this.networkConfig);\n }\n\n /**\n * @inheritdoc\n */\n public async compareContractDefinitions(\n storedSchema: string,\n freshSchema: string\n ): Promise<{\n identical: boolean;\n differences: Array<{\n type: 'added' | 'removed' | 'modified';\n section: string;\n name: string;\n details: string;\n impact: 'low' | 'medium' | 'high';\n oldSignature?: string;\n newSignature?: string;\n }>;\n severity: 'none' | 'minor' | 'major' | 'breaking';\n summary: string;\n }> {\n try {\n const result = abiComparisonService.compareAbis(storedSchema, freshSchema);\n return {\n identical: result.identical,\n differences: result.differences.map((diff) => ({\n type: diff.type,\n section: diff.section,\n name: diff.name,\n details: diff.details,\n impact: diff.impact,\n oldSignature: diff.oldSignature,\n newSignature: diff.newSignature,\n })),\n severity: result.severity,\n summary: result.summary,\n };\n } catch (error) {\n logger.error('EVM contract definition comparison failed:', (error as Error).message);\n throw new Error(`Contract definition comparison failed: ${(error as Error).message}`);\n }\n }\n\n /**\n * @inheritdoc\n */\n public validateContractDefinition(definition: string): {\n valid: boolean;\n errors: string[];\n warnings: string[];\n } {\n const result = abiComparisonService.validateAbi(definition);\n return {\n valid: result.valid,\n errors: result.errors,\n warnings: result.warnings,\n };\n }\n\n /**\n * @inheritdoc\n */\n public hashContractDefinition(definition: string): string {\n return abiComparisonService.hashAbi(definition);\n }\n}\n\n// Also export as default to ensure compatibility with various import styles\nexport default EvmAdapter;\n","/**\n * ABI comparison service for EVM contracts\n * Provides detailed analysis of differences between ABIs\n */\n\nimport type { Abi } from 'viem';\n\nimport { logger, simpleHash } from '@openzeppelin/contracts-ui-builder-utils';\n\nimport type { AbiComparisonResult, AbiDifference, AbiValidationResult } from './types';\nimport { isValidAbiArray } from './types';\n\n/**\n * Service for comparing and validating EVM ABIs\n */\nexport class AbiComparisonService {\n /**\n * Compares two ABIs and returns detailed difference analysis\n */\n public compareAbis(abi1: string, abi2: string): AbiComparisonResult {\n try {\n const validation1 = this.validateAbi(abi1);\n const validation2 = this.validateAbi(abi2);\n\n if (!validation1.valid || !validation2.valid) {\n return {\n identical: false,\n differences: [],\n severity: 'breaking',\n summary: 'One or both ABIs are invalid and cannot be compared',\n };\n }\n\n const normalized1 = this.normalizeAbi(validation1.normalizedAbi!);\n const normalized2 = this.normalizeAbi(validation2.normalizedAbi!);\n\n const hash1 = simpleHash(JSON.stringify(normalized1));\n const hash2 = simpleHash(JSON.stringify(normalized2));\n\n if (hash1 === hash2) {\n return {\n identical: true,\n differences: [],\n severity: 'none',\n summary: 'ABIs are identical',\n };\n }\n\n const differences = this.findDifferences(normalized1, normalized2);\n const severity = this.calculateSeverity(differences);\n\n return {\n identical: false,\n differences,\n severity,\n summary: this.generateSummary(differences),\n };\n } catch (error) {\n logger.error('ABI comparison failed:', (error as Error).message);\n return {\n identical: false,\n differences: [],\n severity: 'breaking',\n summary: `Comparison failed: ${(error as Error).message}`,\n };\n }\n }\n\n /**\n * Validates ABI structure and format\n */\n public validateAbi(abiString: string): AbiValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n try {\n // Parse JSON\n const abi = JSON.parse(abiString);\n\n if (!Array.isArray(abi)) {\n errors.push('ABI must be an array');\n return { valid: false, errors, warnings };\n }\n\n // Empty ABI arrays are not valid contract definitions\n if (abi.length === 0) {\n errors.push(\n 'ABI array cannot be empty - contract must have at least one function, event, or constructor'\n );\n return { valid: false, errors, warnings };\n }\n\n // Validate each ABI item\n for (let i = 0; i < abi.length; i++) {\n const item = abi[i];\n\n if (!item.type) {\n errors.push(`Item ${i}: Missing 'type' field`);\n continue;\n }\n\n if (\n !['function', 'event', 'constructor', 'error', 'fallback', 'receive'].includes(item.type)\n ) {\n errors.push(`Item ${i}: Invalid type '${item.type}'`);\n }\n\n if (item.type === 'function' && !item.name) {\n errors.push(`Item ${i}: Function missing 'name' field`);\n }\n\n if ((item.type === 'function' || item.type === 'event') && !Array.isArray(item.inputs)) {\n errors.push(`Item ${i}: Missing or invalid 'inputs' array`);\n }\n\n if (item.type === 'function' && !Array.isArray(item.outputs)) {\n warnings.push(`Item ${i}: Function missing 'outputs' array`);\n }\n }\n\n // Additional viem-specific validation\n if (errors.length === 0 && !isValidAbiArray(abi)) {\n errors.push('ABI does not conform to expected format');\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n normalizedAbi: errors.length === 0 ? (abi as Abi) : undefined,\n };\n } catch (parseError) {\n errors.push(`Invalid JSON: ${(parseError as Error).message}`);\n return { valid: false, errors, warnings };\n }\n }\n\n /**\n * Creates deterministic hash of ABI for quick comparison\n */\n public hashAbi(abiString: string): string {\n try {\n const validation = this.validateAbi(abiString);\n if (!validation.valid || !validation.normalizedAbi) {\n throw new Error('Cannot hash invalid ABI');\n }\n\n const normalized = this.normalizeAbi(validation.normalizedAbi);\n const normalizedString = JSON.stringify(normalized);\n\n return simpleHash(normalizedString);\n } catch (error) {\n logger.error('ABI hashing failed:', (error as Error).message);\n throw new Error(`Failed to hash ABI: ${(error as Error).message}`);\n }\n }\n\n /**\n * Normalizes ABI for consistent comparison\n */\n private normalizeAbi(abi: Abi): Abi {\n return abi\n .map((item) => {\n // Remove ordering-dependent fields and sort inputs/outputs\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const normalized: any = { ...item };\n\n if (normalized.inputs) {\n normalized.inputs = [...normalized.inputs].sort((a, b) =>\n (a.name || '').localeCompare(b.name || '')\n );\n }\n\n if (normalized.outputs) {\n normalized.outputs = [...normalized.outputs].sort((a, b) =>\n (a.name || '').localeCompare(b.name || '')\n );\n }\n\n return normalized;\n })\n .sort((a, b) => {\n // Sort by type first, then by name\n const typeOrder = {\n constructor: 0,\n fallback: 1,\n receive: 2,\n function: 3,\n event: 4,\n error: 5,\n };\n const aOrder = typeOrder[a.type as keyof typeof typeOrder] ?? 99;\n const bOrder = typeOrder[b.type as keyof typeof typeOrder] ?? 99;\n\n if (aOrder !== bOrder) return aOrder - bOrder;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const aName = (a as any).name || '';\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const bName = (b as any).name || '';\n return aName.localeCompare(bName);\n }) as Abi;\n }\n\n /**\n * Finds detailed differences between two normalized ABIs\n */\n private findDifferences(abi1: Abi, abi2: Abi): AbiDifference[] {\n const differences: AbiDifference[] = [];\n\n // Create maps for efficient lookup\n const map1 = this.createAbiMap(abi1);\n const map2 = this.createAbiMap(abi2);\n\n // Find removed items\n for (const [key, item] of map1) {\n if (!map2.has(key)) {\n differences.push({\n type: 'removed',\n section: item.type as AbiDifference['section'],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n name: (item as any).name || item.type,\n details: `${item.type} was removed`,\n impact: this.calculateImpact(item.type, 'removed'),\n oldSignature: this.generateSignature(item),\n });\n }\n }\n\n // Find added items\n for (const [key, item] of map2) {\n if (!map1.has(key)) {\n differences.push({\n type: 'added',\n section: item.type as AbiDifference['section'],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n name: (item as any).name || item.type,\n details: `${item.type} was added`,\n impact: this.calculateImpact(item.type, 'added'),\n newSignature: this.generateSignature(item),\n });\n }\n }\n\n // Find modified items\n for (const [key, item1] of map1) {\n const item2 = map2.get(key);\n if (item2 && !this.itemsEqual(item1, item2)) {\n differences.push({\n type: 'modified',\n section: item1.type as AbiDifference['section'],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n name: (item1 as any).name || item1.type,\n details: `${item1.type} signature changed`,\n impact: this.calculateImpact(item1.type, 'modified'),\n oldSignature: this.generateSignature(item1),\n newSignature: this.generateSignature(item2),\n });\n }\n }\n\n return differences;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private createAbiMap(abi: Abi): Map<string, any> {\n const map = new Map();\n\n for (const item of abi) {\n const key = this.generateItemKey(item);\n map.set(key, item);\n }\n\n return map;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private generateItemKey(item: any): string {\n if (item.type === 'constructor' || item.type === 'fallback' || item.type === 'receive') {\n return item.type;\n }\n\n const name = item.name || '';\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const inputs = item.inputs?.map((input: any) => input.type).join(',') || '';\n return `${item.type}:${name}(${inputs})`;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private generateSignature(item: any): string {\n if (item.type === 'constructor') {\n const inputs =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n item.inputs?.map((input: any) => `${input.type} ${input.name || ''}`).join(', ') || '';\n return `constructor(${inputs})`;\n }\n\n if (item.type === 'fallback' || item.type === 'receive') {\n return item.type + '()';\n }\n\n if (item.type === 'function') {\n const inputs =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n item.inputs?.map((input: any) => `${input.type} ${input.name || ''}`).join(', ') || '';\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const outputs = item.outputs?.map((output: any) => output.type).join(', ') || '';\n const mutability = item.stateMutability ? ` ${item.stateMutability}` : '';\n return `function ${item.name}(${inputs})${mutability}${outputs ? ` returns (${outputs})` : ''}`;\n }\n\n if (item.type === 'event') {\n const inputs =\n item.inputs\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ?.map((input: any) => {\n const indexed = input.indexed ? ' indexed' : '';\n return `${input.type}${indexed} ${input.name || ''}`;\n })\n .join(', ') || '';\n return `event ${item.name}(${inputs})`;\n }\n\n return JSON.stringify(item);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private itemsEqual(item1: any, item2: any): boolean {\n return JSON.stringify(item1) === JSON.stringify(item2);\n }\n\n private calculateImpact(\n type: string,\n changeType: 'added' | 'removed' | 'modified'\n ): 'low' | 'medium' | 'high' {\n if (type === 'constructor' || type === 'fallback' || type === 'receive') {\n return changeType === 'modified' ? 'high' : 'medium';\n }\n\n if (type === 'function') {\n if (changeType === 'removed') return 'high'; // Breaking: removes functionality\n if (changeType === 'modified') return 'medium'; // Major: changes behavior\n if (changeType === 'added') return 'low'; // Minor: adds functionality\n }\n\n if (type === 'event') {\n return 'low'; // Events don't break existing functionality\n }\n\n if (type === 'error') {\n return 'low'; // Custom errors don't break existing functionality\n }\n\n return 'medium';\n }\n\n private calculateSeverity(differences: AbiDifference[]): 'none' | 'minor' | 'major' | 'breaking' {\n if (differences.length === 0) return 'none';\n\n const hasHighImpact = differences.some((d) => d.impact === 'high');\n const hasMediumImpact = differences.some((d) => d.impact === 'medium');\n const hasRemovedFunctions = differences.some(\n (d) => d.type === 'removed' && d.section === 'function'\n );\n\n if (hasRemovedFunctions || hasHighImpact) return 'breaking';\n if (hasMediumImpact) return 'major';\n return 'minor';\n }\n\n private generateSummary(differences: AbiDifference[]): string {\n const counts = {\n added: differences.filter((d) => d.type === 'added').length,\n removed: differences.filter((d) => d.type === 'removed').length,\n modified: differences.filter((d) => d.type === 'modified').length,\n };\n\n const parts: string[] = [];\n if (counts.added > 0) parts.push(`${counts.added} added`);\n if (counts.removed > 0) parts.push(`${counts.removed} removed`);\n if (counts.modified > 0) parts.push(`${counts.modified} modified`);\n\n const summary = parts.join(', ');\n return `${summary}`;\n }\n}\n\n// Export singleton instance\nexport const abiComparisonService = new AbiComparisonService();\n","/**\n * EVM-specific ABI types for comparison and validation\n * Uses viem's Abi type as the foundation for type safety\n */\n\nimport type { Abi } from 'viem';\n\n/**\n * Result of comparing two ABIs\n */\nexport interface AbiComparisonResult {\n /** Whether the ABIs are identical after normalization */\n identical: boolean;\n /** List of differences found between the ABIs */\n differences: AbiDifference[];\n /** Overall severity of the changes */\n severity: 'none' | 'minor' | 'major' | 'breaking';\n /** Human-readable summary of the comparison */\n summary: string;\n}\n\n/**\n * Represents a single difference between two ABIs\n */\nexport interface AbiDifference {\n /** Type of change */\n type: 'added' | 'removed' | 'modified';\n /** Which section of the ABI was affected */\n section: 'function' | 'event' | 'constructor' | 'error' | 'fallback' | 'receive';\n /** Name of the affected item (or type if no name) */\n name: string;\n /** Detailed description of the change */\n details: string;\n /** Impact level of this change */\n impact: 'low' | 'medium' | 'high';\n /** Signature before the change (for removed/modified) */\n oldSignature?: string;\n /** Signature after the change (for added/modified) */\n newSignature?: string;\n}\n\n/**\n * Result of validating an ABI structure\n */\nexport interface AbiValidationResult {\n /** Whether the ABI is structurally valid */\n valid: boolean;\n /** List of validation errors found */\n errors: string[];\n /** List of validation warnings */\n warnings: string[];\n /** Normalized ABI if validation passed */\n normalizedAbi?: Abi;\n}\n\n/**\n * Type guard to check if a value is a valid ABI array\n */\nexport function isValidAbiArray(value: unknown): value is Abi {\n return Array.isArray(value) && value.every(isValidAbiItem);\n}\n\n/**\n * Type guard to check if a value is a valid ABI item\n */\nexport function isValidAbiItem(item: unknown): boolean {\n if (typeof item !== 'object' || item === null) {\n return false;\n }\n\n const abiItem = item as Record<string, unknown>;\n\n // Must have a valid type\n if (typeof abiItem.type !== 'string') {\n return false;\n }\n\n const validTypes = ['function', 'event', 'constructor', 'error', 'fallback', 'receive'];\n if (!validTypes.includes(abiItem.type)) {\n return false;\n }\n\n // Functions and events must have a name\n if (\n (abiItem.type === 'function' || abiItem.type === 'event') &&\n typeof abiItem.name !== 'string'\n ) {\n return false;\n }\n\n // Functions, events, and constructors should have inputs array\n if (\n (abiItem.type === 'function' || abiItem.type === 'event' || abiItem.type === 'constructor') &&\n abiItem.inputs !== undefined &&\n !Array.isArray(abiItem.inputs)\n ) {\n return false;\n }\n\n return true;\n}\n","/**\n * EVM Contract Definition Provider keys and ordering\n * Avoid magic strings by using typed constants and a union type.\n */\n\nexport const EvmProviderKeys = {\n Etherscan: 'etherscan',\n Sourcify: 'sourcify',\n} as const;\n\nexport type EvmContractDefinitionProviderKey =\n (typeof EvmProviderKeys)[keyof typeof EvmProviderKeys];\n\nexport const EVM_PROVIDER_ORDER_DEFAULT: readonly EvmContractDefinitionProviderKey[] = [\n EvmProviderKeys.Etherscan,\n EvmProviderKeys.Sourcify,\n] as const;\n\nexport function isEvmProviderKey(value: unknown): value is EvmContractDefinitionProviderKey {\n return value === EvmProviderKeys.Etherscan || value === EvmProviderKeys.Sourcify;\n}\n","import { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { createConfig, http } from '@wagmi/core';\nimport { mainnet } from 'viem/chains';\nimport { WagmiProvider } from 'wagmi';\nimport React, { useEffect, useMemo, useState } from 'react';\n\nimport type { EcosystemReactUiProviderProps } from '@openzeppelin/contracts-ui-builder-types';\nimport { logger } from '@openzeppelin/contracts-ui-builder-utils';\n\nimport { WagmiProviderInitializedContext } from '../context/wagmi-context';\nimport { evmUiKitManager, type EvmUiKitManagerState } from '../evmUiKitManager';\nimport type { RainbowKitKitConfig, RainbowKitProviderProps } from '../rainbowkit';\n\n// Create a single QueryClient instance to be reused by EvmWalletUiRoot instances.\n// This should be stable across re-renders of EvmWalletUiRoot itself.\nconst stableQueryClient = new QueryClient();\n\n// Create a minimal, default WagmiConfig to use when no other config is ready.\n// This ensures WagmiProvider can always be mounted with a valid config object.\n// Uses mainnet as a minimal default chain with HTTP transport.\nconst minimalDefaultWagmiConfig = createConfig({\n chains: [mainnet], // At least one chain is required in wagmi v2.20+\n connectors: [], // Empty connectors array\n transports: {\n [mainnet.id]: http(), // Basic HTTP transport for the default chain\n },\n});\n\nexport const EvmWalletUiRoot: React.FC<EcosystemReactUiProviderProps> = ({ children }) => {\n const [managerState, setManagerState] = useState<EvmUiKitManagerState>(\n evmUiKitManager.getState()\n );\n\n useEffect(() => {\n const handleStateChange = () => {\n setManagerState(evmUiKitManager.getState());\n };\n const unsubscribe = evmUiKitManager.subscribe(handleStateChange);\n handleStateChange();\n return unsubscribe;\n }, []); // Kept empty dep array as per previous working state of subscription\n\n // Memoize QueryClient to ensure stability if we ever decide to make it configurable per instance\n const queryClient = useMemo(() => stableQueryClient, []);\n\n const {\n wagmiConfig,\n kitProviderComponent,\n isKitAssetsLoaded,\n currentFullUiKitConfig,\n isInitializing,\n error,\n } = managerState;\n\n const configForWagmiProvider = wagmiConfig || minimalDefaultWagmiConfig;\n const isWagmiContextEffectivelyReady = !!wagmiConfig && !error;\n\n let finalChildren = children;\n\n // TODO: If many UI kits are added, and each requires a distinct way of being\n // rendered as a provider around `children` (beyond just passing different providerProps),\n // this conditional logic might become complex. Consider a strategy pattern or a\n // more abstract way to obtain the fully composed `innerContent` based on kitName.\n // For n